commits@javamail.java.net

[javamail~mercurial:900] MimeUtility.unfold squashes multiple spaces - bug 8846

From: <shannon_at_java.net>
Date: Tue, 21 Feb 2017 22:44:05 +0000

Project: javamail
Repository: mercurial
Revision: 900
Author: shannon
Date: 2017-02-21 22:36:18 UTC
Link:

Log Message:
------------
SMTP support for the CHUNKING extension of RFC 3030 - bug 8833
sync with further updates for JAF 1.2.
MimeUtility.unfold squashes multiple spaces - bug 8846


Revisions:
----------
898
899
900


Modified Paths:
---------------
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
mail/src/main/java/com/sun/mail/smtp/package.html
mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
android/activation/src/main/java/javax/activation/MailcapCommandMap.java
android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java
mail/src/main/java/javax/mail/internet/MimeUtility.java
mail/src/test/resources/javax/mail/internet/addrlist
mail/src/test/resources/javax/mail/internet/folddata


Added Paths:
------------
mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java


Diffs:
------
diff -r b4e0e84ae6cf -r 3bd1fb127cc2 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Mon Feb 13 14:46:51 2017 -0800
+++ b/doc/release/CHANGES.txt Wed Feb 15 17:01:38 2017 -0800
@@ -48,6 +48,7 @@
 K 8748 look for resource files in <java.home>/conf on JDK 1.9
 K 8810 MimeUtility should treat GB2312 as one of the supersets GBK or GB18030
 K 8822 Flags convenience methods
+K 8833 SMTP support for the CHUNKING extension of RFC 3030
 
 
                   CHANGES IN THE 1.5.6 RELEASE

diff -r b4e0e84ae6cf -r 3bd1fb127cc2 mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
--- a/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Mon Feb 13 14:46:51 2017 -0800
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java Wed Feb 15 17:01:38 2017 -0800
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2017 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
@@ -142,6 +142,7 @@
     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?
+ private int chunkSize; // chunk size if CHUNKING supported
 
     /** Headers that should not be included when sending */
     private static final String[] ignoreList = { "Bcc", "Content-Length" };
@@ -232,6 +233,11 @@
         if (allowutf8)
             logger.config("allow UTF-8");
 
