From db46462c6a30ed1ed488d65f755dcb026ca88716 Mon Sep 17 00:00:00 2001 From: Junseo Bae Date: Wed, 8 Apr 2026 21:05:39 +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. 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 e1f8422caa02..9f9fe568cd3f 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 SimpleMailMessage(SimpleMailMessage original) { 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 void setBcc(String @Nullable ... bcc) { @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 void copyTo(MailMessage target) { 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 String toString() { 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 976f8abfbb4f..04b4b073b4eb 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 @@ void deepCopyOfStringArrayTypedFieldsOnCopyCtor() { 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 @@ void copyToChokesOnNullTargetMessage() { 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) { + } + } + }