Compare commits
20 Commits
sso-refact
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c7b83ee87 | ||
|
|
9152e64b9f | ||
|
|
96655f7cac | ||
|
|
8f7153b30a | ||
|
|
366bec602d | ||
|
|
c9c8378fe0 | ||
|
|
7a7338c6de | ||
|
|
77dec10f25 | ||
|
|
12b03be2be | ||
|
|
222c18cdae | ||
|
|
d2bc281e42 | ||
|
|
ac10c9fa43 | ||
|
|
4fabc07a44 | ||
|
|
2ab951e080 | ||
|
|
a1f7bb3e4a | ||
|
|
f64d7d42d9 | ||
|
|
300011f9b6 | ||
|
|
e328833f02 | ||
|
|
4c701b2e69 | ||
|
|
16295c7bb9 |
23
.github/scripts/check_language_properties.py
vendored
23
.github/scripts/check_language_properties.py
vendored
@@ -164,7 +164,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
|
|||||||
if current_entry["type"] == "entry":
|
if current_entry["type"] == "entry":
|
||||||
if ref_entry_copy["type"] != "entry":
|
if ref_entry_copy["type"] != "entry":
|
||||||
continue
|
continue
|
||||||
if ref_entry_copy["key"] == current_entry["key"]:
|
if ref_entry_copy["key"].lower() == current_entry["key"].lower():
|
||||||
ref_entry_copy["value"] = current_entry["value"]
|
ref_entry_copy["value"] = current_entry["value"]
|
||||||
updated_properties.append(ref_entry_copy)
|
updated_properties.append(ref_entry_copy)
|
||||||
write_json_file(os.path.join(branch, file_path), updated_properties)
|
write_json_file(os.path.join(branch, file_path), updated_properties)
|
||||||
@@ -199,29 +199,30 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
|
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
|
||||||
|
|
||||||
for file_path in file_arr:
|
for file_path in file_arr:
|
||||||
absolute_path = os.path.abspath(file_path)
|
file_normpath = os.path.normpath(file_path)
|
||||||
|
absolute_path = os.path.abspath(file_normpath)
|
||||||
# Verify that file is within the expected directory
|
# Verify that file is within the expected directory
|
||||||
if not absolute_path.startswith(base_dir):
|
if not absolute_path.startswith(base_dir):
|
||||||
raise ValueError(f"Unsafe file found: {file_path}")
|
raise ValueError(f"Unsafe file found: {file_normpath}")
|
||||||
# Verify file size before processing
|
# Verify file size before processing
|
||||||
if os.path.getsize(os.path.join(branch, file_path)) > MAX_FILE_SIZE:
|
if os.path.getsize(os.path.join(branch, file_normpath)) > MAX_FILE_SIZE:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The file {file_path} is too large and could pose a security risk."
|
f"The file {file_normpath} is too large and could pose a security risk."
|
||||||
)
|
)
|
||||||
|
|
||||||
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
basename_current_file = os.path.basename(os.path.join(branch, file_normpath))
|
||||||
if (
|
if (
|
||||||
basename_current_file == basename_reference_file
|
basename_current_file == basename_reference_file
|
||||||
or (
|
or (
|
||||||
# only local windows command
|
# only local windows command
|
||||||
not file_path.startswith(
|
not file_normpath.startswith(
|
||||||
os.path.join("", "src", "main", "resources", "messages_")
|
os.path.join("", "src", "main", "resources", "messages_")
|
||||||
)
|
)
|
||||||
and not file_path.startswith(
|
and not file_normpath.startswith(
|
||||||
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
|
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
or not file_path.endswith(".properties")
|
or not file_normpath.endswith(".properties")
|
||||||
or not basename_current_file.startswith("messages_")
|
or not basename_current_file.startswith("messages_")
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
@@ -292,13 +293,13 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
else:
|
else:
|
||||||
report.append("2. **Test Status:** ✅ **_Passed_**")
|
report.append("2. **Test Status:** ✅ **_Passed_**")
|
||||||
|
|
||||||
if find_duplicate_keys(os.path.join(branch, file_path)):
|
if find_duplicate_keys(os.path.join(branch, file_normpath)):
|
||||||
has_differences = True
|
has_differences = True
|
||||||
output = "\n".join(
|
output = "\n".join(
|
||||||
[
|
[
|
||||||
f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`"
|
f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`"
|
||||||
for key, first, duplicate in find_duplicate_keys(
|
for key, first, duplicate in find_duplicate_keys(
|
||||||
os.path.join(branch, file_path)
|
os.path.join(branch, file_normpath)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
2
.github/workflows/licenses-update.yml
vendored
2
.github/workflows/licenses-update.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
|||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: "Update 3rd Party Licenses"
|
commit-message: "Update 3rd Party Licenses"
|
||||||
|
|||||||
2
.github/workflows/pre_commit.yml
vendored
2
.github/workflows/pre_commit.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
|||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: ":file_folder: pre-commit"
|
commit-message: ":file_folder: pre-commit"
|
||||||
|
|||||||
2
.github/workflows/sync_files.yml
vendored
2
.github/workflows/sync_files.yml
vendored
@@ -103,7 +103,7 @@ jobs:
|
|||||||
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
|
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # v7.0.7
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: Update files
|
commit-message: Update files
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.42.0"
|
version = "0.43.1"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -294,8 +294,8 @@ configurations.all {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
//tmp for security bumps
|
//tmp for security bumps
|
||||||
implementation 'ch.qos.logback:logback-core:1.5.16'
|
implementation 'ch.qos.logback:logback-core:1.5.17'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.16'
|
implementation 'ch.qos.logback:logback-classic:1.5.17'
|
||||||
|
|
||||||
|
|
||||||
// Exclude vulnerable BouncyCastle version used in tableau
|
// Exclude vulnerable BouncyCastle version used in tableau
|
||||||
@@ -347,8 +347,8 @@ dependencies {
|
|||||||
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
implementation 'com.coveo:saml-client:5.0.0'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
implementation 'org.snakeyaml:snakeyaml-engine:2.9'
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class LicenseKeyChecker {
|
|||||||
|
|
||||||
public void updateLicenseKey(String newKey) throws IOException {
|
public void updateLicenseKey(String newKey) throws IOException {
|
||||||
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||||
GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false);
|
GeneralUtils.saveKeyToSettings("EnterpriseEdition.key", newKey);
|
||||||
checkLicense();
|
checkLicense();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,18 +83,18 @@ public class SPDFApplication {
|
|||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
// External config files
|
// External config files
|
||||||
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath());
|
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) {
|
log.info("Settings file: {}", settingsPath.toString());
|
||||||
|
if (Files.exists(settingsPath)) {
|
||||||
propertyFiles.put(
|
propertyFiles.put(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location", "file:" + settingsPath.toString());
|
||||||
"file:" + InstallationPathConfig.getSettingsPath());
|
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn("External configuration file '{}' does not exist.", settingsPath.toString());
|
||||||
"External configuration file '{}' does not exist.",
|
|
||||||
InstallationPathConfig.getSettingsPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) {
|
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||||
|
log.info("Custom settings file: {}", customSettingsPath.toString());
|
||||||
|
if (Files.exists(customSettingsPath)) {
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
if (!existingLocation.isEmpty()) {
|
if (!existingLocation.isEmpty()) {
|
||||||
@@ -102,11 +102,11 @@ public class SPDFApplication {
|
|||||||
}
|
}
|
||||||
propertyFiles.put(
|
propertyFiles.put(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
existingLocation + "file:" + InstallationPathConfig.getCustomSettingsPath());
|
existingLocation + "file:" + customSettingsPath.toString());
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Custom configuration file '{}' does not exist.",
|
"Custom configuration file '{}' does not exist.",
|
||||||
InstallationPathConfig.getCustomSettingsPath());
|
customSettingsPath.toString());
|
||||||
}
|
}
|
||||||
Properties finalProps = new Properties();
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ public class SPDFApplication {
|
|||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
|
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
|
||||||
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
|
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
log.error("Error creating directories: {}", e.getMessage());
|
log.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ public class SPDFApplication {
|
|||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
log.error("Error opening browser: {}", e.getMessage());
|
log.error("Error opening browser: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ public class AppConfig {
|
|||||||
|
|
||||||
@Bean(name = "rateLimit")
|
@Bean(name = "rateLimit")
|
||||||
public boolean rateLimit() {
|
public boolean rateLimit() {
|
||||||
String appName = System.getProperty("rateLimit");
|
String rateLimit = System.getProperty("rateLimit");
|
||||||
if (appName == null) appName = System.getenv("rateLimit");
|
if (rateLimit == null) rateLimit = System.getenv("rateLimit");
|
||||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "RunningInDocker")
|
@Bean(name = "RunningInDocker")
|
||||||
@@ -170,16 +170,14 @@ public class AppConfig {
|
|||||||
@Bean(name = "analyticsPrompt")
|
@Bean(name = "analyticsPrompt")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public boolean analyticsPrompt() {
|
public boolean analyticsPrompt() {
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() == null
|
return applicationProperties.getSystem().getEnableAnalytics() == null;
|
||||||
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsEnabled")
|
@Bean(name = "analyticsEnabled")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public boolean analyticsEnabled() {
|
public boolean analyticsEnabled() {
|
||||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() != null
|
return applicationProperties.getSystem().isAnalyticsEnabled();
|
||||||
&& Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "StirlingPDFLabel")
|
@Bean(name = "StirlingPDFLabel")
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ public class ConfigInitializer {
|
|||||||
log.info("Created settings file from template");
|
log.info("Created settings file from template");
|
||||||
} else {
|
} else {
|
||||||
// 2) Merge existing file with the template
|
// 2) Merge existing file with the template
|
||||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
|
||||||
URL templateResource = getClass().getClassLoader().getResource("settings.yml.template");
|
URL templateResource = getClass().getClassLoader().getResource("settings.yml.template");
|
||||||
if (templateResource == null) {
|
if (templateResource == null) {
|
||||||
throw new IOException("Resource not found: settings.yml.template");
|
throw new IOException("Resource not found: settings.yml.template");
|
||||||
@@ -49,160 +47,33 @@ public class ConfigInitializer {
|
|||||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2a) Read lines from both files
|
// Copy setting.yaml to a temp location so we can read lines
|
||||||
List<String> templateLines = Files.readAllLines(tempTemplatePath);
|
Path settingTempPath = Files.createTempFile("settings", ".yaml");
|
||||||
List<String> mainLines = Files.readAllLines(settingsPath);
|
try (InputStream in = Files.newInputStream(destPath)) {
|
||||||
|
Files.copy(in, settingTempPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
// 2b) Merge lines
|
YamlHelper settingsTemplateFile = new YamlHelper(tempTemplatePath);
|
||||||
List<String> mergedLines = mergeYamlLinesWithTemplate(templateLines, mainLines);
|
YamlHelper settingsFile = new YamlHelper(settingTempPath);
|
||||||
|
|
||||||
// 2c) Only write if there's an actual difference
|
boolean changesMade =
|
||||||
if (!mergedLines.equals(mainLines)) {
|
settingsTemplateFile.updateValuesFromYaml(settingsFile, settingsTemplateFile);
|
||||||
Files.write(settingsPath, mergedLines);
|
if (changesMade) {
|
||||||
|
settingsTemplateFile.save(destPath);
|
||||||
log.info("Settings file updated based on template changes.");
|
log.info("Settings file updated based on template changes.");
|
||||||
} else {
|
} else {
|
||||||
log.info("No changes detected; settings file left as-is.");
|
log.info("No changes detected; settings file left as-is.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.deleteIfExists(tempTemplatePath);
|
Files.deleteIfExists(tempTemplatePath);
|
||||||
|
Files.deleteIfExists(settingTempPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Ensure custom settings file exists
|
// 3) Ensure custom settings file exists
|
||||||
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||||
if (!Files.exists(customSettingsPath)) {
|
if (Files.notExists(customSettingsPath)) {
|
||||||
Files.createFile(customSettingsPath);
|
Files.createFile(customSettingsPath);
|
||||||
|
log.info("Created custom_settings file: {}", customSettingsPath.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge logic that: - Reads the template lines block-by-block (where a "block" = a key and all
|
|
||||||
* the lines that belong to it), - If the main file has that key, we keep the main file's block
|
|
||||||
* (preserving whitespace + inline comments). - Otherwise, we insert the template's block. - We
|
|
||||||
* also remove keys from main that no longer exist in the template.
|
|
||||||
*
|
|
||||||
* @param templateLines lines from settings.yml.template
|
|
||||||
* @param mainLines lines from the existing settings.yml
|
|
||||||
* @return merged lines
|
|
||||||
*/
|
|
||||||
private List<String> mergeYamlLinesWithTemplate(
|
|
||||||
List<String> templateLines, List<String> mainLines) {
|
|
||||||
|
|
||||||
// 1) Parse template lines into an ordered map: path -> Block
|
|
||||||
LinkedHashMap<String, Block> templateBlocks = parseYamlBlocks(templateLines);
|
|
||||||
|
|
||||||
// 2) Parse main lines into a map: path -> Block
|
|
||||||
LinkedHashMap<String, Block> mainBlocks = parseYamlBlocks(mainLines);
|
|
||||||
|
|
||||||
// 3) Build the final list by iterating template blocks in order
|
|
||||||
List<String> merged = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, Block> entry : templateBlocks.entrySet()) {
|
|
||||||
String path = entry.getKey();
|
|
||||||
Block templateBlock = entry.getValue();
|
|
||||||
|
|
||||||
if (mainBlocks.containsKey(path)) {
|
|
||||||
// If main has the same block, prefer main's lines
|
|
||||||
merged.addAll(mainBlocks.get(path).lines);
|
|
||||||
} else {
|
|
||||||
// Otherwise, add the template block
|
|
||||||
merged.addAll(templateBlock.lines);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a list of lines into a map of "path -> Block" where "Block" is all lines that belong to
|
|
||||||
* that key (including subsequent indented lines). Very naive approach that may not work with
|
|
||||||
* advanced YAML.
|
|
||||||
*/
|
|
||||||
private LinkedHashMap<String, Block> parseYamlBlocks(List<String> lines) {
|
|
||||||
LinkedHashMap<String, Block> blocks = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
Block currentBlock = null;
|
|
||||||
String currentPath = null;
|
|
||||||
|
|
||||||
for (String line : lines) {
|
|
||||||
if (isLikelyKeyLine(line)) {
|
|
||||||
// Found a new "key: ..." line
|
|
||||||
if (currentBlock != null && currentPath != null) {
|
|
||||||
blocks.put(currentPath, currentBlock);
|
|
||||||
}
|
|
||||||
currentBlock = new Block();
|
|
||||||
currentBlock.lines.add(line);
|
|
||||||
currentPath = computePathForLine(line);
|
|
||||||
} else {
|
|
||||||
// Continuation of current block (comments, blank lines, sub-lines)
|
|
||||||
if (currentBlock == null) {
|
|
||||||
// If file starts with comments/blank lines, treat as "header block" with path
|
|
||||||
// ""
|
|
||||||
currentBlock = new Block();
|
|
||||||
currentPath = "";
|
|
||||||
}
|
|
||||||
currentBlock.lines.add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentBlock != null && currentPath != null) {
|
|
||||||
blocks.put(currentPath, currentBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the line is likely "key:" or "key: value", ignoring comments/blank. Skips lines
|
|
||||||
* starting with "-" or "#".
|
|
||||||
*/
|
|
||||||
private boolean isLikelyKeyLine(String line) {
|
|
||||||
String trimmed = line.trim();
|
|
||||||
if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("-")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int colonIdx = trimmed.indexOf(':');
|
|
||||||
return (colonIdx > 0); // someKey:
|
|
||||||
}
|
|
||||||
|
|
||||||
// For a line like "security: ", returns "security" or "security.enableLogin"
|
|
||||||
// by looking at indentation. Very naive.
|
|
||||||
private static final Deque<String> pathStack = new ArrayDeque<>();
|
|
||||||
private static int currentIndentLevel = 0;
|
|
||||||
|
|
||||||
private String computePathForLine(String line) {
|
|
||||||
// count leading spaces
|
|
||||||
int leadingSpaces = 0;
|
|
||||||
for (char c : line.toCharArray()) {
|
|
||||||
if (c == ' ') leadingSpaces++;
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
// assume 2 spaces = 1 indent
|
|
||||||
int indentLevel = leadingSpaces / 2;
|
|
||||||
|
|
||||||
String trimmed = line.trim();
|
|
||||||
int colonIdx = trimmed.indexOf(':');
|
|
||||||
String keyName = trimmed.substring(0, colonIdx).trim();
|
|
||||||
|
|
||||||
// pop stack until we match the new indent level
|
|
||||||
while (currentIndentLevel >= indentLevel && !pathStack.isEmpty()) {
|
|
||||||
pathStack.pop();
|
|
||||||
currentIndentLevel--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// push the new key
|
|
||||||
pathStack.push(keyName);
|
|
||||||
currentIndentLevel = indentLevel;
|
|
||||||
|
|
||||||
// build path by reversing the stack
|
|
||||||
String[] arr = pathStack.toArray(new String[0]);
|
|
||||||
List<String> reversed = Arrays.asList(arr);
|
|
||||||
Collections.reverse(reversed);
|
|
||||||
return String.join(".", reversed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple holder for the lines that comprise a "block" (i.e. a key and its subsequent lines).
|
|
||||||
*/
|
|
||||||
private static class Block {
|
|
||||||
List<String> lines = new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class InitialSetup {
|
|||||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
// Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
uuid = UUID.randomUUID().toString();
|
uuid = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.UUID", uuid);
|
||||||
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ public class InitialSetup {
|
|||||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
// Generating a random UUID as the secret key
|
// Generating a random UUID as the secret key
|
||||||
secretKey = UUID.randomUUID().toString();
|
secretKey = UUID.randomUUID().toString();
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.key", secretKey);
|
||||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,8 @@ public class InitialSetup {
|
|||||||
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||||
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||||
if (!csrf) {
|
if (!csrf) {
|
||||||
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
|
GeneralUtils.saveKeyToSettings("security.csrfDisabled", false);
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
|
GeneralUtils.saveKeyToSettings("system.enableAnalytics", true);
|
||||||
applicationProperties.getSecurity().setCsrfDisabled(false);
|
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,14 +76,14 @@ public class InitialSetup {
|
|||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
if (StringUtils.isEmpty(termsUrl)) {
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl);
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
||||||
}
|
}
|
||||||
// Initialize Privacy Policy
|
// Initialize Privacy Policy
|
||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
if (StringUtils.isEmpty(privacyUrl)) {
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
||||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
|
GeneralUtils.saveKeyToSettings("legal.privacyPolicy", defaultPrivacyUrl);
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public class InitialSetup {
|
|||||||
appVersion = props.getProperty("version");
|
appVersion = props.getProperty("version");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.appVersion", appVersion);
|
||||||
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -46,26 +47,29 @@ public class InstallationPathConfig {
|
|||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
return System.getenv("APPDATA") + File.separator + "Stirling-PDF" + File.separator;
|
return Paths.get(
|
||||||
|
System.getenv("APPDATA"), // parent path
|
||||||
|
"Stirling-PDF")
|
||||||
|
.toString()
|
||||||
|
+ File.separator;
|
||||||
} else if (os.contains("mac")) {
|
} else if (os.contains("mac")) {
|
||||||
return System.getProperty("user.home")
|
return Paths.get(
|
||||||
+ File.separator
|
System.getProperty("user.home"),
|
||||||
+ "Library"
|
"Library",
|
||||||
+ File.separator
|
"Application Support",
|
||||||
+ "Application Support"
|
"Stirling-PDF")
|
||||||
+ File.separator
|
.toString()
|
||||||
+ "Stirling-PDF"
|
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
} else {
|
} else {
|
||||||
return System.getProperty("user.home")
|
return Paths.get(
|
||||||
+ File.separator
|
System.getProperty("user.home"), // parent path
|
||||||
+ ".config"
|
".config",
|
||||||
+ File.separator
|
"Stirling-PDF")
|
||||||
+ "Stirling-PDF"
|
.toString()
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "./";
|
return "." + File.separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPath() {
|
public static String getPath() {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -33,52 +32,48 @@ public class RuntimePathConfig {
|
|||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.basePath = InstallationPathConfig.getPath();
|
this.basePath = InstallationPathConfig.getPath();
|
||||||
|
|
||||||
String pipelinePath = basePath + "pipeline" + File.separator;
|
this.pipelinePath = Path.of(basePath, "pipeline").toString();
|
||||||
String watchedFoldersPath = pipelinePath + "watchedFolders" + File.separator;
|
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
||||||
String finishedFoldersPath = pipelinePath + "finishedFolders" + File.separator;
|
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
||||||
String webUiConfigsPath = pipelinePath + "defaultWebUIConfigs" + File.separator;
|
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
||||||
|
|
||||||
Pipeline pipeline = properties.getSystem().getCustomPaths().getPipeline();
|
Pipeline pipeline = properties.getSystem().getCustomPaths().getPipeline();
|
||||||
if (pipeline != null) {
|
|
||||||
if (!StringUtils.isEmpty(pipeline.getWatchedFoldersDir())) {
|
|
||||||
watchedFoldersPath = pipeline.getWatchedFoldersDir();
|
|
||||||
}
|
|
||||||
if (!StringUtils.isEmpty(pipeline.getFinishedFoldersDir())) {
|
|
||||||
finishedFoldersPath = pipeline.getFinishedFoldersDir();
|
|
||||||
}
|
|
||||||
if (!StringUtils.isEmpty(pipeline.getWebUIConfigsDir())) {
|
|
||||||
webUiConfigsPath = pipeline.getWebUIConfigsDir();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pipelinePath = pipelinePath;
|
this.pipelineWatchedFoldersPath =
|
||||||
this.pipelineWatchedFoldersPath = watchedFoldersPath;
|
resolvePath(
|
||||||
this.pipelineFinishedFoldersPath = finishedFoldersPath;
|
defaultWatchedFolders,
|
||||||
this.pipelineDefaultWebUiConfigs = webUiConfigsPath;
|
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
|
||||||
|
this.pipelineFinishedFoldersPath =
|
||||||
|
resolvePath(
|
||||||
|
defaultFinishedFolders,
|
||||||
|
pipeline != null ? pipeline.getFinishedFoldersDir() : null);
|
||||||
|
this.pipelineDefaultWebUiConfigs =
|
||||||
|
resolvePath(
|
||||||
|
defaultWebUIConfigs,
|
||||||
|
pipeline != null ? pipeline.getWebUIConfigsDir() : null);
|
||||||
|
|
||||||
boolean isDocker = isRunningInDocker();
|
boolean isDocker = isRunningInDocker();
|
||||||
|
|
||||||
// Initialize Operation paths
|
// Initialize Operation paths
|
||||||
String weasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
|
String defaultWeasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
|
||||||
String unoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
|
String defaultUnoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
|
||||||
|
|
||||||
// Check for custom operation paths
|
|
||||||
Operations operations = properties.getSystem().getCustomPaths().getOperations();
|
Operations operations = properties.getSystem().getCustomPaths().getOperations();
|
||||||
if (operations != null) {
|
this.weasyPrintPath =
|
||||||
if (!StringUtils.isEmpty(operations.getWeasyprint())) {
|
resolvePath(
|
||||||
weasyPrintPath = operations.getWeasyprint();
|
defaultWeasyPrintPath,
|
||||||
}
|
operations != null ? operations.getWeasyprint() : null);
|
||||||
if (!StringUtils.isEmpty(operations.getUnoconvert())) {
|
this.unoConvertPath =
|
||||||
unoConvertPath = operations.getUnoconvert();
|
resolvePath(
|
||||||
}
|
defaultUnoConvertPath,
|
||||||
}
|
operations != null ? operations.getUnoconvert() : null);
|
||||||
|
}
|
||||||
|
|
||||||
// Assign operations final fields
|
private String resolvePath(String defaultPath, String customPath) {
|
||||||
this.weasyPrintPath = weasyPrintPath;
|
return StringUtils.isNotBlank(customPath) ? customPath : defaultPath;
|
||||||
this.unoConvertPath = unoConvertPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRunningInDocker() {
|
private boolean isRunningInDocker() {
|
||||||
return Files.exists(Paths.get("/.dockerenv"));
|
return Files.exists(Path.of("/.dockerenv"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
479
src/main/java/stirling/software/SPDF/config/YamlHelper.java
Normal file
479
src/main/java/stirling/software/SPDF/config/YamlHelper.java
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.snakeyaml.engine.v2.api.Dump;
|
||||||
|
import org.snakeyaml.engine.v2.api.DumpSettings;
|
||||||
|
import org.snakeyaml.engine.v2.api.LoadSettings;
|
||||||
|
import org.snakeyaml.engine.v2.api.StreamDataWriter;
|
||||||
|
import org.snakeyaml.engine.v2.common.FlowStyle;
|
||||||
|
import org.snakeyaml.engine.v2.common.ScalarStyle;
|
||||||
|
import org.snakeyaml.engine.v2.composer.Composer;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.MappingNode;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.Node;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.NodeTuple;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.ScalarNode;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.SequenceNode;
|
||||||
|
import org.snakeyaml.engine.v2.nodes.Tag;
|
||||||
|
import org.snakeyaml.engine.v2.parser.ParserImpl;
|
||||||
|
import org.snakeyaml.engine.v2.scanner.StreamReader;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class YamlHelper {
|
||||||
|
|
||||||
|
// YAML dump settings with comment support and block flow style
|
||||||
|
private static final DumpSettings DUMP_SETTINGS =
|
||||||
|
DumpSettings.builder()
|
||||||
|
.setDumpComments(true)
|
||||||
|
.setWidth(Integer.MAX_VALUE)
|
||||||
|
.setDefaultFlowStyle(FlowStyle.BLOCK)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final String yamlContent; // Stores the entire YAML content as a string
|
||||||
|
|
||||||
|
private LoadSettings loadSettings =
|
||||||
|
LoadSettings.builder()
|
||||||
|
.setUseMarks(true)
|
||||||
|
.setMaxAliasesForCollections(Integer.MAX_VALUE)
|
||||||
|
.setAllowRecursiveKeys(true)
|
||||||
|
.setParseComments(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private Path originalFilePath;
|
||||||
|
private Node updatedRootNode;
|
||||||
|
|
||||||
|
// Constructor with custom LoadSettings and YAML string
|
||||||
|
public YamlHelper(LoadSettings loadSettings, String yamlContent) {
|
||||||
|
this.loadSettings = loadSettings;
|
||||||
|
this.yamlContent = yamlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor that reads YAML from a file path
|
||||||
|
public YamlHelper(Path originalFilePath) throws IOException {
|
||||||
|
this.yamlContent = Files.readString(originalFilePath);
|
||||||
|
this.originalFilePath = originalFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates values in the target YAML based on values from the source YAML. It ensures that only
|
||||||
|
* existing keys in the target YAML are updated.
|
||||||
|
*
|
||||||
|
* @return true if at least one key was updated, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean updateValuesFromYaml(YamlHelper sourceYaml, YamlHelper targetYaml) {
|
||||||
|
boolean updated = false;
|
||||||
|
Set<String> sourceKeys = sourceYaml.getAllKeys();
|
||||||
|
Set<String> targetKeys = targetYaml.getAllKeys();
|
||||||
|
|
||||||
|
for (String key : sourceKeys) {
|
||||||
|
String[] keyArray = key.split("\\.");
|
||||||
|
|
||||||
|
Object newValue = sourceYaml.getValueByExactKeyPath(keyArray);
|
||||||
|
Object currentValue = targetYaml.getValueByExactKeyPath(keyArray);
|
||||||
|
if (newValue != null
|
||||||
|
&& (!newValue.equals(currentValue) || !sourceKeys.equals(targetKeys))) {
|
||||||
|
boolean updatedKey = targetYaml.updateValue(Arrays.asList(keyArray), newValue);
|
||||||
|
if (updatedKey) updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a value in the YAML structure.
|
||||||
|
*
|
||||||
|
* @param keys The hierarchical keys leading to the value.
|
||||||
|
* @param newValue The new value to set.
|
||||||
|
* @return true if the value was updated, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean updateValue(List<String> keys, Object newValue) {
|
||||||
|
return updateValue(getRootNode(), keys, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateValue(Node node, List<String> keys, Object newValue) {
|
||||||
|
if (!(node instanceof MappingNode mappingNode)) return false;
|
||||||
|
|
||||||
|
List<NodeTuple> updatedTuples = new ArrayList<>();
|
||||||
|
boolean updated = false;
|
||||||
|
|
||||||
|
for (NodeTuple tuple : mappingNode.getValue()) {
|
||||||
|
ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;
|
||||||
|
if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {
|
||||||
|
updatedTuples.add(tuple);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node valueNode = tuple.getValueNode();
|
||||||
|
|
||||||
|
if (keys.size() == 1) {
|
||||||
|
Tag tag = valueNode.getTag();
|
||||||
|
Node newValueNode = null;
|
||||||
|
|
||||||
|
if (isAnyInteger(newValue)) {
|
||||||
|
newValueNode =
|
||||||
|
new ScalarNode(Tag.INT, String.valueOf(newValue), ScalarStyle.PLAIN);
|
||||||
|
} else if (isFloat(newValue)) {
|
||||||
|
Object floatValue = Float.valueOf(String.valueOf(newValue));
|
||||||
|
newValueNode =
|
||||||
|
new ScalarNode(
|
||||||
|
Tag.FLOAT, String.valueOf(floatValue), ScalarStyle.PLAIN);
|
||||||
|
} else if ("true".equals(newValue) || "false".equals(newValue)) {
|
||||||
|
newValueNode =
|
||||||
|
new ScalarNode(Tag.BOOL, String.valueOf(newValue), ScalarStyle.PLAIN);
|
||||||
|
} else if (newValue instanceof List<?> list) {
|
||||||
|
List<Node> sequenceNodes = new ArrayList<>();
|
||||||
|
for (Object item : list) {
|
||||||
|
Object obj = String.valueOf(item);
|
||||||
|
if (isAnyInteger(item)) {
|
||||||
|
tag = Tag.INT;
|
||||||
|
} else if (isFloat(item)) {
|
||||||
|
obj = Float.valueOf(String.valueOf(item));
|
||||||
|
tag = Tag.FLOAT;
|
||||||
|
} else if ("true".equals(item) || "false".equals(item)) {
|
||||||
|
tag = Tag.BOOL;
|
||||||
|
} else if (item == null || "null".equals(item)) {
|
||||||
|
tag = Tag.NULL;
|
||||||
|
} else {
|
||||||
|
tag = Tag.STR;
|
||||||
|
}
|
||||||
|
sequenceNodes.add(
|
||||||
|
new ScalarNode(tag, String.valueOf(obj), ScalarStyle.PLAIN));
|
||||||
|
}
|
||||||
|
newValueNode = new SequenceNode(Tag.SEQ, sequenceNodes, FlowStyle.FLOW);
|
||||||
|
} else if (tag == Tag.NULL) {
|
||||||
|
if ("true".equals(newValue)
|
||||||
|
|| "false".equals(newValue)
|
||||||
|
|| newValue instanceof Boolean) {
|
||||||
|
tag = Tag.BOOL;
|
||||||
|
}
|
||||||
|
newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);
|
||||||
|
} else {
|
||||||
|
newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);
|
||||||
|
}
|
||||||
|
copyComments(valueNode, newValueNode);
|
||||||
|
|
||||||
|
updatedTuples.add(new NodeTuple(keyNode, newValueNode));
|
||||||
|
updated = true;
|
||||||
|
} else if (valueNode instanceof MappingNode) {
|
||||||
|
updated = updateValue(valueNode, keys.subList(1, keys.size()), newValue);
|
||||||
|
updatedTuples.add(tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
mappingNode.getValue().clear();
|
||||||
|
mappingNode.getValue().addAll(updatedTuples);
|
||||||
|
}
|
||||||
|
setNewNode(node);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a value based on an exact key path.
|
||||||
|
*
|
||||||
|
* @param keys The key hierarchy leading to the value.
|
||||||
|
* @return The value if found, otherwise null.
|
||||||
|
*/
|
||||||
|
public Object getValueByExactKeyPath(String... keys) {
|
||||||
|
return getValueByExactKeyPath(getRootNode(), new ArrayDeque<>(List.of(keys)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getValueByExactKeyPath(Node node, Deque<String> keyQueue) {
|
||||||
|
if (!(node instanceof MappingNode mappingNode)) return null;
|
||||||
|
|
||||||
|
String currentKey = keyQueue.poll();
|
||||||
|
if (currentKey == null) return null;
|
||||||
|
|
||||||
|
for (NodeTuple tuple : mappingNode.getValue()) {
|
||||||
|
if (tuple.getKeyNode() instanceof ScalarNode keyNode
|
||||||
|
&& keyNode.getValue().equals(currentKey)) {
|
||||||
|
if (keyQueue.isEmpty()) {
|
||||||
|
Node valueNode = tuple.getValueNode();
|
||||||
|
|
||||||
|
if (valueNode instanceof ScalarNode scalarValueNode) {
|
||||||
|
return scalarValueNode.getValue();
|
||||||
|
} else if (valueNode instanceof MappingNode subMapping) {
|
||||||
|
return getValueByExactKeyPath(subMapping, keyQueue);
|
||||||
|
} else if (valueNode instanceof SequenceNode sequenceNode) {
|
||||||
|
List<Object> valuesList = new ArrayList<>();
|
||||||
|
for (Node o : sequenceNode.getValue()) {
|
||||||
|
if (o instanceof ScalarNode scalarValue) {
|
||||||
|
valuesList.add(scalarValue.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valuesList;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getValueByExactKeyPath(tuple.getValueNode(), keyQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> cachedKeys;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the set of all keys present in the YAML structure. Keys are returned as
|
||||||
|
* dot-separated paths for nested keys.
|
||||||
|
*
|
||||||
|
* @return A set containing all keys in dot notation.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllKeys() {
|
||||||
|
if (cachedKeys == null) {
|
||||||
|
cachedKeys = getAllKeys(getRootNode());
|
||||||
|
}
|
||||||
|
return cachedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all keys from the YAML node recursively.
|
||||||
|
*
|
||||||
|
* @param node The current YAML node.
|
||||||
|
* @param currentPath The accumulated path of keys.
|
||||||
|
* @param allKeys The set storing all collected keys.
|
||||||
|
*/
|
||||||
|
private Set<String> getAllKeys(Node node) {
|
||||||
|
Set<String> allKeys = new LinkedHashSet<>();
|
||||||
|
collectKeys(node, "", allKeys);
|
||||||
|
return allKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively traverses the YAML structure to collect all keys.
|
||||||
|
*
|
||||||
|
* @param node The current node in the YAML structure.
|
||||||
|
* @param currentPath The accumulated key path.
|
||||||
|
* @param allKeys The set storing collected keys.
|
||||||
|
*/
|
||||||
|
private void collectKeys(Node node, String currentPath, Set<String> allKeys) {
|
||||||
|
if (node instanceof MappingNode mappingNode) {
|
||||||
|
for (NodeTuple tuple : mappingNode.getValue()) {
|
||||||
|
if (tuple.getKeyNode() instanceof ScalarNode keyNode) {
|
||||||
|
String newPath =
|
||||||
|
currentPath.isEmpty()
|
||||||
|
? keyNode.getValue()
|
||||||
|
: currentPath + "." + keyNode.getValue();
|
||||||
|
allKeys.add(newPath);
|
||||||
|
collectKeys(tuple.getValueNode(), newPath, allKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the root node of the YAML document. If a new node was previously set, it is
|
||||||
|
* returned instead.
|
||||||
|
*
|
||||||
|
* @return The root node of the YAML structure.
|
||||||
|
*/
|
||||||
|
private Node getRootNode() {
|
||||||
|
if (this.updatedRootNode != null) {
|
||||||
|
return this.updatedRootNode;
|
||||||
|
}
|
||||||
|
Composer composer = new Composer(loadSettings, getParserImpl());
|
||||||
|
Optional<Node> rootNodeOpt = composer.getSingleNode();
|
||||||
|
if (rootNodeOpt.isPresent()) {
|
||||||
|
return rootNodeOpt.get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new root node, allowing modifications to be tracked.
|
||||||
|
*
|
||||||
|
* @param newRootNode The modified root node.
|
||||||
|
*/
|
||||||
|
public void setNewNode(Node newRootNode) {
|
||||||
|
this.updatedRootNode = newRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current root node (either the original or the updated one).
|
||||||
|
*
|
||||||
|
* @return The root node.
|
||||||
|
*/
|
||||||
|
public Node getUpdatedRootNode() {
|
||||||
|
if (this.updatedRootNode == null) {
|
||||||
|
this.updatedRootNode = getRootNode();
|
||||||
|
}
|
||||||
|
return this.updatedRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the YAML parser.
|
||||||
|
*
|
||||||
|
* @return The configured parser.
|
||||||
|
*/
|
||||||
|
private ParserImpl getParserImpl() {
|
||||||
|
return new ParserImpl(loadSettings, getStreamReader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stream reader for the YAML content.
|
||||||
|
*
|
||||||
|
* @return The configured stream reader.
|
||||||
|
*/
|
||||||
|
private StreamReader getStreamReader() {
|
||||||
|
return new StreamReader(loadSettings, yamlContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingNode save(Path saveFilePath) throws IOException {
|
||||||
|
if (!saveFilePath.equals(originalFilePath)) {
|
||||||
|
Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));
|
||||||
|
}
|
||||||
|
return (MappingNode) getUpdatedRootNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveOverride(Path saveFilePath) throws IOException {
|
||||||
|
Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a YAML node back to a YAML-formatted string.
|
||||||
|
*
|
||||||
|
* @param rootNode The root node to be converted.
|
||||||
|
* @return A YAML-formatted string.
|
||||||
|
*/
|
||||||
|
public String convertNodeToYaml(Node rootNode) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
StreamDataWriter streamDataWriter =
|
||||||
|
new StreamDataWriter() {
|
||||||
|
@Override
|
||||||
|
public void write(String str) {
|
||||||
|
writer.write(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(String str, int off, int len) {
|
||||||
|
writer.write(str, off, len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Dump(DUMP_SETTINGS).dumpNode(rootNode, streamDataWriter);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isParsable(String value, Function<String, ?> parser) {
|
||||||
|
try {
|
||||||
|
parser.apply(value);
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given object is an integer.
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents an integer, false otherwise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
||||||
|
public static boolean isInteger(Object object) {
|
||||||
|
if (object instanceof Integer
|
||||||
|
|| object instanceof Short
|
||||||
|
|| object instanceof Byte
|
||||||
|
|| object instanceof Long) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (object instanceof String str) {
|
||||||
|
return isParsable(str, Integer::parseInt);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given object is a floating-point number.
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents a float, false otherwise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
||||||
|
public static boolean isFloat(Object object) {
|
||||||
|
return (object instanceof Float || object instanceof Double)
|
||||||
|
|| (object instanceof String str && isParsable(str, Float::parseFloat));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given object is a short integer.
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents a short integer, false otherwise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
||||||
|
public static boolean isShort(Object object) {
|
||||||
|
return (object instanceof Long)
|
||||||
|
|| (object instanceof String str && isParsable(str, Short::parseShort));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given object is a byte.
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents a byte, false otherwise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
||||||
|
public static boolean isByte(Object object) {
|
||||||
|
return (object instanceof Long)
|
||||||
|
|| (object instanceof String str && isParsable(str, Byte::parseByte));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given object is a long integer.
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents a long integer, false otherwise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
|
||||||
|
public static boolean isLong(Object object) {
|
||||||
|
return (object instanceof Long)
|
||||||
|
|| (object instanceof String str && isParsable(str, Long::parseLong));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an object is any type of integer (short, byte, long, or int).
|
||||||
|
*
|
||||||
|
* @param object The object to check.
|
||||||
|
* @return True if the object represents an integer type, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isAnyInteger(Object object) {
|
||||||
|
return isInteger(object) || isShort(object) || isByte(object) || isLong(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies comments from an old node to a new one.
|
||||||
|
*
|
||||||
|
* @param oldNode The original node with comments.
|
||||||
|
* @param newValueNode The new node to which comments should be copied.
|
||||||
|
*/
|
||||||
|
private void copyComments(Node oldNode, Node newValueNode) {
|
||||||
|
if (oldNode == null || newValueNode == null) return;
|
||||||
|
if (oldNode.getBlockComments() != null) {
|
||||||
|
newValueNode.setBlockComments(oldNode.getBlockComments());
|
||||||
|
}
|
||||||
|
if (oldNode.getInLineComments() != null) {
|
||||||
|
newValueNode.setInLineComments(oldNode.getInLineComments());
|
||||||
|
}
|
||||||
|
if (oldNode.getEndComments() != null) {
|
||||||
|
newValueNode.setEndComments(oldNode.getEndComments());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,12 +45,12 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
if (!response.isCommitted()) {
|
if (!response.isCommitted()) {
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
if (authentication instanceof Saml2Authentication) {
|
if (authentication instanceof Saml2Authentication samlAuthentication) {
|
||||||
// Handle SAML2 logout redirection
|
// Handle SAML2 logout redirection
|
||||||
getRedirect_saml2(request, response, authentication);
|
getRedirect_saml2(request, response, samlAuthentication);
|
||||||
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
} else if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {
|
||||||
// Handle OAuth2 logout redirection
|
// Handle OAuth2 logout redirection
|
||||||
getRedirect_oauth2(request, response, authentication);
|
getRedirect_oauth2(request, response, oAuthToken);
|
||||||
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
// Handle Username/Password logout
|
// Handle Username/Password logout
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||||
@@ -71,13 +71,14 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
|
|
||||||
// Redirect for SAML2 authentication logout
|
// Redirect for SAML2 authentication logout
|
||||||
private void getRedirect_saml2(
|
private void getRedirect_saml2(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Saml2Authentication samlAuthentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
String registrationId = samlConf.getRegistrationId();
|
String registrationId = samlConf.getRegistrationId();
|
||||||
|
|
||||||
Saml2Authentication samlAuthentication = (Saml2Authentication) authentication;
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
CustomSaml2AuthenticatedPrincipal principal =
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
||||||
|
|
||||||
@@ -115,32 +116,46 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
|
|
||||||
// Redirect for OAuth2 authentication logout
|
// Redirect for OAuth2 authentication logout
|
||||||
private void getRedirect_oauth2(
|
private void getRedirect_oauth2(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
OAuth2AuthenticationToken oAuthToken)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String registrationId;
|
String registrationId;
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
String path = checkForErrors(request);
|
String path = checkForErrors(request);
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken oauthToken) {
|
|
||||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
|
||||||
} else {
|
|
||||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirectUrl = UrlUtils.getOrigin(request) + "/login?" + path;
|
String redirectUrl = UrlUtils.getOrigin(request) + "/login?" + path;
|
||||||
|
registrationId = oAuthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
// Redirect based on OAuth2 provider
|
||||||
switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase()) {
|
||||||
case "keycloak" -> {
|
case "keycloak" -> {
|
||||||
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
||||||
String logoutUrl =
|
|
||||||
keycloak.getIssuer()
|
boolean isKeycloak = !keycloak.getIssuer().isBlank();
|
||||||
+ "/protocol/openid-connect/logout"
|
boolean isCustomOAuth = !oauth.getIssuer().isBlank();
|
||||||
+ "?client_id="
|
|
||||||
+ keycloak.getClientId()
|
String logoutUrl = redirectUrl;
|
||||||
+ "&post_logout_redirect_uri="
|
|
||||||
+ response.encodeRedirectURL(redirectUrl);
|
if (isKeycloak) {
|
||||||
log.info("Redirecting to Keycloak logout URL: {}", logoutUrl);
|
logoutUrl = keycloak.getIssuer();
|
||||||
|
} else if (isCustomOAuth) {
|
||||||
|
logoutUrl = oauth.getIssuer();
|
||||||
|
}
|
||||||
|
if (isKeycloak || isCustomOAuth) {
|
||||||
|
logoutUrl +=
|
||||||
|
"/protocol/openid-connect/logout"
|
||||||
|
+ "?client_id="
|
||||||
|
+ oauth.getClientId()
|
||||||
|
+ "&post_logout_redirect_uri="
|
||||||
|
+ response.encodeRedirectURL(redirectUrl);
|
||||||
|
log.info("Redirecting to Keycloak logout URL: {}", logoutUrl);
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
"No redirect URL for {} available. Redirecting to default logout URL: {}",
|
||||||
|
registrationId,
|
||||||
|
logoutUrl);
|
||||||
|
}
|
||||||
response.sendRedirect(logoutUrl);
|
response.sendRedirect(logoutUrl);
|
||||||
}
|
}
|
||||||
case "github", "google" -> {
|
case "github", "google" -> {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public class IPRateLimitingFilter implements Filter {
|
|||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
if (request instanceof HttpServletRequest) {
|
if (request instanceof HttpServletRequest httpServletRequest) {
|
||||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
HttpServletRequest httpRequest = httpServletRequest;
|
||||||
String method = httpRequest.getMethod();
|
String method = httpRequest.getMethod();
|
||||||
String requestURI = httpRequest.getRequestURI();
|
String requestURI = httpRequest.getRequestURI();
|
||||||
// Check if the request is for static resources
|
// Check if the request is for static resources
|
||||||
|
|||||||
@@ -36,12 +36,13 @@ public class InitialSecuritySetup {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
try {
|
try {
|
||||||
if (databaseService.hasBackup()) {
|
|
||||||
databaseService.importDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userService.hasUsers()) {
|
if (!userService.hasUsers()) {
|
||||||
initializeAdminUser();
|
if (databaseService.hasBackup()) {
|
||||||
|
databaseService.importDatabase();
|
||||||
|
} else {
|
||||||
|
initializeAdminUser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userService.migrateOauth2ToSSO();
|
userService.migrateOauth2ToSSO();
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ public class SecurityConfiguration {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("SAML 2 login is not enabled. Using default.");
|
log.debug("SAML 2 login is not enabled. Using default.");
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
|
|||||||
@@ -123,9 +123,11 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request header.\n"
|
"Authentication required. Please provide a X-API-KEY in request"
|
||||||
|
+ " header.\n"
|
||||||
+ "This is found in Settings -> Account Settings -> API Key\n"
|
+ "This is found in Settings -> Account Settings -> API Key\n"
|
||||||
+ "Alternatively you can disable authentication if this is unexpected");
|
+ "Alternatively you can disable authentication if this is"
|
||||||
|
+ " unexpected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,21 +143,21 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Extract username and determine the login method
|
// Extract username and determine the login method
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
username = ((UserDetails) principal).getUsername();
|
username = detailsUser.getUsername();
|
||||||
loginMethod = LoginMethod.USERDETAILS;
|
loginMethod = LoginMethod.USERDETAILS;
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
username = ((OAuth2User) principal).getName();
|
username = oAuth2User.getName();
|
||||||
loginMethod = LoginMethod.OAUTH2USER;
|
loginMethod = LoginMethod.OAUTH2USER;
|
||||||
OAUTH2 oAuth = securityProp.getOauth2();
|
OAUTH2 oAuth = securityProp.getOauth2();
|
||||||
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
username = saml2User.name();
|
||||||
loginMethod = LoginMethod.SAML2USER;
|
loginMethod = LoginMethod.SAML2USER;
|
||||||
SAML2 saml2 = securityProp.getSaml2();
|
SAML2 saml2 = securityProp.getSaml2();
|
||||||
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String stringUser) {
|
||||||
username = (String) principal;
|
username = stringUser;
|
||||||
loginMethod = LoginMethod.STRINGUSER;
|
loginMethod = LoginMethod.STRINGUSER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +172,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
boolean isUserDisabled = userService.isUserDisabled(username);
|
boolean isUserDisabled = userService.isUserDisabled(username);
|
||||||
|
|
||||||
boolean notSsoLogin =
|
boolean notSsoLogin =
|
||||||
!loginMethod.equals(LoginMethod.OAUTH2USER)
|
!LoginMethod.OAUTH2USER.equals(loginMethod)
|
||||||
&& !loginMethod.equals(LoginMethod.SAML2USER);
|
&& !LoginMethod.SAML2USER.equals(loginMethod);
|
||||||
|
|
||||||
// Block user registration if not allowed by configuration
|
// Block user registration if not allowed by configuration
|
||||||
if (blockRegistration && !isUserExists) {
|
if (blockRegistration && !isUserExists) {
|
||||||
|
|||||||
@@ -121,12 +121,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public User addApiKeyToUser(String username) {
|
public User addApiKeyToUser(String username) {
|
||||||
Optional<User> user = findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = findByUsernameIgnoreCase(username);
|
||||||
if (user.isPresent()) {
|
User user = saveUser(userOpt, generateApiKey());
|
||||||
user.get().setApiKey(generateApiKey());
|
try {
|
||||||
return userRepository.save(user.get());
|
databaseService.exportDatabase();
|
||||||
|
} catch (SQLException | UnsupportedProviderException e) {
|
||||||
|
log.error("Error exporting database after adding API key to user", e);
|
||||||
}
|
}
|
||||||
throw new UsernameNotFoundException("User not found");
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User refreshApiKeyForUser(String username) {
|
public User refreshApiKeyForUser(String username) {
|
||||||
@@ -171,6 +173,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
saveUser(username, authenticationType, Role.USER.getRoleId());
|
saveUser(username, authenticationType, Role.USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private User saveUser(Optional<User> user, String apiKey) {
|
||||||
|
if (user.isPresent()) {
|
||||||
|
user.get().setApiKey(apiKey);
|
||||||
|
return userRepository.save(user.get());
|
||||||
|
}
|
||||||
|
throw new UsernameNotFoundException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
public void saveUser(String username, AuthenticationType authenticationType, String role)
|
||||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
@@ -375,14 +385,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||||
for (SessionInformation sessionsInformation :
|
for (SessionInformation sessionsInformation :
|
||||||
sessionRegistry.getAllSessions(principal, false)) {
|
sessionRegistry.getAllSessions(principal, false)) {
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
usernameP = userDetails.getUsername();
|
usernameP = detailsUser.getUsername();
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
usernameP = saml2User.name();
|
usernameP = saml2User.name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String stringUser) {
|
||||||
usernameP = (String) principal;
|
usernameP = stringUser;
|
||||||
}
|
}
|
||||||
if (usernameP.equalsIgnoreCase(username)) {
|
if (usernameP.equalsIgnoreCase(username)) {
|
||||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||||
@@ -394,17 +404,17 @@ public class UserService implements UserServiceInterface {
|
|||||||
public String getCurrentUsername() {
|
public String getCurrentUsername() {
|
||||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
return ((UserDetails) principal).getUsername();
|
return detailsUser.getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
return ((OAuth2User) principal)
|
return oAuth2User.getAttribute(
|
||||||
.getAttribute(
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
return saml2User.name();
|
||||||
return ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
} else if (principal instanceof String stringUser) {
|
||||||
} else {
|
return stringUser;
|
||||||
return principal.toString();
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package stirling.software.SPDF.config.security.database;
|
package stirling.software.SPDF.config.security.database;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -37,8 +35,8 @@ public class DatabaseConfig {
|
|||||||
DATASOURCE_DEFAULT_URL =
|
DATASOURCE_DEFAULT_URL =
|
||||||
"jdbc:h2:file:"
|
"jdbc:h2:file:"
|
||||||
+ InstallationPathConfig.getConfigPath()
|
+ InstallationPathConfig.getConfigPath()
|
||||||
+ File.separator
|
|
||||||
+ "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
+ "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
||||||
|
log.debug("Database URL: {}", DATASOURCE_DEFAULT_URL);
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.runningEE = runningEE;
|
this.runningEE = runningEE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof OAuth2AuthenticationException) {
|
if (exception instanceof OAuth2AuthenticationException oAuth2Exception) {
|
||||||
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
|
OAuth2Error error = oAuth2Exception.getError();
|
||||||
|
|
||||||
String errorCode = error.getErrorCode();
|
String errorCode = error.getErrorCode();
|
||||||
|
|
||||||
if (error.getErrorCode().equals("Password must not be null")) {
|
if ("Password must not be null".equals(error.getErrorCode())) {
|
||||||
errorCode = "userAlreadyExistsWeb";
|
errorCode = "userAlreadyExistsWeb";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = "";
|
String username = "";
|
||||||
|
|
||||||
if (principal instanceof OAuth2User oauthUser) {
|
if (principal instanceof OAuth2User oAuth2User) {
|
||||||
username = oauthUser.getName();
|
username = oAuth2User.getName();
|
||||||
} else if (principal instanceof UserDetails oauthUser) {
|
} else if (principal instanceof UserDetails detailsUser) {
|
||||||
username = oauthUser.getUsername();
|
username = detailsUser.getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the saved request
|
// Get the saved request
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ public class OAuth2Configuration {
|
|||||||
.scope(oidcProvider.getScopes())
|
.scope(oidcProvider.getScopes())
|
||||||
.userNameAttributeName(oidcProvider.getUseAsUsername().getName())
|
.userNameAttributeName(oidcProvider.getUseAsUsername().getName())
|
||||||
.clientName(clientName)
|
.clientName(clientName)
|
||||||
.redirectUri(REDIRECT_URI_PATH + name)
|
.redirectUri(REDIRECT_URI_PATH + "oidc")
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
.authorizationGrantType(AUTHORIZATION_CODE)
|
||||||
.build())
|
.build())
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
@@ -230,7 +230,7 @@ public class OAuth2Configuration {
|
|||||||
// Add existing OAUTH2 Authorities
|
// Add existing OAUTH2 Authorities
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||||
// Add Authorities from database for existing user, if user is present.
|
// Add Authorities from database for existing user, if user is present.
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
if (authority instanceof OAuth2UserAuthority oAuth2Auth) {
|
||||||
String useAsUsername =
|
String useAsUsername =
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getSecurity()
|
.getSecurity()
|
||||||
@@ -238,7 +238,7 @@ public class OAuth2Configuration {
|
|||||||
.getUseAsUsername();
|
.getUseAsUsername();
|
||||||
Optional<User> userOpt =
|
Optional<User> userOpt =
|
||||||
userService.findByUsernameIgnoreCase(
|
userService.findByUsernameIgnoreCase(
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
(String) oAuth2Auth.getAttributes().get(useAsUsername));
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
mappedAuthorities.add(
|
mappedAuthorities.add(
|
||||||
|
|||||||
@@ -40,13 +40,12 @@ public class CertificateUtils {
|
|||||||
Object object = pemParser.readObject();
|
Object object = pemParser.readObject();
|
||||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
|
||||||
|
|
||||||
if (object instanceof PEMKeyPair) {
|
if (object instanceof PEMKeyPair keypair) {
|
||||||
// Handle traditional RSA private key format
|
// Handle traditional RSA private key format
|
||||||
PEMKeyPair keypair = (PEMKeyPair) object;
|
|
||||||
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
|
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
|
||||||
} else if (object instanceof PrivateKeyInfo) {
|
} else if (object instanceof PrivateKeyInfo keyInfo) {
|
||||||
// Handle PKCS#8 format
|
// Handle PKCS#8 format
|
||||||
return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object);
|
return (RSAPrivateKey) converter.getPrivateKey(keyInfo);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Unsupported key format: "
|
"Unsupported key format: "
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||||
|
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
public record CustomSaml2AuthenticatedPrincipal(String name, Map<String, List<Object>> attributes, String nameId, List<String> sessionIndexes)
|
public record CustomSaml2AuthenticatedPrincipal(
|
||||||
|
String name,
|
||||||
|
Map<String, List<Object>> attributes,
|
||||||
|
String nameId,
|
||||||
|
List<String> sessionIndexes)
|
||||||
implements Saml2AuthenticatedPrincipal, Serializable {
|
implements Saml2AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -20,5 +24,4 @@ public record CustomSaml2AuthenticatedPrincipal(String name, Map<String, List<Ob
|
|||||||
public Map<String, List<Object>> getAttributes() {
|
public Map<String, List<Object>> getAttributes() {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
log.debug("Starting SAML2 authentication success handling");
|
log.debug("Starting SAML2 authentication success handling");
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2Principal) {
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
String username = saml2Principal.name();
|
||||||
log.debug("Authenticated principal found for user: {}", username);
|
log.debug("Authenticated principal found for user: {}", username);
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
List<SessionInformation> sessionInformations = new ArrayList<>();
|
List<SessionInformation> sessionInformations = new ArrayList<>();
|
||||||
String principalName = null;
|
String principalName = null;
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = detailsUser.getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
principalName = saml2User.name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String stringUser) {
|
||||||
principalName = (String) principal;
|
principalName = stringUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
@@ -74,14 +74,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
public void registerNewSession(String sessionId, Object principal) {
|
public void registerNewSession(String sessionId, Object principal) {
|
||||||
String principalName = null;
|
String principalName = null;
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = detailsUser.getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
principalName = saml2User.name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String stringUser) {
|
||||||
principalName = (String) principal;
|
principalName = stringUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ public class SettingsController {
|
|||||||
@PostMapping("/update-enable-analytics")
|
@PostMapping("/update-enable-analytics")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||||
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
|
||||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||||
.body(
|
.body(
|
||||||
"Setting has already been set, To adjust please edit "
|
"Setting has already been set, To adjust please edit "
|
||||||
+ InstallationPathConfig.getSettingsPath());
|
+ InstallationPathConfig.getSettingsPath());
|
||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
GeneralUtils.saveKeyToSettings("system.enableAnalytics", enabled);
|
||||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
applicationProperties.getSystem().setEnableAnalytics(enabled);
|
||||||
return ResponseEntity.ok("Updated");
|
return ResponseEntity.ok("Updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,14 +297,14 @@ public class UserController {
|
|||||||
for (Object principal : principals) {
|
for (Object principal : principals) {
|
||||||
List<SessionInformation> sessionsInformation =
|
List<SessionInformation> sessionsInformation =
|
||||||
sessionRegistry.getAllSessions(principal, false);
|
sessionRegistry.getAllSessions(principal, false);
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
userNameP = ((UserDetails) principal).getUsername();
|
userNameP = detailsUser.getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
userNameP = ((OAuth2User) principal).getName();
|
userNameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
userNameP = saml2User.name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String stringUser) {
|
||||||
userNameP = (String) principal;
|
userNameP = stringUser;
|
||||||
}
|
}
|
||||||
if (userNameP.equalsIgnoreCase(username)) {
|
if (userNameP.equalsIgnoreCase(username)) {
|
||||||
for (SessionInformation sessionInfo : sessionsInformation) {
|
for (SessionInformation sessionInfo : sessionsInformation) {
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ public class AutoSplitPdfController {
|
|||||||
private static String decodeQRCode(BufferedImage bufferedImage) {
|
private static String decodeQRCode(BufferedImage bufferedImage) {
|
||||||
LuminanceSource source;
|
LuminanceSource source;
|
||||||
|
|
||||||
if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) {
|
if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte dataBufferByte) {
|
||||||
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
|
byte[] pixels = dataBufferByte.getData();
|
||||||
source =
|
source =
|
||||||
new PlanarYUVLuminanceSource(
|
new PlanarYUVLuminanceSource(
|
||||||
pixels,
|
pixels,
|
||||||
@@ -73,8 +73,9 @@ public class AutoSplitPdfController {
|
|||||||
bufferedImage.getWidth(),
|
bufferedImage.getWidth(),
|
||||||
bufferedImage.getHeight(),
|
bufferedImage.getHeight(),
|
||||||
false);
|
false);
|
||||||
} else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
} else if (bufferedImage.getRaster().getDataBuffer()
|
||||||
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
instanceof DataBufferInt dataBufferInt) {
|
||||||
|
int[] pixels = dataBufferInt.getData();
|
||||||
byte[] newPixels = new byte[pixels.length];
|
byte[] newPixels = new byte[pixels.length];
|
||||||
for (int i = 0; i < pixels.length; i++) {
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
newPixels[i] = (byte) (pixels[i] & 0xff);
|
newPixels[i] = (byte) (pixels[i] & 0xff);
|
||||||
@@ -91,7 +92,8 @@ public class AutoSplitPdfController {
|
|||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data");
|
"BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed"
|
||||||
|
+ " int), byte gray, or 3-byte/4-byte RGB image data");
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
@@ -108,7 +110,10 @@ public class AutoSplitPdfController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Auto split PDF pages into separate documents",
|
summary = "Auto split PDF pages into separate documents",
|
||||||
description =
|
description =
|
||||||
"This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO")
|
"This endpoint accepts a PDF file, scans each page for a specific QR code, and"
|
||||||
|
+ " splits the document at the QR code boundaries. The output is a zip file"
|
||||||
|
+ " containing each separate PDF document. Input:PDF Output:ZIP-PDF"
|
||||||
|
+ " Type:SISO")
|
||||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
|
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ public class CompressController {
|
|||||||
if (res != null && res.getXObjectNames() != null) {
|
if (res != null && res.getXObjectNames() != null) {
|
||||||
for (COSName name : res.getXObjectNames()) {
|
for (COSName name : res.getXObjectNames()) {
|
||||||
PDXObject xobj = res.getXObject(name);
|
PDXObject xobj = res.getXObject(name);
|
||||||
if (xobj instanceof PDImageXObject) {
|
if (xobj instanceof PDImageXObject image) {
|
||||||
PDImageXObject image = (PDImageXObject) xobj;
|
|
||||||
BufferedImage bufferedImage = image.getImage();
|
BufferedImage bufferedImage = image.getImage();
|
||||||
|
|
||||||
int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
|
int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
|
||||||
@@ -119,7 +118,8 @@ public class CompressController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Optimize PDF file",
|
summary = "Optimize PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
|
"This endpoint accepts a PDF file and optimizes it based on the provided"
|
||||||
|
+ " parameters. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request)
|
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@@ -221,7 +221,8 @@ public class CompressController {
|
|||||||
// Check if optimized file is larger than the original
|
// Check if optimized file is larger than the original
|
||||||
if (pdfBytes.length > inputFileSize) {
|
if (pdfBytes.length > inputFileSize) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Optimized file is larger than the original. Returning the original file instead.");
|
"Optimized file is larger than the original. Returning the original file"
|
||||||
|
+ " instead.");
|
||||||
finalFile = tempInputFile;
|
finalFile = tempInputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,9 +118,8 @@ public class PipelineProcessor {
|
|||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
body.add("fileInput", file);
|
body.add("fileInput", file);
|
||||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
if (entry.getValue() instanceof List) {
|
if (entry.getValue() instanceof List<?> entryList) {
|
||||||
List<?> list = (List<?>) entry.getValue();
|
for (Object item : entryList) {
|
||||||
for (Object item : list) {
|
|
||||||
body.add(entry.getKey(), item);
|
body.add(entry.getKey(), item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -139,7 +138,7 @@ public class PipelineProcessor {
|
|||||||
log.info("Skipping file due to filtering {}", operation);
|
log.info("Skipping file due to filtering {}", operation);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
if (!HttpStatus.OK.equals(response.getStatusCode())) {
|
||||||
logPrintStream.println("Error: " + response.getBody());
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -180,9 +179,8 @@ public class PipelineProcessor {
|
|||||||
body.add("fileInput", file);
|
body.add("fileInput", file);
|
||||||
}
|
}
|
||||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
if (entry.getValue() instanceof List) {
|
if (entry.getValue() instanceof List<?> entryList) {
|
||||||
List<?> list = (List<?>) entry.getValue();
|
for (Object item : entryList) {
|
||||||
for (Object item : list) {
|
|
||||||
body.add(entry.getKey(), item);
|
body.add(entry.getKey(), item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -191,7 +189,7 @@ public class PipelineProcessor {
|
|||||||
}
|
}
|
||||||
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
// Handle the response
|
// Handle the response
|
||||||
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
if (HttpStatus.OK.equals(response.getStatusCode())) {
|
||||||
processOutputFiles(operation, response, newOutputFiles);
|
processOutputFiles(operation, response, newOutputFiles);
|
||||||
} else {
|
} else {
|
||||||
// Log error if the response status is not OK
|
// Log error if the response status is not OK
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ public class CertSignController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sign PDF with a Digital Certificate",
|
summary = "Sign PDF with a Digital Certificate",
|
||||||
description =
|
description =
|
||||||
"This endpoint accepts a PDF file, a digital certificate and related information to sign"
|
"This endpoint accepts a PDF file, a digital certificate and related"
|
||||||
+ " the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF"
|
+ " information to sign the PDF. It then returns the digitally signed PDF"
|
||||||
+ " Type:SISO")
|
+ " file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile pdf = request.getFileInput();
|
MultipartFile pdf = request.getFileInput();
|
||||||
@@ -201,17 +201,14 @@ public class CertSignController {
|
|||||||
Object pemObject = pemParser.readObject();
|
Object pemObject = pemParser.readObject();
|
||||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||||
PrivateKeyInfo pkInfo;
|
PrivateKeyInfo pkInfo;
|
||||||
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo pkcs8EncryptedPrivateKeyInfo) {
|
||||||
InputDecryptorProvider decProv =
|
InputDecryptorProvider decProv =
|
||||||
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
||||||
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
pkInfo = pkcs8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(decProv);
|
||||||
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
} else if (pemObject instanceof PEMEncryptedKeyPair pemEncryptedKeyPair) {
|
||||||
PEMDecryptorProvider decProv =
|
PEMDecryptorProvider decProv =
|
||||||
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
||||||
pkInfo =
|
pkInfo = pemEncryptedKeyPair.decryptKeyPair(decProv).getPrivateKeyInfo();
|
||||||
((PEMEncryptedKeyPair) pemObject)
|
|
||||||
.decryptKeyPair(decProv)
|
|
||||||
.getPrivateKeyInfo();
|
|
||||||
} else {
|
} else {
|
||||||
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,10 +214,7 @@ public class GetInfoOnPDF {
|
|||||||
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
||||||
for (PDPage page : pdfBoxDoc.getPages()) {
|
for (PDPage page : pdfBoxDoc.getPages()) {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation instanceof PDAnnotationFileAttachment) {
|
if (annotation instanceof PDAnnotationFileAttachment fileAttachmentAnnotation) {
|
||||||
PDAnnotationFileAttachment fileAttachmentAnnotation =
|
|
||||||
(PDAnnotationFileAttachment) annotation;
|
|
||||||
|
|
||||||
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
||||||
attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName());
|
attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName());
|
||||||
attachmentNode.put("Description", fileAttachmentAnnotation.getContents());
|
attachmentNode.put("Description", fileAttachmentAnnotation.getContents());
|
||||||
@@ -437,9 +434,7 @@ public class GetInfoOnPDF {
|
|||||||
|
|
||||||
for (COSName name : resources.getXObjectNames()) {
|
for (COSName name : resources.getXObjectNames()) {
|
||||||
PDXObject xObject = resources.getXObject(name);
|
PDXObject xObject = resources.getXObject(name);
|
||||||
if (xObject instanceof PDImageXObject) {
|
if (xObject instanceof PDImageXObject image) {
|
||||||
PDImageXObject image = (PDImageXObject) xObject;
|
|
||||||
|
|
||||||
ObjectNode imageNode = objectMapper.createObjectNode();
|
ObjectNode imageNode = objectMapper.createObjectNode();
|
||||||
imageNode.put("Width", image.getWidth());
|
imageNode.put("Width", image.getWidth());
|
||||||
imageNode.put("Height", image.getHeight());
|
imageNode.put("Height", image.getHeight());
|
||||||
@@ -462,10 +457,8 @@ public class GetInfoOnPDF {
|
|||||||
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
||||||
|
|
||||||
for (PDAnnotation annotation : annotations) {
|
for (PDAnnotation annotation : annotations) {
|
||||||
if (annotation instanceof PDAnnotationLink) {
|
if (annotation instanceof PDAnnotationLink linkAnnotation) {
|
||||||
PDAnnotationLink linkAnnotation = (PDAnnotationLink) annotation;
|
if (linkAnnotation.getAction() instanceof PDActionURI uriAction) {
|
||||||
if (linkAnnotation.getAction() instanceof PDActionURI) {
|
|
||||||
PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction();
|
|
||||||
String uri = uriAction.getURI();
|
String uri = uriAction.getURI();
|
||||||
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
||||||
}
|
}
|
||||||
@@ -541,8 +534,7 @@ public class GetInfoOnPDF {
|
|||||||
Iterable<COSName> colorSpaceNames = resources.getColorSpaceNames();
|
Iterable<COSName> colorSpaceNames = resources.getColorSpaceNames();
|
||||||
for (COSName name : colorSpaceNames) {
|
for (COSName name : colorSpaceNames) {
|
||||||
PDColorSpace colorSpace = resources.getColorSpace(name);
|
PDColorSpace colorSpace = resources.getColorSpace(name);
|
||||||
if (colorSpace instanceof PDICCBased) {
|
if (colorSpace instanceof PDICCBased iccBased) {
|
||||||
PDICCBased iccBased = (PDICCBased) colorSpace;
|
|
||||||
PDStream iccData = iccBased.getPDStream();
|
PDStream iccData = iccBased.getPDStream();
|
||||||
byte[] iccBytes = iccData.toByteArray();
|
byte[] iccBytes = iccData.toByteArray();
|
||||||
|
|
||||||
@@ -698,12 +690,10 @@ public class GetInfoOnPDF {
|
|||||||
ArrayNode elementsArray = objectMapper.createArrayNode();
|
ArrayNode elementsArray = objectMapper.createArrayNode();
|
||||||
if (nodes != null) {
|
if (nodes != null) {
|
||||||
for (Object obj : nodes) {
|
for (Object obj : nodes) {
|
||||||
if (obj instanceof PDStructureNode) {
|
if (obj instanceof PDStructureNode node) {
|
||||||
PDStructureNode node = (PDStructureNode) obj;
|
|
||||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||||
|
|
||||||
if (node instanceof PDStructureElement) {
|
if (node instanceof PDStructureElement structureElement) {
|
||||||
PDStructureElement structureElement = (PDStructureElement) node;
|
|
||||||
elementNode.put("Type", structureElement.getStructureType());
|
elementNode.put("Type", structureElement.getStructureType());
|
||||||
elementNode.put("Content", getContent(structureElement));
|
elementNode.put("Content", getContent(structureElement));
|
||||||
|
|
||||||
@@ -724,8 +714,7 @@ public class GetInfoOnPDF {
|
|||||||
StringBuilder contentBuilder = new StringBuilder();
|
StringBuilder contentBuilder = new StringBuilder();
|
||||||
|
|
||||||
for (Object item : structureElement.getKids()) {
|
for (Object item : structureElement.getKids()) {
|
||||||
if (item instanceof COSString) {
|
if (item instanceof COSString cosString) {
|
||||||
COSString cosString = (COSString) item;
|
|
||||||
contentBuilder.append(cosString.getString());
|
contentBuilder.append(cosString.getString());
|
||||||
} else if (item instanceof PDStructureElement) {
|
} else if (item instanceof PDStructureElement) {
|
||||||
// For simplicity, we're handling only COSString and PDStructureElement here
|
// For simplicity, we're handling only COSString and PDStructureElement here
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ public class SanitizeController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sanitize a PDF file",
|
summary = "Sanitize a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
"This endpoint processes a PDF file and removes specific elements based on the"
|
||||||
|
+ " provided options. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@@ -103,8 +104,7 @@ public class SanitizeController {
|
|||||||
|
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation instanceof PDAnnotationWidget) {
|
if (annotation instanceof PDAnnotationWidget widget) {
|
||||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
|
||||||
PDAction action = widget.getAction();
|
PDAction action = widget.getAction();
|
||||||
if (action instanceof PDActionJavaScript) {
|
if (action instanceof PDActionJavaScript) {
|
||||||
widget.setAction(null);
|
widget.setAction(null);
|
||||||
@@ -157,12 +157,12 @@ public class SanitizeController {
|
|||||||
private void sanitizeLinks(PDDocument document) throws IOException {
|
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation != null && annotation instanceof PDAnnotationLink) {
|
if (annotation != null && annotation instanceof PDAnnotationLink linkAnnotation) {
|
||||||
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
PDAction action = linkAnnotation.getAction();
|
||||||
if (action != null
|
if (action != null
|
||||||
&& (action instanceof PDActionLaunch
|
&& (action instanceof PDActionLaunch
|
||||||
|| action instanceof PDActionURI)) {
|
|| action instanceof PDActionURI)) {
|
||||||
((PDAnnotationLink) annotation).setAction(null);
|
linkAnnotation.setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,9 +123,7 @@ public class AccountWebController {
|
|||||||
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
||||||
|
|
||||||
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
|
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
|
||||||
return "redirect:"
|
return "redirect:" + request.getRequestURL() + saml2AuthenticationPath;
|
||||||
+ request.getRequestURL()
|
|
||||||
+ saml2AuthenticationPath;
|
|
||||||
} else {
|
} else {
|
||||||
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
||||||
}
|
}
|
||||||
@@ -329,21 +327,21 @@ public class AccountWebController {
|
|||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
|
|
||||||
// Retrieve username and other attributes and add login attributes to the model
|
// Retrieve username and other attributes and add login attributes to the model
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
username = userDetails.getUsername();
|
username = detailsUser.getUsername();
|
||||||
model.addAttribute("oAuth2Login", false);
|
model.addAttribute("oAuth2Login", false);
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User userDetails) {
|
if (principal instanceof OAuth2User oAuth2User) {
|
||||||
username = userDetails.getName();
|
username = oAuth2User.getName();
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
username = userDetails.name();
|
username = saml2User.name();
|
||||||
model.addAttribute("saml2Login", true);
|
model.addAttribute("saml2Login", true);
|
||||||
}
|
}
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
@@ -395,10 +393,10 @@ public class AccountWebController {
|
|||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
String username = userDetails.getUsername();
|
String username = detailsUser.getUsername();
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
||||||
if (user.isEmpty()) {
|
if (user.isEmpty()) {
|
||||||
|
|||||||
@@ -230,7 +230,11 @@ public class GeneralWebController {
|
|||||||
// Extract font names from external directory
|
// Extract font names from external directory
|
||||||
fontNames.addAll(
|
fontNames.addAll(
|
||||||
getFontNamesFromLocation(
|
getFontNamesFromLocation(
|
||||||
"file:" + InstallationPathConfig.getStaticPath() + "fonts/*"));
|
"file:"
|
||||||
|
+ InstallationPathConfig.getStaticPath()
|
||||||
|
+ "fonts"
|
||||||
|
+ File.separator
|
||||||
|
+ "*"));
|
||||||
return fontNames;
|
return fontNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -284,10 +284,14 @@ public class ApplicationProperties {
|
|||||||
private boolean customHTMLFiles;
|
private boolean customHTMLFiles;
|
||||||
private String tessdataDir;
|
private String tessdataDir;
|
||||||
private Boolean enableAlphaFunctionality;
|
private Boolean enableAlphaFunctionality;
|
||||||
private String enableAnalytics;
|
private Boolean enableAnalytics;
|
||||||
private Datasource datasource;
|
private Datasource datasource;
|
||||||
private Boolean disableSanitize;
|
private Boolean disableSanitize;
|
||||||
private CustomPaths customPaths = new CustomPaths();
|
private CustomPaths customPaths = new CustomPaths();
|
||||||
|
|
||||||
|
public boolean isAnalyticsEnabled() {
|
||||||
|
return this.getEnableAnalytics() != null && this.getEnableAnalytics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ public class CertificateValidationService {
|
|||||||
Enumeration<String> aliases = trustStore.aliases();
|
Enumeration<String> aliases = trustStore.aliases();
|
||||||
while (aliases.hasMoreElements()) {
|
while (aliases.hasMoreElements()) {
|
||||||
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||||
if (trustCert instanceof X509Certificate) {
|
if (trustCert instanceof X509Certificate x509Cert) {
|
||||||
anchors.add(new TrustAnchor((X509Certificate) trustCert, null));
|
anchors.add(new TrustAnchor(x509Cert, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class PostHogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void captureSystemInfo() {
|
private void captureSystemInfo() {
|
||||||
if (!Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (!applicationProperties.getSystem().isAnalyticsEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -60,7 +60,7 @@ public class PostHogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void captureEvent(String eventName, Map<String, Object> properties) {
|
public void captureEvent(String eventName, Map<String, Object> properties) {
|
||||||
if (!Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (!applicationProperties.getSystem().isAnalyticsEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
postHog.capture(uniqueId, eventName, properties);
|
postHog.capture(uniqueId, eventName, properties);
|
||||||
@@ -315,7 +315,7 @@ public class PostHogService {
|
|||||||
addIfNotEmpty(
|
addIfNotEmpty(
|
||||||
properties,
|
properties,
|
||||||
"system_enableAnalytics",
|
"system_enableAnalytics",
|
||||||
applicationProperties.getSystem().getEnableAnalytics());
|
applicationProperties.getSystem().isAnalyticsEnabled());
|
||||||
|
|
||||||
// Capture UI properties
|
// Capture UI properties
|
||||||
addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName());
|
addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName());
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ public class FileMonitor {
|
|||||||
this.pathFilter = pathFilter;
|
this.pathFilter = pathFilter;
|
||||||
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
|
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
|
||||||
this.watchService = FileSystems.getDefault().newWatchService();
|
this.watchService = FileSystems.getDefault().newWatchService();
|
||||||
this.rootDir = Path.of(runtimePathConfig.getPipelineWatchedFoldersPath()).toAbsolutePath();
|
log.info("Monitoring directory: {}", runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||||
|
this.rootDir = Path.of(runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldNotProcess(Path path) {
|
private boolean shouldNotProcess(Path path) {
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ public class FileToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// search for the main HTML file.
|
// Search for the main HTML file.
|
||||||
try (Stream<Path> walk = Files.walk(tempDirectory)) {
|
try (Stream<Path> walk = Files.walk(tempDirectory)) {
|
||||||
List<Path> htmlFiles =
|
List<Path> htmlFiles =
|
||||||
walk.filter(file -> file.toString().endsWith(".html"))
|
walk.filter(file -> file.toString().endsWith(".html"))
|
||||||
@@ -190,46 +190,20 @@ public class FileToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] convertBookTypeToPdf(byte[] bytes, String originalFilename)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
if (originalFilename == null || originalFilename.lastIndexOf('.') == -1) {
|
|
||||||
throw new IllegalArgumentException("Invalid original filename.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf('.'));
|
|
||||||
List<String> command = new ArrayList<>();
|
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
|
||||||
Path tempInputFile = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create temp file with appropriate extension
|
|
||||||
tempInputFile = Files.createTempFile("input_", fileExtension);
|
|
||||||
Files.write(tempInputFile, bytes);
|
|
||||||
|
|
||||||
command.add("ebook-convert");
|
|
||||||
command.add(tempInputFile.toString());
|
|
||||||
command.add(tempOutputFile.toString());
|
|
||||||
ProcessExecutorResult returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
|
|
||||||
return Files.readAllBytes(tempOutputFile);
|
|
||||||
} finally {
|
|
||||||
// Clean up temporary files
|
|
||||||
if (tempInputFile != null) {
|
|
||||||
Files.deleteIfExists(tempInputFile);
|
|
||||||
}
|
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String sanitizeZipFilename(String entryName) {
|
static String sanitizeZipFilename(String entryName) {
|
||||||
if (entryName == null || entryName.trim().isEmpty()) {
|
if (entryName == null || entryName.trim().isEmpty()) {
|
||||||
return entryName;
|
return "";
|
||||||
}
|
}
|
||||||
|
// Remove any drive letters (e.g., "C:\") and leading forward/backslashes
|
||||||
|
entryName = entryName.replaceAll("^[a-zA-Z]:[\\\\/]+", "");
|
||||||
|
entryName = entryName.replaceAll("^[\\\\/]+", "");
|
||||||
|
|
||||||
|
// Recursively remove path traversal sequences
|
||||||
while (entryName.contains("../") || entryName.contains("..\\")) {
|
while (entryName.contains("../") || entryName.contains("..\\")) {
|
||||||
entryName = entryName.replace("../", "").replace("..\\", "");
|
entryName = entryName.replace("../", "").replace("..\\", "");
|
||||||
}
|
}
|
||||||
|
// Normalize all backslashes to forward slashes
|
||||||
|
entryName = entryName.replaceAll("\\\\", "/");
|
||||||
return entryName;
|
return entryName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -30,6 +25,7 @@ import io.github.pixee.security.Urls;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
|
import stirling.software.SPDF.config.YamlHelper;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GeneralUtils {
|
public class GeneralUtils {
|
||||||
@@ -338,218 +334,16 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, String key) throws IOException {
|
|
||||||
saveKeyToConfig(id, key, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, boolean key) throws IOException {
|
|
||||||
saveKeyToConfig(id, key, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
|
||||||
throws IOException {
|
|
||||||
doSaveKeyToConfig(id, (key == null ? "" : key), autoGenerated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
|
|
||||||
throws IOException {
|
|
||||||
doSaveKeyToConfig(id, String.valueOf(key), autoGenerated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*
|
/*------------------------------------------------------------------------*
|
||||||
* Internal Implementation Details *
|
* Internal Implementation Details *
|
||||||
*------------------------------------------------------------------------*/
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/**
|
public static void saveKeyToSettings(String key, Object newValue) throws IOException {
|
||||||
* Actually performs the line-based update for the given path (e.g. "security.csrfDisabled") to
|
String[] keyArray = key.split("\\.");
|
||||||
* a new string value (e.g. "true"), possibly marking it as auto-generated.
|
|
||||||
*/
|
|
||||||
private static void doSaveKeyToConfig(String fullPath, String newValue, boolean autoGenerated)
|
|
||||||
throws IOException {
|
|
||||||
// 1) Load the file (settings.yml)
|
|
||||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
if (!Files.exists(settingsPath)) {
|
YamlHelper settingsYaml = new YamlHelper(settingsPath);
|
||||||
log.warn("Settings file not found at {}, creating a new empty file...", settingsPath);
|
settingsYaml.updateValue(Arrays.asList(keyArray), newValue);
|
||||||
Files.createDirectories(settingsPath.getParent());
|
settingsYaml.saveOverride(settingsPath);
|
||||||
Files.createFile(settingsPath);
|
|
||||||
}
|
|
||||||
List<String> lines = Files.readAllLines(settingsPath);
|
|
||||||
|
|
||||||
// 2) Build a map of "nestedKeyPath -> lineIndex" by parsing indentation
|
|
||||||
// Also track each line's indentation so we can preserve it when rewriting.
|
|
||||||
Map<String, LineInfo> pathToLine = parseNestedYamlKeys(lines);
|
|
||||||
|
|
||||||
// 3) If the path is found, rewrite its line. Else, append at the bottom (no indentation).
|
|
||||||
boolean changed = false;
|
|
||||||
if (pathToLine.containsKey(fullPath)) {
|
|
||||||
// Rewrite existing line
|
|
||||||
LineInfo info = pathToLine.get(fullPath);
|
|
||||||
String oldLine = lines.get(info.lineIndex);
|
|
||||||
String newLine =
|
|
||||||
rewriteLine(oldLine, info.indentSpaces, fullPath, newValue, autoGenerated);
|
|
||||||
if (!newLine.equals(oldLine)) {
|
|
||||||
lines.set(info.lineIndex, newLine);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Append a new line at the bottom, with zero indentation
|
|
||||||
String appended = fullPath + ": " + newValue;
|
|
||||||
if (autoGenerated) {
|
|
||||||
appended += " # Automatically Generated Settings (Do Not Edit Directly)";
|
|
||||||
}
|
|
||||||
lines.add(appended);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) If changed, write back to file
|
|
||||||
if (changed) {
|
|
||||||
Files.write(settingsPath, lines);
|
|
||||||
log.info(
|
|
||||||
"Updated '{}' to '{}' (autoGenerated={}) in {}",
|
|
||||||
fullPath,
|
|
||||||
newValue,
|
|
||||||
autoGenerated,
|
|
||||||
settingsPath);
|
|
||||||
} else {
|
|
||||||
log.info("No changes for '{}' (already set to '{}').", fullPath, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A small record-like class that holds: - lineIndex - indentSpaces */
|
|
||||||
private static class LineInfo {
|
|
||||||
int lineIndex;
|
|
||||||
int indentSpaces;
|
|
||||||
|
|
||||||
public LineInfo(int lineIndex, int indentSpaces) {
|
|
||||||
this.lineIndex = lineIndex;
|
|
||||||
this.indentSpaces = indentSpaces;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the YAML lines to build a map: "full.nested.key" -> (lineIndex, indentSpaces). We do a
|
|
||||||
* naive indentation-based path stacking: - 2 spaces = 1 indent level - lines that start with
|
|
||||||
* fewer or equal indentation pop the stack - lines that look like "key:" or "key: value" cause
|
|
||||||
* a push
|
|
||||||
*/
|
|
||||||
private static Map<String, LineInfo> parseNestedYamlKeys(List<String> lines) {
|
|
||||||
Map<String, LineInfo> result = new HashMap<>();
|
|
||||||
|
|
||||||
// We'll maintain a stack of (keyName, indentLevel).
|
|
||||||
// Each line that looks like "myKey:" or "myKey: value" is a new "child" of the top of the
|
|
||||||
// stack if indent is deeper.
|
|
||||||
Deque<String> pathStack = new ArrayDeque<>();
|
|
||||||
Deque<Integer> indentStack = new ArrayDeque<>();
|
|
||||||
indentStack.push(-1); // sentinel
|
|
||||||
|
|
||||||
for (int i = 0; i < lines.size(); i++) {
|
|
||||||
String line = lines.get(i);
|
|
||||||
String trimmed = line.trim();
|
|
||||||
|
|
||||||
// skip blank lines, comment lines, or list items
|
|
||||||
if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("-")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// check if there's a colon
|
|
||||||
int colonIdx = trimmed.indexOf(':');
|
|
||||||
if (colonIdx <= 0) { // must have at least one char before ':'
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// parse out key
|
|
||||||
String keyPart = trimmed.substring(0, colonIdx).trim();
|
|
||||||
if (keyPart.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// count leading spaces for indentation
|
|
||||||
int leadingSpaces = countLeadingSpaces(line);
|
|
||||||
int indentLevel = leadingSpaces / 2; // assume 2 spaces per level
|
|
||||||
|
|
||||||
// pop from stack until we get to a shallower indentation
|
|
||||||
while (indentStack.peek() != null && indentStack.peek() >= indentLevel) {
|
|
||||||
indentStack.pop();
|
|
||||||
pathStack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// push the new key
|
|
||||||
pathStack.push(keyPart);
|
|
||||||
indentStack.push(indentLevel);
|
|
||||||
|
|
||||||
// build the full path
|
|
||||||
String[] arr = pathStack.toArray(new String[0]);
|
|
||||||
List<String> reversed = Arrays.asList(arr);
|
|
||||||
Collections.reverse(reversed);
|
|
||||||
String fullPath = String.join(".", reversed);
|
|
||||||
|
|
||||||
// store line info
|
|
||||||
result.put(fullPath, new LineInfo(i, leadingSpaces));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewrite a single line to set a new value, preserving indentation and (optionally) the
|
|
||||||
* existing or auto-generated inline comment.
|
|
||||||
*
|
|
||||||
* <p>For example, oldLine might be: " csrfDisabled: false # set to 'true' to disable CSRF
|
|
||||||
* protection" newValue = "true" autoGenerated = false
|
|
||||||
*
|
|
||||||
* <p>We'll produce something like: " csrfDisabled: true # set to 'true' to disable CSRF
|
|
||||||
* protection"
|
|
||||||
*/
|
|
||||||
private static String rewriteLine(
|
|
||||||
String oldLine, int indentSpaces, String path, String newValue, boolean autoGenerated) {
|
|
||||||
// We'll keep the exact leading indentation (indentSpaces).
|
|
||||||
// Then "key: newValue". We'll try to preserve any existing inline comment unless
|
|
||||||
// autoGenerated is true.
|
|
||||||
|
|
||||||
// 1) Extract leading spaces from the old line (just in case they differ from indentSpaces).
|
|
||||||
int actualLeadingSpaces = countLeadingSpaces(oldLine);
|
|
||||||
String leading = oldLine.substring(0, actualLeadingSpaces);
|
|
||||||
|
|
||||||
// 2) Remove leading spaces from the rest
|
|
||||||
String trimmed = oldLine.substring(actualLeadingSpaces);
|
|
||||||
|
|
||||||
// 3) Check for existing comment
|
|
||||||
int hashIndex = trimmed.indexOf('#');
|
|
||||||
String lineWithoutComment =
|
|
||||||
(hashIndex >= 0) ? trimmed.substring(0, hashIndex).trim() : trimmed.trim();
|
|
||||||
String oldComment = (hashIndex >= 0) ? trimmed.substring(hashIndex).trim() : "";
|
|
||||||
|
|
||||||
// 4) Rebuild "key: newValue"
|
|
||||||
// The "key" here is everything before ':' in lineWithoutComment
|
|
||||||
int colonIdx = lineWithoutComment.indexOf(':');
|
|
||||||
String existingKey =
|
|
||||||
(colonIdx >= 0)
|
|
||||||
? lineWithoutComment.substring(0, colonIdx).trim()
|
|
||||||
: path; // fallback if line is malformed
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(leading); // restore original leading spaces
|
|
||||||
|
|
||||||
// "key: newValue"
|
|
||||||
sb.append(existingKey).append(": ").append(newValue);
|
|
||||||
|
|
||||||
// 5) If autoGenerated, add/replace comment
|
|
||||||
if (autoGenerated) {
|
|
||||||
sb.append(" # Automatically Generated Settings (Do Not Edit Directly)");
|
|
||||||
} else {
|
|
||||||
// preserve the old comment if it exists
|
|
||||||
if (!oldComment.isEmpty()) {
|
|
||||||
sb.append(" ").append(oldComment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int countLeadingSpaces(String line) {
|
|
||||||
int count = 0;
|
|
||||||
for (char c : line.toCharArray()) {
|
|
||||||
if (c == ' ') count++;
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateMachineFingerprint() {
|
public static String generateMachineFingerprint() {
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ public class ImageProcessingUtils {
|
|||||||
|
|
||||||
public static byte[] getImageData(BufferedImage image) {
|
public static byte[] getImageData(BufferedImage image) {
|
||||||
DataBuffer dataBuffer = image.getRaster().getDataBuffer();
|
DataBuffer dataBuffer = image.getRaster().getDataBuffer();
|
||||||
if (dataBuffer instanceof DataBufferByte) {
|
if (dataBuffer instanceof DataBufferByte dataBufferByte) {
|
||||||
return ((DataBufferByte) dataBuffer).getData();
|
return dataBufferByte.getData();
|
||||||
} else if (dataBuffer instanceof DataBufferInt) {
|
} else if (dataBuffer instanceof DataBufferInt dataBufferInt) {
|
||||||
int[] intData = ((DataBufferInt) dataBuffer).getData();
|
int[] intData = dataBufferInt.getData();
|
||||||
ByteBuffer byteBuffer = ByteBuffer.allocate(intData.length * 4);
|
ByteBuffer byteBuffer = ByteBuffer.allocate(intData.length * 4);
|
||||||
byteBuffer.asIntBuffer().put(intData);
|
byteBuffer.asIntBuffer().put(intData);
|
||||||
return byteBuffer.array();
|
return byteBuffer.array();
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -222,7 +226,7 @@ public class ProcessExecutor {
|
|||||||
boolean isQpdf =
|
boolean isQpdf =
|
||||||
command != null && !command.isEmpty() && command.get(0).contains("qpdf");
|
command != null && !command.isEmpty() && command.get(0).contains("qpdf");
|
||||||
|
|
||||||
if (outputLines.size() > 0) {
|
if (!outputLines.isEmpty()) {
|
||||||
String outputMessage = String.join("\n", outputLines);
|
String outputMessage = String.join("\n", outputLines);
|
||||||
messages += outputMessage;
|
messages += outputMessage;
|
||||||
if (!liveUpdates) {
|
if (!liveUpdates) {
|
||||||
@@ -230,7 +234,7 @@ public class ProcessExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorLines.size() > 0) {
|
if (!errorLines.isEmpty()) {
|
||||||
String errorMessage = String.join("\n", errorLines);
|
String errorMessage = String.join("\n", errorLines);
|
||||||
messages += errorMessage;
|
messages += errorMessage;
|
||||||
if (!liveUpdates) {
|
if (!liveUpdates) {
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=تحويل إلى PDF
|
|||||||
compress.title=ضغط
|
compress.title=ضغط
|
||||||
compress.header=ضغط ملف PDF
|
compress.header=ضغط ملف PDF
|
||||||
compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
|
compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
|
||||||
|
compress.grayscale.label=تطبيق التدرج الرمادي للضغط
|
||||||
compress.selectText.1=الوضع اليدوي - من 1 إلى 5
|
compress.selectText.1=الوضع اليدوي - من 1 إلى 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=مستوى التحسين:
|
compress.selectText.2=مستوى التحسين:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=تطبيق التدرج الرمادي للضغط
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF-ə Çevir
|
|||||||
compress.title=Sıxışdır
|
compress.title=Sıxışdır
|
||||||
compress.header=PDF-i Sıxışdır
|
compress.header=PDF-i Sıxışdır
|
||||||
compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
|
compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
|
||||||
|
compress.grayscale.label=Sıxma üçün Boz Rəng Tətbiq Edin
|
||||||
compress.selectText.1=Manual Mod - 1-dən 5-ə
|
compress.selectText.1=Manual Mod - 1-dən 5-ə
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizasiya səviyyəsi:
|
compress.selectText.2=Optimizasiya səviyyəsi:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Sıxma üçün Boz Rəng Tətbiq Edin
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Преобразуване към PDF
|
|||||||
compress.title=Компресиране
|
compress.title=Компресиране
|
||||||
compress.header=Компресиране на PDF
|
compress.header=Компресиране на PDF
|
||||||
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
||||||
|
compress.grayscale.label=Приложи сива скала за компресиране
|
||||||
compress.selectText.1=Ръчен режим - от 1 до 5
|
compress.selectText.1=Ръчен режим - от 1 до 5
|
||||||
compress.selectText.1.1=При нива на оптимизация от 6 до 9, в допълнение към общото компресиране на PDF, резолюцията на изображението се намалява, за да се намали допълнително размерът на файла. По-високите нива водят до по-силна компресия на изображенията (до 50% от оригиналния размер), като се постига по-голямо намаляване на размера, но с потенциална загуба на качество на изображенията.
|
compress.selectText.1.1=При нива на оптимизация от 6 до 9, в допълнение към общото компресиране на PDF, резолюцията на изображението се намалява, за да се намали допълнително размерът на файла. По-високите нива водят до по-силна компресия на изображенията (до 50% от оригиналния размер), като се постига по-голямо намаляване на размера, но с потенциална загуба на качество на изображенията.
|
||||||
compress.selectText.2=Ниво на оптимизация:
|
compress.selectText.2=Ниво на оптимизация:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Версия
|
|||||||
validateSignature.cert.keyUsage=Предназначение на ключа за използване
|
validateSignature.cert.keyUsage=Предназначение на ключа за използване
|
||||||
validateSignature.cert.selfSigned=Самостоятелно подписан
|
validateSignature.cert.selfSigned=Самостоятелно подписан
|
||||||
validateSignature.cert.bits=битове
|
validateSignature.cert.bits=битове
|
||||||
compress.grayscale.label=Приложи сива скала за компресиране
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Converteix a PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
|
compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
|
||||||
|
compress.grayscale.label=Aplicar escala de grisos per a la compressió
|
||||||
compress.selectText.1=Mode manual: de l'1 al 5
|
compress.selectText.1=Mode manual: de l'1 al 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivell d'optimització:
|
compress.selectText.2=Nivell d'optimització:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de grisos per a la compressió
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Převést na PDF
|
|||||||
compress.title=Komprimovat
|
compress.title=Komprimovat
|
||||||
compress.header=Komprimovat PDF
|
compress.header=Komprimovat PDF
|
||||||
compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
|
compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
|
||||||
|
compress.grayscale.label=Použít stupnici šedi pro kompresi
|
||||||
compress.selectText.1=Ruční režim - Od 1 do 5
|
compress.selectText.1=Ruční režim - Od 1 do 5
|
||||||
compress.selectText.1.1=V úrovních optimalizace 6 až 9 je kromě obecné komprese PDF sníženo rozlišení obrázků pro další zmenšení velikosti souboru. Vyšší úrovně vedou k silnější kompresi obrázků (až na 50 % původní velikosti), čímž dosahují většího zmenšení velikosti, ale s potenciální ztrátou kvality obrázků.
|
compress.selectText.1.1=V úrovních optimalizace 6 až 9 je kromě obecné komprese PDF sníženo rozlišení obrázků pro další zmenšení velikosti souboru. Vyšší úrovně vedou k silnější kompresi obrázků (až na 50 % původní velikosti), čímž dosahují většího zmenšení velikosti, ale s potenciální ztrátou kvality obrázků.
|
||||||
compress.selectText.2=Úroveň optimalizace:
|
compress.selectText.2=Úroveň optimalizace:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Verze
|
|||||||
validateSignature.cert.keyUsage=Použití klíče
|
validateSignature.cert.keyUsage=Použití klíče
|
||||||
validateSignature.cert.selfSigned=Podepsaný sám sebou
|
validateSignature.cert.selfSigned=Podepsaný sám sebou
|
||||||
validateSignature.cert.bits=bitů
|
validateSignature.cert.bits=bitů
|
||||||
compress.grayscale.label=Použít stupnici šedi pro kompresi
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konvertér til PDF
|
|||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
|
compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
|
||||||
|
compress.grayscale.label=Anvend gråskala til komprimering
|
||||||
compress.selectText.1=Manuel Tilstand - Fra 1 til 5
|
compress.selectText.1=Manuel Tilstand - Fra 1 til 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsniveau:
|
compress.selectText.2=Optimeringsniveau:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Anvend gråskala til komprimering
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=In PDF konvertieren
|
|||||||
compress.title=Komprimieren
|
compress.title=Komprimieren
|
||||||
compress.header=PDF komprimieren
|
compress.header=PDF komprimieren
|
||||||
compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
|
compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
|
||||||
|
compress.grayscale.label=Graustufen für Komprimierung anwenden
|
||||||
compress.selectText.1=Manueller Modus – Von 1 bis 5
|
compress.selectText.1=Manueller Modus – Von 1 bis 5
|
||||||
compress.selectText.1.1=In den Optimierungsstufen 6 bis 9 wird zusätzlich zur allgemeinen PDF-Komprimierung die Bildauflösung reduziert, um die Dateigröße weiter zu verringern. Höhere Stufen führen zu einer stärkeren Bildkomprimierung (bis zu 50 % der Originalgröße), wodurch eine stärkere Größenreduzierung erreicht wird, die jedoch mit einem möglichen Qualitätsverlust der Bilder einhergeht.
|
compress.selectText.1.1=In den Optimierungsstufen 6 bis 9 wird zusätzlich zur allgemeinen PDF-Komprimierung die Bildauflösung reduziert, um die Dateigröße weiter zu verringern. Höhere Stufen führen zu einer stärkeren Bildkomprimierung (bis zu 50 % der Originalgröße), wodurch eine stärkere Größenreduzierung erreicht wird, die jedoch mit einem möglichen Qualitätsverlust der Bilder einhergeht.
|
||||||
compress.selectText.2=Optimierungsstufe:
|
compress.selectText.2=Optimierungsstufe:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Schlüsselverwendung
|
validateSignature.cert.keyUsage=Schlüsselverwendung
|
||||||
validateSignature.cert.selfSigned=Selbstsigniert
|
validateSignature.cert.selfSigned=Selbstsigniert
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Graustufen für Komprimierung anwenden
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Μετατροπή σε PDF
|
|||||||
compress.title=Συμπίεση
|
compress.title=Συμπίεση
|
||||||
compress.header=Συμπίεση PDF
|
compress.header=Συμπίεση PDF
|
||||||
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για συμπίεση/βελτιστοποίηση PDF.
|
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για συμπίεση/βελτιστοποίηση PDF.
|
||||||
|
compress.grayscale.label=Εφαρμογή κλίμακας του γκρι για συμπίεση
|
||||||
compress.selectText.1=Χειροκίνητη λειτουργία - Από 1 έως 5
|
compress.selectText.1=Χειροκίνητη λειτουργία - Από 1 έως 5
|
||||||
compress.selectText.1.1=Στα επίπεδα βελτιστοποίησης 6 έως 9, εκτός από τη γενική συμπίεση PDF, η ανάλυση εικόνας μειώνεται για περαιτέρω μείωση του μεγέθους αρχείου. Υψηλότερα επίπεδα οδηγούν σε ισχυρότερη συμπίεση εικόνας (έως και 50% του αρχικού μεγέθους), επιτυγχάνοντας μεγαλύτερη μείωση μεγέθους αλλά με πιθανή απώλεια ποιότητας στις εικόνες.
|
compress.selectText.1.1=Στα επίπεδα βελτιστοποίησης 6 έως 9, εκτός από τη γενική συμπίεση PDF, η ανάλυση εικόνας μειώνεται για περαιτέρω μείωση του μεγέθους αρχείου. Υψηλότερα επίπεδα οδηγούν σε ισχυρότερη συμπίεση εικόνας (έως και 50% του αρχικού μεγέθους), επιτυγχάνοντας μεγαλύτερη μείωση μεγέθους αλλά με πιθανή απώλεια ποιότητας στις εικόνες.
|
||||||
compress.selectText.2=Επίπεδο βελτιστοποίησης:
|
compress.selectText.2=Επίπεδο βελτιστοποίησης:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Έκδοση
|
|||||||
validateSignature.cert.keyUsage=Χρήση κλειδιού
|
validateSignature.cert.keyUsage=Χρήση κλειδιού
|
||||||
validateSignature.cert.selfSigned=Αυτο-υπογεγραμμένο
|
validateSignature.cert.selfSigned=Αυτο-υπογεγραμμένο
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Εφαρμογή κλίμακας του γκρι για συμπίεση
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Convert to PDF
|
|||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
|
compress.grayscale.label=Apply Grayscale for Compression
|
||||||
compress.selectText.1=Manual Mode - From 1 to 5
|
compress.selectText.1=Manual Mode - From 1 to 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimisation level:
|
compress.selectText.2=Optimisation level:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Apply Grayscale for Compression
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Convert to PDF
|
|||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
|
compress.grayscale.label=Apply Grayscale for Compression
|
||||||
compress.selectText.1=Manual Mode - From 1 to 5
|
compress.selectText.1=Manual Mode - From 1 to 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimization level:
|
compress.selectText.2=Optimization level:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Apply Grayscale for Compression
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Convertir a PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
||||||
|
compress.grayscale.label=Aplicar escala de grises para compresión
|
||||||
compress.selectText.1=Modo manual - De 1 a 5
|
compress.selectText.1=Modo manual - De 1 a 5
|
||||||
compress.selectText.1.1=En los niveles de optimización 6 a 9, además de la compresión general de PDF, se reduce la resolución de la imagen para reducir aún más el tamaño del archivo. Los niveles más altos dan como resultado una mayor compresión de la imagen (hasta el 50 % del tamaño original), lo que permite lograr una mayor reducción del tamaño, pero con una posible pérdida de calidad en las imágenes.
|
compress.selectText.1.1=En los niveles de optimización 6 a 9, además de la compresión general de PDF, se reduce la resolución de la imagen para reducir aún más el tamaño del archivo. Los niveles más altos dan como resultado una mayor compresión de la imagen (hasta el 50 % del tamaño original), lo que permite lograr una mayor reducción del tamaño, pero con una posible pérdida de calidad en las imágenes.
|
||||||
compress.selectText.2=Nivel de optimización:
|
compress.selectText.2=Nivel de optimización:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versión
|
|||||||
validateSignature.cert.keyUsage=Uso de la llave
|
validateSignature.cert.keyUsage=Uso de la llave
|
||||||
validateSignature.cert.selfSigned=Autofirmado
|
validateSignature.cert.selfSigned=Autofirmado
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de grises para compresión
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF bihurtu
|
|||||||
compress.title=Konprimatu
|
compress.title=Konprimatu
|
||||||
compress.header=PDFa konprimatu
|
compress.header=PDFa konprimatu
|
||||||
compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
|
compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
|
||||||
|
compress.grayscale.label=Aplikatu grisezko eskala konpresiorako
|
||||||
compress.selectText.1=Eskuz 1etik 5ra
|
compress.selectText.1=Eskuz 1etik 5ra
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizazio maila:
|
compress.selectText.2=Optimizazio maila:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplikatu grisezko eskala konpresiorako
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=تبدیل به PDF
|
|||||||
compress.title=فشردهسازی
|
compress.title=فشردهسازی
|
||||||
compress.header=فشردهسازی PDF
|
compress.header=فشردهسازی PDF
|
||||||
compress.credit=این سرویس از qpdf برای فشردهسازی / بهینهسازی PDF استفاده میکند.
|
compress.credit=این سرویس از qpdf برای فشردهسازی / بهینهسازی PDF استفاده میکند.
|
||||||
|
compress.grayscale.label=اعمال مقیاس خاکستری برای فشردهسازی
|
||||||
compress.selectText.1=حالت دستی - از 1 تا 5
|
compress.selectText.1=حالت دستی - از 1 تا 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=سطح بهینهسازی:
|
compress.selectText.2=سطح بهینهسازی:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=نسخه
|
|||||||
validateSignature.cert.keyUsage=کاربرد کلید
|
validateSignature.cert.keyUsage=کاربرد کلید
|
||||||
validateSignature.cert.selfSigned=با امضای خود
|
validateSignature.cert.selfSigned=با امضای خود
|
||||||
validateSignature.cert.bits=بیتها
|
validateSignature.cert.bits=بیتها
|
||||||
compress.grayscale.label=اعمال مقیاس خاکستری برای فشردهسازی
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Convertir
|
|||||||
compress.title=Compresser un PDF
|
compress.title=Compresser un PDF
|
||||||
compress.header=Compresser un PDF (lorsque c'est possible!)
|
compress.header=Compresser un PDF (lorsque c'est possible!)
|
||||||
compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
|
compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
|
||||||
|
compress.grayscale.label=Appliquer l'échelle de gris pour la compression
|
||||||
compress.selectText.1=Mode manuel – de 1 à 5
|
compress.selectText.1=Mode manuel – de 1 à 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Niveau d'optimisation
|
compress.selectText.2=Niveau d'optimisation
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Usage de la clé
|
validateSignature.cert.keyUsage=Usage de la clé
|
||||||
validateSignature.cert.selfSigned=Auto-signé
|
validateSignature.cert.selfSigned=Auto-signé
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Appliquer l'échelle de gris pour la compression
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Ainm úsáideora nó pasfhocal neamhbhailí.
|
|||||||
login.locked=Tá do chuntas glasáilte.
|
login.locked=Tá do chuntas glasáilte.
|
||||||
login.signinTitle=Sínigh isteach le do thoil
|
login.signinTitle=Sínigh isteach le do thoil
|
||||||
login.ssoSignIn=Logáil isteach trí Chlárú Aonair
|
login.ssoSignIn=Logáil isteach trí Chlárú Aonair
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Uath-Chruthaigh Úsáideoir faoi Mhíchumas
|
login.oAuth2AutoCreateDisabled=OAUTH2 Uath-Chruthaigh Úsáideoir faoi Mhíchumas
|
||||||
login.oauth2AdminBlockedUser=Tá bac faoi láthair ar chlárú nó logáil isteach úsáideoirí neamhchláraithe. Déan teagmháil leis an riarthóir le do thoil.
|
login.oAuth2AdminBlockedUser=Tá bac faoi láthair ar chlárú nó logáil isteach úsáideoirí neamhchláraithe. Déan teagmháil leis an riarthóir le do thoil.
|
||||||
login.oauth2RequestNotFound=Níor aimsíodh iarratas údaraithe
|
login.oauth2RequestNotFound=Níor aimsíodh iarratas údaraithe
|
||||||
login.oauth2InvalidUserInfoResponse=Freagra Neamhbhailí Faisnéise Úsáideora
|
login.oauth2InvalidUserInfoResponse=Freagra Neamhbhailí Faisnéise Úsáideora
|
||||||
login.oauth2invalidRequest=Iarratas Neamhbhailí
|
login.oauth2invalidRequest=Iarratas Neamhbhailí
|
||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Tiontaigh go PDF
|
|||||||
compress.title=Comhbhrúigh
|
compress.title=Comhbhrúigh
|
||||||
compress.header=Comhbhrúigh PDF
|
compress.header=Comhbhrúigh PDF
|
||||||
compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
|
compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
|
||||||
|
compress.grayscale.label=Cuir Scála Liath i bhFeidhm le Comhbhrú
|
||||||
compress.selectText.1=Mód Láimhe - Ó 1 go 5
|
compress.selectText.1=Mód Láimhe - Ó 1 go 5
|
||||||
compress.selectText.1.1=I leibhéil optamaithe 6 go 9, chomh maith le comhbhrú ginearálta PDF, déantar réiteach íomhá a laghdú de réir scála chun méid comhaid a laghdú tuilleadh. Mar thoradh ar leibhéil níos airde tá comhbhrú íomhá níos láidre (suas le 50% den mhéid bunaidh), ag baint amach laghdú méide níos mó ach le caillteanas cáilíochta féideartha in íomhánna.
|
compress.selectText.1.1=I leibhéil optamaithe 6 go 9, chomh maith le comhbhrú ginearálta PDF, déantar réiteach íomhá a laghdú de réir scála chun méid comhaid a laghdú tuilleadh. Mar thoradh ar leibhéil níos airde tá comhbhrú íomhá níos láidre (suas le 50% den mhéid bunaidh), ag baint amach laghdú méide níos mó ach le caillteanas cáilíochta féideartha in íomhánna.
|
||||||
compress.selectText.2=Leibhéal optamaithe:
|
compress.selectText.2=Leibhéal optamaithe:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Leagan
|
|||||||
validateSignature.cert.keyUsage=Úsáid Eochrach
|
validateSignature.cert.keyUsage=Úsáid Eochrach
|
||||||
validateSignature.cert.selfSigned=Féin-Sínithe
|
validateSignature.cert.selfSigned=Féin-Sínithe
|
||||||
validateSignature.cert.bits=giotáin
|
validateSignature.cert.bits=giotáin
|
||||||
compress.grayscale.label=Cuir Scála Liath i bhFeidhm le Comhbhrú
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF में बदलें
|
|||||||
compress.title=कम्प्रेस
|
compress.title=कम्प्रेस
|
||||||
compress.header=PDF कम्प्रेस करें
|
compress.header=PDF कम्प्रेस करें
|
||||||
compress.credit=यह सेवा PDF कम्प्रेस/अनुकूलन के लिए qpdf का उपयोग करती है।
|
compress.credit=यह सेवा PDF कम्प्रेस/अनुकूलन के लिए qpdf का उपयोग करती है।
|
||||||
|
compress.grayscale.label=संपीड़न के लिए ग्रेस्केल लागू करें
|
||||||
compress.selectText.1=मैनुअल मोड - स्तर 1 से 4
|
compress.selectText.1=मैनुअल मोड - स्तर 1 से 4
|
||||||
compress.selectText.1.1=अनुकूलन स्तर 6 से 9 में, सामान्य PDF कम्प्रेसन के अतिरिक्त, फ़ाइल आकार को और कम करने के लिए छवि रेज़ोल्यूशन को कम किया जाता है। उच्च स्तर पर छवियों का अधिक कम्प्रेसन होता है (मूल आकार का 50% तक), जिससे आकार में अधिक कमी आती है लेकिन छवियों की गुणवत्ता प्रभावित हो सकती है।
|
compress.selectText.1.1=अनुकूलन स्तर 6 से 9 में, सामान्य PDF कम्प्रेसन के अतिरिक्त, फ़ाइल आकार को और कम करने के लिए छवि रेज़ोल्यूशन को कम किया जाता है। उच्च स्तर पर छवियों का अधिक कम्प्रेसन होता है (मूल आकार का 50% तक), जिससे आकार में अधिक कमी आती है लेकिन छवियों की गुणवत्ता प्रभावित हो सकती है।
|
||||||
compress.selectText.2=अनुकूलन स्तर:
|
compress.selectText.2=अनुकूलन स्तर:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=संस्करण
|
|||||||
validateSignature.cert.keyUsage=कुंजी उपयोग
|
validateSignature.cert.keyUsage=कुंजी उपयोग
|
||||||
validateSignature.cert.selfSigned=स्व-हस्ताक्षरित
|
validateSignature.cert.selfSigned=स्व-हस्ताक्षरित
|
||||||
validateSignature.cert.bits=बिट्स
|
validateSignature.cert.bits=बिट्स
|
||||||
compress.grayscale.label=संपीड़न के लिए ग्रेस्केल लागू करें
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Pretvori u PDF
|
|||||||
compress.title=Komprimirajte
|
compress.title=Komprimirajte
|
||||||
compress.header=Komprimirajte PDF
|
compress.header=Komprimirajte PDF
|
||||||
compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
|
compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
|
||||||
|
compress.grayscale.label=Primijeni sivinu za kompresiju
|
||||||
compress.selectText.1=Ručni režim - Od 1 do 5
|
compress.selectText.1=Ručni režim - Od 1 do 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivo optimizacije:
|
compress.selectText.2=Nivo optimizacije:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Primijeni sivinu za kompresiju
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konvertálás PDF-be
|
|||||||
compress.title=Tömörítés
|
compress.title=Tömörítés
|
||||||
compress.header=PDF tömörítése
|
compress.header=PDF tömörítése
|
||||||
compress.credit=Ez a szolgáltatás a qpdf használatával végzi a PDF tömörítését/optimalizálását.
|
compress.credit=Ez a szolgáltatás a qpdf használatával végzi a PDF tömörítését/optimalizálását.
|
||||||
|
compress.grayscale.label=Szürkeárnyalatok alkalmazása tömörítéshez
|
||||||
compress.selectText.1=Kézi mód - 1-től 5-ig
|
compress.selectText.1=Kézi mód - 1-től 5-ig
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimalizálási szint:
|
compress.selectText.2=Optimalizálási szint:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Verzió
|
|||||||
validateSignature.cert.keyUsage=Kulcshasználat
|
validateSignature.cert.keyUsage=Kulcshasználat
|
||||||
validateSignature.cert.selfSigned=Önaláírt
|
validateSignature.cert.selfSigned=Önaláírt
|
||||||
validateSignature.cert.bits=bit
|
validateSignature.cert.bits=bit
|
||||||
compress.grayscale.label=Szürkeárnyalatok alkalmazása tömörítéshez
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konversi ke PDF
|
|||||||
compress.title=Kompres
|
compress.title=Kompres
|
||||||
compress.header=Kompres PDF
|
compress.header=Kompres PDF
|
||||||
compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
|
compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
|
||||||
|
compress.grayscale.label=Terapkan Skala Abu-Abu untuk Kompresi
|
||||||
compress.selectText.1=Mode Manual - Dari 1 hingga 5
|
compress.selectText.1=Mode Manual - Dari 1 hingga 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Tingkat Optimalisasi:
|
compress.selectText.2=Tingkat Optimalisasi:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Terapkan Skala Abu-Abu untuk Kompresi
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ home.desc=La tua pagina auto-gestita per modificare qualsiasi PDF.
|
|||||||
home.searchBar=Cerca funzionalità...
|
home.searchBar=Cerca funzionalità...
|
||||||
|
|
||||||
|
|
||||||
home.viewPdf.title=View/Edit PDF
|
home.viewPdf.title=Visualizza/Modifica PDF
|
||||||
home.viewPdf.desc=Visualizza, annota, aggiungi testo o immagini
|
home.viewPdf.desc=Visualizza, annota, aggiungi testo o immagini
|
||||||
viewPdf.tags=visualizzare,leggere,annotare,testo,immagine
|
viewPdf.tags=visualizzare,leggere,annotare,testo,immagine
|
||||||
|
|
||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Converti in PDF
|
|||||||
compress.title=Comprimi
|
compress.title=Comprimi
|
||||||
compress.header=Comprimi PDF
|
compress.header=Comprimi PDF
|
||||||
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
|
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
|
||||||
|
compress.grayscale.label=Applica scala di grigio per la compressione
|
||||||
compress.selectText.1=Modalità manuale - Da 1 a 5
|
compress.selectText.1=Modalità manuale - Da 1 a 5
|
||||||
compress.selectText.1.1=Nei livelli di ottimizzazione da 6 a 9, oltre alla compressione PDF generale, la risoluzione dell'immagine viene ridotta per ridurre ulteriormente le dimensioni del file. Livelli più alti comportano una compressione dell'immagine più forte (fino al 50% delle dimensioni originali), ottenendo una maggiore riduzione delle dimensioni ma con una potenziale perdita di qualità nelle immagini.
|
compress.selectText.1.1=Nei livelli di ottimizzazione da 6 a 9, oltre alla compressione PDF generale, la risoluzione dell'immagine viene ridotta per ridurre ulteriormente le dimensioni del file. Livelli più alti comportano una compressione dell'immagine più forte (fino al 50% delle dimensioni originali), ottenendo una maggiore riduzione delle dimensioni ma con una potenziale perdita di qualità nelle immagini.
|
||||||
compress.selectText.2=Livello di ottimizzazione:
|
compress.selectText.2=Livello di ottimizzazione:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versione
|
|||||||
validateSignature.cert.keyUsage=Utilizzo della chiave
|
validateSignature.cert.keyUsage=Utilizzo della chiave
|
||||||
validateSignature.cert.selfSigned=Autofirmato
|
validateSignature.cert.selfSigned=Autofirmato
|
||||||
validateSignature.cert.bits=bit
|
validateSignature.cert.bits=bit
|
||||||
compress.grayscale.label=Applica scala di grigio per la compressione
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDFを変換
|
|||||||
compress.title=圧縮
|
compress.title=圧縮
|
||||||
compress.header=PDFを圧縮
|
compress.header=PDFを圧縮
|
||||||
compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
|
compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
|
||||||
|
compress.grayscale.label=圧縮にグレースケールを適用する
|
||||||
compress.selectText.1=手動モード - 1から9
|
compress.selectText.1=手動モード - 1から9
|
||||||
compress.selectText.1.1=最適化レベル6~9では、一般的なPDF圧縮に加えて画像解像度が縮小され、ファイルサイズがさらに縮小されます。レベルが高くなると、画像圧縮が強化され (元のサイズの最大 50%)、サイズはさらに縮小されますが、画像の品質が低下する可能性があります。
|
compress.selectText.1.1=最適化レベル6~9では、一般的なPDF圧縮に加えて画像解像度が縮小され、ファイルサイズがさらに縮小されます。レベルが高くなると、画像圧縮が強化され (元のサイズの最大 50%)、サイズはさらに縮小されますが、画像の品質が低下する可能性があります。
|
||||||
compress.selectText.2=品質レベル:
|
compress.selectText.2=品質レベル:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=バージョン
|
|||||||
validateSignature.cert.keyUsage=キーの使用法
|
validateSignature.cert.keyUsage=キーの使用法
|
||||||
validateSignature.cert.selfSigned=自己署名
|
validateSignature.cert.selfSigned=自己署名
|
||||||
validateSignature.cert.bits=ビット
|
validateSignature.cert.bits=ビット
|
||||||
compress.grayscale.label=圧縮にグレースケールを適用する
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF로 변환
|
|||||||
compress.title=압축
|
compress.title=압축
|
||||||
compress.header=PDF 압축
|
compress.header=PDF 압축
|
||||||
compress.credit=이 서비스는 PDF 압축/최적화를 위해 qpdf를 사용합니다.
|
compress.credit=이 서비스는 PDF 압축/최적화를 위해 qpdf를 사용합니다.
|
||||||
|
compress.grayscale.label=압축을 위해 그레이스케일 적용
|
||||||
compress.selectText.1=수동 모드 - 1에서 5
|
compress.selectText.1=수동 모드 - 1에서 5
|
||||||
compress.selectText.1.1=최적화 레벨 6에서 9에서는 일반적인 PDF 압축 외에도 이미지 해상도가 낮아져 파일 크기가 더욱 감소합니다. 높은 레벨은 더 강력한 이미지 압축(원본 크기의 최대 50%)을 초래하여 더 큰 크기 감소를 달성하지만 이미지 품질이 저하될 수 있습니다.
|
compress.selectText.1.1=최적화 레벨 6에서 9에서는 일반적인 PDF 압축 외에도 이미지 해상도가 낮아져 파일 크기가 더욱 감소합니다. 높은 레벨은 더 강력한 이미지 압축(원본 크기의 최대 50%)을 초래하여 더 큰 크기 감소를 달성하지만 이미지 품질이 저하될 수 있습니다.
|
||||||
compress.selectText.2=최적화 레벨:
|
compress.selectText.2=최적화 레벨:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=버전
|
|||||||
validateSignature.cert.keyUsage=키 용도
|
validateSignature.cert.keyUsage=키 용도
|
||||||
validateSignature.cert.selfSigned=자체 서명
|
validateSignature.cert.selfSigned=자체 서명
|
||||||
validateSignature.cert.bits=비트
|
validateSignature.cert.bits=비트
|
||||||
compress.grayscale.label=압축을 위해 그레이스케일 적용
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Omzetten naar PDF
|
|||||||
compress.title=Comprimeren
|
compress.title=Comprimeren
|
||||||
compress.header=PDF comprimeren
|
compress.header=PDF comprimeren
|
||||||
compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
|
compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
|
||||||
|
compress.grayscale.label=Grijsschaal toepassen voor compressie
|
||||||
compress.selectText.1=Handmatige modus - Van 1 tot 5
|
compress.selectText.1=Handmatige modus - Van 1 tot 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimalisatieniveau:
|
compress.selectText.2=Optimalisatieniveau:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Grijsschaal toepassen voor compressie
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konverter til PDF
|
|||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
|
compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
|
||||||
|
compress.grayscale.label=Bruk gråskala for komprimering
|
||||||
compress.selectText.1=Manuell modus - Fra 1 til 5
|
compress.selectText.1=Manuell modus - Fra 1 til 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsnivå:
|
compress.selectText.2=Optimeringsnivå:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Bruk gråskala for komprimering
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konwertuj na PDF
|
|||||||
compress.title=Kompresuj
|
compress.title=Kompresuj
|
||||||
compress.header=Kompresuj PDF
|
compress.header=Kompresuj PDF
|
||||||
compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF.
|
compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF.
|
||||||
|
compress.grayscale.label=Zastosuj skalę szarości do kompresji
|
||||||
compress.selectText.1=Tryb ręczny - Od 1 do 5
|
compress.selectText.1=Tryb ręczny - Od 1 do 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Poziom optymalizacji:
|
compress.selectText.2=Poziom optymalizacji:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Zastosuj skalę szarości do kompresji
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Converter para PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir
|
compress.header=Comprimir
|
||||||
compress.credit=Este serviço usa o Qpdf para compressão/otimização de PDF.
|
compress.credit=Este serviço usa o Qpdf para compressão/otimização de PDF.
|
||||||
|
compress.grayscale.label=Aplicar escala de cinza para compressão
|
||||||
compress.selectText.1=Modo Manual - De 1 a 9
|
compress.selectText.1=Modo Manual - De 1 a 9
|
||||||
compress.selectText.1.1=Nos níveis de otimização 6-9, além da compressão normal do PDF, a resolução das imagens são reduzidas, para diminuir ainda mais o tamanho do arquivo. Quanto maior o nível, maior a compressão da imagem (até 50% do tamanho original), resultando em tamanho menor do arquivo, porém com menor qualidade nas imagens.
|
compress.selectText.1.1=Nos níveis de otimização 6-9, além da compressão normal do PDF, a resolução das imagens são reduzidas, para diminuir ainda mais o tamanho do arquivo. Quanto maior o nível, maior a compressão da imagem (até 50% do tamanho original), resultando em tamanho menor do arquivo, porém com menor qualidade nas imagens.
|
||||||
compress.selectText.2=Nível de Otimização:
|
compress.selectText.2=Nível de Otimização:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versão
|
|||||||
validateSignature.cert.keyUsage=Uso da chave
|
validateSignature.cert.keyUsage=Uso da chave
|
||||||
validateSignature.cert.selfSigned=Autoassinados
|
validateSignature.cert.selfSigned=Autoassinados
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de cinza para compressão
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Converter para PDF
|
|||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Este serviço usa qpdf para Compressão/Otimização de PDF.
|
compress.credit=Este serviço usa qpdf para Compressão/Otimização de PDF.
|
||||||
|
compress.grayscale.label=Aplicar escala de cinzentos para compressão
|
||||||
compress.selectText.1=Modo Manual - De 1 a 5
|
compress.selectText.1=Modo Manual - De 1 a 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nível de otimização:
|
compress.selectText.2=Nível de otimização:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versão
|
|||||||
validateSignature.cert.keyUsage=Utilização da Chave
|
validateSignature.cert.keyUsage=Utilização da Chave
|
||||||
validateSignature.cert.selfSigned=Auto-Assinado
|
validateSignature.cert.selfSigned=Auto-Assinado
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de cinzentos para compressão
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Convertiți în PDF
|
|||||||
compress.title=Comprimare
|
compress.title=Comprimare
|
||||||
compress.header=Comprimare PDF
|
compress.header=Comprimare PDF
|
||||||
compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor.
|
compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor.
|
||||||
|
compress.grayscale.label=Aplicare scală de gri pentru compresie
|
||||||
compress.selectText.1=Modul manual - de la 1 la 5
|
compress.selectText.1=Modul manual - de la 1 la 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivel de optimizare:
|
compress.selectText.2=Nivel de optimizare:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicare scală de gri pentru compresie
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Преобразовать в PDF
|
|||||||
compress.title=Сжать
|
compress.title=Сжать
|
||||||
compress.header=Сжать PDF
|
compress.header=Сжать PDF
|
||||||
compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF.
|
compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF.
|
||||||
|
compress.grayscale.label=Применить шкалу серого для сжатия
|
||||||
compress.selectText.1=Ручной режим - от 1 до 5
|
compress.selectText.1=Ручной режим - от 1 до 5
|
||||||
compress.selectText.1.1=На уровнях оптимизации от 6 до 9, помимо общего сжатия PDF, разрешение изображений уменьшается для дальнейшего сокращения размера файла. Более высокие уровни приводят к более сильному сжатию изображений (до 50% от исходного размера), обеспечивая большее уменьшение размера, но с возможной потерей качества изображений.
|
compress.selectText.1.1=На уровнях оптимизации от 6 до 9, помимо общего сжатия PDF, разрешение изображений уменьшается для дальнейшего сокращения размера файла. Более высокие уровни приводят к более сильному сжатию изображений (до 50% от исходного размера), обеспечивая большее уменьшение размера, но с возможной потерей качества изображений.
|
||||||
compress.selectText.2=Уровень оптимизации:
|
compress.selectText.2=Уровень оптимизации:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Версия
|
|||||||
validateSignature.cert.keyUsage=Использование ключа
|
validateSignature.cert.keyUsage=Использование ключа
|
||||||
validateSignature.cert.selfSigned=Самоподписанный
|
validateSignature.cert.selfSigned=Самоподписанный
|
||||||
validateSignature.cert.bits=бит
|
validateSignature.cert.bits=бит
|
||||||
compress.grayscale.label=Применить шкалу серого для сжатия
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konvertovať do PDF
|
|||||||
compress.title=Komprimovať
|
compress.title=Komprimovať
|
||||||
compress.header=Komprimovať PDF
|
compress.header=Komprimovať PDF
|
||||||
compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF.
|
compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF.
|
||||||
|
compress.grayscale.label=Použiť odtiene šedej na kompresiu
|
||||||
compress.selectText.1=Manuálny režim - Od 1 do 5
|
compress.selectText.1=Manuálny režim - Od 1 do 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Úroveň optimalizácie:
|
compress.selectText.2=Úroveň optimalizácie:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Použiť odtiene šedej na kompresiu
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Pretvori v PDF
|
|||||||
compress.title=Stisnite
|
compress.title=Stisnite
|
||||||
compress.header=Stisnite PDF
|
compress.header=Stisnite PDF
|
||||||
compress.credit=Ta storitev uporablja qpdf za stiskanje/optimizacijo PDF.
|
compress.credit=Ta storitev uporablja qpdf za stiskanje/optimizacijo PDF.
|
||||||
|
compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje
|
||||||
compress.selectText.1=Ročni način - Od 1 do 5
|
compress.selectText.1=Ročni način - Od 1 do 5
|
||||||
compress.selectText.1.1=Na stopnjah optimizacije od 6 do 9 je poleg splošnega stiskanja PDF ločljivost slike zmanjšana, da se dodatno zmanjša velikost datoteke. Višje ravni povzročijo močnejše stiskanje slike (do 50 % prvotne velikosti), s čimer se doseže večje zmanjšanje velikosti, vendar s potencialno izgubo kakovosti slik.
|
compress.selectText.1.1=Na stopnjah optimizacije od 6 do 9 je poleg splošnega stiskanja PDF ločljivost slike zmanjšana, da se dodatno zmanjša velikost datoteke. Višje ravni povzročijo močnejše stiskanje slike (do 50 % prvotne velikosti), s čimer se doseže večje zmanjšanje velikosti, vendar s potencialno izgubo kakovosti slik.
|
||||||
compress.selectText.2=Raven optimizacije:
|
compress.selectText.2=Raven optimizacije:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Različica
|
|||||||
validateSignature.cert.keyUsage=Uporaba ključa
|
validateSignature.cert.keyUsage=Uporaba ključa
|
||||||
validateSignature.cert.selfSigned=Samopodpisano
|
validateSignature.cert.selfSigned=Samopodpisano
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konvertuj u PDF
|
|||||||
compress.title=Kompresija
|
compress.title=Kompresija
|
||||||
compress.header=Kompresuj PDF
|
compress.header=Kompresuj PDF
|
||||||
compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a.
|
compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a.
|
||||||
|
compress.grayscale.label=Primeni sivinu za kompresiju
|
||||||
compress.selectText.1=Ručni režim - Od 1 do 5
|
compress.selectText.1=Ručni režim - Od 1 do 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivo optimizacije:
|
compress.selectText.2=Nivo optimizacije:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Primeni sivinu za kompresiju
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Konvertera till PDF
|
|||||||
compress.title=Komprimera
|
compress.title=Komprimera
|
||||||
compress.header=Komprimera PDF
|
compress.header=Komprimera PDF
|
||||||
compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering.
|
compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering.
|
||||||
|
compress.grayscale.label=Tillämpa gråskala för komprimering
|
||||||
compress.selectText.1=Manuellt läge - Från 1 till 5
|
compress.selectText.1=Manuellt läge - Från 1 till 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimeringsnivå:
|
compress.selectText.2=Optimeringsnivå:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Tillämpa gråskala för komprimering
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=แปลงเป็น PDF
|
|||||||
compress.title=บีบอัด
|
compress.title=บีบอัด
|
||||||
compress.header=บีบอัด PDF
|
compress.header=บีบอัด PDF
|
||||||
compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF
|
compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF
|
||||||
|
compress.grayscale.label=ใช้ระดับสีเทาสำหรับการบีบอัด
|
||||||
compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 5
|
compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ:
|
compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=ใช้ระดับสีเทาสำหรับการบีบอัด
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF'e Dönüştür
|
|||||||
compress.title=Sıkıştır
|
compress.title=Sıkıştır
|
||||||
compress.header=PDF'i Sıkıştır
|
compress.header=PDF'i Sıkıştır
|
||||||
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
|
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
|
||||||
|
compress.grayscale.label=Sıkıştırma için Gri Ton Uygula
|
||||||
compress.selectText.1=Manuel Mod - 1'den 5'e
|
compress.selectText.1=Manuel Mod - 1'den 5'e
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimizasyon seviyesi:
|
compress.selectText.2=Optimizasyon seviyesi:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Sıkıştırma için Gri Ton Uygula
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Перетворити у PDF
|
|||||||
compress.title=Стиснути
|
compress.title=Стиснути
|
||||||
compress.header=Стиснути PDF
|
compress.header=Стиснути PDF
|
||||||
compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF.
|
compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF.
|
||||||
|
compress.grayscale.label=Застосувати відтінки сірого для стиснення
|
||||||
compress.selectText.1=Ручний режим - від 1 до 5
|
compress.selectText.1=Ручний режим - від 1 до 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Рівень оптимізації:
|
compress.selectText.2=Рівень оптимізації:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Застосувати відтінки сірого для стиснення
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=Chuyển đổi sang PDF
|
|||||||
compress.title=Nén
|
compress.title=Nén
|
||||||
compress.header=Nén PDF
|
compress.header=Nén PDF
|
||||||
compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF.
|
compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF.
|
||||||
|
compress.grayscale.label=Áp dụng thang độ xám để nén
|
||||||
compress.selectText.1=Chế độ thủ công - Từ 1 đến 5
|
compress.selectText.1=Chế độ thủ công - Từ 1 đến 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Mức độ tối ưu hóa:
|
compress.selectText.2=Mức độ tối ưu hóa:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Áp dụng thang độ xám để nén
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=PDF ལ་བསྒྱུར་བ།
|
|||||||
compress.title=སྡུད་སྒྲིལ།
|
compress.title=སྡུད་སྒྲིལ།
|
||||||
compress.header=PDF སྡུད་སྒྲིལ།
|
compress.header=PDF སྡུད་སྒྲིལ།
|
||||||
compress.credit=ཞབས་ཞུ་འདིས་ PDF སྡུད་སྒྲིལ་/ཡར་རྒྱས་གཏོང་བའི་ཆེད་དུ་ qpdf བེད་སྤྱོད་བྱེད་པ།
|
compress.credit=ཞབས་ཞུ་འདིས་ PDF སྡུད་སྒྲིལ་/ཡར་རྒྱས་གཏོང་བའི་ཆེད་དུ་ qpdf བེད་སྤྱོད་བྱེད་པ།
|
||||||
|
compress.grayscale.label=应用灰度进行压缩
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mode - From 1 to 4
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Optimisation level:
|
compress.selectText.2=Optimisation level:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=པར་གཞི།
|
|||||||
validateSignature.cert.keyUsage=ལྡེ་མིག་བེད་སྤྱོད།
|
validateSignature.cert.keyUsage=ལྡེ་མིག་བེད་སྤྱོད།
|
||||||
validateSignature.cert.selfSigned=རང་མིང་རྟགས།
|
validateSignature.cert.selfSigned=རང་མིང་རྟགས།
|
||||||
validateSignature.cert.bits=གནས།
|
validateSignature.cert.bits=གནས།
|
||||||
compress.grayscale.label=应用灰度进行压缩
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=转换为 PDF
|
|||||||
compress.title=压缩
|
compress.title=压缩
|
||||||
compress.header=压缩 PDF
|
compress.header=压缩 PDF
|
||||||
compress.credit=此服务使用qpdf进行 PDF 压缩/优化。
|
compress.credit=此服务使用qpdf进行 PDF 压缩/优化。
|
||||||
|
compress.grayscale.label=应用灰度进行压缩
|
||||||
compress.selectText.1=手动模式 - 从 1 到 5
|
compress.selectText.1=手动模式 - 从 1 到 5
|
||||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=优化级别:
|
compress.selectText.2=优化级别:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=版本
|
|||||||
validateSignature.cert.keyUsage=密钥用途
|
validateSignature.cert.keyUsage=密钥用途
|
||||||
validateSignature.cert.selfSigned=自签名
|
validateSignature.cert.selfSigned=自签名
|
||||||
validateSignature.cert.bits=比特
|
validateSignature.cert.bits=比特
|
||||||
compress.grayscale.label=应用灰度进行压缩
|
|
||||||
|
|||||||
@@ -951,6 +951,7 @@ fileToPDF.submit=轉換為 PDF
|
|||||||
compress.title=壓縮
|
compress.title=壓縮
|
||||||
compress.header=壓縮 PDF
|
compress.header=壓縮 PDF
|
||||||
compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。
|
compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。
|
||||||
|
compress.grayscale.label=應用灰階進行壓縮
|
||||||
compress.selectText.1=手動模式 - 從 1 到 5
|
compress.selectText.1=手動模式 - 從 1 到 5
|
||||||
compress.selectText.1.1=在最佳化等級 6 到 9 時,除了一般 PDF 壓縮外,圖片解析度也會降低以進一步減少檔案大小。較高的壓縮等級會進行更高強度的圖片壓縮(最多可壓縮到原始大小的 50%),以達到更高的壓縮率,但可能會影響圖片品質。
|
compress.selectText.1.1=在最佳化等級 6 到 9 時,除了一般 PDF 壓縮外,圖片解析度也會降低以進一步減少檔案大小。較高的壓縮等級會進行更高強度的圖片壓縮(最多可壓縮到原始大小的 50%),以達到更高的壓縮率,但可能會影響圖片品質。
|
||||||
compress.selectText.2=最佳化等級:
|
compress.selectText.2=最佳化等級:
|
||||||
@@ -1384,4 +1385,3 @@ validateSignature.cert.version=版本
|
|||||||
validateSignature.cert.keyUsage=金鑰用途
|
validateSignature.cert.keyUsage=金鑰用途
|
||||||
validateSignature.cert.selfSigned=自我簽署
|
validateSignature.cert.selfSigned=自我簽署
|
||||||
validateSignature.cert.bits=位元
|
validateSignature.cert.bits=位元
|
||||||
compress.grayscale.label=應用灰階進行壓縮
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ system:
|
|||||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
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
|
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
||||||
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
||||||
enableAnalytics: 'undefined' # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
enableAnalytics: null # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
||||||
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
||||||
datasource:
|
datasource:
|
||||||
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
|
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
|
||||||
@@ -111,7 +111,7 @@ ui:
|
|||||||
appName: '' # application's visible name
|
appName: '' # application's visible name
|
||||||
homeDescription: '' # short description or tagline shown on the homepage
|
homeDescription: '' # short description or tagline shown on the homepage
|
||||||
appNameNavbar: '' # name displayed on the navigation bar
|
appNameNavbar: '' # name displayed on the navigation bar
|
||||||
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
|
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
|
||||||
|
|
||||||
endpoints:
|
endpoints:
|
||||||
toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
{
|
{
|
||||||
"moduleName": "ch.qos.logback:logback-classic",
|
"moduleName": "ch.qos.logback:logback-classic",
|
||||||
"moduleUrl": "http://www.qos.ch",
|
"moduleUrl": "http://www.qos.ch",
|
||||||
"moduleVersion": "1.5.16",
|
"moduleVersion": "1.5.17",
|
||||||
"moduleLicense": "GNU Lesser General Public License",
|
"moduleLicense": "GNU Lesser General Public License",
|
||||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "ch.qos.logback:logback-core",
|
"moduleName": "ch.qos.logback:logback-core",
|
||||||
"moduleUrl": "http://www.qos.ch",
|
"moduleUrl": "http://www.qos.ch",
|
||||||
"moduleVersion": "1.5.16",
|
"moduleVersion": "1.5.17",
|
||||||
"moduleLicense": "GNU Lesser General Public License",
|
"moduleLicense": "GNU Lesser General Public License",
|
||||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||||
},
|
},
|
||||||
@@ -1371,6 +1371,13 @@
|
|||||||
"moduleLicense": "MIT License",
|
"moduleLicense": "MIT License",
|
||||||
"moduleLicenseUrl": "http://www.opensource.org/licenses/mit-license.php"
|
"moduleLicenseUrl": "http://www.opensource.org/licenses/mit-license.php"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.snakeyaml:snakeyaml-engine",
|
||||||
|
"moduleUrl": "https://bitbucket.org/snakeyaml/snakeyaml-engine",
|
||||||
|
"moduleVersion": "2.9",
|
||||||
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.springdoc:springdoc-openapi-starter-common",
|
"moduleName": "org.springdoc:springdoc-openapi-starter-common",
|
||||||
"moduleVersion": "2.2.0",
|
"moduleVersion": "2.2.0",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const storedVersion = localStorage.getItem('surveyVersion');
|
const storedVersion = localStorage.getItem('surveyVersion');
|
||||||
if (storedVersion && storedVersion !== surveyVersion) {
|
if (storedVersion && storedVersion !== surveyVersion) {
|
||||||
localStorage.setItem('pageViews', '0');
|
localStorage.setItem('pageViews', '0');
|
||||||
|
localStorage.setItem('surveyVersion', surveyVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
|
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
|
||||||
|
|||||||
@@ -59,9 +59,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="groupConvertFrom" class="feature-group">
|
<div id="groupConvertFrom" class="feature-group">
|
||||||
@@ -92,9 +89,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-book', 'book', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags', 'convert')}">
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-markdown', 'markdown_copy', 'home.PDFToMarkdown.title', 'home.PDFToMarkdown.desc', 'PDFToMarkdown.tags', 'convert')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-markdown', 'markdown_copy', 'home.PDFToMarkdown.title', 'home.PDFToMarkdown.desc', 'PDFToMarkdown.tags', 'convert')}">
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +114,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
|
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
|
||||||
</div>
|
</div>
|
||||||
@@ -151,9 +142,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-book', 'book', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags', 'convert')}">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="groupSecurity" class="feature-group">
|
<div id="groupSecurity" class="feature-group">
|
||||||
|
|||||||
@@ -89,12 +89,12 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" th:href="@{'/split-pdfs'}"
|
<a class="nav-link" href="#" th:href="@{'/view-pdf'}"
|
||||||
th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:title="#{home.split.desc}">
|
th:classappend="${currentPage}=='view-pdf' ? 'active' : ''" th:title="#{home.viewPdf.desc}">
|
||||||
<span class="material-symbols-rounded">
|
<span class="material-symbols-rounded">
|
||||||
cut
|
menu_book
|
||||||
</span>
|
</span>
|
||||||
<span class="icon-text" th:data-text="#{home.split.title}" th:text="#{home.split.title}"></span>
|
<span class="icon-text" th:data-text="#{home.viewPdf.title}" th:text="#{home.viewPdf.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|||||||
@@ -163,9 +163,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', toolIcon='markdown', tags=#{MarkdownToPDF.tags}, toolGroup='convert')}">
|
th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', toolIcon='markdown', tags=#{MarkdownToPDF.tags}, toolGroup='convert')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/card :: card(id='book-to-pdf', cardTitle=#{home.BookToPDF.title}, cardText=#{home.BookToPDF.desc}, cardLink='book-to-pdf', toolIcon='book', tags=#{BookToPDF.tags}, toolGroup='convert')}">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -198,9 +195,6 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', toolIcon='csv', tags=#{tableExtraxt.tags}, toolGroup='convert')}">
|
th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', toolIcon='csv', tags=#{tableExtraxt.tags}, toolGroup='convert')}">
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
th:replace="~{fragments/card :: card(id='pdf-to-book', cardTitle=#{home.PDFToBook.title}, cardText=#{home.PDFToBook.desc}, cardLink='pdf-to-book', toolIcon='book', tags=#{PDFToBook.tags}, toolGroup='convert')}">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -357,10 +357,6 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
data-l10n-id="pdfjs-page-input" autocomplete="off">
|
data-l10n-id="pdfjs-page-input" autocomplete="off">
|
||||||
</span>
|
</span>
|
||||||
<span id="numPages" class="toolbarLabel"></span>
|
<span id="numPages" class="toolbarLabel"></span>
|
||||||
<a class="navbar-brand hiddenMediumView" th:href="@{'/'}" tabindex="16">
|
|
||||||
<img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon" style="max-height: 1.6rem; width: auto;">
|
|
||||||
<span class="icon-text" th:text="${@appName}">Stirling PDF</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="toolbarViewerRight">
|
<div id="toolbarViewerRight">
|
||||||
<div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
|
<div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
|
||||||
|
|||||||
@@ -259,4 +259,4 @@ class CustomLogoutSuccessHandlerTest {
|
|||||||
|
|
||||||
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ConvertWebsiteToPdfTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private RuntimePathConfig runtimePathConfig;
|
private RuntimePathConfig runtimePathConfig;
|
||||||
|
|
||||||
|
|
||||||
private ConvertWebsiteToPDF convertWebsiteToPDF;
|
private ConvertWebsiteToPDF convertWebsiteToPDF;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -5,31 +5,79 @@ import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class FileToPdfTest {
|
public class FileToPdfTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the HTML to PDF conversion.
|
||||||
|
* This test expects an IOException when an empty HTML input is provided.
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testConvertHtmlToPdf() {
|
public void testConvertHtmlToPdf() {
|
||||||
HTMLToPdfRequest request = new HTMLToPdfRequest();
|
HTMLToPdfRequest request = new HTMLToPdfRequest();
|
||||||
byte[] fileBytes = new byte[0]; // Sample file bytes
|
byte[] fileBytes = new byte[0]; // Sample file bytes (empty input)
|
||||||
String fileName = "test.html"; // Sample file name
|
String fileName = "test.html"; // Sample file name indicating an HTML file
|
||||||
boolean disableSanitize = false; // Sample boolean value
|
boolean disableSanitize = false; // Flag to control sanitization
|
||||||
|
|
||||||
// Check if the method throws IOException
|
// Expect an IOException to be thrown due to empty input
|
||||||
assertThrows(IOException.class, () -> {
|
Throwable thrown =
|
||||||
FileToPdf.convertHtmlToPdf("/path/",request, fileBytes, fileName, disableSanitize);
|
assertThrows(
|
||||||
});
|
IOException.class,
|
||||||
|
() ->
|
||||||
|
FileToPdf.convertHtmlToPdf(
|
||||||
|
"/path/", request, fileBytes, fileName, disableSanitize));
|
||||||
|
assertNotNull(thrown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test sanitizeZipFilename with null or empty input.
|
||||||
|
* It should return an empty string in these cases.
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testConvertBookTypeToPdf() {
|
public void testSanitizeZipFilename_NullOrEmpty() {
|
||||||
byte[] bytes = new byte[10]; // Sample bytes
|
assertEquals("", FileToPdf.sanitizeZipFilename(null));
|
||||||
String originalFilename = "test.epub"; // Sample original filename
|
assertEquals("", FileToPdf.sanitizeZipFilename(" "));
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the method throws IOException
|
/**
|
||||||
assertThrows(IOException.class, () -> {
|
* Test sanitizeZipFilename to ensure it removes path traversal sequences.
|
||||||
FileToPdf.convertBookTypeToPdf(bytes, originalFilename);
|
* This includes removing both forward and backward slash sequences.
|
||||||
});
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSanitizeZipFilename_RemovesTraversalSequences() {
|
||||||
|
String input = "../some/../path/..\\to\\file.txt";
|
||||||
|
String expected = "some/path/to/file.txt";
|
||||||
|
|
||||||
|
// Print output for debugging purposes
|
||||||
|
System.out.println("sanitizeZipFilename " + FileToPdf.sanitizeZipFilename(input));
|
||||||
|
System.out.flush();
|
||||||
|
|
||||||
|
// Expect that the method replaces backslashes with forward slashes
|
||||||
|
// and removes path traversal sequences
|
||||||
|
assertEquals(expected, FileToPdf.sanitizeZipFilename(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test sanitizeZipFilename to ensure that it removes leading drive letters and slashes.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSanitizeZipFilename_RemovesLeadingDriveAndSlashes() {
|
||||||
|
String input = "C:\\folder\\file.txt";
|
||||||
|
String expected = "folder/file.txt";
|
||||||
|
assertEquals(expected, FileToPdf.sanitizeZipFilename(input));
|
||||||
|
|
||||||
|
input = "/folder/file.txt";
|
||||||
|
expected = "folder/file.txt";
|
||||||
|
assertEquals(expected, FileToPdf.sanitizeZipFilename(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test sanitizeZipFilename to verify that safe filenames remain unchanged.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSanitizeZipFilename_NoChangeForSafeNames() {
|
||||||
|
String input = "folder/subfolder/file.txt";
|
||||||
|
assertEquals(input, FileToPdf.sanitizeZipFilename(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ class ValidatorTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user