From c1c2f2bc06564476ce80850d453efbc8e7bf5436 Mon Sep 17 00:00:00 2001 From: Jorge Bescos Gascon Date: Thu, 11 Dec 2025 11:52:37 +0100 Subject: [PATCH] Default MailDateFormat pattern might not be compatible with RFC 2822? #820 move from MailDateFormat (not thread safe) to DateTimeFormatter #590 Signed-off-by: Jorge Bescos Gascon --- .../jakarta/mail/internet/MailDateFormat.java | 6 ++-- .../jakarta/mail/internet/MimeMessage.java | 13 +++---- .../mail/internet/MailDateFormatTest.java | 16 ++++----- doc/release/CHANGES.txt | 2 ++ www/docs/COMPAT.txt | 34 ++++++++++++++++--- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/api/src/main/java/jakarta/mail/internet/MailDateFormat.java b/api/src/main/java/jakarta/mail/internet/MailDateFormat.java index 0f4ae64f..58e3a4b2 100644 --- a/api/src/main/java/jakarta/mail/internet/MailDateFormat.java +++ b/api/src/main/java/jakarta/mail/internet/MailDateFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -124,7 +124,7 @@ public class MailDateFormat extends SimpleDateFormat { private static final long serialVersionUID = -8148227605210628779L; - private static final String PATTERN = "EEE, d MMM yyyy HH:mm:ss Z (z)"; + private static final String PATTERN = "EEE, d MMM yyyy HH:mm:ss Z"; private static final Logger LOGGER = Logger.getLogger(MailDateFormat.class.getName()); @@ -149,7 +149,7 @@ public MailDateFormat() { */ private Object writeReplace() throws ObjectStreamException { MailDateFormat fmt = new MailDateFormat(); - fmt.superApplyPattern("EEE, d MMM yyyy HH:mm:ss 'XXXXX' (z)"); + fmt.superApplyPattern(PATTERN); fmt.setTimeZone(getTimeZone()); return fmt; } diff --git a/api/src/main/java/jakarta/mail/internet/MimeMessage.java b/api/src/main/java/jakarta/mail/internet/MimeMessage.java index dbfc339f..9452738e 100644 --- a/api/src/main/java/jakarta/mail/internet/MimeMessage.java +++ b/api/src/main/java/jakarta/mail/internet/MimeMessage.java @@ -169,9 +169,6 @@ public class MimeMessage extends Message implements MimePart { */ protected Object cachedContent; - // Used to parse dates - private static final MailDateFormat mailDateFormat = new MailDateFormat(); - // Should addresses in headers be parsed in "strict" mode? private boolean strict = true; // Is UTF-8 allowed in headers? @@ -897,10 +894,9 @@ public void setSubject(String subject, String charset) public Date getSentDate() throws MessagingException { String s = getHeader("Date", null); if (s != null) { + MailDateFormat mailDateFormat = new MailDateFormat(); try { - synchronized (mailDateFormat) { - return mailDateFormat.parse(s); - } + return mailDateFormat.parse(s); } catch (ParseException pex) { return null; } @@ -926,9 +922,8 @@ public void setSentDate(Date d) throws MessagingException { if (d == null) removeHeader("Date"); else { - synchronized (mailDateFormat) { - setHeader("Date", mailDateFormat.format(d)); - } + MailDateFormat mailDateFormat = new MailDateFormat(); + setHeader("Date", mailDateFormat.format(d)); } } diff --git a/api/src/test/java/jakarta/mail/internet/MailDateFormatTest.java b/api/src/test/java/jakarta/mail/internet/MailDateFormatTest.java index baaeb768..c40505da 100644 --- a/api/src/test/java/jakarta/mail/internet/MailDateFormatTest.java +++ b/api/src/test/java/jakarta/mail/internet/MailDateFormatTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -79,7 +79,7 @@ public void formatMustThrowNpeForNullArgs() { @Test public void mustFormatRfc2822WithOptionalCfws() { Date date = mustPass(getDefault(), "1 Jan 2015 00:00 +0000"); - assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)"); + assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000"); } @Test @@ -89,7 +89,7 @@ public void mustUseTimeZoneForFormatting() { Date date = mustPass(fmt, input); fmt.setTimeZone(TimeZone.getTimeZone("Etc/GMT+8")); assertThat(fmt.format(date), - is("Wed, 31 Dec 2014 15:00:00 -0800 (GMT-08:00)")); + is("Wed, 31 Dec 2014 15:00:00 -0800")); } /* @@ -239,7 +239,7 @@ public void mustFailOrSkipCfws() { // NOTE this test fails with getLenient(), // since lenient parsing must remain backward compatible Date date = getStrict().parse(input); - assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)"); + assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000"); } catch (ParseException ignored) { assertTrue("Not supporting CFWS is allowed", true); } @@ -309,7 +309,7 @@ public void lenientMustAcceptYearsBetween1000and1899() { @Test public void lenientMustAdd1900To3DigitYear() { Date date = mustPass(getLenient(), "1 Jul 900 00:00 +0000"); - assertThatDate(date, "Sat, 1 Jul 2800 00:00:00 +0000 (UTC)"); + assertThatDate(date, "Sat, 1 Jul 2800 00:00:00 +0000"); } @Test @@ -338,13 +338,13 @@ public void mustParseLeapSecondAsJsr310() { @Test public void mustParseNegativeZeroesZoneAsUtc() { Date date = mustPass(getDefault(), "1 Jan 2015 00:00 -0000"); - assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)"); + assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000"); } @Test public void mustCorrectlyParseInputWithTrailingDigits() { Date date = mustPass(getStrict(), "1 Jan 2015 00:00 -001530"); - assertThatDate(date, "Thu, 1 Jan 2015 00:15:00 +0000 (UTC)"); + assertThatDate(date, "Thu, 1 Jan 2015 00:15:00 +0000"); } @Test @@ -424,7 +424,7 @@ public void mustProhibitSetDateFormatSymbols() { SimpleDateFormat fmt = getStrict(); fmt.setDateFormatSymbols(new DateFormatSymbols(Locale.FRENCH)); Date date = mustPass(fmt, "1 Jan 2015 00:00:00 +0000"); - assertThatDate(date, "jeu., 1 janv. 2015 00:00:00 +0000 (UTC)"); + assertThatDate(date, "jeu., 1 janv. 2015 00:00:00 +0000"); } /* diff --git a/doc/release/CHANGES.txt b/doc/release/CHANGES.txt index dfba5b58..41179eac 100644 --- a/doc/release/CHANGES.txt +++ b/doc/release/CHANGES.txt @@ -28,6 +28,8 @@ E 758 Improve MimeMessage UTF8 handling E 804 Restore streamProvider fields for backwards compatibility E 810 InternetHeaders.InternetHeader toString() E 818 Change getSize() return type from int to long +E 590 move from MailDateFormat (not thread safe) to DateTimeFormatter +E 820 Default MailDateFormat pattern might not be compatible with RFC 2822? CHANGES IN THE 2.1.5 RELEASE ---------------------------- diff --git a/www/docs/COMPAT.txt b/www/docs/COMPAT.txt index 596701c2..6d50f1c9 100644 --- a/www/docs/COMPAT.txt +++ b/www/docs/COMPAT.txt @@ -3,20 +3,46 @@ -- Jakarta Mail 2.2.0 -- - Removal of finalize() +- Removal of finalize() The finalize() methods have been removed throughout the Mail API. This has several implications: 1. No-arg close() behavior - Calling the no-argument close() method on objects that are already closed is no longer considered "hostile" (i.e. it no longer throws or behaves unexpectedly if the resource is already closed). + Calling the no-argument close() method on objects that are already + closed is no longer considered "hostile" (i.e. it no longer throws + or behaves unexpectedly if the resource is already closed). 2. Event thread shutdown - The no-arg close() is now required to signal internal event threads to stop. In other words, previously finalize() might have been relied on to clean up or shut down threads; that is no longer the case. Users must call close() explicitly to ensure proper shutdown. + The no-arg close() is now required to signal internal event threads + to stop. In other words, previously finalize() might have been + relied on to clean up or shut down threads; that is no longer the + case. Users must call close() explicitly to ensure proper shutdown. 3. Resource / memory management - Because finalizers are removed, users must more explicitly manage resources: ensure streams, folders, other resources are closed properly. Do not rely on garbage collection / finalization to clean up. + Because finalizers are removed, users must more explicitly manage + resources: ensure streams, folders, other resources are closed + properly. Do not rely on garbage collection / finalization to + clean up. + +- MailDateFormat pattern change + + The default date format pattern used by MailDateFormat has been + updated. The legacy pattern: + + "EEE, d MMM yyyy HH:mm:ss Z (z)" + + has been changed to: + + "EEE, d MMM yyyy HH:mm:ss Z" + + The optional parenthesized time zone name was removed to improve + consistency with RFC 2822–style header formatting and to align with + the behavior of modern Java time formatters. Applications that rely + on the presence of the "(z)" suffix in generated Date headers should + update their expectations accordingly. + -- Jakarta Mail 2.1.4 --