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]