From 4a34de67ac845d1624d2046719de6c2686004973 Mon Sep 17 00:00:00 2001 From: Damian Sniezek Date: Mon, 24 Nov 2025 10:49:59 +0100 Subject: [PATCH] fix: add correct UTF-8 encoding to notification payload Signed-off-by: Damian Sniezek --- .../publisher/AbstractWebhookPublisher.java | 23 ++++++++------- .../publisher/AbstractPublisherTest.java | 29 +++++++++++++++++++ .../publisher/JiraPublisherTest.java | 23 +++++++++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java b/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java index f897be242..6965eb2a2 100644 --- a/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java +++ b/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java @@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory; import jakarta.json.JsonObject; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.List; public abstract class AbstractWebhookPublisher implements Publisher { @@ -48,13 +49,13 @@ public abstract class AbstractWebhookPublisher implements Publisher { final Logger logger = LoggerFactory.getLogger(getClass()); if (config == null) { - logger.warn("No publisher configuration found; Skipping notification (%s)".formatted(ctx)); + logger.warn("No publisher configuration found; Skipping notification ({})", ctx); return; } final String destination = getDestinationUrl(config); if (destination == null) { - logger.warn("No destination configured; Skipping notification (%s)".formatted(ctx)); + logger.warn("No destination configured; Skipping notification ({})", ctx); return; } @@ -64,7 +65,7 @@ public abstract class AbstractWebhookPublisher implements Publisher { } catch (RuntimeException e) { logger.warn(""" An error occurred during the retrieval of credentials needed for notification \ - publication; Skipping notification (%s)""".formatted(ctx), e); + publication; Skipping notification ({})""", ctx, e); return; } @@ -91,19 +92,19 @@ public abstract class AbstractWebhookPublisher implements Publisher { } try { - request.setEntity(new StringEntity(content)); + request.setEntity(new StringEntity(content, StandardCharsets.UTF_8)); try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode < 200 || statusCode >= 300) { - logger.warn("Destination responded with with status code %d, likely indicating a processing failure (%s)" - .formatted(statusCode, ctx)); + logger.warn("Destination responded with with status code {}, likely indicating a processing failure ({})", + statusCode, ctx); if (logger.isDebugEnabled()) { - logger.debug("Response headers: %s".formatted((Object[]) response.getAllHeaders())); - logger.debug("Response body: %s".formatted(EntityUtils.toString(response.getEntity()))); + logger.debug("Response headers: {}", (Object[]) response.getAllHeaders()); + logger.debug("Response body: {}", EntityUtils.toString(response.getEntity())); } } else if (ctx.shouldLogSuccess()) { - logger.info("Destination acknowledged reception of notification with status code %d (%s)" - .formatted(statusCode, ctx)); + logger.info("Destination acknowledged reception of notification with status code {} ({})", + statusCode, ctx); } } } catch (IOException ex) { @@ -136,7 +137,7 @@ public abstract class AbstractWebhookPublisher implements Publisher { } protected void handleRequestException(final PublishContext ctx, final Logger logger, final Exception e) { - logger.error("Failed to send notification request (%s)".formatted(ctx), e); + logger.error("Failed to send notification request ({})", ctx, e); } } diff --git a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java index 345e8b0d4..a4edc8b95 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java @@ -267,6 +267,35 @@ abstract class AbstractPublisherTest extends PersistenceCap .withMessage("Unexpected tag name \"include\" ({% include '/some/path' %}:1)"); } + public final void baseTestInformWithNewVulnerabilityCustomUTF8TemplateNotification() throws Exception { + final var project = createProject(); + final var component = createComponent(project); + final var vuln = createVulnerability(); + + final var subject = new NewVulnerabilityIdentified(vuln, component, Set.of(project), + VulnerabilityAnalysisLevel.BOM_UPLOAD_ANALYSIS); + + + final var notification = new Notification() + .scope(NotificationScope.SYSTEM) + .group(NotificationGroup.NEW_VULNERABILITY) + .title(NotificationConstants.Title.NOTIFICATION_TEST) + .level(NotificationLevel.INFORMATIONAL) + .timestamp(LocalDateTime.ofEpochSecond(66666, 666, ZoneOffset.UTC)) + .subject(subject); + + final JsonObject defaultConfig = createConfig(); + final String defaultTemplate = defaultConfig.getString(Publisher.CONFIG_TEMPLATE_KEY); + final String template = defaultTemplate.replaceAll("Vulnerability", "Vulnérabilité"); + + final JsonObject config = Json.createObjectBuilder(createConfig()) + .add(Publisher.CONFIG_TEMPLATE_KEY, template) + .build(); + + assertThatNoException() + .isThrownBy(() -> publisherInstance.inform(PublishContext.from(notification), notification, config)); + } + public final void baseTestPublishWithScheduledNewVulnerabilitiesNotification() { final var project = createProject(); final var component = createComponent(project); diff --git a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java index 183267402..653531907 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java @@ -178,6 +178,29 @@ class JiraPublisherTest extends AbstractWebhookPublisherTest { """))); } + @Test + public void testInformWithNewVulnerabilityCustomUTF8TemplateNotification() throws Exception { + super.baseTestInformWithNewVulnerabilityCustomUTF8TemplateNotification(); + + verify(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) + .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "fields" : { + "project" : { + "key" : "PROJECT" + }, + "issuetype" : { + "name" : "Task" + }, + "summary" : "[Dependency-Track] [NEW_VULNERABILITY] [] New vulnerability identified: ", + "description" : "A new vulnerability has been identified on your project(s).\\n\\\\\\\\\\n\\\\\\\\\\n*Vulnérabilité description*\\n{code:none|bgColor=white|borderStyle=none}{code}\\n\\n*VulnID*\\n\\n\\n*Severity*\\n\\n\\n*Component*\\n[|https://example.com/components/]\\n\\n*Affected project(s)*\\n" + } + } + """))); + } + @Test public void testInformWithNewVulnerabilityNotification() { super.baseTestInformWithNewVulnerabilityNotification();