Skip to content

Commit c744f3a

Browse files
committed
UniqueValue should use UUID::randomUUID for boundary and messageid #460
Signed-off-by: jmehrens <jason_mehrens@hotmail.com>
1 parent 226d2d6 commit c744f3a

File tree

4 files changed

+287
-22
lines changed

4 files changed

+287
-22
lines changed

mail/src/main/java/jakarta/mail/internet/MimeMessage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import jakarta.mail.*;
2020
import jakarta.activation.*;
21-
import java.lang.*;
2221
import java.io.*;
2322
import java.util.*;
2423
import java.text.ParseException;
@@ -2212,6 +2211,7 @@ public void saveChanges() throws MessagingException {
22122211
* to override only the algorithm for choosing a Message-ID.
22132212
*
22142213
* @exception MessagingException for failures
2214+
* @see InternetAddress#getLocalAddress
22152215
* @since JavaMail 1.4
22162216
*/
22172217
protected void updateMessageID() throws MessagingException {

mail/src/main/java/jakarta/mail/internet/UniqueValue.java

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,9 +16,10 @@
1616

1717
package jakarta.mail.internet;
1818

19-
import java.net.*;
19+
import com.sun.mail.util.PropUtil;
2020
import java.util.concurrent.atomic.AtomicInteger;
2121
import jakarta.mail.Session;
22+
import java.util.UUID;
2223

2324
/**
2425
* This is a utility class that generates unique values. The generated
@@ -36,7 +37,7 @@ class UniqueValue {
3637
/**
3738
* A global unique number, to ensure uniqueness of generated strings.
3839
*/
39-
private static AtomicInteger id = new AtomicInteger();
40+
private static final AtomicInteger id = new AtomicInteger();
4041

4142
/**
4243
* Get a unique value for use in a multipart boundary string.
@@ -47,12 +48,16 @@ class UniqueValue {
4748
*/
4849
public static String getUniqueBoundaryValue() {
4950
StringBuilder s = new StringBuilder();
50-
long hash = s.hashCode();
51-
52-
// Unique string is ----=_Part_<part>_<hashcode>.<currentTime>
53-
s.append("----=_Part_").append(id.getAndIncrement()).append("_").
54-
append(hash).append('.').
55-
append(System.currentTimeMillis());
51+
s.append("----=_Part_");
52+
if (PropUtil.getBooleanSystemProperty(
53+
"mail.mime.multipart.boundary.format", true)) {
54+
s.append(UUID.randomUUID());
55+
} else {
56+
// Unique string is ----=_Part_<part>_<hashcode>.<currentTime>
57+
s.append(id.getAndIncrement()).append("_").
58+
append(System.identityHashCode(s)).append('.').
59+
append(System.currentTimeMillis());
60+
}
5661
return s.toString();
5762
}
5863

@@ -71,25 +76,45 @@ public static String getUniqueBoundaryValue() {
7176
* @see jakarta.mail.internet.InternetAddress
7277
*/
7378
public static String getUniqueMessageIDValue(Session ssn) {
74-
String suffix = null;
79+
StringBuilder s = new StringBuilder();
80+
if (messageIdFormat(ssn)) {
81+
s.append(UUID.randomUUID());
82+
} else {
83+
// Unique string is <hashcode>.<id>.<currentTime><suffix>
84+
s.append(System.identityHashCode(s)).append('.').
85+
append(id.getAndIncrement()).append('.').
86+
append(System.currentTimeMillis());
87+
}
7588

89+
String suffix;
7690
InternetAddress addr = InternetAddress.getLocalAddress(ssn);
7791
if (addr != null)
7892
suffix = addr.getAddress();
7993
else {
8094
suffix = "jakartamailuser@localhost"; // worst-case default
8195
}
82-
int at = suffix.lastIndexOf('@');
83-
if (at >= 0)
84-
suffix = suffix.substring(at);
85-
86-
StringBuilder s = new StringBuilder();
8796

88-
// Unique string is <hashcode>.<id>.<currentTime><suffix>
89-
s.append(s.hashCode()).append('.').
90-
append(id.getAndIncrement()).append('.').
91-
append(System.currentTimeMillis()).
92-
append(suffix);
97+
int at = suffix.lastIndexOf('@');
98+
if (at >= 0) {
99+
s.append(suffix, at, suffix.length());
100+
} else {
101+
s.append(suffix);
102+
}
93103
return s.toString();
94104
}
105+
106+
private static boolean messageIdFormat(Session ssn) {
107+
String k = "mail.mime.messageid.format";
108+
boolean def = true;
109+
if (ssn != null) {
110+
return PropUtil.getBooleanProperty(ssn.getProperties(), k, def);
111+
} else { //Act like default default session without creating it.
112+
return PropUtil.getBooleanSystemProperty(k, def);
113+
}
114+
}
115+
116+
// No one should instantiate this class.
117+
private UniqueValue() throws IllegalAccessException {
118+
throw new IllegalAccessException(UniqueValue.class.getName());
119+
}
95120
}

mail/src/main/java/jakarta/mail/internet/package.html

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<!--
66
7-
Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
7+
Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
88
99
This program and the accompanying materials are made available under the
1010
terms of the Eclipse Public License v. 2.0, which is available at
@@ -290,6 +290,19 @@
290290
</TD>
291291
</TR>
292292

293+
<TR>
294+
<TD><A ID="mail.mime.messageid.format">mail.mime.messageid.format</A></TD>
295+
<TD>boolean</TD>
296+
<TD>
297+
Sets the format of the
298+
{@link jakarta.mail.internet.MimeMessage#updateMessageID message id} of the
299+
MimeMessage class. If set to <code>"false"</code>, the format is compatible with
300+
older versions of JakartaMail. If set to <code>"true"</code> the message id
301+
will contain a randomly generator UUID. The default is <code>"true"</code> if
302+
not specified or format is invalid.
303+
</TD>
304+
</TR>
305+
293306
<TR>
294307
<TD><A ID="mail.replyallcc">mail.replyallcc</A></TD>
295308
<TD>boolean</TD>
@@ -344,6 +357,18 @@
344357
</TD>
345358
</TR>
346359

360+
<TR>
361+
<TD><A ID="mail.mime.multipart.boundary.format">mail.mime.multipart.boundary.format</A></TD>
362+
<TD>boolean</TD>
363+
<TD>
364+
Sets the format of the MimeMultipart boundary line. If set to
365+
<code>"false"</code>, the format is compatible with older versions of
366+
JakartaMail. If set to <code>"true"</code> the boundary line will contain a
367+
randomly generator UUID. The default is <code>"true"</code> if not specified or
368+
format is invalid.
369+
</TD>
370+
</TR>
371+
347372
<TR>
348373
<TD><A ID="mail.mime.setcontenttypefilename">mail.mime.setcontenttypefilename</A></TD>
349374
<TD>boolean</TD>
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
package jakarta.mail.internet;
17+
18+
import java.util.Properties;
19+
import java.util.UUID;
20+
import jakarta.mail.Session;
21+
import org.junit.Test;
22+
import static org.junit.Assert.*;
23+
24+
public class UniqueValueTest {
25+
26+
private static final String MESSAGEID_KEY = "mail.mime.messageid.format";
27+
28+
private static final String BOUNDARY_KEY
29+
= "mail.mime.multipart.boundary.format";
30+
31+
public UniqueValueTest() {
32+
}
33+
34+
@Test(expected = java.lang.IllegalAccessException.class)
35+
public void testDeclaredConstructor() throws ReflectiveOperationException {
36+
UniqueValue.class.getDeclaredConstructor().newInstance();
37+
}
38+
39+
@Test
40+
public void testDefGetUniqueBoundaryValue() {
41+
String v = System.getProperty(BOUNDARY_KEY);
42+
assertNull(v, v);
43+
expectUuidBoundary();
44+
}
45+
46+
private void expectUuidBoundary() {
47+
String start = "----=_Part_";
48+
String result = UniqueValue.getUniqueBoundaryValue();
49+
assertTrue(result, result.startsWith(start));
50+
try {
51+
UUID.fromString(result.substring(start.length()));
52+
} catch (Throwable t) {
53+
throw new RuntimeException(result, t);
54+
}
55+
}
56+
57+
@Test
58+
public void testInvalidUuidGetUniqueBoundaryValue() {
59+
System.setProperty(BOUNDARY_KEY, UniqueValueTest.class.getName());
60+
try {
61+
expectUuidBoundary();
62+
} finally {
63+
System.getProperties().remove(BOUNDARY_KEY);
64+
}
65+
}
66+
67+
@Test
68+
public void testUuidGetUniqueBoundaryValue() {
69+
System.setProperty(BOUNDARY_KEY, "true");
70+
try {
71+
expectUuidBoundary();
72+
} finally {
73+
System.getProperties().remove(BOUNDARY_KEY);
74+
}
75+
}
76+
77+
@Test
78+
public void testUvGetUniqueBoundaryValue() {
79+
System.setProperty(BOUNDARY_KEY, "false");
80+
try {
81+
String start = "----=_Part_";
82+
String result = UniqueValue.getUniqueBoundaryValue();
83+
assertTrue(result, result.startsWith(start));
84+
try {
85+
int s = start.length();
86+
int n = result.indexOf('_', s);
87+
Long.parseLong(result.substring(s, n)); //id
88+
89+
s = n + 1;
90+
n = result.indexOf('.', s);
91+
Long.parseLong(result.substring(s, n)); //hashCode
92+
93+
Long.parseLong(result.substring(n + 1)); //millis
94+
} catch (Throwable t) {
95+
throw new RuntimeException(result, t);
96+
}
97+
} finally {
98+
System.getProperties().remove(BOUNDARY_KEY);
99+
}
100+
}
101+
102+
@Test
103+
public void testDefGetUniqueMessageIDValue() {
104+
Properties p = new Properties();
105+
String v = p.getProperty(MESSAGEID_KEY);
106+
assertNull(v, v);
107+
expectUuidMessageID(Session.getInstance(p));
108+
}
109+
110+
/**
111+
* If a session is given but the value is not defined ensure code will not
112+
* fallback to using system properties.
113+
*/
114+
@Test
115+
public void testUuidNoheritGetUniqueMessageIDValue() {
116+
System.setProperty(MESSAGEID_KEY, "false");
117+
try {
118+
Properties p = new Properties();
119+
String v = p.getProperty(MESSAGEID_KEY);
120+
assertNull(v, v);
121+
expectUuidMessageID(Session.getInstance(p));
122+
} finally {
123+
System.getProperties().remove(MESSAGEID_KEY);
124+
}
125+
}
126+
127+
128+
@Test
129+
public void testUvGetUniqueMessageIDValue() {
130+
Properties p = new Properties();
131+
p.put(MESSAGEID_KEY, "false");
132+
String result = UniqueValue.getUniqueMessageIDValue(
133+
Session.getInstance(p));
134+
try {
135+
int s = 0;
136+
int n = result.indexOf('.', s);
137+
Long.parseLong(result.substring(s, n)); //id
138+
139+
s = n + 1;
140+
n = result.indexOf('.', s);
141+
Long.parseLong(result.substring(s, n)); //hashCode
142+
143+
s = n + 1;
144+
n = result.indexOf('@', s);
145+
Long.parseLong(result.substring(s, n)); //millis
146+
} catch (Throwable t) {
147+
throw new RuntimeException(result, t);
148+
}
149+
}
150+
151+
@Test
152+
public void testUuidGetUniqueMessageIDValue() {
153+
Properties p = new Properties();
154+
p.put(MESSAGEID_KEY, "true");
155+
expectUuidMessageID(Session.getInstance(p));
156+
}
157+
158+
@Test
159+
public void testDefSystemGetUniqueMessageIDValue() {
160+
String v = System.getProperty(MESSAGEID_KEY);
161+
assertNull(v, v);
162+
expectUuidMessageID((Session) null);
163+
}
164+
165+
@Test
166+
public void testInvalidGetUniqueMessageIDValue() {
167+
Properties p = new Properties();
168+
p.put(MESSAGEID_KEY, UniqueValueTest.class.getName());
169+
expectUuidMessageID(Session.getInstance(p));
170+
}
171+
172+
private void expectUuidMessageID(Session s) {
173+
String result = UniqueValue.getUniqueMessageIDValue(s);
174+
try {
175+
UUID.fromString(result.substring(0, result.indexOf('@')));
176+
} catch (Throwable t) {
177+
throw new RuntimeException(result, t);
178+
}
179+
}
180+
181+
@Test
182+
public void testUuidSystemGetUniqueMessageIDValue() {
183+
System.setProperty(MESSAGEID_KEY, "true");
184+
try {
185+
expectUuidMessageID((Session) null);
186+
} finally {
187+
System.getProperties().remove(MESSAGEID_KEY);
188+
}
189+
}
190+
191+
@Test
192+
public void testUvSystemGetUniqueMessageIDValue() {
193+
System.setProperty(MESSAGEID_KEY, "false");
194+
try {
195+
String result = UniqueValue.getUniqueMessageIDValue((Session) null);
196+
try {
197+
int s = 0;
198+
int n = result.indexOf('.', s);
199+
Long.parseLong(result.substring(s, n)); //id
200+
201+
s = n + 1;
202+
n = result.indexOf('.', s);
203+
Long.parseLong(result.substring(s, n)); //hashCode
204+
205+
s = n + 1;
206+
n = result.indexOf('@', s);
207+
Long.parseLong(result.substring(s, n)); //millis
208+
} catch (Throwable t) {
209+
throw new RuntimeException(result, t);
210+
}
211+
} finally {
212+
System.getProperties().remove(MESSAGEID_KEY);
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)