commits@javamail.java.net

[javamail~mercurial:767] add support for IMAP login referrals (RFC 2221) - bug 6997

From: <shannon_at_java.net>
Date: Tue, 29 Sep 2015 20:54:32 +0000

Project: javamail
Repository: mercurial
Revision: 767
Author: shannon
Date: 2015-09-29 00:02:04 UTC
Link:

Log Message:
------------
improve synchronization in watch method; add isRunning method
MailHandler needs better support for stateful filters. - bug 6989
Rename the private fixUpXXX methods.

(From Jason)
update introductory text since bugs.sun.com is long gone
add support for IMAP login referrals (RFC 2221) - bug 6997


Revisions:
----------
764
765
766
767


Modified Paths:
---------------
mail/src/main/java/com/sun/mail/imap/IdleManager.java
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
javadoc/pom.xml
mail/src/main/java/com/sun/mail/imap/IMAPStore.java
mail/src/main/java/com/sun/mail/imap/package.html
mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java
mail/src/test/java/com/sun/mail/imap/IMAPHandler.java
mail/src/test/java/com/sun/mail/imap/IMAPSaslHandler.java


Added Paths:
------------
mail/src/main/java/com/sun/mail/imap/ReferralException.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
mail/src/test/java/com/sun/mail/imap/IMAPLoginReferralTest.java


Diffs:
------
diff -r ce73a959d6e4 -r 597d886ccafe mail/src/main/java/com/sun/mail/imap/IdleManager.java
--- a/mail/src/main/java/com/sun/mail/imap/IdleManager.java Tue Sep 15 15:00:38 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IdleManager.java Tue Sep 22 13:32:04 2015 -0700
@@ -139,6 +139,7 @@
     private Selector selector;
     private MailLogger logger;
     private volatile boolean die = false;
+ private volatile boolean running;
     private Queue<IMAPFolder> toWatch = new ConcurrentLinkedQueue<IMAPFolder>();
     private Queue<IMAPFolder> toAbort = new ConcurrentLinkedQueue<IMAPFolder>();
 
@@ -159,8 +160,10 @@
             public void run() {
                 logger.fine("IdleManager select starting");
                 try {
+ running = true;
                     select();
                 } finally {
+ running = false;
                     logger.fine("IdleManager select terminating");
                 }
             }
@@ -168,13 +171,27 @@
     }
 
     /**
+ * Is the IdleManager currently running? The IdleManager starts
+ * running when the Executor schedules its task. The IdleManager
+ * stops running after its task detects the stop request from the
+ * {_at_link #stop stop} method, or if it terminates abnormally due
+ * to an unexpected error.
+ *
+ * @return true if the IdleMaanger is running
+ * @since JavaMail 1.5.5
+ */
+ public boolean isRunning() {
+ return running;
+ }
+
+ /**
      * Watch the Folder for new messages and other events using the IMAP IDLE
      * command.
      *
      * @param folder the folder to watch
      * @exception MessagingException for errors related to the folder
      */
- public synchronized void watch(Folder folder)
+ public void watch(Folder folder)
                                 throws MessagingException {
         if (die) // XXX - should be IllegalStateException?
             throw new MessagingException("IdleManager is not running");
@@ -189,10 +206,29 @@
                                                         folderName(ifolder));
         // keep trying to start the IDLE command until we're successful.
         // may block if we're in the middle of aborting an IDLE command.
- while (!ifolder.startIdle(this))
- ;
- toWatch.add(ifolder);
- selector.wakeup();
+ int tries = 0;
+ while (!ifolder.startIdle(this)) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle failed for {0}",
+ folderName(ifolder));
+ tries++;
+ }
+ if (logger.isLoggable(Level.FINEST)) {
+ if (tries > 0)
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle succeeded for {0}" +
+ " after " + tries + " tries",
+ folderName(ifolder));
+ else
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle succeeded for {0}",
+ folderName(ifolder));
+ }
+ synchronized (this) {
+ toWatch.add(ifolder);
+ selector.wakeup();
+ }
     }
 
     /**


diff -r 597d886ccafe -r c50bbbb0892e doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Tue Sep 22 13:32:04 2015 -0700
+++ b/doc/release/CHANGES.txt Wed Sep 23 11:11:48 2015 -0700
@@ -27,6 +27,7 @@
         mail.<protocol>.auth.<mechanism>.disable
 K 6966 add support for OAuth 2.0 without SASL
 K 6973 capability() command doesn't properly transform errors
+K 6989 MailHandler needs better support for stateful filters.
 
 
                   CHANGES IN THE 1.5.4 RELEASE

diff -r 597d886ccafe -r c50bbbb0892e mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
--- a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Tue Sep 22 13:32:04 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Wed Sep 23 11:11:48 2015 -0700
@@ -381,20 +381,21 @@
             = new GetAndSetContext(MailHandler.class);
     /**
      * A thread local mutex used to prevent logging loops.
- * The MUTEX has 3 states:
- * 1. MUTEX_RESET which is the null state.
+ * 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.
- * 3. MUTEX_REPORT when cycle of records is detected.
+ * 3. The index of the first filter to accept a log record.
+ * 4. MUTEX_REPORT when cycle of records is detected.
      */
- private static final ThreadLocal<Level> MUTEX = new ThreadLocal<Level>();
+ private static final ThreadLocal<Integer> MUTEX = new ThreadLocal<Integer>();
     /**
      * The marker object used to report a publishing state.
      */
- private static final Level MUTEX_PUBLISH = Level.ALL;
+ private static final Integer MUTEX_PUBLISH = -2;
     /**
- * The marker object used to report a error reporting state.
+ * The used for the error reporting state.
      */
