commits@javamail.java.net

[javamail~mercurial:828] SMTPTransport.sasllogin should not be public

From: <shannon_at_java.net>
Date: Wed, 22 Jun 2016 20:25:48 +0000

Project: javamail
Repository: mercurial
Revision: 828
Author: shannon
Date: 2016-06-14 21:18:45 UTC
Link:

Log Message:
------------
Improve protocolConnect debug output and align with IMAP.
Logging should support LogRecord.getInstant - K7092
MailHandler workaround for JDK-8152515
MailHandler guard for unexpected ThreadLocal null values.
MailHandler allow username without a password.
MailHandler deal with empty urlname host for 3rd party transports.
MailHandlerTest add JavaMail class linkage test.
MailHandlerTest add username without a password test.

(From Jason)
When calling IMAPFolder.doProtocolCommand on a closed folder we also need
to hold the Folder lock to prevent deadlock due to one thead holding the
Folder lock and trying to acquire the Store protocol while another thread
has the Store protocol and tries to acquire the Folder lock. - bug 7378
InternetAddress.getLocalAddress should use InetAddress.getCanonicalHostName -
bug 7471
SMTPTransport.sasllogin should not be public


Revisions:
----------
824
825
826
827
828


Modified Paths:
---------------
mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java
mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java
mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
doc/release/COMPAT.txt
mail/src/main/java/javax/mail/internet/InternetAddress.java
mail/src/main/java/javax/mail/internet/package.html


Diffs:
------
diff -r 3ed6a59fffe8 -r dda23c548c22 mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
--- a/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Tue Apr 26 14:52:54 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Tue May 03 14:02:09 2016 -0700
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
  *
  * The contents of this file are subject to the terms of either the GNU
  * General Public License Version 2 only ("GPL") or the Common Development
@@ -138,6 +138,8 @@
     private SaslAuthenticator saslAuthenticator; // if SASL is being used
 
     private boolean noauthdebug = true; // hide auth info in debug output
+ private boolean debugusername; // include username in debug output?
+ private boolean debugpassword; // include password in debug output?
 
     /** Headers that should not be included when sending */
     private static final String[] ignoreList = { "Bcc", "Content-Length" };
@@ -171,6 +173,10 @@
         traceLogger = logger.getSubLogger("protocol", null);
         noauthdebug = !PropUtil.getBooleanSessionProperty(session,
                             "mail.debug.auth", false);
+ debugusername = PropUtil.getBooleanSessionProperty(session,
+ "mail.debug.auth.username", true);
+ debugpassword = PropUtil.getBooleanSessionProperty(session,
+ "mail.debug.auth.password", false);
         if (urlname != null)
             name = urlname.getProtocol();
         this.name = name;
@@ -651,7 +657,8 @@
      * @exception MessagingException for non-authentication failures
      */
     protected synchronized boolean protocolConnect(String host, int port,
- String user, String passwd) throws MessagingException {
+ String user, String password)
+ throws MessagingException {
         // setting mail.smtp.auth to true enables attempts to use AUTH
         boolean useAuth = PropUtil.getBooleanSessionProperty(session,
                                         "mail." + name + ".auth", false);
@@ -662,8 +669,14 @@
          * because the server doesn't support ESMTP or doesn't support
          * the AUTH extension).
          */
- if (useAuth && (user == null || passwd == null)) {
- logger.fine("need username and password for authentication");
+ if (useAuth && (user == null || password == null)) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("need username and password for authentication");
+ logger.fine("protocolConnect returning false" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ }
             return false;
         }
 
@@ -724,10 +737,15 @@
                 }
             }
 
- if ((useAuth || (user != null && passwd != null)) &&
+ if ((useAuth || (user != null && password != null)) &&
                   (supportsExtension("AUTH") ||
                    supportsExtension("AUTH=LOGIN"))) {
- connected = authenticate(user, passwd);
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("protocolConnect login" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ connected = authenticate(user, password);
                 return connected;
             }
 
@@ -2524,6 +2542,15 @@
         return sb != null ? sb.toString() : s;
     }
 
+ private String traceUser(String user) {
+ return debugusername ? user : "<user name suppressed>";
+ }
+
+ private String tracePassword(String password) {
+ return debugpassword ? password :
+ (password == null ? "<null>" : "<non-null>");
+ }
+
     /*
      * Probe points for GlassFish monitoring.
      */


diff -r dda23c548c22 -r 50f4ccfca842 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Tue May 03 14:02:09 2016 -0700
+++ b/doc/release/CHANGES.txt Mon May 09 13:39:39 2016 -0700
@@ -20,6 +20,7 @@
 The following bugs have been fixed in the 1.5.6 release.
 
 K 7091 Support LogRecord.setMillis being deprecated in JDK 9
+K 7092 Logging should support LogRecord.getInstant
 K 7097 Create common super class for logging tests
 K 7140 NPE by APOP detection when no greeting banner
 K 7150 Make IMAPProtocol.handleLoginResult protected

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
--- a/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java Mon May 09 13:39:39 2016 -0700
@@ -117,8 +117,11 @@
      * java.util.Formatter} format string specified in the
      * &lt;formatter-name&gt;.format property or the format that was given when
      * this formatter was created.</li>
