mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-12 17:52:13 +02:00
summary #2388
This commit is contained in:
parent
f94b8c3b22
commit
d575609837
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,6 +13,7 @@ local.properties
|
|||||||
.recommenders
|
.recommenders
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
*.local.json
|
||||||
version.properties
|
version.properties
|
||||||
|
|
||||||
#### Stirling-PDF Files ###
|
#### Stirling-PDF Files ###
|
||||||
|
@ -47,19 +47,20 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
// Shared HTTP client for connection pooling
|
// Shared HTTP client for connection pooling
|
||||||
private static final HttpClient httpClient = HttpClient.newBuilder()
|
private static final HttpClient httpClient =
|
||||||
.version(HttpClient.Version.HTTP_2)
|
HttpClient.newBuilder()
|
||||||
.connectTimeout(java.time.Duration.ofSeconds(10))
|
.version(HttpClient.Version.HTTP_2)
|
||||||
.build();
|
.connectTimeout(java.time.Duration.ofSeconds(10))
|
||||||
|
.build();
|
||||||
|
|
||||||
// License metadata context class to avoid shared mutable state
|
// License metadata context class to avoid shared mutable state
|
||||||
private static class LicenseContext {
|
private static class LicenseContext {
|
||||||
private boolean isFloatingLicense = false;
|
private boolean isFloatingLicense = false;
|
||||||
private int maxMachines = 1; // Default to 1 if not specified
|
private int maxMachines = 1; // Default to 1 if not specified
|
||||||
private boolean isEnterpriseLicense = false;
|
private boolean isEnterpriseLicense = false;
|
||||||
|
|
||||||
public LicenseContext() {}
|
public LicenseContext() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +249,7 @@ public class KeygenLicenseVerifier {
|
|||||||
// Check for floating license
|
// Check for floating license
|
||||||
context.isFloatingLicense = attributesObj.optBoolean("floating", false);
|
context.isFloatingLicense = attributesObj.optBoolean("floating", false);
|
||||||
context.maxMachines = attributesObj.optInt("maxMachines", 1);
|
context.maxMachines = attributesObj.optInt("maxMachines", 1);
|
||||||
|
|
||||||
// Extract metadata
|
// Extract metadata
|
||||||
JSONObject metadataObj = attributesObj.optJSONObject("metadata");
|
JSONObject metadataObj = attributesObj.optJSONObject("metadata");
|
||||||
if (metadataObj != null) {
|
if (metadataObj != null) {
|
||||||
@ -411,14 +412,16 @@ public class KeygenLicenseVerifier {
|
|||||||
// Check for floating license in policy
|
// Check for floating license in policy
|
||||||
boolean policyFloating = policyObj.optBoolean("floating", false);
|
boolean policyFloating = policyObj.optBoolean("floating", false);
|
||||||
int policyMaxMachines = policyObj.optInt("maxMachines", 1);
|
int policyMaxMachines = policyObj.optInt("maxMachines", 1);
|
||||||
|
|
||||||
// Policy settings take precedence
|
// Policy settings take precedence
|
||||||
if (policyFloating) {
|
if (policyFloating) {
|
||||||
context.isFloatingLicense = true;
|
context.isFloatingLicense = true;
|
||||||
context.maxMachines = policyMaxMachines;
|
context.maxMachines = policyMaxMachines;
|
||||||
log.info("Policy defines floating license with max machines: {}", context.maxMachines);
|
log.info(
|
||||||
|
"Policy defines floating license with max machines: {}",
|
||||||
|
context.maxMachines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract max users and isEnterprise from policy or metadata
|
// Extract max users and isEnterprise from policy or metadata
|
||||||
int users = policyObj.optInt("users", 1);
|
int users = policyObj.optInt("users", 1);
|
||||||
context.isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false);
|
context.isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false);
|
||||||
@ -474,7 +477,8 @@ public class KeygenLicenseVerifier {
|
|||||||
activateMachine(licenseKey, licenseId, machineFingerprint, context);
|
activateMachine(licenseKey, licenseId, machineFingerprint, context);
|
||||||
if (activated) {
|
if (activated) {
|
||||||
// Revalidate after activation
|
// Revalidate after activation
|
||||||
validationResponse = validateLicense(licenseKey, machineFingerprint, context);
|
validationResponse =
|
||||||
|
validateLicense(licenseKey, machineFingerprint, context);
|
||||||
isValid =
|
isValid =
|
||||||
validationResponse != null
|
validationResponse != null
|
||||||
&& validationResponse
|
&& validationResponse
|
||||||
@ -494,8 +498,8 @@ public class KeygenLicenseVerifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonNode validateLicense(String licenseKey, String machineFingerprint, LicenseContext context)
|
private JsonNode validateLicense(
|
||||||
throws Exception {
|
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception {
|
||||||
String requestBody =
|
String requestBody =
|
||||||
String.format(
|
String.format(
|
||||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||||
@ -514,7 +518,8 @@ public class KeygenLicenseVerifier {
|
|||||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response =
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.info("ValidateLicenseResponse body: {}", response.body());
|
log.info("ValidateLicenseResponse body: {}", response.body());
|
||||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
@ -527,21 +532,23 @@ public class KeygenLicenseVerifier {
|
|||||||
log.info("License validity: " + isValid);
|
log.info("License validity: " + isValid);
|
||||||
log.info("Validation detail: " + detail);
|
log.info("Validation detail: " + detail);
|
||||||
log.info("Validation code: " + code);
|
log.info("Validation code: " + code);
|
||||||
|
|
||||||
// Check if the license itself has floating attribute
|
// Check if the license itself has floating attribute
|
||||||
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
|
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
|
||||||
if (!licenseAttrs.isMissingNode()) {
|
if (!licenseAttrs.isMissingNode()) {
|
||||||
context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
|
context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
|
||||||
context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
|
context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
|
||||||
|
|
||||||
log.info("License floating (from license): {}, maxMachines: {}",
|
log.info(
|
||||||
context.isFloatingLicense, context.maxMachines);
|
"License floating (from license): {}, maxMachines: {}",
|
||||||
|
context.isFloatingLicense,
|
||||||
|
context.maxMachines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check the policy for floating license support if included
|
// Also check the policy for floating license support if included
|
||||||
JsonNode includedNode = jsonResponse.path("included");
|
JsonNode includedNode = jsonResponse.path("included");
|
||||||
JsonNode policyNode = null;
|
JsonNode policyNode = null;
|
||||||
|
|
||||||
if (includedNode.isArray()) {
|
if (includedNode.isArray()) {
|
||||||
for (JsonNode node : includedNode) {
|
for (JsonNode node : includedNode) {
|
||||||
if ("policies".equals(node.path("type").asText())) {
|
if ("policies".equals(node.path("type").asText())) {
|
||||||
@ -550,20 +557,23 @@ public class KeygenLicenseVerifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policyNode != null) {
|
if (policyNode != null) {
|
||||||
// Check if this is a floating license from policy
|
// Check if this is a floating license from policy
|
||||||
boolean policyFloating = policyNode.path("attributes").path("floating").asBoolean(false);
|
boolean policyFloating =
|
||||||
|
policyNode.path("attributes").path("floating").asBoolean(false);
|
||||||
int policyMaxMachines = policyNode.path("attributes").path("maxMachines").asInt(1);
|
int policyMaxMachines = policyNode.path("attributes").path("maxMachines").asInt(1);
|
||||||
|
|
||||||
// Policy takes precedence over license attributes
|
// Policy takes precedence over license attributes
|
||||||
if (policyFloating) {
|
if (policyFloating) {
|
||||||
context.isFloatingLicense = true;
|
context.isFloatingLicense = true;
|
||||||
context.maxMachines = policyMaxMachines;
|
context.maxMachines = policyMaxMachines;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("License floating (from policy): {}, maxMachines: {}",
|
log.info(
|
||||||
context.isFloatingLicense, context.maxMachines);
|
"License floating (from policy): {}, maxMachines: {}",
|
||||||
|
context.isFloatingLicense,
|
||||||
|
context.maxMachines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract user count, default to 1 if not specified
|
// Extract user count, default to 1 if not specified
|
||||||
@ -593,86 +603,104 @@ public class KeygenLicenseVerifier {
|
|||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint,
|
private boolean activateMachine(
|
||||||
LicenseContext context) throws Exception {
|
String licenseKey, String licenseId, String machineFingerprint, LicenseContext context)
|
||||||
|
throws Exception {
|
||||||
// For floating licenses, we first need to check if we need to deregister any machines
|
// For floating licenses, we first need to check if we need to deregister any machines
|
||||||
if (context.isFloatingLicense) {
|
if (context.isFloatingLicense) {
|
||||||
log.info("Processing floating license activation. Max machines allowed: {}", context.maxMachines);
|
log.info(
|
||||||
|
"Processing floating license activation. Max machines allowed: {}",
|
||||||
|
context.maxMachines);
|
||||||
|
|
||||||
// Get the current machines for this license
|
// Get the current machines for this license
|
||||||
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
|
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
|
||||||
if (machinesResponse != null) {
|
if (machinesResponse != null) {
|
||||||
JsonNode machines = machinesResponse.path("data");
|
JsonNode machines = machinesResponse.path("data");
|
||||||
int currentMachines = machines.size();
|
int currentMachines = machines.size();
|
||||||
|
|
||||||
log.info("Current machine count: {}, Max allowed: {}", currentMachines, context.maxMachines);
|
log.info(
|
||||||
|
"Current machine count: {}, Max allowed: {}",
|
||||||
|
currentMachines,
|
||||||
|
context.maxMachines);
|
||||||
|
|
||||||
// Check if the current fingerprint is already activated
|
// Check if the current fingerprint is already activated
|
||||||
boolean isCurrentMachineActivated = false;
|
boolean isCurrentMachineActivated = false;
|
||||||
String currentMachineId = null;
|
String currentMachineId = null;
|
||||||
|
|
||||||
for (JsonNode machine : machines) {
|
for (JsonNode machine : machines) {
|
||||||
if (machineFingerprint.equals(machine.path("attributes").path("fingerprint").asText())) {
|
if (machineFingerprint.equals(
|
||||||
|
machine.path("attributes").path("fingerprint").asText())) {
|
||||||
isCurrentMachineActivated = true;
|
isCurrentMachineActivated = true;
|
||||||
currentMachineId = machine.path("id").asText();
|
currentMachineId = machine.path("id").asText();
|
||||||
log.info("Current machine is already activated with ID: {}", currentMachineId);
|
log.info(
|
||||||
|
"Current machine is already activated with ID: {}",
|
||||||
|
currentMachineId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current machine is already activated, there's no need to do anything
|
// If the current machine is already activated, there's no need to do anything
|
||||||
if (isCurrentMachineActivated) {
|
if (isCurrentMachineActivated) {
|
||||||
log.info("Machine already activated. No action needed.");
|
log.info("Machine already activated. No action needed.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've reached the max machines limit, we need to deregister the oldest machine
|
// If we've reached the max machines limit, we need to deregister the oldest machine
|
||||||
if (currentMachines >= context.maxMachines) {
|
if (currentMachines >= context.maxMachines) {
|
||||||
log.info("Max machines reached. Deregistering oldest machine to make room for the new machine.");
|
log.info(
|
||||||
|
"Max machines reached. Deregistering oldest machine to make room for the new machine.");
|
||||||
|
|
||||||
// Find the oldest machine based on creation timestamp
|
// Find the oldest machine based on creation timestamp
|
||||||
if (machines.size() > 0) {
|
if (machines.size() > 0) {
|
||||||
// Find the machine with the oldest creation date
|
// Find the machine with the oldest creation date
|
||||||
String oldestMachineId = null;
|
String oldestMachineId = null;
|
||||||
java.time.Instant oldestTime = null;
|
java.time.Instant oldestTime = null;
|
||||||
|
|
||||||
for (JsonNode machine : machines) {
|
for (JsonNode machine : machines) {
|
||||||
String createdStr = machine.path("attributes").path("created").asText(null);
|
String createdStr =
|
||||||
|
machine.path("attributes").path("created").asText(null);
|
||||||
if (createdStr != null && !createdStr.isEmpty()) {
|
if (createdStr != null && !createdStr.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
java.time.Instant createdTime = java.time.Instant.parse(createdStr);
|
java.time.Instant createdTime =
|
||||||
|
java.time.Instant.parse(createdStr);
|
||||||
if (oldestTime == null || createdTime.isBefore(oldestTime)) {
|
if (oldestTime == null || createdTime.isBefore(oldestTime)) {
|
||||||
oldestTime = createdTime;
|
oldestTime = createdTime;
|
||||||
oldestMachineId = machine.path("id").asText();
|
oldestMachineId = machine.path("id").asText();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Could not parse creation time for machine: {}", e.getMessage());
|
log.warn(
|
||||||
|
"Could not parse creation time for machine: {}",
|
||||||
|
e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we couldn't determine the oldest by timestamp, use the first one
|
// If we couldn't determine the oldest by timestamp, use the first one
|
||||||
if (oldestMachineId == null) {
|
if (oldestMachineId == null) {
|
||||||
log.warn("Could not determine oldest machine by timestamp, using first machine in list");
|
log.warn(
|
||||||
|
"Could not determine oldest machine by timestamp, using first machine in list");
|
||||||
oldestMachineId = machines.path(0).path("id").asText();
|
oldestMachineId = machines.path(0).path("id").asText();
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Deregistering machine with ID: {}", oldestMachineId);
|
log.info("Deregistering machine with ID: {}", oldestMachineId);
|
||||||
|
|
||||||
boolean deregistered = deregisterMachine(licenseKey, oldestMachineId);
|
boolean deregistered = deregisterMachine(licenseKey, oldestMachineId);
|
||||||
if (!deregistered) {
|
if (!deregistered) {
|
||||||
log.error("Failed to deregister machine. Cannot proceed with activation.");
|
log.error(
|
||||||
|
"Failed to deregister machine. Cannot proceed with activation.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
log.info("Machine deregistered successfully. Proceeding with activation of new machine.");
|
log.info(
|
||||||
|
"Machine deregistered successfully. Proceeding with activation of new machine.");
|
||||||
} else {
|
} else {
|
||||||
log.error("License has reached machine limit but no machines were found to deregister. This is unexpected.");
|
log.error(
|
||||||
|
"License has reached machine limit but no machines were found to deregister. This is unexpected.");
|
||||||
// We'll still try to activate, but it might fail
|
// We'll still try to activate, but it might fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proceed with machine activation
|
// Proceed with machine activation
|
||||||
String hostname;
|
String hostname;
|
||||||
try {
|
try {
|
||||||
@ -720,7 +748,8 @@ public class KeygenLicenseVerifier {
|
|||||||
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
|
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response =
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.info("activateMachine Response body: " + response.body());
|
log.info("activateMachine Response body: " + response.body());
|
||||||
if (response.statusCode() == 201) {
|
if (response.statusCode() == 201) {
|
||||||
log.info("Machine activated successfully");
|
log.info("Machine activated successfully");
|
||||||
@ -738,61 +767,76 @@ public class KeygenLicenseVerifier {
|
|||||||
private String generateMachineFingerprint() {
|
private String generateMachineFingerprint() {
|
||||||
return GeneralUtils.generateMachineFingerprint();
|
return GeneralUtils.generateMachineFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all machines associated with a specific license
|
* Fetches all machines associated with a specific license
|
||||||
*
|
*
|
||||||
* @param licenseKey The license key to check
|
* @param licenseKey The license key to check
|
||||||
* @param licenseId The license ID
|
* @param licenseId The license ID
|
||||||
* @return JsonNode containing the list of machines, or null if an error occurs
|
* @return JsonNode containing the list of machines, or null if an error occurs
|
||||||
* @throws Exception if an error occurs during the HTTP request
|
* @throws Exception if an error occurs during the HTTP request
|
||||||
*/
|
*/
|
||||||
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
|
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request =
|
||||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/licenses/" + licenseId + "/machines"))
|
HttpRequest.newBuilder()
|
||||||
.header("Content-Type", "application/vnd.api+json")
|
.uri(
|
||||||
.header("Accept", "application/vnd.api+json")
|
URI.create(
|
||||||
.header("Authorization", "License " + licenseKey)
|
BASE_URL
|
||||||
.GET()
|
+ "/"
|
||||||
.build();
|
+ ACCOUNT_ID
|
||||||
|
+ "/licenses/"
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
+ licenseId
|
||||||
|
+ "/machines"))
|
||||||
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
|
.header("Accept", "application/vnd.api+json")
|
||||||
|
.header("Authorization", "License " + licenseKey)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response =
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.info("fetchMachinesForLicense Response body: {}", response.body());
|
log.info("fetchMachinesForLicense Response body: {}", response.body());
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
return objectMapper.readTree(response.body());
|
return objectMapper.readTree(response.body());
|
||||||
} else {
|
} else {
|
||||||
log.error("Error fetching machines for license. Status code: {}, error: {}",
|
log.error(
|
||||||
response.statusCode(), response.body());
|
"Error fetching machines for license. Status code: {}, error: {}",
|
||||||
|
response.statusCode(),
|
||||||
|
response.body());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deregisters a machine from a license
|
* Deregisters a machine from a license
|
||||||
*
|
*
|
||||||
* @param licenseKey The license key
|
* @param licenseKey The license key
|
||||||
* @param machineId The ID of the machine to deregister
|
* @param machineId The ID of the machine to deregister
|
||||||
* @return true if deregistration was successful, false otherwise
|
* @return true if deregistration was successful, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean deregisterMachine(String licenseKey, String machineId) {
|
private boolean deregisterMachine(String licenseKey, String machineId) {
|
||||||
try {
|
try {
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request =
|
||||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
HttpRequest.newBuilder()
|
||||||
.header("Content-Type", "application/vnd.api+json")
|
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
||||||
.header("Accept", "application/vnd.api+json")
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
.header("Authorization", "License " + licenseKey)
|
.header("Accept", "application/vnd.api+json")
|
||||||
.DELETE()
|
.header("Authorization", "License " + licenseKey)
|
||||||
.build();
|
.DELETE()
|
||||||
|
.build();
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
|
HttpResponse<String> response =
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
if (response.statusCode() == 204) {
|
if (response.statusCode() == 204) {
|
||||||
log.info("Machine {} successfully deregistered", machineId);
|
log.info("Machine {} successfully deregistered", machineId);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
log.error("Error deregistering machine. Status code: {}, error: {}",
|
log.error(
|
||||||
response.statusCode(), response.body());
|
"Error deregistering machine. Status code: {}, error: {}",
|
||||||
|
response.statusCode(),
|
||||||
|
response.body());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -47,7 +47,8 @@ public class ConvertMarkdownToPdf {
|
|||||||
description =
|
description =
|
||||||
"This endpoint takes a Markdown file input, converts it to HTML, and then to"
|
"This endpoint takes a Markdown file input, converts it to HTML, and then to"
|
||||||
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile) throws Exception {
|
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile)
|
||||||
|
throws Exception {
|
||||||
MultipartFile fileInput = generalFile.getFileInput();
|
MultipartFile fileInput = generalFile.getFileInput();
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
|
@ -626,32 +626,32 @@ public class CompressController {
|
|||||||
|
|
||||||
// Scale factors for different optimization levels
|
// Scale factors for different optimization levels
|
||||||
private double getScaleFactorForLevel(int optimizeLevel) {
|
private double getScaleFactorForLevel(int optimizeLevel) {
|
||||||
return switch (optimizeLevel) {
|
return switch (optimizeLevel) {
|
||||||
case 3 -> 0.85;
|
case 3 -> 0.85;
|
||||||
case 4 -> 0.75;
|
case 4 -> 0.75;
|
||||||
case 5 -> 0.65;
|
case 5 -> 0.65;
|
||||||
case 6 -> 0.55;
|
case 6 -> 0.55;
|
||||||
case 7 -> 0.45;
|
case 7 -> 0.45;
|
||||||
case 8 -> 0.35;
|
case 8 -> 0.35;
|
||||||
case 9 -> 0.25;
|
case 9 -> 0.25;
|
||||||
case 10 -> 0.15;
|
case 10 -> 0.15;
|
||||||
default -> 1.0;
|
default -> 1.0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// JPEG quality for different optimization levels
|
// JPEG quality for different optimization levels
|
||||||
private float getJpegQualityForLevel(int optimizeLevel) {
|
private float getJpegQualityForLevel(int optimizeLevel) {
|
||||||
return switch (optimizeLevel) {
|
return switch (optimizeLevel) {
|
||||||
case 3 -> 0.85f;
|
case 3 -> 0.85f;
|
||||||
case 4 -> 0.80f;
|
case 4 -> 0.80f;
|
||||||
case 5 -> 0.75f;
|
case 5 -> 0.75f;
|
||||||
case 6 -> 0.70f;
|
case 6 -> 0.70f;
|
||||||
case 7 -> 0.60f;
|
case 7 -> 0.60f;
|
||||||
case 8 -> 0.50f;
|
case 8 -> 0.50f;
|
||||||
case 9 -> 0.35f;
|
case 9 -> 0.35f;
|
||||||
case 10 -> 0.2f;
|
case 10 -> 0.2f;
|
||||||
default -> 0.7f;
|
default -> 0.7f;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
|
@ -146,8 +146,8 @@ public class CertSignController {
|
|||||||
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"
|
"This endpoint accepts a PDF file, a digital certificate and related"
|
||||||
+ " information to sign the PDF. It then returns the digitally signed PDF"
|
+ " information to sign the PDF. It then returns the digitally signed PDF"
|
||||||
+ " file. Input:PDF Output: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();
|
||||||
|
@ -91,6 +91,59 @@ public class GetInfoOnPDF {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates structured summary data about the PDF highlighting its unique characteristics such
|
||||||
|
* as encryption status, permission restrictions, and standards compliance.
|
||||||
|
*
|
||||||
|
* @param document The PDF document to analyze
|
||||||
|
* @return An ObjectNode containing structured summary data
|
||||||
|
*/
|
||||||
|
private ObjectNode generatePDFSummaryData(PDDocument document) {
|
||||||
|
ObjectNode summaryData = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
// Check if encrypted
|
||||||
|
if (document.isEncrypted()) {
|
||||||
|
summaryData.put("encrypted", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
AccessPermission ap = document.getCurrentAccessPermission();
|
||||||
|
ArrayNode restrictedPermissions = objectMapper.createArrayNode();
|
||||||
|
|
||||||
|
if (!ap.canAssembleDocument()) restrictedPermissions.add("document assembly");
|
||||||
|
if (!ap.canExtractContent()) restrictedPermissions.add("content extraction");
|
||||||
|
if (!ap.canExtractForAccessibility()) restrictedPermissions.add("accessibility extraction");
|
||||||
|
if (!ap.canFillInForm()) restrictedPermissions.add("form filling");
|
||||||
|
if (!ap.canModify()) restrictedPermissions.add("modification");
|
||||||
|
if (!ap.canModifyAnnotations()) restrictedPermissions.add("annotation modification");
|
||||||
|
if (!ap.canPrint()) restrictedPermissions.add("printing");
|
||||||
|
|
||||||
|
if (restrictedPermissions.size() > 0) {
|
||||||
|
summaryData.set("restrictedPermissions", restrictedPermissions);
|
||||||
|
summaryData.put("restrictedPermissionsCount", restrictedPermissions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check standard compliance
|
||||||
|
if (checkForStandard(document, "PDF/A")) {
|
||||||
|
summaryData.put("standardCompliance", "PDF/A");
|
||||||
|
summaryData.put("standardPurpose", "long-term archiving");
|
||||||
|
} else if (checkForStandard(document, "PDF/X")) {
|
||||||
|
summaryData.put("standardCompliance", "PDF/X");
|
||||||
|
summaryData.put("standardPurpose", "graphic exchange");
|
||||||
|
} else if (checkForStandard(document, "PDF/UA")) {
|
||||||
|
summaryData.put("standardCompliance", "PDF/UA");
|
||||||
|
summaryData.put("standardPurpose", "universal accessibility");
|
||||||
|
} else if (checkForStandard(document, "PDF/E")) {
|
||||||
|
summaryData.put("standardCompliance", "PDF/E");
|
||||||
|
summaryData.put("standardPurpose", "engineering workflows");
|
||||||
|
} else if (checkForStandard(document, "PDF/VT")) {
|
||||||
|
summaryData.put("standardCompliance", "PDF/VT");
|
||||||
|
summaryData.put("standardPurpose", "variable and transactional printing");
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaryData;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||||
// Check XMP Metadata
|
// Check XMP Metadata
|
||||||
try {
|
try {
|
||||||
@ -191,6 +244,12 @@ public class GetInfoOnPDF {
|
|||||||
}
|
}
|
||||||
jsonOutput.set("FormFields", formFieldsNode);
|
jsonOutput.set("FormFields", formFieldsNode);
|
||||||
|
|
||||||
|
// Generate structured summary data about PDF characteristics
|
||||||
|
ObjectNode summaryData = generatePDFSummaryData(pdfBoxDoc);
|
||||||
|
if (summaryData != null && summaryData.size() > 0) {
|
||||||
|
jsonOutput.set("SummaryData", summaryData);
|
||||||
|
}
|
||||||
|
|
||||||
// embeed files TODO size
|
// embeed files TODO size
|
||||||
if (catalog.getNames() != null) {
|
if (catalog.getNames() != null) {
|
||||||
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
|
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
|
||||||
@ -622,8 +681,8 @@ public class GetInfoOnPDF {
|
|||||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||||
permissionsNode.put(
|
permissionsNode.put(
|
||||||
"Extracting for accessibility",
|
"Extracting for accessibility",
|
||||||
getPermissionState(ap.canExtractForAccessibility()));
|
getPermissionState(ap.canExtractForAccessibility()));
|
||||||
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||||
|
@ -675,6 +675,28 @@ getPdfInfo.title=Get Info on PDF
|
|||||||
getPdfInfo.header=Get Info on PDF
|
getPdfInfo.header=Get Info on PDF
|
||||||
getPdfInfo.submit=Get Info
|
getPdfInfo.submit=Get Info
|
||||||
getPdfInfo.downloadJson=Download JSON
|
getPdfInfo.downloadJson=Download JSON
|
||||||
|
getPdfInfo.summary=PDF Summary
|
||||||
|
getPdfInfo.summary.encrypted=This PDF is encrypted so may face issues with some applications
|
||||||
|
getPdfInfo.summary.permissions=This PDF has {0} restricted permissions which may limit what you can do with it
|
||||||
|
getPdfInfo.summary.compliance=This PDF complies with the {0} standard
|
||||||
|
getPdfInfo.summary.basicInfo=Basic Information
|
||||||
|
getPdfInfo.summary.docInfo=Document Information
|
||||||
|
getPdfInfo.summary.encrypted.alert=Encrypted PDF - This document is password protected
|
||||||
|
getPdfInfo.summary.not.encrypted.alert=Unencrypted PDF - No password protection
|
||||||
|
getPdfInfo.summary.permissions.alert=Restricted Permissions - {0} actions are not allowed
|
||||||
|
getPdfInfo.summary.all.permissions.alert=All Permissions Allowed
|
||||||
|
getPdfInfo.summary.compliance.alert={0} Compliant
|
||||||
|
getPdfInfo.summary.no.compliance.alert=No Compliance Standards
|
||||||
|
getPdfInfo.summary.security.section=Security Status
|
||||||
|
getPdfInfo.section.BasicInfo=Basic Information about the PDF document including file size, page count, and language
|
||||||
|
getPdfInfo.section.Metadata=Document metadata including title, author, creation date and other document properties
|
||||||
|
getPdfInfo.section.DocumentInfo=Technical details about the PDF document structure and version
|
||||||
|
getPdfInfo.section.Compliancy=PDF standards compliance information (PDF/A, PDF/X, etc.)
|
||||||
|
getPdfInfo.section.Encryption=Security and encryption details of the document
|
||||||
|
getPdfInfo.section.Permissions=Document permission settings that control what actions can be performed
|
||||||
|
getPdfInfo.section.Other=Additional document components like bookmarks, layers, and embedded files
|
||||||
|
getPdfInfo.section.FormFields=Interactive form fields present in the document
|
||||||
|
getPdfInfo.section.PerPageInfo=Detailed information about each page in the document
|
||||||
|
|
||||||
|
|
||||||
#markdown-to-pdf
|
#markdown-to-pdf
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6 bg-card">
|
<div class="col-md-7 bg-card">
|
||||||
<div class="tool-header">
|
<div class="tool-header">
|
||||||
<span class="material-symbols-rounded tool-header-icon other">info</span>
|
<span class="material-symbols-rounded tool-header-icon other">info</span>
|
||||||
<span class="tool-header-text" th:text="#{getPdfInfo.header}"></span>
|
<span class="tool-header-text" th:text="#{getPdfInfo.header}"></span>
|
||||||
@ -22,6 +22,82 @@
|
|||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{getPdfInfo.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{getPdfInfo.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
<div class="container mt-0">
|
<div class="container mt-0">
|
||||||
|
<!-- PDF Summary section -->
|
||||||
|
<div id="pdf-summary" class="card mt-3 mb-3" style="display: none;">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0" id="summary-heading">PDF Summary</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Quick overview of key details -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6 id="summary-basic-info-heading">Basic Information</h6>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><strong>Pages:</strong> <span id="summary-pages">-</span></li>
|
||||||
|
<li><strong>File Size:</strong> <span id="summary-size">-</span></li>
|
||||||
|
<li><strong>PDF Version:</strong> <span id="summary-version">-</span></li>
|
||||||
|
<li><strong>Language:</strong> <span id="summary-language">-</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6 id="summary-doc-info-heading">Document Information</h6>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><strong>Title:</strong> <span id="summary-title">-</span></li>
|
||||||
|
<li><strong>Author:</strong> <span id="summary-author">-</span></li>
|
||||||
|
<li><strong>Created:</strong> <span id="summary-created">-</span></li>
|
||||||
|
<li><strong>Modified:</strong> <span id="summary-modified">-</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Security section -->
|
||||||
|
<div class="mt-4 mb-3">
|
||||||
|
<h6 id="summary-security-heading">Security Status</h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div id="encryption-status" class="card mb-2 h-100">
|
||||||
|
<div class="card-body p-2 d-flex align-items-center">
|
||||||
|
<span id="encryption-icon" class="me-2"><i class="bi bi-lock"></i></span>
|
||||||
|
<span id="encryption-text" class="small">Encryption: Unknown</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div id="permissions-status" class="card mb-2 h-100">
|
||||||
|
<div class="card-body p-2 d-flex align-items-center">
|
||||||
|
<span id="permissions-icon" class="me-2"><i class="bi bi-shield"></i></span>
|
||||||
|
<span id="permissions-text" class="small">Permissions: Unknown</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div id="compliance-status" class="card mb-2 h-100">
|
||||||
|
<div class="card-body p-2 d-flex align-items-center">
|
||||||
|
<span id="compliance-icon" class="me-2"><i class="bi bi-check-circle"></i></span>
|
||||||
|
<span id="compliance-text" class="small">Compliance: Unknown</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detailed alerts -->
|
||||||
|
<div id="summary-alerts" class="mt-3">
|
||||||
|
<!-- Will be populated with detailed alerts for encryption, permissions, etc. -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptive note about PDF characteristics -->
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">PDF Overview</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p id="summary-text" class="mb-0"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Iterate over each main section in the JSON -->
|
<!-- Iterate over each main section in the JSON -->
|
||||||
<div id="json-content">
|
<div id="json-content">
|
||||||
<!-- JavaScript will populate this section -->
|
<!-- JavaScript will populate this section -->
|
||||||
@ -31,10 +107,48 @@
|
|||||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
||||||
<script>
|
<script th:inline="javascript">
|
||||||
|
// Pre-load message translations
|
||||||
|
const getPdfInfoSummary = /*[[#{getPdfInfo.summary}]]*/ "PDF Summary";
|
||||||
|
const getPdfInfoSummaryEncrypted = /*[[#{getPdfInfo.summary.encrypted}]]*/ "This PDF is encrypted so may face issues with some applications";
|
||||||
|
const getPdfInfoSummaryPermissions = /*[[#{getPdfInfo.summary.permissions}]]*/ "This PDF has {0} restricted permissions which may limit what you can do with it";
|
||||||
|
const getPdfInfoSummaryCompliance = /*[[#{getPdfInfo.summary.compliance}]]*/ "This PDF complies with the {0} standard, meaning it is suitable for {1}";
|
||||||
|
const getPdfInfoSummaryBasicInfo = /*[[#{getPdfInfo.summary.basicInfo}]]*/ "Basic Information";
|
||||||
|
const getPdfInfoSummaryDocInfo = /*[[#{getPdfInfo.summary.docInfo}]]*/ "Document Information";
|
||||||
|
const getPdfInfoSummarySecuritySection = /*[[#{getPdfInfo.summary.security.section}]]*/ "Security Status";
|
||||||
|
const getPdfInfoSummaryEncryptedAlert = /*[[#{getPdfInfo.summary.encrypted.alert}]]*/ "Encrypted PDF - This document is password protected";
|
||||||
|
const getPdfInfoSummaryNotEncryptedAlert = /*[[#{getPdfInfo.summary.not.encrypted.alert}]]*/ "Unencrypted PDF - No password protection";
|
||||||
|
const getPdfInfoSummaryPermissionsAlert = /*[[#{getPdfInfo.summary.permissions.alert}]]*/ "Restricted Permissions - {0} actions are not allowed";
|
||||||
|
const getPdfInfoSummaryAllPermissionsAlert = /*[[#{getPdfInfo.summary.all.permissions.alert}]]*/ "All Permissions Allowed";
|
||||||
|
const getPdfInfoSummaryComplianceAlert = /*[[#{getPdfInfo.summary.compliance.alert}]]*/ "{0} Compliant";
|
||||||
|
const getPdfInfoSummaryNoComplianceAlert = /*[[#{getPdfInfo.summary.no.compliance.alert}]]*/ "No Compliance Standards";
|
||||||
|
|
||||||
|
// Update the summary headings
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.getElementById('summary-heading').textContent = getPdfInfoSummary;
|
||||||
|
document.getElementById('summary-basic-info-heading').textContent = getPdfInfoSummaryBasicInfo;
|
||||||
|
document.getElementById('summary-doc-info-heading').textContent = getPdfInfoSummaryDocInfo;
|
||||||
|
document.getElementById('summary-security-heading').textContent = getPdfInfoSummarySecuritySection;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-load section descriptions
|
||||||
|
const getPdfInfoSectionBasicInfo = /*[[#{getPdfInfo.section.BasicInfo}]]*/ "Basic Information about the PDF document including file size, page count, and language";
|
||||||
|
const getPdfInfoSectionMetadata = /*[[#{getPdfInfo.section.Metadata}]]*/ "Document metadata including title, author, creation date and other document properties";
|
||||||
|
const getPdfInfoSectionDocumentInfo = /*[[#{getPdfInfo.section.DocumentInfo}]]*/ "Technical details about the PDF document structure and version";
|
||||||
|
const getPdfInfoSectionCompliancy = /*[[#{getPdfInfo.section.Compliancy}]]*/ "PDF standards compliance information (PDF/A, PDF/X, etc.)";
|
||||||
|
const getPdfInfoSectionEncryption = /*[[#{getPdfInfo.section.Encryption}]]*/ "Security and encryption details of the document";
|
||||||
|
const getPdfInfoSectionPermissions = /*[[#{getPdfInfo.section.Permissions}]]*/ "Document permission settings that control what actions can be performed";
|
||||||
|
const getPdfInfoSectionOther = /*[[#{getPdfInfo.section.Other}]]*/ "Additional document components like bookmarks, layers, and embedded files";
|
||||||
|
const getPdfInfoSectionFormFields = /*[[#{getPdfInfo.section.FormFields}]]*/ "Interactive form fields present in the document";
|
||||||
|
const getPdfInfoSectionPerPageInfo = /*[[#{getPdfInfo.section.PerPageInfo}]]*/ "Detailed information about each page in the document";
|
||||||
|
|
||||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Clear previous results when submitting a new form
|
||||||
|
document.getElementById('json-content').innerHTML = '';
|
||||||
|
document.getElementById('pdf-summary').style.display = 'none';
|
||||||
|
document.getElementById('downloadJson').style.display = 'none';
|
||||||
|
|
||||||
const formData = new FormData(event.target);
|
const formData = new FormData(event.target);
|
||||||
|
|
||||||
@ -42,10 +156,330 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => response.json()).then(data => {
|
}).then(response => response.json()).then(data => {
|
||||||
|
// Populate and display the enhanced PDF summary
|
||||||
|
populateSummarySection(data);
|
||||||
|
|
||||||
displayJsonData(data);
|
displayJsonData(data);
|
||||||
setDownloadLink(data);
|
setDownloadLink(data);
|
||||||
document.getElementById("downloadJson").style.display = "block";
|
document.getElementById("downloadJson").style.display = "block";
|
||||||
}).catch(error => console.error('Error:', error));
|
}).catch(error => console.error('Error:', error));
|
||||||
|
|
||||||
|
// Function to reset all summary elements to default state
|
||||||
|
function resetSummaryElements() {
|
||||||
|
// Reset basic information fields
|
||||||
|
document.getElementById('summary-pages').textContent = '-';
|
||||||
|
document.getElementById('summary-size').textContent = '-';
|
||||||
|
document.getElementById('summary-version').textContent = '-';
|
||||||
|
document.getElementById('summary-language').textContent = '-';
|
||||||
|
|
||||||
|
// Reset document information fields
|
||||||
|
document.getElementById('summary-title').textContent = '-';
|
||||||
|
document.getElementById('summary-author').textContent = '-';
|
||||||
|
document.getElementById('summary-created').textContent = '-';
|
||||||
|
document.getElementById('summary-modified').textContent = '-';
|
||||||
|
|
||||||
|
// Reset security status cards
|
||||||
|
const cards = ['encryption-status', 'permissions-status', 'compliance-status'];
|
||||||
|
cards.forEach(id => {
|
||||||
|
const card = document.getElementById(id);
|
||||||
|
// Remove all classes except the base ones
|
||||||
|
card.className = 'card mb-2 h-100';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset status text and icons
|
||||||
|
document.getElementById('encryption-icon').innerHTML = '<i class="bi bi-lock"></i>';
|
||||||
|
document.getElementById('encryption-text').textContent = 'Encryption: Unknown';
|
||||||
|
|
||||||
|
document.getElementById('permissions-icon').innerHTML = '<i class="bi bi-shield"></i>';
|
||||||
|
document.getElementById('permissions-text').textContent = 'Permissions: Unknown';
|
||||||
|
|
||||||
|
document.getElementById('compliance-icon').innerHTML = '<i class="bi bi-check-circle"></i>';
|
||||||
|
document.getElementById('compliance-text').textContent = 'Compliance: Unknown';
|
||||||
|
|
||||||
|
// Clear alerts container
|
||||||
|
document.getElementById('summary-alerts').innerHTML = '';
|
||||||
|
|
||||||
|
// Reset summary text
|
||||||
|
document.getElementById('summary-text').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to populate the enhanced summary section
|
||||||
|
function populateSummarySection(data) {
|
||||||
|
// Reset all elements first
|
||||||
|
resetSummaryElements();
|
||||||
|
|
||||||
|
// Get basic information
|
||||||
|
if (data.BasicInfo) {
|
||||||
|
document.getElementById('summary-pages').textContent = data.BasicInfo["Number of pages"] || "-";
|
||||||
|
|
||||||
|
// Format file size nicely
|
||||||
|
let fileSize = data.BasicInfo["FileSizeInBytes"];
|
||||||
|
if (fileSize) {
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(fileSize) / Math.log(1024));
|
||||||
|
fileSize = (fileSize / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
|
||||||
|
document.getElementById('summary-size').textContent = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('summary-language').textContent = data.BasicInfo["Language"] || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get document information
|
||||||
|
if (data.DocumentInfo) {
|
||||||
|
document.getElementById('summary-version').textContent = data.DocumentInfo["PDF version"] || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get metadata
|
||||||
|
if (data.Metadata) {
|
||||||
|
document.getElementById('summary-title').textContent = data.Metadata["Title"] || "-";
|
||||||
|
document.getElementById('summary-author').textContent = data.Metadata["Author"] || "-";
|
||||||
|
document.getElementById('summary-created').textContent = data.Metadata["CreationDate"] || "-";
|
||||||
|
document.getElementById('summary-modified').textContent = data.Metadata["ModificationDate"] || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update security status cards
|
||||||
|
|
||||||
|
// Encryption status
|
||||||
|
const encryptionStatusCard = document.getElementById('encryption-status');
|
||||||
|
const encryptionIcon = document.getElementById('encryption-icon');
|
||||||
|
const encryptionText = document.getElementById('encryption-text');
|
||||||
|
|
||||||
|
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||||
|
encryptionIcon.innerHTML = '<i class="bi bi-lock-fill"></i>';
|
||||||
|
encryptionText.textContent = getPdfInfoSummaryEncryptedAlert;
|
||||||
|
} else {
|
||||||
|
encryptionIcon.innerHTML = '<i class="bi bi-unlock-fill"></i>';
|
||||||
|
encryptionText.textContent = getPdfInfoSummaryNotEncryptedAlert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions status
|
||||||
|
const permissionsStatusCard = document.getElementById('permissions-status');
|
||||||
|
const permissionsIcon = document.getElementById('permissions-icon');
|
||||||
|
const permissionsText = document.getElementById('permissions-text');
|
||||||
|
|
||||||
|
let restrictedPermissions = [];
|
||||||
|
if (data.Permissions) {
|
||||||
|
for (const [permission, state] of Object.entries(data.Permissions)) {
|
||||||
|
if (state === "Not Allowed") {
|
||||||
|
restrictedPermissions.push(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restrictedPermissions.length > 0) {
|
||||||
|
permissionsIcon.innerHTML = '<i class="bi bi-shield-lock-fill"></i>';
|
||||||
|
const formattedAlert = getPdfInfoSummaryPermissionsAlert.replace('{0}', restrictedPermissions.length);
|
||||||
|
permissionsText.textContent = formattedAlert;
|
||||||
|
} else {
|
||||||
|
permissionsIcon.innerHTML = '<i class="bi bi-shield-check"></i>';
|
||||||
|
permissionsText.textContent = getPdfInfoSummaryAllPermissionsAlert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compliance status
|
||||||
|
const complianceStatusCard = document.getElementById('compliance-status');
|
||||||
|
const complianceIcon = document.getElementById('compliance-icon');
|
||||||
|
const complianceText = document.getElementById('compliance-text');
|
||||||
|
|
||||||
|
let hasCompliance = false;
|
||||||
|
let compliantStandards = [];
|
||||||
|
|
||||||
|
if (data.Compliancy) {
|
||||||
|
for (const [standard, compliant] of Object.entries(data.Compliancy)) {
|
||||||
|
if (compliant === true) {
|
||||||
|
hasCompliance = true;
|
||||||
|
const standardName = standard.replace("Is", "").replace("Compliant", "");
|
||||||
|
compliantStandards.push(standardName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCompliance) {
|
||||||
|
complianceIcon.innerHTML = '<i class="bi bi-check-circle-fill"></i>';
|
||||||
|
const formattedAlert = getPdfInfoSummaryComplianceAlert.replace('{0}', compliantStandards.join(', '));
|
||||||
|
complianceText.textContent = formattedAlert;
|
||||||
|
} else {
|
||||||
|
complianceIcon.innerHTML = '<i class="bi bi-dash-circle"></i>';
|
||||||
|
complianceText.textContent = getPdfInfoSummaryNoComplianceAlert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create detailed characteristic alerts
|
||||||
|
const alertsContainer = document.getElementById('summary-alerts');
|
||||||
|
|
||||||
|
// Clear previous alerts
|
||||||
|
alertsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Create a single comprehensive security details section
|
||||||
|
let hasSummaryInfo = false;
|
||||||
|
|
||||||
|
// Create a consolidated security details card if there are security details worth highlighting
|
||||||
|
if ((data.Encryption && data.Encryption.IsEncrypted) ||
|
||||||
|
restrictedPermissions.length > 0 ||
|
||||||
|
hasCompliance) {
|
||||||
|
|
||||||
|
const securityDetailsCard = document.createElement('div');
|
||||||
|
securityDetailsCard.className = 'card mt-3 mb-3';
|
||||||
|
|
||||||
|
const cardHeader = document.createElement('div');
|
||||||
|
cardHeader.className = 'card-header';
|
||||||
|
cardHeader.innerHTML = '<h6 class="mb-0">Detailed Security Information</h6>';
|
||||||
|
securityDetailsCard.appendChild(cardHeader);
|
||||||
|
|
||||||
|
const cardBody = document.createElement('div');
|
||||||
|
cardBody.className = 'card-body';
|
||||||
|
|
||||||
|
// Add detailed encryption info
|
||||||
|
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||||
|
const encryptionDiv = document.createElement('div');
|
||||||
|
encryptionDiv.className = 'mb-3';
|
||||||
|
encryptionDiv.innerHTML = '<h6>Encryption Details:</h6>';
|
||||||
|
|
||||||
|
const encryptionList = document.createElement('ul');
|
||||||
|
encryptionList.className = 'list-unstyled';
|
||||||
|
|
||||||
|
if (data.Encryption.EncryptionAlgorithm) {
|
||||||
|
encryptionList.innerHTML += `<li><strong>Algorithm:</strong> ${data.Encryption.EncryptionAlgorithm}</li>`;
|
||||||
|
}
|
||||||
|
if (data.Encryption.KeyLength) {
|
||||||
|
encryptionList.innerHTML += `<li><strong>Key Length:</strong> ${data.Encryption.KeyLength} bits</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionDiv.appendChild(encryptionList);
|
||||||
|
cardBody.appendChild(encryptionDiv);
|
||||||
|
hasSummaryInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add detailed permissions info
|
||||||
|
if (restrictedPermissions.length > 0) {
|
||||||
|
const permissionsDiv = document.createElement('div');
|
||||||
|
permissionsDiv.className = 'mb-3';
|
||||||
|
permissionsDiv.innerHTML = '<h6>Restricted Permissions:</h6>';
|
||||||
|
|
||||||
|
const permissionsList = document.createElement('ul');
|
||||||
|
restrictedPermissions.forEach(perm => {
|
||||||
|
permissionsList.innerHTML += `<li>${perm}</li>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
permissionsDiv.appendChild(permissionsList);
|
||||||
|
cardBody.appendChild(permissionsDiv);
|
||||||
|
hasSummaryInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add detailed compliance info
|
||||||
|
if (hasCompliance) {
|
||||||
|
const complianceDiv = document.createElement('div');
|
||||||
|
complianceDiv.className = 'mb-3';
|
||||||
|
complianceDiv.innerHTML = '<h6>Standards Compliance:</h6>';
|
||||||
|
|
||||||
|
const complianceList = document.createElement('ul');
|
||||||
|
complianceList.className = 'list-unstyled';
|
||||||
|
|
||||||
|
compliantStandards.forEach(standard => {
|
||||||
|
let standardDescription = '';
|
||||||
|
|
||||||
|
// Add brief descriptions for standards
|
||||||
|
if (standard === "PDF/A") {
|
||||||
|
standardDescription = 'ISO standard for long-term document archiving';
|
||||||
|
} else if (standard === "PDF/X") {
|
||||||
|
standardDescription = 'ISO standard for printing and graphic arts exchange';
|
||||||
|
} else if (standard === "PDF/UA") {
|
||||||
|
standardDescription = 'ISO standard for universal accessibility';
|
||||||
|
} else if (standard === "PDF/E") {
|
||||||
|
standardDescription = 'ISO standard for engineering documents';
|
||||||
|
} else if (standard === "PDF/VT") {
|
||||||
|
standardDescription = 'ISO standard for variable and transactional printing';
|
||||||
|
}
|
||||||
|
|
||||||
|
complianceList.innerHTML += `<li><strong>${standard}:</strong> ${standardDescription}</li>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
complianceDiv.appendChild(complianceList);
|
||||||
|
cardBody.appendChild(complianceDiv);
|
||||||
|
hasSummaryInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
securityDetailsCard.appendChild(cardBody);
|
||||||
|
|
||||||
|
if (hasSummaryInfo) {
|
||||||
|
alertsContainer.appendChild(securityDetailsCard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a general document summary
|
||||||
|
const summaryTextElement = document.getElementById('summary-text');
|
||||||
|
|
||||||
|
// Create a general summary for the document
|
||||||
|
let generalSummary = `This is a ${data.BasicInfo["Number of pages"] || "multi"}-page PDF`;
|
||||||
|
|
||||||
|
if (data.Metadata && data.Metadata["Title"]) {
|
||||||
|
generalSummary += ` titled "${data.Metadata["Title"]}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.Metadata && data.Metadata["Author"]) {
|
||||||
|
generalSummary += ` created by ${data.Metadata["Author"]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.DocumentInfo && data.DocumentInfo["PDF version"]) {
|
||||||
|
generalSummary += ` (PDF version ${data.DocumentInfo["PDF version"]})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add security information to the general summary if relevant
|
||||||
|
if (data.Encryption && data.Encryption.IsEncrypted) {
|
||||||
|
generalSummary += '. The document is password protected';
|
||||||
|
|
||||||
|
if (data.Encryption.EncryptionAlgorithm) {
|
||||||
|
generalSummary += ` using ${data.Encryption.EncryptionAlgorithm}`;
|
||||||
|
|
||||||
|
if (data.Encryption.KeyLength) {
|
||||||
|
generalSummary += ` (${data.Encryption.KeyLength} bit)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restrictedPermissions.length > 0) {
|
||||||
|
generalSummary += `. It has ${restrictedPermissions.length} restricted permissions`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compliance standards if available
|
||||||
|
if (hasCompliance && compliantStandards.length > 0) {
|
||||||
|
generalSummary += `. This document complies with the ${compliantStandards.join(', ')} PDF standard${compliantStandards.length > 1 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generalSummary += '.';
|
||||||
|
|
||||||
|
// Remove SummaryData from JSON to avoid duplication
|
||||||
|
if (data.SummaryData) {
|
||||||
|
delete data.SummaryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryTextElement.innerHTML = generalSummary;
|
||||||
|
|
||||||
|
// Display the summary section
|
||||||
|
document.getElementById('pdf-summary').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSummaryFromData(summaryData) {
|
||||||
|
let summary = [];
|
||||||
|
|
||||||
|
// Handle encryption information
|
||||||
|
if (summaryData.encrypted) {
|
||||||
|
summary.push(getPdfInfoSummaryEncrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle permissions information
|
||||||
|
if (summaryData.restrictedPermissions && summaryData.restrictedPermissions.length > 0) {
|
||||||
|
const formattedPermissionsText = getPdfInfoSummaryPermissions.replace('{0}', summaryData.restrictedPermissionsCount);
|
||||||
|
summary.push(formattedPermissionsText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle standard compliance information
|
||||||
|
if (summaryData.standardCompliance) {
|
||||||
|
const formattedComplianceText = getPdfInfoSummaryCompliance
|
||||||
|
.replace('{0}', summaryData.standardCompliance);
|
||||||
|
summary.push(formattedComplianceText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary.join(' ');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function displayJsonData(jsonData) {
|
function displayJsonData(jsonData) {
|
||||||
@ -77,8 +511,9 @@
|
|||||||
header.className = 'card-header';
|
header.className = 'card-header';
|
||||||
header.id = `${safeKey}-heading-${depth}`;
|
header.id = `${safeKey}-heading-${depth}`;
|
||||||
const h5Elem = document.createElement('h5');
|
const h5Elem = document.createElement('h5');
|
||||||
h5Elem.className = 'mb-0';
|
h5Elem.className = 'mb-0 d-flex align-items-center';
|
||||||
|
|
||||||
|
// Create the main content (button or text)
|
||||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||||
h5Elem.appendChild(buttonElem);
|
h5Elem.appendChild(buttonElem);
|
||||||
@ -94,6 +529,8 @@
|
|||||||
} else {
|
} else {
|
||||||
h5Elem.textContent = `${key}: ${String(value)}`;
|
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Info buttons removed as requested
|
||||||
|
|
||||||
header.appendChild(h5Elem);
|
header.appendChild(h5Elem);
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
@ -2,36 +2,32 @@ package stirling.software.SPDF.config.security.mail;
|
|||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import jakarta.mail.MessagingException;
|
|
||||||
import jakarta.mail.internet.MimeMessage;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.mail.MessagingException;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.api.Email;
|
import stirling.software.SPDF.model.api.Email;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class EmailServiceTest {
|
public class EmailServiceTest {
|
||||||
|
|
||||||
@Mock
|
@Mock private JavaMailSender mailSender;
|
||||||
private JavaMailSender mailSender;
|
|
||||||
|
|
||||||
@Mock
|
@Mock private ApplicationProperties applicationProperties;
|
||||||
private ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
@Mock
|
@Mock private ApplicationProperties.Mail mailProperties;
|
||||||
private ApplicationProperties.Mail mailProperties;
|
|
||||||
|
|
||||||
@Mock
|
@Mock private MultipartFile fileInput;
|
||||||
private MultipartFile fileInput;
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks private EmailService emailService;
|
||||||
private EmailService emailService;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendEmailWithAttachment() throws MessagingException {
|
void testSendEmailWithAttachment() throws MessagingException {
|
||||||
|
@ -4,7 +4,6 @@ import static org.mockito.Mockito.*;
|
|||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
|
|
||||||
import jakarta.mail.MessagingException;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@ -14,6 +13,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.mail.MessagingException;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.security.mail.EmailService;
|
import stirling.software.SPDF.config.security.mail.EmailService;
|
||||||
import stirling.software.SPDF.model.api.Email;
|
import stirling.software.SPDF.model.api.Email;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user