- private static final Level MUTEX_REPORT = Level.OFF;
+ private static final Integer MUTEX_REPORT = -4;
     /**
      * Used to turn off security checks.
      */
@@ -419,6 +420,13 @@
      */
     private Session session;
     /**
+ * A mapping of log record to matching filter index. Negative one is used
+ * to track the body filter. Zero and greater is used to track the
+ * attachment parts. All indexes less than or equal to the matched value
+ * have already seen the given log record.
+ */
+ private int[] matched;
+ /**
      * Holds all of the log records that will be used to create the email.
      */
     private LogRecord[] data;
@@ -578,6 +586,7 @@
 
         Filter body = getFilter();
         if (body == null || body.isLoggable(record)) {
+ setMatchedPart(-1);
             return true;
         }
 
@@ -634,6 +643,8 @@
             }
 
             if (size < data.length) {
+ //assert data.length == matched.length;
+ matched[size] = getMatchedPart();
                 data[size] = record;
                 ++size; //Be nice to client compiler.
                 priority = isPushable(record);
@@ -662,7 +673,8 @@
      * @since JavaMail 1.4.6
      */
     private void reportUnPublishedError(LogRecord record) {
- if (MUTEX_PUBLISH.equals(MUTEX.get())) {
+ final Integer idx = MUTEX.get();
+ if (!MUTEX_REPORT.equals(idx)) {
             MUTEX.set(MUTEX_REPORT);
             try {
                 final String msg;
@@ -679,7 +691,7 @@
                         + Thread.currentThread());
                 reportError(msg, e, ErrorManager.WRITE_FAILURE);
             } finally {
- MUTEX.set(MUTEX_PUBLISH);
+ MUTEX.set(idx);
             }
         }
     }
@@ -710,6 +722,51 @@
     }
 
     /**
+ * This is used to get the filter index from when {_at_code isLoggable} and
+ * {_at_code isAttachmentLoggable} was invoked by {_at_code publish} method.
+ *
+ * @return the filter index or MUTEX_PUBLISH if unknown.
+ * @since JavaMail 1.5.5
+ * @throws NullPointerException if tryMutex was not called.
+ */
+ private int getMatchedPart() {
+ //assert Thread.holdsLock(this);
+ int idx = MUTEX.get();
+ if (idx >= readOnlyAttachmentFilters().length) {
+ idx = MUTEX_PUBLISH;
+ }
+ return idx;
+ }
+
+ /**
+ * This is used to record the filter index when {_at_code isLoggable} and
+ * {_at_code isAttachmentLoggable} was invoked by {_at_code publish} method.
+ *
+ * @param index the filter index.
+ * @since JavaMail 1.5.5
+ */
+ private void setMatchedPart(int index) {
+ if (MUTEX_PUBLISH.equals(MUTEX.get())) {
+ MUTEX.set(index);
+ }
+ }
+
+ /**
+ * Clear previous matches when the filters are modified and there are
+ * existing log records that were matched.
+ * @param index the lowest filter index to clear.
+ * @since JavaMail 1.5.5
+ */
+ private void clearMatches(int index) {
+ assert Thread.holdsLock(this);
+ for (int r = 0; r < size; ++r) {
+ if (matched[r] >= index) {
+ matched[r] = MUTEX_PUBLISH;
+ }
+ }
+ }
+
+ /**
      * A callback method for when this object is about to be placed into
      * commission. This contract is defined by the
      * {_at_code org.glassfish.hk2.api.PostConstruct} interface. If this class is
@@ -793,6 +850,7 @@
                 //Ensure not inside a push.
                 if (size == 0 && data.length != 1) {
                     this.data = new LogRecord[1];
+ this.matched = new int[this.data.length];
                 }
             }
         }
@@ -898,6 +956,9 @@
     public void setFilter(final Filter newFilter) {
         checkAccess();
         synchronized (this) { //Wait for writeLogRecords.
+ if (newFilter != filter) {
+ clearMatches(-1);
+ }
             this.filter = newFilter;
         }
     }
@@ -1133,7 +1194,7 @@
                 throw new IllegalStateException();
             }
             this.auth = auth;
- settings = fixUpSession();
+ settings = updateSession();
         }
         verifySettings(settings);
     }
@@ -1167,7 +1228,7 @@
                 throw new IllegalStateException();
             }
             this.mailProps = props;
- settings = fixUpSession();
+ settings = updateSession();
         }
         verifySettings(settings);
     }
@@ -1220,6 +1281,15 @@
             if (isWriting) {
                 throw new IllegalStateException();
             }
+
+ if (size != 0) {
+ for (int i = 0; i < filters.length; ++i) {
+ if (filters[i] != attachmentFilters[i]) {
+ clearMatches(i);
+ break;
+ }
+ }
+ }
             this.attachmentFilters = filters;
         }
     }
@@ -1269,8 +1339,8 @@
             }
 
             this.attachmentFormatters = formatters;
- this.fixUpAttachmentFilters();
- this.fixUpAttachmentNames();
+ this.alignAttachmentFilters();
+ this.alignAttachmentNames();
         }
     }
 
@@ -1751,16 +1821,18 @@
     }
 
     /**
- * Expand or shrink the attachment name formatters.
- * @return true if fixed.
+ * Expand or shrink the attachment name formatters with the attachment
+ * formatters.
+ * @return true if size was changed.
      */