- * <li>{_at_code date} - a {_at_linkplain Date} object representing
- * {_at_linkplain LogRecord#getMillis event time} of the log record.</li>
+ * <li>{_at_code date} - if the log record supports nanoseconds then a
+ * ZonedDateTime object representing the event time of the log record in
+ * the system time zone. Otherwise, a {_at_linkplain Date} object
+ * representing {_at_linkplain LogRecord#getMillis event time} of the log
+ * record.</li>
      * <li>{_at_code source} - a string representing the caller, if available;
      * otherwise, the logger's name.</li>
      * <li>{_at_code logger} - the logger's
@@ -230,7 +233,7 @@
         String thrown = formatThrown(record);
         String err = formatError(record);
         Object[] params = {
- new Date(record.getMillis()),
+ formatZonedDateTime(record),
             formatSource(record),
             formatLoggerName(record),
             formatLevel(record),
@@ -525,6 +528,22 @@
     }
 
     /**
+ * Gets the zoned date time from the given log record.
+ *
+ * @param record the current log record.
+ * @return a zoned date time or a legacy date object.
+ * @throws NullPointerException if the given record is null.
+ * @since JavaMail 1.5.6
+ */
+ private Comparable<?> formatZonedDateTime(final LogRecord record) {
+ Comparable<?> zdt = LogManagerProperties.getZonedDateTime(record);
+ if (zdt == null) {
+ zdt = new java.util.Date(record.getMillis());
+ }
+ return zdt;
+ }
+
+ /**
      * Determines if a stack frame should be ignored as the cause of an error.
      * This does not check for unknown line numbers because code can be compiled
      * without debugging info.

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
--- a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Mon May 09 13:39:39 2016 -0700
@@ -44,6 +44,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.*;
@@ -79,6 +80,63 @@
      */
     private static final long serialVersionUID = -2239983349056806252L;
     /**
+ * Holds the method used to get the LogRecord instant if running on JDK 9 or
+ * later.
+ */
+ private static final Method LR_GET_INSTANT;
+
+ /**
+ * Holds the method used to get the default time zone if running on JDK 9 or
+ * later.
+ */
+ private static final Method ZI_SYSTEM_DEFAULT;
+
+ /**
+ * Holds the method used to convert and instant to a zoned date time if
+ * running on JDK 9 later.
+ */
+ private static final Method ZDT_OF_INSTANT;
+
+ static {
+ Method lrgi = null;
+ Method zisd = null;
+ Method zdtoi = null;
+ try {
+ lrgi = LogRecord.class.getMethod("getInstant");
+ assert Comparable.class
+ .isAssignableFrom(lrgi.getReturnType()) : lrgi;
+ zisd = findClass("java.time.ZoneId")
+ .getMethod("systemDefault");
+ if (!Modifier.isStatic(zisd.getModifiers())) {
+ throw new NoSuchMethodException(zisd.toString());
+ }
+
+ zdtoi = findClass("java.time.ZonedDateTime")
+ .getMethod("ofInstant", findClass("java.time.Instant"),
+ findClass("java.time.ZoneId"));
+ if (!Modifier.isStatic(zdtoi.getModifiers())) {
+ throw new NoSuchMethodException(zdtoi.toString());
+ }
+
+ if (!Comparable.class.isAssignableFrom(zdtoi.getReturnType())) {
+ throw new NoSuchMethodException(zdtoi.toString());
+ }
+ } catch (final RuntimeException ignore) {
+ } catch (final Exception ignore) { //No need for specific catch.
+ } catch (final LinkageError ignore) {
+ } finally {
+ if (lrgi == null || zisd == null || zdtoi == null) {
+ lrgi = null; //If any are null then clear all.
+ zisd = null;
+ zdtoi = null;
+ }
+ }
+
+ LR_GET_INSTANT = lrgi;
+ ZI_SYSTEM_DEFAULT = zisd;
+ ZDT_OF_INSTANT = zdtoi;
+ }
+ /**
      * Caches the read only reflection class names string array. Declared
      * volatile for safe publishing only. The VO_VOLATILE_REFERENCE_TO_ARRAY
      * warning is a false positive.
@@ -257,6 +315,44 @@
     }
 
     /**
+ * Gets the ZonedDateTime from the given log record.
+ *
+ * @param record used to generate the zoned date time.
+ * @return null if LogRecord doesn't support nanoseconds otherwise a new
+ * zoned date time is returned.
+ * @throws NullPointerException if record is null.
+ * @since JavaMail 1.5.6
+ */
+ @SuppressWarnings("UseSpecificCatch")
+ static Comparable<?> getZonedDateTime(LogRecord record) {
+ if (record == null) {
+ throw new NullPointerException();
+ }
+ final Method m = ZDT_OF_INSTANT;
+ if (m != null) {
+ try {
+ return (Comparable<?>) m.invoke((Object) null,
+ LR_GET_INSTANT.invoke(record),
+ ZI_SYSTEM_DEFAULT.invoke((Object) null));
+ } catch (final RuntimeException ignore) {
+ assert LR_GET_INSTANT != null
+ && ZI_SYSTEM_DEFAULT != null : ignore;
+ } catch (final InvocationTargetException ite) {
+ final Throwable cause = ite.getCause();
+ if (cause instanceof Error) {
+ throw (Error) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else { //Should never happen.
+ throw new UndeclaredThrowableException(ite);
+ }
+ } catch (final Exception ignore) {
+ }
+ }
+ return null;
+ }
+
+ /**
      * Gets the local host name from the given service.
      *
      * @param s the service to examine.

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
--- a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Mon May 09 13:39:39 2016 -0700
@@ -380,7 +380,10 @@
     private static final PrivilegedAction<Object> MAILHANDLER_LOADER
             = new GetAndSetContext(MailHandler.class);
     /**
- * A thread local mutex used to prevent logging loops.
+ * A thread local mutex used to prevent logging loops. This code has to be
+ * prepared to deal with unexpected null values since the
+ * WebappClassLoader.clearReferencesThreadLocals() and
+ * InnocuousThread.eraseThreadLocals() can remove thread local values.
      * The MUTEX has 4 states:
      * 1. A null value meaning default state of not publishing.
      * 2. MUTEX_PUBLISH on first entry of a push or publish.
@@ -390,13 +393,20 @@
     private static final ThreadLocal<Integer> MUTEX = new ThreadLocal<Integer>();
     /**
      * The marker object used to report a publishing state.
+ * This must be less than the body filter index.
      */
     private static final Integer MUTEX_PUBLISH = -2;
     /**
      * The used for the error reporting state.
+ * This must be less than the PUBLISH state.
      */
     private static final Integer MUTEX_REPORT = -4;
     /**
+ * The used for linkage error reporting.
+ * This must be less than the REPORT state.
+ */
+ private static final Integer MUTEX_LINKAGE = -8;
+ /**
      * Used to turn off security checks.
      */
     private volatile boolean sealed;
@@ -622,6 +632,8 @@
                     record.getSourceMethodName(); //Infer caller.
                     publish0(record);
                 }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.WRITE_FAILURE);
             } finally {
                 releaseMutex();
             }