+ chunkSize = PropUtil.getIntSessionProperty(session,
+ "mail." + name + ".chunksize", -1);
+ if (chunkSize > 0 && logger.isLoggable(Level.CONFIG))
+ logger.config("chunk size " + chunkSize);
+
         // created here, because they're inner classes that reference "this"
         Authenticator[] a = new Authenticator[] {
             new LoginAuthenticator(),
@@ -1276,8 +1282,22 @@
         try {
             mailFrom();
             rcptTo();
- this.message.writeTo(data(), ignoreList);
- finishData();
+ if (chunkSize > 0 && supportsExtension("CHUNKING")) {
+ /*
+ * Use BDAT to send the data in chunks.
+ * Note that even though the BDAT command is able to send
+ * messages that contain binary data, we can't use it to
+ * do that because a) we still need to canonicalize the
+ * line terminators for text data, which we can't tell apart
+ * from the message content, and b) the message content is
+ * encoded before we even know that we can use BDAT.
+ */
+ this.message.writeTo(bdat(), ignoreList);
+ finishBdat();
+ } else {
+ this.message.writeTo(data(), ignoreList);
+ finishData();
+ }
             if (sendPartiallyFailed) {
                 // throw the exception,
                 // fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
@@ -2074,6 +2094,32 @@
     }
 
     /**
+ * Return a stream that will use the SMTP BDAT command to send data.
+ *
+ * @return the stream to write to
+ * @exception MessagingException for failures
+ * @since JavaMail 1.6.0
+ */
+ protected OutputStream bdat() throws MessagingException {
+ assert Thread.holdsLock(this);
+ dataStream = new BDATOutputStream(serverOutput, chunkSize);
+ return dataStream;
+ }
+
+ /**
+ * Terminate the sent data.
+ *
+ * @exception IOException for I/O errors
+ * @exception MessagingException for other failures
+ * @since JavaMail 1.6.0
+ */
+ protected void finishBdat() throws IOException, MessagingException {
+ assert Thread.holdsLock(this);
+ dataStream.ensureAtBOL();
+ dataStream.close(); // doesn't close underlying socket
+ }
+
+ /**
      * Issue the <code>STARTTLS</code> command and switch the socket to
      * TLS mode if it succeeds.
      *
@@ -2615,4 +2661,138 @@
      */
     private void sendMessageStart(String subject) { }
     private void sendMessageEnd() { }
+
+
+ /**
+ * An SMTPOutputStream that wraps a ChunkedOutputStream.
+ */
+ private class BDATOutputStream extends SMTPOutputStream {
+
+ /**
+ * Create a BDATOutputStream that wraps a ChunkedOutputStream
+ * of the given size and built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream
+ * @param size the chunk size
+ */
+ public BDATOutputStream(OutputStream out, int size) {
+ super(new ChunkedOutputStream(out, size));
+ }
+
+ /**
+ * Close this output stream.
+ *
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void close() throws IOException {
+ out.close();
+ }
+ }
+
+ /**
+ * An OutputStream that buffers data in chunks and uses the
+ * RFC 3030 BDAT SMTP command to send each chunk.
+ */
+ private class ChunkedOutputStream extends OutputStream {
+ private final OutputStream out;
+ private final byte[] buf;
+ private int count = 0;
+
+ /**
+ * Create a ChunkedOutputStream built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream
+ * @param size the chunk size
+ */
+ public ChunkedOutputStream(OutputStream out, int size) {
+ this.out = out;
+ buf = new byte[size];
+ }
+
+ /**
+ * Writes the specified <code>byte</code> to this output stream.
+ *
+ * @param b the byte to write
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void write(int b) throws IOException {
+ buf[count++] = (byte)b;
+ if (count >= buf.length)
+ flush();
+ }
+
+ /**
+ * Writes len bytes to this output stream starting at off.
+ *
+ * @param b bytes to write
+ * @param off offset in array
+ * @param len number of bytes to write
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ while (len > 0) {
+ int size = Math.min(buf.length - count, len);
+ if (size == buf.length) {
+ // avoid the copy
+ bdat(b, off, size, false);
+ } else {
+ System.arraycopy(b, off, buf, count, size);
+ count += size;
+ }
+ off += size;
+ len -= size;
+ if (count >= buf.length)
+ flush();
+ }
+ }
+
+ /**
+ * Flush this output stream.
+ *
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void flush() throws IOException {
+ bdat(buf, 0, count, false);
+ count = 0;
+ }
+
+ /**
+ * Close this output stream.
+ *
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void close() throws IOException {
+ bdat(buf, 0, count, true);
+ count = 0;
+ }
+
+ /**
+ * Send the specified bytes using the BDAT command.
+ */
+ private void bdat(byte[] b, int off, int len, boolean last)
+ throws IOException {
+ if (len > 0 || last) {
+ try {
+ if (last)
+ sendCommand("BDAT " + len + " LAST");
+ else
+ sendCommand("BDAT " + len);
+ out.write(b, off, len);
+ out.flush();
+ int ret = readServerResponse();
+ if (ret != 250)
+ throw new IOException(lastServerResponse);
+ } catch (MessagingException mex) {
+ throw new IOException("BDAT write exception", mex);
+ }
+ }
+ }
+ }
 }

diff -r b4e0e84ae6cf -r 3bd1fb127cc2 mail/src/main/java/com/sun/mail/smtp/package.html
--- a/mail/src/main/java/com/sun/mail/smtp/package.html Mon Feb 13 14:46:51 2017 -0800
+++ b/mail/src/main/java/com/sun/mail/smtp/package.html Wed Feb 15 17:01:38 2017 -0800
@@ -5,7 +5,7 @@
 
     DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
- Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 1997-2017 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
@@ -163,6 +163,15 @@
 start setting properties and then complain to us when it doesn't work
 like you expect it to work. <strong>READ THE RFCs FIRST!!!</strong>
 <P>
+The SMTP protocol provider supports the CHUNKING extension defined in
+<A HREF="http://www.ietf.org/rfc/rfc3030.txt" TARGET="_top">RFC 3030</A>.
+Set the <code>mail.smtp.chunksize</code> property to the desired chunk
+size in bytes.
+If the server supports the CHUNKING extension, the BDAT command will be
+used to send the message in chunksize pieces. Note that no pipelining is
+done so this will be slower than sending the message in one piece.
+Note also that the BINARYMIME extension described in RFC 3030 is NOT supported.
+<P>
 <A NAME="properties">
 <H4>Properties</H4>
 </A>

diff -r b4e0e84ae6cf -r 3bd1fb127cc2 mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java Wed Feb 15 17:01:38 2017 -0800
@@ -0,0 +1,175 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2009-2017 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the BDAT command.
+ */
+public final class SMTPBdatTest {
+
+ private byte[] message;
+
+ @Test
+ public void testBdatSuccess() throws Exception {
+ TestServer server = null;
+ try {
+ SMTPHandler handler = new SMTPHandler() {
+ {{ extensions.add("CHUNKING"); }}
+
+ @Override
+ public void setMessage(byte[] msg) {
+ message = msg;
+ }
+ };
+ server = new TestServer(handler);
+ server.start();
+
+ final Properties properties = new Properties();
+ properties.setProperty("mail.smtp.host", "localhost");
+ properties.setProperty("mail.smtp.port", "" + server.getPort());
+ properties.setProperty("mail.smtp.chunksize", "128");
+ final Session session = Session.getInstance(properties);
+ //session.setDebug(true);
+
+ final Transport t = session.getTransport("smtp");
+ try {
+ MimeMessage msg = new MimeMessage(session);
+ msg.setRecipients(Message.RecipientType.TO, "joe_at_example.com");
+ msg.setSubject("test");
+ msg.setText("test\r\n");
+ t.connect();
+ t.sendMessage(msg, msg.getAllRecipients());
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ msg.writeTo(bos);
+ bos.close();
+ byte[] orig = bos.toByteArray();
+ assertArrayEquals(orig, message);
+ } catch (MessagingException ex) {
+ fail(ex.getMessage());
+ } finally {
+ t.close();
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ if (server != null) {
+ server.quit();
+ server.interrupt();
+ // wait for handler to exit
+ server.join();
+ }
+ }
+ }
+
+ @Test
+ public void testBdatFailure() throws Exception {
+ TestServer server = null;
+ try {
+ SMTPHandler handler = new SMTPHandler() {
+ {{ extensions.add("CHUNKING"); }}
+
+ @Override
+ public void bdat(String line) throws IOException {
+ String[] tok = line.split("\\s+");
+ int bytes = Integer.parseInt(tok[1]);
+ boolean last = tok.length > 2 &&
+ tok[2].equalsIgnoreCase("LAST");
+ readBdatMessage(bytes, last);
+ println("444 failed");
+ }
+ };
+ server = new TestServer(handler);
+ server.start();
+
+ final Properties properties = new Properties();
+ properties.setProperty("mail.smtp.host", "localhost");
+ properties.setProperty("mail.smtp.port", "" + server.getPort());
+ properties.setProperty("mail.smtp.chunksize", "128");
+ final Session session = Session.getInstance(properties);
+ //session.setDebug(true);
+
+ final Transport t = session.getTransport("smtp");
+ try {
+ MimeMessage msg = new MimeMessage(session);
+ msg.setRecipients(Message.RecipientType.TO, "joe_at_example.com");
+ msg.setSubject("test");
+ msg.setText("test\r\n");
+ t.connect();
+ t.sendMessage(msg, msg.getAllRecipients());
+ fail("no exception");
+ } catch (MessagingException ex) {
+ // expect it to fail
+ } finally {
+ t.close();
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ if (server != null) {
+ server.quit();
+ server.interrupt();
+ // wait for handler to exit
+ server.join();
+ }
+ }
+ }
+}

diff -r b4e0e84ae6cf -r 3bd1fb127cc2 mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
--- a/mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java Mon Feb 13 14:46:51 2017 -0800
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java Wed Feb 15 17:01:38 2017 -0800
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 2009-2016 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2017 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
@@ -40,8 +40,10 @@
 
 package com.sun.mail.smtp;
 
-import java.io.IOException;
+import java.io.*;
 import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.HashSet;
 import java.util.logging.Logger;
 import java.util.logging.Level;
 
@@ -57,6 +59,12 @@
     /** Current line. */
     private String currentLine;
 
+ /** A message being accumulated. */
+ private ByteArrayOutputStream messageStream;
+
+ /** SMTP extensions supported. */
+ protected Set<String> extensions = new HashSet<String>();
+
     /**
      * Send greetings.
      *
@@ -92,12 +100,8 @@
     public void handleCommand() throws IOException {
         currentLine = readLine();
 
- if (currentLine == null) {
- // XXX - often happens when shutting down
- //LOGGER.severe("Current line is null!");
- exit();
+ if (currentLine == null)
             return;
- }
 
         final StringTokenizer st = new StringTokenizer(currentLine, " ");
         final String commandName = st.nextToken().toUpperCase();
@@ -117,6 +121,8 @@
             rcpt(currentLine);
         } else if (commandName.equals("DATA")) {
             data();
+ } else if (commandName.equals("BDAT")) {
+ bdat(currentLine);
         } else if (commandName.equals("NOOP")) {
             noop();
         } else if (commandName.equals("RSET")) {
@@ -135,7 +141,8 @@
         currentLine = super.readLine();
 
         if (currentLine == null) {
- LOGGER.severe("Current line is null!");
+ // XXX - often happens when shutting down
+ //LOGGER.severe("Current line is null!");
             exit();
         }
         return currentLine;
@@ -159,6 +166,8 @@
      */
     public void ehlo() throws IOException {
         println("250-hello");
+ for (String ext : extensions)
+ println("250-" + ext);
         println("250 AUTH PLAIN"); // PLAIN is simplest to fake
     }
 
@@ -195,13 +204,65 @@
     }
 
     /**
- * For now, just consume the message and throw it away.
+ * BDAT command.
+ *
+ * @throws IOException
+ * unable to read/write to socket
+ */
+ public void bdat(String line) throws IOException {
+ StringTokenizer st = new StringTokenizer(line, " ");
+ String commandName = st.nextToken();
+ int bytes = Integer.parseInt(st.nextToken());
+ boolean last = st.hasMoreTokens() &&
+ st.nextToken().equalsIgnoreCase("LAST");
+ readBdatMessage(bytes, last);
+ ok();
+ }
+
+ /**
+ * Allow subclasses to override to save the message.
+ */
+ protected void setMessage(byte[] msg) {
+ }
+
+ /**
+ * Consume the message and save it.
      */
     protected void readMessage() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(bos, "utf-8"));
         String line;
         while ((line = super.readLine()) != null) {
             if (line.equals("."))
                 break;
+ if (line.startsWith("."))
+ line = line.substring(1);
+ pw.print(line);
+ pw.print("\r\n");
+ }
+ pw.close();
+ setMessage(bos.toByteArray());
+ }
+
+ /**
+ * Consume a chunk of the message and save it.
+ * Save the entire message when the last chunk is received.
+ */
+ protected void readBdatMessage(int bytes, boolean last) throws IOException {
+ byte[] data = new byte[bytes];
+ int len = data.length;
+ int off = 0;
+ int n;
+ while (len > 0 && (n = in.read(data, off, len)) > 0) {
+ off += n;
+ len -= n;
+ }
+ if (messageStream == null)
+ messageStream = new ByteArrayOutputStream();
+ messageStream.write(data);
+ if (last) {
+ setMessage(messageStream.toByteArray());
+ messageStream = null;
         }
     }
 


diff -r 3bd1fb127cc2 -r 73d997261186 android/activation/src/main/java/javax/activation/MailcapCommandMap.java
--- a/android/activation/src/main/java/javax/activation/MailcapCommandMap.java Wed Feb 15 17:01:38 2017 -0800
+++ b/android/activation/src/main/java/javax/activation/MailcapCommandMap.java Tue Feb 21 12:09:34 2017 -0800
@@ -43,6 +43,8 @@
 import java.util.*;
 import java.io.*;
 import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import com.sun.activation.registries.MailcapFile;
 import com.sun.activation.registries.LogSupport;
 
@@ -145,13 +147,18 @@
     static {
         String dir = null;
         try {
- String home = System.getProperty("java.home");
- String newdir = home + File.separator + "conf";
- File conf = new File(newdir);
- if (conf.exists())
- dir = newdir + File.separator;
- else
- dir = home + File.separator + "lib" + File.separator;
+ dir = (String)AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ String home = System.getProperty("java.home");
+ String newdir = home + File.separator + "conf";
+ File conf = new File(newdir);
+ if (conf.exists())
+ return newdir + File.separator;
+ else
+ return home + File.separator + "lib" + File.separator;
+ }
+ });
         } catch (Exception ex) {
             // ignore any exceptions
         }
