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 ###
|
||||||
|
@ -49,7 +49,8 @@ public class KeygenLicenseVerifier {
|
|||||||
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 =
|
||||||
|
HttpClient.newBuilder()
|
||||||
.version(HttpClient.Version.HTTP_2)
|
.version(HttpClient.Version.HTTP_2)
|
||||||
.connectTimeout(java.time.Duration.ofSeconds(10))
|
.connectTimeout(java.time.Duration.ofSeconds(10))
|
||||||
.build();
|
.build();
|
||||||
@ -416,7 +417,9 @@ public class KeygenLicenseVerifier {
|
|||||||
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
|
||||||
@ -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) {
|
||||||
@ -534,8 +539,10 @@ public class KeygenLicenseVerifier {
|
|||||||
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
|
||||||
@ -553,7 +560,8 @@ 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
|
||||||
@ -562,8 +570,10 @@ public class KeygenLicenseVerifier {
|
|||||||
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,11 +603,14 @@ 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);
|
||||||
@ -605,17 +618,23 @@ public class KeygenLicenseVerifier {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -628,7 +647,8 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
// 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) {
|
||||||
@ -637,23 +657,28 @@ public class KeygenLicenseVerifier {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,12 +686,15 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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");
|
||||||
@ -748,22 +777,33 @@ public class KeygenLicenseVerifier {
|
|||||||
* @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()
|
||||||
|
.uri(
|
||||||
|
URI.create(
|
||||||
|
BASE_URL
|
||||||
|
+ "/"
|
||||||
|
+ ACCOUNT_ID
|
||||||
|
+ "/licenses/"
|
||||||
|
+ licenseId
|
||||||
|
+ "/machines"))
|
||||||
.header("Content-Type", "application/vnd.api+json")
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
.header("Accept", "application/vnd.api+json")
|
.header("Accept", "application/vnd.api+json")
|
||||||
.header("Authorization", "License " + licenseKey)
|
.header("Authorization", "License " + licenseKey)
|
||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,7 +817,8 @@ public class KeygenLicenseVerifier {
|
|||||||
*/
|
*/
|
||||||
private boolean deregisterMachine(String licenseKey, String machineId) {
|
private boolean deregisterMachine(String licenseKey, String machineId) {
|
||||||
try {
|
try {
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request =
|
||||||
|
HttpRequest.newBuilder()
|
||||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
|
||||||
.header("Content-Type", "application/vnd.api+json")
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
.header("Accept", "application/vnd.api+json")
|
.header("Accept", "application/vnd.api+json")
|
||||||
@ -785,14 +826,17 @@ public class KeygenLicenseVerifier {
|
|||||||
.DELETE()
|
.DELETE()
|
||||||
.build();
|
.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) {
|
||||||
|
@ -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();
|
||||||
|
@ -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,21 +107,379 @@
|
|||||||
<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);
|
||||||
|
|
||||||
fetchWithCsrf('api/v1/security/get-info-on-pdf', {
|
fetchWithCsrf('api/v1/security/get-info-on-pdf', {
|
||||||
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);
|
||||||
@ -95,6 +530,8 @@
|
|||||||
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