Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UniqueValue should use UUID::randomUUID for boundary and messageid #460 #510

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mail/src/main/java/jakarta/mail/internet/MimeMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import jakarta.mail.*;
import jakarta.activation.*;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.text.ParseException;
Expand Down Expand Up @@ -2212,6 +2211,7 @@ public void saveChanges() throws MessagingException {
* to override only the algorithm for choosing a Message-ID.
*
* @exception MessagingException for failures
* @see InternetAddress#getLocalAddress
* @since JavaMail 1.4
*/
protected void updateMessageID() throws MessagingException {
Expand Down
65 changes: 45 additions & 20 deletions mail/src/main/java/jakarta/mail/internet/UniqueValue.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2021 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
Expand All @@ -16,9 +16,10 @@

package jakarta.mail.internet;

import java.net.*;
import com.sun.mail.util.PropUtil;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.mail.Session;
import java.util.UUID;

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

/**
* Get a unique value for use in a multipart boundary string.
Expand All @@ -47,12 +48,16 @@ class UniqueValue {
*/
public static String getUniqueBoundaryValue() {
StringBuilder s = new StringBuilder();
long hash = s.hashCode();

// Unique string is ----=_Part_<part>_<hashcode>.<currentTime>
s.append("----=_Part_").append(id.getAndIncrement()).append("_").
append(hash).append('.').
append(System.currentTimeMillis());
s.append("----=_Part_");
if (PropUtil.getBooleanSystemProperty(
"mail.mime.multipart.boundary.format", true)) {
s.append(UUID.randomUUID());
} else {
// Unique string is ----=_Part_<part>_<hashcode>.<currentTime>
s.append(id.getAndIncrement()).append("_").
append(System.identityHashCode(s)).append('.').
append(System.currentTimeMillis());
}
return s.toString();
}

Expand All @@ -71,25 +76,45 @@ public static String getUniqueBoundaryValue() {
* @see jakarta.mail.internet.InternetAddress
*/
public static String getUniqueMessageIDValue(Session ssn) {
String suffix = null;
StringBuilder s = new StringBuilder();
if (messageIdFormat(ssn)) {
s.append(UUID.randomUUID());
} else {
// Unique string is <hashcode>.<id>.<currentTime><suffix>
s.append(System.identityHashCode(s)).append('.').
append(id.getAndIncrement()).append('.').
append(System.currentTimeMillis());
}

String suffix;
InternetAddress addr = InternetAddress.getLocalAddress(ssn);
if (addr != null)
suffix = addr.getAddress();
else {
suffix = "jakartamailuser@localhost"; // worst-case default
}
int at = suffix.lastIndexOf('@');
if (at >= 0)
suffix = suffix.substring(at);

StringBuilder s = new StringBuilder();

// Unique string is <hashcode>.<id>.<currentTime><suffix>
s.append(s.hashCode()).append('.').
append(id.getAndIncrement()).append('.').
append(System.currentTimeMillis()).
append(suffix);
int at = suffix.lastIndexOf('@');
if (at >= 0) {
s.append(suffix, at, suffix.length());
} else {
s.append(suffix);
}
return s.toString();
}

private static boolean messageIdFormat(Session ssn) {
String k = "mail.mime.messageid.format";
boolean def = true;
if (ssn != null) {
return PropUtil.getBooleanProperty(ssn.getProperties(), k, def);
} else { //Act like default default session without creating it.
return PropUtil.getBooleanSystemProperty(k, def);
}
}

// No one should instantiate this class.
private UniqueValue() throws IllegalAccessException {
throw new IllegalAccessException(UniqueValue.class.getName());
}
}
27 changes: 26 additions & 1 deletion mail/src/main/java/jakarta/mail/internet/package.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--

Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 1997, 2021 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
Expand Down Expand Up @@ -290,6 +290,19 @@
</TD>
</TR>

<TR>
<TD><A ID="mail.mime.messageid.format">mail.mime.messageid.format</A></TD>
<TD>boolean</TD>
<TD>
Sets the format of the
{@link jakarta.mail.internet.MimeMessage#updateMessageID message id} of the
MimeMessage class. If set to <code>"false"</code>, the format is compatible with
older versions of JakartaMail. If set to <code>"true"</code> the message id
will contain a randomly generator UUID. The default is <code>"true"</code> if
not specified or format is invalid.
</TD>
</TR>

<TR>
<TD><A ID="mail.replyallcc">mail.replyallcc</A></TD>
<TD>boolean</TD>
Expand Down Expand Up @@ -344,6 +357,18 @@
</TD>
</TR>

<TR>
<TD><A ID="mail.mime.multipart.boundary.format">mail.mime.multipart.boundary.format</A></TD>
<TD>boolean</TD>
<TD>
Sets the format of the MimeMultipart boundary line. If set to
<code>"false"</code>, the format is compatible with older versions of
JakartaMail. If set to <code>"true"</code> the boundary line will contain a
randomly generator UUID. The default is <code>"true"</code> if not specified or
format is invalid.
</TD>
</TR>

<TR>
<TD><A ID="mail.mime.setcontenttypefilename">mail.mime.setcontenttypefilename</A></TD>
<TD>boolean</TD>
Expand Down
215 changes: 215 additions & 0 deletions mail/src/test/java/jakarta/mail/internet/UniqueValueTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* Copyright (c) 2021 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
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;

import java.util.Properties;
import java.util.UUID;
import jakarta.mail.Session;
import org.junit.Test;
import static org.junit.Assert.*;

public class UniqueValueTest {

private static final String MESSAGEID_KEY = "mail.mime.messageid.format";

private static final String BOUNDARY_KEY
= "mail.mime.multipart.boundary.format";

public UniqueValueTest() {
}

@Test(expected = java.lang.IllegalAccessException.class)
public void testDeclaredConstructor() throws ReflectiveOperationException {
UniqueValue.class.getDeclaredConstructor().newInstance();
}

@Test
public void testDefGetUniqueBoundaryValue() {
String v = System.getProperty(BOUNDARY_KEY);
assertNull(v, v);
expectUuidBoundary();
}

private void expectUuidBoundary() {
String start = "----=_Part_";
String result = UniqueValue.getUniqueBoundaryValue();
assertTrue(result, result.startsWith(start));
try {
UUID.fromString(result.substring(start.length()));
} catch (Throwable t) {
throw new RuntimeException(result, t);
}
}

@Test
public void testInvalidUuidGetUniqueBoundaryValue() {
System.setProperty(BOUNDARY_KEY, UniqueValueTest.class.getName());
try {
expectUuidBoundary();
} finally {
System.getProperties().remove(BOUNDARY_KEY);
}
}

@Test
public void testUuidGetUniqueBoundaryValue() {
System.setProperty(BOUNDARY_KEY, "true");
try {
expectUuidBoundary();
} finally {
System.getProperties().remove(BOUNDARY_KEY);
}
}

@Test
public void testUvGetUniqueBoundaryValue() {
System.setProperty(BOUNDARY_KEY, "false");
try {
String start = "----=_Part_";
String result = UniqueValue.getUniqueBoundaryValue();
assertTrue(result, result.startsWith(start));
try {
int s = start.length();
int n = result.indexOf('_', s);
Long.parseLong(result.substring(s, n)); //id

s = n + 1;
n = result.indexOf('.', s);
Long.parseLong(result.substring(s, n)); //hashCode

Long.parseLong(result.substring(n + 1)); //millis
} catch (Throwable t) {
throw new RuntimeException(result, t);
}
} finally {
System.getProperties().remove(BOUNDARY_KEY);
}
}

@Test
public void testDefGetUniqueMessageIDValue() {
Properties p = new Properties();
String v = p.getProperty(MESSAGEID_KEY);
assertNull(v, v);
expectUuidMessageID(Session.getInstance(p));
}

/**
* If a session is given but the value is not defined ensure code will not
* fallback to using system properties.
*/
@Test
public void testUuidNoheritGetUniqueMessageIDValue() {
System.setProperty(MESSAGEID_KEY, "false");
try {
Properties p = new Properties();
String v = p.getProperty(MESSAGEID_KEY);
assertNull(v, v);
expectUuidMessageID(Session.getInstance(p));
} finally {
System.getProperties().remove(MESSAGEID_KEY);
}
}


@Test
public void testUvGetUniqueMessageIDValue() {
Properties p = new Properties();
p.put(MESSAGEID_KEY, "false");
String result = UniqueValue.getUniqueMessageIDValue(
Session.getInstance(p));
try {
int s = 0;
int n = result.indexOf('.', s);
Long.parseLong(result.substring(s, n)); //id

s = n + 1;
n = result.indexOf('.', s);
Long.parseLong(result.substring(s, n)); //hashCode

s = n + 1;
n = result.indexOf('@', s);
Long.parseLong(result.substring(s, n)); //millis
} catch (Throwable t) {
throw new RuntimeException(result, t);
}
}

@Test
public void testUuidGetUniqueMessageIDValue() {
Properties p = new Properties();
p.put(MESSAGEID_KEY, "true");
expectUuidMessageID(Session.getInstance(p));
}

@Test
public void testDefSystemGetUniqueMessageIDValue() {
String v = System.getProperty(MESSAGEID_KEY);
assertNull(v, v);
expectUuidMessageID((Session) null);
}

@Test
public void testInvalidGetUniqueMessageIDValue() {
Properties p = new Properties();
p.put(MESSAGEID_KEY, UniqueValueTest.class.getName());
expectUuidMessageID(Session.getInstance(p));
}

private void expectUuidMessageID(Session s) {
String result = UniqueValue.getUniqueMessageIDValue(s);
try {
UUID.fromString(result.substring(0, result.indexOf('@')));
} catch (Throwable t) {
throw new RuntimeException(result, t);
}
}

@Test
public void testUuidSystemGetUniqueMessageIDValue() {
System.setProperty(MESSAGEID_KEY, "true");
try {
expectUuidMessageID((Session) null);
} finally {
System.getProperties().remove(MESSAGEID_KEY);
}
}

@Test
public void testUvSystemGetUniqueMessageIDValue() {
System.setProperty(MESSAGEID_KEY, "false");
try {
String result = UniqueValue.getUniqueMessageIDValue((Session) null);
try {
int s = 0;
int n = result.indexOf('.', s);
Long.parseLong(result.substring(s, n)); //id

s = n + 1;
n = result.indexOf('.', s);
Long.parseLong(result.substring(s, n)); //hashCode

s = n + 1;
n = result.indexOf('@', s);
Long.parseLong(result.substring(s, n)); //millis
} catch (Throwable t) {
throw new RuntimeException(result, t);
}
} finally {
System.getProperties().remove(MESSAGEID_KEY);
}
}
}