commits@javamail.java.net

[javamail~mercurial:400] Fix excludes; fix name of taglib.jar.

From: <shannon_at_kenai.com>
Date: Tue, 6 Dec 2011 05:26:43 +0000

Project: javamail
Repository: mercurial
Revision: 400
Author: shannon
Date: 2011-12-06 05:10:14 UTC
Link:

Log Message:
------------
Follow IMAP spec more closely - keep reading responses after BYE response.
Updates from Jason:

MailHandler create session during a push or if verify is implied.
MailHandler don't verify initial session if replaced during construction.
MailHandler avoid extra call to Message.saveChanges() during send.
MailHandler call setAcceptLang outside of lock.
MailHandler ignore case of mime type in setContent.
MailHandler verify that DataHandler can be loaded during a write.
MailHandler verify sendMessage throws no content exception.
MailHandler verify localhost alternate if specified.
MailHandler force a copy of localhost properties from the SMTPTransport.
MailHandler clone arrays as signature class type to match EMPTY_***_ARRAY.
MailHandlerTest tests for initial session verify.
MailHandlerTest test is content missing method.
MailHandlerTest modify test to ensure previous session is freed.
MailHandlerTest modify test to ensure subtype arrays are not stored.
Fix problem with checkserveridentity by configuring SSL before checking
server identity - Kenai bug 4296.
Make PasswordAuthentication thread safe - Kenai bug 4511.
Fix handling of old Sun V3 messages. A fix to MimeMultipart to call
parse() in addBodyPart() was causing infinite recursion in this parse()
method.
suppress auth info in debug output, unless mail.debug.auth=true
better handle timeouts from POP3 server, throwing FolderClosedException
Update email address.
Fix DOCTYPE.
Removing unneeded imports; cleanup.
Resolve ambiguous imports with javax.servlet.http.Part.
Fix copyright/license notice - should be BSD.
jtl.jar is now named taglib.jar to match maven build.
Fix excludes; fix name of taglib.jar.


Revisions:
----------
387
388
389
390
391
392
393
394
395
396
397
398
399
400


Modified Paths:
---------------
mail/src/main/java/com/sun/mail/iap/Protocol.java
mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/util/SocketFetcher.java
mail/src/main/java/javax/mail/PasswordAuthentication.java
mbox/src/main/java/com/sun/mail/mbox/SunV3Multipart.java
mail/src/main/java/com/sun/mail/imap/IMAPStore.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
mail/src/main/java/com/sun/mail/pop3/Protocol.java
mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
mail/src/main/java/javax/mail/package.html
mail/src/main/java/javax/mail/internet/MimePartDataSource.java
mail/src/test/java/com/sun/mail/pop3/POP3Handler.java
webapp/src/main/webapp/index.html
taglib/src/main/resources/META-INF/taglib.tld
taglib/src/main/java/demo/AttachmentInfo.java
webapp/src/main/java/demo/AttachmentServlet.java
webapp/build.bat
webapp/build.sh
webapp/pom.xml


Added Paths:
------------
mail/src/test/java/com/sun/mail/imap/IMAPAuthDebugTest.java
mail/src/test/java/com/sun/mail/pop3/POP3AuthDebugTest.java
mail/src/test/java/com/sun/mail/smtp/SMTPAuthDebugTest.java
mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
mail/src/test/java/com/sun/mail/smtp/SMTPServer.java
mail/src/test/java/com/sun/mail/pop3/POP3FolderClosedExceptionTest.java


Diffs:
------
diff -r 91e372ffcc60 -r 0598e45eedb0 mail/src/main/java/com/sun/mail/iap/Protocol.java
--- a/mail/src/main/java/com/sun/mail/iap/Protocol.java Wed Sep 21 15:46:54 2011 -0700
+++ b/mail/src/main/java/com/sun/mail/iap/Protocol.java Thu Nov 10 13:37:08 2011 -0800
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2011 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
@@ -307,26 +307,33 @@
             done = true;
         }
 
+ Response byeResp = null;
         while (!done) {
             try {
                 r = readResponse();
             } catch (IOException ioex) {
+ if (byeResp != null) // connection closed after BYE was sent
+ break;
                 // convert this into a BYE response
                 r = Response.byeResponse(ioex);
             } catch (ProtocolException pex) {
                 continue; // skip this response
             }
-
+
+ if (r.isBYE()) {
+ byeResp = r;
+ continue;
+ }
+
             v.addElement(r);
 
- if (r.isBYE()) // shouldn't wait for command completion response
- done = true;
-
             // If this is a matching command completion response, we are done
             if (r.isTagged() && r.getTag().equals(tag))
                 done = true;
         }
 
+ if (byeResp != null)
+ v.addElement(byeResp); // must be last
         Response[] responses = new Response[v.size()];
         v.copyInto(responses);
         timestamp = System.currentTimeMillis();


diff -r 0598e45eedb0 -r e4fc91c18666 mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
--- a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Thu Nov 10 13:37:08 2011 -0800
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Mon Nov 21 16:58:45 2011 -0800
@@ -41,6 +41,7 @@
 
 package com.sun.mail.util.logging;
 
+import com.sun.mail.smtp.SMTPTransport;
 import java.io.*;
 import java.lang.reflect.*;
 import java.net.InetAddress;