@@ -180,12 +187,14 @@
         } catch (SecurityException ex) {}
 
         LogSupport.log("MailcapCommandMap: load SYS");
- if (confDir != null) {
+ try {
             // check system's home
- mf = loadFile(confDir + "mailcap");
- if (mf != null)
- dbv.add(mf);
- }
+ if (confDir != null) {
+ mf = loadFile(confDir + "mailcap");
+ if (mf != null)
+ dbv.add(mf);
+ }
+ } catch (SecurityException ex) {}
 
         LogSupport.log("MailcapCommandMap: load JAR");
         // load from the app's jar file

diff -r 3bd1fb127cc2 -r 73d997261186 android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java
--- a/android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java Wed Feb 15 17:01:38 2017 -0800
+++ b/android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java Tue Feb 21 12:09:34 2017 -0800
@@ -43,6 +43,8 @@
 import java.io.*;
 import java.net.*;
 import java.util.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import com.sun.activation.registries.MimeTypeFile;
 import com.sun.activation.registries.LogSupport;
 
@@ -97,13 +99,18 @@
     static {
         String dir = null;
         try {
- String home = System.getProperty("java.home");
- String newdir = home + File.separator + "conf";
- File conf = new File(newdir);
- if (conf.exists())
- dir = newdir + File.separator;
- else
- dir = home + File.separator + "lib" + File.separator;
+ dir = (String)AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ String home = System.getProperty("java.home");
+ String newdir = home + File.separator + "conf";
+ File conf = new File(newdir);
+ if (conf.exists())
+ return newdir + File.separator;
+ else
+ return home + File.separator + "lib" + File.separator;
+ }
+ });
         } catch (Exception ex) {
             // ignore any exceptions
         }
