commits@javamail.java.net

[javamail~mercurial:808] Exchange returns NIL instead of "" for empty parameter, causing NPE - bug

From: <shannon_at_java.net>
Date: Fri, 11 Dec 2015 22:23:00 +0000

Project: javamail
Repository: mercurial
Revision: 808
Author: shannon
Date: 2015-12-11 21:09:02 UTC
Link:

Log Message:
------------
Add IMAPFolder.FetchProfileItem.INTERNALDATE - bug 7094
Fix and exclude some FindBugs errors in gimap.
DurationFilter added isIdle method.
DurationFilter improve equals method.
LogManagerProperties relax parameter of parseDurationToMillis.
LogManagerProperties add missing javadoc throws.
DurationFilterTest add isIdle test.
DurationFilterTest add test timeouts.

(From Jason)
Exchange returns NIL instead of "" for empty parameter, causing NPE - bug 7104


Revisions:
----------
805
806
807
808


Modified Paths:
---------------
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
gimap/exclude.xml
gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java
gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java
mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java
mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java
mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java


Added Paths:
------------
mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java
mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java


Diffs:
------
diff -r 122c7b3a6fdb -r 1d1ebca326f3 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Wed Dec 02 13:30:36 2015 -0800
+++ b/doc/release/CHANGES.txt Fri Dec 04 14:21:10 2015 -0800
@@ -45,6 +45,7 @@
 K 7083 CollectorFormatter descending order data race
 K 7090 off-by-1 error in Response.readStringList causes early termination of
         parsing FETCH response
+K 7094 INTERNALDATE FetchProfile Item
 
 
                   CHANGES IN THE 1.5.4 RELEASE

diff -r 122c7b3a6fdb -r 1d1ebca326f3 mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Wed Dec 02 13:30:36 2015 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri Dec 04 14:21:10 2015 -0800
@@ -354,6 +354,27 @@
          */
         public static final FetchProfileItem MESSAGE =
                 new FetchProfileItem("MESSAGE");