@@ -170,7 +171,8 @@
  * (defaults to {_at_linkplain javax.mail.Message#setFrom()})
  *
  * <li>com.sun.mail.util.logging.MailHandler.mail.host the host name or IP
- * address of the email server. (defaults to <tt>null</tt>, use default
+ * address of the email server. (defaults to <tt>null</tt>, use
+ * {_at_linkplain Transport#protocolConnect default}
  * <tt>Java Mail</tt> behavior)
  *
  * <li>com.sun.mail.util.logging.MailHandler.mail.reply.to a comma separated
@@ -303,6 +305,10 @@
      */
     private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
     /**
+ * Min byte size for header data. Used for initial arrays sizing.
+ */
+ private static final int MIN_HEADER_SIZE = 1024;
+ /**
      * Cache the off value.
      */
     private static final int offValue = Level.OFF.intValue();
@@ -401,7 +407,7 @@
      * caller does not have <tt>LoggingPermission("control")</tt>.
      */
     public MailHandler() {
- init();
+ init(true);
         sealed = true;
     }
 
@@ -413,7 +419,7 @@
      * caller does not have <tt>LoggingPermission("control")</tt>.
      */
     public MailHandler(final int capacity) {
- init();
+ init(true);
         sealed = true;
         setCapacity0(capacity);
     }
@@ -429,7 +435,7 @@
      * caller does not have <tt>LoggingPermission("control")</tt>.
      */
     public MailHandler(final Properties props) {
- init();
+ init(false);
         sealed = true;
         setMailProperties0(props);
     }
@@ -482,7 +488,7 @@
          * See close().
          */
         if (isLoggable(record)) {
- record.getSourceMethodName(); //infer caller, outside of lock
+ record.getSourceMethodName(); //Infer caller, outside of lock.
             publish0(record);
         }
     }
@@ -502,14 +508,14 @@
 
             if (size < data.length) {
                 data[size] = record;
- ++size; //be nice to client compiler.
+ ++size; //Be nice to client compiler.
                 priority = isPushable(record);
                 if (priority || size >= capacity) {
                     ctx = writeLogRecords(ErrorManager.WRITE_FAILURE);
                 } else {
                     ctx = null;
                 }
- } else { //drain the buffer and try again.
+ } else { //Drain the buffer and try again.
                 priority = false;
                 ctx = flushAndRePublish(record);
             }
@@ -532,7 +538,7 @@
      */
     private MessageContext flushAndRePublish(final LogRecord record) {
         MessageContext ctx = writeLogRecords(ErrorManager.WRITE_FAILURE);
- if (ctx != null) { //try again with an empty buffer.
+ if (ctx != null) { //Try again with an empty buffer.
             /**
              * The common case should be a simple store, no push.
              * Rarely should we be pushing while holding a lock.
@@ -590,7 +596,7 @@
     public void close() {
         MessageContext ctx = null;
         synchronized (this) {
- super.setLevel(Level.OFF); //security check first.
+ super.setLevel(Level.OFF); //Security check first.
             try {
                 ctx = writeLogRecords(ErrorManager.CLOSE_FAILURE);
             } finally {
@@ -603,7 +609,7 @@
                     this.capacity = -this.capacity;
                 }
 
- if (size == 0 && data.length != 1) { //ensure not inside a push.
+ if (size == 0 && data.length != 1) { //Ensure not inside a push.
                     this.data = new LogRecord[1];
                 }
             }
@@ -626,7 +632,7 @@
     public synchronized void setLevel(final Level newLevel) {
         if (this.capacity > 0) {
             super.setLevel(newLevel);
- } else { //don't allow a closed handler to be opened (half way).
+ } else { //Don't allow a closed handler to be opened (half way).
             if (newLevel == null) {
                 throw new NullPointerException();
             }
@@ -756,7 +762,7 @@
                 throw new IllegalStateException();
             }
             this.auth = auth;
- settings = this.fixUpSession();
+ settings = fixUpSession();
         }
         verifySettings(settings);
     }
@@ -778,14 +784,14 @@
 
     private void setMailProperties0(Properties props) {
         checkAccess();
- props = (Properties) props.clone();
+ props = (Properties) props.clone(); //Allow subclass.
         Session settings;
         synchronized (this) {
             if (isWriting) {
                 throw new IllegalStateException();
             }
             this.mailProps = props;
- settings = this.fixUpSession();
+ settings = fixUpSession();
         }
         verifySettings(settings);
     }
@@ -829,7 +835,7 @@
      */
     public final void setAttachmentFilters(Filter[] filters) {
         checkAccess();
- filters = (Filter[]) filters.clone();
+ filters = (Filter[]) copyOf(filters, filters.length, Filter[].class);
         synchronized (this) {
             if (this.attachmentFormatters.length != filters.length) {
                 throw attachmentMismatch(this.attachmentFormatters.length, filters.length);
@@ -869,10 +875,11 @@
      */
     public final void setAttachmentFormatters(Formatter[] formatters) {
         checkAccess();
- if (formatters.length == 0) { //null check and length check.
+ if (formatters.length == 0) { //Null check and length check.
             formatters = emptyFormatterArray();
         } else {
- formatters = (Formatter[]) formatters.clone();
+ formatters = (Formatter[]) copyOf(formatters,
+ formatters.length, Formatter[].class);
             for (int i = 0; i < formatters.length; ++i) {
                 if (formatters[i] == null) {
                     throw new NullPointerException(atIndexMsg(i));
@@ -978,7 +985,7 @@
     public final void setAttachmentNames(Formatter[] formatters) {
         checkAccess();
 
- formatters = (Formatter[]) formatters.clone();
+ formatters = (Formatter[]) copyOf(formatters, formatters.length, Formatter[].class);
         for (int i = 0; i < formatters.length; ++i) {
             if (formatters[i] == null) {
                 throw new NullPointerException(atIndexMsg(i));
@@ -1120,6 +1127,34 @@
     }
 
     /**
+ * Determines if the given throwable is a no content exception.
+ * Package-private for unit testing.
+ * @param msg the message without content.
+ * @param t the throwable to test.
+ * @return true if the throwable is a missing content exception.
+ * @throws NullPointerException if any of the arguments are null.
+ * @since JavaMail 1.4.5
+ */
+ final boolean isMissingContent(Message msg, Throwable t) {
+ for (Throwable cause = t.getCause(); cause != null;) {
+ t = cause;
+ cause = cause.getCause();
+ }
+
+ try {
+ msg.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+ } catch (final RuntimeException RE) {
+ throw RE; //Avoid catch all.
+ } catch (final Exception noContent) {
+ final String txt = noContent.getMessage();
+ if (!isEmpty(txt) && noContent.getClass() == t.getClass()) {
+ return txt.equals(t.getMessage());
+ }
+ }
+ return false;
+ }
+
+ /**
      * Converts a mime message to a raw string or formats the reason
      * why message can't be changed to raw string and reports it.
      * @param msg the mime message.
@@ -1128,7 +1163,7 @@
      * @since JavaMail 1.4.5
      */
     private void reportError(Message msg, Exception ex, int code) {
- try { //use super call so we do not prefix raw email.
+ try { //Use super call so we do not prefix raw email.
             super.reportError(toRawString(msg), ex, code);
         } catch (final MessagingException rawMe) {
             reportError(toMsgString(rawMe), ex, code);
@@ -1147,7 +1182,7 @@
         assert Thread.holdsLock(this);
         final String type = contentTypes.getContentType(name);
         if ("application/octet-stream".equalsIgnoreCase(type)) {
- return null; //formatters return strings, default to text/plain.
+ return null; //Formatters return strings, default to text/plain.
         }
         return type;
     }
@@ -1165,7 +1200,7 @@
             encoding = MimeUtility.getDefaultJavaCharset();
         }
 
- if (type != null && !"text/plain".equals(type)) {
+ if (type != null && !"text/plain".equalsIgnoreCase(type)) {
             type = contentWithEncoding(type, encoding);
             try {
                 DataSource source = new ByteArrayDataSource(buf.toString(), type);
@@ -1217,7 +1252,7 @@
             throw new IllegalStateException();
         }
 
- if (this.capacity < 0) { //if closed, remain closed.
+ if (this.capacity < 0) { //If closed, remain closed.
             this.capacity = -newCapacity;
         } else {
             this.capacity = newCapacity;
@@ -1264,7 +1299,7 @@
             fixed = current != 0;
         }
 
- //copy of zero length array is cheap, warm up copyOf.
+ //Copy of zero length array is cheap, warm up copyOf.
         if (expect == 0) {
             this.attachmentNames = emptyFormatterArray();
             assert this.attachmentNames.length == 0;
@@ -1294,7 +1329,7 @@
             fixed = current != 0;
         }
 
- //copy of zero length array is cheap, warm up copyOf.
+ //Copy of zero length array is cheap, warm up copyOf.
         if (expect == 0) {
            this.attachmentFilters = emptyFilterArray();
            assert this.attachmentFilters.length == 0;
@@ -1308,13 +1343,32 @@
      * @param size the new size.
      * @return new copy
      */
- private static Object[] copyOf(Object[] a, int size) {
- Object[] copy = (Object[]) Array.newInstance(a.getClass().getComponentType(), size);
+ private static Object[] copyOf(final Object[] a, final int size) {
+ final Object[] copy = (Object[]) Array.newInstance(
+ a.getClass().getComponentType(), size);
         System.arraycopy(a, 0, copy, 0, Math.min(a.length, size));
         return copy;
     }
 
     /**
+ * Copies the given array to a new array type.
+ * Can be removed when Java Mail requires Java 1.6.
+ * @param a the original array.
+ * @param size the new size.
+ * @return new copy
+ */
+ private static Object[] copyOf(Object[] a, int len, Class type) {
+ if (type == a.getClass()) {
+ return (Object[]) a.clone();
+ } else {
+ final Object[] copy = (Object[]) Array.newInstance(
+ type.getComponentType(), len);
+ System.arraycopy(a, 0, copy, 0, Math.min(len, a.length));
+ return copy;
+ }
+ }
+
+ /**
      * Sets the size to zero and clears the current buffer.
      */
     private void reset() {
@@ -1343,10 +1397,11 @@
 
     /**
      * Configures the handler properties from the log manager.
+ * @param inherit true if session verify is allowed from LogManager.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
      */
- private synchronized void init() {
+ private synchronized void init(boolean inherit) {
         final LogManager manager = LogManagerProperties.getLogManager();
         final String p = getClass().getName();
         this.mailProps = new Properties();
@@ -1359,7 +1414,6 @@
         initFilter(manager, p);
         initCapacity(manager, p);
         initAuthenticator(manager, p);
- final Session settings = this.fixUpSession();
 
         initEncoding(manager, p);
         initFormatter(manager, p);
@@ -1373,12 +1427,27 @@
         initAttachmentFilters(manager, p);
         initAttachmentNames(manager, p);
 
- verifySettings(settings);
+ if (inherit && manager.getProperty(p.concat(".verify")) != null) {
+ verifySettings(initSession());
+ }
     }
 
+ /**
+ * Checks a string value for null or empty.
+ * @param s the string.
+ * @return true if the given string is null or zero length.
+ */
+ private static boolean isEmpty(final String s) {
+ return s == null || s.length() == 0;
+ }
 
+ /**
+ * Checks that a string is not empty and not equal to the literal "null".
+ * @param name the string to check for a value.
+ * @return true if the string has a valid value.
+ */
     private static boolean hasValue(final String name) {
- return name != null && name.length() > 0 && !"null".equalsIgnoreCase(name);
+ return !isEmpty(name) && !"null".equalsIgnoreCase(name);
     }
 
     private void initAttachmentFilters(LogManager manager, String p) {
@@ -1394,7 +1463,7 @@
                     try {
                         a[i] = LogManagerProperties.newFilter(names[i]);
                     } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
                     } catch (final Exception E) {
                         reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
                     }
@@ -1435,7 +1504,7 @@
                             reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
                         }
                     } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
                     } catch (final Exception E) {
                         a[i] = new SimpleFormatter();
                         reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
@@ -1473,7 +1542,7 @@
                             a[i] = new TailNameFormatter(names[i]);
                         }
                     } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
                     } catch (final Exception E) {
                         reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
                     }
@@ -1484,7 +1553,7 @@
             }
             
             this.attachmentNames = a;
- if (fixUpAttachmentNames()) { //any null indexes are repaired.
+ if (fixUpAttachmentNames()) { //Any null indexes are repaired.
                reportError("Attachment names.",
                     attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
             }
@@ -1518,7 +1587,7 @@
                 super.setLevel(Level.WARNING);
             }
         } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
         } catch (final RuntimeException RE) {
             reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
             try {
@@ -1537,7 +1606,7 @@
                 super.setFilter(LogManagerProperties.newFilter(name));
             }
         } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
         } catch (final Exception E) {
             reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
         }
@@ -1569,7 +1638,7 @@
         try {
             super.setEncoding(manager.getProperty(p.concat(".encoding")));
         } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
         } catch (final UnsupportedEncodingException UEE) {
             reportError(UEE.getMessage(), UEE, ErrorManager.OPEN_FAILURE);
         } catch (final RuntimeException RE) {
@@ -1585,7 +1654,7 @@
                 ErrorManager em = LogManagerProperties.newErrorManager(name);
                 super.setErrorManager(em);
             } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
             } catch (final Exception E) {
                 reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
             }
@@ -1605,7 +1674,7 @@
                     super.setFormatter(new SimpleFormatter());
                 }
             } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
             } catch (final Exception E) {
                 reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
                 try {
@@ -1626,7 +1695,7 @@
             try {
                 this.comparator = LogManagerProperties.newComparator(name);
             } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
             } catch (final Exception E) {
                 reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
             }
@@ -1673,7 +1742,7 @@
             try {
                 this.pushFilter = LogManagerProperties.newFilter(name);
             } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
             } catch (final Exception E) {
                 reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
             }
@@ -1687,7 +1756,7 @@
             try {
                 this.subjectFormatter = LogManagerProperties.newFormatter(name);
             } catch (final SecurityException SE) {
- throw SE; //avoid catch all.
+ throw SE; //Avoid catch all.
             } catch (final ClassNotFoundException literalSubject) {
                 this.subjectFormatter = new TailNameFormatter(name);
             } catch (final ClassCastException literalSubject) {
@@ -1698,7 +1767,7 @@
             }
         }
 
- if (this.subjectFormatter == null) { //ensure not null.
+ if (this.subjectFormatter == null) { //Ensure not null.
             this.subjectFormatter = new TailNameFormatter("");
         }
     }
@@ -1764,7 +1833,7 @@
         final Message msg = ctx.getMessage();
         try {
             envelopeFor(ctx, priority);
- Transport.send(msg);
+ Transport.send(msg); //Calls save changes.
         } catch (final Exception E) {
             reportError(msg, E, code);
         }
@@ -1803,6 +1872,9 @@
         MimeMessage msg = null;
         isWriting = true;
         try {
+ if (session == null) {
+ initSession();
+ }
             msg = new MimeMessage(session);
             msg.setDescription(descriptionFrom(comparator, pushLevel, pushFilter));
 
@@ -1829,12 +1901,11 @@
             final Formatter bodyFormat = getFormatter();
             final Filter bodyFilter = getFilter();
 
- setAcceptLang(msg);
             Locale lastLocale = null;
             for (int ix = 0; ix < size; ++ix) {
                 boolean formatted = false;
                 final LogRecord r = data[ix];
- data[ix] = null; //clear while formatting.
+ data[ix] = null; //Clear while formatting.
 
                 final Locale locale = localeFor(r);
                 appendSubject(msg, format(subjectFormatter, r));
@@ -1875,7 +1946,7 @@
                     if (locale != null && !locale.equals(lastLocale)) {
                         appendContentLang(msg, locale);
                     }
- } else { //belongs to no mime part.
+ } else { //Belongs to no mime part.
                     reportFilterError(r);
                 }
                 lastLocale = locale;
@@ -1889,14 +1960,14 @@
 
                     if (buffers[i].length() > 0) {
                         String name = parts[i].getFileName();
- if (name == null || name.length() == 0) {
+ if (isEmpty(name)) {
                             name = toString(attachmentFormatters[i]);
                             parts[i].setFileName(name);
                         }
                         setContent(parts[i], buffers[i], getContentType(name));
                     } else {
                         setIncompleteCopy(msg);
- parts[i] = null; //skip this part.
+ parts[i] = null; //Skip this part.
                     }
                     buffers[i] = null;
                 }
@@ -1945,21 +2016,23 @@
      * performed on create because this handler may be at the end of a handler
      * chain and therefore may not see any log records until LogManager.reset()
      * is called and at that time all of the settings have been cleared.
- * @param session the current session.
+ * @param session the current session or null.
      * @since JavaMail 1.4.4
      */
     private void verifySettings(final Session session) {
- 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 (value.length() > 0 && !value.equals("null")) {
- verifySettings0(session, value);
- }
- } else {
- if (check != null) { //fail
- verifySettings0(session, check.getClass().toString());
+ 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());
+ }
             }
         }
     }
@@ -1985,13 +2058,13 @@
 
         //Perform all of the copy actions first.
         final MimeMessage abort = new MimeMessage(session);
- synchronized (this) { //create the subject.
+ synchronized (this) { //Create the subject.
             appendSubject(abort, head(subjectFormatter));
             appendSubject(abort, tail(subjectFormatter, ""));
         }
 
- setIncompleteCopy(abort); //original body part is never added.
- envelopeFor(new MessageContext(abort), true); //calls saveChanges.
+ setIncompleteCopy(abort); //Original body part is never added.
+ envelopeFor(new MessageContext(abort), true);
 
         final String msg;
         if (InternetAddress.getLocalAddress(session) == null) {
@@ -2001,16 +2074,22 @@
         }
 
         try {
+ abort.saveChanges();
+ } catch (final MessagingException ME) {
+ reportError(msg, ME, ErrorManager.FORMAT_FAILURE);
+ }
+
+ try {
             //Ensure transport provider is installed.
             Address[] all = abort.getAllRecipients();
- if (all == null) { //don't pass null to sendMessage.
+ if (all == null) { //Don't pass null to sendMessage.
                all = new InternetAddress[0];
             }
             Transport t;
             try {
                 if (all.length > 0) {
                     t = session.getTransport(all[0]);
- session.getProperty("mail.transport.protocol"); //force copy
+ session.getProperty("mail.transport.protocol"); //Force copy
                 } else {
                     MessagingException me =
                         new MessagingException("No recipient addresses.");
@@ -2021,21 +2100,26 @@
                 try {
                     t = session.getTransport();
                 } catch (final MessagingException fail) {
- throw attach(fail, protocol);
+ throw attach(protocol, fail);
                 }
             }
 
+ String host = null;
             if ("remote".equals(verify)) {
                 MessagingException closed = null;
                 t.connect();
                 try {
                     try {
+ //Capture localhost while connection is open.
+ if (t instanceof SMTPTransport) {
+ host = ((SMTPTransport) t).getLocalHost();
+ }
                         //A message without content will fail at message writeTo
                         //when sendMessage is called. This allows the handler
                         //to capture all mail properties set in the LogManager.
                         t.sendMessage(abort, all);
                     } finally {
- try { //close the transport before reporting errors.
+ try { //Close the transport before reporting errors.
                             t.close();
                         } catch (final MessagingException ME) {
                             closed = ME;
@@ -2053,19 +2137,48 @@
                     if (recip != null && recip.length != 0) {
                         reportUnexpectedSend(abort, verify, sfe);
                     }
- } catch (final MessagingException expectNoContent) {
+ } catch (final MessagingException ME) {
+ if (!isMissingContent(abort, ME)) {
+ throw attach(ME, closed);
+ }
                 }
 
                 if (closed != null) {
                     throw closed;
                 }
- } else { //force a property copy.
+ } else { //Force a property copy.
                 final String protocol = t.getURLName().getProtocol();
                 session.getProperty("mail.host");
                 session.getProperty("mail.user");
                 session.getProperty("mail." + protocol + ".host");
                 session.getProperty("mail." + protocol + ".port");
                 session.getProperty("mail." + protocol + ".user");
+ if (t instanceof SMTPTransport) {
+ host = ((SMTPTransport) t).getLocalHost();
+ } else {
+ host = session.getProperty("mail."
+ + protocol + ".localhost");
+ if (isEmpty(host)) {
+ host = session.getProperty("mail."
+ + protocol + ".localaddress");
+ }
+ }
+ }
+
+ try { //Verify that DataHandler can be loaded.
+ final MimeMultipart multipart = new MimeMultipart();
+ final MimeBodyPart body = new MimeBodyPart();
+ body.setDisposition(Part.INLINE);
+ body.setDescription(verify);
+ setAcceptLang(body);
+ setContent(body, "", "text/plain");
+ multipart.addBodyPart(body);
+ abort.setContent(multipart);
+ abort.saveChanges();
+ abort.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+ } catch (final IOException IOE) {
+ fixUpContent(abort, verify, IOE);
+ reportError(abort, IOE, ErrorManager.FORMAT_FAILURE);
             }
 
             Address[] from = abort.getFrom();
@@ -2095,8 +2208,16 @@
             //Check the host name after the address checks.
             if ("local".equals(verify)) {
                 try {
- if (InetAddress.getLocalHost().getHostName().length() == 0) {
- throw new UnknownHostException();
+ if (isEmpty(host)) {
+ if (InetAddress.getLocalHost()
+ .getCanonicalHostName().length() == 0) {
+ throw new UnknownHostException();
+ }
+ } else {
+ if (InetAddress.getByName(host)
+ .getCanonicalHostName().length() == 0) {
+ throw new UnknownHostException(host);
+ }
                     }
                 } catch (final IOException IOE) {
                     throw new MessagingException(msg, IOE);
@@ -2135,7 +2256,7 @@
      * @since JavaMail 1.4.5
      */
     private void fixUpContent(MimeMessage msg, String verify, Throwable t) {
- try { //add content so toRawString doesn't fail.
+ try { //Add content so toRawString doesn't fail.
             final MimeBodyPart body;
             final String subjectType;
             final String msgDesc;
@@ -2167,11 +2288,24 @@
     /**
      * Used to update the cached session object based on changes in
      * mail properties or authenticator.
- * @return the current session.
+ * @return the current session or null if no verify is required.
      */
     private Session fixUpSession() {
         assert Thread.holdsLock(this);
- String p = getClass().getName();
+ final Session settings;
+ if (mailProps.getProperty("verify") != null) {
+ settings = initSession();
+ assert settings == session;
+ } else {
+ session = null; //Remove old session.
+ settings = null;
+ }
+ return settings;
+ }
+
+ private Session initSession() {
+ assert Thread.holdsLock(this);
+ final String p = getClass().getName();
         LogManagerProperties proxy = new LogManagerProperties(mailProps, p);
         session = Session.getInstance(proxy, auth);
         return session;
@@ -2187,6 +2321,7 @@
     private void envelopeFor(MessageContext ctx, boolean priority) {
         final Message msg = ctx.getMessage();
         final Properties proxyProps = ctx.getSession().getProperties();
+ setAcceptLang(msg);
         setFrom(msg, proxyProps);
         setRecipient(msg, proxyProps, "mail.to", Message.RecipientType.TO);
         setRecipient(msg, proxyProps, "mail.cc", Message.RecipientType.CC);
@@ -2197,9 +2332,9 @@
         if (priority) {
             setPriority(msg);
         }
+
         try {
             msg.setSentDate(new java.util.Date());
- msg.saveChanges();
         } catch (final MessagingException ME) {
             reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
         }
@@ -2262,7 +2397,7 @@
      */
     private String getClassId(final Formatter f) {
         if (f instanceof TailNameFormatter) {
- return String.class.getName(); //literal string.
+ return String.class.getName(); //Literal string.
         } else {
             return f.getClass().getName();
         }
@@ -2362,7 +2497,7 @@
         final ResourceBundle rb = r.getResourceBundle();
         if (rb != null) {
             l = rb.getLocale();
- if (l == null || l.getLanguage().length() == 0) {
+ if (l == null || isEmpty(l.getLanguage())) {
                 //The language of the fallback bundle (root) is unknown.
                 //1. Use default locale. Should only be wrong if the app is
                 // used with a langauge that was unintended. (unlikely)
@@ -2391,7 +2526,7 @@
             String lang = LogManagerProperties.toLanguageTag(l);
             if (lang.length() != 0) {
                 String header = p.getHeader("Content-Language", null);
- if (header == null || header.length() == 0) {
+ if (isEmpty(header)) {
                     p.setHeader("Content-Language", lang);
                 } else if (!header.equalsIgnoreCase(lang)) {
                     lang = ",".concat(lang);
@@ -2406,7 +2541,7 @@
 
                     if (idx < 0) {
                         int len = header.lastIndexOf("\r\n\t");
- if (len < 0) { //if not folded.
+ if (len < 0) { //If not folded.
                             len = (18 + 2) + header.length();
                         } else {
                             len = (header.length() - len) + 8;
@@ -2557,7 +2692,7 @@
                 } else {
                     if (address.length == 1) {
                         msg.setFrom(address[0]);
- } else { //greater than 1 address.
+ } else { //Greater than 1 address.
                         msg.addFrom(address);
                     }
                 }
@@ -2644,12 +2779,12 @@
      */
     private String toRawString(final Message msg) throws MessagingException, IOException {
         if (msg != null) {
- final int nbytes = Math.max(msg.getSize() + 1024, 1024);
+ final int nbytes = Math.max(msg.getSize() + MIN_HEADER_SIZE, MIN_HEADER_SIZE);
             final ByteArrayOutputStream out = new ByteArrayOutputStream(nbytes);
             msg.writeTo(out);
- return out.toString("US-ASCII"); //raw message is always ASCII.
+ return out.toString("US-ASCII"); //Raw message is always ASCII.
         } else { //Must match this.reportError behavior, see push method.
- return null; //null is the safe choice.
+ return null; //Null is the safe choice.
         }
     }
 
@@ -2664,7 +2799,8 @@
            return "null";
         }
         
- final ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+ final ByteArrayOutputStream out =
+ new ByteArrayOutputStream(MIN_HEADER_SIZE);
         final PrintStream ps = new PrintStream(out);
         ps.println(t.getMessage());
         t.printStackTrace(ps);

diff -r 0598e45eedb0 -r e4fc91c18666 mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Thu Nov 10 13:37:08 2011 -0800
+++ b/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Mon Nov 21 16:58:45 2011 -0800
@@ -2308,7 +2308,7 @@
     }
 
     @Test
- public void testMailProperties() {
+ public void testMailProperties() throws Exception {
         Properties props = new Properties();
         MailHandler instance = new MailHandler();
         InternalErrorManager em = new InternalErrorManager();
@@ -2329,26 +2329,56 @@
         Properties stored = instance.getMailProperties();
 
         assertNotNull(stored);
- assertEquals(false, props == stored);
- assertEquals(Properties.class, stored.getClass());
+ assertNotSame(props, stored);
+ assertEquals(props.getClass(), stored.getClass());
 
         assertEquals(true, em.exceptions.isEmpty());
         instance.close();
 
- final String p = MailHandler.class.getName();
         instance = createHandlerWithRecords();
         props = instance.getMailProperties();
         em = new InternalErrorManager();
         instance.setErrorManager(em);
- props.setProperty(p.concat(".mail.from"), "::1");
- props.setProperty(p.concat(".mail.to"), "::1");
- props.setProperty(p.concat(".mail.sender"), "::1");
- props.setProperty(p.concat(".mail.cc"), "::1");
- props.setProperty(p.concat(".mail.bcc"), "::1");
- props.setProperty(p.concat(".mail.reply.to"), "::1");
+
+ props.setProperty("mail.from", "localhost_at_localdomain");
+ props.setProperty("mail.to", "localhost_at_localdomain");
         instance.setMailProperties(props);
+ instance.flush();
+ for (int i = 0; i < em.exceptions.size(); i++) {
+ final Throwable t = em.exceptions.get(i);
+ if (t instanceof MessagingException
+ && t.getCause() instanceof UnknownHostException) {
+ continue;
+ } else {
+ dump(t);
+ fail(t.toString());
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+
+ props.setProperty("mail.from", "localhost_at_localdomain");
+ props.setProperty("mail.to", "::1@@");
+ instance.setMailProperties(props);
+
+ em = new InternalErrorManager();
+ instance.setErrorManager(em);
+
+ instance.publish(new LogRecord(Level.SEVERE, "test"));
         instance.close();
- assertEquals(false, em.exceptions.isEmpty());
+ int failed = 0;
+ for (int i = 0; i < em.exceptions.size(); i++) {
+ final Throwable t = em.exceptions.get(i);
+ if (t instanceof AddressException
+ || (t instanceof SendFailedException
+ && t.getCause() instanceof UnknownHostException == false)) {
+ continue;
+ } else {
+ dump(t);
+ failed++;
+ }
+ }
+ assertEquals(0, failed);
+ assertFalse(em.exceptions.isEmpty());
     }
 
     @Test
@@ -2444,6 +2474,11 @@
             fail(re.toString());
         }
 
+
+ assertEquals(instance.getAttachmentFormatters().length, 2);
+ instance.setAttachmentFilters(new ThrowFilter[]{new ThrowFilter(), new ThrowFilter()});
+ assertEquals(Filter[].class, instance.getAttachmentFilters().getClass());
+
         assertEquals(em.exceptions.isEmpty(), true);
         instance.close();
     }
@@ -2502,6 +2537,12 @@
             fail(re.toString());
         }
 
+
+ instance.setAttachmentFormatters(new ThrowFormatter[]{new ThrowFormatter()});
+ assertEquals(Formatter[].class, instance.getAttachmentFormatters().getClass());
+ assertEquals(Filter[].class, instance.getAttachmentFilters().getClass());
+ assertEquals(Formatter[].class, instance.getAttachmentNames().getClass());
+
         assertEquals(em.exceptions.isEmpty(), true);
         instance.close();
     }
@@ -2619,6 +2660,8 @@
         formatters[0] = new XMLFormatter();
         assertEquals(formatters[0].equals(instance.getAttachmentNames()[0]), false);
 
+ instance.setAttachmentNames(new ThrowFormatter[]{new ThrowFormatter(), new ThrowFormatter()});
+ assertEquals(Formatter[].class, instance.getAttachmentNames().getClass());
         assertEquals(em.exceptions.isEmpty(), true);
         instance.close();
     }
@@ -3500,7 +3543,6 @@
 
             //ensure VerifyErrorManager was installed.
             assertEquals(VerifyErrorManager.class, em.getClass());
- assertFalse(em.exceptions.isEmpty());
 
             for (int i = 0; i < em.exceptions.size(); i++) {
                 final Throwable t = em.exceptions.get(i);
@@ -3532,15 +3574,18 @@
         Address[] from = InternetAddress.parse("me_at_localhost", false);
         msg.addFrom(from);
         msg.setRecipients(Message.RecipientType.TO, from);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteArrayOutputStream out = new ByteArrayOutputStream(384);
+ msg.saveChanges();
         try {
             msg.writeTo(out);
             fail("Verify type 'remote' may send a message with no content.");
         } catch (MessagingException expect) {
             msg.setContent("", "text/plain");
+ msg.saveChanges();
             msg.writeTo(out);
         } catch (IOException expect) {
             msg.setContent("", "text/plain");
+ msg.saveChanges();
             msg.writeTo(out);
         } finally {
             out.close();
@@ -3548,6 +3593,34 @@
     }
 
     @Test
+ public void testIsMissingContent() throws Exception {
+ Properties props = new Properties();
+ props.put("mail.host", "bad-host-name");
+ props.put("mail.smtp.host", "bad-host-name");
+ props.put("mail.smtp.port", Integer.toString(OPEN_PORT));
+ props.put("mail.smtp.connectiontimeout", "1");
+ props.put("mail.smtp.timeout", "1");
+
+ MailHandler target = new MailHandler();
+ Session session = Session.getInstance(new Properties());
+ MimeMessage msg = new MimeMessage(session);
+ Address[] from = InternetAddress.parse("me_at_localhost", false);
+ msg.addFrom(from);
+ msg.setRecipients(Message.RecipientType.TO, from);
+ msg.saveChanges();
+ try {
+ msg.writeTo(new ByteArrayOutputStream(384));
+ fail("Verify type 'remote' may hide remote exceptions.");
+ } catch (RuntimeException re) {
+ throw re; //Avoid catch all.
+ } catch (Exception expect) {
+ assertNotNull(expect.getMessage());
+ assertTrue(expect.getMessage().length() != 0);
+ assertTrue(target.isMissingContent(msg, expect));
+ }
+ }
+
+ @Test
     public void testVerifyLogManager() throws Exception {
         LogManager manager = LogManager.getLogManager();
         try {
@@ -3574,7 +3647,6 @@
                     (InternalErrorManager) instance.getErrorManager();
 
             assertEquals(InternalErrorManager.class, em.getClass());
- assertFalse(em.exceptions.isEmpty());
 
             for (int i = 0; i < em.exceptions.size(); i++) {
                 final Throwable t = em.exceptions.get(i);
@@ -3583,6 +3655,7 @@
                     fail(t.toString());
                 }
             }
+ assertFalse(em.exceptions.isEmpty());
 
             instance.close();
 
@@ -3593,7 +3666,6 @@
             em = (InternalErrorManager) instance.getErrorManager();
 
             assertEquals(InternalErrorManager.class, em.getClass());
- assertFalse(em.exceptions.isEmpty());
 
             for (int i = 0; i < em.exceptions.size(); i++) {
                 final Throwable t = em.exceptions.get(i);
@@ -3606,6 +3678,7 @@
                     fail(t.toString());
                 }
             }
+ assertFalse(em.exceptions.isEmpty());
         } finally {
             manager.reset();
         }
@@ -3666,6 +3739,121 @@
     }
 
     @Test
+ public void testVerifyPropertiesConstructor() throws Exception {
+ LogManager manager = LogManager.getLogManager();
+ try {
+ manager.reset();
+
+ final String p = MailHandler.class.getName();
+ Properties props = new Properties();
+ props.put(p.concat(".mail.host"), "bad-host-name");
+ props.put(p.concat(".mail.smtp.host"), "bad-host-name");
+ props.put(p.concat(".mail.smtp.port"), Integer.toString(OPEN_PORT));
+ props.put(p.concat(".mail.to"), "badAddress");
+ props.put(p.concat(".mail.cc"), "badAddress");
+ props.put(p.concat(".subject"), p.concat(" test"));
+ props.put(p.concat(".mail.from"), "badAddress");
+ props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+ props.put(p.concat(".mail.smtp.connectiontimeout"), "1");
+ props.put(p.concat(".mail.smtp.timeout"), "1"); //no verify.
+
+ read(manager, props);
+
+ props = new Properties();
+ props.put("mail.host", "bad-host-name");
+ props.put("mail.smtp.host", "bad-host-name");
+ props.put("mail.smtp.port", Integer.toString(OPEN_PORT));
+ props.put("mail.to", "badAddress");
+ props.put("mail.cc", "badAddress");
+ props.put("subject", "test");
+ props.put("mail.from", "badAddress");
+ props.put("mail.smtp.connectiontimeout", "1");
+ props.put("mail.smtp.timeout", "1");
+ props.put("verify", "local");
+
+ MailHandler instance = new MailHandler(props);
+ try {
+ InternalErrorManager em =
+ (InternalErrorManager) instance.getErrorManager();
+
+ for (int i = 0; i < em.exceptions.size(); i++) {
+ final Throwable t = em.exceptions.get(i);
+ if (t instanceof AddressException == false) {
+ dump(t);
+ fail(t.toString());
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ } finally {
+ instance.close();
+ }
+
+ props.put("verify", "remote");
+ instance = new MailHandler(props);
+ try {
+ InternalErrorManager em =
+ (InternalErrorManager) instance.getErrorManager();
+
+ for (int i = 0; i < em.exceptions.size(); i++) {
+ final Throwable t = em.exceptions.get(i);
+ if (t instanceof AddressException) {
+ continue;
+ } else if (t.getMessage().indexOf("bad-host-name") > -1) {
+ continue;
+ } else {
+ dump(t);
+ fail(t.toString());
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ } finally {
+ instance.close();
+ }
+ } finally {
+ manager.reset();
+ }
+ }
+
+ @Test
+ public void testNoVerifyReplacedProperties() throws Exception {
+ LogManager manager = LogManager.getLogManager();
+ try {
+ manager.reset();
+
+ final String p = MailHandler.class.getName();
+ Properties props = new Properties();
+ props.put(p.concat(".mail.host"), "bad-host-name");
+ props.put(p.concat(".mail.smtp.host"), "bad-host-name");
+ props.put(p.concat(".mail.smtp.port"), Integer.toString(OPEN_PORT));
+ props.put(p.concat(".mail.to"), "badAddress");
+ props.put(p.concat(".mail.cc"), "badAddress");
+ props.put(p.concat(".subject"), p.concat(" test"));
+ props.put(p.concat(".mail.from"), "badAddress");
+ props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+ props.put(p.concat(".mail.smtp.connectiontimeout"), "1");
+ props.put(p.concat(".mail.smtp.timeout"), "1");
+ props.put(p.concat(".verify"), "remote");
+
+ read(manager, props);
+
+ MailHandler instance = new MailHandler(new Properties());
+ InternalErrorManager em =
+ (InternalErrorManager) instance.getErrorManager();
+ assertEquals(InternalErrorManager.class, em.getClass());
+ instance.close();
+
+ for (int i = 0; i < em.exceptions.size(); i++) {
+ final Throwable t = em.exceptions.get(i);
+ dump(t);
+ fail(t.toString());
+ }
+ assertTrue(em.exceptions.isEmpty());
+ } finally {
+ manager.reset();
+ }
+ }
+
+ @Test
     public void testInitSubject() throws Exception {
         InternalErrorManager em;
         MailHandler target;


diff -r e4fc91c18666 -r 50ff23a35feb doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon Nov 21 16:58:45 2011 -0800
+++ b/doc/release/CHANGES.txt Mon Nov 28 12:30:28 2011 -0800
@@ -21,6 +21,7 @@
 7021190 MimeMessage.setRecipients(type, String) does not accept null address
 K 4002 MultipartReport.setReport and setDeliveryStatus are broken
 K 4065 Wrong representation of CR/LF are appended to the attachment
+K 4296 SSL Re-Negotiation Problem with checkserveridentity=true
 <no id> properly handle timeouts during SSL negotiation
 <no id> free MIME headers in IMAPMessage.invalidateHeaders
 <no id> fix exception in POP3Message when reading file cached content twice

diff -r e4fc91c18666 -r 50ff23a35feb mail/src/main/java/com/sun/mail/util/SocketFetcher.java
--- a/mail/src/main/java/com/sun/mail/util/SocketFetcher.java Mon Nov 21 16:58:45 2011 -0800
+++ b/mail/src/main/java/com/sun/mail/util/SocketFetcher.java Mon Nov 28 12:30:28 2011 -0800
@@ -237,7 +237,6 @@
                 socket.setSoTimeout(to);
         }
 
- configureSSLSocket(socket, props, prefix);
         return socket;
     }
 
@@ -292,6 +291,7 @@
         else
             socket.connect(new InetSocketAddress(host, port));
 
+ configureSSLSocket(socket, props, prefix);
         if (idCheck && socket instanceof SSLSocket)
             checkServerIdentity(host, (SSLSocket)socket);
         if (sf instanceof MailSSLSocketFactory) {
@@ -435,6 +435,7 @@
             }
 
             socket = ssf.createSocket(socket, host, port, true);
+ configureSSLSocket(socket, props, prefix);
             boolean idCheck = PropUtil.getBooleanProperty(props,
                                 prefix + ".ssl.checkserveridentity", false);
             if (idCheck)
@@ -449,7 +450,6 @@
                     }
                 }
             }
- configureSSLSocket(socket, props, prefix);
         } catch (Exception ex) {
             if (ex instanceof InvocationTargetException) {
                 Throwable t =


diff -r 50ff23a35feb -r f7d0272fb52d doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon Nov 28 12:30:28 2011 -0800
+++ b/doc/release/CHANGES.txt Mon Nov 28 16:33:42 2011 -0800
@@ -22,6 +22,7 @@
 K 4002 MultipartReport.setReport and setDeliveryStatus are broken
 K 4065 Wrong representation of CR/LF are appended to the attachment
 K 4296 SSL Re-Negotiation Problem
[truncated due to length]