@@ -131,12 +138,14 @@
         } catch (SecurityException ex) {}
 
         LogSupport.log("MimetypesFileTypeMap: load SYS");
- if (confDir != null) {
+ try {
             // check system's home
- mf = loadFile(confDir + "mime.types");
- if (mf != null)
- dbv.addElement(mf);
- }
+ if (confDir != null) {
+ mf = loadFile(confDir + "mime.types");
+ if (mf != null)
+ dbv.addElement(mf);
+ }
+ } catch (SecurityException ex) {}
 
         LogSupport.log("MimetypesFileTypeMap: load JAR");
         // load from the app's jar file


diff -r 73d997261186 -r 619dd8dd6335 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Tue Feb 21 12:09:34 2017 -0800
+++ b/doc/release/CHANGES.txt Tue Feb 21 14:36:18 2017 -0800
@@ -49,6 +49,7 @@
 K 8810 MimeUtility should treat GB2312 as one of the supersets GBK or GB18030
 K 8822 Flags convenience methods
 K 8833 SMTP support for the CHUNKING extension of RFC 3030
+K 8846 MimeUtility.unfold squashes multiple spaces
 
 
                   CHANGES IN THE 1.5.6 RELEASE

diff -r 73d997261186 -r 619dd8dd6335 mail/src/main/java/javax/mail/internet/MimeUtility.java
--- a/mail/src/main/java/javax/mail/internet/MimeUtility.java Tue Feb 21 12:09:34 2017 -0800
+++ b/mail/src/main/java/javax/mail/internet/MimeUtility.java Tue Feb 21 14:36:18 2017 -0800
@@ -1164,40 +1164,35 @@
         int i;
         while ((i = indexOfAny(s, "\r\n")) >= 0) {
             int start = i;
- int l = s.length();
+ int slen = s.length();
             i++; // skip CR or NL
- if (i < l && s.charAt(i - 1) == '\r' && s.charAt(i) == '\n')
+ if (i < slen && s.charAt(i - 1) == '\r' && s.charAt(i) == '\n')
                 i++; // skip LF
- if (start == 0 || s.charAt(start - 1) != '\\') {
- char c;
- // if next line starts with whitespace, skip all of it
- // XXX - always has to be true?
- if (i < l && ((c = s.charAt(i)) == ' ' || c == '\t')) {
- i++; // skip whitespace
- while (i < l && ((c = s.charAt(i)) == ' ' || c == '\t'))
- i++;
- if (sb == null)
- sb = new StringBuffer(s.length());
- if (start != 0) {
- sb.append(s.substring(0, start));
- sb.append(' ');
- }
- s = s.substring(i);
- continue;
- }
- // it's not a continuation line, just leave it in
- if (sb == null)
- sb = new StringBuffer(s.length());
- sb.append(s.substring(0, i));
- s = s.substring(i);
- } else {
- // there's a backslash at "start - 1"
+ if (start > 0 && s.charAt(start - 1) == '\\') {
+ // there's a backslash before the line break
                 // strip it out, but leave in the line break
                 if (sb == null)
                     sb = new StringBuffer(s.length());
                 sb.append(s.substring(0, start - 1));
                 sb.append(s.substring(start, i));
                 s = s.substring(i);
+ } else {
+ char c;
+ // if next line starts with whitespace,
+ // or at the end of the string, remove the line break
+ // XXX - next line should always start with whitespace
+ if (i >= slen || (c = s.charAt(i)) == ' ' || c == '\t') {
+ if (sb == null)
+ sb = new StringBuffer(s.length());
+ sb.append(s.substring(0, start));
+ s = s.substring(i);
+ } else {
+ // it's not a continuation line, just leave in the newline
+ if (sb == null)
+ sb = new StringBuffer(s.length());
+ sb.append(s.substring(0, i));
+ s = s.substring(i);
+ }
             }
         }
         if (sb != null) {

diff -r 73d997261186 -r 619dd8dd6335 mail/src/test/resources/javax/mail/internet/addrlist
--- a/mail/src/test/resources/javax/mail/internet/addrlist Tue Feb 21 12:09:34 2017 -0800
+++ b/mail/src/test/resources/javax/mail/internet/addrlist Tue Feb 21 14:36:18 2017 -0800
@@ -2,7 +2,7 @@
 
     DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
- Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 1997-2017 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
@@ -321,7 +321,7 @@
         Smith" <jsmith@
         crappy-email-archiving-company.com>
 Expect: 1
- jsmith@ crappy-email-archiving-company.com
+ jsmith@ crappy-email-archiving-company.com
 Header: false
 To: (Test) <djoe_at_zimbra.com>,djoe_at_zimbra.com (Test)
 Expect: 2
@@ -749,7 +749,7 @@
         streetwalker!from, streetwalker!id, streetwalker!oak.sunecd.com,
         streetwalker!root, streetwalker!rr, streetwalker!streetwalker.sun.com
 Expect: 24
- 55:55_at_07, 57:18_at_07, 36:39_at_10, 36:42_at_10, streetwalker!1987, streetwalker!5, streetwalker!87, <8710051455.AA08484_at_oak.sunecd.com>, <oak!kinnear>, streetwalker!AA00159;
+ 55:55_at_07, 57:18_at_07, 36:39_at_10, 36:42_at_10, streetwalker!1987, streetwalker!5, streetwalker!87, <8710051455.AA08484_at_oak.sunecd.com>, <oak!kinnear>, streetwalker!AA00159;
         streetwalker!AA08484
         streetwalker!AA16178
         streetwalker!Date:

diff -r 73d997261186 -r 619dd8dd6335 mail/src/test/resources/javax/mail/internet/folddata
--- a/mail/src/test/resources/javax/mail/internet/folddata Tue Feb 21 12:09:34 2017 -0800
+++ b/mail/src/test/resources/javax/mail/internet/folddata Tue Feb 21 14:36:18 2017 -0800
@@ -1,7 +1,7 @@
 #
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 #
-# Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 1997-2017 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
@@ -136,6 +136,12 @@
 EXPECT
 a
  b$
+UNFOLD
+\
+ a$
+EXPECT
+
+ a$
 #
 # Test with leading and trailing newlines
 # XXX - Should trailing whitespace always be removed,
@@ -145,7 +151,7 @@
 
  a$
 EXPECT
-a$
+ a$
 UNFOLD
 
 a$
@@ -156,7 +162,7 @@
 
  a$
 EXPECT
-a$
+ a$
 UNFOLD
 
 a$
@@ -167,8 +173,7 @@
 a
 $
 EXPECT
-a
-$
+a$
 UNFOLD
 a
  $
@@ -259,12 +264,12 @@
 a
   b$
 EXPECT
-a b$
+a b$
 UNFOLD
 a
         b$
 EXPECT
-a b$
+a b$
 #
 # Fold with embedded newlines
 #