commits@javamail.java.net

[javamail~mercurial:888] Fix a test and a sign extension bug that it detected with UTF-8 character

From: <shannon_at_java.net>
Date: Thu, 22 Dec 2016 19:30:26 +0000

Project: javamail
Repository: mercurial
Revision: 888
Author: shannon
Date: 2016-12-20 23:55:09 UTC
Link:

Log Message:
------------
Fix typo in javadocs.
MimeMultipart should throw ParseException for parsing errors - bug 6069
Handle NIL instead of empty string for message content - bug 8583
Support addressing i18n via RFC 6530/6531/6532 - bug 6216
Fix a test and a sign extension bug that it detected with UTF-8 characters.


Revisions:
----------
884
885
886
887
888


Modified Paths:
---------------
mail/src/main/java/javax/mail/Flags.java
doc/release/CHANGES.txt
doc/spec/JavaMail-1.6-changes.txt
mail/src/main/java/javax/mail/internet/MimeMultipart.java
mail/src/main/java/javax/mail/internet/ParseException.java
mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java
mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java
mail/src/main/java/com/sun/mail/iap/Argument.java
mail/src/main/java/com/sun/mail/iap/Protocol.java
mail/src/main/java/com/sun/mail/iap/Response.java
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
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/ID.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
mail/src/main/java/com/sun/mail/imap/protocol/Status.java
mail/src/main/java/com/sun/mail/smtp/DigestMD5.java
mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
mail/src/main/java/com/sun/mail/util/LineInputStream.java
mail/src/main/java/com/sun/mail/util/LineOutputStream.java
mail/src/main/java/javax/mail/internet/InternetAddress.java
mail/src/main/java/javax/mail/internet/InternetHeaders.java
mail/src/main/java/javax/mail/internet/MimeBodyPart.java
mail/src/main/java/javax/mail/internet/MimeMessage.java
mail/src/main/java/javax/mail/internet/MimeUtility.java
mail/src/main/java/javax/mail/internet/package.html
mail/src/test/java/com/sun/mail/imap/IMAPCloseFailureTest.java
mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java
mail/src/test/java/com/sun/mail/imap/IMAPHandler.java
mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java
mail/src/test/java/com/sun/mail/imap/IMAPIdleUntaggedResponseTest.java
mail/src/test/java/com/sun/mail/imap/IMAPMessageNumberOutOfRangeTest.java
mail/src/test/java/com/sun/mail/imap/IMAPUidExpungeTest.java
mail/src/test/java/com/sun/mail/imap/protocol/NamespacesTest.java
mail/src/test/java/com/sun/mail/pop3/POP3Handler.java
mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
mail/src/test/java/com/sun/mail/smtp/SMTPIOExceptionTest.java
mail/src/test/java/com/sun/mail/test/ProtocolHandler.java
mail/src/test/resources/javax/mail/internet/addrlist
mail/src/test/java/com/sun/mail/imap/protocol/StatusTest.java


Added Paths:
------------
mail/src/test/java/com/sun/mail/imap/IMAPFolderTest.java
mail/src/test/java/com/sun/mail/smtp/SMTPLoginHandler.java
mail/src/test/java/com/sun/mail/smtp/SMTPUtf8Test.java


Diffs:
------
diff -r fa2068436d55 -r 2eae165d123e mail/src/main/java/javax/mail/Flags.java
--- a/mail/src/main/java/javax/mail/Flags.java Fri Nov 18 16:41:17 2016 -0800
+++ b/mail/src/main/java/javax/mail/Flags.java Mon Nov 21 15:27:05 2016 -0800
@@ -153,7 +153,7 @@
 
         /**
          * This message is seen. This flag is implicitly set by the
- * implementation when the this Message's content is returned
+ * implementation when this Message's content is returned
          * to the client in some form. The <code>getInputStream</code>
          * and <code>getContent</code> methods on Message cause this
          * flag to be set. <p>


diff -r 2eae165d123e -r 87fa71ac4cd0 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon Nov 21 15:27:05 2016 -0800
+++ b/doc/release/CHANGES.txt Mon Nov 21 16:12:47 2016 -0800
@@ -19,6 +19,7 @@
                   ----------------------------
 The following bugs have been fixed in the 1.6.0 release.
 
+K 6069 MimeMultipart should throw ParseException for parsing errors
 K 6074 MimeMessage.updateHeaders should set the Date header if not already set
 K 6281 The UIDFolder interface should have a getter for UIDNEXT
 K 6568 MailHandler should choose a better default subject formatter.

diff -r 2eae165d123e -r 87fa71ac4cd0 doc/spec/JavaMail-1.6-changes.txt
--- a/doc/spec/JavaMail-1.6-changes.txt Mon Nov 21 15:27:05 2016 -0800
+++ b/doc/spec/JavaMail-1.6-changes.txt Mon Nov 21 16:12:47 2016 -0800
@@ -429,3 +429,62 @@
          * @since JavaMail 1.6
          */
         public static final long MAXUID = 0xffffffffL;
+
+
+===================================================================
+
+8. MimeMultipart should throw ParseException for parsing errors (6069)
+---------------------------------------------------------------
+
+ParseException indicates an error parsing MIME messages. In addition
+to applying to MIME headers, it seems reasonable to expand it to cover
+multipart message parsing. Since ParseException is a subclass of
+MessagingException, it merely reports more precisely the cause of the
+error.
+
+The description of ParseException is changed to:
+
+ * The exception thrown due to an error in parsing RFC822
+ * or MIME headers, including multipart bodies.
+
+MimeMultipart documents that ParseException can be thrown from an
+existing constructor and method:
+
+ /**
+ * Constructs a MimeMultipart object and its bodyparts from the
+ * given DataSource. <p>
+ *
+ * This constructor handles as a special case the situation where the
+ * given DataSource is a MultipartDataSource object. In this case, this
+ * method just invokes the superclass (i.e., Multipart) constructor
+ * that takes a MultipartDataSource object. <p>
+ *
+ * Otherwise, the DataSource is assumed to provide a MIME multipart
+ * byte stream. The <code>parsed</code> flag is set to false. When
+ * the data for the body parts are needed, the parser extracts the
+ * "boundary" parameter from the content type of this DataSource,
+ * skips the 'preamble' and reads bytes till the terminating
+ * boundary and creates MimeBodyParts for each part of the stream.
+ *
+ * @param ds DataSource, can be a MultipartDataSource
+ * @exception ParseException for failures parsing the message
+ * @exception MessagingException for other failures
+ */
+ public MimeMultipart(DataSource ds) throws MessagingException
+
+ ...
+
+ /**
+ * Parse the InputStream from our DataSource, constructing the
+ * appropriate MimeBodyParts. The <code>parsed</code> flag is
+ * set to true, and if true on entry nothing is done. This
+ * method is called by all other methods that need data for
+ * the body parts, to make sure the data has been parsed.
+ * The {_at_link #initializeProperties} method is called before
+ * parsing the data.
+ *
+ * @exception ParseException for failures parsing the message
+ * @exception MessagingException for other failures
+ * @since JavaMail 1.2
+ */
+ protected synchronized void parse() throws MessagingException

