This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 773d1bd80 fix(alert): parse quoted email recipient lists correctly 
(#6663)
773d1bd80 is described below

commit 773d1bd8092fadcd220aaef5d2df66e271ba470e
Author: 健康宝 <[email protected]>
AuthorDate: Thu Oct 15 19:07:44 2026 +0800

    fix(alert): parse quoted email recipient lists correctly (#6663)
---
 .../ops/alert/NotificationOutboxService.java       | 41 ++++++++++++--
 .../ops/alert/NotificationOutboxServiceTest.java   | 64 ++++++++++++++++++++++
 2 files changed, 99 insertions(+), 6 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
index 9a44f4560..26e8b017f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxService.java
@@ -498,20 +498,49 @@ public class NotificationOutboxService {
 
     private static String[] parseRecipients(String raw) throws 
AddressException {
         List<String> recipients = new ArrayList<>();
-        for (String value : raw.split("[,;]")) {
-            String recipient = value.trim();
-            if (!recipient.isEmpty()) {
-                InternetAddress address = new InternetAddress(recipient, true);
-                address.validate();
-                recipients.add(address.getAddress());
+        // Split on the documented separators (comma, plus the legacy
+        // semicolon) only outside a quoted display name: a list like
+        // `"Doe, John" <[email protected]>, [email protected]` is valid RFC 822
+        // input, but a plain [,;] split broke it into unbalanced fragments
+        // that failed validation and drove the notification into retry
+        // instead of sending.
+        StringBuilder current = new StringBuilder();
+        boolean quoted = false;
+        boolean escaped = false;
+        for (int i = 0; i < raw.length(); i++) {
+            char c = raw.charAt(i);
+            if (escaped) {
+                current.append(c);
+                escaped = false;
+            } else if (quoted && c == '\\') {
+                current.append(c);
+                escaped = true;
+            } else if (c == '"') {
+                current.append(c);
+                quoted = !quoted;
+            } else if (!quoted && (c == ',' || c == ';')) {
+                addRecipient(current, recipients);
+                current.setLength(0);
+            } else {
+                current.append(c);
             }
         }
+        addRecipient(current, recipients);
         if (recipients.isEmpty()) {
             throw new AddressException("No valid email recipients");
         }
         return recipients.toArray(String[]::new);
     }
 
+    private static void addRecipient(CharSequence token, List<String> 
recipients) throws AddressException {
+        String recipient = token.toString().trim();
+        if (!recipient.isEmpty()) {
+            InternetAddress address = new InternetAddress(recipient, true);
+            address.validate();
+            recipients.add(address.getAddress());
+        }
+    }
+
     private Map<String, Object> payload(SystemAlertVO alert, String channel, 
String content) {
         if ("dingtalk".equals(channel)) {
             return Map.of("msgtype", "text", "text", Map.of("content", 
content));
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
index e17a2da23..37fa0bcf8 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NotificationOutboxServiceTest.java
@@ -466,6 +466,70 @@ class NotificationOutboxServiceTest {
                 "alertId=9, channel=email", "SUCCESS", null);
     }
 
+    @Test
+    void dispatchesEmailWithQuotedDisplayNameRecipientsTest() {
+        RmqAlertNotificationOutboxMapper mapper = 
mock(RmqAlertNotificationOutboxMapper.class);
+        SettingsRepository settings = mock(SettingsRepository.class);
+        AlertRepository alerts = mock(AlertRepository.class);
+        OperationAuditService audit = mock(OperationAuditService.class);
+        JavaMailSender mailSender = mock(JavaMailSender.class);
+        RmqAlertNotificationOutbox row = new RmqAlertNotificationOutbox();
+        row.setId(18L);
+        row.setAlertId(9L);
+        row.setChannel("email");
+        row.setStatus("PENDING");
+        row.setAttemptCount(0);
+        when(mapper.findDispatchable(any(LocalDateTime.class), 
any(LocalDateTime.class), any(Integer.class)))
+                .thenReturn(List.of(row));
+        when(mapper.claimForDispatch(any(), any(LocalDateTime.class), 
any(LocalDateTime.class),
+                any(LocalDateTime.class), anyString())).thenReturn(1);
+        when(mapper.update(any(), any())).thenReturn(1);
+        
when(alerts.findAlertById(9L)).thenReturn(Optional.of(SystemAlertVO.builder().id(9L)
+                
.level(AlertLevel.warning).title("Lag").description("high").instanceId("local").build()));
+        
when(settings.loadGeneralSettings()).thenReturn(GeneralSettingsVO.builder()
+                .emailRecipients("\"Doe, John\" <[email protected]>, 
[email protected]").build());
+
+        new NotificationOutboxService(mapper, settings, 
mock(AlertSilenceService.class), alerts, audit,
+                new RestTemplate(), () -> mailSender).dispatch();
+
+        // A comma inside a quoted display name is not a separator: the
+        // delivery must reach both configured recipients, not fail parsing.
+        verify(mailSender).send(argThat((SimpleMailMessage message) -> 
message.getTo() != null
+                && Arrays.equals(message.getTo(), new String[] 
{"[email protected]", "[email protected]"})));
+    }
+
+    @Test
+    void dispatchesEmailToSemicolonSeparatedRecipientsTest() {
+        RmqAlertNotificationOutboxMapper mapper = 
mock(RmqAlertNotificationOutboxMapper.class);
+        SettingsRepository settings = mock(SettingsRepository.class);
+        AlertRepository alerts = mock(AlertRepository.class);
+        OperationAuditService audit = mock(OperationAuditService.class);
+        JavaMailSender mailSender = mock(JavaMailSender.class);
+        RmqAlertNotificationOutbox row = new RmqAlertNotificationOutbox();
+        row.setId(19L);
+        row.setAlertId(9L);
+        row.setChannel("email");
+        row.setStatus("PENDING");
+        row.setAttemptCount(0);
+        when(mapper.findDispatchable(any(LocalDateTime.class), 
any(LocalDateTime.class), any(Integer.class)))
+                .thenReturn(List.of(row));
+        when(mapper.claimForDispatch(any(), any(LocalDateTime.class), 
any(LocalDateTime.class),
+                any(LocalDateTime.class), anyString())).thenReturn(1);
+        when(mapper.update(any(), any())).thenReturn(1);
+        
when(alerts.findAlertById(9L)).thenReturn(Optional.of(SystemAlertVO.builder().id(9L)
+                
.level(AlertLevel.warning).title("Lag").description("high").instanceId("local").build()));
+        
when(settings.loadGeneralSettings()).thenReturn(GeneralSettingsVO.builder()
+                .emailRecipients("[email protected]; \"Night; Shift\" 
<[email protected]>").build());
+
+        new NotificationOutboxService(mapper, settings, 
mock(AlertSilenceService.class), alerts, audit,
+                new RestTemplate(), () -> mailSender).dispatch();
+
+        // The legacy semicolon separator keeps working, including inside a
+        // quoted display name.
+        verify(mailSender).send(argThat((SimpleMailMessage message) -> 
message.getTo() != null
+                && Arrays.equals(message.getTo(), new String[] 
{"[email protected]", "[email protected]"})));
+    }
+
     @Test
     void reclaimsAStaleSendingDeliveryTest() {
         RmqAlertNotificationOutboxMapper mapper = 
mock(RmqAlertNotificationOutboxMapper.class);

Reply via email to