From 4b0101a9dcdf7dbe1841dfc33992fa8eb27eaf2f Mon Sep 17 00:00:00 2001 From: Junseo Bae <69619752+bebeis@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:07:33 +0900 Subject: [PATCH] Defensively copy sentDate in SimpleMailMessage Use defensive Date copies for sentDate to avoid shared mutable state. Apply consistent handling in setSentDate, getSentDate, the copy constructor, and copyTo. Add regression tests for mutation safety and copy isolation. Closes gh-36626 Signed-off-by: Junseo Bae --- .../mail/SimpleMailMessage.java | 15 ++- .../mail/SimpleMailMessageTests.java | 108 ++++++++++++++++++ 2 files changed, 118 insertions(+), 5 deletions(-) diff --git a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java index e1f8422caa0..9f9fe568cd3 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java +++ b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java @@ -79,7 +79,7 @@ public class SimpleMailMessage implements MailMessage, Serializable { this.to = copyOrNull(original.getTo()); this.cc = copyOrNull(original.getCc()); this.bcc = copyOrNull(original.getBcc()); - this.sentDate = original.getSentDate(); + this.sentDate = copyOrNull(original.sentDate); this.subject = original.getSubject(); this.text = original.getText(); } @@ -147,11 +147,11 @@ public class SimpleMailMessage implements MailMessage, Serializable { @Override public void setSentDate(@Nullable Date sentDate) { - this.sentDate = sentDate; + this.sentDate = copyOrNull(sentDate); } public @Nullable Date getSentDate() { - return this.sentDate; + return copyOrNull(this.sentDate); } @Override @@ -194,8 +194,9 @@ public class SimpleMailMessage implements MailMessage, Serializable { if (getBcc() != null) { target.setBcc(copy(getBcc())); } - if (getSentDate() != null) { - target.setSentDate(getSentDate()); + if (this.sentDate != null) { + Date sentDate = this.sentDate; + target.setSentDate((Date) sentDate.clone()); } if (getSubject() != null) { target.setSubject(getSubject()); @@ -247,6 +248,10 @@ public class SimpleMailMessage implements MailMessage, Serializable { return copy(state); } + private static @Nullable Date copyOrNull(@Nullable Date date) { + return (date != null ? (Date) date.clone() : null); + } + private static String[] copy(String[] state) { return state.clone(); } diff --git a/spring-context-support/src/test/java/org/springframework/mail/SimpleMailMessageTests.java b/spring-context-support/src/test/java/org/springframework/mail/SimpleMailMessageTests.java index 976f8abfbb4..04b4b073b4e 100644 --- a/spring-context-support/src/test/java/org/springframework/mail/SimpleMailMessageTests.java +++ b/spring-context-support/src/test/java/org/springframework/mail/SimpleMailMessageTests.java @@ -98,6 +98,60 @@ class SimpleMailMessageTests { assertThat(copy.getBcc()[0]).isEqualTo("us@mail.org"); } + @Test + void setSentDateStoresACopy() { + SimpleMailMessage message = new SimpleMailMessage(); + Date sentDate = new Date(1234L); + + message.setSentDate(sentDate); + sentDate.setTime(0L); + + assertThat(message.getSentDate()).isEqualTo(new Date(1234L)); + } + + @Test + void getSentDateReturnsACopy() { + SimpleMailMessage message = new SimpleMailMessage(); + Date sentDate = new Date(1234L); + message.setSentDate(sentDate); + + Date exportedDate = message.getSentDate(); + exportedDate.setTime(0L); + + assertThat(message.getSentDate()).isEqualTo(new Date(1234L)); + } + + @Test + void copyConstructorCopiesSentDate() { + Date sentDate = new Date(1234L); + SimpleMailMessage original = new SimpleMailMessage(); + original.setSentDate(sentDate); + + SimpleMailMessage copy = new SimpleMailMessage(original); + sentDate.setTime(0L); + + Date copiedDate = copy.getSentDate(); + assertThat(copiedDate).isNotNull(); + copiedDate.setTime(1L); + + assertThat(original.getSentDate()).isEqualTo(new Date(1234L)); + assertThat(copy.getSentDate()).isEqualTo(new Date(1234L)); + } + + @Test + void copyToCopiesSentDate() { + SimpleMailMessage source = new SimpleMailMessage(); + source.setSentDate(new Date(1234L)); + + TestMailMessage target = new TestMailMessage(); + source.copyTo(target); + + assertThat(target.getSentDate()).isNotNull(); + target.getSentDate().setTime(0L); + + assertThat(source.getSentDate()).isEqualTo(new Date(1234L)); + } + /** * Tests that two equal SimpleMailMessages have equal hash codes. */ @@ -166,4 +220,58 @@ class SimpleMailMessageTests { assertThatIllegalArgumentException().isThrownBy(() -> new SimpleMailMessage().copyTo(null)); } + private static class TestMailMessage implements MailMessage { + + private Date sentDate; + + Date getSentDate() { + return this.sentDate; + } + + @Override + public void setFrom(String from) { + } + + @Override + public void setReplyTo(String replyTo) { + } + + @Override + public void setTo(String to) { + } + + @Override + public void setTo(String... to) { + } + + @Override + public void setCc(String cc) { + } + + @Override + public void setCc(String... cc) { + } + + @Override + public void setBcc(String bcc) { + } + + @Override + public void setBcc(String... bcc) { + } + + @Override + public void setSentDate(Date sentDate) { + this.sentDate = sentDate; + } + + @Override + public void setSubject(String subject) { + } + + @Override + public void setText(String text) { + } + } + }