diff -r 2eae165d123e -r 87fa71ac4cd0 mail/src/main/java/javax/mail/internet/MimeMultipart.java
--- a/mail/src/main/java/javax/mail/internet/MimeMultipart.java Mon Nov 21 15:27:05 2016 -0800
+++ b/mail/src/main/java/javax/mail/internet/MimeMultipart.java Mon Nov 21 16:12:47 2016 -0800
@@ -263,7 +263,8 @@
      * boundary and creates MimeBodyParts for each part of the stream.
      *
      * @param ds DataSource, can be a MultipartDataSource
- * @exception MessagingException for failures
+ * @exception ParseException for failures parsing the message
+ * @exception MessagingException for other failures
      */
     public MimeMultipart(DataSource ds) throws MessagingException {
         super();
@@ -575,7 +576,8 @@
      * The {_at_link #initializeProperties} method is called before
      * parsing the data.
      *
- * @exception MessagingException for failures
+ * @exception ParseException for failures parsing the message
+ * @exception MessagingException for other failures
      * @since JavaMail 1.2
      */
     protected synchronized void parse() throws MessagingException {
@@ -609,7 +611,7 @@
         }
         if (boundary == null && !ignoreMissingBoundaryParameter &&
                 !ignoreExistingBoundaryParameter)
- throw new MessagingException("Missing boundary parameter");
+ throw new ParseException("Missing boundary parameter");
 
         try {
             // Skip and save the preamble
@@ -686,7 +688,7 @@
                 if (allowEmpty)
                     return;
                 else
- throw new MessagingException("Missing start boundary");
+ throw new ParseException("Missing start boundary");
             }
 
             // save individual boundary bytes for comparison later
@@ -738,7 +740,7 @@
                         ;
                     if (line == null) {
                         if (!ignoreMissingEndBoundary)
- throw new MessagingException(
+ throw new ParseException(
                                         "missing multipart end boundary");
                         // assume there's just a missing end boundary
                         complete = false;
@@ -786,7 +788,7 @@
                     if (inSize < bl) {
                         // hit EOF
                         if (!ignoreMissingEndBoundary)
- throw new MessagingException(
+ throw new ParseException(
                                         "missing multipart end boundary");
                         if (sin != null)
                             end = sin.getPosition();

diff -r 2eae165d123e -r 87fa71ac4cd0 mail/src/main/java/javax/mail/internet/ParseException.java
--- a/mail/src/main/java/javax/mail/internet/ParseException.java Mon Nov 21 15:27:05 2016 -0800
+++ b/mail/src/main/java/javax/mail/internet/ParseException.java Mon Nov 21 16:12:47 2016 -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-2016 Oracle and/or its affiliates. All rights reserved.
  *
  * The contents of this file are subject to the terms of either the GNU
  * General Public License Version 2 only ("GPL") or the Common Development
@@ -44,7 +44,7 @@
 
 /**
  * The exception thrown due to an error in parsing RFC822
- * or MIME headers
+ * or MIME headers, including multipart bodies.
  *
  * @author John Mani
  */


diff -r 87fa71ac4cd0 -r 377a55e66991 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon Nov 21 16:12:47 2016 -0800
+++ b/doc/release/CHANGES.txt Tue Nov 29 12:22:07 2016 -0800
@@ -43,6 +43,7 @@
 K 8540 MailHandler support for non-multipart messages
 K 8550 use of YoungerTerm/OlderTerm on server without WITHIN support fails
 K 8568 The UIDFolder interface should have a MAXUID constant
+K 8583 java.io.IOException: No content when reading msg with empty attachment
 
 
                   CHANGES IN THE 1.5.6 RELEASE

diff -r 87fa71ac4cd0 -r 377a55e66991 mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java Mon Nov 21 16:12:47 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java Tue Nov 29 12:22:07 2016 -0800
@@ -217,10 +217,15 @@
             }
         }
 
- if (is == null)
- throw new MessagingException("No content");
- else
- return is;
+ if (is == null) {
+ message.forceCheckExpunged(); // may throw MessageRemovedException
+ // nope, the server doesn't think it's expunged.
+ // can't tell the difference between the server returning NIL
+ // and some other error that caused null to be returned above,
+ // so we'll just assume it was empty content.
+ is = new ByteArrayInputStream(new byte[0]);
+ }
+ return is;
     }
 
     /**

diff -r 87fa71ac4cd0 -r 377a55e66991 mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java Mon Nov 21 16:12:47 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java Tue Nov 29 12:22:07 2016 -0800
@@ -164,7 +164,11 @@
 
             if (b == null || ((ba = b.getByteArray()) == null)) {
                 forceCheckExpunged();
- throw new IOException("No content");
+ // nope, the server doesn't think it's expunged.
+ // can't tell the difference between the server returning NIL
+ // and some other error that caused null to be returned above,
+ // so we'll just assume it was empty content.
+ ba = new ByteArray(0);
             }
         }
 

diff -r 87fa71ac4cd0 -r 377a55e66991 mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Mon Nov 21 16:12:47 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Tue Nov 29 12:22:07 2016 -0800
@@ -783,10 +783,15 @@
             }
         }
 
- if (is == null)
- throw new MessagingException("No content");
- else
- return is;
+ if (is == null) {
+ forceCheckExpunged(); // may throw MessageRemovedException
+ // nope, the server doesn't think it's expunged.
+ // can't tell the difference between the server returning NIL
+ // and some other error that caused null to be returned above,
+ // so we'll just assume it was empty content.
+ is = new ByteArrayInputStream(new byte[0]);
+ }
+ return is;
     }
 
     /**
@@ -882,9 +887,11 @@
 
         if (is == null) {
             forceCheckExpunged(); // may throw MessageRemovedException
- // nope, the server doesn't think it's expunged,
- // something else is wrong
- throw new MessagingException("No content");
+ // nope, the server doesn't think it's expunged.
+ // can't tell the difference between the server returning NIL
+ // and some other error that caused null to be returned above,
+ // so we'll just assume it was empty content.
+ is = new ByteArrayInputStream(new byte[0]);
         }
         return is;
     }

diff -r 87fa71ac4cd0 -r 377a55e66991 mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java
--- a/mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java Mon Nov 21 16:12:47 2016 -0800
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java Tue Nov 29 12:22:07 2016 -0800
@@ -50,6 +50,8 @@
 import javax.mail.Session;
 import javax.mail.Store;
 import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.BodyPart;
 import javax.mail.MessagingException;
 
 import com.sun.mail.test.TestServer;
@@ -80,9 +82,10 @@
         "((NIL NIL \"testuser\" \"example.com\")) NIL NIL NIL " +
         "\"<40D98512.9040803_at_example.com>\")";
 
- public static interface IMAPTest {
- public void test(Folder folder, IMAPHandlerMessage handler)
- throws MessagingException;
+ public static abstract class IMAPTest {
+ public void init(Properties props) { };
+ public abstract void test(Folder folder, IMAPHandlerMessage handler)
+ throws Exception;
     }
 
     /**
@@ -127,7 +130,85 @@
             });
     }
 
- public void testWithHandler(IMAPTest test, IMAPHandlerMessage handler) {
+ /**
+ * Test that returning NIL instead of an empty string for the content
+ * of the message works correctly.
+ */
+ @Test
+ public void testEmptyBody() {
+ testWithHandler(
+ new IMAPTest() {
+ @Override
+ public void init(Properties props) {
+ props.setProperty("mail.imap.partialfetch","false");
+ }
+
+ @Override
+ public void test(Folder folder, IMAPHandlerMessage handler)
+ throws MessagingException, IOException {
+ Message m = folder.getMessage(1);
+ String t = (String)m.getContent();
+ assertEquals("", t);
+ }
+ },
+ new IMAPHandlerMessage() {
+ @Override
+ public void fetch(String line) throws IOException {
+ if (line.indexOf("BODYSTRUCTURE") >= 0)
+ untagged("1 FETCH (BODYSTRUCTURE " +
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+ "NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL)" +
+ ")");
+ else if (line.indexOf("BODY[TEXT]") >= 0)
+ untagged("1 FETCH (BODY[TEXT] NIL " +
+ "FLAGS (\\Seen \\Recent))");
+ ok();
+ }
+ });
+ }
+
+ /**
+ * Test that returning NIL instead of an empty string for the content
+ * of an empty body part works correctly.
+ */
+ @Test
+ public void testEmptyBodyAttachment() {
+ testWithHandler(
+ new IMAPTest() {
+ @Override
+ public void init(Properties props) {
+ props.setProperty("mail.imap.partialfetch","false");
+ }
+
+ @Override
+ public void test(Folder folder, IMAPHandlerMessage handler)
+ throws MessagingException, IOException {
+ Message m = folder.getMessage(1);
+ Multipart mp = (Multipart)m.getContent();
+ BodyPart bp = mp.getBodyPart(1);
+ String t = (String)bp.getContent();
+ assertEquals("", t);
+ }
+ },
+ new IMAPHandlerMessage() {
+ @Override
+ public void fetch(String line) throws IOException {
+ if (line.indexOf("BODYSTRUCTURE") >= 0)
+ untagged("1 FETCH (BODYSTRUCTURE (" +
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+ "NIL NIL \"7bit\" 4 0 NIL NIL NIL NIL)" +
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+ "NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL)" +
+ " \"mixed\" (\"boundary\" \"----=_x\") NIL NIL))");
+ else if (line.indexOf("BODY[2]") >= 0)
+ untagged("1 FETCH (BODY[2] NIL " +
+ "FLAGS (\\Seen \\Recent))");
+ ok();
+ }
+ });
+ }
+
+ private void testWithHandler(IMAPTest test, IMAPHandlerMessage handler) {
         TestServer server = null;
         try {
             server = new TestServer(handler);
@@ -136,6 +217,7 @@
             final Properties properties = new Properties();
             properties.setProperty("mail.imap.host", "localhost");
             properties.setProperty("mail.imap.port", "" + server.getPort());
+ test.init(properties);
             final Session session = Session.getInstance(properties);
             //session.setDebug(true);
 


diff -r 377a55e66991 -r 3fbb3229820d doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Tue Nov 29 12:22:07 2016 -0800
+++ b/doc/release/CHANGES.txt Tue Dec 20 14:46:25 2016 -0800
@@ -21,6 +21,7 @@
 
 K 6069 MimeMultipart should throw ParseException for parsing errors
 K 6074 MimeMessage.updateHeaders should set the Date header if not already set
+K 6216 Support addressing i18n via RFC 6530/6531/6532
 K 6281 The UIDFolder interface should have a getter for UIDNEXT
 K 6568 MailHandler should choose a better default subject formatter.
 K 6823 Store, Transport, and Folder should implement AutoCloseable

diff -r 377a55e66991 -r 3fbb3229820d doc/spec/JavaMail-1.6-changes.txt
--- a/doc/spec/JavaMail-1.6-changes.txt Tue Nov 29 12:22:07 2016 -0800
+++ b/doc/spec/JavaMail-1.6-changes.txt Tue Dec 20 14:46:25 2016 -0800
@@ -488,3 +488,100 @@
          * @since JavaMail 1.2
          */
         protected synchronized void parse() throws MessagingException
+
+
+===================================================================
+
+9. Support addressing i18n via RFC 6530/6531/6532 (6216)
+--------------------------------------------------------
+
+To enable support for UTF-8 email addresses, the following methods
+are added to InternetAddress:
+
+ /**
+ * Convert the given array of InternetAddress objects into
+ * a comma separated sequence of address strings. The
+ * resulting string contains Unicode characters. <p>
+ *
+ * @param addresses array of InternetAddress objects
+ * @exception ClassCastException if any address object in the
+ * given array is not an InternetAddress object.
+ * Note that this is a RuntimeException.
+ * @return comma separated string of addresses
+ * @since JavaMail 1.6
+ */
+ public static String toUnicodeString(Address[] addresses)
+
+ /**
+ * Convert the given array of InternetAddress objects into
+ * a comma separated sequence of address strings. The
+ * resulting string contains Unicode characters. <p>
+ *
+ * The 'used' parameter specifies the number of character positions
+ * already taken up in the field into which the resulting address
+ * sequence string is to be inserted. It is used to determine the
+ * line-break positions in the resulting address sequence string.
+ *
+ * @param addresses array of InternetAddress objects
+ * @param used number of character positions already used, in
+ * the field into which the address string is to
+ * be inserted.
+ * @exception ClassCastException if any address object in the
+ * given array is not an InternetAddress object.
+ * Note that this is a RuntimeException.
+ * @return comma separated string of addresses
+ * @since JavaMail 1.6
+ */
+ public static String toUnicodeString(Address[] addresses, int used)
+
+
+The following constructor and method are added to InternetHeaders:
+
+ /**
+ * Read and parse the given RFC822 message stream till the
+ * blank line separating the header from the body. The input
+ * stream is left positioned at the start of the body. The
+ * header lines are stored internally. <p>
+ *
+ * For efficiency, wrap a BufferedInputStream around the actual
+ * input stream and pass it as the parameter. <p>
+ *
+ * No placeholder entries are inserted; the original order of
+ * the headers is preserved.
+ *
+ * @param is RFC822 input stream
+ * @param allowutf8 if UTF-8 encoded headers are allowed
+ * @exception MessagingException for any I/O error reading the stream
+ * @since JavaMail 1.6
+ */
+ public InternetHeaders(InputStream is, boolean allowutf8)
+ throws MessagingException
+
+
+ /**
+ * Read and parse the given RFC822 message stream till the
+ * blank line separating the header from the body. Store the
+ * header lines inside this InternetHeaders object. The order
+ * of header lines is preserved. <p>
+ *
+ * Note that the header lines are added into this InternetHeaders
+ * object, so any existing headers in this object will not be
+ * affected. Headers are added to the end of the existing list
+ * of headers, in order.
+ *
+ * @param is RFC822 input stream
+ * @param allowutf8 if UTF-8 encoded headers are allowed
+ * @exception MessagingException for any I/O error reading the stream
+ * @since JavaMail 1.6
+ */
+ public void load(InputStream is, boolean allowutf8)
+ throws MessagingException
+
+The following Session property can be set to enable implicit use of
+these new methods:
+
+mail.mime.allowutf8:
+
+ If set to "true", UTF-8 strings are allowed in message headers,
+ e.g., in addresses. This should only be set if the mail server also
+ supports UTF-8.

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/iap/Argument.java
--- a/mail/src/main/java/com/sun/mail/iap/Argument.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/iap/Argument.java Tue Dec 20 14:46:25 2016 -0800
@@ -43,6 +43,7 @@
 import java.util.List;
 import java.util.ArrayList;
 import java.io.*;
+import java.nio.charset.Charset;
 import com.sun.mail.util.*;
 
 /**
@@ -107,6 +108,23 @@
     }
 
     /**
+ * Convert the given string into bytes in the specified
+ * charset, and write the bytes out as an ASTRING
+ *
+ * @param s String to write out
+ * @param charset the charset
+ * @return this
+ * @since JavaMail 1.6.0
+ */
+ public Argument writeString(String s, Charset charset) {
+ if (charset == null) // convenience
+ writeString(s);
+ else
+ items.add(new AString(s.getBytes(charset)));
+ return this;
+ }
+
+ /**
      * Write out given string as an NSTRING, depending on the type
      * of the characters inside the string. The string should
      * contain only ASCII characters. <p>
@@ -145,6 +163,25 @@
     }
 
     /**
+ * Convert the given string into bytes in the specified
+ * charset, and write the bytes out as an NSTRING
+ *
+ * @param s String to write out
+ * @param charset the charset
+ * @return this
+ * @since JavaMail 1.6.0
+ */
+ public Argument writeNString(String s, Charset charset) {
+ if (s == null)
+ items.add(new NString(null));
+ else if (charset == null) // convenience
+ writeString(s);
+ else
+ items.add(new NString(s.getBytes(charset)));
+ return this;
+ }
+
+ /**
      * Write out given byte[] as a Literal.
      * @param b byte[] to write out
      * @return this
@@ -286,17 +323,20 @@
         // if 0 length, send as quoted-string
         boolean quote = len == 0 ? true : doQuote;
         boolean escape = false;
-
+ boolean utf8 = protocol.supportsUtf8();
+
         byte b;
         for (int i = 0; i < len; i++) {
             b = bytes[i];
- if (b == '\0' || b == '\r' || b == '\n' || ((b & 0xff) > 0177)) {
+ if (b == '\0' || b == '\r' || b == '\n' ||
+ (!utf8 && ((b & 0xff) > 0177))) {
                 // NUL, CR or LF means the bytes need to be sent as literals
                 literal(bytes, protocol);
                 return;
             }
             if (b == '*' || b == '%' || b == '(' || b == ')' || b == '{' ||
- b == '"' || b == '\\' || ((b & 0xff) <= ' ')) {
+ b == '"' || b == '\\' ||
+ ((b & 0xff) <= ' ') || ((b & 0xff) > 0177)) {
                 quote = true;
                 if (b == '"' || b == '\\') // need to escape these characters
                     escape = true;

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/iap/Protocol.java
--- a/mail/src/main/java/com/sun/mail/iap/Protocol.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/iap/Protocol.java Tue Dec 20 14:46:25 2016 -0800
@@ -559,6 +559,17 @@
     }
 
     /**
+ * Does the server support UTF-8?
+ * This implementation returns false.
+ * Subclasses should override as appropriate.
+ *
+ * @since JavaMail 1.6.0
+ */
+ public boolean supportsUtf8() {
+ return false;
+ }
+
+ /**
      * Disconnect.
      */
     protected synchronized void disconnect() {

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/iap/Response.java
--- a/mail/src/main/java/com/sun/mail/iap/Response.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/iap/Response.java Tue Dec 20 14:46:25 2016 -0800
@@ -42,13 +42,16 @@
 
 import java.io.*;
 import java.util.*;
+import java.nio.charset.StandardCharsets;
+
 import com.sun.mail.util.*;
 
 /**
  * This class represents a response obtained from the input stream
  * of an IMAP server.
  *
- * @author John Mani
+ * @author John Mani
+ * @author Bill Shannon
  */
 
 public class Response {
@@ -61,6 +64,8 @@
     /** @since JavaMail 1.5.4 */
     protected Exception ex;
 
+ private boolean utf8;
+
     private static final int increment = 100;
 
     // The first and second bits indicate whether this response
@@ -96,8 +101,21 @@
     private static String ASTRING_CHAR_DELIM = " (){%*\"\\";
 
     public Response(String s) {
- buffer = ASCIIUtility.getBytes(s);
+ this(s, true);
+ }
+
+ /**
+ * Constructor for testing.
+ *
+ * @since JavaMail 1.6.0
+ */
+ public Response(String s, boolean supportsUtf8) {
+ if (supportsUtf8)
+ buffer = s.getBytes(StandardCharsets.UTF_8);
+ else
+ buffer = s.getBytes(StandardCharsets.US_ASCII);
         size = buffer.length;
+ utf8 = supportsUtf8;
         parse();
     }
 
@@ -114,6 +132,7 @@
         ByteArray response = p.getInputStream().readResponse(ba);
         buffer = response.getBytes();
         size = response.getCount() - 2; // Skip the terminating CRLF
+ utf8 = p.supportsUtf8();
 
         parse();
     }
@@ -147,6 +166,15 @@
         return r;
     }
 
+ /**
+ * Does the server support UTF-8?
+ *
+ * @since JavaMail 1.6.0
+ */
+ public boolean supportsUtf8() {
+ return utf8;
+ }
+
     private void parse() {
         index = 0; // position internal index at start
 
@@ -261,7 +289,7 @@
                delim.indexOf((char)b) < 0 && b >= ' ' && b != 0x7f)
             index++;
 
- return ASCIIUtility.toString(buffer, start, index);
+ return toString(buffer, start, index);
     }
 
     /**
@@ -282,7 +310,7 @@
         while (index < size && buffer[index] != delim)
             index++;
 
- return ASCIIUtility.toString(buffer, start, index);
+ return toString(buffer, start, index);
     }
 
     public String[] readStringList() {
@@ -458,7 +486,7 @@
                 index++; // skip past the terminating quote
 
             if (returnString)
- return ASCIIUtility.toString(buffer, start, copyto);
+ return toString(buffer, start, copyto);
             else
                 return new ByteArray(buffer, start, copyto-start);
         } else if (b == '{') { // Literal
@@ -479,7 +507,7 @@
             index = start + count; // position index to beyond the literal
 
             if (returnString) // return as String
- return ASCIIUtility.toString(buffer, start, start + count);
+ return toString(buffer, start, start + count);
             else
                     return new ByteArray(buffer, start, count);
         } else if (parseAtoms) { // parse as ASTRING-CHARs
@@ -497,6 +525,12 @@
         return null; // Error
     }
 
+ private String toString(byte[] buffer, int start, int end) {
+ return utf8 ?
+ new String(buffer, start, end - start, StandardCharsets.UTF_8) :
+ ASCIIUtility.toString(buffer, start, end);
+ }
+
     public int getType() {
         return type;
     }
@@ -550,7 +584,7 @@
      */
     public String getRest() {
         skipSpaces();
- return ASCIIUtility.toString(buffer, index, size);
+ return toString(buffer, index, size);
     }
 
     /**
@@ -572,7 +606,7 @@
 
     @Override
     public String toString() {
- return ASCIIUtility.toString(buffer, 0, size);
+ return toString(buffer, 0, size);
     }
 
 }

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Tue Dec 20 14:46:25 2016 -0800
@@ -2538,6 +2538,8 @@
             releaseStoreProtocol(p);
         }
 
+ if (status == null)
+ throw new MessagingException("Cannot obtain UIDValidity");
         return status.uidvalidity;
     }
 
@@ -2583,6 +2585,8 @@
             releaseStoreProtocol(p);
         }
 
+ if (status == null)
+ throw new MessagingException("Cannot obtain UIDNext");
         return status.uidnext;
     }
 
@@ -2816,6 +2820,8 @@
             releaseStoreProtocol(p);
         }
 
+ if (status == null)
+ throw new MessagingException("Cannot obtain HIGHESTMODSEQ");
         return status.highestmodseq;
     }
 
@@ -3431,7 +3437,7 @@
                 p = getStoreProtocol(); // XXX
                 String[] items = { item };
                 status = p.status(fullName, items);
- return status.getItem(item);
+ return status != null ? status.getItem(item) : -1;
             } catch (BadCommandException bex) {
                 // doesn't support STATUS, probably vanilla IMAP4 ..
                 // Could EXAMINE, SEARCH for UNREAD messages and

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/IMAPStore.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Tue Dec 20 14:46:25 2016 -0800
@@ -841,6 +841,11 @@
                 p.compress();
             }
         }
+
+ // if server supports UTF-8, enable it for client use
+ // note that this is safe to enable even if mail.mime.allowutf8=false
+ if (p.hasCapability("UTF8=ACCEPT") || p.hasCapability("UTF8=ONLY"))
+ p.enable("UTF8=ACCEPT");
     }
 
     /**

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/package.html
--- a/mail/src/main/java/com/sun/mail/imap/package.html Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/package.html Tue Dec 20 14:46:25 2016 -0800
@@ -226,6 +226,13 @@
 If the server supports the extension and the
 <code>mail.imap.compress.enable</code> property is set to "true",
 compression will be enabled.
+<P>
+<H4>UTF-8 Support</H4>
+The IMAP UTF8 extension
+(<A HREF="http://www.ietf.org/rfc/rfc6855.txt" TARGET="_top">RFC 6855</A>)
+is supported.
+If the server supports the extension, the client will enable use of UTF-8,
+allowing use of UTF-8 in IMAP protocol strings such as folder names.
 <A NAME="properties">
 <H4>Properties</H4>
 </A>

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/ID.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/ID.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/ID.java Tue Dec 20 14:46:25 2016 -0800
@@ -112,7 +112,7 @@
         Argument list = new Argument();
         // add params to list
         for (Map.Entry<String, String> e : clientParams.entrySet()) {
- list.writeNString(e.getKey());
+ list.writeNString(e.getKey()); // assume these are ASCII only
             list.writeNString(e.getValue());
         }
         arg.writeArgument(list);

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Tue Dec 20 14:46:25 2016 -0800
@@ -45,6 +45,7 @@
 import java.text.*;
 import java.lang.reflect.*;
 import java.util.logging.Level;
+import java.nio.charset.StandardCharsets;
 
 import javax.mail.*;
 import javax.mail.internet.*;
@@ -91,6 +92,7 @@
     private List<String> authmechs;
     // WARNING: authmechs may be initialized as a result of superclass
     // constructor, don't initialize it here.
+ private boolean utf8; // UTF-8 support enabled?
 
     protected SearchSequence searchSequence;
     protected String[] searchCharsets; // array of search charsets
@@ -440,6 +442,15 @@
     }
 
     /**
+ * Does the server support UTF-8?
+ *
+ * @since JavaMail 1.6.0
+ */
+ public boolean supportsUtf8() {
+ return utf8;
+ }
+
+ /**
      * Close socket connection.
      *
      * This method just makes the Protocol.disconnect() method
@@ -586,7 +597,7 @@
                         s = p;
                     
                     // obtain b64 encoded bytes
- b64os.write(ASCIIUtility.getBytes(s));
+ b64os.write(toBytes(s));
                     b64os.flush(); // complete the encoding
 
                     bos.write(CRLF); // CRLF termination
@@ -701,7 +712,7 @@
                                     nullByte + u + nullByte + p;
 
                     // obtain b64 encoded bytes
- b64os.write(ASCIIUtility.getBytes(s));
+ b64os.write(toBytes(s));
                     b64os.flush(); // complete the encoding
 
                     bos.write(CRLF); // CRLF termination
@@ -807,7 +818,7 @@
                         s = ntlm.generateType3Msg(r.getRest());
                     }
  
- os.write(ASCIIUtility.getBytes(s));
+ os.write(toBytes(s));
                     os.write(CRLF); // CRLF termination
                     os.flush(); // flush the stream
                 } else if (r.isTagged() && r.getTag().equals(tag))
@@ -882,8 +893,7 @@
             args.writeAtom("XOAUTH2");
             if (hasCapability("SASL-IR")) {
                 String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001";
- byte[] ba = BASE64EncoderStream.encode(
- ASCIIUtility.getBytes(resp));
+ byte[] ba = BASE64EncoderStream.encode(toBytes(resp));
                 String irs = ASCIIUtility.toString(ba, 0, ba.length);
                 args.writeAtom(irs);
             }
@@ -903,8 +913,7 @@
                     // Server challenge ..
                     String resp = "user=" + u + "\001auth=Bearer " +
                                     p + "\001\001";
- byte[] b = BASE64EncoderStream.encode(
- ASCIIUtility.getBytes(resp));
+ byte[] b = BASE64EncoderStream.encode(toBytes(resp));
                     os.write(b); // write out response
                     os.write(CRLF); // CRLF termination
                     os.flush(); // flush the stream
@@ -1159,6 +1168,21 @@
     }
 
     /**
+ * Encode a mailbox name appropriately depending on whether or not
+ * the server supports UTF-8, and add the encoded name to the
+ * Argument.
+ *
+ * @since JavaMail 1.6.0
+ */
+ protected void writeMailboxName(Argument args, String name) {
+ if (utf8)
+ args.writeString(name, StandardCharsets.UTF_8);
+ else
+ // encode the mbox as per RFC2060
+ args.writeString(BASE64MailboxEncoder.encode(name));
+ }
+
+ /**
      * SELECT Command.
      *
      * @param mbox the mailbox name
@@ -1183,11 +1207,8 @@
      */
     public MailboxInfo select(String mbox, ResyncData rd)
                                 throws ProtocolException {
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         if (rd != null) {
             if (rd == ResyncData.CONDSTORE) {
@@ -1248,11 +1269,8 @@
      */
     public MailboxInfo examine(String mbox, ResyncData rd)
                                 throws ProtocolException {
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         if (rd != null) {
             if (rd == ResyncData.CONDSTORE) {
@@ -1313,6 +1331,9 @@
         if (enabled == null)
             enabled = new HashSet<>();
         enabled.add(cap.toUpperCase(Locale.ENGLISH));
+
+ // update the utf8 flag
+ utf8 = isEnabled("UTF8=ACCEPT");
     }
 
     /**
@@ -1359,11 +1380,8 @@
             // does support this.
             throw new BadCommandException("STATUS not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Argument itemArgs = new Argument();
         if (items == null)
@@ -1409,11 +1427,8 @@
      * @see "RFC2060, section 6.3.3"
      */
     public void create(String mbox) throws ProtocolException {
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         simpleCommand("CREATE", args);
     }
@@ -1426,11 +1441,8 @@
      * @see "RFC2060, section 6.3.4"
      */
     public void delete(String mbox) throws ProtocolException {
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         simpleCommand("DELETE", args);
     }
@@ -1444,13 +1456,9 @@
      * @see "RFC2060, section 6.3.5"
      */
     public void rename(String o, String n) throws ProtocolException {
- // encode the mbox as per RFC2060
- o = BASE64MailboxEncoder.encode(o);
- n = BASE64MailboxEncoder.encode(n);
-
         Argument args = new Argument();
- args.writeString(o);
- args.writeString(n);
+ writeMailboxName(args, o);
+ writeMailboxName(args, n);
 
         simpleCommand("RENAME", args);
     }
@@ -1464,9 +1472,7 @@
      */
     public void subscribe(String mbox) throws ProtocolException {
         Argument args = new Argument();
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         simpleCommand("SUBSCRIBE", args);
     }
@@ -1480,9 +1486,7 @@
      */
     public void unsubscribe(String mbox) throws ProtocolException {
         Argument args = new Argument();
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         simpleCommand("UNSUBSCRIBE", args);
     }
@@ -1528,13 +1532,9 @@
      */
     protected ListInfo[] doList(String cmd, String ref, String pat)
                         throws ProtocolException {
- // encode the mbox as per RFC2060
- ref = BASE64MailboxEncoder.encode(ref);
- pat = BASE64MailboxEncoder.encode(pat);
-
         Argument args = new Argument();
- args.writeString(ref);
- args.writeString(pat);
+ writeMailboxName(args, ref);
+ writeMailboxName(args, pat);
 
         Response[] r = command(cmd, args);
 
@@ -1597,11 +1597,8 @@
 
     public AppendUID appenduid(String mbox, Flags f, Date d,
                         Literal data, boolean uid) throws ProtocolException {
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         if (f != null) { // set Flags in appended message
             // can't set the \Recent flag in APPEND
@@ -2212,12 +2209,10 @@
                                 throws ProtocolException {
         if (uid && !hasCapability("UIDPLUS"))
             throw new BadCommandException("UIDPLUS not supported");
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
 
         Argument args = new Argument();
         args.writeAtom(msgSequence);
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Response[] r = command("COPY", args);
 
@@ -2310,12 +2305,10 @@
             throw new BadCommandException("MOVE not supported");
         if (uid && !hasCapability("UIDPLUS"))
             throw new BadCommandException("UIDPLUS not supported");
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
 
         Argument args = new Argument();
         args.writeAtom(msgSequence);
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Response[] r = command("MOVE", args);
 
@@ -2737,11 +2730,8 @@
         if (!hasCapability("QUOTA"))
             throw new BadCommandException("GETQUOTAROOT not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Response[] r = command("GETQUOTAROOT", args);
 
@@ -2810,7 +2800,7 @@
             throw new BadCommandException("QUOTA not supported");
 
         Argument args = new Argument();
- args.writeString(root);
+ args.writeString(root); // XXX - could be UTF-8?
 
         Response[] r = command("GETQUOTA", args);
 
@@ -2853,7 +2843,7 @@
             throw new BadCommandException("QUOTA not supported");
 
         Argument args = new Argument();
- args.writeString(quota.quotaRoot);
+ args.writeString(quota.quotaRoot); // XXX - could be UTF-8?
         Argument qargs = new Argument();
         if (quota.resources != null) {
             for (int i = 0; i < quota.resources.length; i++) {
@@ -2940,11 +2930,8 @@
         if (!hasCapability("ACL"))
             throw new BadCommandException("ACL not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
         args.writeString(acl.getName());
         String rights = acl.getRights().toString();
         if (modifier == '+' || modifier == '-')
@@ -2971,12 +2958,9 @@
         if (!hasCapability("ACL"))
             throw new BadCommandException("ACL not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
- args.writeString(user);
+ writeMailboxName(args, mbox);
+ args.writeString(user); // XXX - could be UTF-8?
 
         Response[] r = command("DELETEACL", args);
         Response response = r[r.length-1];
@@ -2998,11 +2982,8 @@
         if (!hasCapability("ACL"))
             throw new BadCommandException("ACL not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Response[] r = command("GETACL", args);
         Response response = r[r.length-1];
@@ -3053,12 +3034,9 @@
         if (!hasCapability("ACL"))
             throw new BadCommandException("ACL not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
- args.writeString(user);
+ writeMailboxName(args, mbox);
+ args.writeString(user); // XXX - could be UTF-8?
 
         Response[] r = command("LISTRIGHTS", args);
         Response response = r[r.length-1];
@@ -3104,11 +3082,8 @@
         if (!hasCapability("ACL"))
             throw new BadCommandException("ACL not supported");
 
- // encode the mbox as per RFC2060
- mbox = BASE64MailboxEncoder.encode(mbox);
-
         Argument args = new Argument();
- args.writeString(mbox);
+ writeMailboxName(args, mbox);
 
         Response[] r = command("MYRIGHTS", args);
         Response response = r[r.length-1];
@@ -3327,4 +3302,16 @@
         handleResult(response);
         return id == null ? null : id.getServerParams();
     }
+
+ /**
+ * Convert the String to either ASCII or UTF-8 bytes
+ * depending on the utf8 flag.
+ */
+ private byte[] toBytes(String s) {
+ if (utf8)
+ return s.getBytes(StandardCharsets.UTF_8);
+ else
+ // don't use StandardCharsets.US_ASCII because it rejects non-ASCII
+ return ASCIIUtility.getBytes(s);
+ }
 }

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java Tue Dec 20 14:46:25 2016 -0800
@@ -93,15 +93,31 @@
      * @exception ProtocolException for protocol failures
      */
     public IMAPResponse(String r) throws IOException, ProtocolException {
- super(r);
+ this(r, true);
+ }
+
+ /**
+ * For testing.
+ *
+ * @param r the response string
+ * @param utf8 UTF-8 allowed?
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.6.0
+ */
+ public IMAPResponse(String r, boolean utf8)
+ throws IOException, ProtocolException {
+ super(r, utf8);
         init();
     }
 
     /**
- * Read a list of space-separated "flag_extension" sequences and
+ * Read a list of space-separated "flag-extension" sequences and
      * return the list as a array of Strings. An empty list is returned
- * as null. This is an IMAP-ism, and perhaps this method should
- * moved into the IMAP layer.
+ * as null. Each item is expected to be an atom, possibly preceeded
+ * by a backslash, but we aren't that strict; we just look for strings
+ * separated by spaces and terminated by a right paren. We assume items
+ * are always ASCII.
      *
      * @return the list items as a String array
      */

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java Tue Dec 20 14:46:25 2016 -0800
@@ -96,7 +96,8 @@
         r.skipSpaces();
         name = r.readAtomString();
 
- // decode the name (using RFC2060's modified UTF7)
- name = BASE64MailboxDecoder.decode(name);
+ if (!r.supportsUtf8())
+ // decode the name (using RFC2060's modified UTF7)
+ name = BASE64MailboxDecoder.decode(name);
     }
 }

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java Tue Dec 20 14:46:25 2016 -0800
@@ -81,7 +81,9 @@
                 throw new ProtocolException(
                                         "Missing '(' at start of Namespace");
             // first, the prefix
- prefix = BASE64MailboxDecoder.decode(r.readString());
+ prefix = r.readString();
+ if (!r.supportsUtf8())
+ prefix = BASE64MailboxDecoder.decode(prefix);
             r.skipSpaces();
             // delimiter is a quoted character or NIL
             if (r.peekByte() == '"') {

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/imap/protocol/Status.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/Status.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/Status.java Tue Dec 20 14:46:25 2016 -0800
@@ -68,7 +68,9 @@
 
     public Status(Response r) throws ParsingException {
         // mailbox := astring
- mbox = BASE64MailboxDecoder.decode(r.readAtomString());
+ mbox = r.readAtomString();
+ if (!r.supportsUtf8())
+ mbox = BASE64MailboxDecoder.decode(mbox);
 
         // Workaround buggy IMAP servers that don't quote folder names
         // with spaces.

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/smtp/DigestMD5.java
--- a/mail/src/main/java/com/sun/mail/smtp/DigestMD5.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/smtp/DigestMD5.java Tue Dec 20 14:46:25 2016 -0800
@@ -44,6 +44,7 @@
 import java.util.*;
 import java.util.logging.Level;
 import java.security.*;
+import java.nio.charset.StandardCharsets;
 
 import com.sun.mail.util.*;
 
@@ -113,6 +114,10 @@
         // server challenge random value
         String nonce = map.get("nonce");
 
+ // Does server support UTF-8 usernames and passwords?
+ String charset = map.get("charset");
+ boolean utf8 = charset != null && charset.equalsIgnoreCase("utf-8");
+
         random.nextBytes(bytes);
         b64os.write(bytes);
         b64os.flush();
@@ -122,7 +127,11 @@
         bos.reset();
 
         // DIGEST-MD5 computation, common portion (order critical)
- md5.update(md5.digest(
+ if (utf8) {
+ String up = user + ":" + realm + ":" + passwd;
+ md5.update(md5.digest(up.getBytes(StandardCharsets.UTF_8)));
+ } else
+ md5.update(md5.digest(
                 ASCIIUtility.getBytes(user + ":" + realm + ":" + passwd)));
         md5.update(ASCIIUtility.getBytes(":" + nonce + ":" + cnonce));
         clientResponse = toHex(md5.digest())
@@ -140,6 +149,8 @@
         result.append(",nonce=\"" + nonce + "\"");
         result.append(",cnonce=\"" + cnonce + "\"");
         result.append(",digest-uri=\"" + uri + "\"");
+ if (utf8)
+ result.append(",charset=\"utf-8\"");
         result.append(",response=" + toHex(md5.digest()));
 
         if (logger.isLoggable(Level.FINE))

diff -r 377a55e66991 -r 3fbb3229820d mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
--- a/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Tue Nov 29 12:22:07 2016 -0800
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Tue Dec 20 14:46:25 2016 -0800
@@ -45,6 +45,7 @@
 import java.util.*;
 import java.util.logging.Level;
 import java.lang.reflect.*;
+import java.nio.charset.StandardCharsets;
 import javax.net.ssl.SSLSocket;
 
 import javax.mail.*;
@@ -140,6 +141,7 @@
     private boolean noauthdebug = true; // hide auth info in debug output
     private boolean debugusername; // include username in debug output?
     private boolean debugpassword; // include password in debug output?
+ private boolean allowutf8; // allow UTF-8 usernames and passwords?
 
     /** Headers that should not be included when sending */
     private static final String[] ignoreList = { "Bcc", "Content-Length" };
@@ -225,6 +227,11 @@
         if (useCanonicalHostName)
             logger.config("use canonical host name");
 
+ allowutf8 = PropUtil.getBooleanSessionProperty(session,
+ "mail.mime.allowutf8", false);
+ if (allowutf8)
+ logger.config("allow UTF-8");
+
         // created here, because they're inner classes that reference "this"
         Authenticator[] a = new Authenticator[] {
             new LoginAuthenticator(),
@@ -738,6 +745,10 @@
                 }
             }
 
+ if (allowutf8 && !supportsExtension("SMTPUTF8"))
+ logger.log(Level.INFO, "mail.mime.allowutf8 set " +
+ "but server doesn't advertise SMTPUTF8 support");
+
             if ((useAuth || (user != null && password != null)) &&
                   (supportsExtension("AUTH") ||
                    supportsExtension("AUTH=LOGIN"))) {
@@ -962,12 +973,11 @@
         void doAuth(String host, String authzid, String user, String passwd)
                                     throws MessagingException, IOException {
             // send username
- resp = simpleCommand(
- BASE64EncoderStream.encode(ASCIIUtility.getBytes(user)));
+ resp = simpleCommand(BASE64EncoderStream.encode(toBytes(user)));
             if (resp == 334) {
                 // send passwd
                 resp = simpleCommand(
- BASE64EncoderStream.encode(ASCIIUtility.getBytes(passwd)));
+ BASE64EncoderStream.encode(toBytes(passwd)));
             }
         }
     }
@@ -988,11 +998,11 @@
             OutputStream b64os =
                         new BASE64EncoderStream(bos, Integer.MAX_VALUE);
             if (authzid != null)
- b64os.write(ASCIIUtility.getBytes(authzid));
+ b64os.write(toBytes(authzid));
             b64os.write(0);
- b64os.write(ASCIIUtility.getBytes(user));
+ b64os.write(toBytes(user));
             b64os.write(0);
- b64os.write(ASCIIUtility.getBytes(passwd));
+ b64os.write(toBytes(passwd));
             b64os.flush(); // complete the encoding
 
             return ASCIIUtility.toString(bos.toByteArray());
@@ -1093,7 +1103,7 @@
                 String passwd) throws MessagingException, IOException {
             String resp = "user=" + user + "\001auth=Bearer " +
                             passwd + "\001\001";
- byte[] b = BASE64EncoderStream.encode(ASCIIUtility.getBytes(resp));
+ byte[] b = BASE64EncoderStream.encode(toBytes(resp));
             return ASCIIUtility.toString(b);
         }
 
@@ -1721,6 +1731,9 @@
 
         String cmd = "MAIL FROM:" + normalizeAddress(from);
 
+ if (allowutf8 && supportsExtension("SMTPUTF8"))
+ cmd += " SMTPUTF8";
+
         // request delivery status notification?
         if (supportsExtension("DSN")) {
             String ret = null;
@@ -1747,7 +1760,8 @@
             // XXX - check for legal syntax?
             if (submitter != null) {
                 try {
- String s = xtext(submitter);
+ String s = xtext(submitter,
+ allowutf8 && supportsExtension("SMTPUTF8"));
                     cmd += " AUTH=" + s;
                 } catch (IllegalArgumentException ex) {
                     if (logger.isLoggable(Level.FINE))
@@ -2322,7 +2336,7 @@
      * @since JavaMail 1.4.1
      */
     protected void sendCommand(String cmd) throws MessagingException {
- sendCommand(ASCIIUtility.getBytes(cmd));
+ sendCommand(toBytes(cmd));
     }
 
     private void sendCommand(byte[] cmdBytes) throws MessagingException {
@@ -2533,14 +2547,33 @@
      * @return the xtext format string
      * @since JavaMail 1.4.1
      */
+ // XXX - keeping this around only for compatibility
     protected static String xtext(String s) {
+ return xtext(s, false);
+ }
+
+ /**
+ * Like xtext(s), but allow UTF-8 strings.
+ *
+ * @param s the string to convert
+ * @param utf8 convert string to UTF-8 first?
+ * @return the xtext format string
+ * @since JavaMail 1.6.0
+ */
+ protected static String xtext(String s, boolean utf8) {
         StringBuffer sb = null;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c >= 128) // not ASCII
+ byte[] bytes;
+ if (utf8)
+ bytes = s.getBytes(StandardCharsets.UTF_8);
+ else
+ bytes = ASCIIUtility.getBytes(s);
+ for (int i = 0; i < bytes.length; i++) {
+ char c = (char)(((int)bytes[i])&0xff);
+ if (!utf8 && c >= 128) // not ASCII
                 throw new IllegalArgumentException(
- "Non-ASCII character in SMTP submitter: " + s);
- if (c < '!' || c > '~' || c == '+' || c == '=') {
+ "Non-ASCII character in SMTP submitter: " + s);
+ if (c < '!' || c > '~' || c == '+' || c == '=' ||
+ c >= 128) { // not ASCII
                 if (sb == null) {
                     sb = new StringBuffer(s.length() + 4);
                     sb.append(s.substring(0, i));
@@ -2565,6 +2598,18 @@
                                 (password == null ? "<null>" : "<non-null>");
     }
 
+ /**
+ * Convert the String to either ASCII or UTF-8 bytes
+ * depending on allowutf8.
+ */
+ private byte[] toBytes(String s) {
+ if (allowutf8)
+ return s.getBytes(Stand
[truncated due to length]