@@ -675,7 +687,7 @@
      */
     private void reportUnPublishedError(LogRecord record) {
         final Integer idx = MUTEX.get();
- if (!MUTEX_REPORT.equals(idx)) {
+ if (idx == null || idx > MUTEX_REPORT) {
             MUTEX.set(MUTEX_REPORT);
             try {
                 final String msg;
@@ -692,7 +704,11 @@
                         + Thread.currentThread());
                 reportError(msg, e, ErrorManager.WRITE_FAILURE);
             } finally {
- MUTEX.set(idx);
+ if (idx != null) {
+ MUTEX.set(idx);
+ } else {
+ MUTEX.remove();
+ }
             }
         }
     }
@@ -732,8 +748,8 @@
      */
     private int getMatchedPart() {
         //assert Thread.holdsLock(this);
- int idx = MUTEX.get();
- if (idx >= readOnlyAttachmentFilters().length) {
+ Integer idx = MUTEX.get();
+ if (idx == null || idx >= readOnlyAttachmentFilters().length) {
            idx = MUTEX_PUBLISH;
         }
         return idx;
@@ -777,6 +793,7 @@
      *
      * @since JavaMail 1.5.3
      */
+ //_at_javax.annotation.PostConstruct
     public void postConstruct() {
     }
 
@@ -790,6 +807,7 @@
      *
      * @since JavaMail 1.5.3
      */
+ //_at_javax.annotation.PreDestroy
     public void preDestroy() {
         /**
          * Close can require permissions so just trigger a push.
@@ -833,33 +851,37 @@
      */
     @Override
     public void close() {
- checkAccess(); //Ensure setLevel works before clearing the buffer.
- Message msg = null;
- synchronized (this) {
- try {
- msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
- } finally { //Change level after formatting.
- this.logLevel = Level.OFF;
- /**
- * The sign bit of the capacity is set to ensure that
- * records that have passed isLoggable, but have yet to be
- * added to the internal buffer, are immediately pushed as
- * an email.
- */
- if (this.capacity > 0) {
- this.capacity = -this.capacity;
- }
-
- //Ensure not inside a push.
- if (size == 0 && data.length != 1) {
- this.data = new LogRecord[1];
- this.matched = new int[this.data.length];
+ try {
+ checkAccess(); //Ensure setLevel works before clearing the buffer.
+ Message msg = null;
+ synchronized (this) {
+ try {
+ msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
+ } finally { //Change level after formatting.
+ this.logLevel = Level.OFF;
+ /**
+ * The sign bit of the capacity is set to ensure that
+ * records that have passed isLoggable, but have yet to be
+ * added to the internal buffer, are immediately pushed as
+ * an email.
+ */
+ if (this.capacity > 0) {
+ this.capacity = -this.capacity;
+ }
+
+ //Ensure not inside a push.
+ if (size == 0 && data.length != 1) {
+ this.data = new LogRecord[1];
+ this.matched = new int[this.data.length];
+ }
                 }
             }
- }
-
- if (msg != null) {
- send(msg, false, ErrorManager.CLOSE_FAILURE);
+
+ if (msg != null) {
+ send(msg, false, ErrorManager.CLOSE_FAILURE);
+ }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.CLOSE_FAILURE);
         }
     }
 
@@ -1667,65 +1689,36 @@
     }
 
     /**
- * Re-throws the given error or runtime exception only if it wasn't thrown
- * during a call to ErrorManager.error or generated by Handler.reportError.
- * If there is no stack trace information available the given error or
- * runtime exception will be thrown if the given code is a flush error.
+ * Reports the given linkage error or runtime exception.
      *
      * The current LogManager code will stop closing all remaining handlers if
- * an error is thrown during resetLogger. It is best to just swallow
- * these linkage errors thrown from the default ErrorManager to ensure other
- * handlers are closed. This is a workaround for GLASSFISH-21258. Custom
- * error managers are responsible for catching or throwing errors from
- * System.err implementations that are hostile.
+ * an error is thrown during resetLogger. This is a workaround for
+ * GLASSFISH-21258 and JDK-8152515.
      * @param le the linkage error or a RuntimeException.
      * @param code the ErrorManager code.
      * @throws NullPointerException if error is null.
- * @throws LinkageError if the given error should not be suppressed.
- * @throws RuntimeException if the given exception should not be suppressed.
      * @since JavaMail 1.5.3
      */
- private void reportLinkageError(Throwable le, int code) {
+ private void reportLinkageError(final Throwable le, final int code) {
         if (le == null) {
            throw new NullPointerException(String.valueOf(code));
         }
 
- boolean reThrow = true;
- final StackTraceElement[] stack = le.getStackTrace();
- if (stack.length > 1) {
- for (int i = 1; i < stack.length; ++i) {
- final StackTraceElement s = stack[i];
- if ("error".equals(s.getMethodName())
- && "java.util.logging.ErrorManager"
- .equals(s.getClassName())) {
- reThrow = false;
- break;
- } else if ("reportError".equals(s.getMethodName())
- && "java.util.logging.Handler"
- .equals(s.getClassName())) {
- final StackTraceElement p = stack[i - 1];
- if ("println".equals(p.getMethodName()) ||
- "printStackTrace".equals(p.getMethodName())) {
- reThrow = false;
- break;
- }
+ final Integer idx = MUTEX.get();
+ if (idx == null || idx > MUTEX_LINKAGE) {
+ MUTEX.set(MUTEX_LINKAGE);
+ try {
+ Thread.currentThread().getUncaughtExceptionHandler()
+ .uncaughtException(Thread.currentThread(), le);
+ } catch (final RuntimeException ignore) {
+ } catch (final LinkageError ignore) {
+ } finally {
+ if (idx != null) {
+ MUTEX.set(idx);
+ } else {
+ MUTEX.remove();
                 }
             }
- } else {
- //Only user created code calls flush or push.
- if (code != ErrorManager.FLUSH_FAILURE) {
- reThrow = false;
- }
- }
-
- if (reThrow) {
- if (le instanceof Error) {
- throw (Error) le;
- } else if (le instanceof RuntimeException) {
- throw (RuntimeException) le;
- } else {
- assert false : le; //Should not happen.
- }
         }
     }
 
@@ -2344,18 +2337,22 @@
     private void initAuthenticator(final String p) {
         assert Thread.holdsLock(this);
         String name = fromLogManager(p.concat(".authenticator"));
- if (hasValue(name)) {
- try {
- this.auth = LogManagerProperties
- .newObjectFrom(name, Authenticator.class);
- } catch (final SecurityException SE) {
- throw SE;
- } catch (final ClassNotFoundException literalAuth) {
+ if (name != null && !"null".equalsIgnoreCase(name)) {
+ if (name.length() != 0) {
+ try {
+ this.auth = LogManagerProperties
+ .newObjectFrom(name, Authenticator.class);
+ } catch (final SecurityException SE) {
+ throw SE;
+ } catch (final ClassNotFoundException literalAuth) {
+ this.auth = DefaultAuthenticator.of(name);
+ } catch (final ClassCastException literalAuth) {
+ this.auth = DefaultAuthenticator.of(name);
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else { //Authenticator is installed to provide the user name.
                 this.auth = DefaultAuthenticator.of(name);
- } catch (final ClassCastException literalAuth) {
- this.auth = DefaultAuthenticator.of(name);
- } catch (final Exception E) {
- reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
             }
         }
     }
@@ -2691,6 +2688,8 @@
                 if (msg != null) {
                     send(msg, priority, code);
                 }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, code);
             } finally {
                 releaseMutex();
             }
@@ -2937,20 +2936,24 @@
      * @since JavaMail 1.4.4
      */
     private void verifySettings(final Session session) {
- if (session != null) {
- final Properties props = session.getProperties();
- final Object check = props.put("verify", "");
- if (check instanceof String) {
- String value = (String) check;
- //Perform the verify if needed.
- if (hasValue(value)) {
- verifySettings0(session, value);
- }
- } else {
- if (check != null) { //This call will fail.
- verifySettings0(session, check.getClass().toString());
+ try {
+ if (session != null) {
+ final Properties props = session.getProperties();
+ final Object check = props.put("verify", "");
+ if (check instanceof String) {
+ String value = (String) check;
+ //Perform the verify if needed.
+ if (hasValue(value)) {
+ verifySettings0(session, value);
+ }
+ } else {
+ if (check != null) { //This call will fail.
+ verifySettings0(session, check.getClass().toString());
+ }
                 }
             }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
         }
     }
 
@@ -3084,20 +3087,36 @@
             } else {
                 //Force a property copy.
                 final String protocol = t.getURLName().getProtocol();
- session.getProperty("mail.host");
- session.getProperty("mail.user");
- session.getProperty("mail." + protocol + ".host");
+ String mailHost = session.getProperty("mail."
+ + protocol + ".host");
+ if (isEmpty(mailHost)) {
+ mailHost = session.getProperty("mail.host");
+ } else {
+ session.getProperty("mail.host");
+ }
                 session.getProperty("mail." + protocol + ".port");
                 session.getProperty("mail." + protocol + ".user");
+ session.getProperty("mail.user");
+ session.getProperty("mail." + protocol + ".localport");
                 local = session.getProperty("mail." + protocol + ".localhost");
                 if (isEmpty(local)) {
                     local = session.getProperty("mail."
                             + protocol + ".localaddress");
+ } else {
+ session.getProperty("mail." + protocol + ".localaddress");
                 }
 
                 if ("resolve".equals(verify)) {
                     try { //Resolve the remote host name.
- verifyHost(t.getURLName().getHost());
+ String transportHost = t.getURLName().getHost();
+ if (!isEmpty(transportHost)) {
+ verifyHost(transportHost);
+ if (!transportHost.equalsIgnoreCase(mailHost)) {
+ verifyHost(mailHost);
+ }
+ } else {
+ verifyHost(mailHost);
+ }
                     } catch (final IOException IOE) {
                         MessagingException ME =
                                 new MessagingException(msg, IOE);

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java
--- a/mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java Mon May 09 13:39:39 2016 -0700
@@ -133,6 +133,25 @@
     }
 
     /**
+ * Sets the log record time using the seconds and nanoseconds of the epoch
+ * from 1970-01-01T00:00:00Z.
+ *
+ * @param record the log record.
+ * @param epochSecond the seconds.
+ * @param nanoAdjustment the nano seconds.
+ * @throws ClassNotFoundException if running on pre JDK 8.
+ * @throws NoSuchMethodException if running on JDK 8.
+ * @throws Exception if there is a problem.
+ */
+ static void setEpochSecond(final LogRecord record, final long epochSecond,
+ final long nanoAdjustment) throws Exception {
+ final Class<?> k = Class.forName("java.time.Instant");
+ Method instant = k.getMethod("ofEpochSecond", long.class, long.class);
+ Method set = LogRecord.class.getMethod("setInstant", k);
+ set.invoke(record, instant.invoke(null, epochSecond, nanoAdjustment));
+ }
+
+ /**
      * Determines if the {_at_code java.time} APIs are available for this JVM.
      *
      * @return true if the time classes can be loaded.
@@ -152,57 +171,74 @@
 
     /**
      * Fails if any declared types are outside of the logging-mailhandler.jar.
+ * This includes classes from the JavaMail spec.
      *
      * @param k the type to check for dependencies.
      * @throws Exception if there is a problem.
      */
     final void testJavaMailLinkage(final Class<?> k) throws Exception {
- assertFalse(k.getName(), isFromJavaMail(k));
+ testJavaMailLinkage(k, true);
+ }
+
+ /**
+ * Fails if any declared types are outside of the logging-mailhandler.jar.
+ *
+ * @param k the type to check for dependencies.
+ * @param includeSpec if true this includes official JavaMail spec classes.
+ * @throws Exception if there is a problem.
+ */
+ final void testJavaMailLinkage(final Class<?> k, final boolean includeSpec)
+ throws Exception {
+ assertFalse(k.getName(), isFromJavaMail(k, includeSpec));
         for (Annotation an : k.getDeclaredAnnotations()) {
- assertFalse(an.toString(), isFromJavaMail(an.annotationType()));
+ assertFalse(an.toString(),
+ isFromJavaMail(an.annotationType(), includeSpec));
         }
 
         for (Method m : k.getDeclaredMethods()) {
             assertFalse(m.getReturnType().getName(),
- isFromJavaMail(m.getReturnType()));
+ isFromJavaMail(m.getReturnType(), includeSpec));
             for (Class<?> p : m.getParameterTypes()) {
- assertFalse(p.getName(), isFromJavaMail(p));
+ assertFalse(p.getName(), isFromJavaMail(p, includeSpec));
             }
 
             for (Class<?> e : m.getExceptionTypes()) {
- assertFalse(e.getName(), isFromJavaMail(e));
+ assertFalse(e.getName(), isFromJavaMail(e, includeSpec));
             }
 
             for (Annotation an : m.getDeclaredAnnotations()) {
- assertFalse(an.toString(), isFromJavaMail(an.annotationType()));
+ assertFalse(an.toString(),
+ isFromJavaMail(an.annotationType(), includeSpec));
             }
         }
 
         for (Constructor<?> c : k.getDeclaredConstructors()) {
             for (Class<?> p : c.getParameterTypes()) {
- assertFalse(p.getName(), isFromJavaMail(p));
+ assertFalse(p.getName(), isFromJavaMail(p, includeSpec));
             }
 
             for (Class<?> e : c.getExceptionTypes()) {
- assertFalse(e.getName(), isFromJavaMail(e));
+ assertFalse(e.getName(), isFromJavaMail(e, includeSpec));
             }
 
             for (Annotation an : c.getDeclaredAnnotations()) {
- assertFalse(an.toString(), isFromJavaMail(an.annotationType()));
+ assertFalse(an.toString(),
+ isFromJavaMail(an.annotationType(), includeSpec));
             }
         }
 
         for (Field f : k.getDeclaredFields()) {
             for (Annotation an : k.getDeclaredAnnotations()) {
- assertFalse(an.toString(), isFromJavaMail(an.annotationType()));
+ assertFalse(an.toString(),
+ isFromJavaMail(an.annotationType(), includeSpec));
             }
- assertFalse(f.getName(), isFromJavaMail(f.getType()));
+ assertFalse(f.getName(), isFromJavaMail(f.getType(), includeSpec));
         }
     }
 
     /**
      * Tests that the private static loadDeclaredClasses method of the given
- * type. Objects used by the MailHandler during a push might require
+ * type. Objects used by the MailHandler during a push might require
      * declaring classes to be loaded on create since a push may happen after a
      * class loader is shutdown.
      *
@@ -309,11 +345,11 @@
         throw new AssertionError(then + " " + System.currentTimeMillis());
     }
 
- private boolean isFromJavaMail(Class<?> k) throws Exception {
+ private boolean isFromJavaMail(Class<?> k, boolean include) throws Exception {
         for (Class<?> t = k; t != null; t = t.getSuperclass()) {
             final String n = t.getName();
             if (n.startsWith("javax.mail.")) {
- return true;
+ return include;
             }
 
             //Not included with logging-mailhandler.jar.

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java Mon May 09 13:39:39 2016 -0700
@@ -41,6 +41,7 @@
 package com.sun.mail.util.logging;
 
 import java.io.*;
+import java.lang.reflect.Method;
 import java.net.SocketException;
 import java.util.*;
 import java.util.logging.Level;
@@ -475,6 +476,23 @@
                 cf.format(r));
     }
 
+ @Test
+ public void testFormatZoneDateTime() throws Exception {
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ Object zdt = LogManagerProperties.getZonedDateTime(r);
+ if (zdt != null) {
+ String p = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp";
+ CompactFormatter cf = new CompactFormatter(p);
+ assertEquals(String.format(p, zdt), cf.format(r));
+ } else {
+ try {
+ Method m = LogRecord.class.getMethod("getInstant");
+ fail(m.toString());
+ } catch (final NoSuchMethodException expect) {
+ }
+ }
+ }
+
     @Test(expected=NullPointerException.class)
     public void testFormatNull() {
         CompactFormatter cf = new CompactFormatter();

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java Mon May 09 13:39:39 2016 -0700
@@ -1159,6 +1159,41 @@
         }
     }
 
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGetZonedDateTime() throws Exception {
+ LogRecord r1 = new LogRecord(Level.SEVERE, "");
+ LogRecord r2 = new LogRecord(Level.SEVERE, "");
+ try {
+ final Class<?> k = Class.forName("java.time.ZonedDateTime");
+ setEpochSecond(r1, 100, 1);
+ setEpochSecond(r2, 100, 1);
+ Comparable<Object> c1 = (Comparable<Object>)
+ LogManagerProperties.getZonedDateTime(r1);
+ Comparable<Object> c2 = (Comparable<Object>)
+ LogManagerProperties.getZonedDateTime(r2);
+
+ assertEquals(k, c1.getClass());
+ assertEquals(k, c2.getClass());
+ assertNotSame(c1, c2);
+ assertEquals(c1.getClass(), c2.getClass());
+ assertEquals(0, c1.compareTo(c2));
+ } catch (final NoSuchMethodException preJdk9) {
+ assertNull(LogManagerProperties.getZonedDateTime(r1));
+ assertNull(LogManagerProperties.getZonedDateTime(r2));
+ assertTrue(hasJavaTimeModule());
+ } catch (final ClassNotFoundException preJdk8) {
+ assertNull(LogManagerProperties.getZonedDateTime(r1));
+ assertNull(LogManagerProperties.getZonedDateTime(r2));
+ assertFalse(hasJavaTimeModule());
+ }
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void testGetZonedDateTimeNull() throws Exception {
+ LogManagerProperties.getZonedDateTime((LogRecord) null);
+ }
+
     private static void setPending(final Throwable t) {
         if (t != null) {
             PENDING.set(t);

diff -r dda23c548c22 -r 50f4ccfca842 mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Tue May 03 14:02:09 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Mon May 09 13:39:39 2016 -0700
@@ -330,6 +330,16 @@
     }
 
     @Test
+ public void testJavaMailLinkage() throws Exception {
+ /**
+ * The MailHandler has to depend on the official JavaMail spec classes.
+ * The logging-mailhandler.jar needs to be portable to other platforms
+ * so it doesn't depend on reference implementation classes directly.
+ */
+ testJavaMailLinkage(MailHandler.class, false);
+ }
+
+ @Test
     public void testLogManagerModifiers() throws Exception {
         testLogManagerModifiers(MailHandler.class);
     }
@@ -1986,8 +1996,13 @@
         PrintStream ls = new LinkageErrorStream(new StackTraceElement[0]);
         @SuppressWarnings("UseOfSystemOutOrSystemErr")
         final PrintStream err = System.err;
- try {
+ final Thread.UncaughtExceptionHandler ueh
+ = Thread.currentThread().getUncaughtExceptionHandler();
+ try {
+ CountingUncaughtExceptionHandler cueh
+ = new CountingUncaughtExceptionHandler();
             System.setErr(new LinkageErrorStream(new StackTraceElement[0]));
+ Thread.currentThread().setUncaughtExceptionHandler(cueh);
             boolean linkageErrorEscapes = false;
             try {
                 ErrorManager em = new ErrorManager();
@@ -2003,12 +2018,6 @@
                 assertEquals(ErrorManager.class,
                         instance.getErrorManager().getClass());
                 instance.publish(new LogRecord(Level.SEVERE, ""));
- /**
- * LinkageError escapes push and flush because only user created
- * code calls those methods. Therefore, user created code can
- * trap those errors. In all other cases we assume the worst and
- * just swallow the linkage error.
- */
                 if ("preDestroy".equals(method)) {
                     instance.preDestroy();
                 } else if ("publish".equals(method)) {
@@ -2019,21 +2028,9 @@
                             = new CloseLogRecord(Level.SEVERE, "", instance);
                     instance.publish(r);
                 } else if ("flush".equals(method)) {
- boolean escapes = false;
- try {
- instance.flush();
- } catch (LinkageError expect) {
- escapes = true;
- }
- assertTrue(escapes);
+ instance.flush();
                 } else if ("push".equals(method)) {
- boolean escapes = false;
- try {
- instance.push();
- } catch (LinkageError expect) {
- escapes = true;
- }
- assertTrue(escapes);
+ instance.push();
                 } else if ("close".equals(method)) {
                     instance.close();
                 } else {
@@ -2044,8 +2041,10 @@
                 instance.close();
             }
             assertTrue(ls.checkError());
+ assertEquals(1, cueh.count);
         } finally {
             System.setErr(err);
+ Thread.currentThread().setUncaughtExceptionHandler(ueh);
         }
     }
 
@@ -2246,9 +2245,8 @@
 
                 for (Exception exception : em.exceptions) {
                     Throwable t = exception;
- System.err.println("Verify index=" + v);
                     dump(t);
- fail(t.toString());
+ fail("Verify index=" + v);
                 }
 
                 manager.reset();
@@ -2261,14 +2259,12 @@
                             continue;
                         }
                         if (!isConnectOrTimeout(t)) {
- System.err.println("Verify index=" + v);
                             dump(t);
- fail(t.toString());
+ fail("Verify index=" + v);
                         }
                     } else {
- System.err.println("Verify index=" + v);
                         dump(t);
- fail(t.toString());
+ fail("Verify index=" + v);
                     }
                 }
             }
@@ -3274,10 +3270,27 @@
 
     @Test
     public void testAuthenticator_Char_Array_Arg() {
+ PasswordAuthentication pa;
         MailHandler instance = new MailHandler(createInitProperties(""));
         InternalErrorManager em = new InternalErrorManager();
         instance.setErrorManager(em);
 
+ //Null literal means actual password value here.
+ instance.setAuthenticator("null".toCharArray());
+ pa = passwordAuthentication(instance.getAuthenticator(), "user");
+ assertEquals("user", pa.getUserName());
+ assertEquals("null", pa.getPassword());
+
+ instance.setAuthenticator("Null".toCharArray());
+ pa = passwordAuthentication(instance.getAuthenticator(), "user");
+ assertEquals("user", pa.getUserName());
+ assertEquals("Null", pa.getPassword());
+
+ instance.setAuthenticator("NULL".toCharArray());
+ pa = passwordAuthentication(instance.getAuthenticator(), "user");
+ assertEquals("user", pa.getUserName());
+ assertEquals("NULL", pa.getPassword());
+
         try {
             instance.setAuthenticator((char[]) null);
         } catch (RuntimeException RE) {
@@ -3292,7 +3305,7 @@
 
         try {
             instance.setAuthenticator("password".toCharArray());
- PasswordAuthentication pa = passwordAuthentication(
+ pa = passwordAuthentication(
                     instance.getAuthenticator(), "user");
             assertEquals("user", pa.getUserName());
             assertEquals("password", pa.getPassword());
@@ -4460,13 +4473,7 @@
         try {
             try {
                 instance.setErrorManager(new ErrorManager());
- try {
- instance.reportError(null, null, ErrorManager.FLUSH_FAILURE);
- dump(new Throwable());
- fail("Expected runtime exception");
- } catch (RuntimeException expect) {
- } catch (LinkageError expect) {
- }
+ instance.reportError(null, null, ErrorManager.FLUSH_FAILURE);
 
                 instance.setErrorManager(new ErrorManager());
                 instance.reportError(null, null, ErrorManager.CLOSE_FAILURE);
@@ -5804,6 +5811,65 @@
     }
 
     @Test
+ public void testInitAuthenticator() throws Exception {
+ //Liternal null and null reference mean no Authenticator
+ //when dealing with a properties file.
+ testInitAuthenticator("user", null);
+ testInitAuthenticator("user", "null");
+ testInitAuthenticator("user", "NULL");
+ testInitAuthenticator("user", "Null");
+ testInitAuthenticator("user", "");
+ testInitAuthenticator("user", "somepassword");
+ }
+
+ private void testInitAuthenticator(String user, String pass) throws Exception {
+ InternalErrorManager em;
+ MailHandler target;
+ final String p = MailHandler.class.getName();
+ final LogManager manager = LogManager.getLogManager();
+ final Properties props = createInitProperties(p);
+ props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+ if (pass != null) {
+ props.put(p.concat(".authenticator"), pass);
+ }
+ props.put(p.concat(".mail.transport.protocol"), "smtp");
+
+ read(manager, props);
+ try {
+ target = new MailHandler();
+ try {
+ em = internalErrorManagerFrom(target);
+ for (Exception exception : em.exceptions) {
+ dump(exception);
+ }
+ assertTrue(em.exceptions.isEmpty());
+ } finally {
+ target.close();
+ }
+
+ if (pass != null && !"null".equalsIgnoreCase(pass)) {
+ assertNotNull(target.getAuthenticator());
+ PasswordAuthentication initPa = passwordAuthentication(
+ target.getAuthenticator(), user);
+ assertEquals(user, initPa.getUserName());
+ assertEquals(pass, initPa.getPassword());
+
+ target.setAuthenticator(pass.toCharArray());
+ PasswordAuthentication setPa = passwordAuthentication(
+ target.getAuthenticator(), user);
+ assertEquals(setPa.getUserName(), initPa.getUserName());
+ assertEquals(setPa.getPassword(), initPa.getPassword());
+ } else {
+ assertNull(target.getAuthenticator());
+ target.setAuthenticator((char[]) null);
+ assertNull(target.getAuthenticator());
+ }
+ } finally {
+ manager.reset();
+ }
+ }
+
+ @Test
     public void testInitAttachmentFilters() throws Exception {
         InternalErrorManager em;
         MailHandler target;
@@ -7803,6 +7869,17 @@
         }
     }
 
+ private static class CountingUncaughtExceptionHandler
+ implements Thread.UncaughtExceptionHandler {
+
+ int count;
+
+ @SuppressWarnings("override") //JDK-6954234
+ public void uncaughtException(Thread t, Throwable e) {
+ count++;
+ }
+ }
+
     private static class LinkageErrorStream extends PrintStream {
 
         private final StackTraceElement[] stack;


diff -r 50f4ccfca842 -r e6aaa82b769f doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon May 09 13:39:39 2016 -0700
+++ b/doc/release/CHANGES.txt Wed May 25 15:34:27 2016 -0700
@@ -28,6 +28,7 @@
 K 7238 unsolicited FETCH response *must* invalidate X-GM-LABELS in cache
 K 7332 MimeBodyPart.isMimeType returns false if type header can't be parsed
 K 7356 NPE in Tomcat ClassLoader causes Session.getInstance to fail
+K 7378 Deadlock in IMAPFolder.doProtocolCommand()
 
 
                   CHANGES IN THE 1.5.5 RELEASE

diff -r 50f4ccfca842 -r e6aaa82b769f mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Mon May 09 13:39:39 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Wed May 25 15:34:27 2016 -0700
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
  *
  * The contents of this file are subject to the terms of either the GNU
  * General Public License Version 2 only ("GPL") or the Common Development
@@ -3330,7 +3330,8 @@
      * @exception MessagingException for errors
      * @since JavaMail 1.5.2
      */
- public long getStatusItem(String item) throws MessagingException {
+ public synchronized long getStatusItem(String item)
+ throws MessagingException {
         if (!opened) {
             checkExists();
 
@@ -3571,6 +3572,8 @@
      * }
      * </pre></blockquote>
      *
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ *
      * @return the IMAPProtocol for the Store's connection
      * @exception ProtocolException for protocol errors
      */
@@ -3771,18 +3774,16 @@
         return null;
     }
 
- protected Object doProtocolCommand(ProtocolCommand cmd)
+ protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
                                 throws ProtocolException {
- synchronized (this) {
- /*
- * Check whether we have a protocol object, not whether we're
- * opened, to allow use of the exsting protocol object in the
- * open method before the state is changed to "opened".
- */
- if (protocol != null) {
- synchronized (messageCacheLock) {
- return cmd.doCommand(getProtocol());
- }
+ /*
+ * Check whether we have a protocol object, not whether we're
+ * opened, to allow use of the exsting protocol object in the
+ * open method before the state is changed to "opened".
+ */
+ if (protocol != null) {
+ synchronized (messageCacheLock) {
+ return cmd.doCommand(getProtocol());
             }
         }
 
@@ -3802,6 +3803,8 @@
      * object from the connection pool, give it back. If we used our
      * own protocol object, nothing to do.
      *
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ *
      * @param p the IMAPProtocol object
      */
     protected synchronized void releaseStoreProtocol(IMAPProtocol p) {


diff -r e6aaa82b769f -r ac442a6e5d97 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Wed May 25 15:34:27 2016 -0700
+++ b/doc/release/CHANGES.txt Tue Jun 14 14:15:18 2016 -0700
@@ -29,6 +29,8 @@
 K 7332 MimeBodyPart.isMimeType returns false if type header can't be parsed
 K 7356 NPE in Tomcat ClassLoader causes Session.getInstance to fail
 K 7378 Deadlock in IMAPFolder.doProtocolCommand()
+K 7471 InternetAddress.getLocalAddress should use
+ InetAddress.getCanonicalHostName
 
 
                   CHANGES IN THE 1.5.5 RELEASE

diff -r e6aaa82b769f -r ac442a6e5d97 doc/release/COMPAT.txt
--- a/doc/release/COMPAT.txt Wed May 25 15:34:27 2016 -0700
+++ b/doc/release/COMPAT.txt Tue Jun 14 14:15:18 2016 -0700
@@ -14,6 +14,18 @@
 with this release of the JavaMail API.
 
 
+-- JavaMail 1.5.6 --
+
+- InternetAddress.getLocalAddress uses canonical host name
+
+ The InternetAddress.getLocalAddress method now uses the
+ java.net.InetAddress.getCanonicalHostName method if neither the
+ "mail.from" nor "mail.host" properties have been set. The System
+ property "mail.mime.address.usecanonicalhostname" can be set to
+ "false" to revert to the previous behavior.
+
+
+
 -- JavaMail 1.5.4 --
 
 - Idlemanager.watch no longer throws IOException

diff -r e6aaa82b769f -r ac442a6e5d97 mail/src/main/java/javax/mail/internet/InternetAddress.java
--- a/mail/src/main/java/javax/mail/internet/InternetAddress.java Wed May 25 15:34:27 2016 -0700
+++ b/mail/src/main/java/javax/mail/internet/InternetAddress.java Tue Jun 14 14:15:18 2016 -0700
@@ -85,6 +85,10 @@
         PropUtil.getBooleanSystemProperty(
                             "mail.mime.address.ignorebogusgroupname", true);
 
+ private static final boolean useCanonicalHostName =
+ PropUtil.getBooleanSystemProperty(
+ "mail.mime.address.usecanonicalhostname", true);
+
     /**
      * Default constructor.
      */
@@ -567,7 +571,14 @@
         String host = null;
         InetAddress me = InetAddress.getLocalHost();
         if (me != null) {
- host = me.getHostName();
+ // try canonical host name first
+ if (useCanonicalHostName)
+ host = me.getCanonicalHostName();
+ if (host == null)
+ host = me.getHostName();
+ // if we can't get our name, use local address literal
+ if (host == null)
+ host = me.getHostAddress();
             if (host != null && host.length() > 0 && isInetAddressLiteral(host))
                 host = '[' + host + ']';
         }

diff -r e6aaa82b769f -r ac442a6e5d97 mail/src/main/java/javax/mail/internet/package.html
--- a/mail/src/main/java/javax/mail/internet/package.html Wed May 25 15:34:27 2016 -0700
+++ b/mail/src/main/java/javax/mail/internet/package.html Tue Jun 14 14:
[truncated due to length]