+
+ /**
+ * INTERNALDATE is a fetch profile item that can be included in a
+ * <code>FetchProfile</code> during a fetch request to a Folder.
+ * This item indicates that the IMAP INTERNALDATE values
+ * (received date) of the messages in the specified
+ * range are desired to be prefetched. <p>
+ *
+ * An example of how a client uses this is below:
+ * <blockquote><pre>
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
+ * folder.fetch(msgs, fp);
+ *
+ * </pre></blockquote>
+ *
+ * @since JavaMail 1.5.5
+ */
+ public static final FetchProfileItem INTERNALDATE =
+ new FetchProfileItem("INTERNALDATE");
     }
 
     /**
@@ -1181,6 +1202,10 @@
             command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
             first = false;
         }
+ if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) {
+ command.append(first ? "INTERNALDATE" : " INTERNALDATE");
+ first = false;
+ }
 
         // if we're not fetching all headers, fetch individual headers
         String[] hdrs = null;

diff -r 122c7b3a6fdb -r 1d1ebca326f3 mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Wed Dec 02 13:30:36 2015 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Fri Dec 04 14:21:10 2015 -0800
@@ -1106,6 +1106,7 @@
         private boolean needHeaders = false;
         private boolean needSize = false;
         private boolean needMessage = false;
+ private boolean needRDate = false;
         private String[] hdrs = null;
         private Set<FetchItem> need = new HashSet<FetchItem>();
 
@@ -1134,6 +1135,8 @@
                 needSize = true;
             if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE))
                 needMessage = true;
+ if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE))
+ needRDate = true;
             hdrs = fp.getHeaderNames();
             for (int i = 0; i < fitems.length; i++) {
                 if (fp.contains(fitems[i].getFetchProfileItem()))
@@ -1161,6 +1164,8 @@
                 return true;
             if (needMessage && !m.bodyLoaded) // no message body
                 return true;
+ if (needRDate && m.receivedDate == null) // no received date
+ return true;
 
             // Is the desired header present ?
             for (int i = 0; i < hdrs.length; i++) {

diff -r 122c7b3a6fdb -r 1d1ebca326f3 mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java Fri Dec 04 14:21:10 2015 -0800
@@ -0,0 +1,225 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2009-2015 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
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.HashSet;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.FetchProfile;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAP FetchProfile items.
+ */
+public final class IMAPFetchProfileTest {
+
+ // timeout the test in case of deadlock
+ @Rule
+ public Timeout deadlockTimeout = Timeout.seconds(20);
+
+ private static final String RDATE = "23-Jun-2004 06:26:26 -0700";
+ private static final String ENVELOPE =
+ "(\"Wed, 23 Jun 2004 18:56:42 +0530\" \"test\" " +
+ "((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+ "((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+ "((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+ "((NIL NIL \"testuser\" \"example.com\")) NIL NIL NIL " +
+ "\"<40D98512.9040803_at_example.com>\")";
+
+ public static interface IMAPTest {
+ public void test(Folder folder, IMAPHandlerFetch handler)
+ throws MessagingException;
+ }
+
+ @Test
+ public void testINTERNALDATEFetch() {
+ testWithHandler(
+ new IMAPTest() {
+ public void test(Folder folder, IMAPHandlerFetch handler)
+ throws MessagingException {
+ FetchProfile fp = new FetchProfile();
+ fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
+ Message m = folder.getMessage(1);
+ folder.fetch(new Message[] { m }, fp);
+ assertTrue(handler.saw("INTERNALDATE"));
+ handler.reset();
+ assertTrue(m.getReceivedDate() != null);
+ assertFalse(handler.saw("INTERNALDATE"));
+ }
+ },
+ new IMAPHandlerFetch() {
+ @Override
+ public void fetch(String line) throws IOException {
+ if (line.indexOf("INTERNALDATE") >= 0)
+ saw.add("INTERNALDATE");
+ untagged("1 FETCH (INTERNALDATE \"" + RDATE + "\")");
+ ok();
+ }
+ });
+ }
+
+ @Test
+ public void testINTERNALDATEFetchEnvelope() {
+ testWithHandler(
+ new IMAPTest() {
+ public void test(Folder folder, IMAPHandlerFetch handler)
+ throws MessagingException {
+ FetchProfile fp = new FetchProfile();
+ fp.add(FetchProfile.Item.ENVELOPE);
+ Message m = folder.getMessage(1);
+ folder.fetch(new Message[] { m }, fp);
+ assertTrue(handler.saw("INTERNALDATE"));
+ handler.reset();
+ assertTrue(m.getReceivedDate() != null);
+ assertFalse(handler.saw("INTERNALDATE"));
+ }
+ },
+ new IMAPHandlerFetch() {
+ @Override
+ public void fetch(String line) throws IOException {
+ if (line.indexOf("INTERNALDATE") >= 0)
+ saw.add("INTERNALDATE");
+ untagged("1 FETCH (INTERNALDATE \"" + RDATE + "\")");
+ ok();
+ }
+ });
+ }
+
+ @Test
+ public void testINTERNALDATENoFetch() {
+ testWithHandler(
+ new IMAPTest() {
+ public void test(Folder folder, IMAPHandlerFetch handler)
+ throws MessagingException {
+ Message m = folder.getMessage(1);
+ assertTrue(m.getReceivedDate() != null);
+ assertTrue(handler.saw("INTERNALDATE"));
+ }
+ },
+ new IMAPHandlerFetch() {
+ @Override
+ public void fetch(String line) throws IOException {
+ if (line.indexOf("INTERNALDATE") >= 0)
+ saw.add("INTERNALDATE");
+ untagged("1 FETCH (ENVELOPE " + ENVELOPE +
+ " INTERNALDATE \"" + RDATE + "\" RFC822.SIZE 0)");
+ ok();
+ }
+ });
+ }
+
+ public void testWithHandler(IMAPTest test, IMAPHandlerFetch handler) {
+ TestServer server = null;
+ try {
+ server = new TestServer(handler);
+ server.start();
+
+ final Properties properties = new Properties();
+ properties.setProperty("mail.imap.host", "localhost");
+ properties.setProperty("mail.imap.port", "" + server.getPort());
+ final Session session = Session.getInstance(properties);
+ //session.setDebug(true);
+
+ final Store store = session.getStore("imap");
+ Folder folder = null;
+ try {
+ store.connect("test", "test");
+ folder = store.getFolder("INBOX");
+ folder.open(Folder.READ_WRITE);
+ test.test(folder, handler);
+ } catch (Exception ex) {
+ System.out.println(ex);
+ //ex.printStackTrace();
+ fail(ex.toString());
+ } finally {
+ if (folder != null)
+ folder.close(false);
+ store.close();
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ if (server != null) {
+ server.quit();
+ }
+ }
+ }
+
+ /**
+ * Custom handler.
+ */
+ private static class IMAPHandlerFetch extends IMAPHandler {
+ // must be static because handler is cloned for each connection
+ protected static Set<String> saw = new HashSet<String>();
+
+ @Override
+ public void select() throws IOException {
+ numberOfMessages = 1;
+ super.select();
+ }
+
+ public boolean saw(String item) {
+ return saw.contains(item);
+ }
+
+ public void reset() {
+ saw.clear();
+ }
+ }
+}


diff -r 1d1ebca326f3 -r 0bab68015134 gimap/exclude.xml
--- a/gimap/exclude.xml Fri Dec 04 14:21:10 2015 -0800
+++ b/gimap/exclude.xml Fri Dec 04 17:13:41 2015 -0800
@@ -3,7 +3,7 @@
 
     DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
- Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2013-2015 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
@@ -54,4 +54,15 @@
         <Bug pattern="EI_EXPOSE_REP"/>
     </Match>
 
+ <!--
+ The setLabels method needs to be synchronized because of its
+ use of the protocol object. The getLabels method depends on
+ the getItem method to do the synchronization.
+ -->
+ <Match>
+ <Class name="com.sun.mail.gimap.GmailMessage"/>
+ <Method name="getLabels"/>
+ <Bug pattern="UG_SYNC_SET_UNSYNC_GET"/>
+ </Match>
+
 </FindBugsFilter>

diff -r 1d1ebca326f3 -r 0bab68015134 gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java
--- a/gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java Fri Dec 04 14:21:10 2015 -0800
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java Fri Dec 04 17:13:41 2015 -0800
@@ -145,7 +145,9 @@
 
         synchronized(messageCacheLock) {
             try {
- GmailProtocol p = (GmailProtocol)getProtocol();
+ IMAPProtocol ip = getProtocol();
+ assert ip instanceof GmailProtocol;
+ GmailProtocol p = (GmailProtocol)ip;
                 MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
                 if (ms == null)
                     throw new MessageRemovedException(

diff -r 1d1ebca326f3 -r 0bab68015134 gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java
--- a/gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java Fri Dec 04 14:21:10 2015 -0800
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java Fri Dec 04 17:13:41 2015 -0800
@@ -133,7 +133,9 @@
         // Acquire MessageCacheLock, to freeze seqnum.
         synchronized(getMessageCacheLock()) {
             try {
- GmailProtocol p = (GmailProtocol)getProtocol();
+ IMAPProtocol ip = getProtocol();
+ assert ip instanceof GmailProtocol;
+ GmailProtocol p = (GmailProtocol)ip;
                 checkExpunged(); // Insure that this message is not expunged
                 p.storeLabels(getSequenceNumber(), labels, set);
             } catch (ConnectionException cex) {


diff -r 0bab68015134 -r e27ca2fe9bbc mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java
--- a/mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java Fri Dec 04 17:13:41 2015 -0800
+++ b/mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java Wed Dec 09 15:33:34 2015 -0800
@@ -144,11 +144,11 @@
      */
     @Override
     public boolean equals(final Object obj) {
- if (obj == null) {
- return false;
+ if (this == obj) { //Avoid locks and deal with rapid state changes.
+ return true;
         }
 
- if (getClass() != obj.getClass()) {
+ if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
 
@@ -164,14 +164,14 @@
         final long c;
         final long p;
         final long s;
- synchronized (other) {
- c = other.count;
- p = other.peak;
- s = other.start;
+ synchronized (this) {
+ c = this.count;
+ p = this.peak;
+ s = this.start;
         }
 
- synchronized (this) {
- if (c != this.count || p != this.peak || s != this.start) {
+ synchronized (other) {
+ if (c != other.count || p != other.peak || s != other.start) {
                 return false;
             }
         }
@@ -179,6 +179,18 @@
     }
 
     /**
+ * Determines if this filter is able to accept the maximum number of log
+ * records for this instant in time. The result is a best-effort estimate
+ * and should be considered out of date as soon as it is produced. This
+ * method is designed for use in monitoring the state of this filter.
+ *
+ * @return true if the filter is idle; false otherwise.
+ */
+ public boolean isIdle() {
+ return test(0L, System.currentTimeMillis());
+ }
+
+ /**
      * Returns a hash code value for this filter.
      *
      * @return hash code for this filter.
@@ -212,36 +224,30 @@
      * @return true if the filter is not saturated; false otherwise.
      */
     public boolean isLoggable() {
- final long c;
- final long s;
- synchronized (this) {
- c = count;
- s = start;
- }
-
- final long millis = System.currentTimeMillis();
- if (c > 0L) { //If not saturated.
- if (c != records || (millis - s) >= duration) {
- return true;
- }
- } else { //Subtraction is used to deal with numeric overflow.
- if ((millis - s) >= 0L || c == 0L) {
- return true;
- }
- }
- return false;
+ return test(records, System.currentTimeMillis());
     }
 
     /**
- * Returns a string representation of this filter.
+ * Returns a string representation of this filter. The result is a
+ * best-effort estimate and should be considered out of date as soon as it
+ * is produced.
      *
      * @return a string representation of this filter.
      */
     @Override
     public String toString() {
+ boolean idle;
+ boolean loggable;
+ synchronized (this) {
+ final long millis = System.currentTimeMillis();
+ idle = test(0L, millis);
+ loggable = test(records, millis);
+ }
+
         return getClass().getName() + "{records=" + records
                 + ", duration=" + duration
- + ", loggable=" + isLoggable() + '}';
+ + ", idle=" + idle
+ + ", loggable=" + loggable + '}';
     }
 
     /**
@@ -263,6 +269,34 @@
     }
 
     /**
+ * Checks if this filter is not saturated or bellow a maximum rate.
+ *
+ * @param limit the number of records allowed to be under the rate.
+ * @param millis the current time in milliseconds.
+ * @return true if not saturated or bellow the rate.
+ */
+ private boolean test(final long limit, final long millis) {
+ assert limit >= 0L : limit;
+ final long c;
+ final long s;
+ synchronized (this) {
+ c = count;
+ s = start;
+ }
+
+ if (c > 0L) { //If not saturated.
+ if ((millis - s) >= duration || c < limit) {
+ return true;
+ }
+ } else { //Subtraction is used to deal with numeric overflow.
+ if ((millis - s) >= 0L || c == 0L) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
      * Determines if the record is loggable by time.
      *
      * @param millis the log record milliseconds.

diff -r 0bab68015134 -r e27ca2fe9bbc mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
--- a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Fri Dec 04 17:13:41 2015 -0800
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Wed Dec 09 15:33:34 2015 -0800
@@ -181,7 +181,8 @@
     /**
      * Check that the current context is trusted to modify the logging
      * configuration. This requires LoggingPermission("control").
- *
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have {_at_code LoggingPermission("control")}.
      * @since JavaMail 1.5.3
      */
     static void checkLogManagerAccess() {
@@ -211,7 +212,8 @@
      * Check that the current context is trusted to modify the logging
      * configuration when the LogManager is not present. This requires
      * LoggingPermission("control").
- *
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have {_at_code LoggingPermission("control")}.
      * @since JavaMail 1.5.3
      */
     private static void checkLoggingAccess() {
@@ -288,8 +290,8 @@
 
     /**
      * Used to parse an ISO-8601 duration format of {_at_code PnDTnHnMn.nS}.
- *
- * @param value ISO-8601 duration string.
+ *
+ * @param value an ISO-8601 duration character sequence.
      * @return the number of milliseconds parsed from the duration.
      * @throws ClassNotFoundException if the java.time classes are not present.
      * @throws IllegalAccessException if the method is inaccessible.
@@ -303,7 +305,7 @@
      * are not allowed.
      * @since JavaMail 1.5.5
      */
- static long parseDurationToMillis(final String value) throws Exception {
+ static long parseDurationToMillis(final CharSequence value) throws Exception {
         try {
             final Class<?> k = findClass("java.time.Duration");
             final Method parse = k.getMethod("parse", CharSequence.class);

diff -r 0bab68015134 -r e27ca2fe9bbc mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java Fri Dec 04 17:13:41 2015 -0800
+++ b/mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java Wed Dec 09 15:33:34 2015 -0800
@@ -118,7 +118,7 @@
         assertFalse(clone.isLoggable(r));
     }
 
- @Test
+ @Test(timeout = 15000)
     @SuppressWarnings("SleepWhileInLoop")
     public void testIsLoggableNow() throws Exception {
         final int records = 10;
@@ -159,6 +159,47 @@
         assertFalse(sf.isLoggable(r));
     }
 
+ @Test(timeout = 15000)
+ @SuppressWarnings("SleepWhileInLoop")
+ public void testIsIdleNow() throws Exception {
+ final int records = 10;
+ final int duration = 1000;
+ Level lvl = Level.INFO;
+ DurationFilter sf = new DurationFilter(records, duration);
+ LogRecord r = new LogRecord(lvl, "");
+ assertTrue(sf.isIdle());
+ assertTrue(sf.isLoggable(r));
+ assertFalse(sf.isIdle());
+
+ //Allow
+ for (int i = 1; i < records; i++) {
+ r = new LogRecord(lvl, "");
+ String msg = Integer.toString(i);
+ assertFalse(msg, sf.isIdle());
+ assertTrue(msg, sf.isLoggable(r));
+ }
+
+ assertFalse(sf.isIdle());
+ assertFalse(sf.isLoggable(r));
+
+ //Cool down and allow.
+ final long then = System.currentTimeMillis();
+ do {
+ Thread.sleep(duration + 100);
+ } while ((System.currentTimeMillis() - then) < duration);
+
+ assertTrue(sf.isIdle());
+ for (int i = 0; i < records; i++) {
+ r = new LogRecord(lvl, "");
+ String msg = Integer.toString(i);
+ assertTrue(msg, sf.isLoggable(r));
+ assertFalse(msg, sf.isIdle());
+ }
+
+ assertFalse(sf.isIdle());
+ assertFalse(sf.isLoggable(r));
+ }
+
     @Test
     public void testSaturation() {
         long millis = 0;
@@ -492,6 +533,8 @@
         assertTrue(two.equals(two));
         assertFalse(one.equals(two));
         assertFalse(two.equals(one));
+ assertFalse(one.equals((Object) null));
+ assertFalse(two.equals((Object) null));
     }
 
     @Test
@@ -524,6 +567,7 @@
         assertTrue(s.startsWith(f.getClass().getName()));
         assertTrue(s.contains("records="));
         assertTrue(s.contains("duration="));
+ assertTrue(s.contains("idle="));
         assertTrue(s.contains("loggable="));
     }
 


diff -r e27ca2fe9bbc -r 95dcfa94e06d doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Wed Dec 09 15:33:34 2015 -0800
+++ b/doc/release/CHANGES.txt Fri Dec 11 13:09:02 2015 -0800
@@ -46,6 +46,7 @@
 K 7090 off-by-1 error in Response.readStringList causes early termination of
         parsing FETCH response
 K 7094 INTERNALDATE FetchProfile Item
+K 7104 Exchange returns NIL instead of "" for empty parameter, causing NPE
 
 
                   CHANGES IN THE 1.5.4 RELEASE

diff -r e27ca2fe9bbc -r 95dcfa94e06d mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java Wed Dec 09 15:33:34 2015 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java Fri Dec 11 13:09:02 2015 -0800
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2015 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
@@ -419,6 +419,8 @@
                 String value = r.readString();
                 if (parseDebug)
                     System.out.println("DEBUG IMAP: parameter value " + value);
+ if (value == null) // work around buggy servers
+ value = "";
                 list.set(name, value);
             } while (r.readByte() != ')');
             list.combineSegments();

diff -r e27ca2fe9bbc -r 95dcfa94e06d mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java Fri Dec 11 13:09:02 2015 -0800
@@ -0,0 +1,71 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2012-2015 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
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package com.sun.mail.imap.protocol;
+
+import javax.mail.internet.ParameterList;
+
+import com.sun.mail.iap.Response;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Test;
+
+/**
+ * Test the BODYSTRUCTURE class.
+ */
+public class BODYSTRUCTURETest {
+ /**
+ * Test workaround for Exchange bug that returns NIL instead of ""
+ * for a parameter with an empty value (name="").
+ */
+ @Test
+ public void testExchangeEmptyParameterValueBug() throws Exception {
+ IMAPResponse response = new IMAPResponse(
+ "* 3 FETCH (BODYSTRUCTURE ((\"text\" \"plain\" (\"charset\" \"UTF-8\") " +
+ "NIL NIL \"quoted-printable\" 512 13 NIL (\"inline\" NIL) NIL NIL)" +
+ "(\"text\" \"html\" (\"charset\" \"UTF-8\") NIL NIL \"quoted-printable\" " +
+ "784 11 NIL (\"inline\" NIL) NIL NIL) \"alternative\" " +
+ "(\"boundary\" \"__139957996218379.example.com\" \"name\" NIL) NIL NIL))");
+ // here's the incorrect NIL that should be "" ............^
+ FetchResponse fr = new FetchResponse(response);
+ BODYSTRUCTURE bs = fr.getItem(BODYSTRUCTURE.class);
+ ParameterList p = bs.cParams;
+ assertNotNull(p.get("name"));
+ }
+}