Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01b2613efe | ||
|
|
50fc13b30c | ||
|
|
b7dc248f93 | ||
|
|
8b05204047 | ||
|
|
18680f2847 | ||
|
|
6c790299aa | ||
|
|
65d662588e | ||
|
|
ab7acb5db3 | ||
|
|
dde0f5cd10 | ||
|
|
5d70217961 | ||
|
|
a0f0a446de | ||
|
|
cd6f3862f6 | ||
|
|
0f4eb8398a | ||
|
|
c9a3f48e5a | ||
|
|
a7f67961e7 | ||
|
|
32209534a0 | ||
|
|
7196f0f970 | ||
|
|
64d0be5ffa | ||
|
|
31be5baf3d | ||
|
|
4781fd515b | ||
|
|
11497f52d4 | ||
|
|
fa934f06ab | ||
|
|
3d78e01559 | ||
|
|
65f9438639 | ||
|
|
6ffa80c386 | ||
|
|
8eb7b18089 | ||
|
|
9041441c46 | ||
|
|
502a4b1cc3 | ||
|
|
ce13648075 | ||
|
|
9644557a9e | ||
|
|
01964add79 | ||
|
|
822e771f45 | ||
|
|
c0888fb938 | ||
|
|
5cdb3bee21 | ||
|
|
4cce6c1c21 | ||
|
|
b928e294d1 | ||
|
|
ec3aa17f65 | ||
|
|
851d77de8e | ||
|
|
137fdaca6a | ||
|
|
7371f4e87f | ||
|
|
6529eb6b61 | ||
|
|
729af56d1b | ||
|
|
9f4a600eba | ||
|
|
ddb2528ecf | ||
|
|
eb5aeb4595 | ||
|
|
b93bff5cad | ||
|
|
3ae891c62e | ||
|
|
48bd060d6e | ||
|
|
5dee64ab7b | ||
|
|
a2f66493ea | ||
|
|
37a0103699 | ||
|
|
2dabf8955d | ||
|
|
9ab471fb63 | ||
|
|
801fd8bb21 | ||
|
|
5e9c780d31 | ||
|
|
bbaaaf7ae6 | ||
|
|
befd0974f3 | ||
|
|
250c317155 | ||
|
|
2c148eb0c0 | ||
|
|
ead8010bd1 | ||
|
|
0962159523 | ||
|
|
cbb4ccd4b7 | ||
|
|
41e73e4fd1 | ||
|
|
86a6ea5a26 | ||
|
|
ce5af5ddde | ||
|
|
0d193cd235 | ||
|
|
f06d755899 | ||
|
|
4dcf2f5870 | ||
|
|
c2179ccd63 | ||
|
|
94445bceb1 | ||
|
|
7b49d85804 | ||
|
|
4190aa20a6 | ||
|
|
5a832198b4 | ||
|
|
2066bb2ae8 | ||
|
|
5d64c97406 | ||
|
|
d648c6d4b4 | ||
|
|
435bfa3b3f | ||
|
|
4d0135d7b7 | ||
|
|
5975928e89 | ||
|
|
0f8d2937eb | ||
|
|
53fcc51541 | ||
|
|
23b60c73a0 | ||
|
|
65321991c6 | ||
|
|
9d56014ca0 | ||
|
|
7b2493a838 | ||
|
|
1d4db6493d | ||
|
|
c4bfb44f72 | ||
|
|
d9e7ae1380 | ||
|
|
47b10d45d2 | ||
|
|
ffd27acedc | ||
|
|
841b8a6439 | ||
|
|
611d2b22d2 | ||
|
|
4bad105119 | ||
|
|
c7b3f89f48 | ||
|
|
1dd0852e54 | ||
|
|
c1bb1002f5 | ||
|
|
367146b9ad | ||
|
|
1f1cdf6fe8 | ||
|
|
84b355951f | ||
|
|
bfb82e38ab | ||
|
|
ce3e98e240 | ||
|
|
36192ba560 | ||
|
|
0e262dc2bd | ||
|
|
c1fea7c92f | ||
|
|
7ba0067688 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://paypal.me/froodleplex?country.x=GB&locale.x=en_GB'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
custom: ['https://www.paypal.com/donate/?hosted_button_id=MN7JPG5G6G3JL'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -3,14 +3,8 @@ name: "Build repo"
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -36,7 +30,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-build-cache
|
||||
|
||||
2
.github/workflows/push-docker.yml
vendored
2
.github/workflows/push-docker.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: ./gradlew clean build
|
||||
|
||||
2
.github/workflows/releaseArtifacts.yml
vendored
2
.github/workflows/releaseArtifacts.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||
run: ./gradlew clean createExe
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -32,6 +32,15 @@ jobs:
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
# sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Pip requirements
|
||||
run: |
|
||||
pip install -r ./cucumber/requirements.txt
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./test.sh
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -125,3 +125,6 @@ watchedFolders/
|
||||
# Ignore Mac DS_Store files
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
#cucumber
|
||||
/cucumber/reports/**
|
||||
@@ -29,7 +29,7 @@ If you would like to add or modify a translation, please see [How to add new lan
|
||||
|
||||
## Docs
|
||||
|
||||
Documentation for Stirling-PDF is handled in a seperate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||
|
||||
## Fixing Bugs or Adding a New Feature
|
||||
|
||||
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,5 +1,5 @@
|
||||
# Main stage
|
||||
FROM alpine:20240329
|
||||
FROM alpine:3.20.0
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -10,7 +10,6 @@ COPY build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
|
||||
# Set Environment Variables
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
@@ -20,25 +19,24 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
apk update && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
bash \
|
||||
curl \
|
||||
openjdk21-jre \
|
||||
su-exec \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
libreoffice@testing \
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
@@ -60,10 +58,9 @@ openssl-dev \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs && \
|
||||
rm -rf /var/cache/apk/*
|
||||
tesseract --list-langs
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use alpine
|
||||
FROM alpine:3.19.1
|
||||
FROM alpine:3.20.0
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -18,24 +18,23 @@ COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
|
||||
# Set up necessary directories and permissions
|
||||
|
||||
RUN mkdir /configs /logs /customFiles && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
su-exec \
|
||||
shadow \
|
||||
su-exec \
|
||||
openjdk21-jre && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
# User permissions
|
||||
mkdir /configs /logs /customFiles && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
@@ -43,9 +42,8 @@ RUN mkdir /configs /logs /customFiles && \
|
||||
# Set environment variables
|
||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
|
||||
52
README.md
52
README.md
@@ -5,7 +5,7 @@
|
||||
[](https://discord.gg/Cn8pWhQRxZ)
|
||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||
[](https://www.paypal.com/paypalme/froodleplex)
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=MN7JPG5G6G3JL)
|
||||
[](https://github.com/sponsors/Frooodle)
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
||||
@@ -159,38 +159,39 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Stirling PDF currently supports 27!
|
||||
Stirling PDF currently supports 28!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
@@ -226,7 +227,7 @@ security:
|
||||
loginAttemptCount: 5 # lock user account after 5 tries
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
# initialLogin:
|
||||
# username: "admin" # Initial username for the first login (these are defaulted)
|
||||
# username: "admin" # Initial username for the first login
|
||||
# password: "stirling" # Initial password for the first login
|
||||
# oauth2:
|
||||
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||
@@ -237,6 +238,23 @@ security:
|
||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
# client:
|
||||
# google:
|
||||
# clientId: "" # Client ID for Google OAuth2
|
||||
# clientSecret: "" # Client Secret for Google OAuth2
|
||||
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||
# github:
|
||||
# clientId: "" # Client ID for GitHub OAuth2
|
||||
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||
# keycloak:
|
||||
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
|
||||
10
build.gradle
10
build.gradle
@@ -12,7 +12,7 @@ plugins {
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.24.4'
|
||||
version = '0.25.0'
|
||||
|
||||
//17 is lowest but we support and recommend 21
|
||||
sourceCompatibility = '17'
|
||||
@@ -94,7 +94,13 @@ dependencies {
|
||||
implementation("io.github.pixee:java-security-toolkit:1.1.3")
|
||||
|
||||
implementation 'org.yaml:snakeyaml:2.2'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4'
|
||||
|
||||
// Exclude Tomcat and include Jetty
|
||||
implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
|
||||
|
||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.24.4
|
||||
appVersion: 0.25.0
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
|
||||
BIN
cucumber/exampleFiles/example.docx
Normal file
BIN
cucumber/exampleFiles/example.docx
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odp
Normal file
BIN
cucumber/exampleFiles/example.odp
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odt
Normal file
BIN
cucumber/exampleFiles/example.odt
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.pptx
Normal file
BIN
cucumber/exampleFiles/example.pptx
Normal file
Binary file not shown.
158
cucumber/exampleFiles/example.rtf
Normal file
158
cucumber/exampleFiles/example.rtf
Normal file
@@ -0,0 +1,158 @@
|
||||
{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67;
|
||||
\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font;
|
||||
}{\*\ts11\tsrowd\snext11\ssemihidden\spriority0\aspalpha\aspnum\adjustright\ltrpar\li0\lin0\ri0\rin0\ql\faauto\tsvertalt\tsbrdrl\tsbrdrr\tsbrdrt\tsbrdrb\tsbrdrdgr\tsbrdrdgl\tsbrdrh\tsbrdrv\trpaddl108\trpaddfl3\trwWidthB0\trftsWidthB3\trpaddt0\trpaddft3\trpaddb0
|
||||
\trpaddfb3\trpaddr108\trpaddfr3 Normal Table;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2
|
||||
\dbch\af2\hich\f2\strike0\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062\rsid13249109}{\*\generator Aspose.Words for Java 23.4.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0{
|
||||
\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{
|
||||
\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc
|
||||
\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\utinl\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\dgmargin\pgbrdrhead\pgbrdrfoot\rsidroot10976062\sectd\sectlinegrid360\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery720\footery720\colsx720\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2\dbch\af2
|
||||
\hich\f2\strike0\ulnone\cf1 A}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar
|
||||
\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2
|
||||
\dbch\af2\hich\f2\strike0\ulnone\cf1 B}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard
|
||||
\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 C}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{
|
||||
\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing;
|
||||
\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2;
|
||||
\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1;
|
||||
\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1;
|
||||
\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1;
|
||||
\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2;
|
||||
\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2;
|
||||
\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3;
|
||||
\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3;
|
||||
\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4;
|
||||
\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4;
|
||||
\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5;
|
||||
\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5;
|
||||
\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6;
|
||||
\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6;
|
||||
\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}}
|
||||
16
cucumber/features/environment.py
Normal file
16
cucumber/features/environment.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
|
||||
def before_all(context):
|
||||
context.endpoint = None
|
||||
context.request_data = None
|
||||
context.files = {}
|
||||
context.response = None
|
||||
|
||||
def after_scenario(context, scenario):
|
||||
if hasattr(context, 'files'):
|
||||
for file in context.files.values():
|
||||
file.close()
|
||||
if os.path.exists('response_file'):
|
||||
os.remove('response_file')
|
||||
if hasattr(context, 'file_name') and os.path.exists(context.file_name):
|
||||
os.remove(context.file_name)
|
||||
130
cucumber/features/examples.feature
Normal file
130
cucumber/features/examples.feature
Normal file
@@ -0,0 +1,130 @@
|
||||
@example
|
||||
Feature: API Validation
|
||||
|
||||
@positive @password
|
||||
Scenario: Remove password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the pdf is encrypted with password "password123"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | password123 |
|
||||
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF is not passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@negative @password
|
||||
Scenario: Remove password wrong password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the pdf is encrypted with password "password123"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | wrongPassword |
|
||||
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||
Then the response status code should be 500
|
||||
And the response should contain error message "Internal Server Error"
|
||||
|
||||
@positive @info
|
||||
Scenario: Get info
|
||||
Given I generate a PDF file as "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/security/get-info-on-pdf"
|
||||
Then the response content type should be "application/json"
|
||||
And the response file should have size greater than 100
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @password
|
||||
Scenario: Add password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | password123 |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response PDF is passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @password
|
||||
Scenario: Add password with other params
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| ownerPassword | ownerPass |
|
||||
| password | password123 |
|
||||
| keyLength | 256 |
|
||||
| canPrint | true |
|
||||
| canModify | false |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response PDF is passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @watermark
|
||||
Scenario: Add watermark
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| watermarkType | text |
|
||||
| watermarkText | Sample Watermark |
|
||||
| fontSize | 30 |
|
||||
| rotation | 45 |
|
||||
| opacity | 0.5 |
|
||||
| widthSpacer | 50 |
|
||||
| heightSpacer | 50 |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-watermark"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response status code should be 200
|
||||
|
||||
@positive
|
||||
Scenario: Remove blank pages
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 blank pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| threshold | 90 |
|
||||
| whitePercent | 99.9 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/remove-blanks"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF should contain 0 pages
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @flatten
|
||||
Scenario: Flatten PDF
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| flattenOnlyForms | false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/flatten"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @metadata
|
||||
Scenario: Update metadata
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| author | John Doe |
|
||||
| title | Sample Title |
|
||||
| subject | Sample Subject |
|
||||
| keywords | sample, test |
|
||||
| producer | Test Producer |
|
||||
When I send the API request to the endpoint "/api/v1/misc/update-metadata"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF metadata should include "Author" as "John Doe"
|
||||
And the response PDF metadata should include "Keywords" as "sample, test"
|
||||
And the response PDF metadata should include "Subject" as "Sample Subject"
|
||||
And the response PDF metadata should include "Title" as "Sample Title"
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
228
cucumber/features/external.feature
Normal file
228
cucumber/features/external.feature
Normal file
@@ -0,0 +1,228 @@
|
||||
Feature: API Validation
|
||||
|
||||
|
||||
@libre @positive
|
||||
Scenario: Repair PDF
|
||||
Given I generate a PDF file as "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/misc/repair"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Normal |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Extract Image Scans
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 images on 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| angleThreshold | 5 |
|
||||
| tolerance | 20 |
|
||||
| minArea | 8000 |
|
||||
| minContourArea | 500 |
|
||||
| borderSize | 1 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/extract-image-scans"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response file should have extension ".zip"
|
||||
And the response ZIP should contain 2 files
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
|
||||
@ocr @negative
|
||||
Scenario: Process PDF with text and OCR with type normal
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Normal |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response status code should be 500
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Force |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR with sidecar
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | true |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Force |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response file should have extension ".zip"
|
||||
And the response ZIP should contain 2 files
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@libre @positive
|
||||
Scenario Outline: Convert PDF to various word formats
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | <format> |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/word"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension "<extension>"
|
||||
|
||||
Examples:
|
||||
| format | extension |
|
||||
| docx | .docx |
|
||||
| odt | .odt |
|
||||
| doc | .doc |
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA1
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa-1 |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 4 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
| expectedOutputSize | 5KB |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
| expectedOutputSize | 5KB |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@libre @positive
|
||||
Scenario Outline: Convert PDF to various types
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | <format> |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/<type>"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension "<extension>"
|
||||
|
||||
Examples:
|
||||
| type | format | extension |
|
||||
| text | rtf | .rtf |
|
||||
| text | txt | .txt |
|
||||
| presentation | ppt | .ppt |
|
||||
| presentation | pptx | .pptx |
|
||||
| presentation | odp | .odp |
|
||||
| html | html | .zip |
|
||||
|
||||
|
||||
@libre @positive @topdf
|
||||
Scenario Outline: Convert PDF to various types
|
||||
Given I use an example file at "exampleFiles/example<extension>" as parameter "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/convert/file/pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension ".pdf"
|
||||
|
||||
Examples:
|
||||
| extension |
|
||||
| .docx |
|
||||
| .odp |
|
||||
| .odt |
|
||||
| .pptx |
|
||||
| .rtf |
|
||||
|
||||
|
||||
|
||||
96
cucumber/features/general.feature
Normal file
96
cucumber/features/general.feature
Normal file
@@ -0,0 +1,96 @@
|
||||
@general
|
||||
Feature: API Validation
|
||||
|
||||
|
||||
@split-pdf-by-sections @positive
|
||||
Scenario Outline: split-pdf-by-sections with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| horizontalDivisions | <horizontalDivisions> |
|
||||
| verticalDivisions | <verticalDivisions> |
|
||||
| merge | true |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 200
|
||||
And the response status code should be 200
|
||||
And the response PDF should contain <page_count> pages
|
||||
|
||||
Examples:
|
||||
| horizontalDivisions | verticalDivisions | page_count |
|
||||
| 0 | 1 | 4 |
|
||||
| 1 | 1 | 8 |
|
||||
| 1 | 2 | 12 |
|
||||
| 2 | 2 | 18 |
|
||||
|
||||
@split-pdf-by-sections @positive
|
||||
Scenario Outline: split-pdf-by-sections with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| horizontalDivisions | <horizontalDivisions> |
|
||||
| verticalDivisions | <verticalDivisions> |
|
||||
| merge | true |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 200
|
||||
And the response status code should be 200
|
||||
And the response PDF should contain <page_count> pages
|
||||
|
||||
Examples:
|
||||
| horizontalDivisions | verticalDivisions | page_count |
|
||||
| 0 | 1 | 4 |
|
||||
| 1 | 1 | 8 |
|
||||
| 1 | 2 | 12 |
|
||||
| 2 | 2 | 18 |
|
||||
|
||||
|
||||
|
||||
@split-pdf-by-pages @positive
|
||||
Scenario Outline: split-pdf-by-pages with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 20 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| fileInput | fileInput |
|
||||
| pageNumbers | <pageNumbers> |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pages"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response status code should be 200
|
||||
And the response file should have size greater than 200
|
||||
And the response ZIP should contain <file_count> files
|
||||
|
||||
Examples:
|
||||
| pageNumbers | file_count |
|
||||
| 1,3,5-9 | 8 |
|
||||
| all | 20 |
|
||||
| 2n+1 | 11 |
|
||||
| 3n | 7 |
|
||||
|
||||
|
||||
|
||||
@split-pdf-by-size-or-count @positive
|
||||
Scenario Outline: split-pdf-by-size-or-count with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 20 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| fileInput | fileInput |
|
||||
| splitType | <splitType> |
|
||||
| splitValue | <splitValue> |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-by-size-or-count"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response status code should be 200
|
||||
And the response file should have size greater than 200
|
||||
And the response ZIP file should contain <doc_count> documents each having <pages_per_doc> pages
|
||||
|
||||
Examples:
|
||||
| splitType | splitValue | doc_count | pages_per_doc |
|
||||
| 1 | 5 | 4 | 5 |
|
||||
| 2 | 2 | 2 | 10 |
|
||||
| 2 | 4 | 4 | 5 |
|
||||
| 1 | 10 | 2 | 10 |
|
||||
|
||||
|
||||
307
cucumber/features/steps/step_definitions.py
Normal file
307
cucumber/features/steps/step_definitions.py
Normal file
@@ -0,0 +1,307 @@
|
||||
import os
|
||||
import requests
|
||||
from behave import given, when, then
|
||||
from PyPDF2 import PdfWriter, PdfReader
|
||||
import io
|
||||
import random
|
||||
import string
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.pdfgen import canvas
|
||||
import mimetypes
|
||||
import requests
|
||||
import zipfile
|
||||
import shutil
|
||||
|
||||
#########
|
||||
# GIVEN #
|
||||
#########
|
||||
|
||||
@given('I generate a PDF file as "{fileInput}"')
|
||||
def step_generate_pdf(context, fileInput):
|
||||
context.param_name = fileInput
|
||||
context.file_name = "genericNonCustomisableName.pdf"
|
||||
writer = PdfWriter()
|
||||
writer.add_blank_page(width=72, height=72) # Single blank page
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
@given('I use an example file at "{filePath}" as parameter "{fileInput}"')
|
||||
def step_use_example_file(context, filePath, fileInput):
|
||||
context.param_name = fileInput
|
||||
context.file_name = filePath.split('/')[-1]
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
|
||||
# Ensure the file exists before opening
|
||||
try:
|
||||
example_file = open(filePath, 'rb')
|
||||
context.files[context.param_name] = example_file
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"The example file '{filePath}' does not exist.")
|
||||
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages')
|
||||
def step_pdf_contains_pages(context, page_count):
|
||||
writer = PdfWriter()
|
||||
for i in range(page_count):
|
||||
writer.add_blank_page(width=72, height=72)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
# Duplicate for now...
|
||||
@given('the pdf contains {page_count:d} blank pages')
|
||||
def step_pdf_contains_blank_pages(context, page_count):
|
||||
writer = PdfWriter()
|
||||
for i in range(page_count):
|
||||
writer.add_blank_page(width=72, height=72)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
|
||||
def create_black_box_image(file_name, size):
|
||||
can = canvas.Canvas(file_name, pagesize=size)
|
||||
width, height = size
|
||||
can.setFillColorRGB(0, 0, 0)
|
||||
can.rect(0, 0, width, height, fill=1)
|
||||
can.showPage()
|
||||
can.save()
|
||||
|
||||
def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||
page_width, page_height = letter
|
||||
box_size = 72 # 1 inch by 1 inch black box
|
||||
boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0)
|
||||
|
||||
writer = PdfWriter()
|
||||
box_counter = 0
|
||||
|
||||
for page in range(page_count):
|
||||
packet = io.BytesIO()
|
||||
can = canvas.Canvas(packet, pagesize=letter)
|
||||
|
||||
for i in range(boxes_per_page):
|
||||
if box_counter >= image_count:
|
||||
break
|
||||
x = (i % (page_width // box_size)) * box_size
|
||||
y = page_height - ((i // (page_width // box_size) + 1) * box_size)
|
||||
can.setFillColorRGB(0, 0, 0)
|
||||
can.rect(x, y, box_size, box_size, fill=1)
|
||||
box_counter += 1
|
||||
|
||||
can.showPage()
|
||||
can.save()
|
||||
packet.seek(0)
|
||||
new_pdf = PdfReader(packet)
|
||||
writer.add_page(new_pdf.pages[0])
|
||||
|
||||
with open(file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
|
||||
@given('the pdf contains {image_count:d} images on {page_count:d} pages')
|
||||
def step_pdf_contains_images(context, image_count, page_count):
|
||||
if not hasattr(context, 'param_name'):
|
||||
context.param_name = "default"
|
||||
context.file_name = "genericNonCustomisableName.pdf"
|
||||
create_pdf_with_black_boxes(context.file_name, image_count, page_count)
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
if context.param_name in context.files:
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages with random text')
|
||||
def step_pdf_contains_pages_with_random_text(context, page_count):
|
||||
buffer = io.BytesIO()
|
||||
c = canvas.Canvas(buffer, pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
for _ in range(page_count):
|
||||
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
|
||||
c.drawString(100, height - 100, text)
|
||||
c.showPage()
|
||||
|
||||
c.save()
|
||||
|
||||
with open(context.file_name, 'wb') as f:
|
||||
f.write(buffer.getvalue())
|
||||
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the pdf pages all contain the text "{text}"')
|
||||
def step_pdf_pages_contain_text(context, text):
|
||||
buffer = io.BytesIO()
|
||||
c = canvas.Canvas(buffer, pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
for _ in range(len(PdfReader(context.file_name).pages)):
|
||||
c.drawString(100, height - 100, text)
|
||||
c.showPage()
|
||||
|
||||
c.save()
|
||||
|
||||
with open(context.file_name, 'wb') as f:
|
||||
f.write(buffer.getvalue())
|
||||
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the pdf is encrypted with password "{password}"')
|
||||
def step_encrypt_pdf(context, password):
|
||||
writer = PdfWriter()
|
||||
reader = PdfReader(context.file_name)
|
||||
for i in range(len(reader.pages)):
|
||||
writer.add_page(reader.pages[i])
|
||||
writer.encrypt(password)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the request data is')
|
||||
def step_request_data(context):
|
||||
context.request_data = eval(context.text)
|
||||
|
||||
@given('the request data includes')
|
||||
def step_request_data_table(context):
|
||||
context.request_data = {row['parameter']: row['value'] for row in context.table}
|
||||
|
||||
@given('save the generated PDF file as "{filename}" for debugging')
|
||||
def save_generated_pdf(context, filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(context.files[context.param_name].read())
|
||||
print(f"Saved generated PDF content to {filename}")
|
||||
|
||||
########
|
||||
# WHEN #
|
||||
########
|
||||
|
||||
@when('I send the API request to the endpoint "{endpoint}"')
|
||||
def step_send_api_request(context, endpoint):
|
||||
url = f"http://localhost:8080{endpoint}"
|
||||
files = context.files if hasattr(context, 'files') else {}
|
||||
|
||||
if not hasattr(context, 'request_data') or context.request_data is None:
|
||||
context.request_data = {}
|
||||
|
||||
form_data = []
|
||||
for key, value in context.request_data.items():
|
||||
form_data.append((key, (None, value)))
|
||||
|
||||
for key, file in files.items():
|
||||
mime_type, _ = mimetypes.guess_type(file.name)
|
||||
mime_type = mime_type or 'application/octet-stream'
|
||||
print(f"form_data {file.name} with {mime_type}")
|
||||
form_data.append((key, (file.name, file, mime_type)))
|
||||
|
||||
response = requests.post(url, files=form_data)
|
||||
context.response = response
|
||||
|
||||
########
|
||||
# THEN #
|
||||
########
|
||||
|
||||
@then('the response content type should be "{content_type}"')
|
||||
def step_check_response_content_type(context, content_type):
|
||||
actual_content_type = context.response.headers.get('Content-Type', '')
|
||||
assert actual_content_type.startswith(content_type), f"Expected {content_type} but got {actual_content_type}. Response content: {context.response.content}"
|
||||
|
||||
@then('the response file should have size greater than {size:d}')
|
||||
def step_check_response_file_size(context, size):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
assert len(response_file.getvalue()) > size
|
||||
|
||||
@then('the response PDF is not passworded')
|
||||
def step_check_response_pdf_not_passworded(context):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
assert not reader.is_encrypted
|
||||
|
||||
@then('the response PDF is passworded')
|
||||
def step_check_response_pdf_passworded(context):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
try:
|
||||
reader = PdfReader(response_file)
|
||||
assert reader.is_encrypted
|
||||
except PdfReadError as e:
|
||||
raise AssertionError(f"Failed to read PDF: {str(e)}. Response content: {context.response.content}")
|
||||
except Exception as e:
|
||||
raise AssertionError(f"An error occurred: {str(e)}. Response content: {context.response.content}")
|
||||
|
||||
@then('the response status code should be {status_code:d}')
|
||||
def step_check_response_status_code(context, status_code):
|
||||
assert context.response.status_code == status_code, f"Expected status code {status_code} but got {context.response.status_code}"
|
||||
|
||||
@then('the response should contain error message "{message}"')
|
||||
def step_check_response_error_message(context, message):
|
||||
response_json = context.response.json()
|
||||
assert response_json.get('error') == message, f"Expected error message '{message}' but got '{response_json.get('error')}'"
|
||||
|
||||
@then('the response PDF should contain {page_count:d} pages')
|
||||
def step_check_response_pdf_page_count(context, page_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
assert len(reader.pages) == page_count, f"Expected {page_count} pages but got {len(reader.pages)} pages"
|
||||
|
||||
@then('the response PDF metadata should include "{metadata_key}" as "{metadata_value}"')
|
||||
def step_check_response_pdf_metadata(context, metadata_key, metadata_value):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
metadata = reader.metadata
|
||||
assert metadata.get("/" + metadata_key) == metadata_value, f"Expected {metadata_key} to be '{metadata_value}' but got '{metadata.get(metadata_key)}'"
|
||||
|
||||
@then('the response file should have extension "{extension}"')
|
||||
def step_check_response_file_extension(context, extension):
|
||||
content_disposition = context.response.headers.get('Content-Disposition', '')
|
||||
filename = ""
|
||||
if content_disposition:
|
||||
parts = content_disposition.split(';')
|
||||
for part in parts:
|
||||
if part.strip().startswith('filename'):
|
||||
filename = part.split('=')[1].strip().strip('"')
|
||||
break
|
||||
assert filename.endswith(extension), f"Expected file extension {extension} but got {filename}. Response content: {context.response.content}"
|
||||
|
||||
@then('save the response file as "{filename}" for debugging')
|
||||
def step_save_response_file(context, filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(context.response.content)
|
||||
print(f"Saved response content to {filename}")
|
||||
|
||||
|
||||
@then('the response PDF should contain {page_count:d} pages')
|
||||
def step_check_response_pdf_page_count(context, page_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(io.BytesIO(response_file.getvalue()))
|
||||
actual_page_count = len(reader.pages)
|
||||
assert actual_page_count == page_count, f"Expected {page_count} pages but got {actual_page_count} pages"
|
||||
|
||||
@then('the response ZIP should contain {file_count:d} files')
|
||||
def step_check_response_zip_file_count(context, file_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||
actual_file_count = len(zip_file.namelist())
|
||||
assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files"
|
||||
|
||||
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
|
||||
def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||
actual_doc_count = len(zip_file.namelist())
|
||||
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
|
||||
|
||||
for file_name in zip_file.namelist():
|
||||
with zip_file.open(file_name) as pdf_file:
|
||||
reader = PdfReader(pdf_file)
|
||||
actual_pages_per_doc = len(reader.pages)
|
||||
assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}"
|
||||
5
cucumber/requirements.txt
Normal file
5
cucumber/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
behave
|
||||
requests
|
||||
PyPDF2
|
||||
reportlab
|
||||
PyCryptodome
|
||||
@@ -22,10 +22,11 @@ services:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
SECURITY_OAUTH2_ENABLED: "true"
|
||||
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Striling-PDF
|
||||
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF
|
||||
SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
SECURITY_OAUTH2_CLIENTID: "<YOUR CLIENT ID>.apps.googleusercontent.com" # Client ID from your provider
|
||||
SECURITY_OAUTH2_CLIENTSECRET: "<YOUR CLIENT SECRET>" # Client Secret from your provider
|
||||
SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 166 KiB |
@@ -13,6 +13,11 @@ ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[cs_CZ]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[de_DE]
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
|
||||
@@ -8,11 +8,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -62,7 +58,7 @@ public class ConfigInitializer
|
||||
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
||||
|
||||
List<String> resultLines = new ArrayList<>();
|
||||
|
||||
int position = 0;
|
||||
for (String templateLine : templateLines) {
|
||||
// Check if the line is a comment
|
||||
if (templateLine.trim().startsWith("#")) {
|
||||
@@ -70,7 +66,7 @@ public class ConfigInitializer
|
||||
if (!entry.isEmpty()) {
|
||||
// Check if this comment has been uncommented in userLines
|
||||
String key = entry.split(":")[0].trim();
|
||||
addLine(resultLines, userLines, templateLine, key);
|
||||
addLine(resultLines, userLines, templateLine, key, position);
|
||||
} else {
|
||||
resultLines.add(templateLine);
|
||||
}
|
||||
@@ -78,12 +74,13 @@ public class ConfigInitializer
|
||||
// Check if the line is a key-value pair
|
||||
else if (templateLine.contains(":")) {
|
||||
String key = templateLine.split(":")[0].trim();
|
||||
addLine(resultLines, userLines, templateLine, key);
|
||||
addLine(resultLines, userLines, templateLine, key, position);
|
||||
}
|
||||
// Handle empty lines
|
||||
else if (templateLine.trim().length() == 0) {
|
||||
resultLines.add("");
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
// Write the result to the user settings file
|
||||
@@ -96,14 +93,18 @@ public class ConfigInitializer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
||||
private static void addLine(List<String> resultLines, List<String> userLines, String templateLine, String key) {
|
||||
private static void addLine(
|
||||
List<String> resultLines,
|
||||
List<String> userLines,
|
||||
String templateLine,
|
||||
String key,
|
||||
int position) {
|
||||
boolean added = false;
|
||||
int templateIndentationLevel = getIndentationLevel(templateLine);
|
||||
int pos = 0;
|
||||
for (String settingsLine : userLines) {
|
||||
if (settingsLine.trim().startsWith(key + ":")) {
|
||||
if (settingsLine.trim().startsWith(key + ":") && position == pos) {
|
||||
int settingsIndentationLevel = getIndentationLevel(settingsLine);
|
||||
// Check if it is correct settingsLine and has the same parent as templateLine
|
||||
if (settingsIndentationLevel == templateIndentationLevel) {
|
||||
@@ -112,6 +113,7 @@ public class ConfigInitializer
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (!added) {
|
||||
resultLines.add(templateLine);
|
||||
|
||||
@@ -2,6 +2,8 @@ package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -36,7 +38,11 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationS
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@@ -47,6 +53,8 @@ public class SecurityConfiguration {
|
||||
|
||||
@Autowired private CustomUserDetailsService userDetailsService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
@@ -140,6 +148,7 @@ public class SecurityConfiguration {
|
||||
|| trimmedUri.startsWith("/images/")
|
||||
|| trimmedUri.startsWith("/public/")
|
||||
|| trimmedUri.startsWith("/css/")
|
||||
|| trimmedUri.startsWith("/fonts/")
|
||||
|| trimmedUri.startsWith("/js/")
|
||||
|| trimmedUri.startsWith(
|
||||
"/api/v1/info/status");
|
||||
@@ -150,7 +159,8 @@ public class SecurityConfiguration {
|
||||
.authenticationProvider(authenticationProvider());
|
||||
|
||||
// Handle OAUTH2 Logins
|
||||
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||
if (applicationProperties.getSecurity().getOAUTH2() != null
|
||||
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||
|
||||
http.oauth2Login(
|
||||
oauth2 ->
|
||||
@@ -183,7 +193,8 @@ public class SecurityConfiguration {
|
||||
logout.logoutSuccessHandler(
|
||||
new CustomOAuth2LogoutSuccessHandler(
|
||||
this.applicationProperties,
|
||||
sessionRegistry())));
|
||||
sessionRegistry()))
|
||||
.invalidateHttpSession(true));
|
||||
}
|
||||
} else {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
@@ -200,19 +211,127 @@ public class SecurityConfiguration {
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
|
||||
githubClientRegistration().ifPresent(registrations::add);
|
||||
oidcClientRegistration().ifPresent(registrations::add);
|
||||
googleClientRegistration().ifPresent(registrations::add);
|
||||
keycloakClientRegistration().ifPresent(registrations::add);
|
||||
|
||||
if (registrations.isEmpty()) {
|
||||
logger.error("At least one OAuth2 provider must be configured");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private ClientRegistration oidcClientRegistration() {
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> googleClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
return ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GoogleProvider google = client.getGoogle();
|
||||
return google != null && google.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId("google")
|
||||
.clientId(google.getClientId())
|
||||
.clientSecret(google.getClientSecret())
|
||||
.scope(google.getScopes())
|
||||
.authorizationUri(google.getAuthorizationuri())
|
||||
.tokenUri(google.getTokenuri())
|
||||
.userInfoUri(google.getUserinfouri())
|
||||
.userNameAttributeName(google.getUseAsUsername())
|
||||
.clientName("Google")
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/google")
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
|
||||
return keycloak != null && keycloak.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||
.registrationId("keycloak")
|
||||
.clientId(keycloak.getClientId())
|
||||
.clientSecret(keycloak.getClientSecret())
|
||||
.scope(keycloak.getScopes())
|
||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||
.clientName("Keycloak")
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> githubClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GithubProvider github = client.getGithub();
|
||||
return github != null && github.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId("github")
|
||||
.clientId(github.getClientId())
|
||||
.clientSecret(github.getClientSecret())
|
||||
.scope(github.getScopes())
|
||||
.authorizationUri(github.getAuthorizationuri())
|
||||
.tokenUri(github.getTokenuri())
|
||||
.userInfoUri(github.getUserinfouri())
|
||||
.userNameAttributeName(github.getUseAsUsername())
|
||||
.clientName("GitHub")
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/github")
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null
|
||||
|| oauth.getIssuer() == null
|
||||
|| oauth.getIssuer().isEmpty()
|
||||
|| oauth.getClientId() == null
|
||||
|| oauth.getClientId().isEmpty()
|
||||
|| oauth.getClientSecret() == null
|
||||
|| oauth.getClientSecret().isEmpty()
|
||||
|| oauth.getScopes() == null
|
||||
|| oauth.getScopes().isEmpty()
|
||||
|| oauth.getUseAsUsername() == null
|
||||
|| oauth.getUseAsUsername().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(oauth.getClientId())
|
||||
.clientSecret(oauth.getClientSecret())
|
||||
.scope(oauth.getScopes())
|
||||
.userNameAttributeName(oauth.getUseAsUsername())
|
||||
.clientName("OIDC")
|
||||
.build();
|
||||
.build());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -101,6 +101,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
contextPath + "/images/",
|
||||
contextPath + "/public/",
|
||||
contextPath + "/css/",
|
||||
contextPath + "/fonts/",
|
||||
contextPath + "/js/",
|
||||
contextPath + "/pdfjs/",
|
||||
contextPath + "/api/v1/info/status",
|
||||
|
||||
@@ -41,6 +41,7 @@ public class CustomOAuth2AuthenticationFailureHandler
|
||||
} else if (exception instanceof LockedException) {
|
||||
logger.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
} else {
|
||||
logger.error("Unhandled authentication exception", exception);
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -14,6 +15,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
import stirling.software.SPDF.utils.UrlUtils;
|
||||
|
||||
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
@@ -33,11 +36,30 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
public void onLogoutSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String param = "logout=true";
|
||||
String registrationId = null;
|
||||
String issuer = null;
|
||||
String clientId = null;
|
||||
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
String provider = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||
|
||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||
|
||||
try {
|
||||
Provider provider = oauth.getClient().get(registrationId);
|
||||
issuer = provider.getIssuer();
|
||||
clientId = provider.getClientId();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||
issuer = oauth.getIssuer();
|
||||
clientId = oauth.getClientId();
|
||||
}
|
||||
|
||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||
@@ -49,36 +71,46 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
param = "error=oauth2AutoCreateDisabled";
|
||||
}
|
||||
|
||||
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
String sessionId = session.getId();
|
||||
sessionRegistry.removeSessionInformation(sessionId);
|
||||
session.invalidate();
|
||||
logger.debug("Session invalidated: " + sessionId);
|
||||
logger.info("Session invalidated: " + sessionId);
|
||||
}
|
||||
|
||||
switch (provider) {
|
||||
switch (registrationId) {
|
||||
case "keycloak":
|
||||
// Add Keycloak specific logout URL if needed
|
||||
String logoutUrl =
|
||||
oauth.getIssuer()
|
||||
issuer
|
||||
+ "/protocol/openid-connect/logout"
|
||||
+ "?client_id="
|
||||
+ oauth.getClientId()
|
||||
+ clientId
|
||||
+ "&post_logout_redirect_uri="
|
||||
+ response.encodeRedirectURL(
|
||||
request.getScheme()
|
||||
+ "://"
|
||||
+ request.getHeader("host")
|
||||
+ "/login?"
|
||||
+ param);
|
||||
logger.debug("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||
+ response.encodeRedirectURL(redirect_url);
|
||||
logger.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||
response.sendRedirect(logoutUrl);
|
||||
break;
|
||||
case "github":
|
||||
// Add GitHub specific logout URL if needed
|
||||
String githubLogoutUrl = "https://github.com/logout";
|
||||
logger.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||
response.sendRedirect(githubLogoutUrl);
|
||||
break;
|
||||
case "google":
|
||||
// Add Google specific logout URL if needed
|
||||
// String googleLogoutUrl =
|
||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||
// + response.encodeRedirectURL(redirect_url);
|
||||
// logger.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||
// response.sendRedirect(googleLogoutUrl);
|
||||
// break;
|
||||
default:
|
||||
String redirectUrl = request.getContextPath() + "/login?" + param;
|
||||
logger.debug("Redirecting to default logout URL: " + redirectUrl);
|
||||
logger.info("Redirecting to default logout URL: " + redirectUrl);
|
||||
response.sendRedirect(redirectUrl);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package stirling.software.SPDF.config.security.oauth2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
public class CustomOAuthUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomOAuthUserService.class);
|
||||
|
||||
private final OidcUserService delegate = new OidcUserService();
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
public CustomOAuthUserService(ApplicationProperties applicationProperties) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
String usernameAttribute =
|
||||
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
|
||||
try {
|
||||
|
||||
OidcUser user = delegate.loadUser(userRequest);
|
||||
Map<String, Object> attributes = new HashMap<>(user.getAttributes());
|
||||
|
||||
// Ensure the preferred username attribute is present
|
||||
if (!attributes.containsKey(usernameAttribute)) {
|
||||
attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
|
||||
usernameAttribute = "email";
|
||||
logger.info("Adjusted username attribute to use email");
|
||||
}
|
||||
|
||||
// Return a new OidcUser with adjusted attributes
|
||||
return new DefaultOidcUser(
|
||||
user.getAuthorities(),
|
||||
userRequest.getIdToken(),
|
||||
user.getUserInfo(),
|
||||
usernameAttribute);
|
||||
} catch (java.lang.IllegalArgumentException e) {
|
||||
throw new OAuth2AuthenticationException(
|
||||
new OAuth2Error(e.getMessage()), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,9 @@ public class MergeController {
|
||||
throw ex;
|
||||
} finally {
|
||||
for (File file : filesToDelete) {
|
||||
file.delete();
|
||||
if (file != null) {
|
||||
Files.deleteIfExists(file.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ public class PdfOverlayController {
|
||||
} finally {
|
||||
for (File overlayPdfFile : overlayPdfFiles) {
|
||||
if (overlayPdfFile != null) {
|
||||
overlayPdfFile.delete();
|
||||
Files.deleteIfExists(overlayPdfFile.toPath());
|
||||
}
|
||||
}
|
||||
for (File tempFile : tempFiles) { // Delete temporary files
|
||||
if (tempFile != null) {
|
||||
tempFile.delete();
|
||||
Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public class SplitPDFController {
|
||||
|
||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
// return the Resource in the response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -63,10 +63,7 @@ public class SplitPdfBySectionsController {
|
||||
MergeController mergeController = new MergeController();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
mergeController.mergeDocuments(splitDocuments).save(baos);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
filename + "_split.pdf",
|
||||
MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
||||
}
|
||||
for (PDDocument doc : splitDocuments) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
@@ -98,7 +95,7 @@ public class SplitPdfBySectionsController {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -66,7 +66,6 @@ public class ConvertImgPDFController {
|
||||
Integer.valueOf(dpi),
|
||||
filename);
|
||||
|
||||
|
||||
if (result == null || result.length == 0) {
|
||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -41,11 +40,12 @@ public class ConvertOfficeController {
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile =
|
||||
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
|
||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
try {
|
||||
// Run the LibreOffice command
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
@@ -63,12 +63,12 @@ public class ConvertOfficeController {
|
||||
|
||||
// Read the converted PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
|
||||
return pdfBytes;
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidFileExtension(String fileExtension) {
|
||||
|
||||
@@ -61,8 +61,8 @@ public class ConvertPDFToPDFA {
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
|
||||
@@ -59,7 +59,7 @@ public class ConvertWebsiteToPDF {
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempOutputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AutoSplitPdfController {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -67,7 +67,7 @@ public class BlankPageController {
|
||||
String pageText = textStripper.getText(document);
|
||||
boolean hasText = !pageText.trim().isEmpty();
|
||||
|
||||
Boolean blank = false;
|
||||
Boolean blank = true;
|
||||
if (hasText) {
|
||||
logger.info("page " + pageIndex + " has text, not blank");
|
||||
blank = false;
|
||||
|
||||
@@ -136,10 +136,10 @@ public class CompressController {
|
||||
// Increase optimization level for next iteration
|
||||
optimizeLevel++;
|
||||
if (autoMode && optimizeLevel > 4) {
|
||||
System.out.println("Skipping level 5 due to bad results in auto mode");
|
||||
logger.info("Skipping level 5 due to bad results in auto mode");
|
||||
sizeMet = true;
|
||||
} else {
|
||||
System.out.println(
|
||||
logger.info(
|
||||
"Increasing ghostscript optimisation level to " + optimizeLevel);
|
||||
}
|
||||
}
|
||||
@@ -230,10 +230,10 @@ public class CompressController {
|
||||
if (currentSize > expectedOutputSize) {
|
||||
// Log the current file size and scaleFactor
|
||||
|
||||
System.out.println(
|
||||
logger.info(
|
||||
"Current file size: "
|
||||
+ FileUtils.byteCountToDisplaySize(currentSize));
|
||||
System.out.println("Current scale factor: " + scaleFactor);
|
||||
logger.info("Current scale factor: " + scaleFactor);
|
||||
|
||||
// The file is still too large, reduce scaleFactor and try again
|
||||
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
|
||||
@@ -256,7 +256,6 @@ public class CompressController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
@@ -269,17 +268,18 @@ public class CompressController {
|
||||
// Read the original file again
|
||||
pdfBytes = Files.readAllBytes(tempInputFile);
|
||||
}
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
}
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_Optimized.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
// deleted by multipart file handler deu to transferTo?
|
||||
// Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -103,10 +102,7 @@ public class ExtractImageScansController {
|
||||
}
|
||||
} else {
|
||||
tempInputFile = Files.createTempFile("input_", "." + extension);
|
||||
Files.copy(
|
||||
form.getFileInput().getInputStream(),
|
||||
tempInputFile,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
form.getFileInput().transferTo(tempInputFile);
|
||||
// Add input file path to images list
|
||||
images.add(tempInputFile.toString());
|
||||
}
|
||||
@@ -176,11 +172,15 @@ public class ExtractImageScansController {
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
if (processedImageBytes.size() == 0) {
|
||||
throw new IllegalArgumentException("No images detected");
|
||||
} else {
|
||||
|
||||
// Return the processed image as a response
|
||||
byte[] imageBytes = processedImageBytes.get(0);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
@@ -201,7 +201,7 @@ public class ExtractImageScansController {
|
||||
|
||||
if (tempZipFile != null && Files.exists(tempZipFile)) {
|
||||
try {
|
||||
Files.delete(tempZipFile);
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -91,14 +90,12 @@ public class OCRController {
|
||||
}
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Prepare the output file path
|
||||
Path sidecarTextPath = null;
|
||||
|
||||
try {
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// Run OCR Command
|
||||
String languageOption = String.join("+", selectedLanguages);
|
||||
|
||||
@@ -151,7 +148,8 @@ public class OCRController {
|
||||
.runCommandWithOutputHandling(command);
|
||||
if (result.getRc() != 0
|
||||
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
||||
&& result.getMessages().contains("OSError: [Errno 38] Function not implemented")) {
|
||||
&& result.getMessages()
|
||||
.contains("OSError: [Errno 38] Function not implemented")) {
|
||||
command.add("--jobs");
|
||||
command.add("1");
|
||||
result =
|
||||
@@ -178,8 +176,6 @@ public class OCRController {
|
||||
}
|
||||
// Read the OCR processed PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
|
||||
// Return the OCR processed PDF as a response
|
||||
String outputFilename =
|
||||
@@ -213,17 +209,26 @@ public class OCRController {
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(sidecarTextPath);
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
Files.deleteIfExists(sidecarTextPath);
|
||||
|
||||
// Return the zip file containing both the PDF and the text file
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
} else {
|
||||
// Return the OCR processed PDF as a response
|
||||
Files.delete(tempOutputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
}
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
// Comment out as transferTo makes multipart handle cleanup
|
||||
// Files.deleteIfExists(tempInputFile);
|
||||
if (sidecarTextPath != null) {
|
||||
Files.deleteIfExists(sidecarTextPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ public class RepairController {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
byte[] pdfBytes = null;
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
try {
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("gs");
|
||||
@@ -58,11 +58,7 @@ public class RepairController {
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
@@ -70,5 +66,10 @@ public class RepairController {
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_repaired.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,10 +185,12 @@ public class StampController {
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
IOUtils.copy(is, os);
|
||||
}
|
||||
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
tempFile.deleteOnExit();
|
||||
} finally {
|
||||
if (tempFile != null) {
|
||||
Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
@@ -150,10 +150,10 @@ public class WatermarkController {
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
IOUtils.copy(is, os);
|
||||
}
|
||||
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
tempFile.deleteOnExit();
|
||||
} finally {
|
||||
if (tempFile != null) Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -21,6 +24,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
@@ -31,6 +39,7 @@ import stirling.software.SPDF.repository.UserRepository;
|
||||
public class AccountWebController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccountWebController.class);
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
@@ -38,6 +47,33 @@ public class AccountWebController {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
Map<String, String> providerList = new HashMap<>();
|
||||
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth != null) {
|
||||
if (oauth.isSettingsValid()) {
|
||||
providerList.put("oidc", "OpenID Connect");
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client != null) {
|
||||
GoogleProvider google = client.getGoogle();
|
||||
if (google.isSettingsValid()) {
|
||||
providerList.put("google", "Google");
|
||||
}
|
||||
|
||||
GithubProvider github = client.getGithub();
|
||||
if (github.isSettingsValid()) {
|
||||
providerList.put("github", "Github");
|
||||
}
|
||||
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
if (keycloak.isSettingsValid()) {
|
||||
providerList.put("keycloak", "Keycloak");
|
||||
}
|
||||
}
|
||||
}
|
||||
model.addAttribute("providerlist", providerList);
|
||||
|
||||
model.addAttribute(
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||
|
||||
@@ -80,6 +116,21 @@ public class AccountWebController {
|
||||
break;
|
||||
case "invalid_token_response":
|
||||
erroroauth = "login.oauth2InvalidTokenResponse";
|
||||
break;
|
||||
case "authorization_request_not_found":
|
||||
erroroauth = "login.oauth2RequestNotFound";
|
||||
break;
|
||||
case "access_denied":
|
||||
erroroauth = "login.oauth2AccessDenied";
|
||||
break;
|
||||
case "invalid_user_info_response":
|
||||
erroroauth = "login.oauth2InvalidUserInfoResponse";
|
||||
break;
|
||||
case "invalid_request":
|
||||
erroroauth = "login.oauth2invalidRequest";
|
||||
break;
|
||||
case "invalid_id_token":
|
||||
erroroauth = "login.oauth2InvalidIdToken";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
@@ -23,6 +25,7 @@ public class ApplicationProperties {
|
||||
private Metrics metrics;
|
||||
private AutomaticallyGenerated automaticallyGenerated;
|
||||
private AutoPipeline autoPipeline;
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
|
||||
|
||||
public AutoPipeline getAutoPipeline() {
|
||||
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
||||
@@ -188,7 +191,6 @@ public class ApplicationProperties {
|
||||
}
|
||||
|
||||
public static class InitialLogin {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@@ -219,22 +221,21 @@ public class ApplicationProperties {
|
||||
}
|
||||
|
||||
public static class OAUTH2 {
|
||||
|
||||
private boolean enabled;
|
||||
private Boolean enabled = false;
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private boolean autoCreateUser;
|
||||
private Boolean autoCreateUser = false;
|
||||
private String useAsUsername;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String provider;
|
||||
private Client client = new Client();
|
||||
|
||||
private Collection<String> scopes = new ArrayList<String>();
|
||||
|
||||
public boolean getEnabled() {
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@@ -262,20 +263,17 @@ public class ApplicationProperties {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public boolean getAutoCreateUser() {
|
||||
public Boolean getAutoCreateUser() {
|
||||
return autoCreateUser;
|
||||
}
|
||||
|
||||
public void setAutoCreateUser(boolean autoCreateUser) {
|
||||
public void setAutoCreateUser(Boolean autoCreateUser) {
|
||||
this.autoCreateUser = autoCreateUser;
|
||||
}
|
||||
|
||||
public String getUseAsUsername() {
|
||||
if (useAsUsername != null && useAsUsername.trim().length() > 0) {
|
||||
return useAsUsername;
|
||||
}
|
||||
return "email";
|
||||
}
|
||||
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
@@ -293,14 +291,44 @@ public class ApplicationProperties {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String scpoes) {
|
||||
public void setScopes(String scopes) {
|
||||
List<String> scopesList =
|
||||
Arrays.stream(scpoes.split(","))
|
||||
Arrays.stream(scopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList());
|
||||
this.scopes.addAll(scopesList);
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
protected boolean isValid(String value, String name) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isValid(Collection<String> value, String name) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OAUTH2 [enabled="
|
||||
@@ -315,12 +343,353 @@ public class ApplicationProperties {
|
||||
+ autoCreateUser
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ ", provider"
|
||||
+ ", provider="
|
||||
+ provider
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ "]";
|
||||
}
|
||||
|
||||
public static class Client {
|
||||
private GoogleProvider google = new GoogleProvider();
|
||||
private GithubProvider github = new GithubProvider();
|
||||
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||
|
||||
public Provider get(String registrationId) throws Exception {
|
||||
switch (registrationId) {
|
||||
case "gogole":
|
||||
return getGoogle();
|
||||
case "github":
|
||||
return getGithub();
|
||||
case "keycloak":
|
||||
return getKeycloak();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new Exception("Provider not supported, use custom setting.");
|
||||
}
|
||||
|
||||
public GoogleProvider getGoogle() {
|
||||
return google;
|
||||
}
|
||||
|
||||
public void setGoogle(GoogleProvider google) {
|
||||
this.google = google;
|
||||
}
|
||||
|
||||
public GithubProvider getGithub() {
|
||||
return github;
|
||||
}
|
||||
|
||||
public void setGithub(GithubProvider github) {
|
||||
this.github = github;
|
||||
}
|
||||
|
||||
public KeycloakProvider getKeycloak() {
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
public void setKeycloak(KeycloakProvider keycloak) {
|
||||
this.keycloak = keycloak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Client [google="
|
||||
+ google
|
||||
+ ", github="
|
||||
+ github
|
||||
+ ", keycloak="
|
||||
+ keycloak
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GoogleProvider extends Provider {
|
||||
|
||||
private static final String authorizationUri =
|
||||
"https://accounts.google.com/o/oauth2/v2/auth";
|
||||
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||
private static final String userInfoUri =
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Google [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "google";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class GithubProvider extends Provider {
|
||||
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||
private static final String userInfoUri = "https://api.github.com/user";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "login";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return new String();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("read:user");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GitHub [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "github";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeycloakProvider extends Provider {
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("openid");
|
||||
scopes.add("profile");
|
||||
scopes.add("email");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Keycloak [issuer="
|
||||
+ issuer
|
||||
+ ", clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "keycloak";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,9 +750,6 @@ public class ApplicationProperties {
|
||||
this.googlevisibility = googlevisibility;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "System [defaultLocale="
|
||||
|
||||
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class Provider implements ProviderInterface {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected boolean isValid(String value, String name) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||
}
|
||||
|
||||
protected boolean isValid(Collection<String> value, String name) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ProviderInterface {
|
||||
|
||||
public Collection<String> getScopes();
|
||||
|
||||
public void setScopes(String scopes);
|
||||
|
||||
public String getUseAsUsername();
|
||||
|
||||
public void setUseAsUsername(String useAsUsername);
|
||||
|
||||
public String getIssuer();
|
||||
|
||||
public void setIssuer(String issuer);
|
||||
|
||||
public String getClientSecret();
|
||||
|
||||
public void setClientSecret(String clientSecret);
|
||||
|
||||
public String getClientId();
|
||||
|
||||
public void setClientId(String clientId);
|
||||
}
|
||||
@@ -79,8 +79,8 @@ public class FileToPdf {
|
||||
} finally {
|
||||
|
||||
// Clean up temporary files
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
}
|
||||
|
||||
return pdfBytes;
|
||||
|
||||
@@ -38,14 +38,14 @@ public class GeneralUtils {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
Files.delete(file);
|
||||
Files.deleteIfExists(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
||||
throws IOException {
|
||||
Files.delete(dir);
|
||||
Files.deleteIfExists(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -34,7 +33,10 @@ public class PDFToFile {
|
||||
|
||||
// Get the original PDF file name without the extension
|
||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
String pdfBaseName = originalPdfFileName;
|
||||
if (originalPdfFileName.contains(".")) {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
Path tempInputFile = null;
|
||||
Path tempOutputDir = null;
|
||||
@@ -44,8 +46,7 @@ public class PDFToFile {
|
||||
try {
|
||||
// Save the uploaded file to a temporary location
|
||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(
|
||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output directory
|
||||
tempOutputDir = Files.createTempDirectory("output_");
|
||||
@@ -82,7 +83,7 @@ public class PDFToFile {
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
|
||||
@@ -100,8 +101,15 @@ public class PDFToFile {
|
||||
|
||||
// Get the original PDF file name without the extension
|
||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
|
||||
if (originalPdfFileName == null || "".equals(originalPdfFileName.trim())) {
|
||||
originalPdfFileName = "output.pdf";
|
||||
}
|
||||
// Assume file is pdf if no extension
|
||||
String pdfBaseName = originalPdfFileName;
|
||||
if (originalPdfFileName.contains(".")) {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
// Validate output format
|
||||
List<String> allowedFormats =
|
||||
Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text");
|
||||
@@ -117,8 +125,7 @@ public class PDFToFile {
|
||||
try {
|
||||
// Save the uploaded file to a temporary location
|
||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(
|
||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output directory
|
||||
tempOutputDir = Files.createTempDirectory("output_");
|
||||
@@ -170,9 +177,10 @@ public class PDFToFile {
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
System.out.println("fileBytes=" + fileBytes.length);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ public class RequestUriUtils {
|
||||
public static boolean isStaticResource(String requestURI) {
|
||||
|
||||
return requestURI.startsWith("/css/")
|
||||
|| requestURI.startsWith("/fonts/")
|
||||
|| requestURI.startsWith("/js/")
|
||||
|| requestURI.startsWith("/images/")
|
||||
|| requestURI.startsWith("/public/")
|
||||
|
||||
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public class UrlUtils {
|
||||
|
||||
public static String getOrigin(HttpServletRequest request) {
|
||||
String scheme = request.getScheme(); // http or https
|
||||
String serverName = request.getServerName(); // localhost
|
||||
int serverPort = request.getServerPort(); // 8080
|
||||
String contextPath = request.getContextPath(); // /myapp
|
||||
|
||||
return scheme + "://" + serverName + ":" + serverPort + contextPath;
|
||||
}
|
||||
}
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Вашият акаунт е заключен.
|
||||
login.signinTitle=Моля впишете се
|
||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Изтегли
|
||||
|
||||
#crop
|
||||
crop.title=Изрязване
|
||||
crop.header=Изрязване на изображение
|
||||
crop.header=Изрязване на PDF
|
||||
crop.submit=Подайте
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Compte bloquejat
|
||||
login.signinTitle=Autenticat
|
||||
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
|
||||
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Talla
|
||||
crop.header=Talla Imatge
|
||||
crop.header=Talla PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
1081
src/main/resources/messages_cs_CZ.properties
Normal file
1081
src/main/resources/messages_cs_CZ.properties
Normal file
File diff suppressed because it is too large
Load Diff
@@ -59,8 +59,8 @@ deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht w
|
||||
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
|
||||
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
|
||||
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
|
||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||
userAlreadyExistsOAuthMessage=Der Benutzer ist bereits als OAuth2-Benutzer vorhanden.
|
||||
userAlreadyExistsWebMessage=Der Benutzer ist bereits als Webbenutzer vorhanden.
|
||||
error=Fehler
|
||||
oops=Hoppla!
|
||||
help=Hilfe
|
||||
@@ -71,7 +71,7 @@ visitGithub=GitHub-Repository besuchen
|
||||
donate=Spenden
|
||||
color=Farbe
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
info=Die Info
|
||||
|
||||
|
||||
|
||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=Validieren
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.favorite=Favoriten
|
||||
navbar.darkmode=Dunkler Modus
|
||||
navbar.language=Languages
|
||||
navbar.language=Sprachen
|
||||
navbar.settings=Einstellungen
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.allTools=Werkzeuge
|
||||
navbar.multiTool=Multitools
|
||||
navbar.sections.organize=Organisieren
|
||||
navbar.sections.convertTo=In PDF konvertieren
|
||||
navbar.sections.convertFrom=Konvertieren von PDF
|
||||
navbar.sections.security=Zeichen und Sicherheit
|
||||
navbar.sections.advance=Fortschrittlich
|
||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -185,7 +185,7 @@ adminUserSettings.internalApiUser=Interner API-Benutzer
|
||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||
adminUserSettings.submit=Benutzer speichern
|
||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
adminUserSettings.authenticated=Authentifiziert
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -452,6 +452,12 @@ login.locked=Ihr Konto wurde gesperrt.
|
||||
login.signinTitle=Bitte melden Sie sich an.
|
||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
||||
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
||||
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
||||
login.oauth2invalidRequest=ungültige Anfrage
|
||||
login.oauth2AccessDenied=Zugriff abgelehnt
|
||||
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
||||
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Herunterladen
|
||||
|
||||
#crop
|
||||
crop.title=Zuschneiden
|
||||
crop.header=Bild zuschneiden
|
||||
crop.header=PDF zuschneiden
|
||||
crop.submit=Abschicken
|
||||
|
||||
|
||||
@@ -704,7 +710,7 @@ repair.submit=Reparieren
|
||||
#flatten
|
||||
flatten.title=Abflachen
|
||||
flatten.header=PDFs reduzieren
|
||||
flatten.flattenOnlyForms=Flatten only forms
|
||||
flatten.flattenOnlyForms=Nur Formulare abflachen
|
||||
flatten.submit=Abflachen
|
||||
|
||||
|
||||
@@ -752,7 +758,7 @@ extractImages.submit=Extrahieren
|
||||
fileToPDF.title=Datei in PDF
|
||||
fileToPDF.header=Beliebige Dateien in PDF konvertieren
|
||||
fileToPDF.credit=Dieser Dienst verwendet LibreOffice und Unoconv für die Dateikonvertierung.
|
||||
fileToPDF.supportedFileTypesInfo=Supported File types
|
||||
fileToPDF.supportedFileTypesInfo=Unterstützte Dateitypen
|
||||
fileToPDF.supportedFileTypes=Unterstützte Dateitypen sollten die folgenden enthalten, eine vollständige aktualisierte Liste der unterstützten Formate finden Sie jedoch in der LibreOffice-Dokumentation
|
||||
fileToPDF.submit=In PDF konvertieren
|
||||
|
||||
@@ -805,7 +811,7 @@ pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
|
||||
#multiTool
|
||||
multiTool.title=PDF-Multitool
|
||||
multiTool.header=PDF-Multitool
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=Dateiname
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=PDF anzeigen
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
||||
login.signinTitle=Παρακαλώ, συνδεθείτε
|
||||
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
||||
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Λήψη
|
||||
|
||||
#crop
|
||||
crop.title=Κοπή
|
||||
crop.header=Κοπή Εικόνας
|
||||
crop.header=Περικοπή PDF
|
||||
crop.submit=Υποβολή
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Su cuenta se ha bloqueado.
|
||||
login.signinTitle=Por favor, inicie sesión
|
||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Descargar
|
||||
|
||||
#crop
|
||||
crop.title=Recortar
|
||||
crop.header=Recortar Imagen
|
||||
crop.header=Recortar PDF
|
||||
crop.submit=Entregar
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Zure kontua blokeatu egin da.
|
||||
login.signinTitle=Mesedez, hasi saioa
|
||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Distira
|
||||
|
||||
#crop
|
||||
crop.title=Moztu
|
||||
crop.header=Irudia Moztu
|
||||
crop.header=Moztu PDF
|
||||
crop.submit=Bidali
|
||||
|
||||
|
||||
|
||||
@@ -22,18 +22,18 @@ close=Fermer
|
||||
filesSelected=fichiers sélectionnés
|
||||
noFavourites=Aucun favori ajouté
|
||||
downloadComplete=Téléchargement terminé
|
||||
bored=Ennuyé d’attendre ?
|
||||
bored=Marre d’attendre ?
|
||||
alphabet=Alphabet
|
||||
downloadPdf=Télécharger le PDF
|
||||
text=Texte
|
||||
font=Police
|
||||
selectFillter=-- Sélectionnez --
|
||||
pageNum=numéro de page
|
||||
pageNum=Numéro de page
|
||||
sizes.small=Petit
|
||||
sizes.medium=Moyen
|
||||
sizes.large=Grand
|
||||
sizes.x-large=Très grand
|
||||
error.pdfPassword=Le document PDF est protégé par un mot de passe et le mot de passe n’a pas été fourni ou était incorrect
|
||||
error.pdfPassword=Le document PDF est protégé par un mot de passe qui n’a pas été fourni ou était incorrect
|
||||
delete=Supprimer
|
||||
username=Nom d’utilisateur
|
||||
password=Mot de passe
|
||||
@@ -78,13 +78,13 @@ info=Info
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=Menu Pipeline (Alpha)
|
||||
pipeline.header=Menu Pipeline (Beta)
|
||||
pipeline.uploadButton=Charger une personnalisation
|
||||
pipeline.configureButton=Configurer
|
||||
pipeline.defaultOption=Personnaliser
|
||||
pipeline.submitButton=Soumettre
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.help=Aide Pipeline
|
||||
pipeline.scanHelp=Aide analyse de dossier
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
@@ -107,16 +107,16 @@ pipelineOptions.validateButton=Valider
|
||||
#############
|
||||
navbar.favorite=Favoris
|
||||
navbar.darkmode=Mode sombre
|
||||
navbar.language=Langages
|
||||
navbar.language=Langues
|
||||
navbar.settings=Paramètres
|
||||
navbar.allTools=OUtils
|
||||
navbar.multiTool=Multi Outils
|
||||
navbar.sections.organize=Oragnisation
|
||||
navbar.allTools=Outils
|
||||
navbar.multiTool=Outils Multiples
|
||||
navbar.sections.organize=Organisation
|
||||
navbar.sections.convertTo=Convertir en PDF
|
||||
navbar.sections.convertFrom=Convertir depuis PDF
|
||||
navbar.sections.security=Signalisation et sécurité
|
||||
navbar.sections.security=Signature et sécurité
|
||||
navbar.sections.advance=Mode avancé
|
||||
navbar.sections.edit=Voir la modification
|
||||
navbar.sections.edit=Voir et modifier
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -132,7 +132,7 @@ settings.downloadOption.3=Télécharger le fichier
|
||||
settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
|
||||
settings.signOut=Déconnexion
|
||||
settings.accountSettings=Paramètres du compte
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.bored.help=Activer les jeux cachés
|
||||
settings.cacheInputs.name=Sauvegarder les entrées du formulaire
|
||||
settings.cacheInputs.help=Permet de stocker les entrées précédemment utilisées pour les exécutions futures
|
||||
|
||||
@@ -181,7 +181,7 @@ adminUserSettings.apiUser=Utilisateur API limité
|
||||
adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l’API
|
||||
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
||||
adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.internalApiUser=Utilisateur de l'API interne
|
||||
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
|
||||
adminUserSettings.submit=Ajouter
|
||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||
@@ -357,7 +357,7 @@ home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité
|
||||
adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance
|
||||
|
||||
home.crop.title=Redimensionner
|
||||
home.crop.desc=Redimmensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
||||
home.crop.desc=Redimensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
||||
crop.tags=redimensionner,trim,shrink,edit,shape
|
||||
|
||||
home.autoSplitPDF.title=Séparer automatiquement les pages
|
||||
@@ -428,11 +428,11 @@ home.AddStampRequest.desc=Ajouter un texte ou l’image d’un tampon à un empl
|
||||
AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize
|
||||
|
||||
|
||||
home.PDFToBook.title=PDF to Book
|
||||
home.PDFToBook.title=PDF vers eBook
|
||||
home.PDFToBook.desc=Convertit le PDF en formats livre/bande dessinée à l'aide de calibre
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.title=eBook vers PDF
|
||||
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Votre compte a été verrouillé.
|
||||
login.signinTitle=Veuillez vous connecter
|
||||
login.ssoSignIn=Se connecter via l'authentification unique
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -475,8 +481,8 @@ showJS.submit=Afficher
|
||||
|
||||
|
||||
#pdfToSinglePage
|
||||
pdfToSinglePage.title=Fusionner des pages
|
||||
pdfToSinglePage.header=Fusionner des pages
|
||||
pdfToSinglePage.title=Fusionner les pages
|
||||
pdfToSinglePage.header=Fusionner les pages
|
||||
pdfToSinglePage.submit=Convertir en une seule page
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=आपका खाता लॉक कर दिया गया
|
||||
login.signinTitle=कृपया साइन इन करें
|
||||
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=डाउनलोड
|
||||
|
||||
#crop
|
||||
crop.title=कटौती
|
||||
crop.header=छवि काटो
|
||||
crop.header=क्रॉप पीडीएफ़
|
||||
crop.submit=प्रस्तुत करें
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=A fiókja zárolva lett!
|
||||
login.signinTitle=Kérjük, jelentkezzen be!
|
||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Letöltés
|
||||
|
||||
#crop
|
||||
crop.title=Körülvágás
|
||||
crop.header=Kép körülvégésa
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Elküldés
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Akun Anda telah dikunci.
|
||||
login.signinTitle=Silakan masuk
|
||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Unduh
|
||||
|
||||
#crop
|
||||
crop.title=Pangkas
|
||||
crop.header=Pangkas Gambar
|
||||
crop.header=Pangkas PDF
|
||||
crop.submit=Kirim
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Il tuo account è stato bloccato.
|
||||
login.signinTitle=Per favore accedi
|
||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
||||
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
||||
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
||||
login.oauth2invalidRequest=Richiesta non valida
|
||||
login.oauth2AccessDenied=Accesso negato
|
||||
login.oauth2InvalidTokenResponse=Risposta token non valida
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Ritaglia
|
||||
crop.header=Ritaglia l'immagine
|
||||
crop.header=Ritaglia PDF
|
||||
crop.submit=Invia
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=あなたのアカウントはロックされています。
|
||||
login.signinTitle=サインインしてください
|
||||
login.ssoSignIn=シングルサインオンでログイン
|
||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=ダウンロード
|
||||
|
||||
#crop
|
||||
crop.title=切り抜き
|
||||
crop.header=画像の切り抜き
|
||||
crop.header=PDFのトリミング
|
||||
crop.submit=送信
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=계정이 잠겼습니다.
|
||||
login.signinTitle=로그인해 주세요.
|
||||
login.ssoSignIn=싱글사인온을 통한 로그인
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=다운로드
|
||||
|
||||
#crop
|
||||
crop.title=잘라내기
|
||||
crop.header=잘라내기
|
||||
crop.header=PDF 잘라내기
|
||||
crop.submit=확인
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Je account is geblokkeerd.
|
||||
login.signinTitle=Gelieve in te loggen
|
||||
login.ssoSignIn=Inloggen via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Downloaden
|
||||
|
||||
#crop
|
||||
crop.title=Bijwerken
|
||||
crop.header=Afbeelding bijwerken
|
||||
crop.header=PDF bijsnijden
|
||||
crop.submit=Indienen
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Criar Usuário Desativado
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Cortar
|
||||
crop.header=Cortar Imagem
|
||||
crop.header=Cortar PDF
|
||||
crop.submit=Enviar
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=A sua conta foi bloqueada.
|
||||
login.signinTitle=Introduza os seus dados de acesso
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Criação Automática de Utilizador Desativada
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Cortar
|
||||
crop.header=Cortar Imagem
|
||||
crop.header=Cortar PDF
|
||||
crop.submit=Enviar
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Conectare prin conectare unică
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Ваша учетная запись заблокирована.
|
||||
login.signinTitle=Пожалуйста, войдите
|
||||
login.ssoSignIn=Вход через единый вход
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Скачать
|
||||
|
||||
#crop
|
||||
crop.title=Обрезать
|
||||
crop.header=Обрезать изображение
|
||||
crop.header=Обрезать PDF-файл
|
||||
crop.submit=Отправить
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Váš účet bol uzamknutý.
|
||||
login.signinTitle=Prosím, prihláste sa
|
||||
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Stiahnuť
|
||||
|
||||
#crop
|
||||
crop.title=Orezať
|
||||
crop.header=Orezať obrázok
|
||||
crop.header=Orezať PDF
|
||||
crop.submit=Odoslať
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Vaš nalog je zaključan.
|
||||
login.signinTitle=Molimo vas da se prijavite
|
||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Preuzmi
|
||||
|
||||
#crop
|
||||
crop.title=Iseci
|
||||
crop.header=Iseci Sliku
|
||||
crop.header=Skraćivanje PDF-a
|
||||
crop.submit=Potvrdi
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Logga in via enkel inloggning
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
language.direction=ltr
|
||||
|
||||
pdfPrompt=PDF(leri) seçin
|
||||
multiPdfPrompt=PDF seçin (2+)
|
||||
multiPdfPrompt=PDFleri seçin (2+)
|
||||
multiPdfDropPrompt=Tüm gerekli PDF'leri seçin (ya da sürükleyip bırakın)
|
||||
imgPrompt=Resim(leri) seçin
|
||||
genericSubmit=Gönder
|
||||
processTimeWarning=Uyarı: Bu işlem, dosya boyutuna bağlı olarak bir dakikaya kadar sürebilir.
|
||||
pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin) :
|
||||
pageSelectionPrompt=Özel Sayfa Seçimi (1,5,6 sayfa numaralarının virgülle ayrılmış bir listesini veya 2n+1 gibi bir fonksiyon girin) :
|
||||
goToPage=Git
|
||||
goToPage=Sayfaya Git
|
||||
true=Doğru
|
||||
false=Yanlış
|
||||
unknown=Bilinmeyen
|
||||
@@ -130,7 +130,7 @@ settings.downloadOption.1=Aynı pencerede aç
|
||||
settings.downloadOption.2=Yeni pencerede aç
|
||||
settings.downloadOption.3=Dosyayı indir
|
||||
settings.zipThreshold=İndirilen dosya sayısı şu değeri aştığında zip dosyası oluştur:
|
||||
settings.signOut=Oturumu Kapat
|
||||
settings.signOut=Çıkış Yap
|
||||
settings.accountSettings=Hesap Ayarları
|
||||
settings.bored.help=Paskalya yumurtası oyunu etkinleştirir
|
||||
settings.cacheInputs.name=Form girdilerini kaydet
|
||||
@@ -138,7 +138,7 @@ settings.cacheInputs.help=Gelecekteki çalıştırmalar için önceden kullanıl
|
||||
|
||||
changeCreds.title=Giriş Bilgilerini Değiştir
|
||||
changeCreds.header=Hesap Detaylarınızı Güncelleyin
|
||||
changeCreds.changePassword=You are using default login credentials. Please enter a new password
|
||||
changeCreds.changePassword=Varsayılan giriş bilgilerini kullanıyorsunuz. Lütfen yeni bir şifre girin.
|
||||
changeCreds.newUsername=Yeni Kullanıcı Adı
|
||||
changeCreds.oldPassword=Mevcut Şifre
|
||||
changeCreds.newPassword=Yeni Şifre
|
||||
@@ -452,6 +452,12 @@ login.locked=Hesabınız kilitlendi.
|
||||
login.signinTitle=Lütfen giriş yapınız.
|
||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=İndir
|
||||
|
||||
#crop
|
||||
crop.title=Kırp
|
||||
crop.header=Resmi Kırp
|
||||
crop.header=PDF'i Kırp
|
||||
crop.submit=Gönder
|
||||
|
||||
|
||||
@@ -738,7 +744,7 @@ ocr.selectText.11=OCR'den sonra resimleri kaldır (TÜM resimleri kaldırır, sa
|
||||
ocr.selectText.12=Render Türü (İleri Seviye)
|
||||
ocr.help=Lütfen bu belgede başka dillerde nasıl kullanılacağı ve/veya docker'da kullanılmaması hakkında bilgi edinin
|
||||
ocr.credit=Bu hizmet OCR için OCRmyPDF ve Tesseract'ı kullanır.
|
||||
ocr.submit=PDF'i OCR ile İşle
|
||||
ocr.submit=PDF'i OCR(Metin Tanıma) ile İşle
|
||||
|
||||
|
||||
#extractImages
|
||||
@@ -772,7 +778,7 @@ compress.submit=Sıkıştır
|
||||
#Add image
|
||||
addImage.title=Resim Ekle
|
||||
addImage.header=PDF'e resim ekle
|
||||
addImage.everyPage=Her Sayfa?
|
||||
addImage.everyPage=Her Sayfa mı?
|
||||
addImage.upload=Resim ekle
|
||||
addImage.submit=Resim ekle
|
||||
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=Ваш обліковий запис заблоковано.
|
||||
login.signinTitle=Будь ласка, увійдіть
|
||||
login.ssoSignIn=Увійти через єдиний вхід
|
||||
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=Завантажити
|
||||
|
||||
#crop
|
||||
crop.title=Обрізати
|
||||
crop.header=Обрізати зображення
|
||||
crop.header=Обрізати PDF-файл
|
||||
crop.submit=Надіслати
|
||||
|
||||
|
||||
|
||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=验证
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.darkmode=暗模式
|
||||
navbar.language=Languages
|
||||
navbar.favorite=收藏
|
||||
navbar.darkmode=暗黑模式
|
||||
navbar.language=语言
|
||||
navbar.settings=设置
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.allTools=工具箱
|
||||
navbar.multiTool=多功能工具
|
||||
navbar.sections.organize=组织
|
||||
navbar.sections.convertTo=转换成PDF
|
||||
navbar.sections.convertFrom=从PDF转换
|
||||
navbar.sections.security=签名和安全
|
||||
navbar.sections.advance=高级功能
|
||||
navbar.sections.edit=查看和编辑
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -452,6 +452,12 @@ login.locked=您的账户已被锁定。
|
||||
login.signinTitle=请登录
|
||||
login.ssoSignIn=通过单点登录登录
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=下载
|
||||
|
||||
#crop
|
||||
crop.title=裁剪
|
||||
crop.header=裁剪图像
|
||||
crop.header=裁剪PDF
|
||||
crop.submit=提交
|
||||
|
||||
|
||||
@@ -805,7 +811,7 @@ pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
|
||||
#multiTool
|
||||
multiTool.title=PDF多功能工具
|
||||
multiTool.header=PDF多功能工具
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=文件名
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=浏览PDF
|
||||
|
||||
@@ -452,6 +452,12 @@ login.locked=您的帳戶已被鎖定。
|
||||
login.signinTitle=請登入
|
||||
login.ssoSignIn=透過織網單一簽入
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +598,7 @@ adjustContrast.download=下載
|
||||
|
||||
#crop
|
||||
crop.title=裁剪
|
||||
crop.header=裁剪影像
|
||||
crop.header=裁剪 PDF
|
||||
crop.submit=送出
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,29 @@ security:
|
||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
# client:
|
||||
# google:
|
||||
# clientId: "" # Client ID for Google OAuth2
|
||||
# clientSecret: "" # Client Secret for Google OAuth2
|
||||
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||
# github:
|
||||
# clientId: "" # Client ID for GitHub OAuth2
|
||||
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||
# keycloak:
|
||||
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||
showUpdate: true # see when a new update is available
|
||||
showUpdate: false # see when a new update is available
|
||||
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||
|
||||
|
||||
@@ -343,6 +343,13 @@
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.servlet:jakarta.servlet-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "6.0.0",
|
||||
"moduleLicense": "GPL2 w/ CPE",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.transaction:jakarta.transaction-api",
|
||||
"moduleUrl": "https://projects.eclipse.org/projects/ee4j.jta",
|
||||
@@ -357,6 +364,20 @@
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.websocket:jakarta.websocket-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "2.1.1",
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.websocket:jakarta.websocket-client-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "2.1.1",
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.xml.bind:jakarta.xml.bind-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
@@ -452,13 +473,6 @@
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-core",
|
||||
"moduleUrl": "https://tomcat.apache.org/",
|
||||
"moduleVersion": "10.1.19",
|
||||
"moduleLicense": "Eclipse Public License - v 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
|
||||
"moduleUrl": "https://tomcat.apache.org/",
|
||||
@@ -466,13 +480,6 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-websocket",
|
||||
"moduleUrl": "https://tomcat.apache.org/",
|
||||
"moduleVersion": "10.1.19",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.xmlgraphics:batik-all",
|
||||
"moduleVersion": "1.17",
|
||||
@@ -540,6 +547,188 @@
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-alpn-client",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-client",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-http",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-io",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-jndi",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-plus",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-security",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-server",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-session",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-util",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.eclipse.jetty:jetty-xml",
|
||||
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||
"moduleVersion": "12.0.7",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.glassfish.jaxb:jaxb-core",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
@@ -599,7 +788,21 @@
|
||||
{
|
||||
"moduleName": "org.ow2.asm:asm",
|
||||
"moduleUrl": "http://asm.ow2.org",
|
||||
"moduleVersion": "9.3",
|
||||
"moduleVersion": "9.6",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.ow2.asm:asm-commons",
|
||||
"moduleUrl": "http://asm.ow2.org",
|
||||
"moduleVersion": "9.6",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.ow2.asm:asm-tree",
|
||||
"moduleUrl": "http://asm.ow2.org",
|
||||
"moduleVersion": "9.6",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
@@ -705,6 +908,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-jetty",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
"moduleVersion": "3.2.4",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-json",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
@@ -740,13 +950,6 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-tomcat",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
"moduleVersion": "3.2.4",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-web",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png?v=2"/>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#00aba9</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
font-size: 16px;
|
||||
margin-bottom: 2rem;
|
||||
padding: 0.75rem 3.5rem;
|
||||
border: 0.1rem solid var(--md-sys-color-outline-variant);
|
||||
border: 1px solid var(--md-sys-color-outline-variant);
|
||||
border-radius: 3rem;
|
||||
outline-color: var(--md-sys-color-outline-variant);
|
||||
}
|
||||
@@ -19,12 +19,12 @@
|
||||
.features-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
||||
gap: 25px 30px;
|
||||
gap: 30px 30px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
border: 2px solid var(--md-sys-color-surface-5);
|
||||
border-radius: 2rem;
|
||||
border: 1px solid var(--md-sys-color-surface-5);
|
||||
border-radius: 1.75rem;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -47,13 +47,13 @@
|
||||
}
|
||||
|
||||
.feature-card .card-text {
|
||||
flex: 1;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--md-sys-elevation-3);
|
||||
transform: scale(1.08);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
}
|
||||
|
||||
.card-title.text-primary {
|
||||
@@ -79,11 +79,12 @@
|
||||
}
|
||||
|
||||
#tool-text {
|
||||
margin: 0.5rem 0.5rem 0rem;
|
||||
margin: 0.0rem 0 0 1.25rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Only show the favorite icons when the parent card is being hovered over */
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
margin-left: 4px;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -187,8 +187,8 @@ td {
|
||||
.feature-card .nav-icon {
|
||||
vertical-align: middle;
|
||||
font-size: 2rem !important;
|
||||
padding: 0.5rem;
|
||||
border-radius: 1.25rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.9rem;
|
||||
color: var(--md-sys-color-surface);
|
||||
}
|
||||
|
||||
@@ -798,6 +798,8 @@ textarea.form-control {
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: none !important;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border: 1px transparent;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.material-symbols-rounded {
|
||||
font-family: 'Material Symbols Rounded';
|
||||
font-weight: normal;
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
}
|
||||
|
||||
.no-fill {
|
||||
font-variation-settings: var(--md-sys-icon-fill-0);
|
||||
/* font-variation-settings: var(--md-sys-icon-fill-0); */
|
||||
}
|
||||
6
src/main/resources/static/images/flags/cz.svg
Normal file
6
src/main/resources/static/images/flags/cz.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="900" height="600">
|
||||
<rect width="900" height="600" fill="#d7141a"/>
|
||||
<rect width="900" height="300" fill="#fff"/>
|
||||
<path d="M 450,300 0,0 V 600 z" fill="#11457e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 270 B |
@@ -138,7 +138,7 @@ class PdfContainer {
|
||||
}
|
||||
|
||||
async toRenderer(objectUrl) {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs/pdf.worker.js";
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdfjs/pdf.worker.mjs";
|
||||
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
|
||||
return {
|
||||
document: pdf,
|
||||
|
||||
@@ -40,7 +40,6 @@ document.querySelector("#navbarSearchInput").addEventListener("input", function
|
||||
var itemHref = item.getAttribute("href");
|
||||
var tags = item.getAttribute("data-bs-tags") || ""; // If no tags, default to empty string
|
||||
|
||||
|
||||
if (titleElement && iconElement && itemHref !== "#") {
|
||||
var title = titleElement.innerText;
|
||||
if (
|
||||
|
||||
2305
src/main/resources/static/pdfjs/css/viewer.css
vendored
2305
src/main/resources/static/pdfjs/css/viewer.css
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user