mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-23 07:55:07 +00:00
Compare commits
No commits in common. "e1b9b78094d563100d96765b123bd593c485d5f9" and "083b68e2a0e9a44843ce41d7099e940ab97f0c60" have entirely different histories.
e1b9b78094
...
083b68e2a0
39
.github/labels.yml
vendored
39
.github/labels.yml
vendored
@ -137,41 +137,4 @@
|
|||||||
description: "Reverts a previous commit"
|
description: "Reverts a previous commit"
|
||||||
- name: "style"
|
- name: "style"
|
||||||
color: "FFA500"
|
color: "FFA500"
|
||||||
description: "Changes that do not affect the meaning of the code (formatting, etc.)"
|
description: "Changes that do not affect the meaning of the code (formatting, etc.)"
|
||||||
- name: "admin"
|
|
||||||
color: "195055"
|
|
||||||
- name: "codex"
|
|
||||||
color: "ededed"
|
|
||||||
description: null
|
|
||||||
- name: "Github"
|
|
||||||
color: "0052CC"
|
|
||||||
- name: "github_actions"
|
|
||||||
color: "000000"
|
|
||||||
description: "Pull requests that update GitHub Actions code"
|
|
||||||
- name: "needs-changes"
|
|
||||||
color: "A65A86"
|
|
||||||
- name: "on-hold"
|
|
||||||
color: "2526F9"
|
|
||||||
- name: "python"
|
|
||||||
color: "2b67c6"
|
|
||||||
description: "Pull requests that update Python code"
|
|
||||||
- name: "size:L"
|
|
||||||
color: "eb9500"
|
|
||||||
description: "This PR changes 100-499 lines ignoring generated files."
|
|
||||||
- name: "size:M"
|
|
||||||
color: "ebb800"
|
|
||||||
description: "This PR changes 30-99 lines ignoring generated files."
|
|
||||||
- name: "size:S"
|
|
||||||
color: "77b800"
|
|
||||||
description: "This PR changes 10-29 lines ignoring generated files."
|
|
||||||
- name: "size:XL"
|
|
||||||
color: "ff823f"
|
|
||||||
description: "This PR changes 500-999 lines ignoring generated files."
|
|
||||||
- name: "size:XS"
|
|
||||||
color: "00ff00"
|
|
||||||
description: "This PR changes 0-9 lines ignoring generated files."
|
|
||||||
- name: "size:XXL"
|
|
||||||
color: "ffb8b8"
|
|
||||||
description: "This PR changes 1000+ lines ignoring generated files."
|
|
||||||
- name: "to research"
|
|
||||||
color: "FBCA04"
|
|
51
.github/workflows/build.yml
vendored
51
.github/workflows/build.yml
vendored
@ -21,7 +21,6 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
jdk-version: [17, 21]
|
jdk-version: [17, 21]
|
||||||
spring-security: [true, false]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
@ -38,41 +37,56 @@ jobs:
|
|||||||
java-version: ${{ matrix.jdk-version }}
|
java-version: ${{ matrix.jdk-version }}
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
- name: Build with Gradle and no spring security
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
DISABLE_ADDITIONAL_FEATURES: true
|
||||||
|
|
||||||
|
- name: Build with Gradle and with spring security
|
||||||
|
run: ./gradlew clean build
|
||||||
|
env:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: false
|
||||||
|
|
||||||
- name: Check Test Reports Exist
|
- name: Check Test Reports Exist
|
||||||
id: check-reports
|
id: check-reports
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
declare -a dirs=(
|
|
||||||
"stirling-pdf/build/reports/tests/"
|
|
||||||
"stirling-pdf/build/test-results/"
|
|
||||||
"common/build/reports/tests/"
|
|
||||||
"common/build/test-results/"
|
|
||||||
"proprietary/build/reports/tests/"
|
|
||||||
"proprietary/build/test-results/"
|
|
||||||
)
|
|
||||||
missing_reports=()
|
missing_reports=()
|
||||||
for dir in "${dirs[@]}"; do
|
|
||||||
if [ ! -d "$dir" ]; then
|
# Check for required test report directories
|
||||||
missing_reports+=("$dir")
|
if [ ! -d "stirling-pdf/build/reports/tests/" ]; then
|
||||||
fi
|
missing_reports+=("stirling-pdf/build/reports/tests/")
|
||||||
done
|
fi
|
||||||
|
if [ ! -d "stirling-pdf/build/test-results/" ]; then
|
||||||
|
missing_reports+=("stirling-pdf/build/test-results/")
|
||||||
|
fi
|
||||||
|
if [ ! -d "common/build/reports/tests/" ]; then
|
||||||
|
missing_reports+=("common/build/reports/tests/")
|
||||||
|
fi
|
||||||
|
if [ ! -d "common/build/test-results/" ]; then
|
||||||
|
missing_reports+=("common/build/test-results/")
|
||||||
|
fi
|
||||||
|
if [ ! -d "proprietary/build/reports/tests/" ]; then
|
||||||
|
missing_reports+=("proprietary/build/reports/tests/")
|
||||||
|
fi
|
||||||
|
if [ ! -d "proprietary/build/test-results/" ]; then
|
||||||
|
missing_reports+=("proprietary/build/test-results/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fail if any required reports are missing
|
||||||
if [ ${#missing_reports[@]} -gt 0 ]; then
|
if [ ${#missing_reports[@]} -gt 0 ]; then
|
||||||
echo "ERROR: The following required test report directories are missing:"
|
echo "ERROR: The following required test report directories are missing:"
|
||||||
printf '%s\n' "${missing_reports[@]}"
|
printf '%s\n' "${missing_reports[@]}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "All required test report directories are present"
|
echo "All required test report directories are present"
|
||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: steps.check-reports.outcome == 'success'
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}
|
||||||
path: |
|
path: |
|
||||||
stirling-pdf/build/reports/tests/
|
stirling-pdf/build/reports/tests/
|
||||||
stirling-pdf/build/test-results/
|
stirling-pdf/build/test-results/
|
||||||
@ -84,7 +98,6 @@ jobs:
|
|||||||
proprietary/build/test-results/
|
proprietary/build/test-results/
|
||||||
proprietary/build/reports/problems/
|
proprietary/build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
if-no-files-found: warn
|
|
||||||
|
|
||||||
check-licence:
|
check-licence:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -6,73 +6,42 @@ import org.springframework.core.annotation.AliasFor;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for a POST endpoint that is executed through the Stirling "auto‑job" framework.
|
|
||||||
* <p>
|
|
||||||
* Behaviour notes:
|
|
||||||
* <ul>
|
|
||||||
* <li>The endpoint is registered with {@code POST} and, by default, consumes
|
|
||||||
* {@code multipart/form-data} unless you override {@link #consumes()}.</li>
|
|
||||||
* <li>When the client supplies {@code ?async=true} the call is handed to
|
|
||||||
* {@link stirling.software.common.service.JobExecutorService JobExecutorService} where it may
|
|
||||||
* be queued, retried, tracked and subject to time‑outs. For synchronous (default)
|
|
||||||
* invocations these advanced options are ignored.</li>
|
|
||||||
* <li>Progress information (see {@link #trackProgress()}) is stored in
|
|
||||||
* {@link stirling.software.common.service.TaskManager TaskManager} and can be
|
|
||||||
* polled via <code>GET /api/v1/general/job/{id}</code>.</li>
|
|
||||||
* </ul>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>Unless stated otherwise an attribute only affects <em>async</em> execution.</p>
|
|
||||||
*/
|
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@RequestMapping(method = RequestMethod.POST)
|
@RequestMapping(method = RequestMethod.POST)
|
||||||
public @interface AutoJobPostMapping {
|
public @interface AutoJobPostMapping {
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link RequestMapping#value} – the path mapping of the endpoint.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class, attribute = "value")
|
@AliasFor(annotation = RequestMapping.class, attribute = "value")
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
|
||||||
* MIME types this endpoint accepts. Defaults to {@code multipart/form-data}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = RequestMapping.class, attribute = "consumes")
|
@AliasFor(annotation = RequestMapping.class, attribute = "consumes")
|
||||||
String[] consumes() default {"multipart/form-data"};
|
String[] consumes() default {"multipart/form-data"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum execution time in milliseconds before the job is aborted.
|
* Custom timeout in milliseconds for this specific job. If not specified, the default system
|
||||||
* A negative value means "use the application default".
|
* timeout will be used.
|
||||||
* <p>Only honoured when {@code async=true}.</p>
|
|
||||||
*/
|
*/
|
||||||
long timeout() default -1;
|
long timeout() default -1;
|
||||||
|
|
||||||
/**
|
/** Maximum number of times to retry the job on failure. Default is 1 (no retries). */
|
||||||
* Total number of attempts (initial + retries). Must be at least 1.
|
|
||||||
* Retries are executed with exponential back‑off.
|
|
||||||
* <p>Only honoured when {@code async=true}.</p>
|
|
||||||
*/
|
|
||||||
int retryCount() default 1;
|
int retryCount() default 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record percentage / note updates so they can be retrieved via the REST status endpoint.
|
* Whether to track and report progress for this job. If enabled, the job will send progress
|
||||||
* <p>Only honoured when {@code async=true}.</p>
|
* updates through WebSocket.
|
||||||
*/
|
*/
|
||||||
boolean trackProgress() default true;
|
boolean trackProgress() default true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If {@code true} the job may be placed in a queue instead of being rejected when resources
|
* Whether this job can be queued when system resources are limited. If enabled, jobs will be
|
||||||
* are scarce.
|
* queued instead of rejected when the system is under high load. The queue size is dynamically
|
||||||
* <p>Only honoured when {@code async=true}.</p>
|
* adjusted based on available memory and CPU resources.
|
||||||
*/
|
*/
|
||||||
boolean queueable() default false;
|
boolean queueable() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relative resource weight (1–100) used by the scheduler to prioritise / throttle jobs. Values
|
* Optional resource weight of this job (1-100). Higher values indicate more resource-intensive
|
||||||
* below 1 are clamped to 1, values above 100 to 100.
|
* jobs that may need stricter queuing. Default is 50 (medium weight).
|
||||||
*/
|
*/
|
||||||
int resourceWeight() default 50;
|
int resourceWeight() default 50;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,12 @@ import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class EmlToPdf {
|
public class EmlToPdf {
|
||||||
|
|
||||||
private static final class StyleConstants {
|
private static final class StyleConstants {
|
||||||
// Font and layout constants
|
// Font and layout constants
|
||||||
static final int DEFAULT_FONT_SIZE = 12;
|
static final int DEFAULT_FONT_SIZE = 12;
|
||||||
static final String DEFAULT_FONT_FAMILY = "Helvetica, sans-serif";
|
static final String DEFAULT_FONT_FAMILY =
|
||||||
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif";
|
||||||
static final float DEFAULT_LINE_HEIGHT = 1.4f;
|
static final float DEFAULT_LINE_HEIGHT = 1.4f;
|
||||||
static final String DEFAULT_ZOOM = "1.0";
|
static final String DEFAULT_ZOOM = "1.0";
|
||||||
|
|
||||||
@ -70,15 +72,20 @@ public class EmlToPdf {
|
|||||||
static final int EML_CHECK_LENGTH = 8192;
|
static final int EML_CHECK_LENGTH = 8192;
|
||||||
static final int MIN_HEADER_COUNT_FOR_VALID_EML = 2;
|
static final int MIN_HEADER_COUNT_FOR_VALID_EML = 2;
|
||||||
|
|
||||||
private StyleConstants() {}
|
private StyleConstants() {
|
||||||
|
// Utility class - prevent instantiation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MimeConstants {
|
private static final class MimeConstants {
|
||||||
static final Pattern MIME_ENCODED_PATTERN =
|
static final Pattern MIME_ENCODED_PATTERN =
|
||||||
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
||||||
static final String ATTACHMENT_MARKER = "@";
|
static final String PAPERCLIP_EMOJI = "\uD83D\uDCCE"; // 📎
|
||||||
|
static final String ATTACHMENT_ICON_PLACEHOLDER = "icon";
|
||||||
|
|
||||||
private MimeConstants() {}
|
private MimeConstants() {
|
||||||
|
// Utility class - prevent instantiation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FileSizeConstants {
|
private static final class FileSizeConstants {
|
||||||
@ -86,7 +93,9 @@ public class EmlToPdf {
|
|||||||
static final long BYTES_IN_MB = BYTES_IN_KB * 1024L;
|
static final long BYTES_IN_MB = BYTES_IN_KB * 1024L;
|
||||||
static final long BYTES_IN_GB = BYTES_IN_MB * 1024L;
|
static final long BYTES_IN_GB = BYTES_IN_MB * 1024L;
|
||||||
|
|
||||||
private FileSizeConstants() {}
|
private FileSizeConstants() {
|
||||||
|
// Utility class - prevent instantiation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cached Jakarta Mail availability check
|
// Cached Jakarta Mail availability check
|
||||||
@ -95,15 +104,8 @@ public class EmlToPdf {
|
|||||||
private static boolean isJakartaMailAvailable() {
|
private static boolean isJakartaMailAvailable() {
|
||||||
if (jakartaMailAvailable == null) {
|
if (jakartaMailAvailable == null) {
|
||||||
try {
|
try {
|
||||||
// Check for core Jakarta Mail classes
|
|
||||||
Class.forName("jakarta.mail.internet.MimeMessage");
|
Class.forName("jakarta.mail.internet.MimeMessage");
|
||||||
Class.forName("jakarta.mail.Session");
|
Class.forName("jakarta.mail.Session");
|
||||||
Class.forName("jakarta.mail.internet.MimeUtility");
|
|
||||||
Class.forName("jakarta.mail.internet.MimePart");
|
|
||||||
Class.forName("jakarta.mail.internet.MimeMultipart");
|
|
||||||
Class.forName("jakarta.mail.Multipart");
|
|
||||||
Class.forName("jakarta.mail.Part");
|
|
||||||
|
|
||||||
jakartaMailAvailable = true;
|
jakartaMailAvailable = true;
|
||||||
log.debug("Jakarta Mail libraries are available");
|
log.debug("Jakarta Mail libraries are available");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
@ -170,7 +172,7 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateEmlInput(byte[] emlBytes) {
|
private static void validateEmlInput(byte[] emlBytes) throws IOException {
|
||||||
if (emlBytes == null || emlBytes.length == 0) {
|
if (emlBytes == null || emlBytes.length == 0) {
|
||||||
throw new IllegalArgumentException("EML file is empty or null");
|
throw new IllegalArgumentException("EML file is empty or null");
|
||||||
}
|
}
|
||||||
@ -206,6 +208,7 @@ public class EmlToPdf {
|
|||||||
disableSanitize);
|
disableSanitize);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
log.warn("Initial HTML to PDF conversion failed, trying with simplified HTML");
|
log.warn("Initial HTML to PDF conversion failed, trying with simplified HTML");
|
||||||
|
// Try with simplified HTML
|
||||||
String simplifiedHtml = simplifyHtmlContent(htmlContent);
|
String simplifiedHtml = simplifyHtmlContent(htmlContent);
|
||||||
return FileToPdf.convertHtmlToPdf(
|
return FileToPdf.convertHtmlToPdf(
|
||||||
weasyprintPath,
|
weasyprintPath,
|
||||||
@ -256,7 +259,7 @@ public class EmlToPdf {
|
|||||||
html.append("<html><head><meta charset=\"UTF-8\">\n");
|
html.append("<html><head><meta charset=\"UTF-8\">\n");
|
||||||
html.append("<title>").append(escapeHtml(subject)).append("</title>\n");
|
html.append("<title>").append(escapeHtml(subject)).append("</title>\n");
|
||||||
html.append("<style>\n");
|
html.append("<style>\n");
|
||||||
appendEnhancedStyles(html);
|
appendEnhancedStyles(html, request);
|
||||||
html.append("</style>\n");
|
html.append("</style>\n");
|
||||||
html.append("</head><body>\n");
|
html.append("</head><body>\n");
|
||||||
|
|
||||||
@ -295,7 +298,7 @@ public class EmlToPdf {
|
|||||||
html.append("<h3>Attachments</h3>\n");
|
html.append("<h3>Attachments</h3>\n");
|
||||||
html.append(attachmentInfo);
|
html.append(attachmentInfo);
|
||||||
|
|
||||||
// Add a status message about attachment inclusion
|
// Add status message about attachment inclusion
|
||||||
if (request != null && request.isIncludeAttachments()) {
|
if (request != null && request.isIncludeAttachments()) {
|
||||||
html.append("<div class=\"attachment-inclusion-note\">\n");
|
html.append("<div class=\"attachment-inclusion-note\">\n");
|
||||||
html.append(
|
html.append(
|
||||||
@ -313,7 +316,7 @@ public class EmlToPdf {
|
|||||||
|
|
||||||
// Show advanced features status if requested
|
// Show advanced features status if requested
|
||||||
assert request != null;
|
assert request != null;
|
||||||
if (request.getFileInput().isEmpty()) {
|
if (request != null && request.getFileInput().isEmpty()) {
|
||||||
html.append("<div class=\"advanced-features-notice\">\n");
|
html.append("<div class=\"advanced-features-notice\">\n");
|
||||||
html.append(
|
html.append(
|
||||||
"<p><em>Note: Some advanced features require Jakarta Mail dependencies.</em></p>\n");
|
"<p><em>Note: Some advanced features require Jakarta Mail dependencies.</em></p>\n");
|
||||||
@ -337,10 +340,8 @@ public class EmlToPdf {
|
|||||||
sessionClass.getMethod("getDefaultInstance", Properties.class);
|
sessionClass.getMethod("getDefaultInstance", Properties.class);
|
||||||
Object session = getDefaultInstance.invoke(null, new Properties());
|
Object session = getDefaultInstance.invoke(null, new Properties());
|
||||||
|
|
||||||
// Cast the session object to the proper type for the constructor
|
|
||||||
Class<?>[] constructorArgs = new Class<?>[] {sessionClass, InputStream.class};
|
|
||||||
Constructor<?> mimeMessageConstructor =
|
Constructor<?> mimeMessageConstructor =
|
||||||
mimeMessageClass.getConstructor(constructorArgs);
|
mimeMessageClass.getConstructor(sessionClass, InputStream.class);
|
||||||
Object message =
|
Object message =
|
||||||
mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes));
|
mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes));
|
||||||
|
|
||||||
@ -487,7 +488,7 @@ public class EmlToPdf {
|
|||||||
attachmentInfo
|
attachmentInfo
|
||||||
.append("<div class=\"attachment-item\">")
|
.append("<div class=\"attachment-item\">")
|
||||||
.append("<span class=\"attachment-icon\">")
|
.append("<span class=\"attachment-icon\">")
|
||||||
.append(MimeConstants.ATTACHMENT_MARKER)
|
.append(MimeConstants.ATTACHMENT_ICON_PLACEHOLDER)
|
||||||
.append("</span> ")
|
.append("</span> ")
|
||||||
.append("<span class=\"attachment-name\">")
|
.append("<span class=\"attachment-name\">")
|
||||||
.append(escapeHtml(filename))
|
.append(escapeHtml(filename))
|
||||||
@ -650,10 +651,6 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String processEmailHtmlBody(String htmlBody) {
|
private static String processEmailHtmlBody(String htmlBody) {
|
||||||
return processEmailHtmlBody(htmlBody, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String processEmailHtmlBody(String htmlBody, EmailContent emailContent) {
|
|
||||||
if (htmlBody == null) return "";
|
if (htmlBody == null) return "";
|
||||||
|
|
||||||
String processed = htmlBody;
|
String processed = htmlBody;
|
||||||
@ -662,83 +659,10 @@ public class EmlToPdf {
|
|||||||
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*fixed[^;]*;?", "");
|
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*fixed[^;]*;?", "");
|
||||||
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*absolute[^;]*;?", "");
|
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*absolute[^;]*;?", "");
|
||||||
|
|
||||||
// Process inline images (cid: references) if we have email content with attachments
|
|
||||||
if (emailContent != null && !emailContent.getAttachments().isEmpty()) {
|
|
||||||
processed = processInlineImages(processed, emailContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String processInlineImages(String htmlContent, EmailContent emailContent) {
|
private static void appendEnhancedStyles(StringBuilder html, EmlToPdfRequest request) {
|
||||||
if (htmlContent == null || emailContent == null) return htmlContent;
|
|
||||||
|
|
||||||
// Create a map of Content-ID to attachment data
|
|
||||||
Map<String, EmailAttachment> contentIdMap = new HashMap<>();
|
|
||||||
for (EmailAttachment attachment : emailContent.getAttachments()) {
|
|
||||||
if (attachment.isEmbedded()
|
|
||||||
&& attachment.getContentId() != null
|
|
||||||
&& attachment.getData() != null) {
|
|
||||||
contentIdMap.put(attachment.getContentId(), attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentIdMap.isEmpty()) return htmlContent;
|
|
||||||
|
|
||||||
// Pattern to match cid: references in img src attributes
|
|
||||||
Pattern cidPattern =
|
|
||||||
Pattern.compile(
|
|
||||||
"(?i)<img[^>]*\\ssrc\\s*=\\s*['\"]cid:([^'\"]+)['\"][^>]*>",
|
|
||||||
Pattern.CASE_INSENSITIVE);
|
|
||||||
Matcher matcher = cidPattern.matcher(htmlContent);
|
|
||||||
|
|
||||||
StringBuffer result = new StringBuffer();
|
|
||||||
while (matcher.find()) {
|
|
||||||
String contentId = matcher.group(1);
|
|
||||||
EmailAttachment attachment = contentIdMap.get(contentId);
|
|
||||||
|
|
||||||
if (attachment != null && attachment.getData() != null) {
|
|
||||||
// Convert to data URI
|
|
||||||
String mimeType = attachment.getContentType();
|
|
||||||
if (mimeType == null || mimeType.isEmpty()) {
|
|
||||||
// Try to determine MIME type from filename
|
|
||||||
String filename = attachment.getFilename();
|
|
||||||
if (filename != null) {
|
|
||||||
if (filename.toLowerCase().endsWith(".png")) {
|
|
||||||
mimeType = "image/png";
|
|
||||||
} else if (filename.toLowerCase().endsWith(".jpg")
|
|
||||||
|| filename.toLowerCase().endsWith(".jpeg")) {
|
|
||||||
mimeType = "image/jpeg";
|
|
||||||
} else if (filename.toLowerCase().endsWith(".gif")) {
|
|
||||||
mimeType = "image/gif";
|
|
||||||
} else if (filename.toLowerCase().endsWith(".bmp")) {
|
|
||||||
mimeType = "image/bmp";
|
|
||||||
} else {
|
|
||||||
mimeType = "image/png"; // fallback
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mimeType = "image/png"; // fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String base64Data = Base64.getEncoder().encodeToString(attachment.getData());
|
|
||||||
String dataUri = "data:" + mimeType + ";base64," + base64Data;
|
|
||||||
|
|
||||||
// Replace the cid: reference with the data URI
|
|
||||||
String replacement =
|
|
||||||
matcher.group(0).replaceFirst("cid:" + Pattern.quote(contentId), dataUri);
|
|
||||||
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
|
||||||
} else {
|
|
||||||
// Keep original if attachment not found
|
|
||||||
matcher.appendReplacement(result, Matcher.quoteReplacement(matcher.group(0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matcher.appendTail(result);
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void appendEnhancedStyles(StringBuilder html) {
|
|
||||||
int fontSize = StyleConstants.DEFAULT_FONT_SIZE;
|
int fontSize = StyleConstants.DEFAULT_FONT_SIZE;
|
||||||
String textColor = StyleConstants.DEFAULT_TEXT_COLOR;
|
String textColor = StyleConstants.DEFAULT_TEXT_COLOR;
|
||||||
String backgroundColor = StyleConstants.DEFAULT_BACKGROUND_COLOR;
|
String backgroundColor = StyleConstants.DEFAULT_BACKGROUND_COLOR;
|
||||||
@ -920,7 +844,7 @@ public class EmlToPdf {
|
|||||||
processMultipartAdvanced(messageContent, content, request);
|
processMultipartAdvanced(messageContent, content, request);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error processing content: {}", e.getMessage());
|
log.warn("Error processing multipart content: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -937,12 +861,6 @@ public class EmlToPdf {
|
|||||||
private static void processMultipartAdvanced(
|
private static void processMultipartAdvanced(
|
||||||
Object multipart, EmailContent content, EmlToPdfRequest request) {
|
Object multipart, EmailContent content, EmlToPdfRequest request) {
|
||||||
try {
|
try {
|
||||||
// Enhanced multipart type checking
|
|
||||||
if (!isValidJakartaMailMultipart(multipart)) {
|
|
||||||
log.warn("Invalid Jakarta Mail multipart type: {}", multipart.getClass().getName());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?> multipartClass = multipart.getClass();
|
Class<?> multipartClass = multipart.getClass();
|
||||||
java.lang.reflect.Method getCount = multipartClass.getMethod("getCount");
|
java.lang.reflect.Method getCount = multipartClass.getMethod("getCount");
|
||||||
int count = (Integer) getCount.invoke(multipart);
|
int count = (Integer) getCount.invoke(multipart);
|
||||||
@ -963,11 +881,6 @@ public class EmlToPdf {
|
|||||||
private static void processPartAdvanced(
|
private static void processPartAdvanced(
|
||||||
Object part, EmailContent content, EmlToPdfRequest request) {
|
Object part, EmailContent content, EmlToPdfRequest request) {
|
||||||
try {
|
try {
|
||||||
if (!isValidJakartaMailPart(part)) {
|
|
||||||
log.warn("Invalid Jakarta Mail part type: {}", part.getClass().getName());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?> partClass = part.getClass();
|
Class<?> partClass = part.getClass();
|
||||||
java.lang.reflect.Method isMimeType = partClass.getMethod("isMimeType", String.class);
|
java.lang.reflect.Method isMimeType = partClass.getMethod("isMimeType", String.class);
|
||||||
java.lang.reflect.Method getContent = partClass.getMethod("getContent");
|
java.lang.reflect.Method getContent = partClass.getMethod("getContent");
|
||||||
@ -1001,18 +914,10 @@ public class EmlToPdf {
|
|||||||
String[] contentIdHeaders = (String[]) getHeader.invoke(part, "Content-ID");
|
String[] contentIdHeaders = (String[]) getHeader.invoke(part, "Content-ID");
|
||||||
if (contentIdHeaders != null && contentIdHeaders.length > 0) {
|
if (contentIdHeaders != null && contentIdHeaders.length > 0) {
|
||||||
attachment.setEmbedded(true);
|
attachment.setEmbedded(true);
|
||||||
// Store the Content-ID, removing angle brackets if present
|
|
||||||
String contentId = contentIdHeaders[0];
|
|
||||||
if (contentId.startsWith("<") && contentId.endsWith(">")) {
|
|
||||||
contentId = contentId.substring(1, contentId.length() - 1);
|
|
||||||
}
|
|
||||||
attachment.setContentId(contentId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract attachment data if attachments should be included OR if it's an
|
// Extract attachment data only if attachments should be included
|
||||||
// embedded image (needed for inline display)
|
if (request != null && request.isIncludeAttachments()) {
|
||||||
if ((request != null && request.isIncludeAttachments())
|
|
||||||
|| attachment.isEmbedded()) {
|
|
||||||
try {
|
try {
|
||||||
Object attachmentContent = getContent.invoke(part);
|
Object attachmentContent = getContent.invoke(part);
|
||||||
byte[] attachmentData = null;
|
byte[] attachmentData = null;
|
||||||
@ -1033,23 +938,15 @@ public class EmlToPdf {
|
|||||||
|
|
||||||
if (attachmentData != null) {
|
if (attachmentData != null) {
|
||||||
// Check size limit (use default 10MB if request is null)
|
// Check size limit (use default 10MB if request is null)
|
||||||
long maxSizeMB =
|
long maxSizeMB = request.getMaxAttachmentSizeMB();
|
||||||
request != null ? request.getMaxAttachmentSizeMB() : 10L;
|
|
||||||
long maxSizeBytes = maxSizeMB * 1024 * 1024;
|
long maxSizeBytes = maxSizeMB * 1024 * 1024;
|
||||||
|
|
||||||
if (attachmentData.length <= maxSizeBytes) {
|
if (attachmentData.length <= maxSizeBytes) {
|
||||||
attachment.setData(attachmentData);
|
attachment.setData(attachmentData);
|
||||||
attachment.setSizeBytes(attachmentData.length);
|
attachment.setSizeBytes(attachmentData.length);
|
||||||
} else {
|
} else {
|
||||||
// For embedded images, always include data regardless of size
|
// Still show attachment info even if too large
|
||||||
// to ensure inline display works
|
attachment.setSizeBytes(attachmentData.length);
|
||||||
if (attachment.isEmbedded()) {
|
|
||||||
attachment.setData(attachmentData);
|
|
||||||
attachment.setSizeBytes(attachmentData.length);
|
|
||||||
} else {
|
|
||||||
// Still show attachment info even if too large
|
|
||||||
attachment.setSizeBytes(attachmentData.length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1085,7 +982,7 @@ public class EmlToPdf {
|
|||||||
html.append("<html><head><meta charset=\"UTF-8\">\n");
|
html.append("<html><head><meta charset=\"UTF-8\">\n");
|
||||||
html.append("<title>").append(escapeHtml(content.getSubject())).append("</title>\n");
|
html.append("<title>").append(escapeHtml(content.getSubject())).append("</title>\n");
|
||||||
html.append("<style>\n");
|
html.append("<style>\n");
|
||||||
appendEnhancedStyles(html);
|
appendEnhancedStyles(html, request);
|
||||||
html.append("</style>\n");
|
html.append("</style>\n");
|
||||||
html.append("</head><body>\n");
|
html.append("</head><body>\n");
|
||||||
|
|
||||||
@ -1109,7 +1006,7 @@ public class EmlToPdf {
|
|||||||
|
|
||||||
html.append("<div class=\"email-body\">\n");
|
html.append("<div class=\"email-body\">\n");
|
||||||
if (content.getHtmlBody() != null && !content.getHtmlBody().trim().isEmpty()) {
|
if (content.getHtmlBody() != null && !content.getHtmlBody().trim().isEmpty()) {
|
||||||
html.append(processEmailHtmlBody(content.getHtmlBody(), content));
|
html.append(processEmailHtmlBody(content.getHtmlBody()));
|
||||||
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
|
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
|
||||||
html.append("<div class=\"text-body\">");
|
html.append("<div class=\"text-body\">");
|
||||||
html.append(convertTextToHtml(content.getTextBody()));
|
html.append(convertTextToHtml(content.getTextBody()));
|
||||||
@ -1142,7 +1039,7 @@ public class EmlToPdf {
|
|||||||
.append(uniqueId)
|
.append(uniqueId)
|
||||||
.append("\">")
|
.append("\">")
|
||||||
.append("<span class=\"attachment-icon\">")
|
.append("<span class=\"attachment-icon\">")
|
||||||
.append(MimeConstants.ATTACHMENT_MARKER)
|
.append(MimeConstants.PAPERCLIP_EMOJI)
|
||||||
.append("</span> ")
|
.append("</span> ")
|
||||||
.append("<span class=\"attachment-name\">")
|
.append("<span class=\"attachment-name\">")
|
||||||
.append(escapeHtml(safeMimeDecode(attachment.getFilename())))
|
.append(escapeHtml(safeMimeDecode(attachment.getFilename())))
|
||||||
@ -1308,24 +1205,24 @@ public class EmlToPdf {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Find the screen position of all attachment markers
|
// 1. Find the screen position of all emoji anchors
|
||||||
AttachmentMarkerPositionFinder finder = new AttachmentMarkerPositionFinder();
|
EmojiPositionFinder finder = new EmojiPositionFinder();
|
||||||
finder.setSortByPosition(true); // Process pages in order
|
finder.setSortByPosition(true); // Process pages in order
|
||||||
finder.getText(document);
|
finder.getText(document);
|
||||||
List<MarkerPosition> markerPositions = finder.getPositions();
|
List<EmojiPosition> emojiPositions = finder.getPositions();
|
||||||
|
|
||||||
// 2. Warn if the number of markers and attachments don't match
|
// 2. Warn if the number of anchors and attachments don't match
|
||||||
if (markerPositions.size() != attachments.size()) {
|
if (emojiPositions.size() != attachments.size()) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Found {} attachment markers, but there are {} attachments. Annotation count may be incorrect.",
|
"Found {} emoji anchors, but there are {} attachments. Annotation count may be incorrect.",
|
||||||
markerPositions.size(),
|
emojiPositions.size(),
|
||||||
attachments.size());
|
attachments.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Create an invisible annotation over each found marker
|
// 3. Create an invisible annotation over each found emoji
|
||||||
int annotationsToAdd = Math.min(markerPositions.size(), attachments.size());
|
int annotationsToAdd = Math.min(emojiPositions.size(), attachments.size());
|
||||||
for (int i = 0; i < annotationsToAdd; i++) {
|
for (int i = 0; i < annotationsToAdd; i++) {
|
||||||
MarkerPosition position = markerPositions.get(i);
|
EmojiPosition position = emojiPositions.get(i);
|
||||||
EmailAttachment attachment = attachments.get(i);
|
EmailAttachment attachment = attachments.get(i);
|
||||||
|
|
||||||
if (attachment.getEmbeddedFilename() != null) {
|
if (attachment.getEmbeddedFilename() != null) {
|
||||||
@ -1459,6 +1356,8 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MIME header decoding functionality for RFC 2047 encoded headers - moved to constants
|
||||||
|
|
||||||
private static String decodeMimeHeader(String encodedText) {
|
private static String decodeMimeHeader(String encodedText) {
|
||||||
if (encodedText == null || encodedText.trim().isEmpty()) {
|
if (encodedText == null || encodedText.trim().isEmpty()) {
|
||||||
return encodedText;
|
return encodedText;
|
||||||
@ -1547,73 +1446,13 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isJakartaMailAvailable()) {
|
return decodeMimeHeader(headerValue.trim());
|
||||||
// Use Jakarta Mail's MimeUtility for proper MIME decoding
|
|
||||||
Class<?> mimeUtilityClass = Class.forName("jakarta.mail.internet.MimeUtility");
|
|
||||||
Method decodeText = mimeUtilityClass.getMethod("decodeText", String.class);
|
|
||||||
return (String) decodeText.invoke(null, headerValue.trim());
|
|
||||||
} else {
|
|
||||||
// Fallback to basic MIME decoding
|
|
||||||
return decodeMimeHeader(headerValue.trim());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to decode MIME header, using original: {}", headerValue, e);
|
log.warn("Failed to decode MIME header, using original: {}", headerValue, e);
|
||||||
return headerValue;
|
return headerValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isValidJakartaMailPart(Object part) {
|
|
||||||
if (part == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if the object implements jakarta.mail.Part interface
|
|
||||||
Class<?> partInterface = Class.forName("jakarta.mail.Part");
|
|
||||||
if (!partInterface.isInstance(part)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional check for MimePart
|
|
||||||
try {
|
|
||||||
Class<?> mimePartInterface = Class.forName("jakarta.mail.internet.MimePart");
|
|
||||||
return mimePartInterface.isInstance(part);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
// MimePart not available, but Part is sufficient
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.debug("Jakarta Mail Part interface not available for validation");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValidJakartaMailMultipart(Object multipart) {
|
|
||||||
if (multipart == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if the object implements jakarta.mail.Multipart interface
|
|
||||||
Class<?> multipartInterface = Class.forName("jakarta.mail.Multipart");
|
|
||||||
if (!multipartInterface.isInstance(multipart)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional check for MimeMultipart
|
|
||||||
try {
|
|
||||||
Class<?> mimeMultipartClass = Class.forName("jakarta.mail.internet.MimeMultipart");
|
|
||||||
if (mimeMultipartClass.isInstance(multipart)) {
|
|
||||||
log.debug("Found MimeMultipart instance for enhanced processing");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.debug("MimeMultipart not available, using base Multipart interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.debug("Jakarta Mail Multipart interface not available for validation");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class EmailContent {
|
public static class EmailContent {
|
||||||
private String subject;
|
private String subject;
|
||||||
@ -1658,13 +1497,15 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class MarkerPosition {
|
public static class EmojiPosition {
|
||||||
private int pageIndex;
|
private int pageIndex;
|
||||||
private float x;
|
private float x;
|
||||||
private float y;
|
private float y;
|
||||||
private String character;
|
private String character;
|
||||||
|
|
||||||
public MarkerPosition(int pageIndex, float x, float y, String character) {
|
public EmojiPosition() {}
|
||||||
|
|
||||||
|
public EmojiPosition(int pageIndex, float x, float y, String character) {
|
||||||
this.pageIndex = pageIndex;
|
this.pageIndex = pageIndex;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
@ -1672,15 +1513,14 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AttachmentMarkerPositionFinder
|
public static class EmojiPositionFinder extends org.apache.pdfbox.text.PDFTextStripper {
|
||||||
extends org.apache.pdfbox.text.PDFTextStripper {
|
@Getter private final List<EmojiPosition> positions = new ArrayList<>();
|
||||||
@Getter private final List<MarkerPosition> positions = new ArrayList<>();
|
|
||||||
private int currentPageIndex;
|
private int currentPageIndex;
|
||||||
protected boolean sortByPosition;
|
private boolean sortByPosition;
|
||||||
private boolean isInAttachmentSection;
|
private boolean isInAttachmentSection;
|
||||||
private boolean attachmentSectionFound;
|
private boolean attachmentSectionFound;
|
||||||
|
|
||||||
public AttachmentMarkerPositionFinder() {
|
public EmojiPositionFinder() throws IOException {
|
||||||
super();
|
super();
|
||||||
this.currentPageIndex = 0;
|
this.currentPageIndex = 0;
|
||||||
this.sortByPosition = false;
|
this.sortByPosition = false;
|
||||||
@ -1723,18 +1563,24 @@ public class EmlToPdf {
|
|||||||
isInAttachmentSection = false;
|
isInAttachmentSection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only look for markers if we are in the attachment section
|
// Only look for emojis if we are in the attachment section
|
||||||
if (isInAttachmentSection) {
|
if (isInAttachmentSection) {
|
||||||
String attachmentMarker = MimeConstants.ATTACHMENT_MARKER;
|
// Look for paperclip emoji characters (U+1F4CE)
|
||||||
for (int i = 0; (i = string.indexOf(attachmentMarker, i)) != -1; i++) {
|
String paperclipEmoji = "\uD83D\uDCCE"; // 📎 Unicode representation
|
||||||
if (i < textPositions.size()) {
|
|
||||||
|
for (int i = 0; i < string.length(); i++) {
|
||||||
|
// Check if we have a complete paperclip emoji at this position
|
||||||
|
if (i < string.length() - 1
|
||||||
|
&& string.substring(i, i + 2).equals(paperclipEmoji)
|
||||||
|
&& i < textPositions.size()) {
|
||||||
|
|
||||||
org.apache.pdfbox.text.TextPosition textPosition = textPositions.get(i);
|
org.apache.pdfbox.text.TextPosition textPosition = textPositions.get(i);
|
||||||
MarkerPosition position =
|
EmojiPosition position =
|
||||||
new MarkerPosition(
|
new EmojiPosition(
|
||||||
currentPageIndex,
|
currentPageIndex,
|
||||||
textPosition.getXDirAdj(),
|
textPosition.getXDirAdj(),
|
||||||
textPosition.getYDirAdj(),
|
textPosition.getYDirAdj(),
|
||||||
attachmentMarker);
|
paperclipEmoji);
|
||||||
positions.add(position);
|
positions.add(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1746,5 +1592,16 @@ public class EmlToPdf {
|
|||||||
public void setSortByPosition(boolean sortByPosition) {
|
public void setSortByPosition(boolean sortByPosition) {
|
||||||
this.sortByPosition = sortByPosition;
|
this.sortByPosition = sortByPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSortByPosition() {
|
||||||
|
return sortByPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
positions.clear();
|
||||||
|
currentPageIndex = 0;
|
||||||
|
isInAttachmentSection = false;
|
||||||
|
attachmentSectionFound = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -141,7 +142,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Convert", "markdown-to-pdf");
|
addEndpointToGroup("Convert", "markdown-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||||
addEndpointToGroup("Convert", "pdf-to-markdown");
|
addEndpointToGroup("Convert", "pdf-to-markdown");
|
||||||
addEndpointToGroup("Convert", "eml-to-pdf");
|
|
||||||
|
|
||||||
// Adding endpoints to "Security" group
|
// Adding endpoints to "Security" group
|
||||||
addEndpointToGroup("Security", "add-password");
|
addEndpointToGroup("Security", "add-password");
|
||||||
@ -265,7 +265,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
||||||
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
||||||
addEndpointToGroup("Weasyprint", "markdown-to-pdf");
|
addEndpointToGroup("Weasyprint", "markdown-to-pdf");
|
||||||
addEndpointToGroup("Weasyprint", "eml-to-pdf");
|
|
||||||
|
|
||||||
// Pdftohtml dependent endpoints
|
// Pdftohtml dependent endpoints
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div th:fragment="multi-toolAdvert" class="mx-auto">
|
<div th:fragment="multi-toolAdvert" class="mx-auto">
|
||||||
<div id="multi-toolAdvert" class="multi-toolAdvert">
|
<div id="multi-toolAdvert" class="multi-toolAdvert">
|
||||||
<div>
|
<div>
|
||||||
<span th:utext="#{multiTool-advert.message(|multi-tool|)}"></span>
|
<span th:utext="#{multiTool-advert.message(|/multi-tool|)}"></span>
|
||||||
<button id="closeMultiToolAdvert" style="position: absolute;
|
<button id="closeMultiToolAdvert" style="position: absolute;
|
||||||
inset-inline-end: 12px;
|
inset-inline-end: 12px;
|
||||||
inset-block-start: 10px;
|
inset-block-start: 10px;
|
||||||
@ -62,4 +62,4 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
Loading…
x
Reference in New Issue
Block a user