- private boolean fixUpAttachmentNames() {
+ private boolean alignAttachmentNames() {
         assert Thread.holdsLock(this);
         boolean fixed = false;
         final int expect = this.attachmentFormatters.length;
         final int current = this.attachmentNames.length;
         if (current != expect) {
- this.attachmentNames = copyOf(attachmentNames, expect);
+ this.attachmentNames = copyOf(attachmentNames, expect,
+ Formatter[].class);
             fixed = current != 0;
         }
 
@@ -1780,17 +1852,19 @@
     }
 
     /**
- * Expand or shrink the attachment filters.
- * @return true if fixed.
+ * Expand or shrink the attachment filters with the attachment formatters.
+ * @return true if the size was changed.
      */
- private boolean fixUpAttachmentFilters() {
+ private boolean alignAttachmentFilters() {
         assert Thread.holdsLock(this);
 
         boolean fixed = false;
         final int expect = this.attachmentFormatters.length;
         final int current = this.attachmentFilters.length;
         if (current != expect) {
- this.attachmentFilters = copyOf(attachmentFilters, expect);
+ this.attachmentFilters = copyOf(attachmentFilters, expect,
+ Filter[].class);
+ clearMatches(current);
             fixed = current != 0;
 
             //Array elements default to null so skip filling if body filter
@@ -1813,14 +1887,15 @@
 
     /**
      * Copies the given array. Can be removed when Java Mail requires Java 1.6.
- * @param <T> the class of the objects in the array.
      * @param a the original array.
      * @param len the new size.
      * @return new copy
+ * @since JavaMail 1.5.5
      */
- @SuppressWarnings("unchecked")
- private static <T> T[] copyOf(final T[] a, final int len) {
- return (T[]) copyOf(a, len, a.getClass());
+ private static int[] copyOf(final int[] a, final int len) {
+ final int[] copy = new int[len];
+ System.arraycopy(a, 0, copy, 0, Math.min(len, a.length));
+ return copy;
     }
 
     /**
@@ -1864,7 +1939,8 @@
             newCapacity = capacity;
         }
         assert len != capacity : len;
- this.data = copyOf(data, newCapacity);
+ this.data = copyOf(data, newCapacity, LogRecord[].class);
+ this.matched = copyOf(matched, newCapacity);
     }
 
     /**
@@ -2110,13 +2186,13 @@
             }
 
             this.attachmentFilters = a;
- if (fixUpAttachmentFilters()) {
+ if (alignAttachmentFilters()) {
                 reportError("Attachment filters.",
                         attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
             }
         } else {
             this.attachmentFilters = emptyFilterArray();
- fixUpAttachmentFilters();
+ alignAttachmentFilters();
         }
     }
 
@@ -2204,13 +2280,13 @@
             }
 
             this.attachmentNames = a;
- if (fixUpAttachmentNames()) { //Any null indexes are repaired.
+ if (alignAttachmentNames()) { //Any null indexes are repaired.
                 reportError("Attachment names.",
                         attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
             }
         } else {
             this.attachmentNames = emptyFormatterArray();
- fixUpAttachmentNames();
+ alignAttachmentNames();
         }
     }
 
@@ -2309,6 +2385,7 @@
         }
 
         this.data = new LogRecord[1];
+ this.matched = new int[this.data.length];
     }
 
     /**
@@ -2520,6 +2597,7 @@
         for (int i = 0; i < filters.length; ++i) {
             final Filter f = filters[i];
             if (f == null || f.isLoggable(record)) {
+ setMatchedPart(i);
                 return true;
             }
         }
@@ -2531,6 +2609,7 @@
      * <tt>LogRecord</tt> into its internal buffer.
      * @param record a <tt>LogRecord</tt>
      * @return true if the <tt>LogRecord</tt> triggers an email push.
+ * @throws NullPointerException if tryMutex was not called.
      */
     private boolean isPushable(final LogRecord record) {
         assert Thread.holdsLock(this);
@@ -2540,7 +2619,17 @@
         }
 
         final Filter push = getPushFilter();
- return push == null || push.isLoggable(record);
+ if (push == null) {
+ return true;
+ }
+
+ final int match = getMatchedPart();
+ if ((match == -1 && getFilter() == push)
+ || (match >= 0 && attachmentFilters[match] == push)) {
+ return true;
+ } else {
+ return push.isLoggable(record);
+ }
     }
 
     /**
@@ -2688,13 +2777,16 @@
         Locale lastLocale = null;
         for (int ix = 0; ix < size; ++ix) {
             boolean formatted = false;
+ final int match = matched[ix];
             final LogRecord r = data[ix];
             data[ix] = null; //Clear while formatting.
 
             final Locale locale = localeFor(r);
             appendSubject(msg, format(subjectFormatter, r));
-
- if (bodyFilter == null || bodyFilter.isLoggable(r)) {
+ Filter lmf = null; //Identity of last matched filter.
+ if (bodyFilter == null || match == -1 || parts.length == 0
+ || (match < -1 && bodyFilter.isLoggable(r))) {
+ lmf = bodyFilter;
                 if (buf == null) {
                     buf = new StringBuilder();
                     final String head = head(bodyFormat);
@@ -2709,8 +2801,14 @@
             }
 
             for (int i = 0; i < parts.length; ++i) {
+ //A match index less than the attachment index means that
+ //the filter has not seen this record.
                 final Filter af = attachmentFilters[i];
- if (af == null || af.isLoggable(r)) {
+ if (af == null || lmf == af || match == i
+ || (match < i && af.isLoggable(r))) {
+ if (lmf == null && af != null) {
+ lmf = af;
+ }
                     if (parts[i] == null) {
                         parts[i] = createBodyPart(i);
                         buffers[i] = new StringBuilder();
@@ -2917,7 +3015,7 @@
                 } catch (final SendFailedException sfe) {
                     Address[] recip = sfe.getInvalidAddresses();
                     if (recip != null && recip.length != 0) {
- fixUpContent(abort, verify, sfe);
+ setErrorContent(abort, verify, sfe);
                         reportError(abort, sfe, ErrorManager.OPEN_FAILURE);
                     }
 
@@ -2927,13 +3025,13 @@
                     }
                 } catch (final MessagingException ME) {
                     if (!isMissingContent(abort, ME)) {
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
                         reportError(abort, ME, ErrorManager.OPEN_FAILURE);
                     }
                 }
 
                 if (closed != null) {
- fixUpContent(abort, verify, closed);
+ setErrorContent(abort, verify, closed);
                     reportError(abort, closed, ErrorManager.CLOSE_FAILURE);
                 }
             } else {
@@ -2956,12 +3054,12 @@
                     } catch (final IOException IOE) {
                         MessagingException ME =
                                 new MessagingException(msg, IOE);
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
                         reportError(abort, ME, ErrorManager.OPEN_FAILURE);
                     } catch (final RuntimeException RE) {
                         MessagingException ME =
                                 new MessagingException(msg, RE);
- fixUpContent(abort, verify, RE);
+ setErrorContent(abort, verify, RE);
                         reportError(abort, ME, ErrorManager.OPEN_FAILURE);
                     }
                 }
@@ -2975,11 +3073,11 @@
                     verifyHost(local);
                 } catch (final IOException IOE) {
                     MessagingException ME = new MessagingException(msg, IOE);
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
                     reportError(abort, ME, ErrorManager.OPEN_FAILURE);
                 } catch (final RuntimeException RE) {
                     MessagingException ME = new MessagingException(msg, RE);
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
                     reportError(abort, ME, ErrorManager.OPEN_FAILURE);
                 }
 
@@ -3002,7 +3100,7 @@
                     }
                 } catch (final IOException IOE) {
                     MessagingException ME = new MessagingException(msg, IOE);
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
                     reportError(abort, ME, ErrorManager.FORMAT_FAILURE);
                 }
             }
@@ -3043,10 +3141,10 @@
             //Verify reply-to addresses.
             verifyAddresses(abort.getReplyTo());
         } catch (final RuntimeException RE) {
- fixUpContent(abort, verify, RE);
+ setErrorContent(abort, verify, RE);
             reportError(abort, RE, ErrorManager.OPEN_FAILURE);
         } catch (final Exception ME) {
- fixUpContent(abort, verify, ME);
+ setErrorContent(abort, verify, ME);
             reportError(abort, ME, ErrorManager.OPEN_FAILURE);
         }
     }
@@ -3100,7 +3198,7 @@
     private void reportUnexpectedSend(MimeMessage msg, String verify, Exception cause) {
         final MessagingException write = new MessagingException(
                 "An empty message was sent.", cause);
- fixUpContent(msg, verify, write);
+ setErrorContent(msg, verify, write);
         reportError(msg, write, ErrorManager.OPEN_FAILURE);
     }
 
@@ -3113,7 +3211,7 @@
      * @param t the throwable or null.
      * @since JavaMail 1.4.5
      */
- private void fixUpContent(MimeMessage msg, String verify, Throwable t) {
+ private void setErrorContent(MimeMessage msg, String verify, Throwable t) {
         try { //Add content so toRawString doesn't fail.
             final MimeBodyPart body;
             final String subjectType;
@@ -3148,7 +3246,7 @@
      * mail properties or authenticator.
      * @return the current session or null if no verify is required.
      */
- private Session fixUpSession() {
+ private Session updateSession() {
         assert Thread.holdsLock(this);
         final Session settings;
         if (mailProps.getProperty("verify") != null) {

diff -r 597d886ccafe -r c50bbbb0892e mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Tue Sep 22 13:32:04 2015 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java Wed Sep 23 11:11:48 2015 -0700
@@ -337,8 +337,8 @@
     @Test
     public void testWebappClassLoaderFieldNames() throws Exception {
         /**
- * Test that the MailHandler is using field types from the
- * java.* or javax.* packages only.
+ * Test that the MailHandler is using field types from the java.* or
+ * javax.* packages only.
          */
         testWebappClassLoaderFieldNames(MailHandler.class);
     }
@@ -346,9 +346,9 @@
     private void testWebappClassLoaderFieldNames(Class<?> c) throws Exception {
         /**
          * WebappClassLoader.clearReferencesStaticFinal() method will ignore
- * fields that have type names that start with 'java.' or 'javax.'.
- * The MailHandler conforms to this rule so it doesn't become a target
- * for the WebappClassLoader.
+ * fields that have type names that start with 'java.' or 'javax.'. The
+ * MailHandler conforms to this rule so it doesn't become a target for
+ * the WebappClassLoader.
          */
         for (Field f : c.getDeclaredFields()) {
             Class<?> k = f.getType();
@@ -358,11 +358,11 @@
 
             /**
              * The WebappClassLoader ignores primitives, non-static, and
- * synthetic fields. For the MailHandler, the test is stricter than
- * what the WebappClassLoader actually clears. This restricts the
+ * synthetic fields. For the MailHandler, the test is stricter than
+ * what the WebappClassLoader actually clears. This restricts the
              * MailHandler to standard field types for both static and
              * non-static fields and named static inner class to avoid synthetic
- * fields. The idea is to try to stay forward compatible with
+ * fields. The idea is to try to stay forward compatible with
              * WebappClassLoader.
              */
             if (!k.isPrimitive() && !k.getName().startsWith("java.")
@@ -1155,7 +1155,7 @@
     }
 
     @Test
- public void testFixUpEmptyFilter() throws Exception {
+ public void testAlignEmptyFilter() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
@@ -1170,7 +1170,7 @@
     }
 
     @Test
- public void testFixUpEmptyNames() throws Exception {
+ public void testAlignEmptyNames() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
@@ -1185,7 +1185,7 @@
     }
 
     @Test
- public void testFixUpEmptyFilterAndNames() throws Exception {
+ public void testAlignEmptyFilterAndNames() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
@@ -1199,7 +1199,7 @@
     }
 
     @Test
- public void testFixUpErrorFilter() throws Exception {
+ public void testAlignErrorFilter() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"),
@@ -1215,7 +1215,7 @@
     }
 
     @Test
- public void testFixUpErrorNames() throws Exception {
+ public void testAlignErrorNames() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
@@ -1230,7 +1230,7 @@
     }
 
     @Test
- public void testFixUpErrorFilterAndNames() throws Exception {
+ public void testAlignErrorFilterAndNames() throws Exception {
         String p = MailHandler.class.getName();
         Properties props = createInitProperties(p);
         props.put(p.concat(".attachment.formatters"),
@@ -1351,6 +1351,194 @@
     }
 
     @Test
+ public void testStatefulFilter() {
+ MailHandler h = new MailHandler();
+ h.setMailProperties(createInitProperties(""));
+ InternalErrorManager em = new FlushErrorManager(h);
+ h.setErrorManager(em);
+ CountingFilter cf = new CountingFilter();
+ h.setFilter(cf);
+ int MAX_RECORDS = 100;
+ for (int i = 0; i < MAX_RECORDS; i++) {
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ h.publish(r);
+ }
+ h.close();
+ assertEquals(MAX_RECORDS, cf.count);
+ for (Exception exception : em.exceptions) {
+ if (!isConnectOrTimeout(exception)) {
+ dump(exception);
+ fail(String.valueOf(exception));
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ }
+
+ @Test
+ public void testStatefulAttachmentFilter() {
+ MailHandler h = new MailHandler();
+ h.setMailProperties(createInitProperties(""));
+ InternalErrorManager em = new FlushErrorManager(h);
+ h.setErrorManager(em);
+ CountingFilter negativeOne = new CountingFilter(BooleanFilter.FALSE);
+ h.setFilter(negativeOne);
+ h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter(),
+ new SimpleFormatter());
+ CountingFilter one = new CountingFilter(BooleanFilter.FALSE);
+ CountingFilter two = new CountingFilter();
+ h.setAttachmentFilters(BooleanFilter.FALSE, one, two);
+ int MAX_RECORDS = 100;
+ for (int i = 0; i < MAX_RECORDS; i++) {
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ h.publish(r);
+ }
+ h.close();
+
+ assertEquals(MAX_RECORDS, negativeOne.count);
+ assertEquals(MAX_RECORDS, one.count);
+ assertEquals(MAX_RECORDS, two.count);
+ for (Exception exception : em.exceptions) {
+ if (!isConnectOrTimeout(exception)) {
+ dump(exception);
+ fail(String.valueOf(exception));
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ }
+
+ @Test
+ public void testStatefulInternAttachmentFilter() {
+ testStatefulAttachmentFilter(false);
+ }
+
+ private void testStatefulAttachmentFilter(boolean clear) {
+ MailHandler h = new MailHandler();
+ h.setMailProperties(createInitProperties(""));
+ InternalErrorManager em = new FlushErrorManager(h);
+ h.setErrorManager(em);
+ CountingFilter cf = new CountingFilter(BooleanFilter.TRUE);
+ h.setFilter(cf);
+ h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter());
+ CountingFilter one = new CountingFilter();
+ h.setAttachmentFilters(cf, one);
+ int MAX_RECORDS = 100;
+ for (int i = 0; i < MAX_RECORDS; i++) {
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ h.publish(r);
+ }
+
+ if (clear) {
+ Filter[] af = h.getAttachmentFilters();
+ h.setAttachmentFormatters();
+ h.setAttachmentFormatters(new SimpleFormatter(),
+ new SimpleFormatter());
+ h.setAttachmentFilters(af);
+ }
+ h.close();
+
+ assertEquals(MAX_RECORDS, cf.count);
+ assertEquals(MAX_RECORDS, one.count);
+ for (Exception exception : em.exceptions) {
+ if (!isConnectOrTimeout(exception)) {
+ dump(exception);
+ fail(String.valueOf(exception));
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ }
+
+ @Test
+ public void testStatefulAttachmentFilterClearMatches() {
+ testStatefulAttachmentFilter(true);
+ }
+
+ @Test
+ public void testStatefulPushFilter() {
+ MailHandler h = new MailHandler();
+ h.setMailProperties(createInitProperties(""));
+ InternalErrorManager em = new PushErrorManager(h);
+ h.setErrorManager(em);
+ CountingFilter cf = new CountingFilter();
+ h.setFilter(cf);
+ h.setPushLevel(Level.ALL);
+ h.setPushFilter(cf);
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ h.publish(r);
+ h.close();
+ assertEquals(1, cf.count);
+ for (Exception exception : em.exceptions) {
+ if (!isConnectOrTimeout(exception)) {
+ dump(exception);
+ fail(String.valueOf(exception));
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ }
+
+ private void testStatefulPushAttachmentFilter(boolean clear) {
+ final MailHandler h = new MailHandler();
+ h.setMailProperties(createInitProperties(""));
+ final InternalErrorManager em = new PushErrorManager(h);
+ h.setErrorManager(em);
+ final CountingFilter cf = new CountingFilter(BooleanFilter.FALSE);
+ h.setFilter(cf);
+ h.setPushLevel(Level.ALL);
+ final CountingFilter push = new CountingFilter();
+ h.setPushFilter(push);
+ h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter(), new SimpleFormatter());
+ final CountingFilter one = new CountingFilter(BooleanFilter.FALSE);
+ final CountingFilter two = new CountingFilter(BooleanFilter.FALSE);
+
+ if (clear) {
+ h.setAttachmentFilters(one, two,
+ new Filter() {
+ public boolean isLoggable(LogRecord record) {
+ h.setAttachmentFormatters(new SimpleFormatter(),
+ new SimpleFormatter());
+ h.setAttachmentFilters(one, push);
+ return push.isLoggable(record);
+ }
+
+ });
+ } else {
+ h.setAttachmentFilters(one, two, push);
+ }
+
+ LogRecord r = new LogRecord(Level.SEVERE, "");
+ h.publish(r);
+ h.close();
+
+ if (clear) {
+ assertEquals(2, cf.count);
+ assertEquals(2, one.count);
+ assertEquals(1, two.count);
+ assertEquals(3, push.count);
+ } else {
+ assertEquals(1, cf.count);
+ assertEquals(1, one.count);
+ assertEquals(1, two.count);
+ assertEquals(1, push.count);
+ }
+ for (Exception exception : em.exceptions) {
+ if (!isConnectOrTimeout(exception)) {
+ dump(exception);
+ fail(String.valueOf(exception));
+ }
+ }
+ assertFalse(em.exceptions.isEmpty());
+ }
+
+ @Test
+ public void testStatefulPushAttachmentFilter() {
+ testStatefulPushAttachmentFilter(false);
+ }
+
+ @Test
+ public void testStatefulPushFilterClearMatches() {
+ testStatefulPushAttachmentFilter(true);
+ }
+
+ @Test
     public void testPushInsidePush() {
         final Level[] lvls = getAllLevels();
 
@@ -3821,6 +4009,8 @@
         instance.setLevel(Level.ALL);
         instance.setPushLevel(Level.OFF);
         instance.setPushFilter((Filter) null);
+ instance.setAttachmentFormatters(new SimpleFormatter());
+ instance.setAttachmentFilters(BooleanFilter.FALSE);
         InternalErrorManager em = new InternalErrorManager();
         instance.setErrorManager(em);
 
@@ -3848,13 +4038,14 @@
 
     @Test
     public void testFilterFlipFlop() {
- MailHandler instance = new MailHandler(10);
+ MailHandler instance = new MailHandlerOverride(10);
         instance.setMailProperties(createInitProperties(""));
         instance.setLevel(Level.ALL);
         instance.setPushLevel(Level.OFF);
         instance.setPushFilter((Filter) null);
         FlipFlopFilter badFilter = new FlipFlopFilter();
         instance.setFilter(badFilter);
+ instance.setAttachmentFormatters(new SimpleFormatter());
 
         InternalErrorManager em = new InternalErrorManager();
         instance.setErrorManager(em);
@@ -4322,7 +4513,6 @@
             assertEquals(1, h.getAttachmentFormatters().length);
             h.setAttachmentNames(new String[]{"error.txt"});
 
-
             assertEquals(1, h.getAttachmentFormatters().length);
             h.setAttachmentNames(new Formatter[]{new ThrowFormatter()});
 
@@ -6855,6 +7045,25 @@
         }
     }
 
+ public static final class CountingFilter implements Filter {
+
+ private final Filter result;
+ int count;
+
+ public CountingFilter() {
+ this.result = BooleanFilter.TRUE;
+ }
+
+ public CountingFilter(Filter f) {
+ this.result = f;
+ }
+
+ public boolean isLoggable(LogRecord r) {
+ ++count;
+ return result.isLoggable(r);
+ }
+ }
+
     public static final class CountingFormatter extends Formatter {
 
         int head;
@@ -7037,7 +7246,7 @@
                 }
                 for (StackTraceElement e : stack) {
                     if (Handler.class.getName().equals(e.getClassName())) {
- throw se;
+ throw se;
                     }
                 }
             }
@@ -7659,6 +7868,43 @@
         }
     }
 
+ public final static class MailHandlerOverride extends MailHandler {
+
+ public MailHandlerOverride() {
+ super();
+ }
+
+ public MailHandlerOverride(Properties props) {
+ super(props);
+ }
+
+ public MailHandlerOverride(int capacity) {
+ super(capacity);
+ }
+
+ public boolean isLoggable(LogRecord record) {
+ int levelValue = getLevel().intValue();
+ if (record.getLevel().intValue() < levelValue
+ || levelValue == Level.OFF.intValue()) {
+ return false;
+ }
+
+ Filter body = getFilter();
+ if (body == null || body.isLoggable(record)) {
+ return true;
+ }
+
+ final Filter[] filters = this.getAttachmentFilters();
+ for (int i = 0; i < filters.length; ++i) {
+ final Filter f = filters[i];
+ if (f == null || f.isLoggable(record)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
     private final static class ClassLoaderSecurityManager extends SecurityManager {
 
         volatile boolean secure = false;


diff -r c50bbbb0892e -r 1da5e6e0cadb doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Wed Sep 23 11:11:48 2015 -0700
+++ b/doc/release/CHANGES.txt Wed Sep 23 11:16:02 2015 -0700
@@ -1,17 +1,18 @@
-You can find more information about each bug number by visiting the Sun
+You can find more information about each bug number by visiting the
 Bug Database and looking up each bug you're interested in.
 
- http://bugs.sun.com
+Bug IDs that start with "K" can be found in the Kenai Bugzilla
+(after removing the "K"):
+
+ https://kenai.com/bugzilla/
 
 Bug IDs that start with "G" can be found in the GlassFish Issue Tracker
 (after removing the "G"):
 
         https://java.net/jira/browse/GLASSFISH
 
-Bug IDs that start with "K" can be found in the Kenai Bugzilla
-(after removing the "K"):
-
- https://kenai.com/bugzilla/
+Seven digit bug numbers are from the old Sun bug database, which is no
+longer available.
 
 
                   CHANGES IN THE 1.5.5 RELEASE


diff -r 1da5e6e0cadb -r e6d6f201be1a doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Wed Sep 23 11:16:02 2015 -0700
+++ b/doc/release/CHANGES.txt Mon Sep 28 17:02:04 2015 -0700
@@ -29,6 +29,7 @@
 K 6966 add support for OAuth 2.0 without SASL
 K 6973 capability() command doesn't properly transform errors
 K 6989 MailHandler needs better support for stateful filters.
+K 6997 add support for IMAP login referrals (RFC 2221)
 
 
                   CHANGES IN THE 1.5.4 RELEASE

diff -r 1da5e6e0cadb -r e6d6f201be1a javadoc/pom.xml
--- a/javadoc/pom.xml Wed Sep 23 11:16:02 2015 -0700
+++ b/javadoc/pom.xml Mon Sep 28 17:02:04 2015 -0700
@@ -97,6 +97,7 @@
                         com/sun/mail/imap/MessageVanishedEvent.java,
                         com/sun/mail/imap/ModifiedSinceTerm.java,
                         com/sun/mail/imap/IdleManager.java,
+ com/sun/mail/imap/ReferralException.java,
                         com/sun/mail/pop3/POP3Store.java,
                         com/sun/mail/pop3/POP3SSLStore.java,
                         com/sun/mail/pop3/POP3Folder.java,

diff -r 1da5e6e0cadb -r e6d6f201be1a mail/src/main/java/com/sun/mail/imap/IMAPStore.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Wed Sep 23 11:16:02 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Mon Sep 28 17:02:04 2015 -0700
@@ -693,6 +693,12 @@
                     pool.authenticatedConnections.addElement(protocol);
                 }
             }
+ } catch (IMAPReferralException ex) {
+ // login failure due to IMAP REFERRAL, close connection to server
+ if (protocol != null)
+ protocol.disconnect();
+ protocol = null;
+ throw new ReferralException(ex.getUrl(), ex.getMessage());
         } catch (CommandFailedException cex) {
             // login failure, close connection to server
             if (protocol != null)

diff -r 1da5e6e0cadb -r e6d6f201be1a mail/src/main/java/com/sun/mail/imap/ReferralException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/main/java/com/sun/mail/imap/ReferralException.java Mon Sep 28 17:02:04 2015 -0700
@@ -0,0 +1,89 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * 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
+ * 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 javax.mail.AuthenticationFailedException;
+
+/**
+ * A special kind of AuthenticationFailedException that indicates that
+ * the reason for the failure was an IMAP REFERRAL in the response code.
+ * See <a href="http://www.ietf.org/rfc/rfc2221.txt">RFC 2221</a> for details.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class ReferralException extends AuthenticationFailedException {
+
+ private String url;
+ private String text;
+
+ private static final long serialVersionUID = -3414063558596287683L;
+
+ /**
+ * Constructs an ReferralException with the specified URL and text.
+ *
+ * @param text the detail message
+ * @param url the URL
+ */
+ public ReferralException(String url, String text) {
+ super("[REFERRAL " + url + "] " + text);
+ this.url = url;
+ this.text = text;
+ }
+
+ /**
+ * Return the IMAP URL in the referral.
+ *
+ * @return the IMAP URL
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Return the text sent by the server along with the referral.
+ *
+ * @return the text
+ */
+ public String getText() {
+ return text;
+ }
+}

diff -r 1da5e6e0cadb -r e6d6f201be1a mail/src/main/java/com/sun/mail/imap/package.html
--- a/mail/src/main/java/com/sun/mail/imap/package.html Wed Sep 23 11:16:02 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/package.html Mon Sep 28 17:02:04 2015 -0700
@@ -208,6 +208,16 @@
         // search for messages delivered in the last day
         Message[] msgs = folder.search(new YoungerTerm(24 * 60 * 60));
 </PRE>
+<H4>LOGIN-REFERRAL Support</H4>
+<P>
+The IMAP LOGIN-REFERRAL extension
+(<A HREF="http://www.ietf.org/rfc/rfc2221.txt" TARGET="_top">RFC 2221</A>)
+is supported.
+If a login referral is received when connecting or when authentication fails, a
+{_at_link com.sun.mail.imap.ReferralException ReferralException} is thrown.
+A referral can also occur when login succeeds. By default, no exception is
+thrown in this case. To force an exception to be thrown and the authentication
+to fail, set the <code>mail.imap.referralexception</code> property to "true".
 <H4>Properties</H4>
 <P>
 The IMAP protocol provider supports the following properties,
@@ -792,6 +802,17 @@
 </TD>
 </TR>
 
+<TR>
+<TD>mail.imap.referralexception</TD>
+<TD>boolean</TD>
+<TD>
+If set to true and an IMAP login referral is returned when the autnetication
+succeeds, fail the connect request and throw a
+{_at_link com.sun.mail.imap.ReferralException ReferralException}.
+Defaults to false.
+</TD>
+</TR>
+
 </TABLE>
 <P>
 In general, applications should not need to use the classes in this

diff -r 1da5e6e0cadb -r e6d6f201be1a mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Wed Sep 23 11:16:02 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Mon Sep 28 17:02:04 2015 -0700
@@ -79,6 +79,7 @@
     
     private boolean connected = false; // did constructor succeed?
     private boolean rev1 = false; // REV1 server ?
+ private boolean referralException; // throw exception for IMAP REFERRAL?
     private boolean noauthdebug = true; // hide auth info in debug output
     private boolean authenticated; // authenticated?
     // WARNING: authenticated may be set to true in superclass
@@ -128,6 +129,8 @@
             this.name = name;
             noauthdebug =
                 !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
+ referralException = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".referralexception", false);
 
             if (capabilities == null)
                 capability();
@@ -291,8 +294,14 @@
      * @exception ProtocolException for protocol failures
      */
     protected void processGreeting(Response r) throws ProtocolException {
- super.processGreeting(r); // check if it's BAD
+ if (r.isBYE()) {
+ checkReferral(r); // may throw exception
+ throw new ConnectionException(this, r);
+ }
         if (r.isOK()) { // check if it's OK
+ // XXX - is a REFERRAL response code really allowed here?
+ if (referralException)
+ checkReferral(r);
             setCapabilities(r);
             return;
         }
@@ -309,6 +318,33 @@
     }
 
     /**
+ * Check for an IMAP login REFERRAL response code.
+ *
+ * @exception IMAPReferralException if REFERRAL response code found
+ * @see "RFC 2221"
+ */
+ private void checkReferral(Response r) throws IMAPReferralException {
+ String s = r.getRest(); // get the text after the response
+ if (s.startsWith("[")) { // a response code
+ int i = s.indexOf(' ');
+ if (i > 0 && s.substring(1, i).equalsIgnoreCase("REFERRAL")) {
+ String url, msg;
+ int j = s.indexOf(']');
+ if (j > 0) { // should always be true;
+ url = s.substring(i + 1, j);
+ msg = s.substring(j + 1).trim();
+ } else {
+ url = s.substring(i + 1);
+ msg = "";
+ }
+ if (r.isBYE())
+ disconnect();
+ throw new IMAPReferralException(msg, url);
+ }
+ }
+ }
+
+ /**
      * Returns <code>true</code> if the connection has been authenticated,
      * either due to a successful login, or due to a PREAUTH greeting response.
      *
@@ -454,7 +490,7 @@
         // Handle result of this command
         if (noauthdebug && isTracing())
             logger.fine("LOGIN command result: " + r[r.length-1]);
- handleResult(r[r.length-1]);
+ handleLoginResult(r[r.length-1]);
         // If the response includes a CAPABILITY response code, process it
         setCapabilities(r[r.length-1]);
         // if we get this far without an exception, we're authenticated
@@ -563,7 +599,7 @@
         // Handle the final OK, NO, BAD or BYE response
         if (noauthdebug && isTracing())
             logger.fine("AUTHENTICATE LOGIN command result: " + r);
- handleResult(r);
+ handleLoginResult(r);
         // If the response includes a CAPABILITY response code, process it
         setCapabilities(r);
         // if we get this far without an exception, we're authenticated
@@ -673,7 +709,7 @@
         // Handle the final OK, NO, BAD or BYE response
         if (noauthdebug && isTracing())
             logger.fine("AUTHENTICATE PLAIN command result: " + r);
- handleResult(r);
+ handleLoginResult(r);
         // If the response includes a CAPABILITY response code, process it
         setCapabilities(r);
         // if we get this far without an exception, we're authenticated
@@ -771,7 +807,7 @@
         // Handle the final OK, NO, BAD or BYE response
         if (noauthdebug && isTracing())
             logger.fine("AUTHENTICATE NTLM command result: " + r);
- handleResult(r);
+ handleLoginResult(r);
         // If the response includes a CAPABILITY response code, process it
         setCapabilities(r);
         // if we get this far without an exception, we're authenticated
@@ -864,7 +900,7 @@
         // Handle the final OK, NO, BAD or BYE response
         if (noauthdebug && isTracing())
             logger.fine("AUTHENTICATE XOAUTH2 command result: " + r);
- handleResult(r);
+ handleLoginResult(r);
         // If the response includes a CAPABILITY response code, process it
         setCapabilities(r);
         // if we get this far without an exception, we're authenticated
@@ -956,6 +992,21 @@
     }
 
     /**
+ * Handle the result response for a LOGIN or AUTHENTICATE command.
+ * Look for IMAP login REFERRAL.
+ *
+ * @param r the response
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.5
+ */
+ void handleLoginResult(Response r) throws ProtocolException {
+ if (hasCapability("LOGIN-REFERRALS") &&
+ (!r.isOK() || referralException))
+ checkReferral(r);
+ super.handleResult(r);
+ }
+
+ /**
      * PROXYAUTH Command.
      *
      * @param u the PROXYAUTH user name

diff -r 1da5e6e0cadb -r e6d6f201be1a mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java Mon Sep 28 17:02:04 2015 -0700
@@ -0,0 +1,77 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * 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
+ * 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 com.sun.mail.iap.ProtocolException;
+
+/**
+ * A ProtocolException that includes IMAP login referral information.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class IMAPRef
[truncated due to length]