diff --git a/app/build.gradle b/app/build.gradle
index fb3058aa02..37fe359d4b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -296,7 +296,7 @@ dependencies {
// https://javaee.github.io/javamail/
// https://projects.eclipse.org/projects/ee4j.javamail
// https://mvnrepository.com/artifact/com.sun.mail
- implementation "com.sun.mail:android-mail:$javamail_version"
+ //implementation "com.sun.mail:android-mail:$javamail_version"
implementation "com.sun.mail:android-activation:$javamail_version"
// https://jsoup.org/news/
diff --git a/app/src/main/java/com/sun/mail/auth/MD4.java b/app/src/main/java/com/sun/mail/auth/MD4.java
new file mode 100644
index 0000000000..566de43f1e
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/auth/MD4.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2005, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/*
+ * Copied from OpenJDK with permission.
+ */
+
+package com.sun.mail.auth;
+
+import java.security.*;
+
+//import static sun.security.provider.ByteArrayAccess.*;
+
+/**
+ * The MD4 class is used to compute an MD4 message digest over a given
+ * buffer of bytes. It is an implementation of the RSA Data Security Inc
+ * MD4 algorithim as described in internet RFC 1320.
+ *
+ * @author Andreas Sterbenz
+ * @author Bill Shannon (adapted for Jakarta Mail)
+ */
+public final class MD4 {
+
+ // state of this object
+ private final int[] state;
+ // temporary buffer, used by implCompress()
+ private final int[] x;
+
+ // size of the input to the compression function in bytes
+ private static final int blockSize = 64;
+
+ // buffer to store partial blocks, blockSize bytes large
+ private final byte[] buffer = new byte[blockSize];
+ // offset into buffer
+ private int bufOfs;
+
+ // number of bytes processed so far.
+ // also used as a flag to indicate reset status
+ // -1: need to call engineReset() before next call to update()
+ // 0: is already reset
+ private long bytesProcessed;
+
+ // rotation constants
+ private static final int S11 = 3;
+ private static final int S12 = 7;
+ private static final int S13 = 11;
+ private static final int S14 = 19;
+ private static final int S21 = 3;
+ private static final int S22 = 5;
+ private static final int S23 = 9;
+ private static final int S24 = 13;
+ private static final int S31 = 3;
+ private static final int S32 = 9;
+ private static final int S33 = 11;
+ private static final int S34 = 15;
+
+ private static final byte[] padding;
+
+ static {
+ padding = new byte[136];
+ padding[0] = (byte)0x80;
+ }
+
+ /**
+ * Standard constructor, creates a new MD4 instance.
+ */
+ public MD4() {
+ state = new int[4];
+ x = new int[16];
+ implReset();
+ }
+
+ /**
+ * Compute and return the message digest of the input byte array.
+ *
+ * @param in the input byte array
+ * @return the message digest byte array
+ */
+ public byte[] digest(byte[] in) {
+ implReset();
+ engineUpdate(in, 0, in.length);
+ byte[] out = new byte[16];
+ implDigest(out, 0);
+ return out;
+ }
+
+ /**
+ * Reset the state of this object.
+ */
+ private void implReset() {
+ // Load magic initialization constants.
+ state[0] = 0x67452301;
+ state[1] = 0xefcdab89;
+ state[2] = 0x98badcfe;
+ state[3] = 0x10325476;
+ bufOfs = 0;
+ bytesProcessed = 0;
+ }
+
+ /**
+ * Perform the final computations, any buffered bytes are added
+ * to the digest, the count is added to the digest, and the resulting
+ * digest is stored.
+ */
+ private void implDigest(byte[] out, int ofs) {
+ long bitsProcessed = bytesProcessed << 3;
+
+ int index = (int)bytesProcessed & 0x3f;
+ int padLen = (index < 56) ? (56 - index) : (120 - index);
+ engineUpdate(padding, 0, padLen);
+
+ //i2bLittle4((int)bitsProcessed, buffer, 56);
+ //i2bLittle4((int)(bitsProcessed >>> 32), buffer, 60);
+ buffer[56] = (byte)bitsProcessed;
+ buffer[57] = (byte)(bitsProcessed>>8);
+ buffer[58] = (byte)(bitsProcessed>>16);
+ buffer[59] = (byte)(bitsProcessed>>24);
+ buffer[60] = (byte)(bitsProcessed>>32);
+ buffer[61] = (byte)(bitsProcessed>>40);
+ buffer[62] = (byte)(bitsProcessed>>48);
+ buffer[63] = (byte)(bitsProcessed>>56);
+ implCompress(buffer, 0);
+
+ //i2bLittle(state, 0, out, ofs, 16);
+ for (int i = 0; i < state.length; i++) {
+ int x = state[i];
+ out[ofs++] = (byte)x;
+ out[ofs++] = (byte)(x>>8);
+ out[ofs++] = (byte)(x>>16);
+ out[ofs++] = (byte)(x>>24);
+ }
+ }
+
+ private void engineUpdate(byte[] b, int ofs, int len) {
+ if (len == 0) {
+ return;
+ }
+ if ((ofs < 0) || (len < 0) || (ofs > b.length - len)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ if (bytesProcessed < 0) {
+ implReset();
+ }
+ bytesProcessed += len;
+ // if buffer is not empty, we need to fill it before proceeding
+ if (bufOfs != 0) {
+ int n = Math.min(len, blockSize - bufOfs);
+ System.arraycopy(b, ofs, buffer, bufOfs, n);
+ bufOfs += n;
+ ofs += n;
+ len -= n;
+ if (bufOfs >= blockSize) {
+ // compress completed block now
+ implCompress(buffer, 0);
+ bufOfs = 0;
+ }
+ }
+ // compress complete blocks
+ while (len >= blockSize) {
+ implCompress(b, ofs);
+ len -= blockSize;
+ ofs += blockSize;
+ }
+ // copy remainder to buffer
+ if (len > 0) {
+ System.arraycopy(b, ofs, buffer, 0, len);
+ bufOfs = len;
+ }
+ }
+
+ private static int FF(int a, int b, int c, int d, int x, int s) {
+ a += ((b & c) | ((~b) & d)) + x;
+ return ((a << s) | (a >>> (32 - s)));
+ }
+
+ private static int GG(int a, int b, int c, int d, int x, int s) {
+ a += ((b & c) | (b & d) | (c & d)) + x + 0x5a827999;
+ return ((a << s) | (a >>> (32 - s)));
+ }
+
+ private static int HH(int a, int b, int c, int d, int x, int s) {
+ a += ((b ^ c) ^ d) + x + 0x6ed9eba1;
+ return ((a << s) | (a >>> (32 - s)));
+ }
+
+ /**
+ * This is where the functions come together as the generic MD4
+ * transformation operation. It consumes 64
+ * bytes from the buffer, beginning at the specified offset.
+ */
+ private void implCompress(byte[] buf, int ofs) {
+ //b2iLittle64(buf, ofs, x);
+ for (int xfs = 0; xfs < x.length; xfs++) {
+ x[xfs] = (buf[ofs] & 0xff) | ((buf[ofs+1] & 0xff) << 8) |
+ ((buf[ofs+2] & 0xff) << 16) | ((buf[ofs+3] & 0xff) << 24);
+ ofs += 4;
+ }
+
+ int a = state[0];
+ int b = state[1];
+ int c = state[2];
+ int d = state[3];
+
+ /* Round 1 */
+ a = FF (a, b, c, d, x[ 0], S11); /* 1 */
+ d = FF (d, a, b, c, x[ 1], S12); /* 2 */
+ c = FF (c, d, a, b, x[ 2], S13); /* 3 */
+ b = FF (b, c, d, a, x[ 3], S14); /* 4 */
+ a = FF (a, b, c, d, x[ 4], S11); /* 5 */
+ d = FF (d, a, b, c, x[ 5], S12); /* 6 */
+ c = FF (c, d, a, b, x[ 6], S13); /* 7 */
+ b = FF (b, c, d, a, x[ 7], S14); /* 8 */
+ a = FF (a, b, c, d, x[ 8], S11); /* 9 */
+ d = FF (d, a, b, c, x[ 9], S12); /* 10 */
+ c = FF (c, d, a, b, x[10], S13); /* 11 */
+ b = FF (b, c, d, a, x[11], S14); /* 12 */
+ a = FF (a, b, c, d, x[12], S11); /* 13 */
+ d = FF (d, a, b, c, x[13], S12); /* 14 */
+ c = FF (c, d, a, b, x[14], S13); /* 15 */
+ b = FF (b, c, d, a, x[15], S14); /* 16 */
+
+ /* Round 2 */
+ a = GG (a, b, c, d, x[ 0], S21); /* 17 */
+ d = GG (d, a, b, c, x[ 4], S22); /* 18 */
+ c = GG (c, d, a, b, x[ 8], S23); /* 19 */
+ b = GG (b, c, d, a, x[12], S24); /* 20 */
+ a = GG (a, b, c, d, x[ 1], S21); /* 21 */
+ d = GG (d, a, b, c, x[ 5], S22); /* 22 */
+ c = GG (c, d, a, b, x[ 9], S23); /* 23 */
+ b = GG (b, c, d, a, x[13], S24); /* 24 */
+ a = GG (a, b, c, d, x[ 2], S21); /* 25 */
+ d = GG (d, a, b, c, x[ 6], S22); /* 26 */
+ c = GG (c, d, a, b, x[10], S23); /* 27 */
+ b = GG (b, c, d, a, x[14], S24); /* 28 */
+ a = GG (a, b, c, d, x[ 3], S21); /* 29 */
+ d = GG (d, a, b, c, x[ 7], S22); /* 30 */
+ c = GG (c, d, a, b, x[11], S23); /* 31 */
+ b = GG (b, c, d, a, x[15], S24); /* 32 */
+
+ /* Round 3 */
+ a = HH (a, b, c, d, x[ 0], S31); /* 33 */
+ d = HH (d, a, b, c, x[ 8], S32); /* 34 */
+ c = HH (c, d, a, b, x[ 4], S33); /* 35 */
+ b = HH (b, c, d, a, x[12], S34); /* 36 */
+ a = HH (a, b, c, d, x[ 2], S31); /* 37 */
+ d = HH (d, a, b, c, x[10], S32); /* 38 */
+ c = HH (c, d, a, b, x[ 6], S33); /* 39 */
+ b = HH (b, c, d, a, x[14], S34); /* 40 */
+ a = HH (a, b, c, d, x[ 1], S31); /* 41 */
+ d = HH (d, a, b, c, x[ 9], S32); /* 42 */
+ c = HH (c, d, a, b, x[ 5], S33); /* 43 */
+ b = HH (b, c, d, a, x[13], S34); /* 44 */
+ a = HH (a, b, c, d, x[ 3], S31); /* 45 */
+ d = HH (d, a, b, c, x[11], S32); /* 46 */
+ c = HH (c, d, a, b, x[ 7], S33); /* 47 */
+ b = HH (b, c, d, a, x[15], S34); /* 48 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/auth/Ntlm.java b/app/src/main/java/com/sun/mail/auth/Ntlm.java
new file mode 100644
index 0000000000..882776fcca
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/auth/Ntlm.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2005, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/*
+ * Copied from OpenJDK with permission.
+ */
+
+package com.sun.mail.auth;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.PrintStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+import java.util.Random;
+import java.util.logging.Level;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.MailLogger;
+
+
+/**
+ * NTLMAuthentication:
+ *
+ * @author Michael McMahon
+ * @author Bill Shannon (adapted for Jakarta Mail)
+ */
+public class Ntlm {
+
+ private byte[] type1;
+ private byte[] type3;
+
+ private SecretKeyFactory fac;
+ private Cipher cipher;
+ private MD4 md4;
+ private String hostname;
+ private String ntdomain;
+ private String username;
+ private String password;
+
+ private Mac hmac;
+
+ private MailLogger logger;
+
+ // NTLM flags, as defined in Microsoft NTLM spec
+ // https://msdn.microsoft.com/en-us/library/cc236621.aspx
+ private static final int NTLMSSP_NEGOTIATE_UNICODE = 0x00000001;
+ private static final int NTLMSSP_NEGOTIATE_OEM = 0x00000002;
+ private static final int NTLMSSP_REQUEST_TARGET = 0x00000004;
+ private static final int NTLMSSP_NEGOTIATE_SIGN = 0x00000010;
+ private static final int NTLMSSP_NEGOTIATE_SEAL = 0x00000020;
+ private static final int NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040;
+ private static final int NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080;
+ private static final int NTLMSSP_NEGOTIATE_NTLM = 0x00000200;
+ private static final int NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000;
+ private static final int NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000;
+ private static final int NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
+ private static final int NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000;
+ private static final int NTLMSSP_TARGET_TYPE_SERVER = 0x00020000;
+ private static final int NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000;
+ private static final int NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000;
+ private static final int NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000;
+ private static final int NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000;
+ private static final int NTLMSSP_NEGOTIATE_VERSION = 0x02000000;
+ private static final int NTLMSSP_NEGOTIATE_128 = 0x20000000;
+ private static final int NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000;
+ private static final int NTLMSSP_NEGOTIATE_56 = 0x80000000;
+
+ private static final byte RESPONSERVERSION = 1;
+ private static final byte HIRESPONSERVERSION = 1;
+ private static final byte[] Z6 = new byte[] { 0, 0, 0, 0, 0, 0 };
+ private static final byte[] Z4 = new byte[] { 0, 0, 0, 0 };
+
+ private void init0() {
+ type1 = new byte[256]; // hopefully large enough
+ type3 = new byte[512]; // ditto
+ System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,1}, 0,
+ type1, 0, 9);
+ System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,3}, 0,
+ type3, 0, 9);
+
+ try {
+ fac = SecretKeyFactory.getInstance("DES");
+ cipher = Cipher.getInstance("DES/ECB/NoPadding");
+ md4 = new MD4();
+ } catch (NoSuchPaddingException e) {
+ assert false;
+ } catch (NoSuchAlgorithmException e) {
+ assert false;
+ }
+ };
+
+ /**
+ * Create an NTLM authenticator.
+ * Username may be specified as domain\\username in the Authenticator.
+ * If this notation is not used, then the domain will be taken
+ * from the ntdomain parameter.
+ *
+ * @param ntdomain the NT domain
+ * @param hostname the host name
+ * @param username the user name
+ * @param password the password
+ * @param logger the MailLogger
+ */
+ public Ntlm(String ntdomain, String hostname, String username,
+ String password, MailLogger logger) {
+ int i = hostname.indexOf('.');
+ if (i != -1) {
+ hostname = hostname.substring(0, i);
+ }
+ i = username.indexOf('\\');
+ if (i != -1) {
+ ntdomain = username.substring(0, i).toUpperCase(Locale.ENGLISH);
+ username = username.substring(i+1);
+ } else if (ntdomain == null) {
+ ntdomain = "";
+ }
+ this.ntdomain = ntdomain;
+ this.hostname = hostname;
+ this.username = username;
+ this.password = password;
+ this.logger = logger.getLogger(this.getClass(), "DEBUG NTLM");
+ init0();
+ }
+
+ private void copybytes(byte[] dest, int destpos, String src, String enc) {
+ try {
+ byte[] x = src.getBytes(enc);
+ System.arraycopy(x, 0, dest, destpos, x.length);
+ } catch (UnsupportedEncodingException e) {
+ assert false;
+ }
+ }
+
+ // for compatibility, just in case
+ public String generateType1Msg(int flags) {
+ return generateType1Msg(flags, false);
+ }
+
+ public String generateType1Msg(int flags, boolean v2) {
+ int dlen = ntdomain.length();
+ int type1flags =
+ NTLMSSP_NEGOTIATE_UNICODE |
+ NTLMSSP_NEGOTIATE_OEM |
+ NTLMSSP_NEGOTIATE_NTLM |
+ NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED |
+ NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
+ flags;
+ if (dlen != 0)
+ type1flags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
+ if (v2)
+ type1flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
+ writeInt(type1, 12, type1flags);
+ type1[28] = (byte) 0x20; // host name offset
+ writeShort(type1, 16, dlen);
+ writeShort(type1, 18, dlen);
+
+ int hlen = hostname.length();
+ writeShort(type1, 24, hlen);
+ writeShort(type1, 26, hlen);
+
+ copybytes(type1, 32, hostname, "iso-8859-1");
+ copybytes(type1, hlen+32, ntdomain, "iso-8859-1");
+ writeInt(type1, 20, hlen+32);
+
+ byte[] msg = new byte[32 + hlen + dlen];
+ System.arraycopy(type1, 0, msg, 0, 32 + hlen + dlen);
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("type 1 message: " + toHex(msg));
+
+ String result = null;
+ try {
+ result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ assert false;
+ }
+ return result;
+ }
+
+ /**
+ * Convert a 7 byte array to an 8 byte array (for a des key with parity).
+ * Input starts at offset off.
+ */
+ private byte[] makeDesKey(byte[] input, int off) {
+ int[] in = new int[input.length];
+ for (int i = 0; i < in.length; i++) {
+ in[i] = input[i] < 0 ? input[i] + 256: input[i];
+ }
+ byte[] out = new byte[8];
+ out[0] = (byte)in[off+0];
+ out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
+ out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
+ out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
+ out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
+ out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
+ out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
+ out[7] = (byte)((in[off+6] << 1) & 0xFF);
+ return out;
+ }
+
+ /**
+ * Compute hash-based message authentication code for NTLMv2.
+ */
+ private byte[] hmacMD5(byte[] key, byte[] text) {
+ try {
+ if (hmac == null)
+ hmac = Mac.getInstance("HmacMD5");
+ } catch (NoSuchAlgorithmException ex) {
+ throw new AssertionError();
+ }
+ try {
+ byte[] nk = new byte[16];
+ System.arraycopy(key, 0, nk, 0, key.length > 16 ? 16 : key.length);
+ SecretKeySpec skey = new SecretKeySpec(nk, "HmacMD5");
+ hmac.init(skey);
+ return hmac.doFinal(text);
+ } catch (InvalidKeyException ex) {
+ assert false;
+ } catch (RuntimeException e) {
+ assert false;
+ }
+ return null;
+ }
+
+ private byte[] calcLMHash() throws GeneralSecurityException {
+ byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
+ byte[] pwb = null;
+ try {
+ pwb = password.toUpperCase(Locale.ENGLISH).getBytes("iso-8859-1");
+ } catch (UnsupportedEncodingException ex) {
+ // should never happen
+ assert false;
+ }
+ byte[] pwb1 = new byte[14];
+ int len = password.length();
+ if (len > 14)
+ len = 14;
+ System.arraycopy(pwb, 0, pwb1, 0, len); /* Zero padded */
+
+ DESKeySpec dks1 = new DESKeySpec(makeDesKey(pwb1, 0));
+ DESKeySpec dks2 = new DESKeySpec(makeDesKey(pwb1, 7));
+
+ SecretKey key1 = fac.generateSecret(dks1);
+ SecretKey key2 = fac.generateSecret(dks2);
+ cipher.init(Cipher.ENCRYPT_MODE, key1);
+ byte[] out1 = cipher.doFinal(magic, 0, 8);
+ cipher.init(Cipher.ENCRYPT_MODE, key2);
+ byte[] out2 = cipher.doFinal(magic, 0, 8);
+
+ byte[] result = new byte [21];
+ System.arraycopy(out1, 0, result, 0, 8);
+ System.arraycopy(out2, 0, result, 8, 8);
+ return result;
+ }
+
+ private byte[] calcNTHash() throws GeneralSecurityException {
+ byte[] pw = null;
+ try {
+ pw = password.getBytes("UnicodeLittleUnmarked");
+ } catch (UnsupportedEncodingException e) {
+ assert false;
+ }
+ byte[] out = md4.digest(pw);
+ byte[] result = new byte[21];
+ System.arraycopy(out, 0, result, 0, 16);
+ return result;
+ }
+
+ /*
+ * Key is a 21 byte array. Split it into 3 7 byte chunks,
+ * convert each to 8 byte DES keys, encrypt the text arg with
+ * each key and return the three results in a sequential [].
+ */
+ private byte[] calcResponse(byte[] key, byte[] text)
+ throws GeneralSecurityException {
+ assert key.length == 21;
+ DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
+ DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
+ DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
+ SecretKey key1 = fac.generateSecret(dks1);
+ SecretKey key2 = fac.generateSecret(dks2);
+ SecretKey key3 = fac.generateSecret(dks3);
+ cipher.init(Cipher.ENCRYPT_MODE, key1);
+ byte[] out1 = cipher.doFinal(text, 0, 8);
+ cipher.init(Cipher.ENCRYPT_MODE, key2);
+ byte[] out2 = cipher.doFinal(text, 0, 8);
+ cipher.init(Cipher.ENCRYPT_MODE, key3);
+ byte[] out3 = cipher.doFinal(text, 0, 8);
+ byte[] result = new byte[24];
+ System.arraycopy(out1, 0, result, 0, 8);
+ System.arraycopy(out2, 0, result, 8, 8);
+ System.arraycopy(out3, 0, result, 16, 8);
+ return result;
+ }
+
+ /*
+ * Calculate the NTLMv2 response based on the nthash, additional data,
+ * and the original challenge.
+ */
+ private byte[] calcV2Response(byte[] nthash, byte[] blob, byte[] challenge)
+ throws GeneralSecurityException {
+ byte[] txt = null;
+ try {
+ txt = (username.toUpperCase(Locale.ENGLISH) + ntdomain).
+ getBytes("UnicodeLittleUnmarked");
+ } catch (UnsupportedEncodingException ex) {
+ // should never happen
+ assert false;
+ }
+ byte[] ntlmv2hash = hmacMD5(nthash, txt);
+ byte[] cb = new byte[blob.length + 8];
+ System.arraycopy(challenge, 0, cb, 0, 8);
+ System.arraycopy(blob, 0, cb, 8, blob.length);
+ byte[] result = new byte[blob.length + 16];
+ System.arraycopy(hmacMD5(ntlmv2hash, cb), 0, result, 0, 16);
+ System.arraycopy(blob, 0, result, 16, blob.length);
+ return result;
+ }
+
+ public String generateType3Msg(String type2msg) {
+ try {
+
+ /* First decode the type2 message to get the server challenge */
+ /* challenge is located at type2[24] for 8 bytes */
+ byte[] type2 = null;
+ try {
+ type2 = BASE64DecoderStream.decode(type2msg.getBytes("us-ascii"));
+ } catch (UnsupportedEncodingException ex) {
+ // should never happen
+ assert false;
+ }
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("type 2 message: " + toHex(type2));
+
+ byte[] challenge = new byte[8];
+ System.arraycopy(type2, 24, challenge, 0, 8);
+
+ int type3flags =
+ NTLMSSP_NEGOTIATE_UNICODE |
+ NTLMSSP_NEGOTIATE_NTLM |
+ NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+
+ int ulen = username.length()*2;
+ writeShort(type3, 36, ulen);
+ writeShort(type3, 38, ulen);
+ int dlen = ntdomain.length()*2;
+ writeShort(type3, 28, dlen);
+ writeShort(type3, 30, dlen);
+ int hlen = hostname.length()*2;
+ writeShort(type3, 44, hlen);
+ writeShort(type3, 46, hlen);
+
+ int l = 64;
+ copybytes(type3, l, ntdomain, "UnicodeLittleUnmarked");
+ writeInt(type3, 32, l);
+ l += dlen;
+ copybytes(type3, l, username, "UnicodeLittleUnmarked");
+ writeInt(type3, 40, l);
+ l += ulen;
+ copybytes(type3, l, hostname, "UnicodeLittleUnmarked");
+ writeInt(type3, 48, l);
+ l += hlen;
+
+ byte[] msg = null;
+ byte[] lmresponse = null;
+ byte[] ntresponse = null;
+ int flags = readInt(type2, 20);
+
+ // did the server agree to NTLMv2?
+ if ((flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0) {
+ // yes, create an NTLMv2 response
+ logger.fine("Using NTLMv2");
+ type3flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
+ byte[] nonce = new byte[8];
+ // XXX - allow user to specify Random instance via properties?
+ (new Random()).nextBytes(nonce);
+ byte[] nthash = calcNTHash();
+ lmresponse = calcV2Response(nthash, nonce, challenge);
+ byte[] targetInfo = new byte[0];
+ if ((flags & NTLMSSP_NEGOTIATE_TARGET_INFO) != 0) {
+ int tlen = readShort(type2, 40);
+ int toff = readInt(type2, 44);
+ targetInfo = new byte[tlen];
+ System.arraycopy(type2, toff, targetInfo, 0, tlen);
+ }
+ byte[] blob = new byte[32 + targetInfo.length];
+ blob[0] = RESPONSERVERSION;
+ blob[1] = HIRESPONSERVERSION;
+ System.arraycopy(Z6, 0, blob, 2, 6);
+ // convert time to NT format
+ long now = (System.currentTimeMillis() + 11644473600000L) * 10000L;
+ for (int i = 0; i < 8; i++) {
+ blob[8 + i] = (byte)(now & 0xff);
+ now >>= 8;
+ }
+ System.arraycopy(nonce, 0, blob, 16, 8);
+ System.arraycopy(Z4, 0, blob, 24, 4);
+ System.arraycopy(targetInfo, 0, blob, 28, targetInfo.length);
+ System.arraycopy(Z4, 0, blob, 28 + targetInfo.length, 4);
+ ntresponse = calcV2Response(nthash, blob, challenge);
+ } else {
+ byte[] lmhash = calcLMHash();
+ lmresponse = calcResponse(lmhash, challenge);
+ byte[] nthash = calcNTHash();
+ ntresponse = calcResponse(nthash, challenge);
+ }
+ System.arraycopy(lmresponse, 0, type3, l, lmresponse.length);
+ writeShort(type3, 12, lmresponse.length);
+ writeShort(type3, 14, lmresponse.length);
+ writeInt(type3, 16, l);
+ l += 24;
+ System.arraycopy(ntresponse, 0, type3, l, ntresponse.length);
+ writeShort(type3, 20, ntresponse.length);
+ writeShort(type3, 22, ntresponse.length);
+ writeInt(type3, 24, l);
+ l += ntresponse.length;
+ writeShort(type3, 56, l);
+
+ msg = new byte[l];
+ System.arraycopy(type3, 0, msg, 0, l);
+
+ writeInt(type3, 60, type3flags);
+
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("type 3 message: " + toHex(msg));
+
+ String result = null;
+ try {
+ result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ assert false;
+ }
+ return result;
+
+ } catch (GeneralSecurityException ex) {
+ // should never happen
+ logger.log(Level.FINE, "GeneralSecurityException", ex);
+ return ""; // will fail later
+ }
+ }
+
+ private static int readShort(byte[] b, int off) {
+ return (((int)b[off]) & 0xff) |
+ ((((int)b[off+1]) & 0xff) << 8);
+ }
+
+ private void writeShort(byte[] b, int off, int data) {
+ b[off] = (byte) (data & 0xff);
+ b[off+1] = (byte) ((data >> 8) & 0xff);
+ }
+
+ private static int readInt(byte[] b, int off) {
+ return (((int)b[off]) & 0xff) |
+ ((((int)b[off+1]) & 0xff) << 8) |
+ ((((int)b[off+2]) & 0xff) << 16) |
+ ((((int)b[off+3]) & 0xff) << 24);
+ }
+
+ private void writeInt(byte[] b, int off, int data) {
+ b[off] = (byte) (data & 0xff);
+ b[off+1] = (byte) ((data >> 8) & 0xff);
+ b[off+2] = (byte) ((data >> 16) & 0xff);
+ b[off+3] = (byte) ((data >> 24) & 0xff);
+ }
+
+ private static char[] hex =
+ { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
+
+ private static String toHex(byte[] b) {
+ StringBuilder sb = new StringBuilder(b.length * 3);
+ for (int i = 0; i < b.length; i++)
+ sb.append(hex[(b[i]>>4)&0xF]).append(hex[b[i]&0xF]).append(' ');
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/auth/package.html b/app/src/main/java/com/sun/mail/auth/package.html
new file mode 100644
index 0000000000..476f2ee06b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/auth/package.html
@@ -0,0 +1,33 @@
+
+
+
+ items;
+
+ /**
+ * Constructor
+ */
+ public Argument() {
+ items = new ArrayList<>(1);
+ }
+
+ /**
+ * Append the given Argument to this Argument. All items
+ * from the source argument are copied into this destination
+ * argument.
+ *
+ * @param arg the Argument to append
+ * @return this
+ */
+ public Argument append(Argument arg) {
+ items.addAll(arg.items);
+ return this;
+ }
+
+ /**
+ * Write out given string as an ASTRING, depending on the type
+ * of the characters inside the string. The string should
+ * contain only ASCII characters.
+ *
+ * XXX: Hmm .. this should really be called writeASCII()
+ *
+ * @param s String to write out
+ * @return this
+ */
+ public Argument writeString(String s) {
+ items.add(new AString(ASCIIUtility.getBytes(s)));
+ return this;
+ }
+
+ /**
+ * 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
+ * @exception UnsupportedEncodingException for bad charset
+ */
+ public Argument writeString(String s, String charset)
+ throws UnsupportedEncodingException {
+ if (charset == null) // convenience
+ writeString(s);
+ else
+ items.add(new AString(s.getBytes(charset)));
+ return this;
+ }
+
+ /**
+ * 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.
+ *
+ * @param s String to write out
+ * @return this
+ * @since JavaMail 1.5.1
+ */
+ public Argument writeNString(String s) {
+ if (s == null)
+ items.add(new NString(null));
+ else
+ items.add(new NString(ASCIIUtility.getBytes(s)));
+ return this;
+ }
+
+ /**
+ * 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
+ * @exception UnsupportedEncodingException for bad charset
+ * @since JavaMail 1.5.1
+ */
+ public Argument writeNString(String s, String charset)
+ throws UnsupportedEncodingException {
+ 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;
+ }
+
+ /**
+ * 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
+ */
+ public Argument writeBytes(byte[] b) {
+ items.add(b);
+ return this;
+ }
+
+ /**
+ * Write out given ByteArrayOutputStream as a Literal.
+ * @param b ByteArrayOutputStream to be written out.
+ * @return this
+ */
+ public Argument writeBytes(ByteArrayOutputStream b) {
+ items.add(b);
+ return this;
+ }
+
+ /**
+ * Write out given data as a literal.
+ * @param b Literal representing data to be written out.
+ * @return this
+ */
+ public Argument writeBytes(Literal b) {
+ items.add(b);
+ return this;
+ }
+
+ /**
+ * Write out given string as an Atom. Note that an Atom can contain only
+ * certain US-ASCII characters. No validation is done on the characters
+ * in the string.
+ * @param s String
+ * @return this
+ */
+ public Argument writeAtom(String s) {
+ items.add(new Atom(s));
+ return this;
+ }
+
+ /**
+ * Write out number.
+ * @param i number
+ * @return this
+ */
+ public Argument writeNumber(int i) {
+ items.add(Integer.valueOf(i));
+ return this;
+ }
+
+ /**
+ * Write out number.
+ * @param i number
+ * @return this
+ */
+ public Argument writeNumber(long i) {
+ items.add(Long.valueOf(i));
+ return this;
+ }
+
+ /**
+ * Write out as parenthesised list.
+ *
+ * @param c the Argument
+ * @return this
+ */
+ public Argument writeArgument(Argument c) {
+ items.add(c);
+ return this;
+ }
+
+ /*
+ * Write out all the buffered items into the output stream.
+ */
+ public void write(Protocol protocol)
+ throws IOException, ProtocolException {
+ int size = items != null ? items.size() : 0;
+ DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+
+ for (int i=0; i < size; i++) {
+ if (i > 0) // write delimiter if not the first item
+ os.write(' ');
+
+ Object o = items.get(i);
+ if (o instanceof Atom) {
+ os.writeBytes(((Atom)o).string);
+ } else if (o instanceof Number) {
+ os.writeBytes(((Number)o).toString());
+ } else if (o instanceof AString) {
+ astring(((AString)o).bytes, protocol);
+ } else if (o instanceof NString) {
+ nstring(((NString)o).bytes, protocol);
+ } else if (o instanceof byte[]) {
+ literal((byte[])o, protocol);
+ } else if (o instanceof ByteArrayOutputStream) {
+ literal((ByteArrayOutputStream)o, protocol);
+ } else if (o instanceof Literal) {
+ literal((Literal)o, protocol);
+ } else if (o instanceof Argument) {
+ os.write('('); // open parans
+ ((Argument)o).write(protocol);
+ os.write(')'); // close parans
+ }
+ }
+ }
+
+ /**
+ * Write out given String as either an Atom, QuotedString or Literal
+ */
+ private void astring(byte[] bytes, Protocol protocol)
+ throws IOException, ProtocolException {
+ nastring(bytes, protocol, false);
+ }
+
+ /**
+ * Write out given String as either NIL, QuotedString, or Literal.
+ */
+ private void nstring(byte[] bytes, Protocol protocol)
+ throws IOException, ProtocolException {
+ if (bytes == null) {
+ DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+ os.writeBytes("NIL");
+ } else
+ nastring(bytes, protocol, true);
+ }
+
+ private void nastring(byte[] bytes, Protocol protocol, boolean doQuote)
+ throws IOException, ProtocolException {
+ DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+ int len = bytes.length;
+
+ // If length is greater than 1024 bytes, send as literal
+ if (len > 1024) {
+ literal(bytes, protocol);
+ return;
+ }
+
+ // 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' ||
+ (!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 & 0xff) > 0177)) {
+ quote = true;
+ if (b == '"' || b == '\\') // need to escape these characters
+ escape = true;
+ }
+ }
+
+ /*
+ * Make sure the (case-independent) string "NIL" is always quoted,
+ * so as not to be confused with a real NIL (handled above in nstring).
+ * This is more than is necessary, but it's rare to begin with and
+ * this makes it safer than doing the test in nstring above in case
+ * some code calls writeString when it should call writeNString.
+ */
+ if (!quote && bytes.length == 3 &&
+ (bytes[0] == 'N' || bytes[0] == 'n') &&
+ (bytes[1] == 'I' || bytes[1] == 'i') &&
+ (bytes[2] == 'L' || bytes[2] == 'l'))
+ quote = true;
+
+ if (quote) // start quote
+ os.write('"');
+
+ if (escape) {
+ // already quoted
+ for (int i = 0; i < len; i++) {
+ b = bytes[i];
+ if (b == '"' || b == '\\')
+ os.write('\\');
+ os.write(b);
+ }
+ } else
+ os.write(bytes);
+
+
+ if (quote) // end quote
+ os.write('"');
+ }
+
+ /**
+ * Write out given byte[] as a literal
+ */
+ private void literal(byte[] b, Protocol protocol)
+ throws IOException, ProtocolException {
+ startLiteral(protocol, b.length).write(b);
+ }
+
+ /**
+ * Write out given ByteArrayOutputStream as a literal.
+ */
+ private void literal(ByteArrayOutputStream b, Protocol protocol)
+ throws IOException, ProtocolException {
+ b.writeTo(startLiteral(protocol, b.size()));
+ }
+
+ /**
+ * Write out given Literal as a literal.
+ */
+ private void literal(Literal b, Protocol protocol)
+ throws IOException, ProtocolException {
+ b.writeTo(startLiteral(protocol, b.size()));
+ }
+
+ private OutputStream startLiteral(Protocol protocol, int size)
+ throws IOException, ProtocolException {
+ DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+ boolean nonSync = protocol.supportsNonSyncLiterals();
+
+ os.write('{');
+ os.writeBytes(Integer.toString(size));
+ if (nonSync) // server supports non-sync literals
+ os.writeBytes("+}\r\n");
+ else
+ os.writeBytes("}\r\n");
+ os.flush();
+
+ // If we are using synchronized literals, wait for the server's
+ // continuation signal
+ if (!nonSync) {
+ for (; ;) {
+ Response r = protocol.readResponse();
+ if (r.isContinuation())
+ break;
+ if (r.isTagged())
+ throw new LiteralException(r);
+ // XXX - throw away untagged responses;
+ // violates IMAP spec, hope no servers do this
+ }
+ }
+ return os;
+ }
+}
+
+class Atom {
+ String string;
+
+ Atom(String s) {
+ string = s;
+ }
+}
+
+class AString {
+ byte[] bytes;
+
+ AString(byte[] b) {
+ bytes = b;
+ }
+}
+
+class NString {
+ byte[] bytes;
+
+ NString(byte[] b) {
+ bytes = b;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/BadCommandException.java b/app/src/main/java/com/sun/mail/iap/BadCommandException.java
new file mode 100644
index 0000000000..4a1b33bf6e
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/BadCommandException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class BadCommandException extends ProtocolException {
+
+ private static final long serialVersionUID = 5769722539397237515L;
+
+ /**
+ * Constructs an BadCommandException with no detail message.
+ */
+ public BadCommandException() {
+ super();
+ }
+
+ /**
+ * Constructs an BadCommandException with the specified detail message.
+ * @param s the detail message
+ */
+ public BadCommandException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an BadCommandException with the specified Response.
+ * @param r the Response
+ */
+ public BadCommandException(Response r) {
+ super(r);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ByteArray.java b/app/src/main/java/com/sun/mail/iap/ByteArray.java
new file mode 100644
index 0000000000..e05110bcaf
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ByteArray.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * A simple wrapper around a byte array, with a start position and
+ * count of bytes.
+ *
+ * @author John Mani
+ */
+
+public class ByteArray {
+ private byte[] bytes; // the byte array
+ private int start; // start position
+ private int count; // count of bytes
+
+ /**
+ * Constructor
+ *
+ * @param b the byte array to wrap
+ * @param start start position in byte array
+ * @param count number of bytes in byte array
+ */
+ public ByteArray(byte[] b, int start, int count) {
+ bytes = b;
+ this.start = start;
+ this.count = count;
+ }
+
+ /**
+ * Constructor that creates a byte array of the specified size.
+ *
+ * @param size the size of the ByteArray
+ * @since JavaMail 1.4.1
+ */
+ public ByteArray(int size) {
+ this(new byte[size], 0, size);
+ }
+
+ /**
+ * Returns the internal byte array. Note that this is a live
+ * reference to the actual data, not a copy.
+ *
+ * @return the wrapped byte array
+ */
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ /**
+ * Returns a new byte array that is a copy of the data.
+ *
+ * @return a new byte array with the bytes from start for count
+ */
+ public byte[] getNewBytes() {
+ byte[] b = new byte[count];
+ System.arraycopy(bytes, start, b, 0, count);
+ return b;
+ }
+
+ /**
+ * Returns the start position
+ *
+ * @return the start position
+ */
+ public int getStart() {
+ return start;
+ }
+
+ /**
+ * Returns the count of bytes
+ *
+ * @return the number of bytes
+ */
+ public int getCount() {
+ return count;
+ }
+
+ /**
+ * Set the count of bytes.
+ *
+ * @param count the number of bytes
+ * @since JavaMail 1.4.1
+ */
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ /**
+ * Returns a ByteArrayInputStream.
+ *
+ * @return the ByteArrayInputStream
+ */
+ public ByteArrayInputStream toByteArrayInputStream() {
+ return new ByteArrayInputStream(bytes, start, count);
+ }
+
+ /**
+ * Grow the byte array by incr bytes.
+ *
+ * @param incr how much to grow
+ * @since JavaMail 1.4.1
+ */
+ public void grow(int incr) {
+ byte[] nbuf = new byte[bytes.length + incr];
+ System.arraycopy(bytes, 0, nbuf, 0, bytes.length);
+ bytes = nbuf;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/CommandFailedException.java b/app/src/main/java/com/sun/mail/iap/CommandFailedException.java
new file mode 100644
index 0000000000..8c141e7ba5
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/CommandFailedException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class CommandFailedException extends ProtocolException {
+
+ private static final long serialVersionUID = 793932807880443631L;
+
+ /**
+ * Constructs an CommandFailedException with no detail message.
+ */
+ public CommandFailedException() {
+ super();
+ }
+
+ /**
+ * Constructs an CommandFailedException with the specified detail message.
+ * @param s the detail message
+ */
+ public CommandFailedException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an CommandFailedException with the specified Response.
+ * @param r the Response.
+ */
+ public CommandFailedException(Response r) {
+ super(r);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ConnectionException.java b/app/src/main/java/com/sun/mail/iap/ConnectionException.java
new file mode 100644
index 0000000000..12dd496106
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ConnectionException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ConnectionException extends ProtocolException {
+ private transient Protocol p;
+
+ private static final long serialVersionUID = 5749739604257464727L;
+
+ /**
+ * Constructs an ConnectionException with no detail message.
+ */
+ public ConnectionException() {
+ super();
+ }
+
+ /**
+ * Constructs an ConnectionException with the specified detail message.
+ *
+ * @param s the detail message
+ */
+ public ConnectionException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an ConnectionException with the specified Response.
+ *
+ * @param p the Protocol object
+ * @param r the Response
+ */
+ public ConnectionException(Protocol p, Response r) {
+ super(r);
+ this.p = p;
+ }
+
+ public Protocol getProtocol() {
+ return p;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/Literal.java b/app/src/main/java/com/sun/mail/iap/Literal.java
new file mode 100644
index 0000000000..14c592b274
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/Literal.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+
+/**
+ * An interface for objects that provide data dynamically for use in
+ * a literal protocol element.
+ *
+ * @author Bill Shannon
+ */
+
+public interface Literal {
+ /**
+ * Return the size of the data.
+ *
+ * @return the size of the data
+ */
+ public int size();
+
+ /**
+ * Write the data to the OutputStream.
+ *
+ * @param os the output stream
+ * @exception IOException for I/O errors
+ */
+ public void writeTo(OutputStream os) throws IOException;
+}
diff --git a/app/src/main/java/com/sun/mail/iap/LiteralException.java b/app/src/main/java/com/sun/mail/iap/LiteralException.java
new file mode 100644
index 0000000000..660747f442
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/LiteralException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author Bill Shannon
+ */
+
+public class LiteralException extends ProtocolException {
+
+ private static final long serialVersionUID = -6919179828339609913L;
+
+ /**
+ * Constructs a LiteralException with the specified Response object.
+ *
+ * @param r the response object
+ */
+ public LiteralException(Response r) {
+ super(r.toString());
+ response = r;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ParsingException.java b/app/src/main/java/com/sun/mail/iap/ParsingException.java
new file mode 100644
index 0000000000..a1e7168a8f
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ParsingException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ParsingException extends ProtocolException {
+
+ private static final long serialVersionUID = 7756119840142724839L;
+
+ /**
+ * Constructs an ParsingException with no detail message.
+ */
+ public ParsingException() {
+ super();
+ }
+
+ /**
+ * Constructs an ParsingException with the specified detail message.
+ * @param s the detail message
+ */
+ public ParsingException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an ParsingException with the specified Response.
+ * @param r the Response
+ */
+ public ParsingException(Response r) {
+ super(r);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/Protocol.java b/app/src/main/java/com/sun/mail/iap/Protocol.java
new file mode 100644
index 0000000000..b7bd688248
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/Protocol.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.util.Properties;
+import java.io.*;
+import java.nio.channels.SocketChannel;
+import java.net.*;
+import javax.net.ssl.SSLSocket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+
+/**
+ * General protocol handling code for IMAP-like protocols.
+ *
+ * The Protocol object is multithread safe.
+ *
+ * @author John Mani
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+
+public class Protocol {
+ protected String host;
+ private Socket socket;
+ // in case we turn on TLS, we'll need these later
+ protected boolean quote;
+ protected MailLogger logger;
+ protected MailLogger traceLogger;
+ protected Properties props;
+ protected String prefix;
+
+ private TraceInputStream traceInput; // the Tracer
+ private volatile ResponseInputStream input;
+
+ private TraceOutputStream traceOutput; // the Tracer
+ private volatile DataOutputStream output;
+
+ private int tagCounter = 0;
+ private final String tagPrefix;
+
+ private String localHostName;
+
+ private final List handlers
+ = new CopyOnWriteArrayList<>();
+
+ private volatile long timestamp;
+
+ // package private, to allow testing
+ static final AtomicInteger tagNum = new AtomicInteger();
+
+ private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
+
+ /**
+ * Constructor.
+ *
+ * Opens a connection to the given host at given port.
+ *
+ * @param host host to connect to
+ * @param port portnumber to connect to
+ * @param props Properties object used by this protocol
+ * @param prefix Prefix to prepend to property keys
+ * @param isSSL use SSL?
+ * @param logger log messages here
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public Protocol(String host, int port,
+ Properties props, String prefix,
+ boolean isSSL, MailLogger logger)
+ throws IOException, ProtocolException {
+ boolean connected = false; // did constructor succeed?
+ tagPrefix = computePrefix(props, prefix);
+ try {
+ this.host = host;
+ this.props = props;
+ this.prefix = prefix;
+ this.logger = logger;
+ traceLogger = logger.getSubLogger("protocol", null);
+
+ socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
+ quote = PropUtil.getBooleanProperty(props,
+ "mail.debug.quote", false);
+
+ initStreams();
+
+ // Read server greeting
+ processGreeting(readResponse());
+
+ timestamp = System.currentTimeMillis();
+
+ connected = true; // must be last statement in constructor
+ } finally {
+ /*
+ * If we get here because an exception was thrown, we need
+ * to disconnect to avoid leaving a connected socket that
+ * no one will be able to use because this object was never
+ * completely constructed.
+ */
+ if (!connected)
+ disconnect();
+ }
+ }
+
+ private void initStreams() throws IOException {
+ traceInput = new TraceInputStream(socket.getInputStream(), traceLogger);
+ traceInput.setQuote(quote);
+ input = new ResponseInputStream(traceInput);
+
+ traceOutput =
+ new TraceOutputStream(socket.getOutputStream(), traceLogger);
+ traceOutput.setQuote(quote);
+ output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+ }
+
+ /**
+ * Compute the tag prefix to be used for this connection.
+ * Start with "A" - "Z", then "AA" - "ZZ", and finally "AAA" - "ZZZ".
+ * Wrap around after that.
+ */
+ private String computePrefix(Properties props, String prefix) {
+ // XXX - in case someone depends on the tag prefix
+ if (PropUtil.getBooleanProperty(props,
+ prefix + ".reusetagprefix", false))
+ return "A";
+ // tag prefix, wrap around after three letters
+ int n = tagNum.getAndIncrement() % (26*26*26 + 26*26 + 26);
+ String tagPrefix;
+ if (n < 26)
+ tagPrefix = new String(new char[] { (char)('A' + n) });
+ else if (n < (26*26 + 26)) {
+ n -= 26;
+ tagPrefix = new String(new char[] {
+ (char)('A' + n/26), (char)('A' + n%26) });
+ } else {
+ n -= (26*26 + 26);
+ tagPrefix = new String(new char[] {
+ (char)('A' + n/(26*26)),
+ (char)('A' + (n%(26*26))/26),
+ (char)('A' + n%26) });
+ }
+ return tagPrefix;
+ }
+
+ /**
+ * Constructor for debugging.
+ *
+ * @param in the InputStream to read from
+ * @param out the PrintStream to write to
+ * @param props Properties object used by this protocol
+ * @param debug true to enable debugging output
+ * @exception IOException for I/O errors
+ */
+ public Protocol(InputStream in, PrintStream out, Properties props,
+ boolean debug) throws IOException {
+ this.host = "localhost";
+ this.props = props;
+ this.quote = false;
+ tagPrefix = computePrefix(props, "mail.imap");
+ logger = new MailLogger(this.getClass(), "DEBUG", debug, System.out);
+ traceLogger = logger.getSubLogger("protocol", null);
+
+ // XXX - inlined initStreams, won't allow later startTLS
+ traceInput = new TraceInputStream(in, traceLogger);
+ traceInput.setQuote(quote);
+ input = new ResponseInputStream(traceInput);
+
+ traceOutput = new TraceOutputStream(out, traceLogger);
+ traceOutput.setQuote(quote);
+ output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+
+ timestamp = System.currentTimeMillis();
+ }
+
+ /**
+ * Returns the timestamp.
+ *
+ * @return the timestamp
+ */
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * Adds a response handler.
+ *
+ * @param h the response handler
+ */
+ public void addResponseHandler(ResponseHandler h) {
+ handlers.add(h);
+ }
+
+ /**
+ * Removed the specified response handler.
+ *
+ * @param h the response handler
+ */
+ public void removeResponseHandler(ResponseHandler h) {
+ handlers.remove(h);
+ }
+
+ /**
+ * Notify response handlers
+ *
+ * @param responses the responses
+ */
+ public void notifyResponseHandlers(Response[] responses) {
+ if (handlers.isEmpty()) {
+ return;
+ }
+
+ for (Response r : responses) {
+ if (r != null) {
+ for (ResponseHandler rh : handlers) {
+ if (rh != null) {
+ rh.handleResponse(r);
+ }
+ }
+ }
+ }
+ }
+
+ protected void processGreeting(Response r) throws ProtocolException {
+ if (r.isBYE())
+ throw new ConnectionException(this, r);
+ }
+
+ /**
+ * Return the Protocol's InputStream.
+ *
+ * @return the input stream
+ */
+ protected ResponseInputStream getInputStream() {
+ return input;
+ }
+
+ /**
+ * Return the Protocol's OutputStream
+ *
+ * @return the output stream
+ */
+ protected OutputStream getOutputStream() {
+ return output;
+ }
+
+ /**
+ * Returns whether this Protocol supports non-synchronizing literals
+ * Default is false. Subclasses should override this if required
+ *
+ * @return true if the server supports non-synchronizing literals
+ */
+ protected synchronized boolean supportsNonSyncLiterals() {
+ return false;
+ }
+
+ public Response readResponse()
+ throws IOException, ProtocolException {
+ return new Response(this);
+ }
+
+ /**
+ * Is another response available in our buffer?
+ *
+ * @return true if another response is in the buffer
+ * @since JavaMail 1.5.4
+ */
+ public boolean hasResponse() {
+ /*
+ * XXX - Really should peek ahead in the buffer to see
+ * if there's a *complete* response available, but if there
+ * isn't who's going to read more data into the buffer
+ * until there is?
+ */
+ try {
+ return input.available() > 0;
+ } catch (IOException ex) {
+ }
+ return false;
+ }
+
+ /**
+ * Return a buffer to be used to read a response.
+ * The default implementation returns null, which causes
+ * a new buffer to be allocated for every response.
+ *
+ * @return the buffer to use
+ * @since JavaMail 1.4.1
+ */
+ protected ByteArray getResponseBuffer() {
+ return null;
+ }
+
+ public String writeCommand(String command, Argument args)
+ throws IOException, ProtocolException {
+ // assert Thread.holdsLock(this);
+ // can't assert because it's called from constructor
+ String tag = tagPrefix + Integer.toString(tagCounter++); // unique tag
+
+ output.writeBytes(tag + " " + command);
+
+ if (args != null) {
+ output.write(' ');
+ args.write(this);
+ }
+
+ output.write(CRLF);
+ output.flush();
+ return tag;
+ }
+
+ /**
+ * Send a command to the server. Collect all responses until either
+ * the corresponding command completion response or a BYE response
+ * (indicating server failure). Return all the collected responses.
+ *
+ * @param command the command
+ * @param args the arguments
+ * @return array of Response objects returned by the server
+ */
+ public synchronized Response[] command(String command, Argument args) {
+ commandStart(command);
+ List v = new ArrayList<>();
+ boolean done = false;
+ String tag = null;
+
+ // write the command
+ try {
+ tag = writeCommand(command, args);
+ } catch (LiteralException lex) {
+ v.add(lex.getResponse());
+ done = true;
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ v.add(Response.byeResponse(ex));
+ done = true;
+ }
+
+ Response byeResp = null;
+ while (!done) {
+ Response r = null;
+ try {
+ r = readResponse();
+ } catch (IOException ioex) {
+ if (byeResp == null) // convert this into a BYE response
+ byeResp = Response.byeResponse(ioex);
+ // else, connection closed after BYE was sent
+ break;
+ } catch (ProtocolException pex) {
+ logger.log(Level.FINE, "ignoring bad response", pex);
+ continue; // skip this response
+ }
+
+ if (r.isBYE()) {
+ byeResp = r;
+ continue;
+ }
+
+ v.add(r);
+
+ // If this is a matching command completion response, we are done
+ if (r.isTagged() && r.getTag().equals(tag))
+ done = true;
+ }
+
+ if (byeResp != null)
+ v.add(byeResp); // must be last
+ Response[] responses = new Response[v.size()];
+ v.toArray(responses);
+ timestamp = System.currentTimeMillis();
+ commandEnd();
+ return responses;
+ }
+
+ /**
+ * Convenience routine to handle OK, NO, BAD and BYE responses.
+ *
+ * @param response the response
+ * @exception ProtocolException for protocol failures
+ */
+ public void handleResult(Response response) throws ProtocolException {
+ if (response.isOK())
+ return;
+ else if (response.isNO())
+ throw new CommandFailedException(response);
+ else if (response.isBAD())
+ throw new BadCommandException(response);
+ else if (response.isBYE()) {
+ disconnect();
+ throw new ConnectionException(this, response);
+ }
+ }
+
+ /**
+ * Convenience routine to handle simple IAP commands
+ * that do not have responses specific to that command.
+ *
+ * @param cmd the command
+ * @param args the arguments
+ * @exception ProtocolException for protocol failures
+ */
+ public void simpleCommand(String cmd, Argument args)
+ throws ProtocolException {
+ // Issue command
+ Response[] r = command(cmd, args);
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ // Handle result of this command
+ handleResult(r[r.length-1]);
+ }
+
+ /**
+ * Start TLS on the current connection.
+ * cmd
is the command to issue to start TLS negotiation.
+ * If the command succeeds, we begin TLS negotiation.
+ * If the socket is already an SSLSocket this is a nop and the command
+ * is not issued.
+ *
+ * @param cmd the command to issue
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public synchronized void startTLS(String cmd)
+ throws IOException, ProtocolException {
+ if (socket instanceof SSLSocket)
+ return; // nothing to do
+ simpleCommand(cmd, null);
+ socket = SocketFetcher.startTLS(socket, host, props, prefix);
+ initStreams();
+ }
+
+ /**
+ * Start compression on the current connection.
+ * cmd
is the command to issue to start compression.
+ * If the command succeeds, we begin compression.
+ *
+ * @param cmd the command to issue
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public synchronized void startCompression(String cmd)
+ throws IOException, ProtocolException {
+ // XXX - check whether compression is already enabled?
+ simpleCommand(cmd, null);
+
+ // need to create our own Inflater and Deflater in order to set nowrap
+ Inflater inf = new Inflater(true);
+ traceInput = new TraceInputStream(new InflaterInputStream(
+ socket.getInputStream(), inf), traceLogger);
+ traceInput.setQuote(quote);
+ input = new ResponseInputStream(traceInput);
+
+ // configure the Deflater
+ int level = PropUtil.getIntProperty(props, prefix + ".compress.level",
+ Deflater.DEFAULT_COMPRESSION);
+ int strategy = PropUtil.getIntProperty(props,
+ prefix + ".compress.strategy",
+ Deflater.DEFAULT_STRATEGY);
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE,
+ "Creating Deflater with compression level {0} and strategy {1}",
+ new Object[] { level, strategy });
+ Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+ try {
+ def.setLevel(level);
+ } catch (IllegalArgumentException ex) {
+ logger.log(Level.FINE, "Ignoring bad compression level", ex);
+ }
+ try {
+ def.setStrategy(strategy);
+ } catch (IllegalArgumentException ex) {
+ logger.log(Level.FINE, "Ignoring bad compression strategy", ex);
+ }
+ traceOutput = new TraceOutputStream(new DeflaterOutputStream(
+ socket.getOutputStream(), def, true), traceLogger);
+ traceOutput.setQuote(quote);
+ output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+ }
+
+ /**
+ * Is this connection using an SSL socket?
+ *
+ * @return true if using SSL
+ * @since JavaMail 1.4.6
+ */
+ public boolean isSSL() {
+ return socket instanceof SSLSocket;
+ }
+
+ /**
+ * Return the address the socket connected to.
+ *
+ * @return the InetAddress the socket is connected to
+ * @since JavaMail 1.5.2
+ */
+ public InetAddress getInetAddress() {
+ return socket.getInetAddress();
+ }
+
+ /**
+ * Return the SocketChannel associated with this connection, if any.
+ *
+ * @return the SocketChannel
+ * @since JavaMail 1.5.2
+ */
+ public SocketChannel getChannel() {
+ SocketChannel ret = socket.getChannel();
+ if (ret != null)
+ return ret;
+
+ // XXX - Android is broken and SSL wrapped sockets don't delegate
+ // the getChannel method to the wrapped Socket
+ if (socket instanceof SSLSocket) {
+ try {
+ Field f = socket.getClass().getDeclaredField("socket");
+ f.setAccessible(true);
+ Socket s = (Socket)f.get(socket);
+ ret = s.getChannel();
+ } catch (Exception ex) {
+ // ignore anything that might go wrong
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Return the local SocketAddress (host and port) for this
+ * end of the connection.
+ *
+ * @return the SocketAddress
+ * @since Jakarta Mail 1.6.4
+ */
+ public SocketAddress getLocalSocketAddress() {
+ return socket.getLocalSocketAddress();
+ }
+
+ /**
+ * Does the server support UTF-8?
+ * This implementation returns false.
+ * Subclasses should override as appropriate.
+ *
+ * @return true if the server supports UTF-8
+ * @since JavaMail 1.6.0
+ */
+ public boolean supportsUtf8() {
+ return false;
+ }
+
+ /**
+ * Disconnect.
+ */
+ protected synchronized void disconnect() {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // ignore it
+ }
+ socket = null;
+ }
+ }
+
+ /**
+ * Get the name of the local host.
+ * The property <prefix>.localhost overrides
+ * <prefix>.localaddress,
+ * which overrides what InetAddress would tell us.
+ *
+ * @return the name of the local host
+ */
+ protected synchronized String getLocalHost() {
+ // get our hostname and cache it for future use
+ if (localHostName == null || localHostName.length() <= 0)
+ localHostName =
+ props.getProperty(prefix + ".localhost");
+ if (localHostName == null || localHostName.length() <= 0)
+ localHostName =
+ props.getProperty(prefix + ".localaddress");
+ try {
+ if (localHostName == null || localHostName.length() <= 0) {
+ InetAddress localHost = InetAddress.getLocalHost();
+ localHostName = localHost.getCanonicalHostName();
+ // if we can't get our name, use local address literal
+ if (localHostName == null)
+ // XXX - not correct for IPv6
+ localHostName = "[" + localHost.getHostAddress() + "]";
+ }
+ } catch (UnknownHostException uhex) {
+ }
+
+ // last chance, try to get our address from our socket
+ if (localHostName == null || localHostName.length() <= 0) {
+ if (socket != null && socket.isBound()) {
+ InetAddress localHost = socket.getLocalAddress();
+ localHostName = localHost.getCanonicalHostName();
+ // if we can't get our name, use local address literal
+ if (localHostName == null)
+ // XXX - not correct for IPv6
+ localHostName = "[" + localHost.getHostAddress() + "]";
+ }
+ }
+ return localHostName;
+ }
+
+ /**
+ * Is protocol tracing enabled?
+ *
+ * @return true if protocol tracing is enabled
+ */
+ protected boolean isTracing() {
+ return traceLogger.isLoggable(Level.FINEST);
+ }
+
+ /**
+ * Temporarily turn off protocol tracing, e.g., to prevent
+ * tracing the authentication sequence, including the password.
+ */
+ protected void suspendTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(false);
+ traceOutput.setTrace(false);
+ }
+ }
+
+ /**
+ * Resume protocol tracing, if it was enabled to begin with.
+ */
+ protected void resumeTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(true);
+ traceOutput.setTrace(true);
+ }
+ }
+
+ /**
+ * Finalizer.
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ disconnect();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /*
+ * Probe points for GlassFish monitoring.
+ */
+ private void commandStart(String command) { }
+ private void commandEnd() { }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ProtocolException.java b/app/src/main/java/com/sun/mail/iap/ProtocolException.java
new file mode 100644
index 0000000000..f4d9e3a5d0
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ProtocolException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ProtocolException extends Exception {
+ protected transient Response response = null;
+
+ private static final long serialVersionUID = -4360500807971797439L;
+
+ /**
+ * Constructs a ProtocolException with no detail message.
+ */
+ public ProtocolException() {
+ super();
+ }
+
+ /**
+ * Constructs a ProtocolException with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public ProtocolException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a ProtocolException with the specified detail message
+ * and cause.
+ *
+ * @param message the detail message
+ * @param cause the cause
+ */
+ public ProtocolException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a ProtocolException with the specified Response object.
+ *
+ * @param r the Response
+ */
+ public ProtocolException(Response r) {
+ super(r.toString());
+ response = r;
+ }
+
+ /**
+ * Return the offending Response object.
+ *
+ * @return the Response object
+ */
+ public Response getResponse() {
+ return response;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/Response.java b/app/src/main/java/com/sun/mail/iap/Response.java
new file mode 100644
index 0000000000..fcc4369957
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/Response.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+import java.util.*;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * This class represents a response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class Response {
+ protected int index; // internal index (updated during the parse)
+ protected int pindex; // index after parse, for reset
+ protected int size; // number of valid bytes in our buffer
+ protected byte[] buffer = null;
+ protected int type = 0;
+ protected String tag = null;
+ /** @since JavaMail 1.5.4 */
+ protected Exception ex;
+ protected boolean utf8;
+
+ private static final int increment = 100;
+
+ // The first and second bits indicate whether this response
+ // is a Continuation, Tagged or Untagged
+ public final static int TAG_MASK = 0x03;
+ public final static int CONTINUATION = 0x01;
+ public final static int TAGGED = 0x02;
+ public final static int UNTAGGED = 0x03;
+
+ // The third, fourth and fifth bits indicate whether this response
+ // is an OK, NO, BAD or BYE response
+ public final static int TYPE_MASK = 0x1C;
+ public final static int OK = 0x04;
+ public final static int NO = 0x08;
+ public final static int BAD = 0x0C;
+ public final static int BYE = 0x10;
+
+ // The sixth bit indicates whether a BYE response is synthetic or real
+ public final static int SYNTHETIC = 0x20;
+
+ /**
+ * An ATOM is any CHAR delimited by:
+ * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\' | ']'
+ * (CTL is handled in readDelimString.)
+ */
+ private static String ATOM_CHAR_DELIM = " (){%*\"\\]";
+
+ /**
+ * An ASTRING_CHAR is any CHAR delimited by:
+ * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\'
+ * (CTL is handled in readDelimString.)
+ */
+ private static String ASTRING_CHAR_DELIM = " (){%*\"\\";
+
+ public Response(String s) {
+ this(s, true);
+ }
+
+ /**
+ * Constructor for testing.
+ *
+ * @param s the response string
+ * @param supportsUtf8 allow UTF-8 in response?
+ * @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();
+ }
+
+ /**
+ * Read a new Response from the given Protocol
+ *
+ * @param p the Protocol object
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public Response(Protocol p) throws IOException, ProtocolException {
+ // read one response into 'buffer'
+ ByteArray ba = p.getResponseBuffer();
+ ByteArray response = p.getInputStream().readResponse(ba);
+ buffer = response.getBytes();
+ size = response.getCount() - 2; // Skip the terminating CRLF
+ utf8 = p.supportsUtf8();
+
+ parse();
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param r the Response to copy
+ */
+ public Response(Response r) {
+ index = r.index;
+ pindex = r.pindex;
+ size = r.size;
+ buffer = r.buffer;
+ type = r.type;
+ tag = r.tag;
+ ex = r.ex;
+ utf8 = r.utf8;
+ }
+
+ /**
+ * Return a Response object that looks like a BYE protocol response.
+ * Include the details of the exception in the response string.
+ *
+ * @param ex the exception
+ * @return the synthetic Response object
+ */
+ public static Response byeResponse(Exception ex) {
+ String err = "* BYE Jakarta Mail Exception: " + ex.toString();
+ err = err.replace('\r', ' ').replace('\n', ' ');
+ Response r = new Response(err);
+ r.type |= SYNTHETIC;
+ r.ex = ex;
+ return r;
+ }
+
+ /**
+ * Does the server support UTF-8?
+ *
+ * @return true if the server supports UTF-8
+ * @since JavaMail 1.6.0
+ */
+ public boolean supportsUtf8() {
+ return utf8;
+ }
+
+ private void parse() {
+ index = 0; // position internal index at start
+
+ if (size == 0) // empty line
+ return;
+ if (buffer[index] == '+') { // Continuation statement
+ type |= CONTINUATION;
+ index += 1; // Position beyond the '+'
+ return; // return
+ } else if (buffer[index] == '*') { // Untagged statement
+ type |= UNTAGGED;
+ index += 1; // Position beyond the '*'
+ } else { // Tagged statement
+ type |= TAGGED;
+ tag = readAtom(); // read the TAG, index positioned beyond tag
+ if (tag == null)
+ tag = ""; // avoid possible NPE
+ }
+
+ int mark = index; // mark
+ String s = readAtom(); // updates index
+ if (s == null)
+ s = ""; // avoid possible NPE
+ if (s.equalsIgnoreCase("OK"))
+ type |= OK;
+ else if (s.equalsIgnoreCase("NO"))
+ type |= NO;
+ else if (s.equalsIgnoreCase("BAD"))
+ type |= BAD;
+ else if (s.equalsIgnoreCase("BYE"))
+ type |= BYE;
+ else
+ index = mark; // reset
+
+ pindex = index;
+ return;
+ }
+
+ public void skipSpaces() {
+ while (index < size && buffer[index] == ' ')
+ index++;
+ }
+
+ /**
+ * Skip past any spaces. If the next non-space character is c,
+ * consume it and return true. Otherwise stop at that point
+ * and return false.
+ *
+ * @param c the character to look for
+ * @return true if the character is found
+ */
+ public boolean isNextNonSpace(char c) {
+ skipSpaces();
+ if (index < size && buffer[index] == (byte)c) {
+ index++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Skip to the next space, for use in error recovery while parsing.
+ */
+ public void skipToken() {
+ while (index < size && buffer[index] != ' ')
+ index++;
+ }
+
+ public void skip(int count) {
+ index += count;
+ }
+
+ public byte peekByte() {
+ if (index < size)
+ return buffer[index];
+ else
+ return 0; // XXX - how else to signal error?
+ }
+
+ /**
+ * Return the next byte from this Statement.
+ *
+ * @return the next byte
+ */
+ public byte readByte() {
+ if (index < size)
+ return buffer[index++];
+ else
+ return 0; // XXX - how else to signal error?
+ }
+
+ /**
+ * Extract an ATOM, starting at the current position. Updates
+ * the internal index to beyond the Atom.
+ *
+ * @return an Atom
+ */
+ public String readAtom() {
+ return readDelimString(ATOM_CHAR_DELIM);
+ }
+
+ /**
+ * Extract a string stopping at control characters or any
+ * character in delim.
+ */
+ private String readDelimString(String delim) {
+ skipSpaces();
+
+ if (index >= size) // already at end of response
+ return null;
+
+ int b;
+ int start = index;
+ while (index < size && ((b = (((int)buffer[index])&0xff)) >= ' ') &&
+ delim.indexOf((char)b) < 0 && b != 0x7f)
+ index++;
+
+ return toString(buffer, start, index);
+ }
+
+ /**
+ * Read a string as an arbitrary sequence of characters,
+ * stopping at the delimiter Used to read part of a
+ * response code inside [].
+ *
+ * @param delim the delimiter character
+ * @return the string
+ */
+ public String readString(char delim) {
+ skipSpaces();
+
+ if (index >= size) // already at end of response
+ return null;
+
+ int start = index;
+ while (index < size && buffer[index] != delim)
+ index++;
+
+ return toString(buffer, start, index);
+ }
+
+ public String[] readStringList() {
+ return readStringList(false);
+ }
+
+ public String[] readAtomStringList() {
+ return readStringList(true);
+ }
+
+ private String[] readStringList(boolean atom) {
+ skipSpaces();
+
+ if (buffer[index] != '(') { // not what we expected
+ return null;
+ }
+ index++; // skip '('
+
+ // to handle buggy IMAP servers, we tolerate multiple spaces as
+ // well as spaces after the left paren or before the right paren
+ List result = new ArrayList<>();
+ while (!isNextNonSpace(')')) {
+ String s = atom ? readAtomString() : readString();
+ if (s == null) // not the expected string or atom
+ break;
+ result.add(s);
+ }
+
+ return result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * Extract an integer, starting at the current position. Updates the
+ * internal index to beyond the number. Returns -1 if a number was
+ * not found.
+ *
+ * @return a number
+ */
+ public int readNumber() {
+ // Skip leading spaces
+ skipSpaces();
+
+ int start = index;
+ while (index < size && Character.isDigit((char)buffer[index]))
+ index++;
+
+ if (index > start) {
+ try {
+ return ASCIIUtility.parseInt(buffer, start, index);
+ } catch (NumberFormatException nex) { }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Extract a long number, starting at the current position. Updates the
+ * internal index to beyond the number. Returns -1 if a long number
+ * was not found.
+ *
+ * @return a long
+ */
+ public long readLong() {
+ // Skip leading spaces
+ skipSpaces();
+
+ int start = index;
+ while (index < size && Character.isDigit((char)buffer[index]))
+ index++;
+
+ if (index > start) {
+ try {
+ return ASCIIUtility.parseLong(buffer, start, index);
+ } catch (NumberFormatException nex) { }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Extract a NSTRING, starting at the current position. Return it as
+ * a String. The sequence 'NIL' is returned as null
+ *
+ * NSTRING := QuotedString | Literal | "NIL"
+ *
+ * @return a String
+ */
+ public String readString() {
+ return (String)parseString(false, true);
+ }
+
+ /**
+ * Extract a NSTRING, starting at the current position. Return it as
+ * a ByteArrayInputStream. The sequence 'NIL' is returned as null
+ *
+ * NSTRING := QuotedString | Literal | "NIL"
+ *
+ * @return a ByteArrayInputStream
+ */
+ public ByteArrayInputStream readBytes() {
+ ByteArray ba = readByteArray();
+ if (ba != null)
+ return ba.toByteArrayInputStream();
+ else
+ return null;
+ }
+
+ /**
+ * Extract a NSTRING, starting at the current position. Return it as
+ * a ByteArray. The sequence 'NIL' is returned as null
+ *
+ * NSTRING := QuotedString | Literal | "NIL"
+ *
+ * @return a ByteArray
+ */
+ public ByteArray readByteArray() {
+ /*
+ * Special case, return the data after the continuation uninterpreted.
+ * It's usually a challenge for an AUTHENTICATE command.
+ */
+ if (isContinuation()) {
+ skipSpaces();
+ return new ByteArray(buffer, index, size - index);
+ }
+ return (ByteArray)parseString(false, false);
+ }
+
+ /**
+ * Extract an ASTRING, starting at the current position
+ * and return as a String. An ASTRING can be a QuotedString, a
+ * Literal or an Atom (plus ']').
+ *
+ * Any errors in parsing returns null
+ *
+ * ASTRING := QuotedString | Literal | 1*ASTRING_CHAR
+ *
+ * @return a String
+ */
+ public String readAtomString() {
+ return (String)parseString(true, true);
+ }
+
+ /**
+ * Generic parsing routine that can parse out a Quoted-String,
+ * Literal or Atom and return the parsed token as a String
+ * or a ByteArray. Errors or NIL data will return null.
+ */
+ private Object parseString(boolean parseAtoms, boolean returnString) {
+ byte b;
+
+ // Skip leading spaces
+ skipSpaces();
+
+ b = buffer[index];
+ if (b == '"') { // QuotedString
+ index++; // skip the quote
+ int start = index;
+ int copyto = index;
+
+ while (index < size && (b = buffer[index]) != '"') {
+ if (b == '\\') // skip escaped byte
+ index++;
+ if (index != copyto) { // only copy if we need to
+ // Beware: this is a destructive copy. I'm
+ // pretty sure this is OK, but ... ;>
+ buffer[copyto] = buffer[index];
+ }
+ copyto++;
+ index++;
+ }
+ if (index >= size) {
+ // didn't find terminating quote, something is seriously wrong
+ //throw new ArrayIndexOutOfBoundsException(
+ // "index = " + index + ", size = " + size);
+ return null;
+ } else
+ index++; // skip past the terminating quote
+
+ if (returnString)
+ return toString(buffer, start, copyto);
+ else
+ return new ByteArray(buffer, start, copyto-start);
+ } else if (b == '{') { // Literal
+ int start = ++index; // note the start position
+
+ while (buffer[index] != '}')
+ index++;
+
+ int count = 0;
+ try {
+ count = ASCIIUtility.parseInt(buffer, start, index);
+ } catch (NumberFormatException nex) {
+ // throw new ParsingException();
+ return null;
+ }
+
+ start = index + 3; // skip "}\r\n"
+ index = start + count; // position index to beyond the literal
+
+ if (returnString) // return as String
+ return toString(buffer, start, start + count);
+ else
+ return new ByteArray(buffer, start, count);
+ } else if (parseAtoms) { // parse as ASTRING-CHARs
+ int start = index; // track this, so that we can use to
+ // creating ByteArrayInputStream below.
+ String s = readDelimString(ASTRING_CHAR_DELIM);
+ if (returnString)
+ return s;
+ else // *very* unlikely
+ return new ByteArray(buffer, start, index);
+ } else if (b == 'N' || b == 'n') { // the only valid value is 'NIL'
+ index += 3; // skip past NIL
+ return null;
+ }
+ 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;
+ }
+
+ public boolean isContinuation() {
+ return ((type & TAG_MASK) == CONTINUATION);
+ }
+
+ public boolean isTagged() {
+ return ((type & TAG_MASK) == TAGGED);
+ }
+
+ public boolean isUnTagged() {
+ return ((type & TAG_MASK) == UNTAGGED);
+ }
+
+ public boolean isOK() {
+ return ((type & TYPE_MASK) == OK);
+ }
+
+ public boolean isNO() {
+ return ((type & TYPE_MASK) == NO);
+ }
+
+ public boolean isBAD() {
+ return ((type & TYPE_MASK) == BAD);
+ }
+
+ public boolean isBYE() {
+ return ((type & TYPE_MASK) == BYE);
+ }
+
+ public boolean isSynthetic() {
+ return ((type & SYNTHETIC) == SYNTHETIC);
+ }
+
+ /**
+ * Return the tag, if this is a tagged statement.
+ *
+ * @return tag of this tagged statement
+ */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * Return the rest of the response as a string, usually used to
+ * return the arbitrary message text after a NO response.
+ *
+ * @return the rest of the response
+ */
+ public String getRest() {
+ skipSpaces();
+ return toString(buffer, index, size);
+ }
+
+ /**
+ * Return the exception for a synthetic BYE response.
+ *
+ * @return the exception
+ * @since JavaMail 1.5.4
+ */
+ public Exception getException() {
+ return ex;
+ }
+
+ /**
+ * Reset pointer to beginning of response.
+ */
+ public void reset() {
+ index = pindex;
+ }
+
+ @Override
+ public String toString() {
+ return toString(buffer, 0, size);
+ }
+
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ResponseHandler.java b/app/src/main/java/com/sun/mail/iap/ResponseHandler.java
new file mode 100644
index 0000000000..7d5a61c822
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ResponseHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * This class
+ *
+ * @author John Mani
+ */
+
+public interface ResponseHandler {
+ public void handleResponse(Response r);
+}
diff --git a/app/src/main/java/com/sun/mail/iap/ResponseInputStream.java b/app/src/main/java/com/sun/mail/iap/ResponseInputStream.java
new file mode 100644
index 0000000000..9e05b18f1f
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/ResponseInputStream.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+import com.sun.mail.iap.ByteArray;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ *
+ * Inputstream that is used to read a Response.
+ *
+ * @author Arun Krishnan
+ * @author Bill Shannon
+ */
+
+public class ResponseInputStream {
+
+ private static final int minIncrement = 256;
+ private static final int maxIncrement = 256 * 1024;
+ private static final int incrementSlop = 16;
+
+ // where we read from
+ private BufferedInputStream bin;
+
+ /**
+ * Constructor.
+ *
+ * @param in the InputStream to wrap
+ */
+ public ResponseInputStream(InputStream in) {
+ bin = new BufferedInputStream(in, 2 * 1024);
+ }
+
+ /**
+ * Read a Response from the InputStream.
+ *
+ * @return ByteArray that contains the Response
+ * @exception IOException for I/O errors
+ */
+ public ByteArray readResponse() throws IOException {
+ return readResponse(null);
+ }
+
+ /**
+ * Read a Response from the InputStream.
+ *
+ * @param ba the ByteArray in which to store the response, or null
+ * @return ByteArray that contains the Response
+ * @exception IOException for I/O errors
+ */
+ public ByteArray readResponse(ByteArray ba) throws IOException {
+ if (ba == null)
+ ba = new ByteArray(new byte[128], 0, 128);
+
+ byte[] buffer = ba.getBytes();
+ int idx = 0;
+ for (;;) { // read until CRLF with no preceeding literal
+ // XXX - b needs to be an int, to handle bytes with value 0xff
+ int b = 0;
+ boolean gotCRLF=false;
+
+ // Read a CRLF terminated line from the InputStream
+ while (!gotCRLF &&
+ ((b = bin.read()) != -1)) {
+ if (b == '\n') {
+ if ((idx > 0) && buffer[idx-1] == '\r')
+ gotCRLF = true;
+ }
+ if (idx >= buffer.length) {
+ int incr = buffer.length;
+ if (incr > maxIncrement)
+ incr = maxIncrement;
+ ba.grow(incr);
+ buffer = ba.getBytes();
+ }
+ buffer[idx++] = (byte)b;
+ }
+
+ if (b == -1)
+ throw new IOException("Connection dropped by server?");
+
+ // Now lets check for literals : {}CRLF
+ // Note: index needs to >= 5 for the above sequence to occur
+ if (idx < 5 || buffer[idx-3] != '}')
+ break;
+
+ int i;
+ // look for left curly
+ for (i = idx - 4; i >= 0; i--)
+ if (buffer[i] == '{')
+ break;
+
+ if (i < 0) // Nope, not a literal ?
+ break;
+
+ int count = 0;
+ // OK, handle the literal ..
+ try {
+ count = ASCIIUtility.parseInt(buffer, i+1, idx-3);
+ } catch (NumberFormatException e) {
+ break;
+ }
+
+ // Now read 'count' bytes. (Note: count could be 0)
+ if (count > 0) {
+ int avail = buffer.length - idx; // available space in buffer
+ if (count + incrementSlop > avail) {
+ // need count-avail more bytes
+ ba.grow(minIncrement > count + incrementSlop - avail ?
+ minIncrement : count + incrementSlop - avail);
+ buffer = ba.getBytes();
+ }
+
+ /*
+ * read() might not return all the bytes in one shot,
+ * so call repeatedly till we are done
+ */
+ int actual;
+ while (count > 0) {
+ actual = bin.read(buffer, idx, count);
+ if (actual == -1)
+ throw new IOException("Connection dropped by server?");
+ count -= actual;
+ idx += actual;
+ }
+ }
+ // back to top of loop to read until CRLF
+ }
+ ba.setCount(idx);
+ return ba;
+ }
+
+ /**
+ * How much buffered data do we have?
+ *
+ * @return number of bytes available
+ * @exception IOException if the stream has been closed
+ * @since JavaMail 1.5.4
+ */
+ public int available() throws IOException {
+ return bin.available();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/iap/package.html b/app/src/main/java/com/sun/mail/iap/package.html
new file mode 100644
index 0000000000..2607e33a2e
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/iap/package.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+com.sun.mail.iap package
+
+
+
+
+This package includes internal IMAP support classes and
+SHOULD NOT BE USED DIRECTLY BY APPLICATIONS .
+
+
+
+
diff --git a/app/src/main/java/com/sun/mail/imap/ACL.java b/app/src/main/java/com/sun/mail/imap/ACL.java
new file mode 100644
index 0000000000..ee99c95e2c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/ACL.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.*;
+
+/**
+ * An access control list entry for a particular authentication identifier
+ * (user or group). Associates a set of Rights with the identifier.
+ * See RFC 2086.
+ *
+ *
+ * @author Bill Shannon
+ */
+
+public class ACL implements Cloneable {
+
+ private String name;
+ private Rights rights;
+
+ /**
+ * Construct an ACL entry for the given identifier and with no rights.
+ *
+ * @param name the identifier name
+ */
+ public ACL(String name) {
+ this.name = name;
+ this.rights = new Rights();
+ }
+
+ /**
+ * Construct an ACL entry for the given identifier with the given rights.
+ *
+ * @param name the identifier name
+ * @param rights the rights
+ */
+ public ACL(String name, Rights rights) {
+ this.name = name;
+ this.rights = rights;
+ }
+
+ /**
+ * Get the identifier name for this ACL entry.
+ *
+ * @return the identifier name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the rights associated with this ACL entry.
+ *
+ * @param rights the rights
+ */
+ public void setRights(Rights rights) {
+ this.rights = rights;
+ }
+
+ /**
+ * Get the rights associated with this ACL entry.
+ * Returns the actual Rights object referenced by this ACL;
+ * modifications to the Rights object will effect this ACL.
+ *
+ * @return the rights
+ */
+ public Rights getRights() {
+ return rights;
+ }
+
+ /**
+ * Clone this ACL entry.
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ ACL acl = (ACL)super.clone();
+ acl.rights = (Rights)this.rights.clone();
+ return acl;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/AppendUID.java b/app/src/main/java/com/sun/mail/imap/AppendUID.java
new file mode 100644
index 0000000000..56c77fed82
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/AppendUID.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.iap.*;
+
+/**
+ * Information from the APPENDUID response code
+ * defined by the UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @author Bill Shannon
+ */
+
+public class AppendUID {
+ public long uidvalidity = -1;
+ public long uid = -1;
+
+ public AppendUID(long uidvalidity, long uid) {
+ this.uidvalidity = uidvalidity;
+ this.uid = uid;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/CopyUID.java b/app/src/main/java/com/sun/mail/imap/CopyUID.java
new file mode 100644
index 0000000000..3648fc5cb2
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/CopyUID.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.imap.protocol.UIDSet;
+
+/**
+ * Information from the COPYUID response code
+ * defined by the UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @author Bill Shannon
+ */
+
+public class CopyUID {
+ public long uidvalidity = -1;
+ public UIDSet[] src;
+ public UIDSet[] dst;
+
+ public CopyUID(long uidvalidity, UIDSet[] src, UIDSet[] dst) {
+ this.uidvalidity = uidvalidity;
+ this.src = src;
+ this.dst = dst;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/DefaultFolder.java b/app/src/main/java/com/sun/mail/imap/DefaultFolder.java
new file mode 100644
index 0000000000..9dab04fb5a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/DefaultFolder.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import com.sun.mail.iap.ProtocolException;
+import com.sun.mail.imap.protocol.IMAPProtocol;
+import com.sun.mail.imap.protocol.ListInfo;
+
+/**
+ * The default IMAP folder (root of the naming hierarchy).
+ *
+ * @author John Mani
+ */
+
+public class DefaultFolder extends IMAPFolder {
+
+ protected DefaultFolder(IMAPStore store) {
+ super("", UNKNOWN_SEPARATOR, store, null);
+ exists = true; // of course
+ type = HOLDS_FOLDERS; // obviously
+ }
+
+ @Override
+ public synchronized String getName() {
+ return fullName;
+ }
+
+ @Override
+ public Folder getParent() {
+ return null;
+ }
+
+ @Override
+ public synchronized Folder[] list(final String pattern)
+ throws MessagingException {
+ ListInfo[] li = null;
+
+ li = (ListInfo[])doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ return p.list("", pattern);
+ }
+ });
+
+ if (li == null)
+ return new Folder[0];
+
+ IMAPFolder[] folders = new IMAPFolder[li.length];
+ for (int i = 0; i < folders.length; i++)
+ folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
+ return folders;
+ }
+
+ @Override
+ public synchronized Folder[] listSubscribed(final String pattern)
+ throws MessagingException {
+ ListInfo[] li = null;
+
+ li = (ListInfo[])doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ return p.lsub("", pattern);
+ }
+ });
+
+ if (li == null)
+ return new Folder[0];
+
+ IMAPFolder[] folders = new IMAPFolder[li.length];
+ for (int i = 0; i < folders.length; i++)
+ folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
+ return folders;
+ }
+
+ @Override
+ public boolean hasNewMessages() throws MessagingException {
+ // Not applicable on DefaultFolder
+ return false;
+ }
+
+ @Override
+ public Folder getFolder(String name) throws MessagingException {
+ return ((IMAPStore)store).newIMAPFolder(name, UNKNOWN_SEPARATOR);
+ }
+
+ @Override
+ public boolean delete(boolean recurse) throws MessagingException {
+ // Not applicable on DefaultFolder
+ throw new MethodNotSupportedException("Cannot delete Default Folder");
+ }
+
+ @Override
+ public boolean renameTo(Folder f) throws MessagingException {
+ // Not applicable on DefaultFolder
+ throw new MethodNotSupportedException("Cannot rename Default Folder");
+ }
+
+ @Override
+ public void appendMessages(Message[] msgs) throws MessagingException {
+ // Not applicable on DefaultFolder
+ throw new MethodNotSupportedException("Cannot append to Default Folder");
+ }
+
+ @Override
+ public Message[] expunge() throws MessagingException {
+ // Not applicable on DefaultFolder
+ throw new MethodNotSupportedException("Cannot expunge Default Folder");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPBodyPart.java b/app/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
new file mode 100644
index 0000000000..181bb94512
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+
+import java.util.Enumeration;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ReadableMime;
+import com.sun.mail.util.LineOutputStream;
+import com.sun.mail.util.SharedByteArrayOutputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * An IMAP body part.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class IMAPBodyPart extends MimeBodyPart implements ReadableMime {
+ private IMAPMessage message;
+ private BODYSTRUCTURE bs;
+ private String sectionId;
+
+ // processed values ..
+ private String type;
+ private String description;
+
+ private boolean headersLoaded = false;
+
+ private static final boolean decodeFileName =
+ PropUtil.getBooleanSystemProperty("mail.mime.decodefilename", false);
+
+ protected IMAPBodyPart(BODYSTRUCTURE bs, String sid, IMAPMessage message) {
+ super();
+ this.bs = bs;
+ this.sectionId = sid;
+ this.message = message;
+ // generate content-type
+ ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
+ type = ct.toString();
+ }
+
+ /* Override this method to make it a no-op, rather than throw
+ * an IllegalWriteException. This will permit IMAPBodyParts to
+ * be inserted in newly crafted MimeMessages, especially when
+ * forwarding or replying to messages.
+ */
+ @Override
+ protected void updateHeaders() {
+ return;
+ }
+
+ @Override
+ public int getSize() throws MessagingException {
+ return bs.size;
+ }
+
+ @Override
+ public int getLineCount() throws MessagingException {
+ return bs.lines;
+ }
+
+ @Override
+ public String getContentType() throws MessagingException {
+ return type;
+ }
+
+ @Override
+ public String getDisposition() throws MessagingException {
+ return bs.disposition;
+ }
+
+ @Override
+ public void setDisposition(String disposition) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public String getEncoding() throws MessagingException {
+ return bs.encoding;
+ }
+
+ @Override
+ public String getContentID() throws MessagingException {
+ return bs.id;
+ }
+
+ @Override
+ public String getContentMD5() throws MessagingException {
+ return bs.md5;
+ }
+
+ @Override
+ public void setContentMD5(String md5) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public String getDescription() throws MessagingException {
+ if (description != null) // cached value ?
+ return description;
+
+ if (bs.description == null)
+ return null;
+
+ try {
+ description = MimeUtility.decodeText(bs.description);
+ } catch (UnsupportedEncodingException ex) {
+ description = bs.description;
+ }
+
+ return description;
+ }
+
+ @Override
+ public void setDescription(String description, String charset)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public String getFileName() throws MessagingException {
+ String filename = null;
+ if (bs.dParams != null)
+ filename = bs.dParams.get("filename");
+ if ((filename == null || filename.isEmpty()) && bs.cParams != null)
+ filename = bs.cParams.get("name");
+ if (decodeFileName && filename != null) {
+ try {
+ filename = MimeUtility.decodeText(filename);
+ } catch (UnsupportedEncodingException ex) {
+ throw new MessagingException("Can't decode filename", ex);
+ }
+ }
+ return filename;
+ }
+
+ @Override
+ public void setFileName(String filename) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ protected InputStream getContentStream() throws MessagingException {
+ InputStream is = null;
+ boolean pk = message.getPeek(); // acquire outside of message cache lock
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(message.getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = message.getProtocol();
+
+ // Check whether this message is expunged
+ message.checkExpunged();
+
+ if (p.isREV1() && (message.getFetchBlockSize() != -1))
+ return new IMAPInputStream(message, sectionId,
+ message.ignoreBodyStructureSize() ? -1 : bs.size, pk);
+
+ // Else, vanila IMAP4, no partial fetch
+
+ int seqnum = message.getSequenceNumber();
+ BODY b;
+ if (pk)
+ b = p.peekBody(seqnum, sectionId);
+ else
+ b = p.fetchBody(seqnum, sectionId);
+ if (b != null)
+ is = b.getByteArrayInputStream();
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(
+ message.getFolder(), cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Return the MIME format stream of headers for this body part.
+ */
+ private InputStream getHeaderStream() throws MessagingException {
+ if (!message.isREV1())
+ loadHeaders(); // will be needed below
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(message.getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = message.getProtocol();
+
+ // Check whether this message got expunged
+ message.checkExpunged();
+
+ if (p.isREV1()) {
+ int seqnum = message.getSequenceNumber();
+ BODY b = p.peekBody(seqnum, sectionId + ".MIME");
+
+ if (b == null)
+ throw new MessagingException("Failed to fetch headers");
+
+ ByteArrayInputStream bis = b.getByteArrayInputStream();
+ if (bis == null)
+ throw new MessagingException("Failed to fetch headers");
+ return bis;
+
+ } else {
+ // Can't read it from server, have to fake it
+ SharedByteArrayOutputStream bos =
+ new SharedByteArrayOutputStream(0);
+ LineOutputStream los = new LineOutputStream(bos);
+
+ try {
+ // Write out the header
+ Enumeration hdrLines
+ = super.getAllHeaderLines();
+ while (hdrLines.hasMoreElements())
+ los.writeln(hdrLines.nextElement());
+
+ // The CRLF separator between header and content
+ los.writeln();
+ } catch (IOException ioex) {
+ // should never happen
+ } finally {
+ try {
+ los.close();
+ } catch (IOException cex) { }
+ }
+ return bos.toStream();
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(
+ message.getFolder(), cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ /**
+ * Return the MIME format stream corresponding to this message part.
+ *
+ * @return the MIME format stream
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public InputStream getMimeStream() throws MessagingException {
+ /*
+ * The IMAP protocol doesn't support returning the entire
+ * part content in one operation so we have to fake it by
+ * concatenating the header stream and the content stream.
+ */
+ return new SequenceInputStream(getHeaderStream(), getContentStream());
+ }
+
+ @Override
+ public synchronized DataHandler getDataHandler()
+ throws MessagingException {
+ if (dh == null) {
+ if (bs.isMulti())
+ dh = new DataHandler(
+ new IMAPMultipartDataSource(
+ this, bs.bodies, sectionId, message)
+ );
+ else if (bs.isNested() && message.isREV1() && bs.envelope != null)
+ dh = new DataHandler(
+ new IMAPNestedMessage(message,
+ bs.bodies[0],
+ bs.envelope,
+ sectionId),
+ type
+ );
+ }
+
+ return super.getDataHandler();
+ }
+
+ @Override
+ public void setDataHandler(DataHandler content) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public void setContent(Object o, String type) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public void setContent(Multipart mp) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public String[] getHeader(String name) throws MessagingException {
+ loadHeaders();
+ return super.getHeader(name);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public void removeHeader(String name) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public Enumeration getAllHeaders() throws MessagingException {
+ loadHeaders();
+ return super.getAllHeaders();
+ }
+
+ @Override
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ loadHeaders();
+ return super.getMatchingHeaders(names);
+ }
+
+ @Override
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ loadHeaders();
+ return super.getNonMatchingHeaders(names);
+ }
+
+ @Override
+ public void addHeaderLine(String line) throws MessagingException {
+ throw new IllegalWriteException("IMAPBodyPart is read-only");
+ }
+
+ @Override
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ loadHeaders();
+ return super.getAllHeaderLines();
+ }
+
+ @Override
+ public Enumeration getMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ loadHeaders();
+ return super.getMatchingHeaderLines(names);
+ }
+
+ @Override
+ public Enumeration getNonMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ loadHeaders();
+ return super.getNonMatchingHeaderLines(names);
+ }
+
+ private synchronized void loadHeaders() throws MessagingException {
+ if (headersLoaded)
+ return;
+
+ // "headers" should never be null since it's set in the constructor.
+ // If something did go wrong this will fix it, but is an unsynchronized
+ // assignment of "headers".
+ if (headers == null)
+ headers = new InternetHeaders();
+
+ // load headers
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(message.getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = message.getProtocol();
+
+ // Check whether this message got expunged
+ message.checkExpunged();
+
+ if (p.isREV1()) {
+ int seqnum = message.getSequenceNumber();
+ BODY b = p.peekBody(seqnum, sectionId + ".MIME");
+
+ if (b == null)
+ throw new MessagingException("Failed to fetch headers");
+
+ ByteArrayInputStream bis = b.getByteArrayInputStream();
+ if (bis == null)
+ throw new MessagingException("Failed to fetch headers");
+
+ headers.load(bis);
+
+ } else {
+
+ // RFC 1730 does not provide for fetching BodyPart headers
+ // So, just dump the RFC1730 BODYSTRUCTURE into the
+ // headerStore
+
+ // Content-Type
+ headers.addHeader("Content-Type", type);
+ // Content-Transfer-Encoding
+ headers.addHeader("Content-Transfer-Encoding", bs.encoding);
+ // Content-Description
+ if (bs.description != null)
+ headers.addHeader("Content-Description",
+ bs.description);
+ // Content-ID
+ if (bs.id != null)
+ headers.addHeader("Content-ID", bs.id);
+ // Content-MD5
+ if (bs.md5 != null)
+ headers.addHeader("Content-MD5", bs.md5);
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(
+ message.getFolder(), cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ headersLoaded = true;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPFolder.java b/app/src/main/java/com/sun/mail/imap/IMAPFolder.java
new file mode 100644
index 0000000000..74f2f56b96
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPFolder.java
@@ -0,0 +1,4153 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.io.*;
+import java.net.SocketTimeoutException;
+import java.nio.channels.SocketChannel;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.CRLFOutputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * This class implements an IMAP folder.
+ *
+ * A closed IMAPFolder object shares a protocol connection with its IMAPStore
+ * object. When the folder is opened, it gets its own protocol connection.
+ *
+ * Applications that need to make use of IMAP-specific features may cast
+ * a Folder
object to an IMAPFolder
object and
+ * use the methods on this class.
+ *
+ * The {@link #getQuota getQuota} and
+ * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
+ * Refer to RFC 2087
+ * for more information.
+ *
+ * The {@link #getACL getACL}, {@link #addACL addACL},
+ * {@link #removeACL removeACL}, {@link #addRights addRights},
+ * {@link #removeRights removeRights}, {@link #listRights listRights}, and
+ * {@link #myRights myRights} methods support the IMAP ACL extension.
+ * Refer to RFC 2086
+ * for more information.
+ *
+ * The {@link #getSortedMessages getSortedMessages}
+ * methods support the IMAP SORT extension.
+ * Refer to RFC 5256
+ * for more information.
+ *
+ * The {@link #open(int,com.sun.mail.imap.ResyncData) open(int,ResyncData)}
+ * method and {@link com.sun.mail.imap.ResyncData ResyncData} class supports
+ * the IMAP CONDSTORE and QRESYNC extensions.
+ * Refer to RFC 4551
+ * and RFC 5162
+ * for more information.
+ *
+ * The {@link #doCommand doCommand} method and
+ * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
+ * interface support use of arbitrary IMAP protocol commands.
+ *
+ * See the com.sun.mail.imap package
+ * documentation for further information on the IMAP protocol provider.
+ *
+ * WARNING: The APIs unique to this class should be
+ * considered EXPERIMENTAL . They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Jim Glennon
+ */
+
+/*
+ * The folder object itself serves as a lock for the folder's state
+ * EXCEPT for the message cache (see below), typically by using
+ * synchronized methods. When checking that a folder is open or
+ * closed, the folder's lock must be held. It's important that the
+ * folder's lock is acquired before the messageCacheLock (see below).
+ * Thus, the locking hierarchy is that the folder lock, while optional,
+ * must be acquired before the messageCacheLock, if it's acquired at
+ * all. Be especially careful of callbacks that occur while holding
+ * the messageCacheLock into (e.g.) superclass Folder methods that are
+ * synchronized. Note that methods in IMAPMessage will acquire the
+ * messageCacheLock without acquiring the folder lock.
+ *
+ * When a folder is opened, it creates a messageCache (a Vector) of
+ * empty IMAPMessage objects. Each Message has a messageNumber - which
+ * is its index into the messageCache, and a sequenceNumber - which is
+ * its IMAP sequence-number. All operations on a Message which involve
+ * communication with the server, use the message's sequenceNumber.
+ *
+ * The most important thing to note here is that the server can send
+ * unsolicited EXPUNGE notifications as part of the responses for "most"
+ * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also,
+ * the server sends these notifications AFTER the message has been
+ * expunged. And once a message is expunged, the sequence-numbers of
+ * those messages after the expunged one are renumbered. This essentially
+ * means that the mapping between *any* Message and its sequence-number
+ * can change in the period when a IMAP command is issued and its responses
+ * are processed. Hence we impose a strict locking model as follows:
+ *
+ * We define one mutex per folder - this is just a Java Object (named
+ * messageCacheLock). Any time a command is to be issued to the IMAP
+ * server (i.e., anytime the corresponding IMAPProtocol method is
+ * invoked), follow the below style:
+ *
+ * synchronized (messageCacheLock) { // ACQUIRE LOCK
+ * issue command ()
+ *
+ * // The response processing is typically done within
+ * // the handleResponse() callback. A few commands (Fetch,
+ * // Expunge) return *all* responses and hence their
+ * // processing is done here itself. Now, as part of the
+ * // processing unsolicited EXPUNGE responses, we renumber
+ * // the necessary sequence-numbers. Thus the renumbering
+ * // happens within this critical-region, surrounded by
+ * // locks.
+ * process responses ()
+ * } // RELEASE LOCK
+ *
+ * This technique is used both by methods in IMAPFolder and by methods
+ * in IMAPMessage and other classes that operate on data in the folder.
+ * Note that holding the messageCacheLock has the side effect of
+ * preventing the folder from being closed, and thus ensuring that the
+ * folder's protocol object is still valid. The protocol object should
+ * only be accessed while holding the messageCacheLock (except for calls
+ * to IMAPProtocol.isREV1(), which don't need to be protected because it
+ * doesn't access the server).
+ *
+ * Note that interactions with the Store's protocol connection do
+ * not have to be protected as above, since the Store's protocol is
+ * never in a "meaningful" SELECT-ed state.
+ */
+
+public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
+
+ protected volatile String fullName; // full name
+ protected String name; // name
+ protected int type; // folder type.
+ protected char separator; // separator
+ protected Flags availableFlags; // available flags
+ protected Flags permanentFlags; // permanent flags
+ protected volatile boolean exists; // whether this folder really exists ?
+ protected boolean isNamespace = false; // folder is a namespace name
+ protected volatile String[] attributes;// name attributes from LIST response
+
+ protected volatile IMAPProtocol protocol; // this folder's protocol object
+ protected MessageCache messageCache;// message cache
+ // accessor lock for message cache
+ protected final Object messageCacheLock = new Object();
+
+ protected Hashtable uidTable; // UID->Message hashtable
+
+ /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
+ * We use '\uffff' (a non 7bit character) to indicate that we havent
+ * yet determined what the separator character is.
+ * We use '\u0000' (NUL) to indicate that no separator character
+ * exists, i.e., a flat hierarchy
+ */
+ static final protected char UNKNOWN_SEPARATOR = '\uffff';
+
+ private volatile boolean opened = false; // is this folder opened ?
+
+ /* This field tracks the state of this folder. If the folder is closed
+ * due to external causes (i.e, not thru the close() method), then
+ * this field will remain false. If the folder is closed thru the
+ * close() method, then this field is set to true.
+ *
+ * If reallyClosed is false, then a FolderClosedException is
+ * generated when a method is invoked on any Messaging object
+ * owned by this folder. If reallyClosed is true, then the
+ * IllegalStateException runtime exception is thrown.
+ */
+ private boolean reallyClosed = true;
+
+ /*
+ * The idleState field supports the IDLE command.
+ * Normally when executing an IMAP command we hold the
+ * messageCacheLock and often the folder lock (see above).
+ * While executing the IDLE command we can't hold either
+ * of these locks or it would prevent other threads from
+ * entering Folder methods even far enough to check whether
+ * an IDLE command is in progress. We need to check before
+ * issuing another command so that we can abort the IDLE
+ * command.
+ *
+ * The idleState field is protected by the messageCacheLock.
+ * The RUNNING state is the normal state and means no IDLE
+ * command is in progress. The IDLE state means we've issued
+ * an IDLE command and are reading responses. The ABORTING
+ * state means we've sent the DONE continuation command and
+ * are waiting for the thread running the IDLE command to
+ * break out of its read loop.
+ *
+ * When an IDLE command is in progress, the thread calling
+ * the idle method will be reading from the IMAP connection
+ * while holding neither the folder lock nor the messageCacheLock.
+ * It's obviously critical that no other thread try to send a
+ * command or read from the connection while in this state.
+ * However, other threads can send the DONE continuation
+ * command that will cause the server to break out of the IDLE
+ * loop and send the ending tag response to the IDLE command.
+ * The thread in the idle method that's reading the responses
+ * from the IDLE command will see this ending response and
+ * complete the idle method, setting the idleState field back
+ * to RUNNING, and notifying any threads waiting to use the
+ * connection.
+ *
+ * All uses of the IMAP connection (IMAPProtocol object) must
+ * be done while holding the messageCacheLock and must be
+ * preceeded by a check to make sure an IDLE command is not
+ * running, and abort the IDLE command if necessary. While
+ * waiting for the IDLE command to complete, these other threads
+ * will give up the messageCacheLock, but might still be holding
+ * the folder lock. This check is done by the getProtocol()
+ * method, resulting in a typical usage pattern of:
+ *
+ * synchronized (messageCacheLock) {
+ * IMAPProtocol p = getProtocol(); // may block waiting for IDLE
+ * // ... use protocol
+ * }
+ */
+ private static final int RUNNING = 0; // not doing IDLE command
+ private static final int IDLE = 1; // IDLE command in effect
+ private static final int ABORTING = 2; // IDLE command aborting
+ private int idleState = RUNNING;
+ private IdleManager idleManager;
+
+ private volatile int total = -1; // total number of messages in the
+ // message cache
+ private volatile int recent = -1; // number of recent messages
+ private int realTotal = -1; // total number of messages on
+ // the server
+ private long uidvalidity = -1; // UIDValidity
+ private long uidnext = -1; // UIDNext
+ private boolean uidNotSticky = false; // RFC 4315
+ private volatile long highestmodseq = -1; // RFC 4551 - CONDSTORE
+ private boolean doExpungeNotification = true; // used in expunge handler
+
+ private Status cachedStatus = null;
+ private long cachedStatusTime = 0;
+
+ private boolean hasMessageCountListener = false; // optimize notification
+
+ protected MailLogger logger;
+ private MailLogger connectionPoolLogger;
+
+ /**
+ * A fetch profile item for fetching headers.
+ * This inner class extends the FetchProfile.Item
+ * class to add new FetchProfile item types, specific to IMAPFolders.
+ *
+ * @see FetchProfile
+ */
+ public static class FetchProfileItem extends FetchProfile.Item {
+ protected FetchProfileItem(String name) {
+ super(name);
+ }
+
+ /**
+ * HEADERS is a fetch profile item that can be included in a
+ * FetchProfile
during a fetch request to a Folder.
+ * This item indicates that the headers for messages in the specified
+ * range are desired to be prefetched.
+ *
+ * An example of how a client uses this is below:
+ *
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(IMAPFolder.FetchProfileItem.HEADERS);
+ * folder.fetch(msgs, fp);
+ *
+ *
+ */
+ public static final FetchProfileItem HEADERS =
+ new FetchProfileItem("HEADERS");
+
+ /**
+ * SIZE is a fetch profile item that can be included in a
+ * FetchProfile
during a fetch request to a Folder.
+ * This item indicates that the sizes of the messages in the specified
+ * range are desired to be prefetched.
+ *
+ * SIZE was moved to FetchProfile.Item in JavaMail 1.5.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public static final FetchProfileItem SIZE =
+ new FetchProfileItem("SIZE");
+
+ /**
+ * MESSAGE is a fetch profile item that can be included in a
+ * FetchProfile
during a fetch request to a Folder.
+ * This item indicates that the entire messages (headers and body,
+ * including all "attachments") in the specified
+ * range are desired to be prefetched. Note that the entire message
+ * content is cached in memory while the Folder is open. The cached
+ * message will be parsed locally to return header information and
+ * message content.
+ *
+ * An example of how a client uses this is below:
+ *
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
+ * folder.fetch(msgs, fp);
+ *
+ *
+ *
+ * @since JavaMail 1.5.2
+ */
+ public static final FetchProfileItem MESSAGE =
+ new FetchProfileItem("MESSAGE");
+
+ /**
+ * INTERNALDATE is a fetch profile item that can be included in a
+ * FetchProfile
during a fetch request to a Folder.
+ * This item indicates that the IMAP INTERNALDATE values
+ * (received date) of the messages in the specified
+ * range are desired to be prefetched.
+ *
+ * An example of how a client uses this is below:
+ *
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
+ * folder.fetch(msgs, fp);
+ *
+ *
+ *
+ * @since JavaMail 1.5.5
+ */
+ public static final FetchProfileItem INTERNALDATE =
+ new FetchProfileItem("INTERNALDATE");
+ }
+
+ /**
+ * Constructor used to create a possibly non-existent folder.
+ *
+ * @param fullName fullname of this folder
+ * @param separator the default separator character for this
+ * folder's namespace
+ * @param store the Store
+ * @param isNamespace if this folder represents a namespace
+ */
+ protected IMAPFolder(String fullName, char separator, IMAPStore store,
+ Boolean isNamespace) {
+ super(store);
+ if (fullName == null)
+ throw new NullPointerException("Folder name is null");
+ this.fullName = fullName;
+ this.separator = separator;
+ logger = new MailLogger(this.getClass(), "DEBUG IMAP",
+ store.getSession().getDebug(), store.getSession().getDebugOut());
+ connectionPoolLogger = store.getConnectionPoolLogger();
+
+ /*
+ * Work around apparent bug in Exchange. Exchange
+ * will return a name of "Public Folders/" from
+ * LIST "%".
+ *
+ * If name has one separator, and it's at the end,
+ * assume this is a namespace name and treat it
+ * accordingly. Usually this will happen as a result
+ * of the list method, but this also allows getFolder
+ * to work with namespace names.
+ */
+ this.isNamespace = false;
+ if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
+ int i = this.fullName.indexOf(separator);
+ if (i > 0 && i == this.fullName.length() - 1) {
+ this.fullName = this.fullName.substring(0, i);
+ this.isNamespace = true;
+ }
+ }
+
+ // if we were given a value, override default chosen above
+ if (isNamespace != null)
+ this.isNamespace = isNamespace.booleanValue();
+ }
+
+ /**
+ * Constructor used to create an existing folder.
+ *
+ * @param li the ListInfo for this folder
+ * @param store the store containing this folder
+ */
+ protected IMAPFolder(ListInfo li, IMAPStore store) {
+ this(li.name, li.separator, store, null);
+
+ if (li.hasInferiors)
+ type |= HOLDS_FOLDERS;
+ if (li.canOpen)
+ type |= HOLDS_MESSAGES;
+ exists = true;
+ attributes = li.attrs;
+ }
+
+ /*
+ * Ensure that this folder exists. If 'exists' has been set to true,
+ * we don't attempt to validate it with the server again. Note that
+ * this can result in a possible loss of sync with the server.
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ */
+ protected void checkExists() throws MessagingException {
+ // If the boolean field 'exists' is false, check with the
+ // server by invoking exists() ..
+ if (!exists && !exists())
+ throw new FolderNotFoundException(
+ this, fullName + " not found");
+ }
+
+ /*
+ * Ensure the folder is closed.
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ */
+ protected void checkClosed() {
+ if (opened)
+ throw new IllegalStateException(
+ "This operation is not allowed on an open folder"
+ );
+ }
+
+ /*
+ * Ensure the folder is open.
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ */
+ protected void checkOpened() throws FolderClosedException {
+ assert Thread.holdsLock(this);
+ if (!opened) {
+ if (reallyClosed)
+ throw new IllegalStateException(
+ "This operation is not allowed on a closed folder"
+ );
+ else // Folder was closed "implicitly"
+ throw new FolderClosedException(this,
+ "Lost folder connection to server"
+ );
+ }
+ }
+
+ /*
+ * Check that the given message number is within the range
+ * of messages present in this folder. If the message
+ * number is out of range, we ping the server to obtain any
+ * pending new message notifications from the server.
+ */
+ protected void checkRange(int msgno) throws MessagingException {
+ if (msgno < 1) // message-numbers start at 1
+ throw new IndexOutOfBoundsException("message number < 1");
+
+ if (msgno <= total)
+ return;
+
+ // Out of range, let's ping the server and see if
+ // the server has more messages for us.
+
+ synchronized(messageCacheLock) { // Acquire lock
+ try {
+ keepConnectionAlive(false);
+ } catch (ConnectionException cex) {
+ // Oops, lost connection
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ } // Release lock
+
+ if (msgno > total) // Still out of range ? Throw up ...
+ throw new IndexOutOfBoundsException(msgno + " > " + total);
+ }
+
+ /*
+ * Check whether the given flags are supported by this server,
+ * and also verify that the folder allows setting flags.
+ */
+ private void checkFlags(Flags flags) throws MessagingException {
+ assert Thread.holdsLock(this);
+ if (mode != READ_WRITE)
+ throw new IllegalStateException(
+ "Cannot change flags on READ_ONLY folder: " + fullName
+ );
+ /*
+ if (!availableFlags.contains(flags))
+ throw new MessagingException(
+ "These flags are not supported by this implementation"
+ );
+ */
+ }
+
+ /**
+ * Get the name of this folder.
+ */
+ @Override
+ public synchronized String getName() {
+ /* Return the last component of this Folder's full name.
+ * Folder components are delimited by the separator character.
+ */
+ if (name == null) {
+ try {
+ name = fullName.substring(
+ fullName.lastIndexOf(getSeparator()) + 1
+ );
+ } catch (MessagingException mex) { }
+ }
+ return name;
+ }
+
+ /**
+ * Get the fullname of this folder.
+ */
+ @Override
+ public String getFullName() {
+ return fullName;
+ }
+
+ /**
+ * Get this folder's parent.
+ */
+ @Override
+ public synchronized Folder getParent() throws MessagingException {
+ char c = getSeparator();
+ int index;
+ if ((index = fullName.lastIndexOf(c)) != -1)
+ return ((IMAPStore)store).newIMAPFolder(
+ fullName.substring(0, index), c);
+ else
+ return new DefaultFolder((IMAPStore)store);
+ }
+
+ /**
+ * Check whether this folder really exists on the server.
+ */
+ @Override
+ public synchronized boolean exists() throws MessagingException {
+ // Check whether this folder exists ..
+ ListInfo[] li = null;
+ final String lname;
+ if (isNamespace && separator != '\0')
+ lname = fullName + separator;
+ else
+ lname = fullName;
+
+ li = (ListInfo[])doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ return p.list("", lname);
+ }
+ });
+
+ if (li != null) {
+ int i = findName(li, lname);
+ fullName = li[i].name;
+ separator = li[i].separator;
+ int len = fullName.length();
+ if (separator != '\0' && len > 0 &&
+ fullName.charAt(len - 1) == separator) {
+ fullName = fullName.substring(0, len - 1);
+ }
+ type = 0;
+ if (li[i].hasInferiors)
+ type |= HOLDS_FOLDERS;
+ if (li[i].canOpen)
+ type |= HOLDS_MESSAGES;
+ exists = true;
+ attributes = li[i].attrs;
+ } else {
+ exists = opened;
+ attributes = null;
+ }
+
+ return exists;
+ }
+
+ /**
+ * Which entry in li
matches lname
?
+ * If the name contains wildcards, more than one entry may be
+ * returned.
+ */
+ private int findName(ListInfo[] li, String lname) {
+ int i;
+ // if the name contains a wildcard, there might be more than one
+ for (i = 0; i < li.length; i++) {
+ if (li[i].name.equals(lname))
+ break;
+ }
+ if (i >= li.length) { // nothing matched exactly
+ // XXX - possibly should fail? But what if server
+ // is case insensitive and returns the preferred
+ // case of the name here?
+ i = 0; // use first one
+ }
+ return i;
+ }
+
+ /**
+ * List all subfolders matching the specified pattern.
+ */
+ @Override
+ public Folder[] list(String pattern) throws MessagingException {
+ return doList(pattern, false);
+ }
+
+ /**
+ * List all subscribed subfolders matching the specified pattern.
+ */
+ @Override
+ public Folder[] listSubscribed(String pattern) throws MessagingException {
+ return doList(pattern, true);
+ }
+
+ private synchronized Folder[] doList(final String pattern,
+ final boolean subscribed) throws MessagingException {
+ checkExists(); // insure that this folder does exist.
+
+ // Why waste a roundtrip to the server?
+ if (attributes != null && !isDirectory())
+ return new Folder[0];
+
+ final char c = getSeparator();
+
+ ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ if (subscribed)
+ return p.lsub("", fullName + c + pattern);
+ else
+ return p.list("", fullName + c + pattern);
+ }
+ });
+
+ if (li == null)
+ return new Folder[0];
+
+ /*
+ * The UW based IMAP4 servers (e.g. SIMS2.0) include
+ * current folder (terminated with the separator), when
+ * the LIST pattern is '%' or '*'. i.e,
+ * returns "mail/" as the first LIST response.
+ *
+ * Doesn't make sense to include the current folder in this
+ * case, so we filter it out. Note that I'm assuming that
+ * the offending response is the *first* one, my experiments
+ * with the UW & SIMS2.0 servers indicate that ..
+ */
+ int start = 0;
+ // Check the first LIST response.
+ if (li.length > 0 && li[0].name.equals(fullName + c))
+ start = 1; // start from index = 1
+
+ IMAPFolder[] folders = new IMAPFolder[li.length - start];
+ IMAPStore st = (IMAPStore)store;
+ for (int i = start; i < li.length; i++)
+ folders[i-start] = st.newIMAPFolder(li[i]);
+ return folders;
+ }
+
+ /**
+ * Get the separator character.
+ */
+ @Override
+ public synchronized char getSeparator() throws MessagingException {
+ if (separator == UNKNOWN_SEPARATOR) {
+ ListInfo[] li = null;
+
+ li = (ListInfo[])doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ // REV1 allows the following LIST format to obtain
+ // the hierarchy delimiter of non-existent folders
+ if (p.isREV1()) // IMAP4rev1
+ return p.list(fullName, "");
+ else // IMAP4, note that this folder must exist for this
+ // to work :(
+ return p.list("", fullName);
+ }
+ });
+
+ if (li != null)
+ separator = li[0].separator;
+ else
+ separator = '/'; // punt !
+ }
+ return separator;
+ }
+
+ /**
+ * Get the type of this folder.
+ */
+ @Override
+ public synchronized int getType() throws MessagingException {
+ if (opened) {
+ // never throw FolderNotFoundException if folder is open
+ if (attributes == null)
+ exists(); // try to fetch attributes
+ } else {
+ checkExists();
+ }
+ return type;
+ }
+
+ /**
+ * Check whether this folder is subscribed.
+ */
+ @Override
+ public synchronized boolean isSubscribed() {
+ ListInfo[] li = null;
+ final String lname;
+ if (isNamespace && separator != '\0')
+ lname = fullName + separator;
+ else
+ lname = fullName;
+
+ try {
+ li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.lsub("", lname);
+ }
+ });
+ } catch (ProtocolException pex) {
+ }
+
+ if (li != null) {
+ int i = findName(li, lname);
+ return li[i].canOpen;
+ } else
+ return false;
+ }
+
+ /**
+ * Subscribe/Unsubscribe this folder.
+ */
+ @Override
+ public synchronized void setSubscribed(final boolean subscribe)
+ throws MessagingException {
+ doCommandIgnoreFailure(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ if (subscribe)
+ p.subscribe(fullName);
+ else
+ p.unsubscribe(fullName);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Create this folder, with the specified type.
+ */
+ @Override
+ public synchronized boolean create(final int type)
+ throws MessagingException {
+
+ char c = 0;
+ if ((type & HOLDS_MESSAGES) == 0) // only holds folders
+ c = getSeparator();
+ final char sep = c;
+ Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ if ((type & HOLDS_MESSAGES) == 0) // only holds folders
+ p.create(fullName + sep);
+ else {
+ p.create(fullName);
+
+ // Certain IMAP servers do not allow creation of folders
+ // that can contain messages *and* subfolders. So, if we
+ // were asked to create such a folder, we should verify
+ // that we could indeed do so.
+ if ((type & HOLDS_FOLDERS) != 0) {
+ // we want to hold subfolders and messages. Check
+ // whether we could create such a folder.
+ ListInfo[] li = p.list("", fullName);
+ if (li != null && !li[0].hasInferiors) {
+ // Hmm ..the new folder
+ // doesn't support Inferiors ? Fail
+ p.delete(fullName);
+ throw new ProtocolException("Unsupported type");
+ }
+ }
+ }
+ return Boolean.TRUE;
+ }
+ });
+
+ if (ret == null)
+ return false; // CREATE failure, maybe this
+ // folder already exists ?
+
+ // exists = true;
+ // this.type = type;
+ boolean retb = exists(); // set exists, type, and attributes
+ if (retb) // Notify listeners on self and our Store
+ notifyFolderListeners(FolderEvent.CREATED);
+ return retb;
+ }
+
+ /**
+ * Check whether this folder has new messages.
+ */
+ @Override
+ public synchronized boolean hasNewMessages() throws MessagingException {
+ synchronized (messageCacheLock) {
+ if (opened) { // If we are open, we already have this information
+ // Folder is open, make sure information is up to date
+ // tickle the folder and store connections.
+ try {
+ keepConnectionAlive(true);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return recent > 0 ? true : false;
+ }
+ }
+
+ // First, the cheap way - use LIST and look for the \Marked
+ // or \Unmarked tag
+
+ ListInfo[] li = null;
+ final String lname;
+ if (isNamespace && separator != '\0')
+ lname = fullName + separator;
+ else
+ lname = fullName;
+ li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ return p.list("", lname);
+ }
+ });
+
+ // if folder doesn't exist, throw exception
+ if (li == null)
+ throw new FolderNotFoundException(this, fullName + " not found");
+
+ int i = findName(li, lname);
+ if (li[i].changeState == ListInfo.CHANGED)
+ return true;
+ else if (li[i].changeState == ListInfo.UNCHANGED)
+ return false;
+
+ // LIST didn't work. Try the hard way, using STATUS
+ try {
+ Status status = getStatus();
+ if (status.recent > 0)
+ return true;
+ else
+ return false;
+ } catch (BadCommandException bex) {
+ // Probably doesn't support STATUS, tough luck.
+ return false;
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(store, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the named subfolder.
+ */
+ @Override
+ public synchronized Folder getFolder(String name)
+ throws MessagingException {
+ // If we know that this folder is *not* a directory, don't
+ // send the request to the server at all ...
+ if (attributes != null && !isDirectory())
+ throw new MessagingException("Cannot contain subfolders");
+
+ char c = getSeparator();
+ return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c);
+ }
+
+ /**
+ * Delete this folder.
+ */
+ @Override
+ public synchronized boolean delete(boolean recurse)
+ throws MessagingException {
+ checkClosed(); // insure that this folder is closed.
+
+ if (recurse) {
+ // Delete all subfolders.
+ Folder[] f = list();
+ for (int i = 0; i < f.length; i++)
+ f[i].delete(recurse); // ignore intermediate failures
+ }
+
+ // Attempt to delete this folder
+
+ Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ p.delete(fullName);
+ return Boolean.TRUE;
+ }
+ });
+
+ if (ret == null)
+ // Non-existent folder/No permission ??
+ return false;
+
+ // DELETE succeeded.
+ exists = false;
+ attributes = null;
+
+ // Notify listeners on self and our Store
+ notifyFolderListeners(FolderEvent.DELETED);
+ return true;
+ }
+
+ /**
+ * Rename this folder.
+ */
+ @Override
+ public synchronized boolean renameTo(final Folder f)
+ throws MessagingException {
+ checkClosed(); // insure that we are closed.
+ checkExists();
+ if (f.getStore() != store)
+ throw new MessagingException("Can't rename across Stores");
+
+
+ Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p) throws ProtocolException {
+ p.rename(fullName, f.getFullName());
+ return Boolean.TRUE;
+ }
+ });
+
+ if (ret == null)
+ return false;
+
+ exists = false;
+ attributes = null;
+ notifyFolderRenamedListeners(f);
+ return true;
+ }
+
+ /**
+ * Open this folder in the given mode.
+ */
+ @Override
+ public synchronized void open(int mode) throws MessagingException {
+ open(mode, null);
+ }
+
+ /**
+ * Open this folder in the given mode, with the given
+ * resynchronization data.
+ *
+ * @param mode the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
+ * @param rd the ResyncData instance
+ * @return a List of MailEvent instances, or null if none
+ * @exception MessagingException if the open fails
+ * @since JavaMail 1.5.1
+ */
+ public synchronized List open(int mode, ResyncData rd)
+ throws MessagingException {
+ checkClosed(); // insure that we are not already open
+
+ MailboxInfo mi = null;
+ // Request store for our own protocol connection.
+ protocol = ((IMAPStore)store).getProtocol(this);
+
+ List openEvents = null;
+ synchronized(messageCacheLock) { // Acquire messageCacheLock
+
+ /*
+ * Add response handler right away so we get any alerts or
+ * notifications that occur during the SELECT or EXAMINE.
+ * Have to be sure to remove it if we fail to open the
+ * folder.
+ */
+ protocol.addResponseHandler(this);
+
+ try {
+ /*
+ * Enable QRESYNC or CONDSTORE if needed and not enabled.
+ * QRESYNC implies CONDSTORE, but servers that support
+ * QRESYNC are not required to support just CONDSTORE
+ * per RFC 5162.
+ */
+ if (rd != null) {
+ if (rd == ResyncData.CONDSTORE) {
+ if (!protocol.isEnabled("CONDSTORE") &&
+ !protocol.isEnabled("QRESYNC")) {
+ if (protocol.hasCapability("CONDSTORE"))
+ protocol.enable("CONDSTORE");
+ else
+ protocol.enable("QRESYNC");
+ }
+ } else {
+ if (!protocol.isEnabled("QRESYNC"))
+ protocol.enable("QRESYNC");
+ }
+ }
+
+ if (mode == READ_ONLY)
+ mi = protocol.examine(fullName, rd);
+ else
+ mi = protocol.select(fullName, rd);
+ } catch (CommandFailedException cex) {
+ /*
+ * Handle SELECT or EXAMINE failure.
+ * Try to figure out why the operation failed so we can
+ * report a more reasonable exception.
+ *
+ * Will use our existing protocol object.
+ */
+ try {
+ checkExists(); // throw exception if folder doesn't exist
+
+ if ((type & HOLDS_MESSAGES) == 0)
+ throw new MessagingException(
+ "folder cannot contain messages");
+ throw new MessagingException(cex.getMessage(), cex);
+
+ } finally {
+ // folder not open, don't keep this information
+ exists = false;
+ attributes = null;
+ type = 0;
+ // connection still good, return it
+ releaseProtocol(true);
+ }
+ // NOTREACHED
+ } catch (ProtocolException pex) {
+ // got a BAD or a BYE; connection may be bad, close it
+ try {
+ throw logoutAndThrow(pex.getMessage(), pex);
+ } finally {
+ releaseProtocol(false);
+ }
+ }
+
+ if (mi.mode != mode) {
+ if (mode == READ_WRITE && mi.mode == READ_ONLY &&
+ ((IMAPStore)store).allowReadOnlySelect()) {
+ ; // all ok, allow it
+ } else { // otherwise, it's an error
+ ReadOnlyFolderException ife = new ReadOnlyFolderException(
+ this, "Cannot open in desired mode");
+ throw cleanupAndThrow(ife);
+ }
+ }
+
+ // Initialize stuff.
+ opened = true;
+ reallyClosed = false;
+ this.mode = mi.mode;
+ availableFlags = mi.availableFlags;
+ permanentFlags = mi.permanentFlags;
+ total = realTotal = mi.total;
+ recent = mi.recent;
+ uidvalidity = mi.uidvalidity;
+ uidnext = mi.uidnext;
+ uidNotSticky = mi.uidNotSticky;
+ highestmodseq = mi.highestmodseq;
+
+ // Create the message cache of appropriate size
+ messageCache = new MessageCache(this, (IMAPStore)store, total);
+
+ // process saved responses and return corresponding events
+ if (mi.responses != null) {
+ openEvents = new ArrayList<>();
+ for (IMAPResponse ir : mi.responses) {
+ if (ir.keyEquals("VANISHED")) {
+ // "VANISHED" SP ["(EARLIER)"] SP known-uids
+ String[] s = ir.readAtomStringList();
+ // check that it really is "EARLIER"
+ if (s == null || s.length != 1 ||
+ !s[0].equalsIgnoreCase("EARLIER"))
+ continue; // it's not, what to do with it here?
+ String uids = ir.readAtom();
+ UIDSet[] uidset = UIDSet.parseUIDSets(uids);
+ long[] luid = UIDSet.toArray(uidset, uidnext);
+ if (luid != null && luid.length > 0)
+ openEvents.add(
+ new MessageVanishedEvent(this, luid));
+ } else if (ir.keyEquals("FETCH")) {
+ assert ir instanceof FetchResponse :
+ "!ir instanceof FetchResponse";
+ Message msg = processFetchResponse((FetchResponse)ir);
+ if (msg != null)
+ openEvents.add(new MessageChangedEvent(this,
+ MessageChangedEvent.FLAGS_CHANGED, msg));
+ }
+ }
+ }
+ } // Release lock
+
+ exists = true; // if we opened it, it must exist
+ attributes = null; // but we don't yet know its attributes
+ type = HOLDS_MESSAGES; // lacking more info, we know at least this much
+
+ // notify listeners
+ notifyConnectionListeners(ConnectionEvent.OPENED);
+
+ return openEvents;
+ }
+
+ private MessagingException cleanupAndThrow(MessagingException ife) {
+ try {
+ try {
+ // close mailbox and return connection
+ protocol.close();
+ releaseProtocol(true);
+ } catch (ProtocolException pex) {
+ // something went wrong, close connection
+ try {
+ addSuppressed(ife, logoutAndThrow(pex.getMessage(), pex));
+ } finally {
+ releaseProtocol(false);
+ }
+ }
+ } catch (Throwable thr) {
+ addSuppressed(ife, thr);
+ }
+ return ife;
+ }
+
+ private MessagingException logoutAndThrow(String why, ProtocolException t) {
+ MessagingException ife = new MessagingException(why, t);
+ try {
+ protocol.logout();
+ } catch (Throwable thr) {
+ addSuppressed(ife, thr);
+ }
+ return ife;
+ }
+
+ private void addSuppressed(Throwable ife, Throwable thr) {
+ if (isRecoverable(thr)) {
+ ife.addSuppressed(thr);
+ } else {
+ thr.addSuppressed(ife);
+ if (thr instanceof Error) {
+ throw (Error) thr;
+ }
+ if (thr instanceof RuntimeException) {
+ throw (RuntimeException) thr;
+ }
+ throw new RuntimeException("unexpected exception", thr);
+ }
+ }
+
+ private boolean isRecoverable(Throwable t) {
+ return (t instanceof Exception) || (t instanceof LinkageError);
+ }
+
+ /**
+ * Prefetch attributes, based on the given FetchProfile.
+ */
+ @Override
+ public synchronized void fetch(Message[] msgs, FetchProfile fp)
+ throws MessagingException {
+ // cache this information in case connection is closed and
+ // protocol is set to null
+ boolean isRev1;
+ FetchItem[] fitems;
+ synchronized (messageCacheLock) {
+ checkOpened();
+ isRev1 = protocol.isREV1();
+ fitems = protocol.getFetchItems();
+ }
+
+ StringBuilder command = new StringBuilder();
+ boolean first = true;
+ boolean allHeaders = false;
+
+ if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+ command.append(getEnvelopeCommand());
+ first = false;
+ }
+ if (fp.contains(FetchProfile.Item.FLAGS)) {
+ command.append(first ? "FLAGS" : " FLAGS");
+ first = false;
+ }
+ if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+ command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE");
+ first = false;
+ }
+ if (fp.contains(UIDFolder.FetchProfileItem.UID)) {
+ command.append(first ? "UID" : " UID");
+ first = false;
+ }
+ if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
+ allHeaders = true;
+ if (isRev1)
+ command.append(first ?
+ "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]");
+ else
+ command.append(first ? "RFC822.HEADER" : " RFC822.HEADER");
+ first = false;
+ }
+ if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) {
+ allHeaders = true;
+ if (isRev1)
+ command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]");
+ else
+ command.append(first ? "RFC822" : " RFC822");
+ first = false;
+ }
+ if (fp.contains(FetchProfile.Item.SIZE) ||
+ fp.contains(IMAPFolder.FetchProfileItem.SIZE)) {
+ command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
+ first = false;
+ }
+ if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) {
+ command.append(first ? "INTERNALDATE" : " INTERNALDATE");
+ first = false;
+ }
+
+ // if we're not fetching all headers, fetch individual headers
+ String[] hdrs = null;
+ if (!allHeaders) {
+ hdrs = fp.getHeaderNames();
+ if (hdrs.length > 0) {
+ if (!first)
+ command.append(" ");
+ command.append(createHeaderCommand(hdrs, isRev1));
+ }
+ }
+
+ /*
+ * Add any additional extension fetch items.
+ */
+ for (int i = 0; i < fitems.length; i++) {
+ if (fp.contains(fitems[i].getFetchProfileItem())) {
+ if (command.length() != 0)
+ command.append(" ");
+ command.append(fitems[i].getName());
+ }
+ }
+
+ Utility.Condition condition =
+ new IMAPMessage.FetchProfileCondition(fp, fitems);
+
+ // Acquire the Folder's MessageCacheLock.
+ synchronized (messageCacheLock) {
+
+ // check again to make sure folder is still open
+ checkOpened();
+
+ // Apply the test, and get the sequence-number set for
+ // the messages that need to be prefetched.
+ MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition);
+
+ if (msgsets == null)
+ // We already have what we need.
+ return;
+
+ Response[] r = null;
+ // to collect non-FETCH responses & unsolicited FETCH FLAG responses
+ List v = new ArrayList<>();
+ try {
+ r = getProtocol().fetch(msgsets, command.toString());
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (CommandFailedException cfx) {
+ // Ignore these, as per RFC 2180
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+
+ if (r == null)
+ return;
+
+ for (int i = 0; i < r.length; i++) {
+ if (r[i] == null)
+ continue;
+ if (!(r[i] instanceof FetchResponse)) {
+ v.add(r[i]); // Unsolicited Non-FETCH response
+ continue;
+ }
+
+ // Got a FetchResponse.
+ FetchResponse f = (FetchResponse)r[i];
+ // Get the corresponding message.
+ IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
+
+ int count = f.getItemCount();
+ boolean unsolicitedFlags = false;
+
+ for (int j = 0; j < count; j++) {
+ Item item = f.getItem(j);
+ // Check for the FLAGS item
+ if (item instanceof Flags &&
+ (!fp.contains(FetchProfile.Item.FLAGS) ||
+ msg == null)) {
+ // Ok, Unsolicited FLAGS update.
+ unsolicitedFlags = true;
+ } else if (msg != null)
+ msg.handleFetchItem(item, hdrs, allHeaders);
+ }
+ if (msg != null)
+ msg.handleExtensionFetchItems(f.getExtensionItems());
+
+ // If this response contains any unsolicited FLAGS
+ // add it to the unsolicited response vector
+ if (unsolicitedFlags)
+ v.add(f);
+ }
+
+ // Dispatch any unsolicited responses
+ if (!v.isEmpty()) {
+ Response[] responses = new Response[v.size()];
+ v.toArray(responses);
+ handleResponses(responses);
+ }
+
+ } // Release messageCacheLock
+ }
+
+ /**
+ * Return the IMAP FETCH items to request in order to load
+ * all the "envelope" data. Subclasses can override this
+ * method to fetch more data when FetchProfile.Item.ENVELOPE
+ * is requested.
+ *
+ * @return the IMAP FETCH items to request
+ * @since JavaMail 1.4.6
+ */
+ protected String getEnvelopeCommand() {
+ return IMAPMessage.EnvelopeCmd;
+ }
+
+ /**
+ * Create a new IMAPMessage object to represent the given message number.
+ * Subclasses of IMAPFolder may override this method to create a
+ * subclass of IMAPMessage.
+ *
+ * @param msgnum the message sequence number
+ * @return the new IMAPMessage object
+ * @since JavaMail 1.4.6
+ */
+ protected IMAPMessage newIMAPMessage(int msgnum) {
+ return new IMAPMessage(this, msgnum);
+ }
+
+ /**
+ * Create the appropriate IMAP FETCH command items to fetch the
+ * requested headers.
+ */
+ private String createHeaderCommand(String[] hdrs, boolean isRev1) {
+ StringBuilder sb;
+
+ if (isRev1)
+ sb = new StringBuilder("BODY.PEEK[HEADER.FIELDS (");
+ else
+ sb = new StringBuilder("RFC822.HEADER.LINES (");
+
+ for (int i = 0; i < hdrs.length; i++) {
+ if (i > 0)
+ sb.append(" ");
+ sb.append(hdrs[i]);
+ }
+
+ if (isRev1)
+ sb.append(")]");
+ else
+ sb.append(")");
+
+ return sb.toString();
+ }
+
+ /**
+ * Set the specified flags for the given array of messages.
+ */
+ @Override
+ public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
+ throws MessagingException {
+ checkOpened();
+ checkFlags(flag); // validate flags
+
+ if (msgs.length == 0) // boundary condition
+ return;
+
+ synchronized(messageCacheLock) {
+ try {
+ IMAPProtocol p = getProtocol();
+ MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
+ if (ms == null)
+ throw new MessageRemovedException(
+ "Messages have been removed");
+ p.storeFlags(ms, flag, value);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ /**
+ * Set the specified flags for the given range of message numbers.
+ */
+ @Override
+ public synchronized void setFlags(int start, int end,
+ Flags flag, boolean value) throws MessagingException {
+ checkOpened();
+ Message[] msgs = new Message[end - start + 1];
+ int i = 0;
+ for (int n = start; n <= end; n++)
+ msgs[i++] = getMessage(n);
+ setFlags(msgs, flag, value);
+ }
+
+ /**
+ * Set the specified flags for the given array of message numbers.
+ */
+ @Override
+ public synchronized void setFlags(int[] msgnums, Flags flag, boolean value)
+ throws MessagingException {
+ checkOpened();
+ Message[] msgs = new Message[msgnums.length];
+ for (int i = 0; i < msgnums.length; i++)
+ msgs[i] = getMessage(msgnums[i]);
+ setFlags(msgs, flag, value);
+ }
+
+ /**
+ * Close this folder.
+ */
+ @Override
+ public synchronized void close(boolean expunge) throws MessagingException {
+ close(expunge, false);
+ }
+
+ /**
+ * Close this folder without waiting for the server.
+ *
+ * @exception MessagingException for failures
+ */
+ public synchronized void forceClose() throws MessagingException {
+ close(false, true);
+ }
+
+ /*
+ * Common close method.
+ */
+ private void close(boolean expunge, boolean force)
+ throws MessagingException {
+ assert Thread.holdsLock(this);
+ synchronized(messageCacheLock) {
+ /*
+ * If we already know we're closed, this is illegal.
+ * Can't use checkOpened() because if we were forcibly
+ * closed asynchronously we just want to complete the
+ * closing here.
+ */
+ if (!opened && reallyClosed)
+ throw new IllegalStateException(
+ "This operation is not allowed on a closed folder"
+ );
+
+ reallyClosed = true; // Ok, lets reset
+
+ // Maybe this folder is already closed, or maybe another
+ // thread which had the messageCacheLock earlier, found
+ // that our server connection is dead and cleaned up
+ // everything ..
+ if (!opened)
+ return;
+
+ boolean reuseProtocol = true;
+ try {
+ waitIfIdle();
+ if (force) {
+ logger.log(Level.FINE, "forcing folder {0} to close",
+ fullName);
+ if (protocol != null)
+ protocol.disconnect();
+ } else if (((IMAPStore)store).isConnectionPoolFull()) {
+ // If the connection pool is full, logout the connection
+ logger.fine(
+ "pool is full, not adding an Authenticated connection");
+
+ // If the expunge flag is set, close the folder first.
+ if (expunge && protocol != null)
+ protocol.close();
+
+ if (protocol != null)
+ protocol.logout();
+ } else {
+ // If the expunge flag is set or we're open read-only we
+ // can just close the folder, otherwise open it read-only
+ // before closing, or unselect it if supported.
+ if (!expunge && mode == READ_WRITE) {
+ try {
+ if (protocol != null &&
+ protocol.hasCapability("UNSELECT"))
+ protocol.unselect();
+ else {
+ // Unselect isn't supported so we need to
+ // select a folder to cause this one to be
+ // deselected without expunging messages.
+ // We try to do that by reopening the current
+ // folder read-only. If the current folder
+ // was renamed out from under us, the EXAMINE
+ // might fail, but that's ok because it still
+ // leaves us with the folder deselected.
+ if (protocol != null) {
+ boolean selected = true;
+ try {
+ protocol.examine(fullName);
+ // success, folder still selected
+ } catch (CommandFailedException ex) {
+ // EXAMINE failed, folder is no
+ // longer selected
+ selected = false;
+ }
+ if (selected && protocol != null)
+ protocol.close();
+ }
+ }
+ } catch (ProtocolException pex2) {
+ reuseProtocol = false; // something went wrong
+ }
+ } else {
+ if (protocol != null)
+ protocol.close();
+ }
+ }
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ // cleanup if we haven't already
+ if (opened)
+ cleanup(reuseProtocol);
+ }
+ }
+ }
+
+ // NOTE: this method can currently be invoked from close() or
+ // from handleResponses(). Both invocations are conditional,
+ // based on the "opened" flag, so we are sure that multiple
+ // Connection.CLOSED events are not generated. Also both
+ // invocations are from within messageCacheLock-ed areas.
+ private void cleanup(boolean returnToPool) {
+ assert Thread.holdsLock(messageCacheLock);
+ releaseProtocol(returnToPool);
+ messageCache = null;
+ uidTable = null;
+ exists = false; // to force a recheck in exists().
+ attributes = null;
+ opened = false;
+ idleState = RUNNING; // just in case
+ messageCacheLock.notifyAll(); // wake up anyone waiting
+ notifyConnectionListeners(ConnectionEvent.CLOSED);
+ }
+
+ /**
+ * Check whether this connection is really open.
+ */
+ @Override
+ public synchronized boolean isOpen() {
+ synchronized(messageCacheLock) {
+ // Probe the connection to make sure its really open.
+ if (opened) {
+ try {
+ keepConnectionAlive(false);
+ } catch (ProtocolException pex) { }
+ }
+ }
+
+ return opened;
+ }
+
+ /**
+ * Return the permanent flags supported by the server.
+ */
+ @Override
+ public synchronized Flags getPermanentFlags() {
+ if (permanentFlags == null)
+ return null;
+ return (Flags)(permanentFlags.clone());
+ }
+
+ /**
+ * Get the total message count.
+ */
+ @Override
+ public synchronized int getMessageCount() throws MessagingException {
+ synchronized (messageCacheLock) {
+ if (opened) {
+ // Folder is open, we know what the total message count is ..
+ // tickle the folder and store connections.
+ try {
+ keepConnectionAlive(true);
+ return total;
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ // If this folder is not yet open, we use STATUS to
+ // get the total message count
+ checkExists();
+ try {
+ Status status = getStatus();
+ return status.total;
+ } catch (BadCommandException bex) {
+ // doesn't support STATUS, probably vanilla IMAP4 ..
+ // lets try EXAMINE
+ IMAPProtocol p = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ MailboxInfo minfo = p.examine(fullName);
+ p.close();
+ return minfo.total;
+ } catch (ProtocolException pex) {
+ // Give up.
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(store, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the new message count.
+ */
+ @Override
+ public synchronized int getNewMessageCount() throws MessagingException {
+ synchronized (messageCacheLock) {
+ if (opened) {
+ // Folder is open, we know what the new message count is ..
+ // tickle the folder and store connections.
+ try {
+ keepConnectionAlive(true);
+ return recent;
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ // If this folder is not yet open, we use STATUS to
+ // get the new message count
+ checkExists();
+ try {
+ Status status = getStatus();
+ return status.recent;
+ } catch (BadCommandException bex) {
+ // doesn't support STATUS, probably vanilla IMAP4 ..
+ // lets try EXAMINE
+ IMAPProtocol p = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ MailboxInfo minfo = p.examine(fullName);
+ p.close();
+ return minfo.recent;
+ } catch (ProtocolException pex) {
+ // Give up.
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(store, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the unread message count.
+ */
+ @Override
+ public synchronized int getUnreadMessageCount()
+ throws MessagingException {
+ if (!opened) {
+ checkExists();
+ // If this folder is not yet open, we use STATUS to
+ // get the unseen message count
+ try {
+ Status status = getStatus();
+ return status.unseen;
+ } catch (BadCommandException bex) {
+ // doesn't support STATUS, probably vanilla IMAP4 ..
+ // Could EXAMINE, SEARCH for UNREAD messages and
+ // return the count .. bah, not worth it.
+ return -1;
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(store, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ // if opened, issue server-side search for messages that do
+ // *not* have the SEEN flag.
+ Flags f = new Flags();
+ f.add(Flags.Flag.SEEN);
+ try {
+ synchronized(messageCacheLock) {
+ int[] matches = getProtocol().search(new FlagTerm(f, false));
+ return matches.length; // NOTE: 'matches' is never null
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // Shouldn't happen
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the deleted message count.
+ */
+ @Override
+ public synchronized int getDeletedMessageCount()
+ throws MessagingException {
+ if (!opened) {
+ checkExists();
+ // no way to do this on closed folders
+ return -1;
+ }
+
+ // if opened, issue server-side search for messages that do
+ // have the DELETED flag.
+ Flags f = new Flags();
+ f.add(Flags.Flag.DELETED);
+ try {
+ synchronized(messageCacheLock) {
+ int[] matches = getProtocol().search(new FlagTerm(f, true));
+ return matches.length; // NOTE: 'matches' is never null
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // Shouldn't happen
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /*
+ * Get results of STATUS command for this folder, checking cache first.
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ * ASSERT: The folder must be closed.
+ */
+ private Status getStatus() throws ProtocolException {
+ int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();
+
+ // if allowed to cache and our cache is still valid, return it
+ if (statusCacheTimeout > 0 && cachedStatus != null &&
+ System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
+ return cachedStatus;
+
+ IMAPProtocol p = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ Status s = p.status(fullName, null);
+ // if allowed to cache, do so
+ if (statusCacheTimeout > 0) {
+ cachedStatus = s;
+ cachedStatusTime = System.currentTimeMillis();
+ }
+ return s;
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+
+ /**
+ * Get the specified message.
+ */
+ @Override
+ public synchronized Message getMessage(int msgnum)
+ throws MessagingException {
+ checkOpened();
+ checkRange(msgnum);
+
+ return messageCache.getMessage(msgnum);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized Message[] getMessages() throws MessagingException {
+ /*
+ * Need to override Folder method to throw FolderClosedException
+ * instead of IllegalStateException if not really closed.
+ */
+ checkOpened();
+ int total = getMessageCount();
+ Message[] msgs = new Message[total];
+ for (int i = 1; i <= total; i++)
+ msgs[i - 1] = messageCache.getMessage(i);
+ return msgs;
+ }
+
+ /**
+ * Append the given messages into this folder.
+ */
+ @Override
+ public synchronized void appendMessages(Message[] msgs)
+ throws MessagingException {
+ checkExists(); // verify that self exists
+
+ // XXX - have to verify that messages are in a different
+ // store (if any) than target folder, otherwise could
+ // deadlock trying to fetch messages on the same connection
+ // we're using for the append.
+
+ int maxsize = ((IMAPStore)store).getAppendBufferSize();
+
+ for (int i = 0; i < msgs.length; i++) {
+ final Message m = msgs[i];
+ Date d = m.getReceivedDate(); // retain dates
+ if (d == null)
+ d = m.getSentDate();
+ final Date dd = d;
+ final Flags f = m.getFlags();
+
+ final MessageLiteral mos;
+ try {
+ // if we know the message is too big, don't buffer any of it
+ mos = new MessageLiteral(m,
+ m.getSize() > maxsize ? 0 : maxsize);
+ } catch (IOException ex) {
+ throw new MessagingException(
+ "IOException while appending messages", ex);
+ } catch (MessageRemovedException mrex) {
+ continue; // just skip this expunged message
+ }
+
+ doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ p.append(fullName, f, dd, mos);
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Append the given messages into this folder.
+ * Return array of AppendUID objects containing
+ * UIDs of these messages in the destination folder.
+ * Each element of the returned array corresponds to
+ * an element of the msgs
array. A null
+ * element means the server didn't return UID information
+ * for the appended message.
+ *
+ * Depends on the APPENDUID response code defined by the
+ * UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @param msgs the messages to append
+ * @return array of AppendUID objects
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4
+ */
+ public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
+ throws MessagingException {
+ checkExists(); // verify that self exists
+
+ // XXX - have to verify that messages are in a different
+ // store (if any) than target folder, otherwise could
+ // deadlock trying to fetch messages on the same connection
+ // we're using for the append.
+
+ int maxsize = ((IMAPStore)store).getAppendBufferSize();
+
+ AppendUID[] uids = new AppendUID[msgs.length];
+ for (int i = 0; i < msgs.length; i++) {
+ final Message m = msgs[i];
+ final MessageLiteral mos;
+
+ try {
+ // if we know the message is too big, don't buffer any of it
+ mos = new MessageLiteral(m,
+ m.getSize() > maxsize ? 0 : maxsize);
+ } catch (IOException ex) {
+ throw new MessagingException(
+ "IOException while appending messages", ex);
+ } catch (MessageRemovedException mrex) {
+ continue; // just skip this expunged message
+ }
+
+ Date d = m.getReceivedDate(); // retain dates
+ if (d == null)
+ d = m.getSentDate();
+ final Date dd = d;
+ final Flags f = m.getFlags();
+ AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.appenduid(fullName, f, dd, mos);
+ }
+ });
+ uids[i] = auid;
+ }
+ return uids;
+ }
+
+ /**
+ * Append the given messages into this folder.
+ * Return array of Message objects representing
+ * the messages in the destination folder. Note
+ * that the folder must be open.
+ * Each element of the returned array corresponds to
+ * an element of the msgs
array. A null
+ * element means the server didn't return UID information
+ * for the appended message.
+ *
+ * Depends on the APPENDUID response code defined by the
+ * UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @param msgs the messages to add
+ * @return the messages in this folder
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4
+ */
+ public synchronized Message[] addMessages(Message[] msgs)
+ throws MessagingException {
+ checkOpened();
+ Message[] rmsgs = new MimeMessage[msgs.length];
+ AppendUID[] uids = appendUIDMessages(msgs);
+ for (int i = 0; i < uids.length; i++) {
+ AppendUID auid = uids[i];
+ if (auid != null) {
+ if (auid.uidvalidity == uidvalidity) {
+ try {
+ rmsgs[i] = getMessageByUID(auid.uid);
+ } catch (MessagingException mex) {
+ // ignore errors at this stage
+ }
+ }
+ }
+ }
+ return rmsgs;
+ }
+
+ /**
+ * Copy the specified messages from this folder, to the
+ * specified destination.
+ */
+ @Override
+ public synchronized void copyMessages(Message[] msgs, Folder folder)
+ throws MessagingException {
+ copymoveMessages(msgs, folder, false);
+ }
+
+ /**
+ * Copy the specified messages from this folder, to the
+ * specified destination.
+ * Return array of AppendUID objects containing
+ * UIDs of these messages in the destination folder.
+ * Each element of the returned array corresponds to
+ * an element of the msgs
array. A null
+ * element means the server didn't return UID information
+ * for the copied message.
+ *
+ * Depends on the COPYUID response code defined by the
+ * UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @param msgs the messages to copy
+ * @param folder the folder to copy the messages to
+ * @return array of AppendUID objects
+ * @exception MessagingException for failures
+ * @since JavaMail 1.5.1
+ */
+ public synchronized AppendUID[] copyUIDMessages(Message[] msgs,
+ Folder folder) throws MessagingException {
+ return copymoveUIDMessages(msgs, folder, false);
+ }
+
+ /**
+ * Move the specified messages from this folder, to the
+ * specified destination.
+ *
+ * Depends on the MOVE extension
+ * (RFC 6851 ).
+ *
+ * @param msgs the messages to move
+ * @param folder the folder to move the messages to
+ * @exception MessagingException for failures
+ *
+ * @since JavaMail 1.5.4
+ */
+ public synchronized void moveMessages(Message[] msgs, Folder folder)
+ throws MessagingException {
+ copymoveMessages(msgs, folder, true);
+ }
+
+ /**
+ * Move the specified messages from this folder, to the
+ * specified destination.
+ * Return array of AppendUID objects containing
+ * UIDs of these messages in the destination folder.
+ * Each element of the returned array corresponds to
+ * an element of the msgs
array. A null
+ * element means the server didn't return UID information
+ * for the moved message.
+ *
+ * Depends on the MOVE extension
+ * (RFC 6851 )
+ * and the COPYUID response code defined by the
+ * UIDPLUS extension
+ * (RFC 4315 ).
+ *
+ * @param msgs the messages to move
+ * @param folder the folder to move the messages to
+ * @return array of AppendUID objects
+ * @exception MessagingException for failures
+ * @since JavaMail 1.5.4
+ */
+ public synchronized AppendUID[] moveUIDMessages(Message[] msgs,
+ Folder folder) throws MessagingException {
+ return copymoveUIDMessages(msgs, folder, true);
+ }
+
+ /**
+ * Copy or move the specified messages from this folder, to the
+ * specified destination.
+ *
+ * @since JavaMail 1.5.4
+ */
+ private synchronized void copymoveMessages(Message[] msgs, Folder folder,
+ boolean move) throws MessagingException {
+ checkOpened();
+
+ if (msgs.length == 0) // boundary condition
+ return;
+
+ // If the destination belongs to our same store, optimize
+ if (folder.getStore() == store) {
+ synchronized(messageCacheLock) {
+ try {
+ IMAPProtocol p = getProtocol();
+ MessageSet[] ms = Utility.toMessageSet(msgs, null);
+ if (ms == null)
+ throw new MessageRemovedException(
+ "Messages have been removed");
+ if (move)
+ p.move(ms, folder.getFullName());
+ else
+ p.copy(ms, folder.getFullName());
+ } catch (CommandFailedException cfx) {
+ if (cfx.getMessage().indexOf("TRYCREATE") != -1)
+ throw new FolderNotFoundException(
+ folder,
+ folder.getFullName() + " does not exist"
+ );
+ else
+ throw new MessagingException(cfx.getMessage(), cfx);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ } else // destination is a different store.
+ if (move)
+ throw new MessagingException(
+ "Move between stores not supported");
+ else
+ super.copyMessages(msgs, folder);
+ }
+
+ /**
+ * Copy or move the specified messages from this folder, to the
+ * specified destination.
+ * Return array of AppendUID objects containing
+ * UIDs of these messages in the destination folder.
+ * Each element of the returned array corresponds to
+ * an element of the msgs
array. A null
+ * element means the server didn't return UID information
+ * for the copied message.
+ *
+ * Depends on the COPYUID response code defined by the
+ * UIDPLUS extension -
+ * RFC 4315 .
+ * Move depends on the MOVE extension -
+ * RFC 6851 .
+ *
+ * @param msgs the messages to copy
+ * @param folder the folder to copy the messages to
+ * @param move move instead of copy?
+ * @return array of AppendUID objects
+ * @exception MessagingException for failures
+ * @since JavaMail 1.5.4
+ */
+ private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs,
+ Folder folder, boolean move) throws MessagingException {
+ checkOpened();
+
+ if (msgs.length == 0) // boundary condition
+ return null;
+
+ // the destination must belong to our same store
+ if (folder.getStore() != store) // destination is a different store.
+ throw new MessagingException(
+ move ?
+ "can't moveUIDMessages to a different store" :
+ "can't copyUIDMessages to a different store");
+
+ // call fetch to make sure we have all the UIDs
+ // necessary to interpret the COPYUID response
+ FetchProfile fp = new FetchProfile();
+ fp.add(UIDFolder.FetchProfileItem.UID);
+ fetch(msgs, fp);
+ // XXX - could pipeline the FETCH with the COPY/MOVE below
+
+ synchronized (messageCacheLock) {
+ try {
+ IMAPProtocol p = getProtocol();
+ // XXX - messages have to be from this Folder, who checks?
+ MessageSet[] ms = Utility.toMessageSet(msgs, null);
+ if (ms == null)
+ throw new MessageRemovedException(
+ "Messages have been removed");
+ CopyUID cuid;
+ if (move)
+ cuid = p.moveuid(ms, folder.getFullName());
+ else
+ cuid = p.copyuid(ms, folder.getFullName());
+
+ /*
+ * Correlate source UIDs with destination UIDs.
+ * This won't be time or space efficient if there's
+ * a lot of messages.
+ *
+ * In order to make sense of the returned UIDs, we need
+ * the UIDs for every one of the original messages.
+ * We fetch them above, to make sure we have them.
+ * This is critical for MOVE since after the MOVE the
+ * messages are gone/expunged.
+ *
+ * Assume the common case is that the messages are
+ * in order by UID. Map the returned source
+ * UIDs to their corresponding Message objects.
+ * Step through the msgs array looking for the
+ * Message object in the returned source message
+ * list. Most commonly the source message (UID)
+ * for the Nth original message will be in the Nth
+ * position in the returned source message (UID)
+ * list. Thus, the destination UID is in the Nth
+ * position in the returned destination UID list.
+ * But if the source message isn't where expected,
+ * we have to search the entire source message
+ * list, starting from where we expect it and
+ * wrapping around until we've searched it all.
+ * (Gmail will often return the lists in an unexpected order.)
+ *
+ * A possible optimization:
+ * If the number of UIDs returned is the same as the
+ * number of messages being copied/moved, we could
+ * sort the source messages by message number, sort
+ * the source and destination parallel arrays by source
+ * UID, and the resulting message and destination UID
+ * arrays will correspond.
+ *
+ * If the returned UID array size is different, some
+ * message was expunged while we were trying to copy/move it.
+ * This should be rare but would mean falling back to the
+ * general algorithm.
+ */
+ long[] srcuids = UIDSet.toArray(cuid.src);
+ long[] dstuids = UIDSet.toArray(cuid.dst);
+ // map source UIDs to Message objects
+ // XXX - could inline/optimize this
+ Message[] srcmsgs = getMessagesByUID(srcuids);
+ AppendUID[] result = new AppendUID[msgs.length];
+ for (int i = 0; i < msgs.length; i++) {
+ int j = i;
+ do {
+ if (msgs[i] == srcmsgs[j]) {
+ result[i] = new AppendUID(
+ cuid.uidvalidity, dstuids[j]);
+ break;
+ }
+ j++;
+ if (j >= srcmsgs.length)
+ j = 0;
+ } while (j != i);
+ }
+ return result;
+ } catch (CommandFailedException cfx) {
+ if (cfx.getMessage().indexOf("TRYCREATE") != -1)
+ throw new FolderNotFoundException(
+ folder,
+ folder.getFullName() + " does not exist"
+ );
+ else
+ throw new MessagingException(cfx.getMessage(), cfx);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ /**
+ * Expunge all messages marked as DELETED.
+ */
+ @Override
+ public synchronized Message[] expunge() throws MessagingException {
+ return expunge(null);
+ }
+
+ /**
+ * Expunge the indicated messages, which must have been marked as DELETED.
+ *
+ * Depends on the UIDPLUS extension -
+ * RFC 4315 .
+ *
+ * @param msgs the messages to expunge
+ * @return the expunged messages
+ * @exception MessagingException for failures
+ */
+ public synchronized Message[] expunge(Message[] msgs)
+ throws MessagingException {
+ checkOpened();
+
+ if (msgs != null) {
+ // call fetch to make sure we have all the UIDs
+ FetchProfile fp = new FetchProfile();
+ fp.add(UIDFolder.FetchProfileItem.UID);
+ fetch(msgs, fp);
+ }
+
+ IMAPMessage[] rmsgs;
+ synchronized(messageCacheLock) {
+ doExpungeNotification = false; // We do this ourselves later
+ try {
+ IMAPProtocol p = getProtocol();
+ if (msgs != null)
+ p.uidexpunge(Utility.toUIDSet(msgs));
+ else
+ p.expunge();
+ } catch (CommandFailedException cfx) {
+ // expunge not allowed, perhaps due to a permission problem?
+ if (mode != READ_WRITE)
+ throw new IllegalStateException(
+ "Cannot expunge READ_ONLY folder: " + fullName);
+ else
+ throw new MessagingException(cfx.getMessage(), cfx);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // Bad bad server ..
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ doExpungeNotification = true;
+ }
+
+ // Cleanup expunged messages and sync messageCache with reality.
+ if (msgs != null)
+ rmsgs = messageCache.removeExpungedMessages(msgs);
+ else
+ rmsgs = messageCache.removeExpungedMessages();
+ if (uidTable != null) {
+ for (int i = 0; i < rmsgs.length; i++) {
+ IMAPMessage m = rmsgs[i];
+ /* remove this message from the UIDTable */
+ long uid = m.getUID();
+ if (uid != -1)
+ uidTable.remove(Long.valueOf(uid));
+ }
+ }
+
+ // Update 'total'
+ total = messageCache.size();
+ }
+
+ // Notify listeners. This time its for real, guys.
+ if (rmsgs.length > 0)
+ notifyMessageRemovedListeners(true, rmsgs);
+ return rmsgs;
+ }
+
+ /**
+ * Search whole folder for messages matching the given term.
+ * If the property mail.imap.throwsearchexception
is true,
+ * and the search term is too complex for the IMAP protocol,
+ * SearchException is thrown. Otherwise, if the search term is too
+ * complex, super.search
is called to do the search on
+ * the client.
+ *
+ * @param term the search term
+ * @return the messages that match
+ * @exception SearchException if mail.imap.throwsearchexception is
+ * true and the search is too complex for the IMAP protocol
+ * @exception MessagingException for other failures
+ */
+ @Override
+ public synchronized Message[] search(SearchTerm term)
+ throws MessagingException {
+ checkOpened();
+
+ try {
+ Message[] matchMsgs = null;
+
+ synchronized(messageCacheLock) {
+ int[] matches = getProtocol().search(term);
+ if (matches != null)
+ matchMsgs = getMessagesBySeqNumbers(matches);
+ }
+ return matchMsgs;
+
+ } catch (CommandFailedException cfx) {
+ // unsupported charset or search criterion
+ return super.search(term);
+ } catch (SearchException sex) {
+ // too complex for IMAP
+ if (((IMAPStore)store).throwSearchException())
+ throw sex;
+ return super.search(term);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // bug in our IMAP layer ?
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Search the folder for messages matching the given term. Returns
+ * array of matching messages. Returns an empty array if no matching
+ * messages are found.
+ */
+ @Override
+ public synchronized Message[] search(SearchTerm term, Message[] msgs)
+ throws MessagingException {
+ checkOpened();
+
+ if (msgs.length == 0)
+ // need to return an empty array (not null!)
+ return msgs;
+
+ try {
+ Message[] matchMsgs = null;
+
+ synchronized(messageCacheLock) {
+ IMAPProtocol p = getProtocol();
+ MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
+ if (ms == null)
+ throw new MessageRemovedException(
+ "Messages have been removed");
+ int[] matches = p.search(ms, term);
+ if (matches != null)
+ matchMsgs = getMessagesBySeqNumbers(matches);
+ }
+ return matchMsgs;
+
+ } catch (CommandFailedException cfx) {
+ // unsupported charset or search criterion
+ return super.search(term, msgs);
+ } catch (SearchException sex) {
+ // too complex for IMAP
+ return super.search(term, msgs);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // bug in our IMAP layer ?
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Sort the messages in the folder according to the sort criteria.
+ * The messages are returned in the sorted order, but the order of
+ * the messages in the folder is not changed.
+ *
+ * Depends on the SORT extension -
+ * RFC 5256 .
+ *
+ * @param term the SortTerms
+ * @return the messages in sorted order
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.4
+ */
+ public synchronized Message[] getSortedMessages(SortTerm[] term)
+ throws MessagingException {
+ return getSortedMessages(term, null);
+ }
+
+ /**
+ * Sort the messages in the folder according to the sort criteria.
+ * The messages are returned in the sorted order, but the order of
+ * the messages in the folder is not changed. Only messages matching
+ * the search criteria are considered.
+ *
+ * Depends on the SORT extension -
+ * RFC 5256 .
+ *
+ * @param term the SortTerms
+ * @param sterm the SearchTerm
+ * @return the messages in sorted order
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.4
+ */
+ public synchronized Message[] getSortedMessages(SortTerm[] term,
+ SearchTerm sterm) throws MessagingException {
+ checkOpened();
+
+ try {
+ Message[] matchMsgs = null;
+
+ synchronized(messageCacheLock) {
+ int[] matches = getProtocol().sort(term, sterm);
+ if (matches != null)
+ matchMsgs = getMessagesBySeqNumbers(matches);
+ }
+ return matchMsgs;
+
+ } catch (CommandFailedException cfx) {
+ // unsupported charset or search criterion
+ throw new MessagingException(cfx.getMessage(), cfx);
+ } catch (SearchException sex) {
+ // too complex for IMAP
+ throw new MessagingException(sex.getMessage(), sex);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // bug in our IMAP layer ?
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /*
+ * Override Folder method to keep track of whether we have any
+ * message count listeners. Normally we won't have any, so we
+ * can avoid creating message objects to pass to the notify
+ * method. It's too hard to keep track of when all listeners
+ * are removed, and that's a rare case, so we don't try.
+ */
+ @Override
+ public synchronized void addMessageCountListener(MessageCountListener l) {
+ super.addMessageCountListener(l);
+ hasMessageCountListener = true;
+ }
+
+ /***********************************************************
+ * UIDFolder interface methods
+ **********************************************************/
+
+ /**
+ * Returns the UIDValidity for this folder.
+ */
+ @Override
+ public synchronized long getUIDValidity() throws MessagingException {
+ if (opened) // we already have this information
+ return uidvalidity;
+
+ IMAPProtocol p = null;
+ Status status = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ String[] item = { "UIDVALIDITY" };
+ status = p.status(fullName, item);
+ } catch (BadCommandException bex) {
+ // Probably a RFC1730 server
+ throw new MessagingException("Cannot obtain UIDValidity", bex);
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+
+ if (status == null)
+ throw new MessagingException("Cannot obtain UIDValidity");
+ return status.uidvalidity;
+ }
+
+ /**
+ * Returns the predicted UID that will be assigned to the
+ * next message that is appended to this folder.
+ * If the folder is closed, the STATUS command is used to
+ * retrieve this value. If the folder is open, the value
+ * returned from the SELECT or EXAMINE command is returned.
+ * Note that messages may have been appended to the folder
+ * while it was open and thus this value may be out of
+ * date.
+ *
+ * Servers implementing RFC2060 likely won't return this value
+ * when a folder is opened. Servers implementing RFC3501
+ * should return this value when a folder is opened.
+ *
+ * @return the UIDNEXT value, or -1 if unknown
+ * @exception MessagingException for failures
+ * @since JavaMail 1.3.3
+ */
+ @Override
+ public synchronized long getUIDNext() throws MessagingException {
+ if (opened) // we already have this information
+ return uidnext;
+
+ IMAPProtocol p = null;
+ Status status = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ String[] item = { "UIDNEXT" };
+ status = p.status(fullName, item);
+ } catch (BadCommandException bex) {
+ // Probably a RFC1730 server
+ throw new MessagingException("Cannot obtain UIDNext", bex);
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+
+ if (status == null)
+ throw new MessagingException("Cannot obtain UIDNext");
+ return status.uidnext;
+ }
+
+ /**
+ * Get the Message corresponding to the given UID.
+ * If no such message exists, null
is returned.
+ */
+ @Override
+ public synchronized Message getMessageByUID(long uid)
+ throws MessagingException {
+ checkOpened(); // insure folder is open
+
+ IMAPMessage m = null;
+
+ try {
+ synchronized(messageCacheLock) {
+ Long l = Long.valueOf(uid);
+
+ if (uidTable != null) {
+ // Check in uidTable
+ m = uidTable.get(l);
+ if (m != null) // found it
+ return m;
+ } else
+ uidTable = new Hashtable<>();
+
+ // Check with the server
+ // Issue UID FETCH command
+ getProtocol().fetchSequenceNumber(uid);
+
+ if (uidTable != null) {
+ // Check in uidTable
+ m = uidTable.get(l);
+ if (m != null) // found it
+ return m;
+ }
+ }
+ } catch(ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+
+ return m;
+ }
+
+ /**
+ * Get the Messages specified by the given range.
+ * Returns Message objects for all valid messages in this range.
+ * Returns an empty array if no messages are found.
+ */
+ @Override
+ public synchronized Message[] getMessagesByUID(long start, long end)
+ throws MessagingException {
+ checkOpened(); // insure that folder is open
+
+ Message[] msgs; // array of messages to be returned
+
+ try {
+ synchronized(messageCacheLock) {
+ if (uidTable == null)
+ uidTable = new Hashtable<>();
+
+ // Issue UID FETCH for given range
+ long[] ua = getProtocol().fetchSequenceNumbers(start, end);
+
+ List ma = new ArrayList<>();
+ // NOTE: Below must be within messageCacheLock region
+ for (int i = 0; i < ua.length; i++) {
+ Message m = uidTable.get(Long.valueOf(ua[i]));
+ if (m != null) // found it
+ ma.add(m);
+ }
+ msgs = ma.toArray(new Message[ma.size()]);
+ }
+ } catch(ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+
+ return msgs;
+ }
+
+ /**
+ * Get the Messages specified by the given array.
+ *
+ * uids.length()
elements are returned.
+ * If any UID in the array is invalid, a null
entry
+ * is returned for that element.
+ */
+ @Override
+ public synchronized Message[] getMessagesByUID(long[] uids)
+ throws MessagingException {
+ checkOpened(); // insure that folder is open
+
+ try {
+ synchronized(messageCacheLock) {
+ long[] unavailUids = uids;
+ if (uidTable != null) {
+ // to collect unavailable UIDs
+ List v = new ArrayList<>();
+ for (long uid : uids) {
+ if (!uidTable.containsKey(uid)) {
+ // This UID has not been loaded yet.
+ v.add(uid);
+ }
+ }
+
+ int vsize = v.size();
+ unavailUids = new long[vsize];
+ for (int i = 0; i < vsize; i++) {
+ unavailUids[i] = v.get(i);
+ }
+ } else
+ uidTable = new Hashtable<>();
+
+ if (unavailUids.length > 0) {
+ // Issue UID FETCH request for given uids
+ getProtocol().fetchSequenceNumbers(unavailUids);
+ }
+
+ // Return array of size = uids.length
+ Message[] msgs = new Message[uids.length];
+ for (int i = 0; i < uids.length; i++)
+ msgs[i] = (Message)uidTable.get(Long.valueOf(uids[i]));
+ return msgs;
+ }
+ } catch(ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the UID for the specified message.
+ */
+ @Override
+ public synchronized long getUID(Message message)
+ throws MessagingException {
+ if (message.getFolder() != this)
+ throw new NoSuchElementException(
+ "Message does not belong to this folder");
+
+ checkOpened(); // insure that folder is open
+
+ if (!(message instanceof IMAPMessage))
+ throw new MessagingException("message is not an IMAPMessage");
+ IMAPMessage m = (IMAPMessage)message;
+ // If the message already knows its UID, great ..
+ long uid;
+ if ((uid = m.getUID()) != -1)
+ return uid;
+
+ synchronized(messageCacheLock) { // Acquire Lock
+ try {
+ IMAPProtocol p = getProtocol();
+ m.checkExpunged(); // insure that message is not expunged
+ UID u = p.fetchUID(m.getSequenceNumber());
+
+ if (u != null) {
+ uid = u.uid;
+ m.setUID(uid); // set message's UID
+
+ // insert this message into uidTable
+ if (uidTable == null)
+ uidTable = new Hashtable<>();
+ uidTable.put(Long.valueOf(uid), m);
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ return uid;
+ }
+
+ /**
+ * Servers that support the UIDPLUS extension
+ * (RFC 4315 )
+ * may indicate that this folder does not support persistent UIDs;
+ * that is, UIDVALIDITY will be different each time the folder is
+ * opened. Only valid when the folder is open.
+ *
+ * @return true if UIDs are not sticky
+ * @exception MessagingException for failures
+ * @exception IllegalStateException if the folder isn't open
+ * @see "RFC 4315"
+ * @since JavaMail 1.6.0
+ */
+ public synchronized boolean getUIDNotSticky() throws MessagingException {
+ checkOpened();
+ return uidNotSticky;
+ }
+
+ /**
+ * Get or create Message objects for the UIDs.
+ */
+ private Message[] createMessagesForUIDs(long[] uids) {
+ IMAPMessage[] msgs = new IMAPMessage[uids.length];
+ for (int i = 0; i < uids.length; i++) {
+ IMAPMessage m = null;
+ if (uidTable != null)
+ m = uidTable.get(Long.valueOf(uids[i]));
+ if (m == null) {
+ // fake it, we don't know what message this really is
+ m = newIMAPMessage(-1); // no sequence number
+ m.setUID(uids[i]);
+ m.setExpunged(true);
+ }
+ msgs[i++] = m;
+ }
+ return msgs;
+ }
+
+ /**
+ * Returns the HIGHESTMODSEQ for this folder.
+ *
+ * @return the HIGHESTMODSEQ value
+ * @exception MessagingException for failures
+ * @see "RFC 4551"
+ * @since JavaMail 1.5.1
+ */
+ public synchronized long getHighestModSeq() throws MessagingException {
+ if (opened) // we already have this information
+ return highestmodseq;
+
+ IMAPProtocol p = null;
+ Status status = null;
+
+ try {
+ p = getStoreProtocol(); // XXX
+ if (!p.hasCapability("CONDSTORE"))
+ throw new BadCommandException("CONDSTORE not supported");
+ String[] item = { "HIGHESTMODSEQ" };
+ status = p.status(fullName, item);
+ } catch (BadCommandException bex) {
+ // Probably a RFC1730 server
+ throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex);
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+
+ if (status == null)
+ throw new MessagingException("Cannot obtain HIGHESTMODSEQ");
+ return status.highestmodseq;
+ }
+
+ /**
+ * Get the messages that have been changed since the given MODSEQ value.
+ * Also, prefetch the flags for the messages.
+ *
+ * The server must support the CONDSTORE extension.
+ *
+ * @param start the first message number
+ * @param end the last message number
+ * @param modseq the MODSEQ value
+ * @return the changed messages
+ * @exception MessagingException for failures
+ * @see "RFC 4551"
+ * @since JavaMail 1.5.1
+ */
+ public synchronized Message[] getMessagesByUIDChangedSince(
+ long start, long end, long modseq)
+ throws MessagingException {
+ checkOpened(); // insure that folder is open
+
+ try {
+ synchronized (messageCacheLock) {
+ IMAPProtocol p = getProtocol();
+ if (!p.hasCapability("CONDSTORE"))
+ throw new BadCommandException("CONDSTORE not supported");
+
+ // Issue FETCH for given range
+ int[] nums = p.uidfetchChangedSince(start, end, modseq);
+ return getMessagesBySeqNumbers(nums);
+ }
+ } catch(ConnectionException cex) {
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ /**
+ * Get the quotas for the quotaroot associated with this
+ * folder. Note that many folders may have the same quotaroot.
+ * Quotas are controlled on the basis of a quotaroot, not
+ * (necessarily) a folder. The relationship between folders
+ * and quotaroots depends on the IMAP server. Some servers
+ * might implement a single quotaroot for all folders owned by
+ * a user. Other servers might implement a separate quotaroot
+ * for each folder. A single folder can even have multiple
+ * quotaroots, perhaps controlling quotas for different
+ * resources.
+ *
+ * @return array of Quota objects for the quotaroots associated with
+ * this folder
+ * @exception MessagingException if the server doesn't support the
+ * QUOTA extension
+ */
+ public Quota[] getQuota() throws MessagingException {
+ return (Quota[])doOptionalCommand("QUOTA not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.getQuotaRoot(fullName);
+ }
+ });
+ }
+
+ /**
+ * Set the quotas for the quotaroot specified in the quota argument.
+ * Typically this will be one of the quotaroots associated with this
+ * folder, as obtained from the getQuota
method, but it
+ * need not be.
+ *
+ * @param quota the quota to set
+ * @exception MessagingException if the server doesn't support the
+ * QUOTA extension
+ */
+ public void setQuota(final Quota quota) throws MessagingException {
+ doOptionalCommand("QUOTA not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ p.setQuota(quota);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Get the access control list entries for this folder.
+ *
+ * @return array of access control list entries
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public ACL[] getACL() throws MessagingException {
+ return (ACL[])doOptionalCommand("ACL not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.getACL(fullName);
+ }
+ });
+ }
+
+ /**
+ * Add an access control list entry to the access control list
+ * for this folder.
+ *
+ * @param acl the access control list entry to add
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public void addACL(ACL acl) throws MessagingException {
+ setACL(acl, '\0');
+ }
+
+ /**
+ * Remove any access control list entry for the given identifier
+ * from the access control list for this folder.
+ *
+ * @param name the identifier for which to remove all ACL entries
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public void removeACL(final String name) throws MessagingException {
+ doOptionalCommand("ACL not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ p.deleteACL(fullName, name);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Add the rights specified in the ACL to the entry for the
+ * identifier specified in the ACL. If an entry for the identifier
+ * doesn't already exist, add one.
+ *
+ * @param acl the identifer and rights to add
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public void addRights(ACL acl) throws MessagingException {
+ setACL(acl, '+');
+ }
+
+ /**
+ * Remove the rights specified in the ACL from the entry for the
+ * identifier specified in the ACL.
+ *
+ * @param acl the identifer and rights to remove
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public void removeRights(ACL acl) throws MessagingException {
+ setACL(acl, '-');
+ }
+
+ /**
+ * Get all the rights that may be allowed to the given identifier.
+ * Rights are grouped per RFC 2086 and each group is returned as an
+ * element of the array. The first element of the array is the set
+ * of rights that are always granted to the identifier. Later
+ * elements are rights that may be optionally granted to the
+ * identifier.
+ *
+ * Note that this method lists the rights that it is possible to
+ * assign to the given identifier, not the rights that are
+ * actually granted to the given identifier. For the latter, see
+ * the getACL
method.
+ *
+ * @param name the identifier to list rights for
+ * @return array of Rights objects representing possible
+ * rights for the identifier
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public Rights[] listRights(final String name) throws MessagingException {
+ return (Rights[])doOptionalCommand("ACL not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.listRights(fullName, name);
+ }
+ });
+ }
+
+ /**
+ * Get the rights allowed to the currently authenticated user.
+ *
+ * @return the rights granted to the current user
+ * @exception MessagingException if the server doesn't support the
+ * ACL extension
+ */
+ public Rights myRights() throws MessagingException {
+ return (Rights)doOptionalCommand("ACL not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.myRights(fullName);
+ }
+ });
+ }
+
+ private void setACL(final ACL acl, final char mod)
+ throws MessagingException {
+ doOptionalCommand("ACL not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ p.setACL(fullName, mod, acl);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Get the attributes that the IMAP server returns with the
+ * LIST response.
+ *
+ * @return array of attributes for this folder
+ * @exception MessagingException for failures
+ * @since JavaMail 1.3.3
+ */
+ public synchronized String[] getAttributes() throws MessagingException {
+ checkExists();
+ if (attributes == null)
+ exists(); // do a LIST to set the attributes
+ return attributes == null ? new String[0] : attributes.clone();
+ }
+
+ /**
+ * Use the IMAP IDLE command (see
+ * RFC 2177 ),
+ * if supported by the server, to enter idle mode so that the server
+ * can send unsolicited notifications of new messages arriving, etc.
+ * without the need for the client to constantly poll the server.
+ * Use an appropriate listener to be notified of new messages or
+ * other events. When another thread (e.g., the listener thread)
+ * needs to issue an IMAP comand for this folder, the idle mode will
+ * be terminated and this method will return. Typically the caller
+ * will invoke this method in a loop.
+ *
+ * The mail.imap.minidletime property enforces a minimum delay
+ * before returning from this method, to ensure that other threads
+ * have a chance to issue commands before the caller invokes this
+ * method again. The default delay is 10 milliseconds.
+ *
+ * @exception MessagingException if the server doesn't support the
+ * IDLE extension
+ * @exception IllegalStateException if the folder isn't open
+ *
+ * @since JavaMail 1.4.1
+ */
+ public void idle() throws MessagingException {
+ idle(false);
+ }
+
+ /**
+ * Like {@link #idle}, but if once
is true, abort the
+ * IDLE command after the first notification, to allow the caller
+ * to process any notification synchronously.
+ *
+ * @param once only do one notification?
+ * @exception MessagingException if the server doesn't support the
+ * IDLE extension
+ * @exception IllegalStateException if the folder isn't open
+ *
+ * @since JavaMail 1.4.3
+ */
+ public void idle(boolean once) throws MessagingException {
+ synchronized (this) {
+ /*
+ * We can't support the idle method if we're using SocketChannels
+ * because SocketChannels don't allow simultaneous read and write.
+ * If we're blocked in a read waiting for IDLE responses, we can't
+ * send the DONE message to abort the IDLE. Sigh.
+ * XXX - We could do select here too, like IdleManager, instead
+ * of blocking in read, but that's more complicated.
+ */
+ if (protocol != null && protocol.getChannel() != null)
+ throw new MessagingException(
+ "idle method not supported with SocketChannels");
+ }
+ if (!startIdle(null))
+ return;
+
+ /*
+ * We gave up the folder lock so that other threads
+ * can get into the folder far enough to see that we're
+ * in IDLE and abort the IDLE.
+ *
+ * Now we read responses from the IDLE command, especially
+ * including unsolicited notifications from the server.
+ * We don't hold the messageCacheLock while reading because
+ * it protects the idleState and other threads need to be
+ * able to examine the state.
+ *
+ * The messageCacheLock is held in handleIdle while processing
+ * the responses so that we can update the number of messages
+ * in the folder (for example).
+ */
+ for (;;) {
+ if (!handleIdle(once))
+ break;
+ }
+
+ /*
+ * Enforce a minimum delay to give time to threads
+ * processing the responses that came in while we
+ * were idle.
+ */
+ int minidle = ((IMAPStore)store).getMinIdleTime();
+ if (minidle > 0) {
+ try {
+ Thread.sleep(minidle);
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers might depend on
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Start the IDLE command and put this folder into the IDLE state.
+ * IDLE processing is done later in handleIdle(), e.g., called from
+ * the IdleManager.
+ *
+ * @return true if IDLE started, false otherwise
+ * @exception MessagingException if the server doesn't support the
+ * IDLE extension
+ * @exception IllegalStateException if the folder isn't open
+ * @since JavaMail 1.5.2
+ */
+ boolean startIdle(final IdleManager im) throws MessagingException {
+ // ASSERT: Must NOT be called with this folder's
+ // synchronization lock held.
+ assert !Thread.holdsLock(this);
+ synchronized(this) {
+ checkOpened();
+ if (im != null && idleManager != null && im != idleManager)
+ throw new MessagingException(
+ "Folder already being watched by another IdleManager");
+ Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ // if the IdleManager is already watching this folder,
+ // there's nothing to do here
+ if (idleState == IDLE &&
+ im != null && im == idleManager)
+ return Boolean.TRUE; // already watching it
+ if (idleState == RUNNING) {
+ p.idleStart();
+ logger.finest("startIdle: set to IDLE");
+ idleState = IDLE;
+ idleManager = im;
+ return Boolean.TRUE;
+ } else {
+ // some other thread must be running the IDLE
+ // command, we'll just wait for it to finish
+ // without aborting it ourselves
+ try {
+ // give up lock and wait to be not idle
+ messageCacheLock.wait();
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers
+ // might depend on
+ Thread.currentThread().interrupt();
+ }
+ return Boolean.FALSE;
+ }
+ }
+ });
+ logger.log(Level.FINEST, "startIdle: return {0}", started);
+ return started.booleanValue();
+ }
+ }
+
+ /**
+ * Read a response from the server while we're in the IDLE state.
+ * We hold the messageCacheLock while processing the
+ * responses so that we can update the number of messages
+ * in the folder (for example).
+ *
+ * @param once only do one notification?
+ * @return true if we should look for more IDLE responses,
+ * false if IDLE is done
+ * @exception MessagingException for errors
+ * @since JavaMail 1.5.2
+ */
+ boolean handleIdle(boolean once) throws MessagingException {
+ Response r = null;
+ do {
+ r = protocol.readIdleResponse();
+ try {
+ synchronized (messageCacheLock) {
+ if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
+ /*
+ * If it was a timeout and no bytes were transferred
+ * we ignore it and go back and read again.
+ * If the I/O was otherwise interrupted, and no
+ * bytes were transferred, we take it as a request
+ * to abort the IDLE.
+ */
+ Exception ex = r.getException();
+ if (ex instanceof InterruptedIOException &&
+ ((InterruptedIOException)ex).
+ bytesTransferred == 0) {
+ if (ex instanceof SocketTimeoutException) {
+ logger.finest(
+ "handleIdle: ignoring socket timeout");
+ r = null; // repeat do/while loop
+ } else {
+ logger.finest("handleIdle: interrupting IDLE");
+ IdleManager im = idleManager;
+ if (im != null) {
+ logger.finest(
+ "handleIdle: request IdleManager to abort");
+ im.requestAbort(this);
+ } else {
+ logger.finest("handleIdle: abort IDLE");
+ protocol.idleAbort();
+ idleState = ABORTING;
+ }
+ // normally will exit the do/while loop
+ }
+ continue;
+ }
+ }
+ boolean done = true;
+ try {
+ if (protocol == null ||
+ !protocol.processIdleResponse(r))
+ return false; // done
+ done = false;
+ } finally {
+ if (done) {
+ logger.finest("handleIdle: set to RUNNING");
+ idleState = RUNNING;
+ idleManager = null;
+ messageCacheLock.notifyAll();
+ }
+ }
+ if (once) {
+ if (idleState == IDLE) {
+ try {
+ protocol.idleAbort();
+ } catch (Exception ex) {
+ // ignore any failures, still have to abort.
+ // connection failures will be detected above
+ // in the call to readIdleResponse.
+ }
+ idleState = ABORTING;
+ }
+ }
+ }
+ } catch (ConnectionException cex) {
+ // Oops, the folder died on us.
+ throw new FolderClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ // keep processing responses already in our buffer
+ } while (r == null || protocol.hasResponse());
+ return true;
+ }
+
+ /*
+ * If an IDLE command is in progress, abort it if necessary,
+ * and wait until it completes.
+ * ASSERT: Must be called with the message cache lock held.
+ */
+ void waitIfIdle() throws ProtocolException {
+ assert Thread.holdsLock(messageCacheLock);
+ while (idleState != RUNNING) {
+ if (idleState == IDLE) {
+ IdleManager im = idleManager;
+ if (im != null) {
+ logger.finest("waitIfIdle: request IdleManager to abort");
+ im.requestAbort(this);
+ } else {
+ logger.finest("waitIfIdle: abort IDLE");
+ protocol.idleAbort();
+ idleState = ABORTING;
+ }
+ } else
+ logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState);
+ try {
+ // give up lock and wait to be not idle
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("waitIfIdle: wait to be not idle: " +
+ Thread.currentThread());
+ messageCacheLock.wait();
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("waitIfIdle: wait done, idleState " +
+ idleState + ": " + Thread.currentThread());
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers might depend on
+ Thread.currentThread().interrupt();
+ // If someone is trying to interrupt us we can't keep going
+ // around the loop waiting for IDLE to complete, but we can't
+ // just return because callers expect the idleState to be
+ // RUNNING when we return. Throwing this exception seems
+ // like the best choice.
+ throw new ProtocolException("Interrupted waitIfIdle", ex);
+ }
+ }
+ }
+
+ /*
+ * Send the DONE command that aborts the IDLE; used by IdleManager.
+ */
+ void idleAbort() {
+ synchronized (messageCacheLock) {
+ if (idleState == IDLE && protocol != null) {
+ protocol.idleAbort();
+ idleState = ABORTING;
+ }
+ }
+ }
+
+ /*
+ * Send the DONE command that aborts the IDLE and wait for the response;
+ * used by IdleManager.
+ */
+ void idleAbortWait() {
+ synchronized (messageCacheLock) {
+ if (idleState == IDLE && protocol != null) {
+ protocol.idleAbort();
+ idleState = ABORTING;
+
+ // read responses until OK or connection failure
+ try {
+ for (;;) {
+ if (!handleIdle(false))
+ break;
+ }
+ } catch (Exception ex) {
+ // assume it's a connection failure; nothing more to do
+ logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
+ }
+ logger.finest("IDLE aborted");
+ }
+ }
+ }
+
+ /**
+ * Return the SocketChannel for this connection, if any, for use
+ * in IdleManager.
+ */
+ SocketChannel getChannel() {
+ return protocol != null ? protocol.getChannel() : null;
+ }
+
+ /**
+ * Send the IMAP ID command (if supported by the server) and return
+ * the result from the server. The ID command identfies the client
+ * to the server and returns information about the server to the client.
+ * See RFC 2971 .
+ * The returned Map is unmodifiable.
+ *
+ * @param clientParams a Map of keys and values identifying the client
+ * @return a Map of keys and values identifying the server
+ * @exception MessagingException if the server doesn't support the
+ * ID extension
+ * @since JavaMail 1.5.1
+ */
+ @SuppressWarnings("unchecked")
+ public Map id(final Map clientParams)
+ throws MessagingException {
+ checkOpened();
+ return (Map)doOptionalCommand("ID not supported",
+ new ProtocolCommand() {
+ @Override
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ return p.id(clientParams);
+ }
+ });
+ }
+
+ /**
+ * Use the IMAP STATUS command to get the indicated item.
+ * The STATUS item may be a standard item such as "RECENT" or "UNSEEN",
+ * or may be a server-specific item.
+ * The folder must be closed. If the item is not found, or the
+ * folder is open, -1 is returned.
+ *
+ * @param item the STATUS item to fetch
+ * @return the value of the STATUS item, or -1
+ * @exception MessagingException for errors
+ * @since JavaMail 1.5.2
+ */
+ public synchronized long getStatusItem(String item)
+ throws MessagingException {
+ if (!opened) {
+ checkExists();
+
+ IMAPProtocol p = null;
+ Status status = null;
+ try {
+ p = getStoreProtocol(); // XXX
+ String[] items = { item };
+ status = p.status(fullName, items);
+ return status != null ? status.getItem(item) : -1;
+ } catch (BadCommandException bex) {
+ // doesn't support STATUS, probably vanilla IMAP4 ..
+ // Could EXAMINE, SEARCH for UNREAD messages and
+ // return the count .. bah, not worth it.
+ return -1;
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(store, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The response handler. This is the callback routine that is
+ * invoked by the protocol layer.
+ */
+ /*
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock.
+ * ASSERT: This method must *not* invoke any other method that
+ * might grab the 'folder' lock or 'message' lock (i.e., any
+ * synchronized methods on IMAPFolder or IMAPMessage)
+ * since that will result in violating the locking hierarchy.
+ */
+ @Override
+ public void handleResponse(Response r) {
+ assert Thread.holdsLock(messageCacheLock);
+
+ /*
+ * First, delegate possible ALERT or notification to the Store.
+ */
+ if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+ ((IMAPStore)store).handleResponseCode(r);
+
+ /*
+ * Now check whether this is a BYE or OK response and
+ * handle appropriately.
+ */
+ if (r.isBYE()) {
+ if (opened) // XXX - accessed without holding folder lock
+ cleanup(false);
+ return;
+ } else if (r.isOK()) {
+ // HIGHESTMODSEQ can be updated on any OK response
+ r.skipSpaces();
+ if (r.readByte() == '[') {
+ String s = r.readAtom();
+ if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
+ highestmodseq = r.readLong();
+ }
+ r.reset();
+ return;
+ } else if (!r.isUnTagged()) {
+ return; // might be a continuation for IDLE
+ }
+
+ /* Now check whether this is an IMAP specific response */
+ if (!(r instanceof IMAPResponse)) {
+ // Probably a bug in our code !
+ // XXX - should be an assert
+ logger.fine("UNEXPECTED RESPONSE : " + r.toString());
+ return;
+ }
+
+ IMAPResponse ir = (IMAPResponse)r;
+
+ if (ir.keyEquals("EXISTS")) { // EXISTS
+ int exists = ir.getNumber();
+ if (exists <= realTotal)
+ // Could be the EXISTS following EXPUNGE, ignore 'em
+ return;
+
+ int count = exists - realTotal; // number of new messages
+ Message[] msgs = new Message[count];
+
+ // Add 'count' new IMAPMessage objects into the messageCache
+ messageCache.addMessages(count, realTotal + 1);
+ int oldtotal = total; // used in loop below
+ realTotal += count;
+ total += count;
+
+ // avoid instantiating Message objects if no listeners.
+ if (hasMessageCountListener) {
+ for (int i = 0; i < count; i++)
+ msgs[i] = messageCache.getMessage(++oldtotal);
+
+ // Notify listeners.
+ notifyMessageAddedListeners(msgs);
+ }
+
+ } else if (ir.keyEquals("EXPUNGE")) {
+ // EXPUNGE response.
+
+ int seqnum = ir.getNumber();
+ if (seqnum > realTotal) {
+ // A message was expunged that we never knew about.
+ // Exchange will do this. Just ignore the notification.
+ // (Alternatively, we could simulate an EXISTS for the
+ // expunged message before expunging it.)
+ return;
+ }
+ Message[] msgs = null;
+ if (doExpungeNotification && hasMessageCountListener) {
+ // save the Message object first; can't look it
+ // up after it's expunged
+ msgs = new Message[] { getMessageBySeqNumber(seqnum) };
+ if (msgs[0] == null) // XXX - should never happen
+ msgs = null;
+ }
+
+ messageCache.expungeMessage(seqnum);
+
+ // decrement 'realTotal'; but leave 'total' unchanged
+ realTotal--;
+
+ if (msgs != null) // Do the notification here.
+ notifyMessageRemovedListeners(false, msgs);
+
+ } else if (ir.keyEquals("VANISHED")) {
+ // after the folder is opened with QRESYNC, a VANISHED response
+ // without the (EARLIER) tag is used instead of the EXPUNGE
+ // response
+
+ // "VANISHED" SP ["(EARLIER)"] SP known-uids
+ String[] s = ir.readAtomStringList();
+ if (s == null) { // no (EARLIER)
+ String uids = ir.readAtom();
+ UIDSet[] uidset = UIDSet.parseUIDSets(uids);
+ // assume no duplicates and no UIDs out of range
+ realTotal -= UIDSet.size(uidset);
+ long[] luid = UIDSet.toArray(uidset);
+ Message[] msgs = createMessagesForUIDs(luid);
+ for (Message m : msgs) {
+ if (m.getMessageNumber() > 0)
+ messageCache.expungeMessage(m.getMessageNumber());
+ }
+ if (doExpungeNotification && hasMessageCountListener) {
+ notifyMessageRemovedListeners(true, msgs);
+ }
+ } // else if (EARLIER), ignore
+
+ } else if (ir.keyEquals("FETCH")) {
+ assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
+ Message msg = processFetchResponse((FetchResponse)ir);
+ if (msg != null)
+ notifyMessageChangedListeners(
+ MessageChangedEvent.FLAGS_CHANGED, msg);
+
+ } else if (ir.keyEquals("RECENT")) {
+ // update 'recent'
+ recent = ir.getNumber();
+ }
+ }
+
+ /**
+ * Process a FETCH response.
+ * The only unsolicited FETCH response that makes sense
+ * to me (for now) is FLAGS updates, which might include
+ * UID and MODSEQ information. Ignore any other junk.
+ */
+ private Message processFetchResponse(FetchResponse fr) {
+ IMAPMessage msg = getMessageBySeqNumber(fr.getNumber());
+ if (msg != null) { // should always be true
+ boolean notify = false;
+
+ UID uid = fr.getItem(UID.class);
+ if (uid != null && msg.getUID() != uid.uid) {
+ msg.setUID(uid.uid);
+ if (uidTable == null)
+ uidTable = new Hashtable<>();
+ uidTable.put(Long.valueOf(uid.uid), msg);
+ notify = true;
+ }
+
+ MODSEQ modseq = fr.getItem(MODSEQ.class);
+ if (modseq != null && msg._getModSeq() != modseq.modseq) {
+ msg.setModSeq(modseq.modseq);
+ /*
+ * XXX - should we update the folder's HIGHESTMODSEQ or not?
+ *
+ if (modseq.modseq > highestmodseq)
+ highestmodseq = modseq.modseq;
+ */
+ notify = true;
+ }
+
+ // Get FLAGS response, if present
+ FLAGS flags = fr.getItem(FLAGS.class);
+ if (flags != null) {
+ msg._setFlags(flags); // assume flags changed
+ notify = true;
+ }
+
+ // handle any extension items that might've changed
+ // XXX - no notifications associated with extension items
+ msg.handleExtensionFetchItems(fr.getExtensionItems());
+
+ if (!notify)
+ msg = null;
+ }
+ return msg;
+ }
+
+ /**
+ * Handle the given array of Responses.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock
+ */
+ void handleResponses(Response[] r) {
+ for (int i = 0; i < r.length; i++) {
+ if (r[i] != null)
+ handleResponse(r[i]);
+ }
+ }
+
+ /**
+ * Get this folder's Store's protocol connection.
+ *
+ * When acquiring a store protocol object, it is important to
+ * use the following steps:
+ *
+ *
+ * IMAPProtocol p = null;
+ * try {
+ * p = getStoreProtocol();
+ * // perform the command
+ * } catch (WhateverException ex) {
+ * // handle it
+ * } finally {
+ * releaseStoreProtocol(p);
+ * }
+ *
+ *
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ *
+ * @return the IMAPProtocol for the Store's connection
+ * @exception ProtocolException for protocol errors
+ */
+ protected synchronized IMAPProtocol getStoreProtocol()
+ throws ProtocolException {
+ connectionPoolLogger.fine("getStoreProtocol() borrowing a connection");
+ return ((IMAPStore)store).getFolderStoreProtocol();
+ }
+
+ /**
+ * Throw the appropriate 'closed' exception.
+ *
+ * @param cex the ConnectionException
+ * @exception FolderClosedException if the folder is closed
+ * @exception StoreClosedException if the store is closed
+ */
+ protected synchronized void throwClosedException(ConnectionException cex)
+ throws FolderClosedException, StoreClosedException {
+ // If it's the folder's protocol object, throw a FolderClosedException;
+ // otherwise, throw a StoreClosedException.
+ // If a command has failed because the connection is closed,
+ // the folder will have already been forced closed by the
+ // time we get here and our protocol object will have been
+ // released, so if we no longer have a protocol object we base
+ // this decision on whether we *think* the folder is open.
+ if ((protocol != null && cex.getProtocol() == protocol) ||
+ (protocol == null && !reallyClosed))
+ throw new FolderClosedException(this, cex.getMessage());
+ else
+ throw new StoreClosedException(store, cex.getMessage());
+ }
+
+ /**
+ * Return the IMAPProtocol object for this folder.
+ *
+ * This method will block if necessary to wait for an IDLE
+ * command to finish.
+ *
+ * @return the IMAPProtocol object used when the folder is open
+ * @exception ProtocolException for protocol errors
+ */
+ protected IMAPProtocol getProtocol() throws ProtocolException {
+ assert Thread.holdsLock(messageCacheLock);
+ waitIfIdle();
+ // if we no longer have a protocol object after waiting, it probably
+ // means the connection has been closed due to a communnication error,
+ // or possibly because the folder has been closed
+ if (protocol == null)
+ throw new ConnectionException("Connection closed");
+ return protocol;
+ }
+
+ /**
+ * A simple interface for user-defined IMAP protocol commands.
+ */
+ public static interface ProtocolCommand {
+ /**
+ * Execute the user-defined command using the supplied IMAPProtocol
+ * object.
+ *
+ * @param protocol the IMAPProtocol for the connection
+ * @return the results of the command
+ * @exception ProtocolException for protocol errors
+ */
+ public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
+ }
+
+ /**
+ * Execute a user-supplied IMAP command. The command is executed
+ * in the appropriate context with the necessary locks held and
+ * using the appropriate IMAPProtocol
object.
+ *
+ * This method returns whatever the ProtocolCommand
+ * object's doCommand
method returns. If the
+ * doCommand
method throws a ConnectionException
+ * it is translated into a StoreClosedException
or
+ * FolderClosedException
as appropriate. If the
+ * doCommand
method throws a ProtocolException
+ * it is translated into a MessagingException
.
+ *
+ * The following example shows how to execute the IMAP NOOP command.
+ * Executing more complex IMAP commands requires intimate knowledge
+ * of the com.sun.mail.iap
and
+ * com.sun.mail.imap.protocol
packages, best acquired by
+ * reading the source code.
+ *
+ *
+ * import com.sun.mail.iap.*;
+ * import com.sun.mail.imap.*;
+ * import com.sun.mail.imap.protocol.*;
+ *
+ * ...
+ *
+ * IMAPFolder f = (IMAPFolder)folder;
+ * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
+ * public Object doCommand(IMAPProtocol p)
+ * throws ProtocolException {
+ * p.simpleCommand("NOOP", null);
+ * return null;
+ * }
+ * });
+ *
+ *
+ *
+ * Here's a more complex example showing how to use the proposed
+ * IMAP SORT extension:
+ *
+ *
+ * import com.sun.mail.iap.*;
+ * import com.sun.mail.imap.*;
+ * import com.sun.mail.imap.protocol.*;
+ *
+ * ...
+ *
+ * IMAPFolder f = (IMAPFolder)folder;
+ * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
+ * public Object doCommand(IMAPProtocol p)
+ * throws ProtocolException {
+ * // Issue command
+ * Argument args = new Argument();
+ * Argument list = new Argument();
+ * list.writeString("SUBJECT");
+ * args.writeArgument(list);
+ * args.writeString("UTF-8");
+ * args.writeString("ALL");
+ * Response[] r = p.command("SORT", args);
+ * Response response = r[r.length-1];
+ *
+ * // Grab response
+ * Vector v = new Vector();
+ * if (response.isOK()) { // command succesful
+ * for (int i = 0, len = r.length; i < len; i++) {
+ * if (!(r[i] instanceof IMAPResponse))
+ * continue;
+ *
+ * IMAPResponse ir = (IMAPResponse)r[i];
+ * if (ir.keyEquals("SORT")) {
+ * String num;
+ * while ((num = ir.readAtomString()) != null)
+ * System.out.println(num);
+ * r[i] = null;
+ * }
+ * }
+ * }
+ *
+ * // dispatch remaining untagged responses
+ * p.notifyResponseHandlers(r);
+ * p.handleResult(response);
+ *
+ * return null;
+ * }
+ * });
+ *
+ *
+ * @param cmd the protocol command
+ * @return the result of the command
+ * @exception MessagingException for failures
+ */
+ public Object doCommand(ProtocolCommand cmd) throws MessagingException {
+ try {
+ return doProtocolCommand(cmd);
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return null;
+ }
+
+ public Object doOptionalCommand(String err, ProtocolCommand cmd)
+ throws MessagingException {
+ try {
+ return doProtocolCommand(cmd);
+ } catch (BadCommandException bex) {
+ throw new MessagingException(err, bex);
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return null;
+ }
+
+ public Object doCommandIgnoreFailure(ProtocolCommand cmd)
+ throws MessagingException {
+ try {
+ return doProtocolCommand(cmd);
+ } catch (CommandFailedException cfx) {
+ return null;
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return null;
+ }
+
+ protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
+ throws ProtocolException {
+ /*
+ * Check whether we have a protocol object, not whether we're
+ * opened, to allow use of the exsting protocol object in the
+ * open method before the state is changed to "opened".
+ */
+ if (protocol != null) {
+ synchronized (messageCacheLock) {
+ return cmd.doCommand(getProtocol());
+ }
+ }
+
+ // only get here if using store's connection
+ IMAPProtocol p = null;
+
+ try {
+ p = getStoreProtocol();
+ return cmd.doCommand(p);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+
+ /**
+ * Release the store protocol object. If we borrowed a protocol
+ * object from the connection pool, give it back. If we used our
+ * own protocol object, nothing to do.
+ *
+ * ASSERT: Must be called with this folder's synchronization lock held.
+ *
+ * @param p the IMAPProtocol object
+ */
+ protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
+ if (p != protocol)
+ ((IMAPStore)store).releaseFolderStoreProtocol(p);
+ else {
+ // XXX - should never happen
+ logger.fine("releasing our protocol as store protocol?");
+ }
+ }
+
+ /**
+ * Release the protocol object.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock
+ *
+ * @param returnToPool return the protocol object to the pool?
+ */
+ protected void releaseProtocol(boolean returnToPool) {
+ if (protocol != null) {
+ protocol.removeResponseHandler(this);
+
+ if (returnToPool)
+ ((IMAPStore)store).releaseProtocol(this, protocol);
+ else {
+ protocol.disconnect(); // make sure it's disconnected
+ ((IMAPStore)store).releaseProtocol(this, null);
+ }
+ protocol = null;
+ }
+ }
+
+ /**
+ * Issue a noop command for the connection if the connection has not been
+ * used in more than a second. If keepStoreAlive
is true,
+ * also issue a noop over the store's connection.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock
+ *
+ * @param keepStoreAlive keep the Store alive too?
+ * @exception ProtocolException for protocol errors
+ */
+ protected void keepConnectionAlive(boolean keepStoreAlive)
+ throws ProtocolException {
+
+ assert Thread.holdsLock(messageCacheLock);
+ if (protocol == null) // in case connection was closed
+ return;
+ if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
+ waitIfIdle();
+ if (protocol != null)
+ protocol.noop();
+ }
+
+ if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
+ IMAPProtocol p = null;
+ try {
+ p = ((IMAPStore)store).getFolderStoreProtocol();
+ if (System.currentTimeMillis() - p.getTimestamp() > 1000)
+ p.noop();
+ } finally {
+ ((IMAPStore)store).releaseFolderStoreProtocol(p);
+ }
+ }
+ }
+
+ /**
+ * Get the message object for the given sequence number. If
+ * none found, null is returned.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock
+ *
+ * @param seqnum the message sequence number
+ * @return the IMAPMessage object
+ */
+ protected IMAPMessage getMessageBySeqNumber(int seqnum) {
+ if (seqnum > messageCache.size()) {
+ // Microsoft Exchange will sometimes return message
+ // numbers that it has not yet notified the client
+ // about via EXISTS; ignore those messages here.
+ // GoDaddy IMAP does this too.
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("ignoring message number " +
+ seqnum + " outside range " + messageCache.size());
+ return null;
+ }
+ return messageCache.getMessageBySeqnum(seqnum);
+ }
+
+ /**
+ * Get the message objects for the given sequence numbers.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock
+ *
+ * @param seqnums the array of message sequence numbers
+ * @return the IMAPMessage objects
+ * @since JavaMail 1.5.3
+ */
+ protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) {
+ IMAPMessage[] msgs = new IMAPMessage[seqnums.length];
+ int nulls = 0;
+ // Map seq-numbers into actual Messages.
+ for (int i = 0; i < seqnums.length; i++) {
+ msgs[i] = getMessageBySeqNumber(seqnums[i]);
+ if (msgs[i] == null)
+ nulls++;
+ }
+ if (nulls > 0) { // compress the array to remove the nulls
+ IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls];
+ for (int i = 0, j = 0; i < msgs.length; i++) {
+ if (msgs[i] != null)
+ nmsgs[j++] = msgs[i];
+ }
+ msgs = nmsgs;
+ }
+ return msgs;
+ }
+
+ private boolean isDirectory() {
+ return ((type & HOLDS_FOLDERS) != 0);
+ }
+}
+
+/**
+ * An object that holds a Message object
+ * and reports its size and writes it to another OutputStream
+ * on demand. Used by appendMessages to avoid the need to
+ * buffer the entire message in memory in a single byte array
+ * before sending it to the server.
+ */
+class MessageLiteral implements Literal {
+ private Message msg;
+ private int msgSize = -1;
+ private byte[] buf; // the buffered message, if not null
+
+ public MessageLiteral(Message msg, int maxsize)
+ throws MessagingException, IOException {
+ this.msg = msg;
+ // compute the size here so exceptions can be returned immediately
+ LengthCounter lc = new LengthCounter(maxsize);
+ OutputStream os = new CRLFOutputStream(lc);
+ msg.writeTo(os);
+ os.flush();
+ msgSize = lc.getSize();
+ buf = lc.getBytes();
+ }
+
+ @Override
+ public int size() {
+ return msgSize;
+ }
+
+ @Override
+ public void writeTo(OutputStream os) throws IOException {
+ // the message should not change between the constructor and this call
+ try {
+ if (buf != null)
+ os.write(buf, 0, msgSize);
+ else {
+ os = new CRLFOutputStream(os);
+ msg.writeTo(os);
+ }
+ } catch (MessagingException mex) {
+ // exceptions here are bad, "should" never happen
+ throw new IOException("MessagingException while appending message: "
+ + mex);
+ }
+ }
+}
+
+/**
+ * Count the number of bytes written to the stream.
+ * Also, save a copy of small messages to avoid having to process
+ * the data again.
+ */
+class LengthCounter extends OutputStream {
+ private int size = 0;
+ private byte[] buf;
+ private int maxsize;
+
+ public LengthCounter(int maxsize) {
+ buf = new byte[8192];
+ this.maxsize = maxsize;
+ }
+
+ @Override
+ public void write(int b) {
+ int newsize = size + 1;
+ if (buf != null) {
+ if (newsize > maxsize && maxsize >= 0) {
+ buf = null;
+ } else if (newsize > buf.length) {
+ byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
+ System.arraycopy(buf, 0, newbuf, 0, size);
+ buf = newbuf;
+ buf[size] = (byte)b;
+ } else {
+ buf[size] = (byte)b;
+ }
+ }
+ size = newsize;
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) {
+ if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+ int newsize = size + len;
+ if (buf != null) {
+ if (newsize > maxsize && maxsize >= 0) {
+ buf = null;
+ } else if (newsize > buf.length) {
+ byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
+ System.arraycopy(buf, 0, newbuf, 0, size);
+ buf = newbuf;
+ System.arraycopy(b, off, buf, size, len);
+ } else {
+ System.arraycopy(b, off, buf, size, len);
+ }
+ }
+ size = newsize;
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public byte[] getBytes() {
+ return buf;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPInputStream.java b/app/src/main/java/com/sun/mail/imap/IMAPInputStream.java
new file mode 100644
index 0000000000..52018e288b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPInputStream.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import javax.mail.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.FolderClosedIOException;
+import com.sun.mail.util.MessageRemovedIOException;
+
+/**
+ * This class implements an IMAP data stream.
+ *
+ * @author John Mani
+ */
+
+public class IMAPInputStream extends InputStream {
+ private IMAPMessage msg; // this message
+ private String section; // section-id
+ private int pos; // track the position within the IMAP datastream
+ private int blksize; // number of bytes to read in each FETCH request
+ private int max; // the total number of bytes in this section.
+ // -1 indicates unknown
+ private byte[] buf; // the buffer obtained from fetchBODY()
+ private int bufcount; // The index one greater than the index of the
+ // last valid byte in 'buf'
+ private int bufpos; // The current position within 'buf'
+ private boolean lastBuffer; // is this the last buffer of data?
+ private boolean peek; // peek instead of fetch?
+ private ByteArray readbuf; // reuse for each read
+
+ // Allocate this much extra space in the read buffer to allow
+ // space for the FETCH response overhead
+ private static final int slop = 64;
+
+
+ /**
+ * Create an IMAPInputStream.
+ *
+ * @param msg the IMAPMessage the data will come from
+ * @param section the IMAP section/part identifier for the data
+ * @param max the number of bytes in this section
+ * @param peek peek instead of fetch?
+ */
+ public IMAPInputStream(IMAPMessage msg, String section, int max,
+ boolean peek) {
+ this.msg = msg;
+ this.section = section;
+ this.max = max;
+ this.peek = peek;
+ pos = 0;
+ blksize = msg.getFetchBlockSize();
+ }
+
+ /**
+ * Do a NOOP to force any untagged EXPUNGE responses
+ * and then check if this message is expunged.
+ */
+ private void forceCheckExpunged()
+ throws MessageRemovedIOException, FolderClosedIOException {
+ synchronized (msg.getMessageCacheLock()) {
+ try {
+ msg.getProtocol().noop();
+ } catch (ConnectionException cex) {
+ throw new FolderClosedIOException(msg.getFolder(),
+ cex.getMessage());
+ } catch (FolderClosedException fex) {
+ throw new FolderClosedIOException(fex.getFolder(),
+ fex.getMessage());
+ } catch (ProtocolException pex) {
+ // ignore it
+ }
+ }
+ if (msg.isExpunged())
+ throw new MessageRemovedIOException();
+ }
+
+ /**
+ * Fetch more data from the server. This method assumes that all
+ * data has already been read in, hence bufpos > bufcount.
+ */
+ private void fill() throws IOException {
+ /*
+ * If we've read the last buffer, there's no more to read.
+ * If we know the total number of bytes available from this
+ * section, let's check if we have consumed that many bytes.
+ */
+ if (lastBuffer || max != -1 && pos >= max) {
+ if (pos == 0)
+ checkSeen();
+ readbuf = null; // XXX - return to pool?
+ return; // the caller of fill() will return -1.
+ }
+
+ BODY b = null;
+ if (readbuf == null)
+ readbuf = new ByteArray(blksize + slop);
+
+ ByteArray ba;
+ int cnt;
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized (msg.getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = msg.getProtocol();
+
+ // Check whether this message is expunged
+ if (msg.isExpunged())
+ throw new MessageRemovedIOException(
+ "No content for expunged message");
+
+ int seqnum = msg.getSequenceNumber();
+ cnt = blksize;
+ if (max != -1 && pos + blksize > max)
+ cnt = max - pos;
+ if (peek)
+ b = p.peekBody(seqnum, section, pos, cnt, readbuf);
+ else
+ b = p.fetchBody(seqnum, section, pos, cnt, readbuf);
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new IOException(pex.getMessage());
+ } catch (FolderClosedException fex) {
+ throw new FolderClosedIOException(fex.getFolder(),
+ fex.getMessage());
+ }
+
+ if (b == null || ((ba = b.getByteArray()) == null)) {
+ forceCheckExpunged();
+ // 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);
+ }
+ }
+
+ // make sure the SEEN flag is set after reading the first chunk
+ if (pos == 0)
+ checkSeen();
+
+ // setup new values ..
+ buf = ba.getBytes();
+ bufpos = ba.getStart();
+ int n = ba.getCount(); // will be zero, if all data has been
+ // consumed from the server.
+
+ int origin = b != null ? b.getOrigin() : pos;
+ if (origin < 0) {
+ /*
+ * Some versions of Exchange will return the entire message
+ * body even though we only ask for a chunk, and the returned
+ * data won't specify an "origin". If this happens, and we
+ * get more data than we asked for, assume it's the entire
+ * message body.
+ */
+ if (pos == 0) {
+ /*
+ * If we got more or less than we asked for,
+ * this is the last buffer of data.
+ */
+ lastBuffer = n != cnt;
+ } else {
+ /*
+ * We asked for data NOT starting at the beginning,
+ * but we got back data starting at the beginning.
+ * Possibly we could extract the needed data from
+ * some part of the data we got back, but really this
+ * should never happen so we just assume something is
+ * broken and terminate the data here.
+ */
+ n = 0;
+ lastBuffer = true;
+ }
+ } else if (origin == pos) {
+ /*
+ * If we got less than we asked for,
+ * this is the last buffer of data.
+ */
+ lastBuffer = n < cnt;
+ } else {
+ /*
+ * We got back data that doesn't match the request.
+ * Just terminate the data here.
+ */
+ n = 0;
+ lastBuffer = true;
+ }
+
+ bufcount = bufpos + n;
+ pos += n;
+
+ }
+
+ /**
+ * Reads the next byte of data from this buffered input stream.
+ * If no byte is available, the value -1
is returned.
+ */
+ @Override
+ public synchronized int read() throws IOException {
+ if (bufpos >= bufcount) {
+ fill();
+ if (bufpos >= bufcount)
+ return -1; // EOF
+ }
+ return buf[bufpos++] & 0xff;
+ }
+
+ /**
+ * Reads up to len
bytes of data from this
+ * input stream into the given buffer.
+ *
+ * Returns the total number of bytes read into the buffer,
+ * or -1
if there is no more data.
+ *
+ * Note that this method mimics the "weird !" semantics of
+ * BufferedInputStream in that the number of bytes actually
+ * returned may be less that the requested value. So callers
+ * of this routine should be aware of this and must check
+ * the return value to insure that they have obtained the
+ * requisite number of bytes.
+ */
+ @Override
+ public synchronized int read(byte b[], int off, int len)
+ throws IOException {
+
+ int avail = bufcount - bufpos;
+ if (avail <= 0) {
+ fill();
+ avail = bufcount - bufpos;
+ if (avail <= 0)
+ return -1; // EOF
+ }
+ int cnt = (avail < len) ? avail : len;
+ System.arraycopy(buf, bufpos, b, off, cnt);
+ bufpos += cnt;
+ return cnt;
+ }
+
+ /**
+ * Reads up to b.length
bytes of data from this input
+ * stream into an array of bytes.
+ *
+ * Returns the total number of bytes read into the buffer, or
+ * -1
is there is no more data.
+ *
+ * Note that this method mimics the "weird !" semantics of
+ * BufferedInputStream in that the number of bytes actually
+ * returned may be less that the requested value. So callers
+ * of this routine should be aware of this and must check
+ * the return value to insure that they have obtained the
+ * requisite number of bytes.
+ */
+ @Override
+ public int read(byte b[]) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Returns the number of bytes that can be read from this input
+ * stream without blocking.
+ */
+ @Override
+ public synchronized int available() throws IOException {
+ return (bufcount - bufpos);
+ }
+
+ /**
+ * Normally the SEEN flag will have been set by now, but if not,
+ * force it to be set (as long as the folder isn't open read-only
+ * and we're not peeking).
+ * And of course, if there's no folder (e.g., a nested message)
+ * don't do anything.
+ */
+ private void checkSeen() {
+ if (peek) // if we're peeking, don't set the SEEN flag
+ return;
+ try {
+ Folder f = msg.getFolder();
+ if (f != null && f.getMode() != Folder.READ_ONLY &&
+ !msg.isSet(Flags.Flag.SEEN))
+ msg.setFlag(Flags.Flag.SEEN, true);
+ } catch (MessagingException ex) {
+ // ignore it
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPMessage.java b/app/src/main/java/com/sun/mail/imap/IMAPMessage.java
new file mode 100644
index 0000000000..453479d989
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPMessage.java
@@ -0,0 +1,1700 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import java.io.*;
+import java.util.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+import com.sun.mail.util.ReadableMime;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * This class implements an IMAPMessage object.
+ *
+ * An IMAPMessage object starts out as a light-weight object. It gets
+ * filled-in incrementally when a request is made for some item. Or
+ * when a prefetch is done using the FetchProfile.
+ *
+ * An IMAPMessage has a messageNumber and a sequenceNumber. The
+ * messageNumber is its index into its containing folder's messageCache.
+ * The sequenceNumber is its IMAP sequence-number.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+/*
+ * The lock hierarchy is that the lock on the IMAPMessage object, if
+ * it's acquired at all, must be acquired before the message cache lock.
+ * The IMAPMessage lock protects the message flags, sort of.
+ *
+ * XXX - I'm not convinced that all fields of IMAPMessage are properly
+ * protected by locks.
+ */
+
+public class IMAPMessage extends MimeMessage implements ReadableMime {
+ protected BODYSTRUCTURE bs; // BODYSTRUCTURE
+ protected ENVELOPE envelope; // ENVELOPE
+
+ /**
+ * A map of the extension FETCH items. In addition to saving the
+ * data in this map, an entry in this map indicates that we *have*
+ * the data, and so it doesn't need to be fetched again. The map
+ * is created only when needed, to avoid significantly increasing
+ * the effective size of an IMAPMessage object.
+ *
+ * @since JavaMail 1.4.6
+ */
+ protected Map items; // Map
+
+ private Date receivedDate; // INTERNALDATE
+ private long size = -1; // RFC822.SIZE
+
+ private Boolean peek; // use BODY.PEEK when fetching content?
+
+ // this message's IMAP UID
+ private volatile long uid = -1;
+
+ // this message's IMAP MODSEQ - RFC 4551 CONDSTORE
+ private volatile long modseq = -1;
+
+ // this message's IMAP sectionId (null for toplevel message,
+ // non-null for a nested message)
+ protected String sectionId;
+
+ // processed values
+ private String type; // Content-Type (with params)
+ private String subject; // decoded (Unicode) subject
+ private String description; // decoded (Unicode) desc
+
+ // Indicates that we've loaded *all* headers for this message
+ private volatile boolean headersLoaded = false;
+
+ // Indicates that we've cached the body of this message
+ private volatile boolean bodyLoaded = false;
+
+ /* Hashtable of names of headers we've loaded from the server.
+ * Used in isHeaderLoaded() and getHeaderLoaded() to keep track
+ * of those headers we've attempted to load from the server. We
+ * need this table of names to avoid multiple attempts at loading
+ * headers that don't exist for a particular message.
+ *
+ * Could this somehow be included in the InternetHeaders object ??
+ */
+ private Hashtable loadedHeaders
+ = new Hashtable<>(1);
+
+ // This is our Envelope
+ static final String EnvelopeCmd = "ENVELOPE INTERNALDATE RFC822.SIZE";
+
+ /**
+ * Constructor.
+ *
+ * @param folder the folder containing this message
+ * @param msgnum the message sequence number
+ */
+ protected IMAPMessage(IMAPFolder folder, int msgnum) {
+ super(folder, msgnum);
+ flags = null;
+ }
+
+ /**
+ * Constructor, for use by IMAPNestedMessage.
+ *
+ * @param session the Session
+ */
+ protected IMAPMessage(Session session) {
+ super(session);
+ }
+
+ /**
+ * Get this message's folder's protocol connection.
+ * Throws FolderClosedException, if the protocol connection
+ * is not available.
+ *
+ * ASSERT: Must hold the messageCacheLock.
+ *
+ * @return the IMAPProtocol object for the containing folder
+ * @exception ProtocolException for protocol errors
+ * @exception FolderClosedException if the folder is closed
+ */
+ protected IMAPProtocol getProtocol()
+ throws ProtocolException, FolderClosedException {
+ ((IMAPFolder)folder).waitIfIdle();
+ IMAPProtocol p = ((IMAPFolder)folder).protocol;
+ if (p == null)
+ throw new FolderClosedException(folder);
+ else
+ return p;
+ }
+
+ /*
+ * Is this an IMAP4 REV1 server?
+ */
+ protected boolean isREV1() throws FolderClosedException {
+ // access the folder's protocol object without waiting
+ // for IDLE to complete
+ IMAPProtocol p = ((IMAPFolder)folder).protocol;
+ if (p == null)
+ throw new FolderClosedException(folder);
+ else
+ return p.isREV1();
+ }
+
+ /**
+ * Get the messageCacheLock, associated with this Message's
+ * Folder.
+ *
+ * @return the message cache lock object
+ */
+ protected Object getMessageCacheLock() {
+ return ((IMAPFolder)folder).messageCacheLock;
+ }
+
+ /**
+ * Get this message's IMAP sequence number.
+ *
+ * ASSERT: This method must be called only when holding the
+ * messageCacheLock.
+ *
+ * @return the message sequence number
+ */
+ protected int getSequenceNumber() {
+ return ((IMAPFolder)folder).messageCache.seqnumOf(getMessageNumber());
+ }
+
+ /**
+ * Wrapper around the protected method Message.setMessageNumber() to
+ * make that method accessible to IMAPFolder.
+ */
+ @Override
+ protected void setMessageNumber(int msgnum) {
+ super.setMessageNumber(msgnum);
+ }
+
+ /**
+ * Return the UID for this message.
+ * Returns -1 if not known; use UIDFolder.getUID() in this case.
+ *
+ * @return the UID
+ * @see javax.mail.UIDFolder#getUID
+ */
+ protected long getUID() {
+ return uid;
+ }
+
+ protected void setUID(long uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * Return the modification sequence number (MODSEQ) for this message.
+ * Returns -1 if not known.
+ *
+ * @return the modification sequence number
+ * @exception MessagingException for failures
+ * @see "RFC 4551"
+ * @since JavaMail 1.5.1
+ */
+ public synchronized long getModSeq() throws MessagingException {
+ if (modseq != -1)
+ return modseq;
+
+ synchronized (getMessageCacheLock()) { // Acquire Lock
+ try {
+ IMAPProtocol p = getProtocol();
+ checkExpunged(); // insure that message is not expunged
+ MODSEQ ms = p.fetchMODSEQ(getSequenceNumber());
+
+ if (ms != null)
+ modseq = ms.modseq;
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ return modseq;
+ }
+
+ long _getModSeq() {
+ return modseq;
+ }
+
+ void setModSeq(long modseq) {
+ this.modseq = modseq;
+ }
+
+ // expose to MessageCache
+ @Override
+ protected void setExpunged(boolean set) {
+ super.setExpunged(set);
+ }
+
+ // Convenience routine
+ protected void checkExpunged() throws MessageRemovedException {
+ if (expunged)
+ throw new MessageRemovedException();
+ }
+
+ /**
+ * Do a NOOP to force any untagged EXPUNGE responses
+ * and then check if this message is expunged.
+ *
+ * @exception MessageRemovedException if the message has been removed
+ * @exception FolderClosedException if the folder has been closed
+ */
+ protected void forceCheckExpunged()
+ throws MessageRemovedException, FolderClosedException {
+ synchronized (getMessageCacheLock()) {
+ try {
+ getProtocol().noop();
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ // ignore it
+ }
+ }
+ if (expunged)
+ throw new MessageRemovedException();
+ }
+
+ // Return the block size for FETCH requests
+ // MUST be overridden by IMAPNestedMessage
+ protected int getFetchBlockSize() {
+ return ((IMAPStore)folder.getStore()).getFetchBlockSize();
+ }
+
+ // Should we ignore the size in the BODYSTRUCTURE?
+ // MUST be overridden by IMAPNestedMessage
+ protected boolean ignoreBodyStructureSize() {
+ return ((IMAPStore)folder.getStore()).ignoreBodyStructureSize();
+ }
+
+ /**
+ * Get the "From" attribute.
+ */
+ @Override
+ public Address[] getFrom() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getFrom();
+ loadEnvelope();
+ InternetAddress[] a = envelope.from;
+ /*
+ * Per RFC 2822, the From header is required, and thus the IMAP
+ * spec also requires that it be present, but we know that in
+ * practice it is often missing. Some servers fill in the
+ * From field with the Sender field in this case, but at least
+ * Exchange 2007 does not. Use the same fallback strategy used
+ * by MimeMessage.
+ */
+ if (a == null || a.length == 0)
+ a = envelope.sender;
+ return aaclone(a);
+ }
+
+ @Override
+ public void setFrom(Address address) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ @Override
+ public void addFrom(Address[] addresses) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the "Sender" attribute.
+ */
+ @Override
+ public Address getSender() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getSender();
+ loadEnvelope();
+ if (envelope.sender != null && envelope.sender.length > 0)
+ return (envelope.sender)[0]; // there can be only one sender
+ else
+ return null;
+ }
+
+
+ @Override
+ public void setSender(Address address) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the desired Recipient type.
+ */
+ @Override
+ public Address[] getRecipients(Message.RecipientType type)
+ throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getRecipients(type);
+ loadEnvelope();
+
+ if (type == Message.RecipientType.TO)
+ return aaclone(envelope.to);
+ else if (type == Message.RecipientType.CC)
+ return aaclone(envelope.cc);
+ else if (type == Message.RecipientType.BCC)
+ return aaclone(envelope.bcc);
+ else
+ return super.getRecipients(type);
+ }
+
+ @Override
+ public void setRecipients(Message.RecipientType type, Address[] addresses)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ @Override
+ public void addRecipients(Message.RecipientType type, Address[] addresses)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the ReplyTo addresses.
+ */
+ @Override
+ public Address[] getReplyTo() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getReplyTo();
+ loadEnvelope();
+ /*
+ * The IMAP spec requires that the Reply-To field never be
+ * null, but at least Exchange 2007 fails to fill it in in
+ * some cases. Use the same fallback strategy used by
+ * MimeMessage.
+ */
+ if (envelope.replyTo == null || envelope.replyTo.length == 0)
+ return getFrom();
+ return aaclone(envelope.replyTo);
+ }
+
+ @Override
+ public void setReplyTo(Address[] addresses) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the decoded subject.
+ */
+ @Override
+ public String getSubject() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getSubject();
+
+ if (subject != null) // already cached ?
+ return subject;
+
+ loadEnvelope();
+ if (envelope.subject == null) // no subject
+ return null;
+
+ // Cache and return the decoded value.
+ try {
+ // The server *should* unfold the value, but just in case it
+ // doesn't we unfold it here.
+ subject =
+ MimeUtility.decodeText(MimeUtility.unfold(envelope.subject));
+ } catch (UnsupportedEncodingException ex) {
+ subject = envelope.subject;
+ }
+
+ return subject;
+ }
+
+ @Override
+ public void setSubject(String subject, String charset)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the SentDate.
+ */
+ @Override
+ public Date getSentDate() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getSentDate();
+ loadEnvelope();
+ if (envelope.date == null)
+ return null;
+ else
+ return new Date(envelope.date.getTime());
+ }
+
+ @Override
+ public void setSentDate(Date d) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the received date (INTERNALDATE).
+ */
+ @Override
+ public Date getReceivedDate() throws MessagingException {
+ checkExpunged();
+ if (receivedDate == null)
+ loadEnvelope(); // have to go to the server for this
+ if (receivedDate == null)
+ return null;
+ else
+ return new Date(receivedDate.getTime());
+ }
+
+ /**
+ * Get the message size.
+ *
+ * Note that this returns RFC822.SIZE. That is, it's the
+ * size of the whole message, header and body included.
+ * Note also that if the size of the message is greater than
+ * Integer.MAX_VALUE (2GB), this method returns Integer.MAX_VALUE.
+ */
+ @Override
+ public int getSize() throws MessagingException {
+ checkExpunged();
+ // if bodyLoaded, size is already set
+ if (size == -1)
+ loadEnvelope(); // XXX - could just fetch the size
+ if (size > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE; // the best we can do...
+ else
+ return (int)size;
+ }
+
+ /**
+ * Get the message size as a long.
+ *
+ * Suitable for messages that might be larger than 2GB.
+ * @return the message size as a long integer
+ * @exception MessagingException for failures
+ * @since JavaMail 1.6
+ */
+ public long getSizeLong() throws MessagingException {
+ checkExpunged();
+ // if bodyLoaded, size is already set
+ if (size == -1)
+ loadEnvelope(); // XXX - could just fetch the size
+ return size;
+ }
+
+ /**
+ * Get the total number of lines.
+ *
+ * Returns the "body_fld_lines" field from the
+ * BODYSTRUCTURE. Note that this field is available
+ * only for text/plain and message/rfc822 types
+ */
+ @Override
+ public int getLineCount() throws MessagingException {
+ checkExpunged();
+ // XXX - superclass doesn't implement this
+ loadBODYSTRUCTURE();
+ return bs.lines;
+ }
+
+ /**
+ * Get the content language.
+ */
+ @Override
+ public String[] getContentLanguage() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getContentLanguage();
+ loadBODYSTRUCTURE();
+ if (bs.language != null)
+ return bs.language.clone();
+ else
+ return null;
+ }
+
+ @Override
+ public void setContentLanguage(String[] languages)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the In-Reply-To header.
+ *
+ * @return the In-Reply-To header
+ * @exception MessagingException for failures
+ * @since JavaMail 1.3.3
+ */
+ public String getInReplyTo() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getHeader("In-Reply-To", " ");
+ loadEnvelope();
+ return envelope.inReplyTo;
+ }
+
+ /**
+ * Get the Content-Type.
+ *
+ * Generate this header from the BODYSTRUCTURE. Append parameters
+ * as well.
+ */
+ @Override
+ public synchronized String getContentType() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getContentType();
+
+ // If we haven't cached the type yet ..
+ if (type == null) {
+ loadBODYSTRUCTURE();
+ // generate content-type from BODYSTRUCTURE
+ ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
+ type = ct.toString();
+ }
+ return type;
+ }
+
+ /**
+ * Get the Content-Disposition.
+ */
+ @Override
+ public String getDisposition() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getDisposition();
+ loadBODYSTRUCTURE();
+ return bs.disposition;
+ }
+
+ @Override
+ public void setDisposition(String disposition) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the Content-Transfer-Encoding.
+ */
+ @Override
+ public String getEncoding() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getEncoding();
+ loadBODYSTRUCTURE();
+ return bs.encoding;
+ }
+
+ /**
+ * Get the Content-ID.
+ */
+ @Override
+ public String getContentID() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getContentID();
+ loadBODYSTRUCTURE();
+ return bs.id;
+ }
+
+ @Override
+ public void setContentID(String cid) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the Content-MD5.
+ */
+ @Override
+ public String getContentMD5() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getContentMD5();
+ loadBODYSTRUCTURE();
+ return bs.md5;
+ }
+
+ @Override
+ public void setContentMD5(String md5) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the decoded Content-Description.
+ */
+ @Override
+ public String getDescription() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getDescription();
+
+ if (description != null) // cached value ?
+ return description;
+
+ loadBODYSTRUCTURE();
+ if (bs.description == null)
+ return null;
+
+ try {
+ description = MimeUtility.decodeText(bs.description);
+ } catch (UnsupportedEncodingException ex) {
+ description = bs.description;
+ }
+
+ return description;
+ }
+
+ @Override
+ public void setDescription(String description, String charset)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get the Message-ID.
+ */
+ @Override
+ public String getMessageID() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getMessageID();
+ loadEnvelope();
+ return envelope.messageId;
+ }
+
+ /**
+ * Get the "filename" Disposition parameter. (Only available in
+ * IMAP4rev1). If thats not available, get the "name" ContentType
+ * parameter.
+ */
+ @Override
+ public String getFileName() throws MessagingException {
+ checkExpunged();
+ if (bodyLoaded)
+ return super.getFileName();
+
+ String filename = null;
+ loadBODYSTRUCTURE();
+
+ if (bs.dParams != null)
+ filename = bs.dParams.get("filename");
+ if (filename == null && bs.cParams != null)
+ filename = bs.cParams.get("name");
+ return filename;
+ }
+
+ @Override
+ public void setFileName(String filename) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get all the bytes for this message. Overrides getContentStream()
+ * in MimeMessage. This method is ultimately used by the DataHandler
+ * to obtain the input stream for this message.
+ *
+ * @see javax.mail.internet.MimeMessage#getContentStream
+ */
+ @Override
+ protected InputStream getContentStream() throws MessagingException {
+ if (bodyLoaded)
+ return super.getContentStream();
+ InputStream is = null;
+ boolean pk = getPeek(); // get before acquiring message cache lock
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ // This message could be expunged when we were waiting
+ // to acquire the lock ...
+ checkExpunged();
+
+ if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
+ return new IMAPInputStream(this, toSection("TEXT"),
+ bs != null && !ignoreBodyStructureSize() ?
+ bs.size : -1, pk);
+
+ if (p.isREV1()) {
+ BODY b;
+ if (pk)
+ b = p.peekBody(getSequenceNumber(), toSection("TEXT"));
+ else
+ b = p.fetchBody(getSequenceNumber(), toSection("TEXT"));
+ if (b != null)
+ is = b.getByteArrayInputStream();
+ } else {
+ RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "TEXT");
+ if (rd != null)
+ is = rd.getByteArrayInputStream();
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Get the DataHandler object for this message.
+ */
+ @Override
+ public synchronized DataHandler getDataHandler()
+ throws MessagingException {
+ checkExpunged();
+
+ if (dh == null && !bodyLoaded) {
+ loadBODYSTRUCTURE();
+ if (type == null) { // type not yet computed
+ // generate content-type from BODYSTRUCTURE
+ ContentType ct = new ContentType(bs.type, bs.subtype,
+ bs.cParams);
+ type = ct.toString();
+ }
+
+ /* Special-case Multipart and Nested content. All other
+ * cases are handled by the superclass.
+ */
+ if (bs.isMulti())
+ dh = new DataHandler(
+ new IMAPMultipartDataSource(this, bs.bodies,
+ sectionId, this)
+ );
+ else if (bs.isNested() && isREV1() && bs.envelope != null)
+ /* Nested messages are handled specially only for
+ * IMAP4rev1. IMAP4 doesn't provide enough support to
+ * FETCH the components of nested messages
+ */
+ dh = new DataHandler(
+ new IMAPNestedMessage(this,
+ bs.bodies[0],
+ bs.envelope,
+ sectionId == null ? "1" : sectionId + ".1"),
+ type
+ );
+ }
+
+ return super.getDataHandler();
+ }
+
+ @Override
+ public void setDataHandler(DataHandler content)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Return the MIME format stream corresponding to this message.
+ *
+ * @return the MIME format stream
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public InputStream getMimeStream() throws MessagingException {
+ // XXX - need an "if (bodyLoaded)" version
+ InputStream is = null;
+ boolean pk = getPeek(); // get before acquiring message cache lock
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ checkExpunged(); // insure this message is not expunged
+
+ if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
+ return new IMAPInputStream(this, sectionId, -1, pk);
+
+ if (p.isREV1()) {
+ BODY b;
+ if (pk)
+ b = p.peekBody(getSequenceNumber(), sectionId);
+ else
+ b = p.fetchBody(getSequenceNumber(), sectionId);
+ if (b != null)
+ is = b.getByteArrayInputStream();
+ } else {
+ RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), null);
+ if (rd != null)
+ is = rd.getByteArrayInputStream();
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Write out the bytes into the given OutputStream.
+ */
+ @Override
+ public void writeTo(OutputStream os)
+ throws IOException, MessagingException {
+ if (bodyLoaded) {
+ super.writeTo(os);
+ return;
+ }
+ InputStream is = getMimeStream();
+ try {
+ // write out the bytes
+ byte[] bytes = new byte[16*1024];
+ int count;
+ while ((count = is.read(bytes)) != -1)
+ os.write(bytes, 0, count);
+ } finally {
+ is.close();
+ }
+ }
+
+ /**
+ * Get the named header.
+ */
+ @Override
+ public String[] getHeader(String name) throws MessagingException {
+ checkExpunged();
+
+ if (isHeaderLoaded(name)) // already loaded ?
+ return headers.getHeader(name);
+
+ // Load this particular header
+ InputStream is = null;
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ // This message could be expunged when we were waiting
+ // to acquire the lock ...
+ checkExpunged();
+
+ if (p.isREV1()) {
+ BODY b = p.peekBody(getSequenceNumber(),
+ toSection("HEADER.FIELDS (" + name + ")")
+ );
+ if (b != null)
+ is = b.getByteArrayInputStream();
+ } else {
+ RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
+ "HEADER.LINES (" + name + ")");
+ if (rd != null)
+ is = rd.getByteArrayInputStream();
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+
+ // if we get this far without "is" being set, something has gone
+ // wrong; prevent a later NullPointerException and return null here
+ if (is == null)
+ return null;
+
+ if (headers == null)
+ headers = new InternetHeaders();
+ headers.load(is); // load this header into the Headers object.
+ setHeaderLoaded(name); // Mark this header as loaded
+
+ return headers.getHeader(name);
+ }
+
+ /**
+ * Get the named header.
+ */
+ @Override
+ public String getHeader(String name, String delimiter)
+ throws MessagingException {
+ checkExpunged();
+
+ // force the header to be loaded by invoking getHeader(name)
+ if (getHeader(name) == null)
+ return null;
+ return headers.getHeader(name, delimiter);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ @Override
+ public void removeHeader(String name)
+ throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get all headers.
+ */
+ @Override
+ public Enumeration getAllHeaders() throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getAllHeaders();
+ }
+
+ /**
+ * Get matching headers.
+ */
+ @Override
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getMatchingHeaders(names);
+ }
+
+ /**
+ * Get non-matching headers.
+ */
+ @Override
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getNonMatchingHeaders(names);
+ }
+
+ @Override
+ public void addHeaderLine(String line) throws MessagingException {
+ throw new IllegalWriteException("IMAPMessage is read-only");
+ }
+
+ /**
+ * Get all header-lines.
+ */
+ @Override
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getAllHeaderLines();
+ }
+
+ /**
+ * Get all matching header-lines.
+ */
+ @Override
+ public Enumeration getMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getMatchingHeaderLines(names);
+ }
+
+ /**
+ * Get all non-matching headerlines.
+ */
+ @Override
+ public Enumeration getNonMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ checkExpunged();
+ loadHeaders();
+ return super.getNonMatchingHeaderLines(names);
+ }
+
+ /**
+ * Get the Flags for this message.
+ */
+ @Override
+ public synchronized Flags getFlags() throws MessagingException {
+ checkExpunged();
+ loadFlags();
+ return super.getFlags();
+ }
+
+ /**
+ * Test if the given Flags are set in this message.
+ */
+ @Override
+ public synchronized boolean isSet(Flags.Flag flag)
+ throws MessagingException {
+ checkExpunged();
+ loadFlags();
+ return super.isSet(flag);
+ }
+
+ /**
+ * Set/Unset the given flags in this message.
+ */
+ @Override
+ public synchronized void setFlags(Flags flag, boolean set)
+ throws MessagingException {
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+ checkExpunged(); // Insure that this message is not expunged
+ p.storeFlags(getSequenceNumber(), flag, set);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ }
+ }
+
+ /**
+ * Set whether or not to use the PEEK variant of FETCH when
+ * fetching message content. This overrides the default
+ * value from the "mail.imap.peek" property.
+ *
+ * @param peek the peek flag
+ * @since JavaMail 1.3.3
+ */
+ public synchronized void setPeek(boolean peek) {
+ this.peek = Boolean.valueOf(peek);
+ }
+
+ /**
+ * Get whether or not to use the PEEK variant of FETCH when
+ * fetching message content.
+ *
+ * @return the peek flag
+ * @since JavaMail 1.3.3
+ */
+ public synchronized boolean getPeek() {
+ if (peek == null)
+ return ((IMAPStore)folder.getStore()).getPeek();
+ else
+ return peek.booleanValue();
+ }
+
+ /**
+ * Invalidate cached header and envelope information for this
+ * message. Subsequent accesses of this information will
+ * cause it to be fetched from the server.
+ *
+ * @since JavaMail 1.3.3
+ */
+ public synchronized void invalidateHeaders() {
+ headersLoaded = false;
+ loadedHeaders.clear();
+ headers = null;
+ envelope = null;
+ bs = null;
+ receivedDate = null;
+ size = -1;
+ type = null;
+ subject = null;
+ description = null;
+ flags = null;
+ content = null;
+ contentStream = null;
+ bodyLoaded = false;
+ }
+
+ /**
+ * This class implements the test to be done on each
+ * message in the folder. The test is to check whether the
+ * message has already cached all the items requested in the
+ * FetchProfile. If any item is missing, the test succeeds and
+ * breaks out.
+ */
+ public static class FetchProfileCondition implements Utility.Condition {
+ private boolean needEnvelope = false;
+ private boolean needFlags = false;
+ private boolean needBodyStructure = false;
+ private boolean needUID = false;
+ private boolean needHeaders = false;
+ private boolean needSize = false;
+ private boolean needMessage = false;
+ private boolean needRDate = false;
+ private String[] hdrs = null;
+ private Set need = new HashSet<>();
+
+ /**
+ * Create a FetchProfileCondition to determine if we need to fetch
+ * any of the information specified in the FetchProfile.
+ *
+ * @param fp the FetchProfile
+ * @param fitems the FETCH items
+ */
+ @SuppressWarnings("deprecation") // for FetchProfile.Item.SIZE
+ public FetchProfileCondition(FetchProfile fp, FetchItem[] fitems) {
+ if (fp.contains(FetchProfile.Item.ENVELOPE))
+ needEnvelope = true;
+ if (fp.contains(FetchProfile.Item.FLAGS))
+ needFlags = true;
+ if (fp.contains(FetchProfile.Item.CONTENT_INFO))
+ needBodyStructure = true;
+ if (fp.contains(FetchProfile.Item.SIZE))
+ needSize = true;
+ if (fp.contains(UIDFolder.FetchProfileItem.UID))
+ needUID = true;
+ if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS))
+ needHeaders = true;
+ if (fp.contains(IMAPFolder.FetchProfileItem.SIZE))
+ needSize = true;
+ if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE))
+ needMessage = true;
+ if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE))
+ needRDate = true;
+ hdrs = fp.getHeaderNames();
+ for (int i = 0; i < fitems.length; i++) {
+ if (fp.contains(fitems[i].getFetchProfileItem()))
+ need.add(fitems[i]);
+ }
+ }
+
+ /**
+ * Return true if we NEED to fetch the requested information
+ * for the specified message.
+ */
+ @Override
+ public boolean test(IMAPMessage m) {
+ if (needEnvelope && m._getEnvelope() == null && !m.bodyLoaded)
+ return true; // no envelope
+ if (needFlags && m._getFlags() == null)
+ return true; // no flags
+ if (needBodyStructure && m._getBodyStructure() == null &&
+ !m.bodyLoaded)
+ return true; // no BODYSTRUCTURE
+ if (needUID && m.getUID() == -1) // no UID
+ return true;
+ if (needHeaders && !m.areHeadersLoaded()) // no headers
+ return true;
+ if (needSize && m.size == -1 && !m.bodyLoaded) // no size
+ return true;
+ if (needMessage && !m.bodyLoaded) // no message body
+ return true;
+ if (needRDate && m.receivedDate == null) // no received date
+ return true;
+
+ // Is the desired header present ?
+ for (int i = 0; i < hdrs.length; i++) {
+ if (!m.isHeaderLoaded(hdrs[i]))
+ return true; // Nope, return
+ }
+ Iterator it = need.iterator();
+ while (it.hasNext()) {
+ FetchItem fitem = it.next();
+ if (m.items == null || m.items.get(fitem.getName()) == null)
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Apply the data in the FETCH item to this message.
+ *
+ * ASSERT: Must hold the messageCacheLock.
+ *
+ * @param item the fetch item
+ * @param hdrs the headers we're asking for
+ * @param allHeaders load all headers?
+ * @return did we handle this fetch item?
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.6
+ */
+ protected boolean handleFetchItem(Item item,
+ String[] hdrs, boolean allHeaders)
+ throws MessagingException {
+ // Check for the FLAGS item
+ if (item instanceof Flags)
+ flags = (Flags)item;
+ // Check for ENVELOPE items
+ else if (item instanceof ENVELOPE)
+ envelope = (ENVELOPE)item;
+ else if (item instanceof INTERNALDATE)
+ receivedDate = ((INTERNALDATE)item).getDate();
+ else if (item instanceof RFC822SIZE)
+ size = ((RFC822SIZE)item).size;
+ else if (item instanceof MODSEQ)
+ modseq = ((MODSEQ)item).modseq;
+
+ // Check for the BODYSTRUCTURE item
+ else if (item instanceof BODYSTRUCTURE)
+ bs = (BODYSTRUCTURE)item;
+ // Check for the UID item
+ else if (item instanceof UID) {
+ UID u = (UID)item;
+ uid = u.uid; // set uid
+ // add entry into uid table
+ if (((IMAPFolder)folder).uidTable == null)
+ ((IMAPFolder) folder).uidTable
+ = new Hashtable<>();
+ ((IMAPFolder)folder).uidTable.put(Long.valueOf(u.uid), this);
+ }
+
+ // Check for header items
+ else if (item instanceof RFC822DATA ||
+ item instanceof BODY) {
+ InputStream headerStream;
+ boolean isHeader;
+ if (item instanceof RFC822DATA) { // IMAP4
+ headerStream =
+ ((RFC822DATA)item).getByteArrayInputStream();
+ isHeader = ((RFC822DATA)item).isHeader();
+ } else { // IMAP4rev1
+ headerStream =
+ ((BODY)item).getByteArrayInputStream();
+ isHeader = ((BODY)item).isHeader();
+ }
+
+ if (!isHeader) {
+ // load the entire message by using the superclass
+ // MimeMessage.parse method
+ // first, save the size of the message
+ try {
+ size = headerStream.available();
+ } catch (IOException ex) {
+ // should never occur
+ }
+ parse(headerStream);
+ bodyLoaded = true;
+ setHeadersLoaded(true);
+ } else {
+ // Load the obtained headers.
+ InternetHeaders h = new InternetHeaders();
+ // Some IMAP servers (e.g., gmx.net) return NIL
+ // instead of a string just containing a CR/LF
+ // when the header list is empty.
+ if (headerStream != null)
+ h.load(headerStream);
+ if (headers == null || allHeaders)
+ headers = h;
+ else {
+ /*
+ * This is really painful. A second fetch
+ * of the same headers (which might occur because
+ * a new header was added to the set requested)
+ * will return headers we already know about.
+ * In this case, only load the headers we haven't
+ * seen before to avoid adding duplicates of
+ * headers we already have.
+ *
+ * XXX - There's a race condition here if another
+ * thread is reading headers in the same message
+ * object, because InternetHeaders is not thread
+ * safe.
+ */
+ Enumeration e = h.getAllHeaders();
+ while (e.hasMoreElements()) {
+ Header he = e.nextElement();
+ if (!isHeaderLoaded(he.getName()))
+ headers.addHeader(
+ he.getName(), he.getValue());
+ }
+ }
+
+ // if we asked for all headers, assume we got them
+ if (allHeaders)
+ setHeadersLoaded(true);
+ else {
+ // Mark all headers we asked for as 'loaded'
+ for (int k = 0; k < hdrs.length; k++)
+ setHeaderLoaded(hdrs[k]);
+ }
+ }
+ } else
+ return false; // not handled
+ return true; // something above handled it
+ }
+
+ /**
+ * Apply the data in the extension FETCH items to this message.
+ * This method adds all the items to the items map.
+ * Subclasses may override this method to call super and then
+ * also copy the data to a more convenient form.
+ *
+ * ASSERT: Must hold the messageCacheLock.
+ *
+ * @param extensionItems the Map to add fetch items to
+ * @since JavaMail 1.4.6
+ */
+ protected void handleExtensionFetchItems(
+ Map extensionItems) {
+ if (extensionItems == null || extensionItems.isEmpty())
+ return;
+ if (items == null)
+ items = new HashMap<>();
+ items.putAll(extensionItems);
+ }
+
+ /**
+ * Fetch an individual item for the current message.
+ * Note that handleExtensionFetchItems will have been called
+ * to store this item in the message before this method
+ * returns.
+ *
+ * @param fitem the FetchItem
+ * @return the data associated with the FetchItem
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.6
+ */
+ protected Object fetchItem(FetchItem fitem)
+ throws MessagingException {
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ Object robj = null;
+
+ try {
+ IMAPProtocol p = getProtocol();
+
+ checkExpunged(); // Insure that this message is not expunged
+
+ int seqnum = getSequenceNumber();
+ Response[] r = p.fetch(seqnum, fitem.getName());
+
+ for (int i = 0; i < r.length; i++) {
+ // If this response is NOT a FetchResponse or if it does
+ // not match our seqnum, skip.
+ if (r[i] == null ||
+ !(r[i] instanceof FetchResponse) ||
+ ((FetchResponse)r[i]).getNumber() != seqnum)
+ continue;
+
+ FetchResponse f = (FetchResponse)r[i];
+ handleExtensionFetchItems(f.getExtensionItems());
+ if (items != null) {
+ Object o = items.get(fitem.getName());
+ if (o != null)
+ robj = o;
+ }
+ }
+
+ // ((IMAPFolder)folder).handleResponses(r);
+ p.notifyResponseHandlers(r);
+ p.handleResult(r[r.length - 1]);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return robj;
+
+ } // Release MessageCacheLock
+ }
+
+ /**
+ * Return the data associated with the FetchItem.
+ * If the data hasn't been fetched, call the fetchItem
+ * method to fetch it. Returns null if there is no
+ * data for the FetchItem.
+ *
+ * @param fitem the FetchItem
+ * @return the data associated with the FetchItem
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.6
+ */
+ public synchronized Object getItem(FetchItem fitem)
+ throws MessagingException {
+ Object item = items == null ? null : items.get(fitem.getName());
+ if (item == null)
+ item = fetchItem(fitem);
+ return item;
+ }
+
+ /*
+ * Load the Envelope for this message.
+ */
+ private synchronized void loadEnvelope() throws MessagingException {
+ if (envelope != null) // already loaded
+ return;
+
+ Response[] r = null;
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ checkExpunged(); // Insure that this message is not expunged
+
+ int seqnum = getSequenceNumber();
+ r = p.fetch(seqnum, EnvelopeCmd);
+
+ for (int i = 0; i < r.length; i++) {
+ // If this response is NOT a FetchResponse or if it does
+ // not match our seqnum, skip.
+ if (r[i] == null ||
+ !(r[i] instanceof FetchResponse) ||
+ ((FetchResponse)r[i]).getNumber() != seqnum)
+ continue;
+
+ FetchResponse f = (FetchResponse)r[i];
+
+ // Look for the Envelope items.
+ int count = f.getItemCount();
+ for (int j = 0; j < count; j++) {
+ Item item = f.getItem(j);
+
+ if (item instanceof ENVELOPE)
+ envelope = (ENVELOPE)item;
+ else if (item instanceof INTERNALDATE)
+ receivedDate = ((INTERNALDATE)item).getDate();
+ else if (item instanceof RFC822SIZE)
+ size = ((RFC822SIZE)item).size;
+ }
+ }
+
+ // ((IMAPFolder)folder).handleResponses(r);
+ p.notifyResponseHandlers(r);
+ p.handleResult(r[r.length - 1]);
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+
+ } // Release MessageCacheLock
+
+ if (envelope == null)
+ throw new MessagingException("Failed to load IMAP envelope");
+ }
+
+ /*
+ * Load the BODYSTRUCTURE
+ */
+ private synchronized void loadBODYSTRUCTURE()
+ throws MessagingException {
+ if (bs != null) // already loaded
+ return;
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ // This message could be expunged when we were waiting
+ // to acquire the lock ...
+ checkExpunged();
+
+ bs = p.fetchBodyStructure(getSequenceNumber());
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ if (bs == null) {
+ // if the FETCH is successful, we should always get a
+ // BODYSTRUCTURE, but some servers fail to return it
+ // if the message has been expunged
+ forceCheckExpunged();
+ throw new MessagingException("Unable to load BODYSTRUCTURE");
+ }
+ }
+ }
+
+ /*
+ * Load all headers.
+ */
+ private synchronized void loadHeaders() throws MessagingException {
+ if (headersLoaded)
+ return;
+
+ InputStream is = null;
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized (getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ // This message could be expunged when we were waiting
+ // to acquire the lock ...
+ checkExpunged();
+
+ if (p.isREV1()) {
+ BODY b = p.peekBody(getSequenceNumber(),
+ toSection("HEADER"));
+ if (b != null)
+ is = b.getByteArrayInputStream();
+ } else {
+ RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
+ "HEADER");
+ if (rd != null)
+ is = rd.getByteArrayInputStream();
+ }
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ } // Release MessageCacheLock
+
+ if (is == null)
+ throw new MessagingException("Cannot load header");
+ headers = new InternetHeaders(is);
+ headersLoaded = true;
+ }
+
+ /*
+ * Load this message's Flags
+ */
+ private synchronized void loadFlags() throws MessagingException {
+ if (flags != null)
+ return;
+
+ // Acquire MessageCacheLock, to freeze seqnum.
+ synchronized(getMessageCacheLock()) {
+ try {
+ IMAPProtocol p = getProtocol();
+
+ // This message could be expunged when we were waiting
+ // to acquire the lock ...
+ checkExpunged();
+
+ flags = p.fetchFlags(getSequenceNumber());
+ // make sure flags is always set, even if server is broken
+ if (flags == null)
+ flags = new Flags();
+ } catch (ConnectionException cex) {
+ throw new FolderClosedException(folder, cex.getMessage());
+ } catch (ProtocolException pex) {
+ forceCheckExpunged();
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ } // Release MessageCacheLock
+ }
+
+ /*
+ * Are all headers loaded?
+ */
+ private boolean areHeadersLoaded() {
+ return headersLoaded;
+ }
+
+ /*
+ * Set whether all headers are loaded.
+ */
+ private void setHeadersLoaded(boolean loaded) {
+ headersLoaded = loaded;
+ }
+
+ /*
+ * Check if the given header was ever loaded from the server
+ */
+ private boolean isHeaderLoaded(String name) {
+ if (headersLoaded) // All headers for this message have been loaded
+ return true;
+
+ return loadedHeaders.containsKey(name.toUpperCase(Locale.ENGLISH));
+ }
+
+ /*
+ * Mark that the given headers have been loaded from the server.
+ */
+ private void setHeaderLoaded(String name) {
+ loadedHeaders.put(name.toUpperCase(Locale.ENGLISH), name);
+ }
+
+ /*
+ * Convert the given FETCH item identifier to the approriate
+ * section-string for this message.
+ */
+ private String toSection(String what) {
+ if (sectionId == null)
+ return what;
+ else
+ return sectionId + "." + what;
+ }
+
+ /*
+ * Clone an array of InternetAddresses.
+ */
+ private InternetAddress[] aaclone(InternetAddress[] aa) {
+ if (aa == null)
+ return null;
+ else
+ return aa.clone();
+ }
+
+ private Flags _getFlags() {
+ return flags;
+ }
+
+ private ENVELOPE _getEnvelope() {
+ return envelope;
+ }
+
+ private BODYSTRUCTURE _getBodyStructure() {
+ return bs;
+ }
+
+ /***********************************************************
+ * accessor routines to make available certain private/protected
+ * fields to other classes in this package.
+ ***********************************************************/
+
+ /*
+ * Called by IMAPFolder.
+ * Must not be synchronized.
+ */
+ void _setFlags(Flags flags) {
+ this.flags = flags;
+ }
+
+ /*
+ * Called by IMAPNestedMessage.
+ */
+ Session _getSession() {
+ return session;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java b/app/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java
new file mode 100644
index 0000000000..c55ef022c2
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.imap.protocol.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class
+ *
+ * @author John Mani
+ */
+
+public class IMAPMultipartDataSource extends MimePartDataSource
+ implements MultipartDataSource {
+ private List parts;
+
+ protected IMAPMultipartDataSource(MimePart part, BODYSTRUCTURE[] bs,
+ String sectionId, IMAPMessage msg) {
+ super(part);
+
+ parts = new ArrayList<>(bs.length);
+ for (int i = 0; i < bs.length; i++)
+ parts.add(
+ new IMAPBodyPart(bs[i],
+ sectionId == null ?
+ Integer.toString(i+1) :
+ sectionId + "." + Integer.toString(i+1),
+ msg)
+ );
+ }
+
+ @Override
+ public int getCount() {
+ return parts.size();
+ }
+
+ @Override
+ public BodyPart getBodyPart(int index) throws MessagingException {
+ return parts.get(index);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java b/app/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java
new file mode 100644
index 0000000000..12cd4e453a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import javax.mail.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * This class implements a nested IMAP message
+ *
+ * @author John Mani
+ */
+
+public class IMAPNestedMessage extends IMAPMessage {
+ private IMAPMessage msg; // the enclosure of this nested message
+
+ /**
+ * Package private constructor.
+ *
+ * Note that nested messages have no containing folder, nor
+ * a message number.
+ */
+ IMAPNestedMessage(IMAPMessage m, BODYSTRUCTURE b, ENVELOPE e, String sid) {
+ super(m._getSession());
+ msg = m;
+ bs = b;
+ envelope = e;
+ sectionId = sid;
+ setPeek(m.getPeek());
+ }
+
+ /*
+ * Get the enclosing message's Protocol object. Overrides
+ * IMAPMessage.getProtocol().
+ */
+ @Override
+ protected IMAPProtocol getProtocol()
+ throws ProtocolException, FolderClosedException {
+ return msg.getProtocol();
+ }
+
+ /*
+ * Is this an IMAP4 REV1 server?
+ */
+ @Override
+ protected boolean isREV1() throws FolderClosedException {
+ return msg.isREV1();
+ }
+
+ /*
+ * Get the enclosing message's messageCacheLock. Overrides
+ * IMAPMessage.getMessageCacheLock().
+ */
+ @Override
+ protected Object getMessageCacheLock() {
+ return msg.getMessageCacheLock();
+ }
+
+ /*
+ * Get the enclosing message's sequence number. Overrides
+ * IMAPMessage.getSequenceNumber().
+ */
+ @Override
+ protected int getSequenceNumber() {
+ return msg.getSequenceNumber();
+ }
+
+ /*
+ * Check whether the enclosing message is expunged. Overrides
+ * IMAPMessage.checkExpunged().
+ */
+ @Override
+ protected void checkExpunged() throws MessageRemovedException {
+ msg.checkExpunged();
+ }
+
+ /*
+ * Check whether the enclosing message is expunged. Overrides
+ * Message.isExpunged().
+ */
+ @Override
+ public boolean isExpunged() {
+ return msg.isExpunged();
+ }
+
+ /*
+ * Get the enclosing message's fetchBlockSize.
+ */
+ @Override
+ protected int getFetchBlockSize() {
+ return msg.getFetchBlockSize();
+ }
+
+ /*
+ * Get the enclosing message's ignoreBodyStructureSize.
+ */
+ @Override
+ protected boolean ignoreBodyStructureSize() {
+ return msg.ignoreBodyStructureSize();
+ }
+
+ /*
+ * IMAPMessage uses RFC822.SIZE. We use the "size" field from
+ * our BODYSTRUCTURE.
+ */
+ @Override
+ public int getSize() throws MessagingException {
+ return bs.size;
+ }
+
+ /*
+ * Disallow setting flags on nested messages
+ */
+ @Override
+ public synchronized void setFlags(Flags flag, boolean set)
+ throws MessagingException {
+ // Cannot set FLAGS on a nested IMAP message
+ throw new MethodNotSupportedException(
+ "Cannot set flags on this nested message");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPProvider.java b/app/src/main/java/com/sun/mail/imap/IMAPProvider.java
new file mode 100644
index 0000000000..0f35374380
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The IMAP protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class IMAPProvider extends Provider {
+ public IMAPProvider() {
+ super(Provider.Type.STORE, "imap", IMAPStore.class.getName(),
+ "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java b/app/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java
new file mode 100644
index 0000000000..354e63d490
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The IMAP SSL protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class IMAPSSLProvider extends Provider {
+ public IMAPSSLProvider() {
+ super(Provider.Type.STORE, "imaps", IMAPSSLStore.class.getName(),
+ "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPSSLStore.java b/app/src/main/java/com/sun/mail/imap/IMAPSSLStore.java
new file mode 100644
index 0000000000..488b4ec13c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPSSLStore.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.*;
+
+/**
+ * This class provides access to an IMAP message store over SSL.
+ */
+
+public class IMAPSSLStore extends IMAPStore {
+
+ /**
+ * Constructor that takes a Session object and a URLName that
+ * represents a specific IMAP server.
+ *
+ * @param session the Session
+ * @param url the URLName of this store
+ */
+ public IMAPSSLStore(Session session, URLName url) {
+ super(session, url, "imaps", true); // call super constructor
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IMAPStore.java b/app/src/main/java/com/sun/mail/imap/IMAPStore.java
new file mode 100644
index 0000000000..866f177377
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IMAPStore.java
@@ -0,0 +1,2211 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.lang.reflect.*;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.util.Locale;
+import java.util.Properties;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+import javax.mail.*;
+import javax.mail.event.*;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.MailConnectException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides access to an IMAP message store.
+ *
+ * Applications that need to make use of IMAP-specific features may cast
+ * a Store
object to an IMAPStore
object and
+ * use the methods on this class. The {@link #getQuota getQuota} and
+ * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
+ * Refer to RFC 2087
+ * for more information.
+ *
+ * The {@link #id id} method supports the IMAP ID extension;
+ * see RFC 2971 .
+ * The fields ID_NAME, ID_VERSION, etc. represent the suggested field names
+ * in RFC 2971 section 3.3 and may be used as keys in the Map containing
+ * client values or server values.
+ *
+ * See the com.sun.mail.imap package
+ * documentation for further information on the IMAP protocol provider.
+ *
+ * WARNING: The APIs unique to this class should be
+ * considered EXPERIMENTAL . They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Jim Glennon
+ */
+/*
+ * This package is implemented over the "imap.protocol" package, which
+ * implements the protocol-level commands.
+ *
+ * A connected IMAPStore maintains a pool of IMAP protocol objects for
+ * use in communicating with the IMAP server. The IMAPStore will create
+ * the initial AUTHENTICATED connection and seed the pool with this
+ * connection. As folders are opened and new IMAP protocol objects are
+ * needed, the IMAPStore will provide them from the connection pool,
+ * or create them if none are available. When a folder is closed,
+ * its IMAP protocol object is returned to the connection pool if the
+ * pool is not over capacity. The pool size can be configured by setting
+ * the mail.imap.connectionpoolsize property.
+ *
+ * Note that all connections in the connection pool have their response
+ * handler set to be the Store. When the connection is removed from the
+ * pool for use by a folder, the response handler is removed and then set
+ * to either the Folder or to the special nonStoreResponseHandler, depending
+ * on how the connection is being used. This is probably excessive.
+ * Better would be for the Protocol object to support only a single
+ * response handler, which would be set before the connection is used
+ * and cleared when the connection is in the pool and can't be used.
+ *
+ * A mechanism is provided for timing out idle connection pool IMAP
+ * protocol objects. Timed out connections are closed and removed (pruned)
+ * from the connection pool. The time out interval can be configured via
+ * the mail.imap.connectionpooltimeout property.
+ *
+ * The connected IMAPStore object may or may not maintain a separate IMAP
+ * protocol object that provides the store a dedicated connection to the
+ * IMAP server. This is provided mainly for compatibility with previous
+ * implementations of Jakarta Mail and is determined by the value of the
+ * mail.imap.separatestoreconnection property.
+ *
+ * An IMAPStore object provides closed IMAPFolder objects thru its list()
+ * and listSubscribed() methods. A closed IMAPFolder object acquires an
+ * IMAP protocol object from the store to communicate with the server. When
+ * the folder is opened, it gets its own protocol object and thus its own,
+ * separate connection to the server. The store maintains references to
+ * all 'open' folders. When a folder is/gets closed, the store removes
+ * it from its list. When the store is/gets closed, it closes all open
+ * folders in its list, thus cleaning up all open connections to the
+ * server.
+ *
+ * A mutex is used to control access to the connection pool resources.
+ * Any time any of these resources need to be accessed, the following
+ * convention should be followed:
+ *
+ * synchronized (pool) { // ACQUIRE LOCK
+ * // access connection pool resources
+ * } // RELEASE LOCK
+ *
+ * The locking relationship between the store and folders is that the
+ * store lock must be acquired before a folder lock. This is currently only
+ * applicable in the store's cleanup method. It's important that the
+ * connection pool lock is not held when calling into folder objects.
+ * The locking hierarchy is that a folder lock must be acquired before
+ * any connection pool operations are performed. You never need to hold
+ * all three locks, but if you hold more than one this is the order you
+ * have to acquire them in.
+ *
+ * That is: Store > Folder, Folder > pool, Store > pool
+ *
+ * The IMAPStore implements the ResponseHandler interface and listens to
+ * BYE or untagged OK-notification events from the server as a result of
+ * Store operations. IMAPFolder forwards notifications that result from
+ * Folder operations using the store connection; the IMAPStore ResponseHandler
+ * is not used directly in this case.
+ */
+
+public class IMAPStore extends Store
+ implements QuotaAwareStore, ResponseHandler {
+
+ /**
+ * A special event type for a StoreEvent to indicate an IMAP
+ * response, if the mail.imap.enableimapevents property is set.
+ */
+ public static final int RESPONSE = 1000;
+
+ public static final String ID_NAME = "name";
+ public static final String ID_VERSION = "version";
+ public static final String ID_OS = "os";
+ public static final String ID_OS_VERSION = "os-version";
+ public static final String ID_VENDOR = "vendor";
+ public static final String ID_SUPPORT_URL = "support-url";
+ public static final String ID_ADDRESS = "address";
+ public static final String ID_DATE = "date";
+ public static final String ID_COMMAND = "command";
+ public static final String ID_ARGUMENTS = "arguments";
+ public static final String ID_ENVIRONMENT = "environment";
+
+ protected final String name; // name of this protocol
+ protected final int defaultPort; // default IMAP port
+ protected final boolean isSSL; // use SSL?
+
+ private final int blksize; // Block size for data requested
+ // in FETCH requests. Defaults to
+ // 16K
+
+ private boolean ignoreSize; // ignore the size in BODYSTRUCTURE?
+
+ private final int statusCacheTimeout; // cache Status for 1 second
+
+ private final int appendBufferSize; // max size of msg buffered for append
+
+ private final int minIdleTime; // minimum idle time
+
+ private volatile int port = -1; // port to use
+
+ // Auth info
+ protected String host;
+ protected String user;
+ protected String password;
+ protected String proxyAuthUser;
+ protected String authorizationID;
+ protected String saslRealm;
+
+ private Namespaces namespaces;
+
+ private boolean enableStartTLS = false; // enable STARTTLS
+ private boolean requireStartTLS = false; // require STARTTLS
+ private boolean usingSSL = false; // using SSL?
+ private boolean enableSASL = false; // enable SASL authentication
+ private String[] saslMechanisms;
+ private boolean forcePasswordRefresh = false;
+ // enable notification of IMAP responses
+ private boolean enableResponseEvents = false;
+ // enable notification of IMAP responses during IDLE
+ private boolean enableImapEvents = false;
+ private String guid; // for Yahoo! Mail IMAP
+ private boolean throwSearchException = false;
+ private boolean peek = false;
+ private boolean closeFoldersOnStoreFailure = true;
+ private boolean enableCompress = false; // enable COMPRESS=DEFLATE
+ private boolean finalizeCleanClose = false;
+
+ /*
+ * This field is set in the Store's response handler if we see
+ * a BYE response. The releaseStore method checks this field
+ * and if set it cleans up the Store. Field is volatile because
+ * there's no lock we consistently hold while manipulating it.
+ *
+ * Because volatile doesn't really work before JDK 1.5,
+ * use a lock to protect these two fields.
+ */
+ private volatile boolean connectionFailed = false;
+ private volatile boolean forceClose = false;
+ private final Object connectionFailedLock = new Object();
+
+ private boolean debugusername; // include username in debug output?
+ private boolean debugpassword; // include password in debug output?
+ protected MailLogger logger; // for debug output
+
+ private boolean messageCacheDebug;
+
+ // constructors for IMAPFolder class provided by user
+ private volatile Constructor> folderConstructor = null;
+ private volatile Constructor> folderConstructorLI = null;
+
+ // Connection pool info
+
+ static class ConnectionPool {
+
+ // container for the pool's IMAP protocol objects
+ private Vector authenticatedConnections
+ = new Vector<>();
+
+ // vectore of open folders
+ private Vector folders;
+
+ // is the store connection being used?
+ private boolean storeConnectionInUse = false;
+
+ // the last time (in millis) the pool was checked for timed out
+ // connections
+ private long lastTimePruned;
+
+ // flag to indicate whether there is a dedicated connection for
+ // store commands
+ private final boolean separateStoreConnection;
+
+ // client timeout interval
+ private final long clientTimeoutInterval;
+
+ // server timeout interval
+ private final long serverTimeoutInterval;
+
+ // size of the connection pool
+ private final int poolSize;
+
+ // interval for checking for timed out connections
+ private final long pruningInterval;
+
+ // connection pool logger
+ private final MailLogger logger;
+
+ /*
+ * The idleState field supports the IDLE command.
+ * Normally when executing an IMAP command we hold the
+ * store's lock.
+ * While executing the IDLE command we can't hold the
+ * lock or it would prevent other threads from
+ * entering Store methods even far enough to check whether
+ * an IDLE command is in progress. We need to check before
+ * issuing another command so that we can abort the IDLE
+ * command.
+ *
+ * The idleState field is protected by the store's lock.
+ * The RUNNING state is the normal state and means no IDLE
+ * command is in progress. The IDLE state means we've issued
+ * an IDLE command and are reading responses. The ABORTING
+ * state means we've sent the DONE continuation command and
+ * are waiting for the thread running the IDLE command to
+ * break out of its read loop.
+ *
+ * When an IDLE command is in progress, the thread calling
+ * the idle method will be reading from the IMAP connection
+ * while not holding the store's lock.
+ * It's obviously critical that no other thread try to send a
+ * command or read from the connection while in this state.
+ * However, other threads can send the DONE continuation
+ * command that will cause the server to break out of the IDLE
+ * loop and send the ending tag response to the IDLE command.
+ * The thread in the idle method that's reading the responses
+ * from the IDLE command will see this ending response and
+ * complete the idle method, setting the idleState field back
+ * to RUNNING, and notifying any threads waiting to use the
+ * connection.
+ *
+ * All uses of the IMAP connection (IMAPProtocol object) must
+ * be preceeded by a check to make sure an IDLE command is not
+ * running, and abort the IDLE command if necessary. This check
+ * is made while holding the connection pool lock. While
+ * waiting for the IDLE command to complete, these other threads
+ * will give up the connection pool lock. This check is done by
+ * the getStoreProtocol() method.
+ */
+ private static final int RUNNING = 0; // not doing IDLE command
+ private static final int IDLE = 1; // IDLE command in effect
+ private static final int ABORTING = 2; // IDLE command aborting
+ private int idleState = RUNNING;
+ private IMAPProtocol idleProtocol; // protocol object when IDLE
+
+ ConnectionPool(String name, MailLogger plogger, Session session) {
+ lastTimePruned = System.currentTimeMillis();
+ Properties props = session.getProperties();
+
+ boolean debug = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".connectionpool.debug", false);
+ logger = plogger.getSubLogger("connectionpool",
+ "DEBUG IMAP CP", debug);
+
+ // check if the default connection pool size is overridden
+ int size = PropUtil.getIntProperty(props,
+ "mail." + name + ".connectionpoolsize", -1);
+ if (size > 0) {
+ poolSize = size;
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.connectionpoolsize: " + poolSize);
+ } else
+ poolSize = 1;
+
+ // check if the default client-side timeout value is overridden
+ int connectionPoolTimeout = PropUtil.getIntProperty(props,
+ "mail." + name + ".connectionpooltimeout", -1);
+ if (connectionPoolTimeout > 0) {
+ clientTimeoutInterval = connectionPoolTimeout;
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.connectionpooltimeout: " +
+ clientTimeoutInterval);
+ } else
+ clientTimeoutInterval = 45 * 1000; // 45 seconds
+
+ // check if the default server-side timeout value is overridden
+ int serverTimeout = PropUtil.getIntProperty(props,
+ "mail." + name + ".servertimeout", -1);
+ if (serverTimeout > 0) {
+ serverTimeoutInterval = serverTimeout;
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.servertimeout: " +
+ serverTimeoutInterval);
+ } else
+ serverTimeoutInterval = 30 * 60 * 1000; // 30 minutes
+
+ // check if the default server-side timeout value is overridden
+ int pruning = PropUtil.getIntProperty(props,
+ "mail." + name + ".pruninginterval", -1);
+ if (pruning > 0) {
+ pruningInterval = pruning;
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.pruninginterval: " +
+ pruningInterval);
+ } else
+ pruningInterval = 60 * 1000; // 1 minute
+
+ // check to see if we should use a separate (i.e. dedicated)
+ // store connection
+ separateStoreConnection =
+ PropUtil.getBooleanProperty(props,
+ "mail." + name + ".separatestoreconnection", false);
+ if (separateStoreConnection)
+ logger.config("dedicate a store connection");
+
+ }
+ }
+
+ private final ConnectionPool pool;
+
+ /**
+ * A special response handler for connections that are being used
+ * to perform operations on behalf of an object other than the Store.
+ * It DOESN'T cause the Store to be cleaned up if a BYE is seen.
+ * The BYE may be real or synthetic and in either case just indicates
+ * that the connection is dead.
+ */
+ private ResponseHandler nonStoreResponseHandler = new ResponseHandler() {
+ @Override
+ public void handleResponse(Response r) {
+ // Any of these responses may have a response code.
+ if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+ handleResponseCode(r);
+ if (r.isBYE())
+ logger.fine("IMAPStore non-store connection dead");
+ }
+ };
+
+ /**
+ * Constructor that takes a Session object and a URLName that
+ * represents a specific IMAP server.
+ *
+ * @param session the Session
+ * @param url the URLName of this store
+ */
+ public IMAPStore(Session session, URLName url) {
+ this(session, url, "imap", false);
+ }
+
+ /**
+ * Constructor used by this class and by IMAPSSLStore subclass.
+ *
+ * @param session the Session
+ * @param url the URLName of this store
+ * @param name the protocol name for this store
+ * @param isSSL use SSL?
+ */
+ protected IMAPStore(Session session, URLName url,
+ String name, boolean isSSL) {
+ super(session, url); // call super constructor
+ Properties props = session.getProperties();
+
+ if (url != null)
+ name = url.getProtocol();
+ this.name = name;
+ if (!isSSL)
+ isSSL = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".ssl.enable", false);
+ if (isSSL)
+ this.defaultPort = 993;
+ else
+ this.defaultPort = 143;
+ this.isSSL = isSSL;
+
+ debug = session.getDebug();
+ debugusername = PropUtil.getBooleanProperty(props,
+ "mail.debug.auth.username", true);
+ debugpassword = PropUtil.getBooleanProperty(props,
+ "mail.debug.auth.password", false);
+ logger = new MailLogger(this.getClass(),
+ "DEBUG " + name.toUpperCase(Locale.ENGLISH),
+ session.getDebug(), session.getDebugOut());
+
+ boolean partialFetch = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".partialfetch", true);
+ if (!partialFetch) {
+ blksize = -1;
+ logger.config("mail.imap.partialfetch: false");
+ } else {
+ blksize = PropUtil.getIntProperty(props,
+ "mail." + name +".fetchsize", 1024 * 16);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.fetchsize: " + blksize);
+ }
+
+ ignoreSize = PropUtil.getBooleanProperty(props,
+ "mail." + name +".ignorebodystructuresize", false);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.ignorebodystructuresize: " + ignoreSize);
+
+ statusCacheTimeout = PropUtil.getIntProperty(props,
+ "mail." + name + ".statuscachetimeout", 1000);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.statuscachetimeout: " +
+ statusCacheTimeout);
+
+ appendBufferSize = PropUtil.getIntProperty(props,
+ "mail." + name + ".appendbuffersize", -1);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.appendbuffersize: " + appendBufferSize);
+
+ minIdleTime = PropUtil.getIntProperty(props,
+ "mail." + name + ".minidletime", 10);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.minidletime: " + minIdleTime);
+
+ // check if we should do a PROXYAUTH login
+ String s = session.getProperty("mail." + name + ".proxyauth.user");
+ if (s != null) {
+ proxyAuthUser = s;
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("mail.imap.proxyauth.user: " + proxyAuthUser);
+ }
+
+ // check if STARTTLS is enabled
+ enableStartTLS = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".starttls.enable", false);
+ if (enableStartTLS)
+ logger.config("enable STARTTLS");
+
+ // check if STARTTLS is required
+ requireStartTLS = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".starttls.required", false);
+ if (requireStartTLS)
+ logger.config("require STARTTLS");
+
+ // check if SASL is enabled
+ enableSASL = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".sasl.enable", false);
+ if (enableSASL)
+ logger.config("enable SASL");
+
+ // check if SASL mechanisms are specified
+ if (enableSASL) {
+ s = session.getProperty("mail." + name + ".sasl.mechanisms");
+ if (s != null && s.length() > 0) {
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("SASL mechanisms allowed: " + s);
+ List v = new ArrayList<>(5);
+ StringTokenizer st = new StringTokenizer(s, " ,");
+ while (st.hasMoreTokens()) {
+ String m = st.nextToken();
+ if (m.length() > 0)
+ v.add(m);
+ }
+ saslMechanisms = new String[v.size()];
+ v.toArray(saslMechanisms);
+ }
+ }
+
+ // check if an authorization ID has been specified
+ s = session.getProperty("mail." + name + ".sasl.authorizationid");
+ if (s != null) {
+ authorizationID = s;
+ logger.log(Level.CONFIG, "mail.imap.sasl.authorizationid: {0}",
+ authorizationID);
+ }
+
+ // check if a SASL realm has been specified
+ s = session.getProperty("mail." + name + ".sasl.realm");
+ if (s != null) {
+ saslRealm = s;
+ logger.log(Level.CONFIG, "mail.imap.sasl.realm: {0}", saslRealm);
+ }
+
+ // check if forcePasswordRefresh is enabled
+ forcePasswordRefresh = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".forcepasswordrefresh", false);
+ if (forcePasswordRefresh)
+ logger.config("enable forcePasswordRefresh");
+
+ // check if enableimapevents is enabled
+ enableResponseEvents = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".enableresponseevents", false);
+ if (enableResponseEvents)
+ logger.config("enable IMAP response events");
+
+ // check if enableresponseevents is enabled
+ enableImapEvents = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".enableimapevents", false);
+ if (enableImapEvents)
+ logger.config("enable IMAP IDLE events");
+
+ // check if message cache debugging set
+ messageCacheDebug = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".messagecache.debug", false);
+
+ guid = session.getProperty("mail." + name + ".yahoo.guid");
+ if (guid != null)
+ logger.log(Level.CONFIG, "mail.imap.yahoo.guid: {0}", guid);
+
+ // check if throwsearchexception is enabled
+ throwSearchException = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".throwsearchexception", false);
+ if (throwSearchException)
+ logger.config("throw SearchException");
+
+ // check if peek is set
+ peek = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".peek", false);
+ if (peek)
+ logger.config("peek");
+
+ // check if closeFoldersOnStoreFailure is set
+ closeFoldersOnStoreFailure = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".closefoldersonstorefailure", true);
+ if (closeFoldersOnStoreFailure)
+ logger.config("closeFoldersOnStoreFailure");
+
+ // check if COMPRESS is enabled
+ enableCompress = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".compress.enable", false);
+ if (enableCompress)
+ logger.config("enable COMPRESS");
+
+ // check if finalizeCleanClose is enabled
+ finalizeCleanClose = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".finalizecleanclose", false);
+ if (finalizeCleanClose)
+ logger.config("close connection cleanly in finalize");
+
+ s = session.getProperty("mail." + name + ".folder.class");
+ if (s != null) {
+ logger.log(Level.CONFIG, "IMAP: folder class: {0}", s);
+ try {
+ ClassLoader cl = this.getClass().getClassLoader();
+
+ // now load the class
+ Class> folderClass = null;
+ try {
+ // First try the "application's" class loader.
+ // This should eventually be replaced by
+ // Thread.currentThread().getContextClassLoader().
+ folderClass = Class.forName(s, false, cl);
+ } catch (ClassNotFoundException ex1) {
+ // That didn't work, now try the "system" class loader.
+ // (Need both of these because JDK 1.1 class loaders
+ // may not delegate to their parent class loader.)
+ folderClass = Class.forName(s);
+ }
+
+ Class>[] c = { String.class, char.class, IMAPStore.class,
+ Boolean.class };
+ folderConstructor = folderClass.getConstructor(c);
+ Class>[] c2 = { ListInfo.class, IMAPStore.class };
+ folderConstructorLI = folderClass.getConstructor(c2);
+ } catch (Exception ex) {
+ logger.log(Level.CONFIG,
+ "IMAP: failed to load folder class", ex);
+ }
+ }
+
+ pool = new ConnectionPool(name, logger, session);
+ }
+
+ /**
+ * Implementation of protocolConnect(). Will create a connection
+ * to the server and authenticate the user using the mechanisms
+ * specified by various properties.
+ *
+ * The host
, user
, and password
+ * parameters must all be non-null. If the authentication mechanism
+ * being used does not require a password, an empty string or other
+ * suitable dummy password should be used.
+ */
+ @Override
+ protected synchronized boolean
+ protocolConnect(String host, int pport, String user, String password)
+ throws MessagingException {
+
+ IMAPProtocol protocol = null;
+
+ // check for non-null values of host, password, user
+ if (host == null || password == null || user == null) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("protocolConnect returning false" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ return false;
+ }
+
+ // set the port correctly
+ if (pport != -1) {
+ port = pport;
+ } else {
+ port = PropUtil.getIntProperty(session.getProperties(),
+ "mail." + name + ".port", port);
+ }
+
+ // use the default if needed
+ if (port == -1) {
+ port = defaultPort;
+ }
+
+ try {
+ boolean poolEmpty;
+ synchronized (pool) {
+ poolEmpty = pool.authenticatedConnections.isEmpty();
+ }
+
+ if (poolEmpty) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("trying to connect to host \"" + host +
+ "\", port " + port + ", isSSL " + isSSL);
+ protocol = newIMAPProtocol(host, port);
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("protocolConnect login" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ protocol.addResponseHandler(nonStoreResponseHandler);
+ login(protocol, user, password);
+ protocol.removeResponseHandler(nonStoreResponseHandler);
+ protocol.addResponseHandler(this);
+
+ usingSSL = protocol.isSSL(); // in case anyone asks
+
+ this.host = host;
+ this.user = user;
+ this.password = password;
+
+ synchronized (pool) {
+ pool.authenticatedConnections.addElement(protocol);
+ }
+ }
+ } catch (IMAPReferralException ex) {
+ // login failure due to IMAP REFERRAL, close connection to server
+ if (protocol != null)
+ protocol.disconnect();
+ protocol = null;
+ throw new ReferralException(ex.getUrl(), ex.getMessage());
+ } catch (CommandFailedException cex) {
+ // login failure, close connection to server
+ if (protocol != null)
+ protocol.disconnect();
+ protocol = null;
+ Response r = cex.getResponse();
+ throw new AuthenticationFailedException(
+ r != null ? r.getRest() : cex.getMessage());
+ } catch (ProtocolException pex) { // any other exception
+ // failure in login command, close connection to server
+ if (protocol != null)
+ protocol.disconnect();
+ protocol = null;
+ throw new MessagingException(pex.getMessage(), pex);
+ } catch (SocketConnectException scex) {
+ throw new MailConnectException(scex);
+ } catch (IOException ioex) {
+ throw new MessagingException(ioex.getMessage(), ioex);
+ }
+
+ return true;
+ }
+
+ /**
+ * Create an IMAPProtocol object connected to the host and port.
+ * Subclasses of IMAPStore may override this method to return a
+ * subclass of IMAPProtocol that supports product-specific extensions.
+ *
+ * @param host the host name
+ * @param port the port number
+ * @return the new IMAPProtocol object
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol errors
+ * @since JavaMail 1.4.6
+ */
+ protected IMAPProtocol newIMAPProtocol(String host, int port)
+ throws IOException, ProtocolException {
+ return new IMAPProtocol(name, host, port,
+ session.getProperties(),
+ isSSL,
+ logger
+ );
+ }
+
+ private void login(IMAPProtocol p, String u, String pw)
+ throws ProtocolException {
+ // turn on TLS if it's been enabled or required and is supported
+ // and we're not already using SSL
+ if ((enableStartTLS || requireStartTLS) && !p.isSSL()) {
+ if (p.hasCapability("STARTTLS")) {
+ p.startTLS();
+ // if startTLS succeeds, refresh capabilities
+ p.capability();
+ } else if (requireStartTLS) {
+ logger.fine("STARTTLS required but not supported by server");
+ throw new ProtocolException(
+ "STARTTLS required but not supported by server");
+ }
+ }
+ if (p.isAuthenticated())
+ return; // no need to login
+
+ // allow subclasses to issue commands before login
+ preLogin(p);
+
+ // issue special ID command to Yahoo! Mail IMAP server
+ // http://en.wikipedia.org/wiki/Yahoo%21_Mail#Free_IMAP_and_SMTPs_access
+ if (guid != null) {
+ Map gmap = new HashMap<>();
+ gmap.put("GUID", guid);
+ p.id(gmap);
+ }
+
+ /*
+ * Put a special "marker" in the capabilities list so we can
+ * detect if the server refreshed the capabilities in the OK
+ * response.
+ */
+ p.getCapabilities().put("__PRELOGIN__", "");
+ String authzid;
+ if (authorizationID != null)
+ authzid = authorizationID;
+ else if (proxyAuthUser != null)
+ authzid = proxyAuthUser;
+ else
+ authzid = null;
+
+ if (enableSASL) {
+ try {
+ p.sasllogin(saslMechanisms, saslRealm, authzid, u, pw);
+ if (!p.isAuthenticated())
+ throw new CommandFailedException(
+ "SASL authentication failed");
+ } catch (UnsupportedOperationException ex) {
+ // continue to try other authentication methods below
+ }
+ }
+
+ if (!p.isAuthenticated())
+ authenticate(p, authzid, u, pw);
+
+ if (proxyAuthUser != null)
+ p.proxyauth(proxyAuthUser);
+
+ /*
+ * If marker is still there, capabilities haven't been refreshed,
+ * refresh them now.
+ */
+ if (p.hasCapability("__PRELOGIN__")) {
+ try {
+ p.capability();
+ } catch (ConnectionException cex) {
+ throw cex; // rethrow connection failures
+ // XXX - assume connection has been closed
+ } catch (ProtocolException pex) {
+ // ignore other exceptions that "should never happen"
+ }
+ }
+
+ if (enableCompress) {
+ if (p.hasCapability("COMPRESS=DEFLATE")) {
+ 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");
+ }
+
+ /**
+ * Authenticate using one of the non-SASL mechanisms.
+ *
+ * @param p the IMAPProtocol object
+ * @param authzid the authorization ID
+ * @param user the user name
+ * @param password the password
+ * @exception ProtocolException on failures
+ */
+ private void authenticate(IMAPProtocol p, String authzid,
+ String user, String password)
+ throws ProtocolException {
+ // this list must match the "if" statements below
+ String defaultAuthenticationMechanisms = "PLAIN LOGIN NTLM XOAUTH2";
+
+ // setting mail.imap.auth.mechanisms controls which mechanisms will
+ // be used, and in what order they'll be considered. only the first
+ // match is used.
+ String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
+
+ if (mechs == null)
+ mechs = defaultAuthenticationMechanisms;
+
+ /*
+ * Loop through the list of mechanisms supplied by the user
+ * (or defaulted) and try each in turn. If the server supports
+ * the mechanism and we have an authenticator for the mechanism,
+ * and it hasn't been disabled, use it.
+ */
+ StringTokenizer st = new StringTokenizer(mechs);
+ while (st.hasMoreTokens()) {
+ String m = st.nextToken();
+ m = m.toUpperCase(Locale.ENGLISH);
+
+ /*
+ * If using the default mechanisms, check if this one is disabled.
+ */
+ if (mechs == defaultAuthenticationMechanisms) {
+ String dprop = "mail." + name + ".auth." +
+ m.toLowerCase(Locale.ENGLISH) + ".disable";
+ boolean disabled = PropUtil.getBooleanProperty(
+ session.getProperties(),
+ dprop, m.equals("XOAUTH2"));
+ if (disabled) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("mechanism " + m +
+ " disabled by property: " + dprop);
+ continue;
+ }
+ }
+
+ if (!(p.hasCapability("AUTH=" + m) ||
+ (m.equals("LOGIN") && p.hasCapability("AUTH-LOGIN")))) {
+ logger.log(Level.FINE, "mechanism {0} not supported by server",
+ m);
+ continue;
+ }
+
+ if (m.equals("PLAIN"))
+ p.authplain(authzid, user, password);
+ else if (m.equals("LOGIN"))
+ p.authlogin(user, password);
+ else if (m.equals("NTLM"))
+ p.authntlm(authzid, user, password);
+ else if (m.equals("XOAUTH2"))
+ p.authoauth2(user, password);
+ else {
+ logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
+ continue;
+ }
+ return;
+ }
+
+ if (!p.hasCapability("LOGINDISABLED")) {
+ p.login(user, password);
+ return;
+ }
+
+ throw new ProtocolException("No login methods supported!");
+ }
+
+ /**
+ * This method is called after the connection is made and
+ * TLS is started (if needed), but before any authentication
+ * is attempted. Subclasses can override this method to
+ * issue commands that are needed in the "not authenticated"
+ * state. Note that if the connection is pre-authenticated,
+ * this method won't be called.
+ *
+ * The implementation of this method in this class does nothing.
+ *
+ * @param p the IMAPProtocol connection
+ * @exception ProtocolException for protocol errors
+ * @since JavaMail 1.4.4
+ */
+ protected void preLogin(IMAPProtocol p) throws ProtocolException {
+ }
+
+ /**
+ * Does this IMAPStore use SSL when connecting to the server?
+ *
+ * @return true if using SSL
+ * @since JavaMail 1.4.6
+ */
+ public synchronized boolean isSSL() {
+ return usingSSL;
+ }
+
+ /**
+ * Set the user name that will be used for subsequent connections
+ * after this Store is first connected (for example, when creating
+ * a connection to open a Folder). This value is overridden
+ * by any call to the Store's connect method.
+ *
+ * Some IMAP servers may provide an authentication ID that can
+ * be used for more efficient authentication for future connections.
+ * This authentication ID is provided in a server-specific manner
+ * not described here.
+ *
+ * Most applications will never need to use this method.
+ *
+ * @param user the user name for the store
+ * @since JavaMail 1.3.3
+ */
+ public synchronized void setUsername(String user) {
+ this.user = user;
+ }
+
+ /**
+ * Set the password that will be used for subsequent connections
+ * after this Store is first connected (for example, when creating
+ * a connection to open a Folder). This value is overridden
+ * by any call to the Store's connect method.
+ *
+ * Most applications will never need to use this method.
+ *
+ * @param password the password for the store
+ * @since JavaMail 1.3.3
+ */
+ public synchronized void setPassword(String password) {
+ this.password = password;
+ }
+
+ /*
+ * Get a new authenticated protocol object for this Folder.
+ * Also store a reference to this folder in our list of
+ * open folders.
+ */
+ IMAPProtocol getProtocol(IMAPFolder folder)
+ throws MessagingException {
+ IMAPProtocol p = null;
+
+ // keep looking for a connection until we get a good one
+ while (p == null) {
+
+ // New authenticated protocol objects are either acquired
+ // from the connection pool, or created when the pool is
+ // empty or no connections are available. None are available
+ // if the current pool size is one and the separate store
+ // property is set or the connection is in use.
+
+ synchronized (pool) {
+
+ // If there's none available in the pool,
+ // create a new one.
+ if (pool.authenticatedConnections.isEmpty() ||
+ (pool.authenticatedConnections.size() == 1 &&
+ (pool.separateStoreConnection || pool.storeConnectionInUse))) {
+
+ logger.fine("no connections in the pool, creating a new one");
+ try {
+ if (forcePasswordRefresh)
+ refreshPassword();
+ // Use cached host, port and timeout values.
+ p = newIMAPProtocol(host, port);
+ p.addResponseHandler(nonStoreResponseHandler);
+ // Use cached auth info
+ login(p, user, password);
+ p.removeResponseHandler(nonStoreResponseHandler);
+ } catch(Exception ex1) {
+ if (p != null)
+ try {
+ p.disconnect();
+ } catch (Exception ex2) { }
+ p = null;
+ }
+
+ if (p == null)
+ throw new MessagingException("connection failure");
+ } else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("connection available -- size: " +
+ pool.authenticatedConnections.size());
+
+ // remove the available connection from the Authenticated queue
+ p = pool.authenticatedConnections.lastElement();
+ pool.authenticatedConnections.removeElement(p);
+
+ // check if the connection is still live
+ long lastUsed = System.currentTimeMillis() - p.getTimestamp();
+ if (lastUsed > pool.serverTimeoutInterval) {
+ try {
+ /*
+ * Swap in a special response handler that will handle
+ * alerts, but won't cause the store to be closed and
+ * cleaned up if the connection is dead.
+ */
+ p.removeResponseHandler(this);
+ p.addResponseHandler(nonStoreResponseHandler);
+ p.noop();
+ p.removeResponseHandler(nonStoreResponseHandler);
+ p.addResponseHandler(this);
+ } catch (ProtocolException pex) {
+ try {
+ p.removeResponseHandler(nonStoreResponseHandler);
+ p.disconnect();
+ } catch (RuntimeException ignored) {
+ // don't let any exception stop us
+ }
+ p = null;
+ continue; // try again, from the top
+ }
+ }
+
+ // if proxyAuthUser has changed, switch to new user
+ if (proxyAuthUser != null &&
+ !proxyAuthUser.equals(p.getProxyAuthUser()) &&
+ p.hasCapability("X-UNAUTHENTICATE")) {
+ try {
+ /*
+ * Swap in a special response handler that will handle
+ * alerts, but won't cause the store to be closed and
+ * cleaned up if the connection is dead.
+ */
+ p.removeResponseHandler(this);
+ p.addResponseHandler(nonStoreResponseHandler);
+ p.unauthenticate();
+ login(p, user, password);
+ p.removeResponseHandler(nonStoreResponseHandler);
+ p.addResponseHandler(this);
+ } catch (ProtocolException pex) {
+ try {
+ p.removeResponseHandler(nonStoreResponseHandler);
+ p.disconnect();
+ } catch (RuntimeException ignored) {
+ // don't let any exception stop us
+ }
+ p = null;
+ continue; // try again, from the top
+ }
+ }
+
+ // remove the store as a response handler.
+ p.removeResponseHandler(this);
+ }
+
+ // check if we need to look for client-side timeouts
+ timeoutConnections();
+
+ // Add folder to folder-list
+ if (folder != null) {
+ if (pool.folders == null)
+ pool.folders = new Vector<>();
+ pool.folders.addElement(folder);
+ }
+ }
+
+ }
+
+ return p;
+ }
+
+ /**
+ * Get this Store's protocol connection.
+ *
+ * When acquiring a store protocol object, it is important to
+ * use the following steps:
+ *
+ * IMAPProtocol p = null;
+ * try {
+ * p = getStoreProtocol();
+ * // perform the command
+ * } catch (ConnectionException cex) {
+ * throw new StoreClosedException(this, cex.getMessage());
+ * } catch (WhateverException ex) {
+ * // handle it
+ * } finally {
+ * releaseStoreProtocol(p);
+ * }
+ */
+ private IMAPProtocol getStoreProtocol() throws ProtocolException {
+ IMAPProtocol p = null;
+
+ while (p == null) {
+ synchronized (pool) {
+ waitIfIdle();
+
+ // If there's no authenticated connections available create a
+ // new one and place it in the authenticated queue.
+ if (pool.authenticatedConnections.isEmpty()) {
+ pool.logger.fine("getStoreProtocol() - no connections " +
+ "in the pool, creating a new one");
+ try {
+ if (forcePasswordRefresh)
+ refreshPassword();
+ // Use cached host, port and timeout values.
+ p = newIMAPProtocol(host, port);
+ // Use cached auth info
+ login(p, user, password);
+ } catch(Exception ex1) {
+ if (p != null)
+ try {
+ p.logout();
+ } catch (Exception ex2) { }
+ p = null;
+ }
+
+ if (p == null)
+ throw new ConnectionException(
+ "failed to create new store connection");
+
+ p.addResponseHandler(this);
+ pool.authenticatedConnections.addElement(p);
+
+ } else {
+ // Always use the first element in the Authenticated queue.
+ if (pool.logger.isLoggable(Level.FINE))
+ pool.logger.fine("getStoreProtocol() - " +
+ "connection available -- size: " +
+ pool.authenticatedConnections.size());
+ p = pool.authenticatedConnections.firstElement();
+
+ // if proxyAuthUser has changed, switch to new user
+ if (proxyAuthUser != null &&
+ !proxyAuthUser.equals(p.getProxyAuthUser()) &&
+ p.hasCapability("X-UNAUTHENTICATE")) {
+ p.unauthenticate();
+ login(p, user, password);
+ }
+ }
+
+ if (pool.storeConnectionInUse) {
+ try {
+ // someone else is using the connection, give up
+ // and wait until they're done
+ p = null;
+ pool.wait();
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers might
+ // depend on
+ Thread.currentThread().interrupt();
+ // don't keep looking for a connection if we've been
+ // interrupted
+ throw new ProtocolException(
+ "Interrupted getStoreProtocol", ex);
+ }
+ } else {
+ pool.storeConnectionInUse = true;
+
+ pool.logger.fine("getStoreProtocol() -- storeConnectionInUse");
+ }
+
+ timeoutConnections();
+ }
+ }
+ return p;
+ }
+
+ /**
+ * Get a store protocol object for use by a folder.
+ */
+ IMAPProtocol getFolderStoreProtocol() throws ProtocolException {
+ IMAPProtocol p = getStoreProtocol();
+ p.removeResponseHandler(this);
+ p.addResponseHandler(nonStoreResponseHandler);
+ return p;
+ }
+
+ /*
+ * Some authentication systems use one time passwords
+ * or tokens, so each authentication request requires
+ * a new password. This "kludge" allows a callback
+ * to application code to get a new password.
+ *
+ * XXX - remove this when SASL support is added
+ */
+ private void refreshPassword() {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("refresh password, user: " + traceUser(user));
+ InetAddress addr;
+ try {
+ addr = InetAddress.getByName(host);
+ } catch (UnknownHostException e) {
+ addr = null;
+ }
+ PasswordAuthentication pa =
+ session.requestPasswordAuthentication(addr, port,
+ name, null, user);
+ if (pa != null) {
+ user = pa.getUserName();
+ password = pa.getPassword();
+ }
+ }
+
+ /**
+ * If a SELECT succeeds, but indicates that the folder is
+ * READ-ONLY, and the user asked to open the folder READ_WRITE,
+ * do we allow the open to succeed?
+ */
+ boolean allowReadOnlySelect() {
+ return PropUtil.getBooleanProperty(session.getProperties(),
+ "mail." + name + ".allowreadonlyselect", false);
+ }
+
+ /**
+ * Report whether the separateStoreConnection is set.
+ */
+ boolean hasSeparateStoreConnection() {
+ return pool.separateStoreConnection;
+ }
+
+ /**
+ * Return the connection pool logger.
+ */
+ MailLogger getConnectionPoolLogger() {
+ return pool.logger;
+ }
+
+ /**
+ * Report whether message cache debugging is enabled.
+ */
+ boolean getMessageCacheDebug() {
+ return messageCacheDebug;
+ }
+
+ /**
+ * Report whether the connection pool is full.
+ */
+ boolean isConnectionPoolFull() {
+
+ synchronized (pool) {
+ if (pool.logger.isLoggable(Level.FINE))
+ pool.logger.fine("connection pool current size: " +
+ pool.authenticatedConnections.size() +
+ " pool size: " + pool.poolSize);
+
+ return (pool.authenticatedConnections.size() >= pool.poolSize);
+
+ }
+ }
+
+ /**
+ * Release the protocol object back to the connection pool.
+ */
+ void releaseProtocol(IMAPFolder folder, IMAPProtocol protocol) {
+
+ synchronized (pool) {
+ if (protocol != null) {
+ // If the pool is not full, add the store as a response handler
+ // and return the protocol object to the connection pool.
+ if (!isConnectionPoolFull()) {
+ protocol.addResponseHandler(this);
+ pool.authenticatedConnections.addElement(protocol);
+
+ if (logger.isLoggable(Level.FINE))
+ logger.fine(
+ "added an Authenticated connection -- size: " +
+ pool.authenticatedConnections.size());
+ } else {
+ logger.fine(
+ "pool is full, not adding an Authenticated connection");
+ try {
+ protocol.logout();
+ } catch (ProtocolException pex) {};
+ }
+ }
+
+ if (pool.folders != null)
+ pool.folders.removeElement(folder);
+
+ timeoutConnections();
+ }
+ }
+
+ /**
+ * Release the store connection.
+ */
+ private void releaseStoreProtocol(IMAPProtocol protocol) {
+
+ // will be called from idle() without the Store lock held,
+ // but cleanup is synchronized and will acquire the Store lock
+
+ if (protocol == null) {
+ cleanup(); // failed to ever get the connection
+ return; // nothing to release
+ }
+
+ /*
+ * Read out the flag that says whether this connection failed
+ * before releasing the protocol object for others to use.
+ */
+ boolean failed;
+ synchronized (connectionFailedLock) {
+ failed = connectionFailed;
+ connectionFailed = false; // reset for next use
+ }
+
+ // now free the store connection
+ synchronized (pool) {
+ pool.storeConnectionInUse = false;
+ pool.notifyAll(); // in case anyone waiting
+
+ pool.logger.fine("releaseStoreProtocol()");
+
+ timeoutConnections();
+ }
+
+ /*
+ * If the connection died while we were using it, clean up.
+ * It's critical that the store connection be freed and the
+ * connection pool not be locked while we do this.
+ */
+ assert !Thread.holdsLock(pool);
+ if (failed)
+ cleanup();
+ }
+
+ /**
+ * Release a store protocol object that was being used by a folder.
+ */
+ void releaseFolderStoreProtocol(IMAPProtocol protocol) {
+ if (protocol == null)
+ return; // should never happen
+ protocol.removeResponseHandler(nonStoreResponseHandler);
+ protocol.addResponseHandler(this);
+ synchronized (pool) {
+ pool.storeConnectionInUse = false;
+ pool.notifyAll(); // in case anyone waiting
+
+ pool.logger.fine("releaseFolderStoreProtocol()");
+
+ timeoutConnections();
+ }
+ }
+
+ /**
+ * Empty the connection pool.
+ */
+ private void emptyConnectionPool(boolean force) {
+
+ synchronized (pool) {
+ for (int index = pool.authenticatedConnections.size() - 1;
+ index >= 0; --index) {
+ try {
+ IMAPProtocol p =
+ pool.authenticatedConnections.elementAt(index);
+ p.removeResponseHandler(this);
+ if (force)
+ p.disconnect();
+ else
+ p.logout();
+ } catch (ProtocolException pex) {};
+ }
+
+ pool.authenticatedConnections.removeAllElements();
+ }
+
+ pool.logger.fine("removed all authenticated connections from pool");
+ }
+
+ /**
+ * Check to see if it's time to shrink the connection pool.
+ */
+ private void timeoutConnections() {
+
+ synchronized (pool) {
+
+ // If we've exceeded the pruning interval, look for stale
+ // connections to logout.
+ if (System.currentTimeMillis() - pool.lastTimePruned >
+ pool.pruningInterval &&
+ pool.authenticatedConnections.size() > 1) {
+
+ if (pool.logger.isLoggable(Level.FINE)) {
+ pool.logger.fine("checking for connections to prune: " +
+ (System.currentTimeMillis() - pool.lastTimePruned));
+ pool.logger.fine("clientTimeoutInterval: " +
+ pool.clientTimeoutInterval);
+ }
+
+ IMAPProtocol p;
+
+ // Check the timestamp of the protocol objects in the pool and
+ // logout if the interval exceeds the client timeout value
+ // (leave the first connection).
+ for (int index = pool.authenticatedConnections.size() - 1;
+ index > 0; index--) {
+ p = pool.authenticatedConnections.
+ elementAt(index);
+ if (pool.logger.isLoggable(Level.FINE))
+ pool.logger.fine("protocol last used: " +
+ (System.currentTimeMillis() - p.getTimestamp()));
+ if (System.currentTimeMillis() - p.getTimestamp() >
+ pool.clientTimeoutInterval) {
+
+ pool.logger.fine(
+ "authenticated connection timed out, " +
+ "logging out the connection");
+
+ p.removeResponseHandler(this);
+ pool.authenticatedConnections.removeElementAt(index);
+
+ try {
+ p.logout();
+ } catch (ProtocolException pex) {}
+ }
+ }
+ pool.lastTimePruned = System.currentTimeMillis();
+ }
+ }
+ }
+
+ /**
+ * Get the block size to use for fetch requests on this Store.
+ */
+ int getFetchBlockSize() {
+ return blksize;
+ }
+
+ /**
+ * Ignore the size reported in the BODYSTRUCTURE when fetching data?
+ */
+ boolean ignoreBodyStructureSize() {
+ return ignoreSize;
+ }
+
+ /**
+ * Get a reference to the session.
+ */
+ Session getSession() {
+ return session;
+ }
+
+ /**
+ * Get the number of milliseconds to cache STATUS response.
+ */
+ int getStatusCacheTimeout() {
+ return statusCacheTimeout;
+ }
+
+ /**
+ * Get the maximum size of a message to buffer for append.
+ */
+ int getAppendBufferSize() {
+ return appendBufferSize;
+ }
+
+ /**
+ * Get the minimum amount of time to delay when returning from idle.
+ */
+ int getMinIdleTime() {
+ return minIdleTime;
+ }
+
+ /**
+ * Throw a SearchException if the search expression is too complex?
+ */
+ boolean throwSearchException() {
+ return throwSearchException;
+ }
+
+ /**
+ * Get the default "peek" value.
+ */
+ boolean getPeek() {
+ return peek;
+ }
+
+ /**
+ * Return true if the specified capability string is in the list
+ * of capabilities the server announced.
+ *
+ * @param capability the capability string
+ * @return true if the server supports this capability
+ * @exception MessagingException for failures
+ * @since JavaMail 1.3.3
+ */
+ public synchronized boolean hasCapability(String capability)
+ throws MessagingException {
+ IMAPProtocol p = null;
+ try {
+ p = getStoreProtocol();
+ return p.hasCapability(capability);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+
+ /**
+ * Set the user name to be used with the PROXYAUTH command.
+ * The PROXYAUTH user name can also be set using the
+ * mail.imap.proxyauth.user
property when this
+ * Store is created.
+ *
+ * @param user the user name to set
+ * @since JavaMail 1.5.1
+ */
+ public void setProxyAuthUser(String user) {
+ proxyAuthUser = user;
+ }
+
+ /**
+ * Get the user name to be used with the PROXYAUTH command.
+ *
+ * @return the user name
+ * @since JavaMail 1.5.1
+ */
+ public String getProxyAuthUser() {
+ return proxyAuthUser;
+ }
+
+ /**
+ * Check whether this store is connected. Override superclass
+ * method, to actually ping our server connection.
+ */
+ @Override
+ public synchronized boolean isConnected() {
+ if (!super.isConnected()) {
+ // if we haven't been connected at all, don't bother with
+ // the NOOP.
+ return false;
+ }
+
+ /*
+ * The below noop() request can:
+ * (1) succeed - in which case all is fine.
+ *
+ * (2) fail because the server returns NO or BAD, in which
+ * case we ignore it since we can't really do anything.
+ * (2) fail because a BYE response is obtained from the
+ * server
+ * (3) fail because the socket.write() to the server fails,
+ * in which case the iap.protocol() code converts the
+ * IOException into a BYE response.
+ *
+ * Thus, our BYE handler will take care of closing the Store
+ * in case our connection is really gone.
+ */
+
+ IMAPProtocol p = null;
+ try {
+ p = getStoreProtocol();
+ p.noop();
+ } catch (ProtocolException pex) {
+ // will return false below
+ } finally {
+ releaseStoreProtocol(p);
+ }
+
+
+ return super.isConnected();
+ }
+
+ /**
+ * Close this Store.
+ */
+ @Override
+ public synchronized void close() throws MessagingException {
+ cleanup();
+ // do these again in case cleanup returned early
+ // because we were already closed due to a failure,
+ // in which case we force close everything
+ closeAllFolders(true);
+ emptyConnectionPool(true);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (!finalizeCleanClose) {
+ // when finalizing, close connections abruptly
+ synchronized (connectionFailedLock) {
+ connectionFailed = true;
+ forceClose = true;
+ }
+ closeFoldersOnStoreFailure = true; // make sure folders get closed
+ }
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Cleanup before dying.
+ */
+ private synchronized void cleanup() {
+ // if we're not connected, someone beat us to it
+ if (!super.isConnected()) {
+ logger.fine("IMAPStore cleanup, not connected");
+ return;
+ }
+
+ /*
+ * If forceClose is true, some thread ran into an error that suggests
+ * the server might be dead, so we force the folders to close
+ * abruptly without waiting for the server. Used when
+ * the store connection times out, for example.
+ */
+ boolean force;
+ synchronized (connectionFailedLock) {
+ force = forceClose;
+ forceClose = false;
+ connectionFailed = false;
+ }
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("IMAPStore cleanup, force " + force);
+
+ if (!force || closeFoldersOnStoreFailure) {
+ closeAllFolders(force);
+ }
+
+ emptyConnectionPool(force);
+
+ // to set the state and send the closed connection event
+ try {
+ super.close();
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ logger.fine("IMAPStore cleanup done");
+ }
+
+ /**
+ * Close all open Folders. If force is true, close them forcibly.
+ */
+ private void closeAllFolders(boolean force) {
+ List foldersCopy = null;
+ boolean done = true;
+
+ // To avoid violating the locking hierarchy, there's no lock we
+ // can hold that prevents another thread from trying to open a
+ // folder at the same time we're trying to close all the folders.
+ // Thus, there's an inherent race condition here. We close all
+ // the folders we know about and then check whether any new folders
+ // have been opened in the mean time. We keep trying until we're
+ // successful in closing all the folders.
+ for (;;) {
+ // Make a copy of the folders list so we do not violate the
+ // folder-connection pool locking hierarchy.
+ synchronized (pool) {
+ if (pool.folders != null) {
+ done = false;
+ foldersCopy = pool.folders;
+ pool.folders = null;
+ } else {
+ done = true;
+ }
+ }
+ if (done)
+ break;
+
+ // Close and remove any open folders under this Store.
+ for (int i = 0, fsize = foldersCopy.size(); i < fsize; i++) {
+ IMAPFolder f = foldersCopy.get(i);
+
+ try {
+ if (force) {
+ logger.fine("force folder to close");
+ // Don't want to wait for folder connection to timeout
+ // (if, for example, the server is down) so we close
+ // folders abruptly.
+ f.forceClose();
+ } else {
+ logger.fine("close folder");
+ f.close(false);
+ }
+ } catch (MessagingException mex) {
+ // Who cares ?! Ignore 'em.
+ } catch (IllegalStateException ex) {
+ // Ditto
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Get the default folder, representing the root of this user's
+ * namespace. Returns a closed DefaultFolder object.
+ */
+ @Override
+ public synchronized Folder getDefaultFolder() throws MessagingException {
+ checkConnected();
+ return new DefaultFolder(this);
+ }
+
+ /**
+ * Get named folder. Returns a new, closed IMAPFolder.
+ */
+ @Override
+ public synchronized Folder getFolder(String name)
+ throws MessagingException {
+ checkConnected();
+ return newIMAPFolder(name, IMAPFolder.UNKNOWN_SEPARATOR);
+ }
+
+ /**
+ * Get named folder. Returns a new, closed IMAPFolder.
+ */
+ @Override
+ public synchronized Folder getFolder(URLName url)
+ throws MessagingException {
+ checkConnected();
+ return newIMAPFolder(url.getFile(), IMAPFolder.UNKNOWN_SEPARATOR);
+ }
+
+ /**
+ * Create an IMAPFolder object. If user supplied their own class,
+ * use it. Otherwise, call the constructor.
+ *
+ * @param fullName the full name of the folder
+ * @param separator the separator character for the folder hierarchy
+ * @param isNamespace does this name represent a namespace?
+ * @return the new IMAPFolder object
+ */
+ protected IMAPFolder newIMAPFolder(String fullName, char separator,
+ Boolean isNamespace) {
+ IMAPFolder f = null;
+ if (folderConstructor != null) {
+ try {
+ Object[] o =
+ { fullName, Character.valueOf(separator), this, isNamespace };
+ f = (IMAPFolder)folderConstructor.newInstance(o);
+ } catch (Exception ex) {
+ logger.log(Level.FINE,
+ "exception creating IMAPFolder class", ex);
+ }
+ }
+ if (f == null)
+ f = new IMAPFolder(fullName, separator, this, isNamespace);
+ return f;
+ }
+
+ /**
+ * Create an IMAPFolder object. Call the newIMAPFolder method
+ * above with a null isNamespace.
+ *
+ * @param fullName the full name of the folder
+ * @param separator the separator character for the folder hierarchy
+ * @return the new IMAPFolder object
+ */
+ protected IMAPFolder newIMAPFolder(String fullName, char separator) {
+ return newIMAPFolder(fullName, separator, null);
+ }
+
+ /**
+ * Create an IMAPFolder object. If user supplied their own class,
+ * use it. Otherwise, call the constructor.
+ *
+ * @param li the ListInfo for the folder
+ * @return the new IMAPFolder object
+ */
+ protected IMAPFolder newIMAPFolder(ListInfo li) {
+ IMAPFolder f = null;
+ if (folderConstructorLI != null) {
+ try {
+ Object[] o = { li, this };
+ f = (IMAPFolder)folderConstructorLI.newInstance(o);
+ } catch (Exception ex) {
+ logger.log(Level.FINE,
+ "exception creating IMAPFolder class LI", ex);
+ }
+ }
+ if (f == null)
+ f = new IMAPFolder(li, this);
+ return f;
+ }
+
+ /**
+ * Using the IMAP NAMESPACE command (RFC 2342), return a set
+ * of folders representing the Personal namespaces.
+ */
+ @Override
+ public Folder[] getPersonalNamespaces() throws MessagingException {
+ Namespaces ns = getNamespaces();
+ if (ns == null || ns.personal == null)
+ return super.getPersonalNamespaces();
+ return namespaceToFolders(ns.personal, null);
+ }
+
+ /**
+ * Using the IMAP NAMESPACE command (RFC 2342), return a set
+ * of folders representing the User's namespaces.
+ */
+ @Override
+ public Folder[] getUserNamespaces(String user)
+ throws MessagingException {
+ Namespaces ns = getNamespaces();
+ if (ns == null || ns.otherUsers == null)
+ return super.getUserNamespaces(user);
+ return namespaceToFolders(ns.otherUsers, user);
+ }
+
+ /**
+ * Using the IMAP NAMESPACE command (RFC 2342), return a set
+ * of folders representing the Shared namespaces.
+ */
+ @Override
+ public Folder[] getSharedNamespaces() throws MessagingException {
+ Namespaces ns = getNamespaces();
+ if (ns == null || ns.shared == null)
+ return super.getSharedNamespaces();
+ return namespaceToFolders(ns.shared, null);
+ }
+
+ private synchronized Namespaces getNamespaces() throws MessagingException {
+ checkConnected();
+
+ IMAPProtocol p = null;
+
+ if (namespaces == null) {
+ try {
+ p = getStoreProtocol();
+ namespaces = p.namespace();
+ } catch (BadCommandException bex) {
+ // NAMESPACE not supported, ignore it
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+ return namespaces;
+ }
+
+ private Folder[] namespaceToFolders(Namespaces.Namespace[] ns,
+ String user) {
+ Folder[] fa = new Folder[ns.length];
+ for (int i = 0; i < fa.length; i++) {
+ String name = ns[i].prefix;
+ if (user == null) {
+ // strip trailing delimiter
+ int len = name.length();
+ if ( len > 0 && name.charAt(len - 1) == ns[i].delimiter)
+ name = name.substring(0, len - 1);
+ } else {
+ // add user
+ name += user;
+ }
+ fa[i] = newIMAPFolder(name, ns[i].delimiter,
+ Boolean.valueOf(user == null));
+ }
+ return fa;
+ }
+
+ /**
+ * Get the quotas for the named quota root.
+ * Quotas are controlled on the basis of a quota root, not
+ * (necessarily) a folder. The relationship between folders
+ * and quota roots depends on the IMAP server. Some servers
+ * might implement a single quota root for all folders owned by
+ * a user. Other servers might implement a separate quota root
+ * for each folder. A single folder can even have multiple
+ * quota roots, perhaps controlling quotas for different
+ * resources.
+ *
+ * @param root the name of the quota root
+ * @return array of Quota objects
+ * @exception MessagingException if the server doesn't support the
+ * QUOTA extension
+ */
+ @Override
+ public synchronized Quota[] getQuota(String root)
+ throws MessagingException {
+ checkConnected();
+ Quota[] qa = null;
+
+ IMAPProtocol p = null;
+ try {
+ p = getStoreProtocol();
+ qa = p.getQuotaRoot(root);
+ } catch (BadCommandException bex) {
+ throw new MessagingException("QUOTA not supported", bex);
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ return qa;
+ }
+
+ /**
+ * Set the quotas for the quota root specified in the quota argument.
+ * Typically this will be one of the quota roots obtained from the
+ * getQuota
method, but it need not be.
+ *
+ * @param quota the quota to set
+ * @exception MessagingException if the server doesn't support the
+ * QUOTA extension
+ */
+ @Override
+ public synchronized void setQuota(Quota quota) throws MessagingException {
+ checkConnected();
+ IMAPProtocol p = null;
+ try {
+ p = getStoreProtocol();
+ p.setQuota(quota);
+ } catch (BadCommandException bex) {
+ throw new MessagingException("QUOTA not supported", bex);
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ }
+
+ private void checkConnected() {
+ assert Thread.holdsLock(this);
+ if (!super.isConnected())
+ throw new IllegalStateException("Not connected");
+ }
+
+ /**
+ * Response handler method.
+ */
+ @Override
+ public void handleResponse(Response r) {
+ // Any of these responses may have a response code.
+ if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+ handleResponseCode(r);
+ if (r.isBYE()) {
+ logger.fine("IMAPStore connection dead");
+ // Store's IMAP connection is dead, save the response so that
+ // releaseStoreProtocol will cleanup later.
+ synchronized (connectionFailedLock) {
+ connectionFailed = true;
+ if (r.isSynthetic())
+ forceClose = true;
+ }
+ return;
+ }
+ }
+
+ /**
+ * Use the IMAP IDLE command (see
+ * RFC 2177 ),
+ * if supported by the server, to enter idle mode so that the server
+ * can send unsolicited notifications
+ * without the need for the client to constantly poll the server.
+ * Use a ConnectionListener
to be notified of
+ * events. When another thread (e.g., the listener thread)
+ * needs to issue an IMAP comand for this Store, the idle mode will
+ * be terminated and this method will return. Typically the caller
+ * will invoke this method in a loop.
+ *
+ * If the mail.imap.enableimapevents property is set, notifications
+ * received while the IDLE command is active will be delivered to
+ * ConnectionListener
s as events with a type of
+ * IMAPStore.RESPONSE
. The event's message will be
+ * the raw IMAP response string.
+ * Note that most IMAP servers will not deliver any events when
+ * using the IDLE command on a connection with no mailbox selected
+ * (i.e., this method). In most cases you'll want to use the
+ * idle
method on IMAPFolder
.
+ *
+ * NOTE: This capability is highly experimental and likely will change
+ * in future releases.
+ *
+ * The mail.imap.minidletime property enforces a minimum delay
+ * before returning from this method, to ensure that other threads
+ * have a chance to issue commands before the caller invokes this
+ * method again. The default delay is 10 milliseconds.
+ *
+ * @exception MessagingException if the server doesn't support the
+ * IDLE extension
+ * @exception IllegalStateException if the store isn't connected
+ *
+ * @since JavaMail 1.4.1
+ */
+ public void idle() throws MessagingException {
+ IMAPProtocol p = null;
+ // ASSERT: Must NOT be called with the connection pool
+ // synchronization lock held.
+ assert !Thread.holdsLock(pool);
+ synchronized (this) {
+ checkConnected();
+ }
+ boolean needNotification = false;
+ try {
+ synchronized (pool) {
+ p = getStoreProtocol();
+ if (pool.idleState != ConnectionPool.RUNNING) {
+ // some other thread must be running the IDLE
+ // command, we'll just wait for it to finish
+ // without aborting it ourselves
+ try {
+ // give up lock and wait to be not idle
+ pool.wait();
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers might
+ // depend on
+ Thread.currentThread().interrupt();
+ // stop waiting and return to caller
+ throw new MessagingException("idle interrupted", ex);
+ }
+ return;
+ }
+ p.idleStart();
+ needNotification = true;
+ pool.idleState = ConnectionPool.IDLE;
+ pool.idleProtocol = p;
+ }
+
+ /*
+ * We gave up the pool lock so that other threads
+ * can get into the pool far enough to see that we're
+ * in IDLE and abort the IDLE.
+ *
+ * Now we read responses from the IDLE command, especially
+ * including unsolicited notifications from the server.
+ * We don't hold the pool lock while reading because
+ * it protects the idleState and other threads need to be
+ * able to examine the state.
+ *
+ * We hold the pool lock while processing the responses.
+ */
+ for (;;) {
+ Response r = p.readIdleResponse();
+ synchronized (pool) {
+ if (r == null || !p.processIdleResponse(r)) {
+ pool.idleState = ConnectionPool.RUNNING;
+ pool.idleProtocol = null;
+ pool.notifyAll();
+ needNotification = false;
+ break;
+ }
+ }
+ if (enableImapEvents && r.isUnTagged()) {
+ notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
+ }
+ }
+
+ /*
+ * Enforce a minimum delay to give time to threads
+ * processing the responses that came in while we
+ * were idle.
+ */
+ int minidle = getMinIdleTime();
+ if (minidle > 0) {
+ try {
+ Thread.sleep(minidle);
+ } catch (InterruptedException ex) {
+ // restore the interrupted state, which callers might
+ // depend on
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ } catch (BadCommandException bex) {
+ throw new MessagingException("IDLE not supported", bex);
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ if (needNotification) {
+ synchronized (pool) {
+ pool.idleState = ConnectionPool.RUNNING;
+ pool.idleProtocol = null;
+ pool.notifyAll();
+ }
+ }
+ releaseStoreProtocol(p);
+ }
+ }
+
+ /*
+ * If an IDLE command is in progress, abort it if necessary,
+ * and wait until it completes.
+ * ASSERT: Must be called with the pool's lock held.
+ */
+ private void waitIfIdle() throws ProtocolException {
+ assert Thread.holdsLock(pool);
+ while (pool.idleState != ConnectionPool.RUNNING) {
+ if (pool.idleState == ConnectionPool.IDLE) {
+ pool.idleProtocol.idleAbort();
+ pool.idleState = ConnectionPool.ABORTING;
+ }
+ try {
+ // give up lock and wait to be not idle
+ pool.wait();
+ } catch (InterruptedException ex) {
+ // If someone is trying to interrupt us we can't keep going
+ // around the loop waiting for IDLE to complete, but we can't
+ // just return because callers expect the idleState to be
+ // RUNNING when we return. Throwing this exception seems
+ // like the best choice.
+ throw new ProtocolException("Interrupted waitIfIdle", ex);
+ }
+ }
+ }
+
+ /**
+ * Send the IMAP ID command (if supported by the server) and return
+ * the result from the server. The ID command identfies the client
+ * to the server and returns information about the server to the client.
+ * See RFC 2971 .
+ * The returned Map is unmodifiable.
+ *
+ * @param clientParams a Map of keys and values identifying the client
+ * @return a Map of keys and values identifying the server
+ * @exception MessagingException if the server doesn't support the
+ * ID extension
+ * @since JavaMail 1.5.1
+ */
+ public synchronized Map id(Map clientParams)
+ throws MessagingException {
+ checkConnected();
+ Map serverParams = null;
+
+ IMAPProtocol p = null;
+ try {
+ p = getStoreProtocol();
+ serverParams = p.id(clientParams);
+ } catch (BadCommandException bex) {
+ throw new MessagingException("ID not supported", bex);
+ } catch (ConnectionException cex) {
+ throw new StoreClosedException(this, cex.getMessage());
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ releaseStoreProtocol(p);
+ }
+ return serverParams;
+ }
+
+ /**
+ * Handle notifications and alerts.
+ * Response must be an OK, NO, BAD, or BYE response.
+ */
+ void handleResponseCode(Response r) {
+ if (enableResponseEvents)
+ notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
+ String s = r.getRest(); // get the text after the response
+ boolean isAlert = false;
+ if (s.startsWith("[")) { // a response code
+ int i = s.indexOf(']');
+ // remember if it's an alert
+ if (i > 0 && s.substring(0, i + 1).equalsIgnoreCase("[ALERT]"))
+ isAlert = true;
+ // strip off the response code in any event
+ s = s.substring(i + 1).trim();
+ }
+ if (isAlert)
+ notifyStoreListeners(StoreEvent.ALERT, s);
+ else if (r.isUnTagged() && s.length() > 0)
+ // Only send notifications that come with untagged
+ // responses, and only if there is actually some
+ // text there.
+ notifyStoreListeners(StoreEvent.NOTICE, s);
+ }
+
+ private String traceUser(String user) {
+ return debugusername ? user : "";
+ }
+
+ private String tracePassword(String password) {
+ return debugpassword ? password :
+ (password == null ? "" : "");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/IdleManager.java b/app/src/main/java/com/sun/mail/imap/IdleManager.java
new file mode 100644
index 0000000000..781f58d5cd
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/IdleManager.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Socket;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+import java.util.logging.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+
+import javax.mail.*;
+
+import com.sun.mail.imap.protocol.IMAPProtocol;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * IdleManager uses the optional IMAP IDLE command
+ * (RFC 2177 )
+ * to watch multiple folders for new messages.
+ * IdleManager uses an Executor to execute tasks in separate threads.
+ * An Executor is typically provided by an ExecutorService.
+ * For example, for a Java SE application:
+ *
+ * ExecutorService es = Executors.newCachedThreadPool();
+ * final IdleManager idleManager = new IdleManager(session, es);
+ *
+ * For a Java EE 7 application:
+ *
+ * {@literal @}Resource
+ * ManagedExecutorService es;
+ * final IdleManager idleManager = new IdleManager(session, es);
+ *
+ * To watch for new messages in a folder, open the folder, register a listener,
+ * and ask the IdleManager to watch the folder:
+ *
+ * Folder folder = store.getFolder("INBOX");
+ * folder.open(Folder.READ_WRITE);
+ * folder.addMessageCountListener(new MessageCountAdapter() {
+ * public void messagesAdded(MessageCountEvent ev) {
+ * Folder folder = (Folder)ev.getSource();
+ * Message[] msgs = ev.getMessages();
+ * System.out.println("Folder: " + folder +
+ * " got " + msgs.length + " new messages");
+ * try {
+ * // process new messages
+ * idleManager.watch(folder); // keep watching for new messages
+ * } catch (MessagingException mex) {
+ * // handle exception related to the Folder
+ * }
+ * }
+ * });
+ * idleManager.watch(folder);
+ *
+ * This delivers the events for each folder in a separate thread, NOT
+ * using the Executor. To deliver all events in a single thread
+ * using the Executor, set the following properties for the Session
+ * (once), and then add listeners and watch the folder as above.
+ *
+ * // the following should be done once...
+ * Properties props = session.getProperties();
+ * props.put("mail.event.scope", "session"); // or "application"
+ * props.put("mail.event.executor", es);
+ *
+ * Note that, after processing new messages in your listener, or doing any
+ * other operations on the folder in any other thread, you need to tell
+ * the IdleManager to watch for more new messages. Unless, of course, you
+ * close the folder.
+ *
+ * The IdleManager is created with a Session, which it uses only to control
+ * debug output. A single IdleManager instance can watch multiple Folders
+ * from multiple Stores and multiple Sessions.
+ *
+ * Due to limitations in the Java SE nio support, a
+ * {@link java.nio.channels.SocketChannel SocketChannel} must be used instead
+ * of a {@link java.net.Socket Socket} to connect to the server. However,
+ * SocketChannels don't support all the features of Sockets, such as connecting
+ * through a SOCKS proxy server. SocketChannels also don't support
+ * simultaneous read and write, which means that the
+ * {@link com.sun.mail.imap.IMAPFolder#idle idle} method can't be used if
+ * SocketChannels are being used; use this IdleManager instead.
+ * To enable support for SocketChannels instead of Sockets, set the
+ * mail.imap.usesocketchannels
property in the Session used to
+ * access the IMAP Folder. (Or mail.imaps.usesocketchannels
if
+ * you're using the "imaps" protocol.) This will effect all connections in
+ * that Session, but you can create another Session without this property set
+ * if you need to use the features that are incompatible with SocketChannels.
+ *
+ * NOTE: The IdleManager, and all APIs and properties related to it, should
+ * be considered EXPERIMENTAL . They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @since JavaMail 1.5.2
+ */
+public class IdleManager {
+ private Executor es;
+ private Selector selector;
+ private MailLogger logger;
+ private volatile boolean die = false;
+ private volatile boolean running;
+ private Queue toWatch = new ConcurrentLinkedQueue<>();
+ private Queue toAbort = new ConcurrentLinkedQueue<>();
+
+ /**
+ * Create an IdleManager. The Session is used only to configure
+ * debugging output. The Executor is used to create the
+ * "select" thread.
+ *
+ * @param session the Session containing configuration information
+ * @param es the Executor used to create threads
+ * @exception IOException for Selector failures
+ */
+ public IdleManager(Session session, Executor es) throws IOException {
+ this.es = es;
+ logger = new MailLogger(this.getClass(), "DEBUG IMAP",
+ session.getDebug(), session.getDebugOut());
+ selector = Selector.open();
+ es.execute(new Runnable() {
+ @Override
+ public void run() {
+ logger.fine("IdleManager select starting");
+ try {
+ running = true;
+ select();
+ } finally {
+ running = false;
+ logger.fine("IdleManager select terminating");
+ }
+ }
+ });
+ }
+
+ /**
+ * Is the IdleManager currently running? The IdleManager starts
+ * running when the Executor schedules its task. The IdleManager
+ * stops running after its task detects the stop request from the
+ * {@link #stop stop} method, or if it terminates abnormally due
+ * to an unexpected error.
+ *
+ * @return true if the IdleMaanger is running
+ * @since JavaMail 1.5.5
+ */
+ public boolean isRunning() {
+ return running;
+ }
+
+ /**
+ * Watch the Folder for new messages and other events using the IMAP IDLE
+ * command.
+ *
+ * @param folder the folder to watch
+ * @exception MessagingException for errors related to the folder
+ */
+ public void watch(Folder folder)
+ throws MessagingException {
+ if (die) // XXX - should be IllegalStateException?
+ throw new MessagingException("IdleManager is not running");
+ if (!(folder instanceof IMAPFolder))
+ throw new MessagingException("Can only watch IMAP folders");
+ IMAPFolder ifolder = (IMAPFolder)folder;
+ SocketChannel sc = ifolder.getChannel();
+ if (sc == null) {
+ if (folder.isOpen())
+ throw new MessagingException(
+ "Folder is not using SocketChannels");
+ else
+ throw new MessagingException("Folder is not open");
+ }
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST, "IdleManager watching {0}",
+ folderName(ifolder));
+ // keep trying to start the IDLE command until we're successful.
+ // may block if we're in the middle of aborting an IDLE command.
+ int tries = 0;
+ while (!ifolder.startIdle(this)) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle failed for {0}",
+ folderName(ifolder));
+ tries++;
+ }
+ if (logger.isLoggable(Level.FINEST)) {
+ if (tries > 0)
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle succeeded for {0}" +
+ " after " + tries + " tries",
+ folderName(ifolder));
+ else
+ logger.log(Level.FINEST,
+ "IdleManager.watch startIdle succeeded for {0}",
+ folderName(ifolder));
+ }
+ synchronized (this) {
+ toWatch.add(ifolder);
+ selector.wakeup();
+ }
+ }
+
+ /**
+ * Request that the specified folder abort an IDLE command.
+ * We can't do the abort directly because the DONE message needs
+ * to be sent through the (potentially) SSL socket, which means
+ * we need to be in blocking I/O mode. We can only switch to
+ * blocking I/O mode when not selecting, so wake up the selector,
+ * which will process this request when it wakes up.
+ */
+ void requestAbort(IMAPFolder folder) {
+ toAbort.add(folder);
+ selector.wakeup();
+ }
+
+ /**
+ * Run the {@link java.nio.channels.Selector#select select} loop
+ * to poll each watched folder for events sent from the server.
+ */
+ private void select() {
+ die = false;
+ try {
+ while (!die) {
+ watchAll();
+ logger.finest("IdleManager waiting...");
+ int ns = selector.select();
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager selected {0} channels", ns);
+ if (die || Thread.currentThread().isInterrupted())
+ break;
+
+ /*
+ * Process any selected folders. We cancel the
+ * selection key for any selected folder, so if we
+ * need to continue watching that folder it's added
+ * to the toWatch list again. We can't actually
+ * register that folder again until the previous
+ * selection key is cancelled, so we call selectNow()
+ * just for the side effect of cancelling the selection
+ * keys. But if selectNow() selects something, we
+ * process it before adding folders from the toWatch
+ * queue. And so on until there is nothing to do, at
+ * which point it's safe to register folders from the
+ * toWatch queue. This should be "fair" since each
+ * selection key is used only once before being added
+ * to the toWatch list.
+ */
+ do {
+ processKeys();
+ } while (selector.selectNow() > 0 || !toAbort.isEmpty());
+ }
+ } catch (InterruptedIOException ex) {
+ logger.log(Level.FINEST, "IdleManager interrupted", ex);
+ } catch (IOException ex) {
+ logger.log(Level.FINEST, "IdleManager got I/O exception", ex);
+ } catch (Exception ex) {
+ logger.log(Level.FINEST, "IdleManager got exception", ex);
+ } finally {
+ die = true; // prevent new watches in case of exception
+ logger.finest("IdleManager unwatchAll");
+ try {
+ unwatchAll();
+ selector.close();
+ } catch (IOException ex2) {
+ // nothing to do...
+ logger.log(Level.FINEST, "IdleManager unwatch exception", ex2);
+ }
+ logger.fine("IdleManager exiting");
+ }
+ }
+
+ /**
+ * Register all of the folders in the queue with the selector,
+ * switching them to nonblocking I/O mode first.
+ */
+ private void watchAll() {
+ /*
+ * Pull each of the folders from the toWatch queue
+ * and register it.
+ */
+ IMAPFolder folder;
+ while ((folder = toWatch.poll()) != null) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager adding {0} to selector", folderName(folder));
+ try {
+ SocketChannel sc = folder.getChannel();
+ if (sc == null)
+ continue;
+ // has to be non-blocking to select
+ sc.configureBlocking(false);
+ sc.register(selector, SelectionKey.OP_READ, folder);
+ } catch (IOException ex) {
+ // oh well, nothing to do
+ logger.log(Level.FINEST,
+ "IdleManager can't register folder", ex);
+ } catch (CancelledKeyException ex) {
+ // this should never happen
+ logger.log(Level.FINEST,
+ "IdleManager can't register folder", ex);
+ }
+ }
+ }
+
+ /**
+ * Process the selected keys.
+ */
+ private void processKeys() throws IOException {
+ IMAPFolder folder;
+
+ /*
+ * First, process any channels with data to read.
+ */
+ Set selectedKeys = selector.selectedKeys();
+ /*
+ * XXX - this is simpler, but it can fail with
+ * ConcurrentModificationException
+ *
+ for (SelectionKey sk : selectedKeys) {
+ selectedKeys.remove(sk); // only process each key once
+ ...
+ }
+ */
+ Iterator it = selectedKeys.iterator();
+ while (it.hasNext()) {
+ SelectionKey sk = it.next();
+ it.remove(); // only process each key once
+ // have to cancel so we can switch back to blocking I/O mode
+ sk.cancel();
+ folder = (IMAPFolder)sk.attachment();
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager selected folder: {0}", folderName(folder));
+ SelectableChannel sc = sk.channel();
+ // switch back to blocking to allow normal I/O
+ sc.configureBlocking(true);
+ try {
+ if (folder.handleIdle(false)) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager continue watching folder {0}",
+ folderName(folder));
+ // more to do with this folder, select on it again
+ toWatch.add(folder);
+ } else {
+ // done watching this folder,
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager done watching folder {0}",
+ folderName(folder));
+ }
+ } catch (MessagingException ex) {
+ // something went wrong, stop watching this folder
+ logger.log(Level.FINEST,
+ "IdleManager got exception for folder: " +
+ folderName(folder), ex);
+ }
+ }
+
+ /*
+ * Now, process any folders that we need to abort.
+ */
+ while ((folder = toAbort.poll()) != null) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager aborting IDLE for folder: {0}",
+ folderName(folder));
+ SocketChannel sc = folder.getChannel();
+ if (sc == null)
+ continue;
+ SelectionKey sk = sc.keyFor(selector);
+ // have to cancel so we can switch back to blocking I/O mode
+ if (sk != null)
+ sk.cancel();
+ // switch back to blocking to allow normal I/O
+ sc.configureBlocking(true);
+
+ // if there's a read timeout, have to do the abort in a new thread
+ Socket sock = sc.socket();
+ if (sock != null && sock.getSoTimeout() > 0) {
+ logger.finest("IdleManager requesting DONE with timeout");
+ toWatch.remove(folder);
+ final IMAPFolder folder0 = folder;
+ es.execute(new Runnable() {
+ @Override
+ public void run() {
+ // send the DONE and wait for the response
+ folder0.idleAbortWait();
+ }
+ });
+ } else {
+ folder.idleAbort(); // send the DONE message
+ // watch for OK response to DONE
+ // XXX - what if we also added it above? should be a nop
+ toWatch.add(folder);
+ }
+ }
+ }
+
+ /**
+ * Stop watching all folders. Cancel any selection keys and,
+ * most importantly, switch the channel back to blocking mode.
+ * If there's any folders waiting to be watched, need to abort
+ * them too.
+ */
+ private void unwatchAll() {
+ IMAPFolder folder;
+ Set keys = selector.keys();
+ for (SelectionKey sk : keys) {
+ // have to cancel so we can switch back to blocking I/O mode
+ sk.cancel();
+ folder = (IMAPFolder)sk.attachment();
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager no longer watching folder: {0}",
+ folderName(folder));
+ SelectableChannel sc = sk.channel();
+ // switch back to blocking to allow normal I/O
+ try {
+ sc.configureBlocking(true);
+ folder.idleAbortWait(); // send the DONE message and wait
+ } catch (IOException ex) {
+ // ignore it, channel might be closed
+ logger.log(Level.FINEST,
+ "IdleManager exception while aborting idle for folder: " +
+ folderName(folder), ex);
+ }
+ }
+
+ /*
+ * Finally, process any folders waiting to be watched.
+ */
+ while ((folder = toWatch.poll()) != null) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST,
+ "IdleManager aborting IDLE for unwatched folder: {0}",
+ folderName(folder));
+ SocketChannel sc = folder.getChannel();
+ if (sc == null)
+ continue;
+ try {
+ // channel should still be in blocking mode, but make sure
+ sc.configureBlocking(true);
+ folder.idleAbortWait(); // send the DONE message and wait
+ } catch (IOException ex) {
+ // ignore it, channel might be closed
+ logger.log(Level.FINEST,
+ "IdleManager exception while aborting idle for folder: " +
+ folderName(folder), ex);
+ }
+ }
+ }
+
+ /**
+ * Stop the IdleManager. The IdleManager can not be restarted.
+ */
+ public synchronized void stop() {
+ die = true;
+ logger.fine("IdleManager stopping");
+ selector.wakeup();
+ }
+
+ /**
+ * Return the fully qualified name of the folder, for use in log messages.
+ * Essentially just the getURLName method, but ignoring the
+ * MessagingException that can never happen.
+ */
+ private static String folderName(Folder folder) {
+ try {
+ return folder.getURLName().toString();
+ } catch (MessagingException mex) {
+ // can't happen
+ return folder.getStore().toString() + "/" + folder.toString();
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/MessageCache.java b/app/src/main/java/com/sun/mail/imap/MessageCache.java
new file mode 100644
index 0000000000..86c7899aa1
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/MessageCache.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.PrintStream;
+import java.util.*;
+import java.util.logging.Level;
+
+import javax.mail.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * A cache of IMAPMessage objects along with the
+ * mapping from message number to IMAP sequence number.
+ *
+ * All operations on this object are protected by the messageCacheLock
+ * in IMAPFolder.
+ */
+public class MessageCache {
+ /*
+ * The array of IMAPMessage objects. Elements of the array might
+ * be null if no one has asked for the message. The array expands
+ * as needed and might be larger than the number of messages in the
+ * folder. The "size" field indicates the number of entries that
+ * are valid.
+ */
+ private IMAPMessage[] messages;
+
+ /*
+ * A parallel array of sequence numbers for each message. If the
+ * array pointer is null, the sequence number of a message is just
+ * its message number. This is the common case, until a message is
+ * expunged.
+ */
+ private int[] seqnums;
+
+ /*
+ * The amount of the messages (and seqnum) array that is valid.
+ * Might be less than the actual size of the array.
+ */
+ private int size;
+
+ /**
+ * The folder these messages belong to.
+ */
+ private IMAPFolder folder;
+
+ // debugging logger
+ private MailLogger logger;
+
+ /**
+ * Grow the array by at least this much, to avoid constantly
+ * reallocating the array.
+ */
+ private static final int SLOP = 64;
+
+ /**
+ * Construct a new message cache of the indicated size.
+ */
+ MessageCache(IMAPFolder folder, IMAPStore store, int size) {
+ this.folder = folder;
+ logger = folder.logger.getSubLogger("messagecache", "DEBUG IMAP MC",
+ store.getMessageCacheDebug());
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("create cache of size " + size);
+ ensureCapacity(size, 1);
+ }
+
+ /**
+ * Constructor for debugging and testing.
+ */
+ MessageCache(int size, boolean debug) {
+ this.folder = null;
+ logger = new MailLogger(
+ this.getClass(), "messagecache",
+ "DEBUG IMAP MC", debug, System.out);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config("create DEBUG cache of size " + size);
+ ensureCapacity(size, 1);
+ }
+
+ /**
+ * Size of cache.
+ *
+ * @return the size of the cache
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Get the message object for the indicated message number.
+ * If the message object hasn't been created, create it.
+ *
+ * @param msgnum the message number
+ * @return the message
+ */
+ public IMAPMessage getMessage(int msgnum) {
+ // check range
+ if (msgnum < 1 || msgnum > size)
+ throw new ArrayIndexOutOfBoundsException(
+ "message number (" + msgnum + ") out of bounds (" + size + ")");
+ IMAPMessage msg = messages[msgnum-1];
+ if (msg == null) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("create message number " + msgnum);
+ msg = folder.newIMAPMessage(msgnum);
+ messages[msgnum-1] = msg;
+ // mark message expunged if no seqnum
+ if (seqnumOf(msgnum) <= 0) {
+ logger.fine("it's expunged!");
+ msg.setExpunged(true);
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Get the message object for the indicated sequence number.
+ * If the message object hasn't been created, create it.
+ * Return null if there's no message with that sequence number.
+ *
+ * @param seqnum the sequence number of the message
+ * @return the message
+ */
+ public IMAPMessage getMessageBySeqnum(int seqnum) {
+ int msgnum = msgnumOf(seqnum);
+ if (msgnum < 0) { // XXX - < 1 ?
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("no message seqnum " + seqnum);
+ return null;
+ } else
+ return getMessage(msgnum);
+ }
+
+ /**
+ * Expunge the message with the given sequence number.
+ *
+ * @param seqnum the sequence number of the message to expunge
+ */
+ public void expungeMessage(int seqnum) {
+ int msgnum = msgnumOf(seqnum);
+ if (msgnum < 0) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("expunge no seqnum " + seqnum);
+ return; // XXX - should never happen
+ }
+ IMAPMessage msg = messages[msgnum-1];
+ if (msg != null) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("expunge existing " + msgnum);
+ msg.setExpunged(true);
+ }
+ if (seqnums == null) { // time to fill it in
+ logger.fine("create seqnums array");
+ seqnums = new int[messages.length];
+ for (int i = 1; i < msgnum; i++)
+ seqnums[i-1] = i;
+ seqnums[msgnum - 1] = 0;
+ for (int i = msgnum + 1; i <= seqnums.length; i++)
+ seqnums[i-1] = i - 1;
+ } else {
+ seqnums[msgnum - 1] = 0;
+ for (int i = msgnum + 1; i <= seqnums.length; i++) {
+ assert seqnums[i-1] != 1;
+ if (seqnums[i-1] > 0)
+ seqnums[i-1]--;
+ }
+ }
+ }
+
+ /**
+ * Remove all the expunged messages from the array,
+ * returning a list of removed message objects.
+ *
+ * @return the removed messages
+ */
+ public IMAPMessage[] removeExpungedMessages() {
+ logger.fine("remove expunged messages");
+ // list of expunged messages
+ List mlist = new ArrayList<>();
+
+ /*
+ * Walk through the array compressing it by copying
+ * higher numbered messages further down in the array,
+ * effectively removing expunged messages from the array.
+ * oldnum is the index we use to walk through the array.
+ * newnum is the index where we copy the next valid message.
+ * oldnum == newnum until we encounter an expunged message.
+ */
+ int oldnum = 1;
+ int newnum = 1;
+ while (oldnum <= size) {
+ // is message expunged?
+ if (seqnumOf(oldnum) <= 0) {
+ IMAPMessage m = getMessage(oldnum);
+ mlist.add(m);
+ } else {
+ // keep this message
+ if (newnum != oldnum) {
+ // move message down in the array (compact array)
+ messages[newnum-1] = messages[oldnum-1];
+ if (messages[newnum-1] != null)
+ messages[newnum-1].setMessageNumber(newnum);
+ }
+ newnum++;
+ }
+ oldnum++;
+ }
+ seqnums = null;
+ shrink(newnum, oldnum);
+
+ IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("return " + rmsgs.length);
+ mlist.toArray(rmsgs);
+ return rmsgs;
+ }
+
+ /**
+ * Remove expunged messages in msgs from the array,
+ * returning a list of removed message objects.
+ * All messages in msgs must be IMAPMessage objects
+ * from this folder.
+ *
+ * @param msgs the messages
+ * @return the removed messages
+ */
+ public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
+ logger.fine("remove expunged messages");
+ // list of expunged messages
+ List mlist = new ArrayList<>();
+
+ /*
+ * Copy the message numbers of the expunged messages into
+ * a separate array and sort the array to make it easier to
+ * process later.
+ */
+ int[] mnum = new int[msgs.length];
+ for (int i = 0; i < msgs.length; i++)
+ mnum[i] = msgs[i].getMessageNumber();
+ Arrays.sort(mnum);
+
+ /*
+ * Walk through the array compressing it by copying
+ * higher numbered messages further down in the array,
+ * effectively removing expunged messages from the array.
+ * oldnum is the index we use to walk through the array.
+ * newnum is the index where we copy the next valid message.
+ * oldnum == newnum until we encounter an expunged message.
+ *
+ * Even though we know the message number of the first possibly
+ * expunged message, we still start scanning at message number 1
+ * so that we can check whether there's any message whose
+ * sequence number is different than its message number. If there
+ * is, we can't throw away the seqnums array when we're done.
+ */
+ int oldnum = 1;
+ int newnum = 1;
+ int mnumi = 0; // index into mnum
+ boolean keepSeqnums = false;
+ while (oldnum <= size) {
+ /*
+ * Are there still expunged messsages in msgs to consider,
+ * and is the message we're considering the next one in the
+ * list, and is it expunged?
+ */
+ if (mnumi < mnum.length &&
+ oldnum == mnum[mnumi] &&
+ seqnumOf(oldnum) <= 0) {
+ IMAPMessage m = getMessage(oldnum);
+ mlist.add(m);
+ /*
+ * Just in case there are duplicate entries in the msgs array,
+ * we keep advancing mnumi past any duplicates, but of course
+ * stop when we get to the end of the array.
+ */
+ while (mnumi < mnum.length && mnum[mnumi] <= oldnum)
+ mnumi++; // consider next message in array
+ } else {
+ // keep this message
+ if (newnum != oldnum) {
+ // move message down in the array (compact array)
+ messages[newnum-1] = messages[oldnum-1];
+ if (messages[newnum-1] != null)
+ messages[newnum-1].setMessageNumber(newnum);
+ if (seqnums != null)
+ seqnums[newnum-1] = seqnums[oldnum-1];
+ }
+ if (seqnums != null && seqnums[newnum-1] != newnum)
+ keepSeqnums = true;
+ newnum++;
+ }
+ oldnum++;
+ }
+
+ if (!keepSeqnums)
+ seqnums = null;
+ shrink(newnum, oldnum);
+
+ IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("return " + rmsgs.length);
+ mlist.toArray(rmsgs);
+ return rmsgs;
+ }
+
+ /**
+ * Shrink the messages and seqnums arrays. newend is one past last
+ * valid element. oldend is one past the previous last valid element.
+ */
+ private void shrink(int newend, int oldend) {
+ size = newend - 1;
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("size now " + size);
+ if (size == 0) { // no messages left
+ messages = null;
+ seqnums = null;
+ } else if (size > SLOP && size < messages.length / 2) {
+ // if array shrinks by too much, reallocate it
+ logger.fine("reallocate array");
+ IMAPMessage[] newm = new IMAPMessage[size + SLOP];
+ System.arraycopy(messages, 0, newm, 0, size);
+ messages = newm;
+ if (seqnums != null) {
+ int[] news = new int[size + SLOP];
+ System.arraycopy(seqnums, 0, news, 0, size);
+ seqnums = news;
+ }
+ } else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("clean " + newend + " to " + oldend);
+ // clear out unused entries in array
+ for (int msgnum = newend; msgnum < oldend; msgnum++) {
+ messages[msgnum-1] = null;
+ if (seqnums != null)
+ seqnums[msgnum-1] = 0;
+ }
+ }
+ }
+
+ /**
+ * Add count messages to the cache.
+ * newSeqNum is the sequence number of the first message added.
+ *
+ * @param count the number of messges
+ * @param newSeqNum sequence number of first message
+ */
+ public void addMessages(int count, int newSeqNum) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("add " + count + " messages");
+ // don't have to do anything other than making sure there's space
+ ensureCapacity(size + count, newSeqNum);
+ }
+
+ /*
+ * Make sure the arrays are at least big enough to hold
+ * "newsize" messages.
+ */
+ private void ensureCapacity(int newsize, int newSeqNum) {
+ if (messages == null)
+ messages = new IMAPMessage[newsize + SLOP];
+ else if (messages.length < newsize) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("expand capacity to " + newsize);
+ IMAPMessage[] newm = new IMAPMessage[newsize + SLOP];
+ System.arraycopy(messages, 0, newm, 0, messages.length);
+ messages = newm;
+ if (seqnums != null) {
+ int[] news = new int[newsize + SLOP];
+ System.arraycopy(seqnums, 0, news, 0, seqnums.length);
+ for (int i = size; i < news.length; i++)
+ news[i] = newSeqNum++;
+ seqnums = news;
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("message " + newsize +
+ " has sequence number " + seqnums[newsize-1]);
+ }
+ } else if (newsize < size) { // shrinking?
+ // this should never happen
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("shrink capacity to " + newsize);
+ for (int msgnum = newsize + 1; msgnum <= size; msgnum++) {
+ messages[msgnum-1] = null;
+ if (seqnums != null)
+ seqnums[msgnum-1] = -1;
+ }
+ }
+ size = newsize;
+ }
+
+ /**
+ * Return the sequence number for the given message number.
+ *
+ * @param msgnum the message number
+ * @return the sequence number
+ */
+ public int seqnumOf(int msgnum) {
+ if (seqnums == null)
+ return msgnum;
+ else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("msgnum " + msgnum + " is seqnum " +
+ seqnums[msgnum-1]);
+ return seqnums[msgnum-1];
+ }
+ }
+
+ /**
+ * Return the message number for the given sequence number.
+ */
+ private int msgnumOf(int seqnum) {
+ if (seqnums == null)
+ return seqnum;
+ if (seqnum < 1) { // should never happen
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("bad seqnum " + seqnum);
+ return -1;
+ }
+ for (int msgnum = seqnum; msgnum <= size; msgnum++) {
+ if (seqnums[msgnum-1] == seqnum)
+ return msgnum;
+ if (seqnums[msgnum-1] > seqnum)
+ break; // message doesn't exist
+ }
+ return -1;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java b/app/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java
new file mode 100644
index 0000000000..056cabc68a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.event.MessageCountEvent;
+
+/**
+ * This class provides notification of messages that have been removed
+ * since the folder was last synchronized.
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+
+public class MessageVanishedEvent extends MessageCountEvent {
+
+ /**
+ * The message UIDs.
+ */
+ private long[] uids;
+
+ // a reusable empty array
+ private static final Message[] noMessages = { };
+
+ private static final long serialVersionUID = 2142028010250024922L;
+
+ /**
+ * Constructor.
+ *
+ * @param folder the containing folder
+ * @param uids the UIDs for the vanished messages
+ */
+ public MessageVanishedEvent(Folder folder, long[] uids) {
+ super(folder, REMOVED, true, noMessages);
+ this.uids = uids;
+ }
+
+ /**
+ * Return the UIDs for this event.
+ *
+ * @return the UIDs
+ */
+ public long[] getUIDs() {
+ return uids;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java b/app/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java
new file mode 100644
index 0000000000..7332266f25
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that have been modified since a given MODSEQ value.
+ * Relies on the server implementing the CONDSTORE extension
+ * (RFC 4551 ).
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+public final class ModifiedSinceTerm extends SearchTerm {
+
+ private long modseq;
+
+ private static final long serialVersionUID = 5151457469634727992L;
+
+ /**
+ * Constructor.
+ *
+ * @param modseq modification sequence number
+ */
+ public ModifiedSinceTerm(long modseq) {
+ this.modseq = modseq;
+ }
+
+ /**
+ * Return the modseq.
+ *
+ * @return the modseq
+ */
+ public long getModSeq() {
+ return modseq;
+ }
+
+ /**
+ * The match method.
+ *
+ * @param msg the date comparator is applied to this Message's
+ * MODSEQ
+ * @return true if the comparison succeeds, otherwise false
+ */
+ @Override
+ public boolean match(Message msg) {
+ long m;
+
+ try {
+ if (msg instanceof IMAPMessage)
+ m = ((IMAPMessage)msg).getModSeq();
+ else
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+
+ return m >= modseq;
+ }
+
+ /**
+ * Equality comparison.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModifiedSinceTerm))
+ return false;
+ return modseq == ((ModifiedSinceTerm)obj).modseq;
+ }
+
+ /**
+ * Compute a hashCode for this object.
+ */
+ @Override
+ public int hashCode() {
+ return (int)modseq;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/OlderTerm.java b/app/src/main/java/com/sun/mail/imap/OlderTerm.java
new file mode 100644
index 0000000000..19857c4dfd
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/OlderTerm.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that are older than a given interval (in seconds).
+ * Relies on the server implementing the WITHIN search extension
+ * (RFC 5032 ).
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+public final class OlderTerm extends SearchTerm {
+
+ private int interval;
+
+ private static final long serialVersionUID = 3951078948727995682L;
+
+ /**
+ * Constructor.
+ *
+ * @param interval number of seconds older
+ */
+ public OlderTerm(int interval) {
+ this.interval = interval;
+ }
+
+ /**
+ * Return the interval.
+ *
+ * @return the interval
+ */
+ public int getInterval() {
+ return interval;
+ }
+
+ /**
+ * The match method.
+ *
+ * @param msg the date comparator is applied to this Message's
+ * received date
+ * @return true if the comparison succeeds, otherwise false
+ */
+ @Override
+ public boolean match(Message msg) {
+ Date d;
+
+ try {
+ d = msg.getReceivedDate();
+ } catch (Exception e) {
+ return false;
+ }
+
+ if (d == null)
+ return false;
+
+ return d.getTime() <=
+ System.currentTimeMillis() - ((long)interval * 1000);
+ }
+
+ /**
+ * Equality comparison.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof OlderTerm))
+ return false;
+ return interval == ((OlderTerm)obj).interval;
+ }
+
+ /**
+ * Compute a hashCode for this object.
+ */
+ @Override
+ public int hashCode() {
+ return interval;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/ReferralException.java b/app/src/main/java/com/sun/mail/imap/ReferralException.java
new file mode 100644
index 0000000000..f4a335c163
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/ReferralException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.AuthenticationFailedException;
+
+/**
+ * A special kind of AuthenticationFailedException that indicates that
+ * the reason for the failure was an IMAP REFERRAL in the response code.
+ * See RFC 2221 for details.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class ReferralException extends AuthenticationFailedException {
+
+ private String url;
+ private String text;
+
+ private static final long serialVersionUID = -3414063558596287683L;
+
+ /**
+ * Constructs an ReferralException with the specified URL and text.
+ *
+ * @param text the detail message
+ * @param url the URL
+ */
+ public ReferralException(String url, String text) {
+ super("[REFERRAL " + url + "] " + text);
+ this.url = url;
+ this.text = text;
+ }
+
+ /**
+ * Return the IMAP URL in the referral.
+ *
+ * @return the IMAP URL
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Return the text sent by the server along with the referral.
+ *
+ * @return the text
+ */
+ public String getText() {
+ return text;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/ResyncData.java b/app/src/main/java/com/sun/mail/imap/ResyncData.java
new file mode 100644
index 0000000000..f5c295e426
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/ResyncData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.imap.protocol.UIDSet;
+
+/**
+ * Resynchronization data as defined by the QRESYNC extension
+ * (RFC 5162 ).
+ * An instance of ResyncData
is supplied to the
+ * {@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
+ * IMAPFolder open} method.
+ * The CONDSTORE ResyncData
instance is used to enable the
+ * CONDSTORE extension
+ * (RFC 4551 ).
+ * A ResyncData
instance with uidvalidity and modseq values
+ * is used to enable the QRESYNC extension.
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+
+public class ResyncData {
+ private long uidvalidity = -1;
+ private long modseq = -1;
+ private UIDSet[] uids = null;
+
+ /**
+ * Used to enable only the CONDSTORE extension.
+ */
+ public static final ResyncData CONDSTORE = new ResyncData(-1, -1);
+
+ /**
+ * Used to report on changes since the specified modseq.
+ * If the UIDVALIDITY of the folder has changed, no message
+ * changes will be reported. The application must check the
+ * UIDVALIDITY of the folder after open to make sure it's
+ * the expected folder.
+ *
+ * @param uidvalidity the UIDVALIDITY
+ * @param modseq the MODSEQ
+ */
+ public ResyncData(long uidvalidity, long modseq) {
+ this.uidvalidity = uidvalidity;
+ this.modseq = modseq;
+ this.uids = null;
+ }
+
+ /**
+ * Used to limit the reported message changes to those with UIDs
+ * in the specified range.
+ *
+ * @param uidvalidity the UIDVALIDITY
+ * @param modseq the MODSEQ
+ * @param uidFirst the first UID
+ * @param uidLast the last UID
+ */
+ public ResyncData(long uidvalidity, long modseq,
+ long uidFirst, long uidLast) {
+ this.uidvalidity = uidvalidity;
+ this.modseq = modseq;
+ this.uids = new UIDSet[] { new UIDSet(uidFirst, uidLast) };
+ }
+
+ /**
+ * Used to limit the reported message changes to those with the
+ * specified UIDs.
+ *
+ * @param uidvalidity the UIDVALIDITY
+ * @param modseq the MODSEQ
+ * @param uids the UID values
+ */
+ public ResyncData(long uidvalidity, long modseq, long[] uids) {
+ this.uidvalidity = uidvalidity;
+ this.modseq = modseq;
+ this.uids = UIDSet.createUIDSets(uids);
+ }
+
+ /**
+ * Get the UIDVALIDITY value specified when this instance was created.
+ *
+ * @return the UIDVALIDITY value
+ */
+ public long getUIDValidity() {
+ return uidvalidity;
+ }
+
+ /**
+ * Get the MODSEQ value specified when this instance was created.
+ *
+ * @return the MODSEQ value
+ */
+ public long getModSeq() {
+ return modseq;
+ }
+
+ /*
+ * Package private. IMAPProtocol gets this data indirectly
+ * using Utility.getResyncUIDSet().
+ */
+ UIDSet[] getUIDSet() {
+ return uids;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/Rights.java b/app/src/main/java/com/sun/mail/imap/Rights.java
new file mode 100644
index 0000000000..d9c4ce0298
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/Rights.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.*;
+
+/**
+ * The Rights class represents the set of rights for an authentication
+ * identifier (for instance, a user or a group).
+ *
+ * A right is represented by the Rights.Right
+ * inner class.
+ *
+ * A set of standard rights are predefined (see RFC 2086). Most folder
+ * implementations are expected to support these rights. Some
+ * implementations may also support site-defined rights.
+ *
+ * The following code sample illustrates how to examine your
+ * rights for a folder.
+ *
+ *
+ * Rights rights = folder.myRights();
+ *
+ * // Check if I can write this folder
+ * if (rights.contains(Rights.Right.WRITE))
+ * System.out.println("Can write folder");
+ *
+ * // Now give Joe all my rights, except the ability to write the folder
+ * rights.remove(Rights.Right.WRITE);
+ * ACL acl = new ACL("joe", rights);
+ * folder.setACL(acl);
+ *
+ *
+ *
+ * @author Bill Shannon
+ */
+
+public class Rights implements Cloneable {
+
+ private boolean[] rights = new boolean[128]; // XXX
+
+ /**
+ * This inner class represents an individual right. A set
+ * of standard rights objects are predefined here.
+ */
+ public static final class Right {
+ private static Right[] cache = new Right[128];
+
+ // XXX - initialization order?
+ /**
+ * Lookup - mailbox is visible to LIST/LSUB commands.
+ */
+ public static final Right LOOKUP = getInstance('l');
+
+ /**
+ * Read - SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
+ * SEARCH, COPY from mailbox
+ */
+ public static final Right READ = getInstance('r');
+
+ /**
+ * Keep seen/unseen information across sessions - STORE \SEEN flag.
+ */
+ public static final Right KEEP_SEEN = getInstance('s');
+
+ /**
+ * Write - STORE flags other than \SEEN and \DELETED.
+ */
+ public static final Right WRITE = getInstance('w');
+
+ /**
+ * Insert - perform APPEND, COPY into mailbox.
+ */
+ public static final Right INSERT = getInstance('i');
+
+ /**
+ * Post - send mail to submission address for mailbox,
+ * not enforced by IMAP4 itself.
+ */
+ public static final Right POST = getInstance('p');
+
+ /**
+ * Create - CREATE new sub-mailboxes in any implementation-defined
+ * hierarchy, RENAME or DELETE mailbox.
+ */
+ public static final Right CREATE = getInstance('c');
+
+ /**
+ * Delete - STORE \DELETED flag, perform EXPUNGE.
+ */
+ public static final Right DELETE = getInstance('d');
+
+ /**
+ * Administer - perform SETACL.
+ */
+ public static final Right ADMINISTER = getInstance('a');
+
+ char right; // the right represented by this Right object
+
+ /**
+ * Private constructor used only by getInstance.
+ */
+ private Right(char right) {
+ if ((int)right >= 128)
+ throw new IllegalArgumentException("Right must be ASCII");
+ this.right = right;
+ }
+
+ /**
+ * Get a Right object representing the specified character.
+ * Characters are assigned per RFC 2086.
+ *
+ * @param right the character representing the right
+ * @return the Right object
+ */
+ public static synchronized Right getInstance(char right) {
+ if ((int)right >= 128)
+ throw new IllegalArgumentException("Right must be ASCII");
+ if (cache[(int)right] == null)
+ cache[(int)right] = new Right(right);
+ return cache[(int)right];
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(right);
+ }
+ }
+
+
+ /**
+ * Construct an empty Rights object.
+ */
+ public Rights() { }
+
+ /**
+ * Construct a Rights object initialized with the given rights.
+ *
+ * @param rights the rights for initialization
+ */
+ public Rights(Rights rights) {
+ System.arraycopy(rights.rights, 0, this.rights, 0, this.rights.length);
+ }
+
+ /**
+ * Construct a Rights object initialized with the given rights.
+ *
+ * @param rights the rights for initialization
+ */
+ public Rights(String rights) {
+ for (int i = 0; i < rights.length(); i++)
+ add(Right.getInstance(rights.charAt(i)));
+ }
+
+ /**
+ * Construct a Rights object initialized with the given right.
+ *
+ * @param right the right for initialization
+ */
+ public Rights(Right right) {
+ this.rights[(int)right.right] = true;
+ }
+
+ /**
+ * Add the specified right to this Rights object.
+ *
+ * @param right the right to add
+ */
+ public void add(Right right) {
+ this.rights[(int)right.right] = true;
+ }
+
+ /**
+ * Add all the rights in the given Rights object to this
+ * Rights object.
+ *
+ * @param rights Rights object
+ */
+ public void add(Rights rights) {
+ for (int i = 0; i < rights.rights.length; i++)
+ if (rights.rights[i])
+ this.rights[i] = true;
+ }
+
+ /**
+ * Remove the specified right from this Rights object.
+ *
+ * @param right the right to be removed
+ */
+ public void remove(Right right) {
+ this.rights[(int)right.right] = false;
+ }
+
+ /**
+ * Remove all rights in the given Rights object from this
+ * Rights object.
+ *
+ * @param rights the rights to be removed
+ */
+ public void remove(Rights rights) {
+ for (int i = 0; i < rights.rights.length; i++)
+ if (rights.rights[i])
+ this.rights[i] = false;
+ }
+
+ /**
+ * Check whether the specified right is present in this Rights object.
+ *
+ * @param right the Right to check
+ * @return true of the given right is present, otherwise false.
+ */
+ public boolean contains(Right right) {
+ return this.rights[(int)right.right];
+ }
+
+ /**
+ * Check whether all the rights in the specified Rights object are
+ * present in this Rights object.
+ *
+ * @param rights the Rights to check
+ * @return true if all rights in the given Rights object are present,
+ * otherwise false.
+ */
+ public boolean contains(Rights rights) {
+ for (int i = 0; i < rights.rights.length; i++)
+ if (rights.rights[i] && !this.rights[i])
+ return false;
+
+ // If we've made it till here, return true
+ return true;
+ }
+
+ /**
+ * Check whether the two Rights objects are equal.
+ *
+ * @return true if they're equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Rights))
+ return false;
+
+ Rights rights = (Rights)obj;
+
+ for (int i = 0; i < rights.rights.length; i++)
+ if (rights.rights[i] != this.rights[i])
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Compute a hash code for this Rights object.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < this.rights.length; i++)
+ if (this.rights[i])
+ hash++;
+ return hash;
+ }
+
+ /**
+ * Return all the rights in this Rights object. Returns
+ * an array of size zero if no rights are set.
+ *
+ * @return array of Rights.Right objects representing rights
+ */
+ public Right[] getRights() {
+ List v = new ArrayList<>();
+ for (int i = 0; i < this.rights.length; i++)
+ if (this.rights[i])
+ v.add(Right.getInstance((char)i));
+ return v.toArray(new Right[v.size()]);
+ }
+
+ /**
+ * Returns a clone of this Rights object.
+ */
+ @Override
+ public Object clone() {
+ Rights r = null;
+ try {
+ r = (Rights)super.clone();
+ r.rights = new boolean[128];
+ System.arraycopy(this.rights, 0, r.rights, 0, this.rights.length);
+ } catch (CloneNotSupportedException cex) {
+ // ignore, can't happen
+ }
+ return r;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < this.rights.length; i++)
+ if (this.rights[i])
+ sb.append((char)i);
+ return sb.toString();
+ }
+
+ /*****
+ public static void main(String argv[]) throws Exception {
+ // a new rights object
+ Rights f1 = new Rights();
+ f1.add(Rights.Right.READ);
+ f1.add(Rights.Right.WRITE);
+ f1.add(Rights.Right.CREATE);
+ f1.add(Rights.Right.DELETE);
+
+ // check copy constructor
+ Rights fc = new Rights(f1);
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check clone
+ fc = (Rights)f1.clone();
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // add a right and make sure it still works right
+ f1.add(Rights.Right.ADMINISTER);
+
+ // shouldn't be equal here
+ if (!f1.equals(fc) && !fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check clone
+ fc = (Rights)f1.clone();
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ fc.add(Rights.Right.INSERT);
+ if (!f1.equals(fc) && !fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check copy constructor
+ fc = new Rights(f1);
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // another new rights object
+ Rights f2 = new Rights(Rights.Right.READ);
+ f2.add(Rights.Right.WRITE);
+
+ if (f1.contains(Rights.Right.READ))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains(Rights.Right.WRITE))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains(Rights.Right.CREATE))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains(Rights.Right.DELETE))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f2.contains(Rights.Right.WRITE))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+
+ System.out.println("----------------");
+
+ Right[] r = f1.getRights();
+ for (int i = 0; i < r.length; i++)
+ System.out.println(r[i]);
+ System.out.println("----------------");
+
+ if (f1.contains(f2)) // this should be true
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (!f2.contains(f1)) // this should be false
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ Rights f3 = new Rights();
+ f3.add(Rights.Right.READ);
+ f3.add(Rights.Right.WRITE);
+ f3.add(Rights.Right.CREATE);
+ f3.add(Rights.Right.DELETE);
+ f3.add(Rights.Right.ADMINISTER);
+ f3.add(Rights.Right.LOOKUP);
+
+ f1.add(Rights.Right.LOOKUP);
+
+ if (f1.equals(f3))
+ System.out.println("equals success");
+ else
+ System.out.println("fail");
+ if (f3.equals(f1))
+ System.out.println("equals success");
+ else
+ System.out.println("fail");
+ System.out.println("f1 hash code " + f1.hashCode());
+ System.out.println("f3 hash code " + f3.hashCode());
+ if (f1.hashCode() == f3.hashCode())
+ System.out.println("success");
+ else
+ System.out.println("fail");
+ }
+ ****/
+}
diff --git a/app/src/main/java/com/sun/mail/imap/SortTerm.java b/app/src/main/java/com/sun/mail/imap/SortTerm.java
new file mode 100644
index 0000000000..6f624e8434
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/SortTerm.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+/**
+ * A particular sort criteria, as defined by
+ * RFC 5256 .
+ * Sort criteria are used with the
+ * {@link IMAPFolder#getSortedMessages getSortedMessages} method.
+ * Multiple sort criteria are specified in an array with the order in
+ * the array specifying the order in which the sort criteria are applied.
+ *
+ * @since JavaMail 1.4.4
+ */
+public final class SortTerm {
+ /**
+ * Sort by message arrival date and time.
+ */
+ public static final SortTerm ARRIVAL = new SortTerm("ARRIVAL");
+
+ /**
+ * Sort by email address of first Cc recipient.
+ */
+ public static final SortTerm CC = new SortTerm("CC");
+
+ /**
+ * Sort by sent date and time.
+ */
+ public static final SortTerm DATE = new SortTerm("DATE");
+
+ /**
+ * Sort by first From email address.
+ */
+ public static final SortTerm FROM = new SortTerm("FROM");
+
+ /**
+ * Reverse the sort order of the following item.
+ */
+ public static final SortTerm REVERSE = new SortTerm("REVERSE");
+
+ /**
+ * Sort by the message size.
+ */
+ public static final SortTerm SIZE = new SortTerm("SIZE");
+
+ /**
+ * Sort by the base subject text. Note that the "base subject"
+ * is defined by RFC 5256 and doesn't include items such as "Re:"
+ * in the subject header.
+ */
+ public static final SortTerm SUBJECT = new SortTerm("SUBJECT");
+
+ /**
+ * Sort by email address of first To recipient.
+ */
+ public static final SortTerm TO = new SortTerm("TO");
+
+ private String term;
+ private SortTerm(String term) {
+ this.term = term;
+ }
+
+ @Override
+ public String toString() {
+ return term;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/Utility.java b/app/src/main/java/com/sun/mail/imap/Utility.java
new file mode 100644
index 0000000000..43f19983e3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/Utility.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.mail.*;
+
+import com.sun.mail.imap.protocol.MessageSet;
+import com.sun.mail.imap.protocol.UIDSet;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holder for some static utility methods.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public final class Utility {
+
+ // Cannot be initialized
+ private Utility() { }
+
+ /**
+ * Run thru the given array of messages, apply the given
+ * Condition on each message and generate sets of contiguous
+ * sequence-numbers for the successful messages. If a message
+ * in the given array is found to be expunged, it is ignored.
+ *
+ * ASSERT: Since this method uses and returns message sequence
+ * numbers, you should use this method only when holding the
+ * messageCacheLock.
+ *
+ * @param msgs the messages
+ * @param cond the condition to check
+ * @return the MessageSet array
+ */
+ public static MessageSet[] toMessageSet(Message[] msgs, Condition cond) {
+ List v = new ArrayList<>(1);
+ int current, next;
+
+ IMAPMessage msg;
+ for (int i = 0; i < msgs.length; i++) {
+ msg = (IMAPMessage)msgs[i];
+ if (msg.isExpunged()) // expunged message, skip it
+ continue;
+
+ current = msg.getSequenceNumber();
+ // Apply the condition. If it fails, skip it.
+ if ((cond != null) && !cond.test(msg))
+ continue;
+
+ MessageSet set = new MessageSet();
+ set.start = current;
+
+ // Look for contiguous sequence numbers
+ for (++i; i < msgs.length; i++) {
+ // get next message
+ msg = (IMAPMessage)msgs[i];
+
+ if (msg.isExpunged()) // expunged message, skip it
+ continue;
+ next = msg.getSequenceNumber();
+
+ // Does this message match our condition ?
+ if ((cond != null) && !cond.test(msg))
+ continue;
+
+ if (next == current+1)
+ current = next;
+ else { // break in sequence
+ // We need to reexamine this message at the top of
+ // the outer loop, so decrement 'i' to cancel the
+ // outer loop's autoincrement
+ i--;
+ break;
+ }
+ }
+ set.end = current;
+ v.add(set);
+ }
+
+ if (v.isEmpty()) // No valid messages
+ return null;
+ else {
+ return v.toArray(new MessageSet[v.size()]);
+ }
+ }
+ /**
+ * Sort (a copy of) the given array of messages and then
+ * run thru the sorted array of messages, apply the given
+ * Condition on each message and generate sets of contiguous
+ * sequence-numbers for the successful messages. If a message
+ * in the given array is found to be expunged, it is ignored.
+ *
+ * ASSERT: Since this method uses and returns message sequence
+ * numbers, you should use this method only when holding the
+ * messageCacheLock.
+ *
+ * @param msgs the messages
+ * @param cond the condition to check
+ * @return the MessageSet array
+ * @since JavaMail 1.5.4
+ */
+ public static MessageSet[] toMessageSetSorted(Message[] msgs,
+ Condition cond) {
+ /*
+ * XXX - This is quick and dirty. A more efficient strategy would be
+ * to generate an array of message numbers by applying the condition
+ * (with zero indicating the message doesn't satisfy the condition),
+ * sort it, and then convert it to a MessageSet skipping all the zeroes.
+ */
+ msgs = msgs.clone();
+ Arrays.sort(msgs,
+ new Comparator() {
+ @Override
+ public int compare(Message msg1, Message msg2) {
+ return msg1.getMessageNumber() - msg2.getMessageNumber();
+ }
+ });
+ return toMessageSet(msgs, cond);
+ }
+
+ /**
+ * Return UIDSets for the messages. Note that the UIDs
+ * must have already been fetched for the messages.
+ *
+ * @param msgs the messages
+ * @return the UIDSet array
+ */
+ public static UIDSet[] toUIDSet(Message[] msgs) {
+ List v = new ArrayList<>(1);
+ long current, next;
+
+ IMAPMessage msg;
+ for (int i = 0; i < msgs.length; i++) {
+ msg = (IMAPMessage)msgs[i];
+ if (msg.isExpunged()) // expunged message, skip it
+ continue;
+
+ current = msg.getUID();
+
+ UIDSet set = new UIDSet();
+ set.start = current;
+
+ // Look for contiguous UIDs
+ for (++i; i < msgs.length; i++) {
+ // get next message
+ msg = (IMAPMessage)msgs[i];
+
+ if (msg.isExpunged()) // expunged message, skip it
+ continue;
+ next = msg.getUID();
+
+ if (next == current+1)
+ current = next;
+ else { // break in sequence
+ // We need to reexamine this message at the top of
+ // the outer loop, so decrement 'i' to cancel the
+ // outer loop's autoincrement
+ i--;
+ break;
+ }
+ }
+ set.end = current;
+ v.add(set);
+ }
+
+ if (v.isEmpty()) // No valid messages
+ return null;
+ else {
+ return v.toArray(new UIDSet[v.size()]);
+ }
+ }
+
+ /**
+ * Make the ResyncData UIDSet available to IMAPProtocol,
+ * which is in a different package. Note that this class
+ * is not included in the public javadocs, thus "hiding"
+ * this method.
+ *
+ * @param rd the ResyncData
+ * @return the UIDSet array
+ * @since JavaMail 1.5.1
+ */
+ public static UIDSet[] getResyncUIDSet(ResyncData rd) {
+ return rd.getUIDSet();
+ }
+
+ /**
+ * This interface defines the test to be executed in
+ * toMessageSet()
.
+ */
+ public static interface Condition {
+ public boolean test(IMAPMessage message);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/YoungerTerm.java b/app/src/main/java/com/sun/mail/imap/YoungerTerm.java
new file mode 100644
index 0000000000..e8264aed89
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/YoungerTerm.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that are younger than a given interval (in seconds).
+ * Relies on the server implementing the WITHIN search extension
+ * (RFC 5032 ).
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+public final class YoungerTerm extends SearchTerm {
+
+ private int interval;
+
+ private static final long serialVersionUID = 1592714210688163496L;
+
+ /**
+ * Constructor.
+ *
+ * @param interval number of seconds younger
+ */
+ public YoungerTerm(int interval) {
+ this.interval = interval;
+ }
+
+ /**
+ * Return the interval.
+ *
+ * @return the interval
+ */
+ public int getInterval() {
+ return interval;
+ }
+
+ /**
+ * The match method.
+ *
+ * @param msg the date comparator is applied to this Message's
+ * received date
+ * @return true if the comparison succeeds, otherwise false
+ */
+ @Override
+ public boolean match(Message msg) {
+ Date d;
+
+ try {
+ d = msg.getReceivedDate();
+ } catch (Exception e) {
+ return false;
+ }
+
+ if (d == null)
+ return false;
+
+ return d.getTime() >=
+ System.currentTimeMillis() - ((long)interval * 1000);
+ }
+
+ /**
+ * Equality comparison.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof YoungerTerm))
+ return false;
+ return interval == ((YoungerTerm)obj).interval;
+ }
+
+ /**
+ * Compute a hashCode for this object.
+ */
+ @Override
+ public int hashCode() {
+ return interval;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/package.html b/app/src/main/java/com/sun/mail/imap/package.html
new file mode 100644
index 0000000000..1103d26e8d
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/package.html
@@ -0,0 +1,1002 @@
+
+
+
+
+
+
+com.sun.mail.imap package
+
+
+
+
+An IMAP protocol provider for the Jakarta Mail API
+that provides access to an IMAP message store.
+Both the IMAP4 and IMAP4rev1 protocols are supported.
+Refer to
+RFC 3501
+for more information.
+The IMAP protocol provider also supports many IMAP extensions (described below).
+Note that the server needs to support these extensions (and not all servers do)
+in order to use the support in the IMAP provider.
+You can query the server for support of these extensions using the
+{@link com.sun.mail.imap.IMAPStore#hasCapability IMAPStore hasCapability}
+method using the capability name defined by the extension
+(see the appropriate RFC) after connecting to the server.
+
+UIDPLUS Support
+
+The IMAP UIDPLUS extension
+(RFC 4315 )
+is supported via the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#addMessages addMessages},
+{@link com.sun.mail.imap.IMAPFolder#appendUIDMessages appendUIDMessages}, and
+{@link com.sun.mail.imap.IMAPFolder#copyUIDMessages copyUIDMessages}.
+
+MOVE Support
+
+The IMAP MOVE extension
+(RFC 6851 )
+is supported via the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#moveMessages moveMessages} and
+{@link com.sun.mail.imap.IMAPFolder#moveUIDMessages moveUIDMessages}.
+
+SASL Support
+
+The IMAP protocol provider can use SASL
+(RFC 4422 )
+authentication mechanisms on systems that support the
+javax.security.sasl
APIs.
+The SASL-IR
+(RFC 4959 )
+capability is also supported.
+In addition to the SASL mechanisms that are built into
+the SASL implementation, users can also provide additional
+SASL mechanisms of their own design to support custom authentication
+schemes. See the
+
+Java SASL API Programming and Deployment Guide for details.
+Note that the current implementation doesn't support SASL mechanisms
+that provide their own integrity or confidentiality layer.
+
+OAuth 2.0 Support
+
+Support for OAuth 2.0 authentication via the
+
+XOAUTH2 authentication mechanism is provided either through the SASL
+support described above or as a built-in authentication mechanism in the
+IMAP provider.
+The OAuth 2.0 Access Token should be passed as the password for this mechanism.
+See
+OAuth2 Support for details.
+
+Connection Pool
+
+A connected IMAPStore maintains a pool of IMAP protocol objects for
+use in communicating with the IMAP server. The IMAPStore will create
+the initial AUTHENTICATED connection and seed the pool with this
+connection. As folders are opened and new IMAP protocol objects are
+needed, the IMAPStore will provide them from the connection pool,
+or create them if none are available. When a folder is closed,
+its IMAP protocol object is returned to the connection pool if the
+pool is not over capacity.
+
+
+A mechanism is provided for timing out idle connection pool IMAP
+protocol objects. Timed out connections are closed and removed (pruned)
+from the connection pool.
+
+
+The connected IMAPStore object may or may not maintain a separate IMAP
+protocol object that provides the store a dedicated connection to the
+IMAP server. This is provided mainly for compatibility with previous
+implementations of the IMAP protocol provider.
+
+QUOTA Support
+
+The IMAP QUOTA extension
+(RFC 2087 )
+is supported via the
+{@link javax.mail.QuotaAwareStore QuotaAwareStore} interface implemented by
+{@link com.sun.mail.imap.IMAPStore IMAPStore}, and the
+{@link com.sun.mail.imap.IMAPFolder#getQuota IMAPFolder getQuota} and
+{@link com.sun.mail.imap.IMAPFolder#setQuota IMAPFolder setQuota} methods.
+ACL Support
+
+The IMAP ACL extension
+(RFC 2086 )
+is supported via the
+{@link com.sun.mail.imap.Rights Rights} class and the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#getACL getACL},
+{@link com.sun.mail.imap.IMAPFolder#addACL addACL},
+{@link com.sun.mail.imap.IMAPFolder#removeACL removeACL},
+{@link com.sun.mail.imap.IMAPFolder#addRights addRights},
+{@link com.sun.mail.imap.IMAPFolder#removeRights removeRights},
+{@link com.sun.mail.imap.IMAPFolder#listRights listRights}, and
+{@link com.sun.mail.imap.IMAPFolder#myRights myRights}.
+
+SORT Support
+
+The IMAP SORT extension
+(RFC 5256 )
+is supported via the
+{@link com.sun.mail.imap.SortTerm SortTerm} class and the IMAPFolder
+{@link com.sun.mail.imap.IMAPFolder#getSortedMessages getSortedMessages}
+methods.
+
+CONDSTORE and QRESYNC Support
+
+Basic support is provided for the IMAP CONDSTORE
+(RFC 4551 )
+and QRESYNC
+(RFC 5162 )
+extensions for the purpose of resynchronizing a folder after offline operation.
+Of course, the server must support these extensions.
+Use of these extensions is enabled by using the new
+{@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
+IMAPFolder open} method and supplying an appropriate
+{@link com.sun.mail.imap.ResyncData ResyncData} instance.
+Using
+{@link com.sun.mail.imap.ResyncData#CONDSTORE ResyncData.CONDSTORE}
+enables the CONDSTORE extension, which allows you to discover the
+modification sequence number (modseq) of messages using the
+{@link com.sun.mail.imap.IMAPMessage#getModSeq IMAPMessage getModSeq}
+method and the
+{@link com.sun.mail.imap.IMAPFolder#getHighestModSeq
+IMAPFolder getHighestModSeq} method.
+Using a
+{@link com.sun.mail.imap.ResyncData ResyncData} instance with appropriate
+values also allows the server to report any changes in messages since the last
+resynchronization.
+The changes are reported as a list of
+{@link javax.mail.event.MailEvent MailEvent} instances.
+The special
+{@link com.sun.mail.imap.MessageVanishedEvent MessageVanishedEvent} reports on
+UIDs of messages that have been removed since the last resynchronization.
+A
+{@link javax.mail.event.MessageChangedEvent MessageChangedEvent} reports on
+changes to flags of messages.
+For example:
+
+
+ Folder folder = store.getFolder("whatever");
+ IMAPFolder ifolder = (IMAPFolder)folder;
+ List<MailEvent> events = ifolder.open(Folder.READ_WRITE,
+ new ResyncData(prevUidValidity, prevModSeq));
+ for (MailEvent ev : events) {
+ if (ev instanceOf MessageChangedEvent) {
+ // process flag changes
+ } else if (ev instanceof MessageVanishedEvent) {
+ // process messages that were removed
+ }
+ }
+
+
+See the referenced RFCs for more details on these IMAP extensions.
+
+WITHIN Search Support
+
+The IMAP WITHIN search extension
+(RFC 5032 )
+is supported via the
+{@link com.sun.mail.imap.YoungerTerm YoungerTerm} and
+{@link com.sun.mail.imap.OlderTerm OlderTerm}
+{@link javax.mail.search.SearchTerm SearchTerms}, which can be used as follows:
+
+
+ // search for messages delivered in the last day
+ Message[] msgs = folder.search(new YoungerTerm(24 * 60 * 60));
+
+LOGIN-REFERRAL Support
+
+The IMAP LOGIN-REFERRAL extension
+(RFC 2221 )
+is supported.
+If a login referral is received when connecting or when authentication fails, a
+{@link com.sun.mail.imap.ReferralException ReferralException} is thrown.
+A referral can also occur when login succeeds. By default, no exception is
+thrown in this case. To force an exception to be thrown and the authentication
+to fail, set the mail.imap.referralexception
property to "true".
+
+COMPRESS Support
+
+The IMAP COMPRESS extension
+(RFC 4978 )
+is supported.
+If the server supports the extension and the
+mail.imap.compress.enable
property is set to "true",
+compression will be enabled.
+
+UTF-8 Support
+
+The IMAP UTF8 extension
+(RFC 6855 )
+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.
+
+Properties
+
+The IMAP protocol provider supports the following properties,
+which may be set in the Jakarta Mail Session
object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted. For example, use
+
+
+ props.put("mail.imap.port", "888");
+
+
+to set the mail.imap.port
property, which is of type int.
+
+
+Note that if you're using the "imaps" protocol to access IMAP over SSL,
+all the properties would be named "mail.imaps.*".
+
+
+IMAP properties
+
+Name
+Type
+Description
+
+
+
+mail.imap.user
+String
+Default user name for IMAP.
+
+
+
+mail.imap.host
+String
+The IMAP server to connect to.
+
+
+
+mail.imap.port
+int
+The IMAP server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 143.
+
+
+
+mail.imap.partialfetch
+boolean
+Controls whether the IMAP partial-fetch capability should be used.
+Defaults to true.
+
+
+
+mail.imap.fetchsize
+int
+Partial fetch size in bytes. Defaults to 16K.
+
+
+
+mail.imap.peek
+boolean
+
+If set to true, use the IMAP PEEK option when fetching body parts,
+to avoid setting the SEEN flag on messages.
+Defaults to false.
+Can be overridden on a per-message basis by the
+{@link com.sun.mail.imap.IMAPMessage#setPeek setPeek}
+method on IMAPMessage.
+
+
+
+
+mail.imap.ignorebodystructuresize
+boolean
+The IMAP BODYSTRUCTURE response includes the exact size of each body part.
+Normally, this size is used to determine how much data to fetch for each
+body part.
+Some servers report this size incorrectly in some cases; this property can
+be set to work around such server bugs.
+If this property is set to true, this size is ignored and data is fetched
+until the server reports the end of data.
+This will result in an extra fetch if the data size is a multiple of the
+block size.
+Defaults to false.
+
+
+
+mail.imap.connectiontimeout
+int
+Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.imap.timeout
+int
+Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.imap.writetimeout
+int
+Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.
+
+
+
+mail.imap.statuscachetimeout
+int
+Timeout value in milliseconds for cache of STATUS command response.
+Default is 1000 (1 second). Zero disables cache.
+
+
+
+mail.imap.appendbuffersize
+int
+
+Maximum size of a message to buffer in memory when appending to an IMAP
+folder. If not set, or set to -1, there is no maximum and all messages
+are buffered. If set to 0, no messages are buffered. If set to (e.g.)
+8192, messages of 8K bytes or less are buffered, larger messages are
+not buffered. Buffering saves cpu time at the expense of short term
+memory usage. If you commonly append very large messages to IMAP
+mailboxes you might want to set this to a moderate value (1M or less).
+
+
+
+
+mail.imap.connectionpoolsize
+int
+Maximum number of available connections in the connection pool.
+Default is 1.
+
+
+
+mail.imap.connectionpooltimeout
+int
+Timeout value in milliseconds for connection pool connections. Default
+is 45000 (45 seconds).
+
+
+
+mail.imap.separatestoreconnection
+boolean
+Flag to indicate whether to use a dedicated store connection for store
+commands. Default is false.
+
+
+
+mail.imap.allowreadonlyselect
+boolean
+If false, attempts to open a folder read/write will fail
+if the SELECT command succeeds but indicates that the folder is READ-ONLY.
+This sometimes indicates that the folder contents can'tbe changed, but
+the flags are per-user and can be changed, such as might be the case for
+public shared folders. If true, such open attempts will succeed, allowing
+the flags to be changed. The getMode
method on the
+Folder
object will return Folder.READ_ONLY
+in this case even though the open
method specified
+Folder.READ_WRITE
. Default is false.
+
+
+
+mail.imap.auth.mechanisms
+String
+
+If set, lists the authentication mechanisms to consider, and the order
+in which to consider them. Only mechanisms supported by the server and
+supported by the current implementation will be used.
+The default is "PLAIN LOGIN NTLM"
, which includes all
+the authentication mechanisms supported by the current implementation
+except XOAUTH2.
+
+
+
+
+mail.imap.auth.login.disable
+boolean
+If true, prevents use of the non-standard AUTHENTICATE LOGIN
+command, instead using the plain LOGIN
command.
+Default is false.
+
+
+
+mail.imap.auth.plain.disable
+boolean
+If true, prevents use of the AUTHENTICATE PLAIN
command.
+Default is false.
+
+
+
+mail.imap.auth.ntlm.disable
+boolean
+If true, prevents use of the AUTHENTICATE NTLM
command.
+Default is false.
+
+
+
+mail.imap.auth.ntlm.domain
+String
+
+The NTLM authentication domain.
+
+
+
+
+mail.imap.auth.ntlm.flags
+int
+
+NTLM protocol-specific flags.
+See
+http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags for details.
+
+
+
+
+mail.imap.auth.xoauth2.disable
+boolean
+If true, prevents use of the AUTHENTICATE XOAUTH2
command.
+Because the OAuth 2.0 protocol requires a special access token instead of
+a password, this mechanism is disabled by default. Enable it by explicitly
+setting this property to "false" or by setting the "mail.imap.auth.mechanisms"
+property to "XOAUTH2".
+
+
+
+mail.imap.proxyauth.user
+String
+If the server supports the PROXYAUTH extension, this property
+specifies the name of the user to act as. Authenticate to the
+server using the administrator's credentials. After authentication,
+the IMAP provider will issue the PROXYAUTH
command with
+the user name specified in this property.
+
+
+
+
+mail.imap.localaddress
+String
+
+Local address (host name) to bind to when creating the IMAP socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+
+
+
+
+mail.imap.localport
+int
+
+Local port number to bind to when creating the IMAP socket.
+Defaults to the port number picked by the Socket class.
+
+
+
+
+mail.imap.sasl.enable
+boolean
+
+If set to true, attempt to use the javax.security.sasl package to
+choose an authentication mechanism for login.
+Defaults to false.
+
+
+
+
+mail.imap.sasl.mechanisms
+String
+
+A space or comma separated list of SASL mechanism names to try
+to use.
+
+
+
+
+mail.imap.sasl.authorizationid
+String
+
+The authorization ID to use in the SASL authentication.
+If not set, the authentication ID (user name) is used.
+
+
+
+
+mail.imap.sasl.realm
+String
+The realm to use with SASL authentication mechanisms that
+require a realm, such as DIGEST-MD5.
+
+
+
+mail.imap.sasl.usecanonicalhostname
+boolean
+
+If set to true, the canonical host name returned by
+{@link java.net.InetAddress#getCanonicalHostName InetAddress.getCanonicalHostName}
+is passed to the SASL mechanism, instead of the host name used to connect.
+Defaults to false.
+
+
+
+
+mail.imap.sasl. xgwtrustedapphack.enable
+boolean
+
+If set to true, enables a workaround for a bug in the Novell Groupwise
+XGWTRUSTEDAPP SASL mechanism, when that mechanism is being used.
+Defaults to true.
+
+
+
+
+mail.imap.socketFactory
+SocketFactory
+
+If set to a class that implements the
+javax.net.SocketFactory
interface, this class
+will be used to create IMAP sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.imap.socketFactory.class
+String
+
+If set, specifies the name of a class that implements the
+javax.net.SocketFactory
interface. This class
+will be used to create IMAP sockets.
+
+
+
+
+mail.imap.socketFactory.fallback
+boolean
+
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the java.net.Socket
class.
+Defaults to true.
+
+
+
+
+mail.imap.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.imap.usesocketchannels
+boolean
+
+If set to true, use SocketChannels instead of Sockets for connecting
+to the server. Required if using the IdleManager.
+Ignored if a socket factory is set.
+Defaults to false.
+
+
+
+
+mail.imap.ssl.enable
+boolean
+
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "imap" protocol and true for the "imaps" protocol.
+
+
+
+
+mail.imap.ssl.checkserveridentity
+boolean
+
+If set to true, check the server identity as specified by
+RFC 2595 .
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+
+
+
+
+mail.imap.ssl.trust
+String
+
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+
+
+
+
+mail.imap.ssl.socketFactory
+SSLSocketFactory
+
+If set to a class that extends the
+javax.net.ssl.SSLSocketFactory
class, this class
+will be used to create IMAP SSL sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.imap.ssl.socketFactory.class
+String
+
+If set, specifies the name of a class that extends the
+javax.net.ssl.SSLSocketFactory
class. This class
+will be used to create IMAP SSL sockets.
+
+
+
+
+mail.imap.ssl.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.imap.ssl.protocols
+string
+
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledProtocols
method.
+
+
+
+
+mail.imap.ssl.ciphersuites
+string
+
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledCipherSuites
method.
+
+
+
+
+mail.imap.starttls.enable
+boolean
+If true, enables the use of the STARTTLS
command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+mail.imap.starttls.required
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Default is false.
+
+
+
+mail.imap.starttls.required
+boolean
+
+If true, requires the use of the STARTTLS
command.
+If the server doesn't support the STARTTLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+
+
+
+
+mail.imap.proxy.host
+string
+
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.imap.proxy.port
+string
+
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+
+
+
+
+mail.imap.proxy.user
+string
+
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.imap.proxy.password
+string
+
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.imap.socks.host
+string
+
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.imap.socks.port
+string
+
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+
+
+
+
+mail.imap.minidletime
+int
+
+Applications typically call the idle method in a loop. If another
+thread termiantes the IDLE command, it needs a chance to do its
+work before another IDLE command is issued. The idle method enforces
+a delay to prevent thrashing between the IDLE command and regular
+commands. This property sets the delay in milliseconds. If not
+set, the default is 10 milliseconds.
+
+
+
+
+mail.imap.enableresponseevents
+boolean
+
+Enable special IMAP-specific events to be delivered to the Store's
+ConnectionListener
. If true, IMAP OK, NO, BAD, or BYE responses
+will be sent as ConnectionEvent
s with a type of
+IMAPStore.RESPONSE
. The event's message will be the
+raw IMAP response string.
+By default, these events are not sent.
+NOTE: This capability is highly experimental and likely will change
+in future releases.
+
+
+
+
+mail.imap.enableimapevents
+boolean
+
+Enable special IMAP-specific events to be delivered to the Store's
+ConnectionListener
. If true, unsolicited responses
+received during the Store's idle
method will be sent
+as ConnectionEvent
s with a type of
+IMAPStore.RESPONSE
. The event's message will be the
+raw IMAP response string.
+By default, these events are not sent.
+NOTE: This capability is highly experimental and likely will change
+in future releases.
+
+
+
+
+mail.imap.throwsearchexception
+boolean
+
+If set to true and a {@link javax.mail.search.SearchTerm SearchTerm}
+passed to the
+{@link javax.mail.Folder#search Folder.search}
+method is too complex for the IMAP protocol, throw a
+{@link javax.mail.search.SearchException SearchException}.
+For example, the IMAP protocol only supports less-than and greater-than
+comparisons for a {@link javax.mail.search.SizeTerm SizeTerm}.
+If false, the search will be done locally by fetching the required
+message data and comparing it locally.
+Defaults to false.
+
+
+
+
+mail.imap.folder.class
+String
+
+Class name of a subclass of com.sun.mail.imap.IMAPFolder
.
+The subclass can be used to provide support for additional IMAP commands.
+The subclass must have public constructors of the form
+public MyIMAPFolder(String fullName, char separator, IMAPStore store,
+Boolean isNamespace)
and
+public MyIMAPFolder(ListInfo li, IMAPStore store)
+
+
+
+
+mail.imap.closefoldersonstorefailure
+boolean
+
+In some cases, a failure of the Store connection indicates a failure of the
+server, and all Folders associated with that Store should also be closed.
+In other cases, a Store connection failure may be a transient failure, and
+Folders may continue to operate normally.
+If this property is true (the default), failures in the Store connection cause
+all associated Folders to be closed.
+Set this property to false to better handle transient failures in the Store
+connection.
+
+
+
+
+mail.imap.finalizecleanclose
+boolean
+
+When the finalizer for IMAPStore is called,
+should the connection to the server be closed cleanly, as if the
+application called the close method?
+Or should the connection to the server be closed without sending
+any commands to the server?
+Defaults to false, the connection is not closed cleanly.
+
+
+
+
+mail.imap.referralexception
+boolean
+
+If set to true and an IMAP login referral is returned when the authentication
+succeeds, fail the connect request and throw a
+{@link com.sun.mail.imap.ReferralException ReferralException}.
+Defaults to false.
+
+
+
+
+mail.imap.compress.enable
+boolean
+
+If set to true and the IMAP server supports the COMPRESS=DEFLATE extension,
+compression will be enabled.
+Defaults to false.
+
+
+
+
+mail.imap.compress.level
+int
+
+The compression level to be used, in the range -1 to 9.
+See the {@link java.util.zip.Deflater Deflater} class for details.
+
+
+
+
+mail.imap.compress.strategy
+int
+
+The compression strategy to be used, in the range 0 to 2.
+See the {@link java.util.zip.Deflater Deflater} class for details.
+
+
+
+
+mail.imap.reusetagprefix
+boolean
+
+If true, always use "A" for the IMAP command tag prefix.
+If false, the IMAP command tag prefix is different for each connection,
+from "A" through "ZZZ" and then wrapping around to "A".
+Applications should never need to set this.
+Defaults to false.
+
+
+
+
+
+In general, applications should not need to use the classes in this
+package directly. Instead, they should use the APIs defined by the
+javax.mail
package (and subpackages). Applications should
+never construct instances of IMAPStore
or
+IMAPFolder
directly. Instead, they should use the
+Session
method getStore
to acquire an
+appropriate Store
object, and from that acquire
+Folder
objects.
+
+Loggers
+
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.imap provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+
+
+IMAP Loggers
+
+Logger Name
+Logging Level
+Purpose
+
+
+
+com.sun.mail.imap
+CONFIG
+Configuration of the IMAPStore
+
+
+
+com.sun.mail.imap
+FINE
+General debugging output
+
+
+
+com.sun.mail.imap.connectionpool
+CONFIG
+Configuration of the IMAP connection pool
+
+
+
+com.sun.mail.imap.connectionpool
+FINE
+Debugging output related to the IMAP connection pool
+
+
+
+com.sun.mail.imap.messagecache
+CONFIG
+Configuration of the IMAP message cache
+
+
+
+com.sun.mail.imap.messagecache
+FINE
+Debugging output related to the IMAP message cache
+
+
+
+com.sun.mail.imap.protocol
+FINEST
+Complete protocol trace
+
+
+
+WARNING
+
+WARNING: The APIs unique to this package should be
+considered EXPERIMENTAL . They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+
+
+
+
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java b/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java
new file mode 100644
index 0000000000..af35020e9c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.text.StringCharacterIterator;
+import java.text.CharacterIterator;
+
+/**
+ * See the BASE64MailboxEncoder for a description of the RFC2060 and how
+ * mailbox names should be encoded. This class will do the correct decoding
+ * for mailbox names.
+ *
+ * @author Christopher Cotton
+ */
+
+public class BASE64MailboxDecoder {
+
+ public static String decode(String original) {
+ if (original == null || original.length() == 0)
+ return original;
+
+ boolean changedString = false;
+ int copyTo = 0;
+ // it will always be less than the original
+ char[] chars = new char[original.length()];
+ StringCharacterIterator iter = new StringCharacterIterator(original);
+
+ for(char c = iter.first(); c != CharacterIterator.DONE;
+ c = iter.next()) {
+
+ if (c == '&') {
+ changedString = true;
+ copyTo = base64decode(chars, copyTo, iter);
+ } else {
+ chars[copyTo++] = c;
+ }
+ }
+
+ // now create our string from the char array
+ if (changedString) {
+ return new String(chars, 0, copyTo);
+ } else {
+ return original;
+ }
+ }
+
+
+ protected static int base64decode(char[] buffer, int offset,
+ CharacterIterator iter) {
+ boolean firsttime = true;
+ int leftover = -1;
+
+ while(true) {
+ // get the first byte
+ byte orig_0 = (byte) iter.next();
+ if (orig_0 == -1) break; // no more chars
+ if (orig_0 == '-') {
+ if (firsttime) {
+ // means we got the string "&-" which is turned into a "&"
+ buffer[offset++] = '&';
+ }
+ // we are done now
+ break;
+ }
+ firsttime = false;
+
+ // next byte
+ byte orig_1 = (byte) iter.next();
+ if (orig_1 == -1 || orig_1 == '-')
+ break; // no more chars, invalid base64
+
+ byte a, b, current;
+ a = pem_convert_array[orig_0 & 0xff];
+ b = pem_convert_array[orig_1 & 0xff];
+ // The first decoded byte
+ current = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
+
+ // use the leftover to create a Unicode Character (2 bytes)
+ if (leftover != -1) {
+ buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+ leftover = -1;
+ } else {
+ leftover = current & 0xff;
+ }
+
+ byte orig_2 = (byte) iter.next();
+ if (orig_2 == '=') { // End of this BASE64 encoding
+ continue;
+ } else if (orig_2 == -1 || orig_2 == '-') {
+ break; // no more chars
+ }
+
+ // second decoded byte
+ a = b;
+ b = pem_convert_array[orig_2 & 0xff];
+ current = (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
+
+ // use the leftover to create a Unicode Character (2 bytes)
+ if (leftover != -1) {
+ buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+ leftover = -1;
+ } else {
+ leftover = current & 0xff;
+ }
+
+ byte orig_3 = (byte) iter.next();
+ if (orig_3 == '=') { // End of this BASE64 encoding
+ continue;
+ } else if (orig_3 == -1 || orig_3 == '-') {
+ break; // no more chars
+ }
+
+ // The third decoded byte
+ a = b;
+ b = pem_convert_array[orig_3 & 0xff];
+ current = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
+
+ // use the leftover to create a Unicode Character (2 bytes)
+ if (leftover != -1) {
+ buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+ leftover = -1;
+ } else {
+ leftover = current & 0xff;
+ }
+ }
+
+ return offset;
+ }
+
+ /**
+ * This character array provides the character to value map
+ * based on RFC1521, but with the modification from RFC2060
+ * which changes the '/' to a ','.
+ */
+
+ // shared with BASE64MailboxEncoder
+ static final char pem_array[] = {
+ 'A','B','C','D','E','F','G','H', // 0
+ 'I','J','K','L','M','N','O','P', // 1
+ 'Q','R','S','T','U','V','W','X', // 2
+ 'Y','Z','a','b','c','d','e','f', // 3
+ 'g','h','i','j','k','l','m','n', // 4
+ 'o','p','q','r','s','t','u','v', // 5
+ 'w','x','y','z','0','1','2','3', // 6
+ '4','5','6','7','8','9','+',',' // 7
+ };
+
+ private static final byte pem_convert_array[] = new byte[256];
+
+ static {
+ for (int i = 0; i < 255; i++)
+ pem_convert_array[i] = -1;
+ for(int i = 0; i < pem_array.length; i++)
+ pem_convert_array[pem_array[i]] = (byte) i;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java b/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java
new file mode 100644
index 0000000000..1013baf021
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+
+
+/**
+ * From RFC2060:
+ *
+ *
+ *
+ * 5.1.3. Mailbox International Naming Convention
+ *
+ * By convention, international mailbox names are specified using a
+ * modified version of the UTF-7 encoding described in [UTF-7]. The
+ * purpose of these modifications is to correct the following problems
+ * with UTF-7:
+ *
+ * 1) UTF-7 uses the "+" character for shifting; this conflicts with
+ * the common use of "+" in mailbox names, in particular USENET
+ * newsgroup names.
+ *
+ * 2) UTF-7's encoding is BASE64 which uses the "/" character; this
+ * conflicts with the use of "/" as a popular hierarchy delimiter.
+ *
+ * 3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
+ * the use of "\" as a popular hierarchy delimiter.
+ *
+ * 4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
+ * the use of "~" in some servers as a home directory indicator.
+ *
+ * 5) UTF-7 permits multiple alternate forms to represent the same
+ * string; in particular, printable US-ASCII chararacters can be
+ * represented in encoded form.
+ *
+ * In modified UTF-7, printable US-ASCII characters except for "&"
+ * represent themselves; that is, characters with octet values 0x20-0x25
+ * and 0x27-0x7e. The character "&" (0x26) is represented by the two-
+ * octet sequence "&-".
+ *
+ * All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
+ * Unicode 16-bit octets) are represented in modified BASE64, with a
+ * further modification from [UTF-7] that "," is used instead of "/".
+ * Modified BASE64 MUST NOT be used to represent any printing US-ASCII
+ * character which can represent itself.
+ *
+ * "&" is used to shift to modified BASE64 and "-" to shift back to US-
+ * ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
+ * is, a name that ends with a Unicode 16-bit octet MUST end with a "-
+ * ").
+ *
+ * For example, here is a mailbox name which mixes English, Japanese,
+ * and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
+ *
+ *
+ *
+ * This class will do the correct Encoding for the IMAP mailboxes.
+ *
+ * @author Christopher Cotton
+ */
+
+public class BASE64MailboxEncoder {
+ protected byte[] buffer = new byte[4];
+ protected int bufsize = 0;
+ protected boolean started = false;
+ protected Writer out = null;
+
+
+ public static String encode(String original) {
+ BASE64MailboxEncoder base64stream = null;
+ char origchars[] = original.toCharArray();
+ int length = origchars.length;
+ boolean changedString = false;
+ CharArrayWriter writer = new CharArrayWriter(length);
+
+ // loop over all the chars
+ for(int index = 0; index < length; index++) {
+ char current = origchars[index];
+
+ // octets in the range 0x20-0x25,0x27-0x7e are themselves
+ // 0x26 "&" is represented as "&-"
+ if (current >= 0x20 && current <= 0x7e) {
+ if (base64stream != null) {
+ base64stream.flush();
+ }
+
+ if (current == '&') {
+ changedString = true;
+ writer.write('&');
+ writer.write('-');
+ } else {
+ writer.write(current);
+ }
+ } else {
+
+ // use a B64MailboxEncoder to write out the other bytes
+ // as a modified BASE64. The stream will write out
+ // the beginning '&' and the ending '-' which is part
+ // of every encoding.
+
+ if (base64stream == null) {
+ base64stream = new BASE64MailboxEncoder(writer);
+ changedString = true;
+ }
+
+ base64stream.write(current);
+ }
+ }
+
+
+ if (base64stream != null) {
+ base64stream.flush();
+ }
+
+ if (changedString) {
+ return writer.toString();
+ } else {
+ return original;
+ }
+ }
+
+
+ /**
+ * Create a BASE64 encoder
+ *
+ * @param what where to write the encoded name
+ */
+ public BASE64MailboxEncoder(Writer what) {
+ out = what;
+ }
+
+ public void write(int c) {
+ try {
+ // write out the initial character if this is the first time
+ if (!started) {
+ started = true;
+ out.write('&');
+ }
+
+ // we write each character as a 2 byte unicode character
+ buffer[bufsize++] = (byte) (c >> 8);
+ buffer[bufsize++] = (byte) (c & 0xff);
+
+ if (bufsize >= 3) {
+ encode();
+ bufsize -= 3;
+ }
+ } catch (IOException e) {
+ //e.printStackTrace();
+ }
+ }
+
+
+ public void flush() {
+ try {
+ // flush any bytes we have
+ if (bufsize > 0) {
+ encode();
+ bufsize = 0;
+ }
+
+ // write the terminating character of the encoding
+ if (started) {
+ out.write('-');
+ started = false;
+ }
+ } catch (IOException e) {
+ //e.printStackTrace();
+ }
+ }
+
+
+ protected void encode() throws IOException {
+ byte a, b, c;
+ if (bufsize == 1) {
+ a = buffer[0];
+ b = 0;
+ c = 0;
+ out.write(pem_array[(a >>> 2) & 0x3F]);
+ out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ // no padding characters are written
+ } else if (bufsize == 2) {
+ a = buffer[0];
+ b = buffer[1];
+ c = 0;
+ out.write(pem_array[(a >>> 2) & 0x3F]);
+ out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+ // no padding characters are written
+ } else {
+ a = buffer[0];
+ b = buffer[1];
+ c = buffer[2];
+ out.write(pem_array[(a >>> 2) & 0x3F]);
+ out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+ out.write(pem_array[c & 0x3F]);
+
+ // copy back the extra byte
+ if (bufsize == 4)
+ buffer[0] = buffer[3];
+ }
+ }
+
+ private final static char pem_array[] = {
+ 'A','B','C','D','E','F','G','H', // 0
+ 'I','J','K','L','M','N','O','P', // 1
+ 'Q','R','S','T','U','V','W','X', // 2
+ 'Y','Z','a','b','c','d','e','f', // 3
+ 'g','h','i','j','k','l','m','n', // 4
+ 'o','p','q','r','s','t','u','v', // 5
+ 'w','x','y','z','0','1','2','3', // 6
+ '4','5','6','7','8','9','+',',' // 7
+ };
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/BODY.java b/app/src/main/java/com/sun/mail/imap/protocol/BODY.java
new file mode 100644
index 0000000000..5f04edc2c5
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/BODY.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.ByteArrayInputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * The BODY fetch response item.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BODY implements Item {
+
+ static final char[] name = {'B','O','D','Y'};
+
+ private final int msgno;
+ private final ByteArray data;
+ private final String section;
+ private final int origin;
+ private final boolean isHeader;
+
+ /**
+ * Constructor
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public BODY(FetchResponse r) throws ParsingException {
+ msgno = r.getNumber();
+
+ r.skipSpaces();
+
+ if (r.readByte() != '[')
+ throw new ParsingException(
+ "BODY parse error: missing ``['' at section start");
+ section = r.readString(']');
+ if (r.readByte() != ']')
+ throw new ParsingException(
+ "BODY parse error: missing ``]'' at section end");
+ isHeader = section.regionMatches(true, 0, "HEADER", 0, 6);
+
+ if (r.readByte() == '<') { // origin
+ origin = r.readNumber();
+ r.skip(1); // skip '>';
+ } else
+ origin = -1;
+
+ data = r.readByteArray();
+ }
+
+ public ByteArray getByteArray() {
+ return data;
+ }
+
+ public ByteArrayInputStream getByteArrayInputStream() {
+ if (data != null)
+ return data.toByteArrayInputStream();
+ else
+ return null;
+ }
+
+ public boolean isHeader() {
+ return isHeader;
+ }
+
+ public String getSection() {
+ return section;
+ }
+
+ /**
+ * @since Jakarta Mail 1.6.4
+ */
+ public int getOrigin() {
+ return origin;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java b/app/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java
new file mode 100644
index 0000000000..3a5945cc0e
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import javax.mail.internet.ParameterList;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * A BODYSTRUCTURE response.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BODYSTRUCTURE implements Item {
+
+ static final char[] name =
+ {'B','O','D','Y','S','T','R','U','C','T','U','R','E'};
+ public int msgno;
+
+ public String type; // Type
+ public String subtype; // Subtype
+ public String encoding; // Encoding
+ public int lines = -1; // Size in lines
+ public int size = -1; // Size in bytes
+ public String disposition; // Disposition
+ public String id; // Content-ID
+ public String description; // Content-Description
+ public String md5; // MD-5 checksum
+ public String attachment; // Attachment name
+ public ParameterList cParams; // Body parameters
+ public ParameterList dParams; // Disposition parameters
+ public String[] language; // Language
+ public BODYSTRUCTURE[] bodies; // array of BODYSTRUCTURE objects
+ // for multipart & message/rfc822
+ public ENVELOPE envelope; // for message/rfc822
+
+ private static int SINGLE = 1;
+ private static int MULTI = 2;
+ private static int NESTED = 3;
+ private int processedType; // MULTI | SINGLE | NESTED
+
+ // special debugging output to debug parsing errors
+ private static final boolean parseDebug =
+ PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
+
+
+ public BODYSTRUCTURE(FetchResponse r) throws ParsingException {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parsing BODYSTRUCTURE");
+ msgno = r.getNumber();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: msgno " + msgno);
+
+ r.skipSpaces();
+
+ if (r.readByte() != '(')
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: missing ``('' at start");
+
+ if (r.peekByte() == '(') { // multipart
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parsing multipart");
+ type = "multipart";
+ processedType = MULTI;
+ List v = new ArrayList<>(1);
+ int i = 1;
+ do {
+ v.add(new BODYSTRUCTURE(r));
+ /*
+ * Even though the IMAP spec says there can't be any spaces
+ * between parts, some servers erroneously put a space in
+ * here. In the spirit of "be liberal in what you accept",
+ * we skip it.
+ */
+ r.skipSpaces();
+ } while (r.peekByte() == '(');
+
+ // setup bodies.
+ bodies = v.toArray(new BODYSTRUCTURE[v.size()]);
+
+ subtype = r.readString(); // subtype
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: subtype " + subtype);
+
+ if (r.isNextNonSpace(')')) { // done
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parse DONE");
+ return;
+ }
+
+ // Else, we have extension data
+
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parsing extension data");
+ // Body parameters
+ cParams = parseParameters(r);
+ if (r.isNextNonSpace(')')) { // done
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: body parameters DONE");
+ return;
+ }
+
+ // Disposition
+ byte b = r.peekByte();
+ if (b == '(') {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parse disposition");
+ r.readByte();
+ disposition = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition " +
+ disposition);
+ dParams = parseParameters(r);
+ if (!r.isNextNonSpace(')')) // eat the end ')'
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: " +
+ "missing ``)'' at end of disposition in multipart");
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition DONE");
+ } else if (b == 'N' || b == 'n') {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition NIL");
+ r.skip(3); // skip 'NIL'
+ } else {
+ /*
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: " +
+ type + "/" + subtype + ": " +
+ "bad multipart disposition, b " + b);
+ */
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: bad multipart disposition" +
+ ", applying Exchange bug workaround");
+ description = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: multipart description " +
+ description);
+ // Throw away whatever comes after it, since we have no
+ // idea what it's supposed to be
+ while (r.readByte() == ' ')
+ parseBodyExtension(r);
+ return;
+ }
+
+ // RFC3501 allows no body-fld-lang after body-fld-disp,
+ // even though RFC2060 required it
+ if (r.isNextNonSpace(')')) {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: no body-fld-lang");
+ return; // done
+ }
+
+ // Language
+ if (r.peekByte() == '(') { // a list follows
+ language = r.readStringList();
+ if (parseDebug)
+ System.out.println(
+ "DEBUG IMAP: language len " + language.length);
+ } else {
+ String l = r.readString();
+ if (l != null) {
+ String[] la = { l };
+ language = la;
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: language " + l);
+ }
+ }
+
+ // RFC3501 defines an optional "body location" next,
+ // but for now we ignore it along with other extensions.
+
+ // Throw away any further extension data
+ while (r.readByte() == ' ')
+ parseBodyExtension(r);
+ } else if (r.peekByte() == ')') { // (illegal) empty body
+ /*
+ * Domino will fail to return the body structure of nested messages.
+ * Fake it by providing an empty message. Could probably do better
+ * with more work...
+ */
+ /*
+ * XXX - this prevents the exception, but without the exception
+ * the application has no way to know the data from the message
+ * is missing.
+ *
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: empty body, fake it");
+ r.readByte();
+ type = "text";
+ subtype = "plain";
+ lines = 0;
+ size = 0;
+ */
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: missing body content");
+ } else { // Single part
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: single part");
+ type = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: type " + type);
+ processedType = SINGLE;
+ subtype = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: subtype " + subtype);
+
+ // SIMS 4.0 returns NIL for a Content-Type of "binary", fix it here
+ if (type == null) {
+ type = "application";
+ subtype = "octet-stream";
+ }
+ cParams = parseParameters(r);
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: cParams " + cParams);
+ id = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: id " + id);
+ description = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: description " + description);
+ /*
+ * XXX - Work around bug in Exchange 2010 that
+ * returns unquoted string.
+ */
+ encoding = r.readAtomString();
+ if (encoding != null && encoding.equalsIgnoreCase("NIL")) {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: NIL encoding" +
+ ", applying Exchange bug workaround");
+ encoding = null;
+ }
+ /*
+ * XXX - Work around bug in office365.com that returns
+ * a string with a trailing space in some cases.
+ */
+ if (encoding != null)
+ encoding = encoding.trim();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: encoding " + encoding);
+ size = r.readNumber();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: size " + size);
+ if (size < 0)
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: bad ``size'' element");
+
+ // "text/*" & "message/rfc822" types have additional data ..
+ if (type.equalsIgnoreCase("text")) {
+ lines = r.readNumber();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: lines " + lines);
+ if (lines < 0)
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: bad ``lines'' element");
+ } else if (type.equalsIgnoreCase("message") &&
+ subtype.equalsIgnoreCase("rfc822")) {
+ // Nested message
+ processedType = NESTED;
+ // The envelope comes next, but sadly Gmail handles nested
+ // messages just like simple body parts and fails to return
+ // the envelope and body structure of the message (sort of
+ // like IMAP4 before rev1).
+ r.skipSpaces();
+ if (r.peekByte() == '(') { // the envelope follows
+ envelope = new ENVELOPE(r);
+ if (parseDebug)
+ System.out.println(
+ "DEBUG IMAP: got envelope of nested message");
+ BODYSTRUCTURE[] bs = { new BODYSTRUCTURE(r) };
+ bodies = bs;
+ lines = r.readNumber();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: lines " + lines);
+ if (lines < 0)
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: bad ``lines'' element");
+ } else {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: " +
+ "missing envelope and body of nested message");
+ }
+ } else {
+ // Detect common error of including lines element on other types
+ r.skipSpaces();
+ byte bn = r.peekByte();
+ if (Character.isDigit((char)bn)) // number
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: server erroneously " +
+ "included ``lines'' element with type " +
+ type + "/" + subtype);
+ }
+
+ if (r.isNextNonSpace(')')) {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parse DONE");
+ return; // done
+ }
+
+ // Optional extension data
+
+ // MD5
+ md5 = r.readString();
+ if (r.isNextNonSpace(')')) {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: no MD5 DONE");
+ return; // done
+ }
+
+ // Disposition
+ byte b = r.readByte();
+ if (b == '(') {
+ disposition = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition " +
+ disposition);
+ dParams = parseParameters(r);
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: dParams " + dParams);
+ if (!r.isNextNonSpace(')')) // eat the end ')'
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: " +
+ "missing ``)'' at end of disposition");
+ } else if (b == 'N' || b == 'n') {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition NIL");
+ r.skip(2); // skip 'NIL'
+ } else {
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: " +
+ type + "/" + subtype + ": " +
+ "bad single part disposition, b " + b);
+ }
+
+ if (r.isNextNonSpace(')')) {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: disposition DONE");
+ return; // done
+ }
+
+ // Language
+ if (r.peekByte() == '(') { // a list follows
+ language = r.readStringList();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: language len " +
+ language.length);
+ } else { // protocol is unnessarily complex here
+ String l = r.readString();
+ if (l != null) {
+ String[] la = { l };
+ language = la;
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: language " + l);
+ }
+ }
+
+ // RFC3501 defines an optional "body location" next,
+ // but for now we ignore it along with other extensions.
+
+ // Throw away any further extension data
+ while (r.readByte() == ' ')
+ parseBodyExtension(r);
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: all DONE");
+ }
+ }
+
+ public boolean isMulti() {
+ return processedType == MULTI;
+ }
+
+ public boolean isSingle() {
+ return processedType == SINGLE;
+ }
+
+ public boolean isNested() {
+ return processedType == NESTED;
+ }
+
+ private ParameterList parseParameters(Response r)
+ throws ParsingException {
+ r.skipSpaces();
+
+ ParameterList list = null;
+ byte b = r.readByte();
+ if (b == '(') {
+ list = new ParameterList();
+ do {
+ String name = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parameter name " + name);
+ if (name == null)
+ throw new ParsingException(
+ "BODYSTRUCTURE parse error: " +
+ type + "/" + subtype + ": " +
+ "null name in parameter list");
+ String value = r.readString();
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parameter value " + value);
+ if (value == null) { // work around buggy servers
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: NIL parameter value" +
+ ", applying Exchange bug workaround");
+ value = "";
+ }
+ list.set(name, value);
+ } while (!r.isNextNonSpace(')'));
+ list.combineSegments();
+ } else if (b == 'N' || b == 'n') {
+ if (parseDebug)
+ System.out.println("DEBUG IMAP: parameter list NIL");
+ r.skip(2);
+ } else
+ throw new ParsingException("Parameter list parse error");
+
+ return list;
+ }
+
+ private void parseBodyExtension(Response r) throws ParsingException {
+ r.skipSpaces();
+
+ byte b = r.peekByte();
+ if (b == '(') {
+ r.skip(1); // skip '('
+ do {
+ parseBodyExtension(r);
+ } while (!r.isNextNonSpace(')'));
+ } else if (Character.isDigit((char)b)) // number
+ r.readNumber();
+ else // nstring
+ r.readString();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java b/app/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java
new file mode 100644
index 0000000000..eeafd143e2
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Date;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.MailDateFormat;
+import javax.mail.internet.MimeUtility;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * The ENEVELOPE item of an IMAP FETCH response.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class ENVELOPE implements Item {
+
+ // IMAP item name
+ static final char[] name = {'E','N','V','E','L','O','P','E'};
+ public int msgno;
+
+ public Date date = null;
+ public String subject;
+ public InternetAddress[] from;
+ public InternetAddress[] sender;
+ public InternetAddress[] replyTo;
+ public InternetAddress[] to;
+ public InternetAddress[] cc;
+ public InternetAddress[] bcc;
+ public String inReplyTo;
+ public String messageId;
+
+ // Used to parse dates
+ private static final MailDateFormat mailDateFormat = new MailDateFormat();
+
+ // special debugging output to debug parsing errors
+ private static final boolean parseDebug =
+ PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
+
+ public ENVELOPE(FetchResponse r) throws ParsingException {
+ if (parseDebug)
+ System.out.println("parse ENVELOPE");
+ msgno = r.getNumber();
+
+ r.skipSpaces();
+
+ if (r.readByte() != '(')
+ throw new ParsingException("ENVELOPE parse error");
+
+ String s = r.readString();
+ if (s != null) {
+ try {
+ synchronized (mailDateFormat) {
+ date = mailDateFormat.parse(s);
+ }
+ } catch (ParseException pex) {
+ }
+ }
+ if (parseDebug)
+ System.out.println(" Date: " + date);
+
+ subject = r.readString();
+ if (parseDebug)
+ System.out.println(" Subject: " + subject);
+ if (parseDebug)
+ System.out.println(" From addresses:");
+ from = parseAddressList(r);
+ if (parseDebug)
+ System.out.println(" Sender addresses:");
+ sender = parseAddressList(r);
+ if (parseDebug)
+ System.out.println(" Reply-To addresses:");
+ replyTo = parseAddressList(r);
+ if (parseDebug)
+ System.out.println(" To addresses:");
+ to = parseAddressList(r);
+ if (parseDebug)
+ System.out.println(" Cc addresses:");
+ cc = parseAddressList(r);
+ if (parseDebug)
+ System.out.println(" Bcc addresses:");
+ bcc = parseAddressList(r);
+ inReplyTo = r.readString();
+ if (parseDebug)
+ System.out.println(" In-Reply-To: " + inReplyTo);
+ messageId = r.readString();
+ if (parseDebug)
+ System.out.println(" Message-ID: " + messageId);
+
+ if (!r.isNextNonSpace(')'))
+ throw new ParsingException("ENVELOPE parse error");
+ }
+
+ private InternetAddress[] parseAddressList(Response r)
+ throws ParsingException {
+ r.skipSpaces(); // skip leading spaces
+
+ byte b = r.readByte();
+ if (b == '(') {
+ /*
+ * Some broken servers (e.g., Yahoo Mail) return an empty
+ * list instead of NIL. Handle that here even though it
+ * doesn't conform to the IMAP spec.
+ */
+ if (r.isNextNonSpace(')'))
+ return null;
+
+ List v = new ArrayList<>();
+
+ do {
+ IMAPAddress a = new IMAPAddress(r);
+ if (parseDebug)
+ System.out.println(" Address: " + a);
+ // if we see an end-of-group address at the top, ignore it
+ if (!a.isEndOfGroup())
+ v.add(a);
+ } while (!r.isNextNonSpace(')'));
+
+ return v.toArray(new InternetAddress[v.size()]);
+ } else if (b == 'N' || b == 'n') { // NIL
+ r.skip(2); // skip 'NIL'
+ return null;
+ } else
+ throw new ParsingException("ADDRESS parse error");
+ }
+}
+
+class IMAPAddress extends InternetAddress {
+ private boolean group = false;
+ private InternetAddress[] grouplist;
+ private String groupname;
+
+ private static final long serialVersionUID = -3835822029483122232L;
+
+ IMAPAddress(Response r) throws ParsingException {
+ r.skipSpaces(); // skip leading spaces
+
+ if (r.readByte() != '(')
+ throw new ParsingException("ADDRESS parse error");
+
+ encodedPersonal = r.readString();
+
+ r.readString(); // throw away address_list
+ String mb = r.readString();
+ String host = r.readString();
+ // skip bogus spaces inserted by Yahoo IMAP server if
+ // "undisclosed-recipients" is a recipient
+ r.skipSpaces();
+ if (!r.isNextNonSpace(')')) // skip past terminating ')'
+ throw new ParsingException("ADDRESS parse error");
+
+ if (host == null) {
+ // it's a group list, start or end
+ group = true;
+ groupname = mb;
+ if (groupname == null) // end of group list
+ return;
+ // Accumulate a group list. The members of the group
+ // are accumulated in a List and the corresponding string
+ // representation of the group is accumulated in a StringBuilder.
+ StringBuilder sb = new StringBuilder();
+ sb.append(groupname).append(':');
+ List v = new ArrayList<>();
+ while (r.peekByte() != ')') {
+ IMAPAddress a = new IMAPAddress(r);
+ if (a.isEndOfGroup()) // reached end of group
+ break;
+ if (v.size() != 0) // if not first element, need a comma
+ sb.append(',');
+ sb.append(a.toString());
+ v.add(a);
+ }
+ sb.append(';');
+ address = sb.toString();
+ grouplist = v.toArray(new IMAPAddress[v.size()]);
+ } else {
+ if (mb == null || mb.length() == 0)
+ address = host;
+ else if (host.length() == 0)
+ address = mb;
+ else
+ address = mb + "@" + host;
+ }
+
+ }
+
+ boolean isEndOfGroup() {
+ return group && groupname == null;
+ }
+
+ @Override
+ public boolean isGroup() {
+ return group;
+ }
+
+ @Override
+ public InternetAddress[] getGroup(boolean strict) throws AddressException {
+ if (grouplist == null)
+ return null;
+ return grouplist.clone();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/FLAGS.java b/app/src/main/java/com/sun/mail/imap/protocol/FLAGS.java
new file mode 100644
index 0000000000..5621aa81c3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/FLAGS.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import javax.mail.Flags;
+import com.sun.mail.iap.*;
+
+/**
+ * This class
+ *
+ * @author John Mani
+ */
+
+public class FLAGS extends Flags implements Item {
+
+ // IMAP item name
+ static final char[] name = {'F','L','A','G','S'};
+ public int msgno;
+
+ private static final long serialVersionUID = 439049847053756670L;
+
+ /**
+ * Constructor.
+ *
+ * @param r the IMAPResponse
+ * @exception ParsingException for parsing failures
+ */
+ public FLAGS(IMAPResponse r) throws ParsingException {
+ msgno = r.getNumber();
+
+ r.skipSpaces();
+ String[] flags = r.readSimpleList();
+ if (flags != null) { // if not empty flaglist
+ for (int i = 0; i < flags.length; i++) {
+ String s = flags[i];
+ if (s.length() >= 2 && s.charAt(0) == '\\') {
+ switch (Character.toUpperCase(s.charAt(1))) {
+ case 'S': // \Seen
+ add(Flags.Flag.SEEN);
+ break;
+ case 'R': // \Recent
+ add(Flags.Flag.RECENT);
+ break;
+ case 'D':
+ if (s.length() >= 3) {
+ char c = s.charAt(2);
+ if (c == 'e' || c == 'E') // \Deleted
+ add(Flags.Flag.DELETED);
+ else if (c == 'r' || c == 'R') // \Draft
+ add(Flags.Flag.DRAFT);
+ } else
+ add(s); // unknown, treat it as a user flag
+ break;
+ case 'A': // \Answered
+ add(Flags.Flag.ANSWERED);
+ break;
+ case 'F': // \Flagged
+ add(Flags.Flag.FLAGGED);
+ break;
+ case '*': // \*
+ add(Flags.Flag.USER);
+ break;
+ default:
+ add(s); // unknown, treat it as a user flag
+ break;
+ }
+ } else
+ add(s);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/FetchItem.java b/app/src/main/java/com/sun/mail/imap/protocol/FetchItem.java
new file mode 100644
index 0000000000..1eaae6c1c7
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/FetchItem.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.lang.reflect.*;
+
+import javax.mail.FetchProfile;
+import com.sun.mail.iap.ParsingException;
+
+/**
+ * Metadata describing a FETCH item.
+ * Note that the "name" field MUST be in uppercase.
+ *
+ * @author Bill Shannon
+ * @since JavaMail 1.4.6
+ */
+
+public abstract class FetchItem {
+ private String name;
+ private FetchProfile.Item fetchProfileItem;
+
+ public FetchItem(String name, FetchProfile.Item fetchProfileItem) {
+ this.name = name;
+ this.fetchProfileItem = fetchProfileItem;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public FetchProfile.Item getFetchProfileItem() {
+ return fetchProfileItem;
+ }
+
+ /**
+ * Parse the item into some kind of object appropriate for the item.
+ * Note that the item name will have been parsed and skipped already.
+ *
+ * @param r the response
+ * @return the fetch item
+ * @exception ParsingException for parsing failures
+ */
+ public abstract Object parseItem(FetchResponse r) throws ParsingException;
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java b/app/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java
new file mode 100644
index 0000000000..6477ba5b2d
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents a FETCH response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class FetchResponse extends IMAPResponse {
+ /*
+ * Regular Items are saved in the items array.
+ * Extension items (items handled by subclasses
+ * that extend the IMAP provider) are saved in the
+ * extensionItems map, indexed by the FETCH item name.
+ * The map is only created when needed.
+ *
+ * XXX - Should consider unifying the handling of
+ * regular items and extension items.
+ */
+ private Item[] items;
+ private Map extensionItems;
+ private final FetchItem[] fitems;
+
+ public FetchResponse(Protocol p)
+ throws IOException, ProtocolException {
+ super(p);
+ fitems = null;
+ parse();
+ }
+
+ public FetchResponse(IMAPResponse r)
+ throws IOException, ProtocolException {
+ this(r, null);
+ }
+
+ /**
+ * Construct a FetchResponse that handles the additional FetchItems.
+ *
+ * @param r the IMAPResponse
+ * @param fitems the fetch items
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.4.6
+ */
+ public FetchResponse(IMAPResponse r, FetchItem[] fitems)
+ throws IOException, ProtocolException {
+ super(r);
+ this.fitems = fitems;
+ parse();
+ }
+
+ public int getItemCount() {
+ return items.length;
+ }
+
+ public Item getItem(int index) {
+ return items[index];
+ }
+
+ public T getItem(Class c) {
+ for (int i = 0; i < items.length; i++) {
+ if (c.isInstance(items[i]))
+ return c.cast(items[i]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first fetch response item of the given class
+ * for the given message number.
+ *
+ * @param r the responses
+ * @param msgno the message number
+ * @param c the class
+ * @param the type of fetch item
+ * @return the fetch item
+ */
+ public static T getItem(Response[] r, int msgno,
+ Class c) {
+ if (r == null)
+ return null;
+
+ for (int i = 0; i < r.length; i++) {
+
+ if (r[i] == null ||
+ !(r[i] instanceof FetchResponse) ||
+ ((FetchResponse)r[i]).getNumber() != msgno)
+ continue;
+
+ FetchResponse f = (FetchResponse)r[i];
+ for (int j = 0; j < f.items.length; j++) {
+ if (c.isInstance(f.items[j]))
+ return c.cast(f.items[j]);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all fetch response items of the given class
+ * for the given message number.
+ *
+ * @param r the responses
+ * @param msgno the message number
+ * @param c the class
+ * @param the type of fetch items
+ * @return the list of fetch items
+ * @since JavaMail 1.5.2
+ */
+ public static List getItems(Response[] r, int msgno,
+ Class c) {
+ List items = new ArrayList<>();
+
+ if (r == null)
+ return items;
+
+ for (int i = 0; i < r.length; i++) {
+
+ if (r[i] == null ||
+ !(r[i] instanceof FetchResponse) ||
+ ((FetchResponse)r[i]).getNumber() != msgno)
+ continue;
+
+ FetchResponse f = (FetchResponse)r[i];
+ for (int j = 0; j < f.items.length; j++) {
+ if (c.isInstance(f.items[j]))
+ items.add(c.cast(f.items[j]));
+ }
+ }
+
+ return items;
+ }
+
+ /**
+ * Return a map of the extension items found in this fetch response.
+ * The map is indexed by extension item name. Callers should not
+ * modify the map.
+ *
+ * @return Map of extension items, or null if none
+ * @since JavaMail 1.4.6
+ */
+ public Map getExtensionItems() {
+ return extensionItems;
+ }
+
+ private final static char[] HEADER = {'.','H','E','A','D','E','R'};
+ private final static char[] TEXT = {'.','T','E','X','T'};
+
+ private void parse() throws ParsingException {
+ if (!isNextNonSpace('('))
+ throw new ParsingException(
+ "error in FETCH parsing, missing '(' at index " + index);
+
+ List- v = new ArrayList<>();
+ Item i = null;
+ skipSpaces();
+ do {
+
+ if (index >= size)
+ throw new ParsingException(
+ "error in FETCH parsing, ran off end of buffer, size " + size);
+
+ i = parseItem();
+ if (i != null)
+ v.add(i);
+ else if (!parseExtensionItem())
+ throw new ParsingException(
+ "error in FETCH parsing, unrecognized item at index " +
+ index + ", starts with \"" + next20() + "\"");
+ } while (!isNextNonSpace(')'));
+
+ items = v.toArray(new Item[v.size()]);
+ }
+
+ /**
+ * Return the next 20 characters in the buffer, for exception messages.
+ */
+ private String next20() {
+ if (index + 20 > size)
+ return ASCIIUtility.toString(buffer, index, size);
+ else
+ return ASCIIUtility.toString(buffer, index, index + 20) + "...";
+ }
+
+ /**
+ * Parse the item at the current position in the buffer,
+ * skipping over the item if successful. Otherwise, return null
+ * and leave the buffer position unmodified.
+ */
+ @SuppressWarnings("empty")
+ private Item parseItem() throws ParsingException {
+ switch (buffer[index]) {
+ case 'E': case 'e':
+ if (match(ENVELOPE.name))
+ return new ENVELOPE(this);
+ break;
+ case 'F': case 'f':
+ if (match(FLAGS.name))
+ return new FLAGS((IMAPResponse)this);
+ break;
+ case 'I': case 'i':
+ if (match(INTERNALDATE.name))
+ return new INTERNALDATE(this);
+ break;
+ case 'B': case 'b':
+ if (match(BODYSTRUCTURE.name))
+ return new BODYSTRUCTURE(this);
+ else if (match(BODY.name)) {
+ if (buffer[index] == '[')
+ return new BODY(this);
+ else
+ return new BODYSTRUCTURE(this);
+ }
+ break;
+ case 'R': case 'r':
+ if (match(RFC822SIZE.name))
+ return new RFC822SIZE(this);
+ else if (match(RFC822DATA.name)) {
+ boolean isHeader = false;
+ if (match(HEADER))
+ isHeader = true; // skip ".HEADER"
+ else if (match(TEXT))
+ isHeader = false; // skip ".TEXT"
+ return new RFC822DATA(this, isHeader);
+ }
+ break;
+ case 'U': case 'u':
+ if (match(UID.name))
+ return new UID(this);
+ break;
+ case 'M': case 'm':
+ if (match(MODSEQ.name))
+ return new MODSEQ(this);
+ break;
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /**
+ * If this item is a known extension item, parse it.
+ */
+ private boolean parseExtensionItem() throws ParsingException {
+ if (fitems == null)
+ return false;
+ for (int i = 0; i < fitems.length; i++) {
+ if (match(fitems[i].getName())) {
+ if (extensionItems == null)
+ extensionItems = new HashMap<>();
+ extensionItems.put(fitems[i].getName(),
+ fitems[i].parseItem(this));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does the current buffer match the given item name?
+ * itemName is the name of the IMAP item to compare against.
+ * NOTE that itemName *must* be all uppercase.
+ * If the match is successful, the buffer pointer (index)
+ * is incremented past the matched item.
+ */
+ private boolean match(char[] itemName) {
+ int len = itemName.length;
+ for (int i = 0, j = index; i < len;)
+ // IMAP tokens are case-insensitive. We store itemNames in
+ // uppercase, so convert operand to uppercase before comparing.
+ if (Character.toUpperCase((char)buffer[j++]) != itemName[i++])
+ return false;
+ index += len;
+ return true;
+ }
+
+ /**
+ * Does the current buffer match the given item name?
+ * itemName is the name of the IMAP item to compare against.
+ * NOTE that itemName *must* be all uppercase.
+ * If the match is successful, the buffer pointer (index)
+ * is incremented past the matched item.
+ */
+ private boolean match(String itemName) {
+ int len = itemName.length();
+ for (int i = 0, j = index; i < len;)
+ // IMAP tokens are case-insensitive. We store itemNames in
+ // uppercase, so convert operand to uppercase before comparing.
+ if (Character.toUpperCase((char)buffer[j++]) !=
+ itemName.charAt(i++))
+ return false;
+ index += len;
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/ID.java b/app/src/main/java/com/sun/mail/imap/protocol/ID.java
new file mode 100644
index 0000000000..66cb865fcc
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/ID.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents the response to the ID command.
+ *
+ * See RFC 2971 .
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+
+public class ID {
+
+ private Map serverParams = null;
+
+ /**
+ * Parse the server parameter list out of the response.
+ *
+ * @param r the response
+ * @exception ProtocolException for protocol failures
+ */
+ public ID(Response r) throws ProtocolException {
+ // id_response ::= "ID" SPACE id_params_list
+ // id_params_list ::= "(" #(string SPACE nstring) ")" / nil
+ // ;; list of field value pairs
+
+ r.skipSpaces();
+ int c = r.peekByte();
+ if (c == 'N' || c == 'n') // assume NIL
+ return;
+
+ if (c != '(')
+ throw new ProtocolException("Missing '(' at start of ID");
+
+ serverParams = new HashMap<>();
+
+ String[] v = r.readStringList();
+ if (v != null) {
+ for (int i = 0; i < v.length; i += 2) {
+ String name = v[i];
+ if (name == null)
+ throw new ProtocolException("ID field name null");
+ if (i + 1 >= v.length)
+ throw new ProtocolException("ID field without value: " +
+ name);
+ String value = v[i + 1];
+ serverParams.put(name, value);
+ }
+ }
+ serverParams = Collections.unmodifiableMap(serverParams);
+ }
+
+ /**
+ * Return the parsed server params.
+ */
+ Map getServerParams() {
+ return serverParams;
+ }
+
+ /**
+ * Convert the client parameters into an argument list for the ID command.
+ */
+ static Argument getArgumentList(Map clientParams) {
+ Argument arg = new Argument();
+ if (clientParams == null) {
+ arg.writeAtom("NIL");
+ return arg;
+ }
+ Argument list = new Argument();
+ // add params to list
+ for (Map.Entry e : clientParams.entrySet()) {
+ list.writeNString(e.getKey()); // assume these are ASCII only
+ list.writeNString(e.getValue());
+ }
+ arg.writeArgument(list);
+ return arg;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java b/app/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
new file mode 100644
index 0000000000..a8a88b6c01
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
@@ -0,0 +1,3294 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.lang.reflect.*;
+import java.util.logging.Level;
+import java.nio.charset.StandardCharsets;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.auth.Ntlm;
+
+import com.sun.mail.imap.ACL;
+import com.sun.mail.imap.Rights;
+import com.sun.mail.imap.AppendUID;
+import com.sun.mail.imap.CopyUID;
+import com.sun.mail.imap.SortTerm;
+import com.sun.mail.imap.ResyncData;
+import com.sun.mail.imap.Utility;
+
+/**
+ * This class extends the iap.Protocol object and implements IMAP
+ * semantics. In general, there is a method corresponding to each
+ * IMAP protocol command. The typical implementation issues the
+ * appropriate protocol command, collects all responses, processes
+ * those responses that are specific to this command and then
+ * dispatches the rest (the unsolicited ones) to the dispatcher
+ * using the notifyResponseHandlers(r)
.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class IMAPProtocol extends Protocol {
+
+ private boolean connected = false; // did constructor succeed?
+ private boolean rev1 = false; // REV1 server ?
+ private boolean referralException; // throw exception for IMAP REFERRAL?
+ private boolean noauthdebug = true; // hide auth info in debug output
+ private boolean authenticated; // authenticated?
+ // WARNING: authenticated may be set to true in superclass
+ // constructor, don't initialize it here.
+
+ private Map capabilities;
+ // WARNING: capabilities may be initialized as a result of superclass
+ // constructor, don't initialize it here.
+ private List 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
+
+ protected Set enabled; // enabled capabilities - RFC 5161
+
+ private String name;
+ private SaslAuthenticator saslAuthenticator; // if SASL is being used
+ private String proxyAuthUser; // user name used with PROXYAUTH
+
+ private ByteArray ba; // a buffer for fetchBody
+
+ private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
+
+ private static final FetchItem[] fetchItems = { };
+
+ /**
+ * Constructor.
+ * Opens a connection to the given host at given port.
+ *
+ * @param name the protocol name
+ * @param host host to connect to
+ * @param port port number to connect to
+ * @param props Properties object used by this protocol
+ * @param isSSL true if SSL should be used
+ * @param logger the MailLogger to use for debug output
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public IMAPProtocol(String name, String host, int port,
+ Properties props, boolean isSSL, MailLogger logger)
+ throws IOException, ProtocolException {
+ super(host, port, props, "mail." + name, isSSL, logger);
+
+ try {
+ this.name = name;
+ noauthdebug =
+ !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
+
+ // in case it was not initialized in processGreeting
+ referralException = PropUtil.getBooleanProperty(props,
+ prefix + ".referralexception", false);
+
+ if (capabilities == null)
+ capability();
+
+ if (hasCapability("IMAP4rev1"))
+ rev1 = true;
+
+ searchCharsets = new String[2]; // 2, for now.
+ searchCharsets[0] = "UTF-8";
+ searchCharsets[1] = MimeUtility.mimeCharset(
+ MimeUtility.getDefaultJavaCharset()
+ );
+
+ connected = true; // must be last statement in constructor
+ } finally {
+ /*
+ * If we get here because an exception was thrown, we need
+ * to disconnect to avoid leaving a connected socket that
+ * no one will be able to use because this object was never
+ * completely constructed.
+ */
+ if (!connected)
+ disconnect();
+ }
+ }
+
+ /**
+ * Constructor for debugging.
+ *
+ * @param in the InputStream from which to read
+ * @param out the PrintStream to which to write
+ * @param props Properties object used by this protocol
+ * @param debug true to enable debugging output
+ * @exception IOException for I/O errors
+ */
+ public IMAPProtocol(InputStream in, PrintStream out,
+ Properties props, boolean debug)
+ throws IOException {
+ super(in, out, props, debug);
+
+ this.name = "imap";
+ noauthdebug =
+ !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
+
+ if (capabilities == null)
+ capabilities = new HashMap<>();
+
+ searchCharsets = new String[2]; // 2, for now.
+ searchCharsets[0] = "UTF-8";
+ searchCharsets[1] = MimeUtility.mimeCharset(
+ MimeUtility.getDefaultJavaCharset()
+ );
+
+ connected = true; // must be last statement in constructor
+ }
+
+ /**
+ * Return an array of FetchItem objects describing the
+ * FETCH items supported by this protocol. Subclasses may
+ * override this method to combine their FetchItems with
+ * the FetchItems returned by the superclass.
+ *
+ * @return an array of FetchItem objects
+ * @since JavaMail 1.4.6
+ */
+ public FetchItem[] getFetchItems() {
+ return fetchItems;
+ }
+
+ /**
+ * CAPABILITY command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.1.1"
+ */
+ public void capability() throws ProtocolException {
+ // Check CAPABILITY
+ Response[] r = command("CAPABILITY", null);
+ Response response = r[r.length-1];
+
+ if (response.isOK())
+ handleCapabilityResponse(r);
+ handleResult(response);
+ }
+
+ /**
+ * Handle any untagged CAPABILITY response in the Response array.
+ *
+ * @param r the responses
+ */
+ public void handleCapabilityResponse(Response[] r) {
+ boolean first = true;
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+
+ // Handle *all* untagged CAPABILITY responses.
+ // Though the spec seemingly states that only
+ // one CAPABILITY response string is allowed (6.1.1),
+ // some server vendors claim otherwise.
+ if (ir.keyEquals("CAPABILITY")) {
+ if (first) {
+ // clear out current when first response seen
+ capabilities = new HashMap<>(10);
+ authmechs = new ArrayList<>(5);
+ first = false;
+ }
+ parseCapabilities(ir);
+ }
+ }
+ }
+
+ /**
+ * If the response contains a CAPABILITY response code, extract
+ * it and save the capabilities.
+ *
+ * @param r the response
+ */
+ protected void setCapabilities(Response r) {
+ byte b;
+ while ((b = r.readByte()) > 0 && b != (byte)'[')
+ ;
+ if (b == 0)
+ return;
+ String s;
+ s = r.readAtom();
+ if (!s.equalsIgnoreCase("CAPABILITY"))
+ return;
+ capabilities = new HashMap<>(10);
+ authmechs = new ArrayList<>(5);
+ parseCapabilities(r);
+ }
+
+ /**
+ * Parse the capabilities from a CAPABILITY response or from
+ * a CAPABILITY response code attached to (e.g.) an OK response.
+ *
+ * @param r the CAPABILITY response
+ */
+ protected void parseCapabilities(Response r) {
+ String s;
+ while ((s = r.readAtom()) != null) {
+ if (s.length() == 0) {
+ if (r.peekByte() == (byte)']')
+ break;
+ /*
+ * Probably found something here that's not an atom.
+ * Rather than loop forever or fail completely, we'll
+ * try to skip this bogus capability. This is known
+ * to happen with:
+ * Netscape Messaging Server 4.03 (built Apr 27 1999)
+ * that returns:
+ * * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
+ * The "*" in the middle of the capability list causes
+ * us to loop forever here.
+ */
+ r.skipToken();
+ } else {
+ capabilities.put(s.toUpperCase(Locale.ENGLISH), s);
+ if (s.regionMatches(true, 0, "AUTH=", 0, 5)) {
+ authmechs.add(s.substring(5));
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("AUTH: " + s.substring(5));
+ }
+ }
+ }
+ }
+
+ /**
+ * Check the greeting when first connecting; look for PREAUTH response.
+ *
+ * @param r the greeting response
+ * @exception ProtocolException for protocol failures
+ */
+ @Override
+ protected void processGreeting(Response r) throws ProtocolException {
+ if (r.isBYE()) {
+ checkReferral(r); // may throw exception
+ throw new ConnectionException(this, r);
+ }
+ if (r.isOK()) { // check if it's OK
+ // XXX - is a REFERRAL response code really allowed here?
+ // XXX - referralException hasn't been initialized in c'tor yet
+ referralException = PropUtil.getBooleanProperty(props,
+ prefix + ".referralexception", false);
+ if (referralException)
+ checkReferral(r);
+ setCapabilities(r);
+ return;
+ }
+ // only other choice is PREAUTH
+ assert r instanceof IMAPResponse;
+ IMAPResponse ir = (IMAPResponse)r;
+ if (ir.keyEquals("PREAUTH")) {
+ authenticated = true;
+ setCapabilities(r);
+ } else {
+ disconnect();
+ throw new ConnectionException(this, r);
+ }
+ }
+
+ /**
+ * Check for an IMAP login REFERRAL response code.
+ *
+ * @exception IMAPReferralException if REFERRAL response code found
+ * @see "RFC 2221"
+ */
+ private void checkReferral(Response r) throws IMAPReferralException {
+ String s = r.getRest(); // get the text after the response
+ if (s.startsWith("[")) { // a response code
+ int i = s.indexOf(' ');
+ if (i > 0 && s.substring(1, i).equalsIgnoreCase("REFERRAL")) {
+ String url, msg;
+ int j = s.indexOf(']');
+ if (j > 0) { // should always be true;
+ url = s.substring(i + 1, j);
+ msg = s.substring(j + 1).trim();
+ } else {
+ url = s.substring(i + 1);
+ msg = "";
+ }
+ if (r.isBYE())
+ disconnect();
+ throw new IMAPReferralException(msg, url);
+ }
+ }
+ }
+
+ /**
+ * Returns true
if the connection has been authenticated,
+ * either due to a successful login, or due to a PREAUTH greeting response.
+ *
+ * @return true if the connection has been authenticated
+ */
+ public boolean isAuthenticated() {
+ return authenticated;
+ }
+
+ /**
+ * Returns true
if this is an IMAP4rev1 server
+ *
+ * @return true if this is an IMAP4rev1 server
+ */
+ public boolean isREV1() {
+ return rev1;
+ }
+
+ /**
+ * Returns whether this Protocol supports non-synchronizing literals.
+ *
+ * @return true if non-synchronizing literals are supported
+ */
+ @Override
+ protected boolean supportsNonSyncLiterals() {
+ return hasCapability("LITERAL+");
+ }
+
+ /**
+ * Read a response from the server.
+ *
+ * @return the response
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ @Override
+ public Response readResponse() throws IOException, ProtocolException {
+ // assert Thread.holdsLock(this);
+ // can't assert because it's called from constructor
+ IMAPResponse r = new IMAPResponse(this);
+ if (r.keyEquals("FETCH"))
+ r = new FetchResponse(r, getFetchItems());
+ return r;
+ }
+
+ /**
+ * Check whether the given capability is supported by
+ * this server. Returns true
if so, otherwise
+ * returns false.
+ *
+ * @param c the capability name
+ * @return true if the server has the capability
+ */
+ public boolean hasCapability(String c) {
+ if (c.endsWith("*")) {
+ c = c.substring(0, c.length() - 1).toUpperCase(Locale.ENGLISH);
+ Iterator it = capabilities.keySet().iterator();
+ while (it.hasNext()) {
+ if (it.next().startsWith(c))
+ return true;
+ }
+ return false;
+ }
+ return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Return the map of capabilities returned by the server.
+ *
+ * @return the Map of capabilities
+ * @since JavaMail 1.4.1
+ */
+ public Map getCapabilities() {
+ return capabilities;
+ }
+
+ /**
+ * 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
+ * public.
+ */
+ @Override
+ public void disconnect() {
+ super.disconnect();
+ authenticated = false; // just in case
+ }
+
+ /**
+ * The NOOP command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.1.2"
+ */
+ public void noop() throws ProtocolException {
+ logger.fine("IMAPProtocol noop");
+ simpleCommand("NOOP", null);
+ }
+
+ /**
+ * LOGOUT Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.1.3"
+ */
+ public void logout() throws ProtocolException {
+ try {
+ Response[] r = command("LOGOUT", null);
+
+ authenticated = false;
+ // dispatch any unsolicited responses.
+ // NOTE that the BYE response is dispatched here as well
+ notifyResponseHandlers(r);
+ } finally {
+ disconnect();
+ }
+ }
+
+ /**
+ * LOGIN Command.
+ *
+ * @param u the username
+ * @param p the password
+ * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+ * @see "RFC2060, section 6.2.2"
+ */
+ public void login(String u, String p) throws ProtocolException {
+ Argument args = new Argument();
+ args.writeString(u);
+ args.writeString(p);
+
+ Response[] r = null;
+ try {
+ if (noauthdebug && isTracing()) {
+ logger.fine("LOGIN command trace suppressed");
+ suspendTracing();
+ }
+ r = command("LOGIN", args);
+ } finally {
+ resumeTracing();
+ }
+
+ // handle an illegal but not uncommon untagged CAPABILTY response
+ handleCapabilityResponse(r);
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ // Handle result of this command
+ if (noauthdebug && isTracing())
+ logger.fine("LOGIN command result: " + r[r.length-1]);
+ handleLoginResult(r[r.length-1]);
+ // If the response includes a CAPABILITY response code, process it
+ setCapabilities(r[r.length-1]);
+ // if we get this far without an exception, we're authenticated
+ authenticated = true;
+ }
+
+ /**
+ * The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
+ *
+ * @param u the username
+ * @param p the password
+ * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+ * @see "RFC2060, section 6.2.1"
+ */
+ public synchronized void authlogin(String u, String p)
+ throws ProtocolException {
+ List v = new ArrayList<>();
+ String tag = null;
+ Response r = null;
+ boolean done = false;
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("AUTHENTICATE LOGIN command trace suppressed");
+ suspendTracing();
+ }
+
+ try {
+ tag = writeCommand("AUTHENTICATE LOGIN", null);
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+
+ OutputStream os = getOutputStream(); // stream to IMAP server
+
+ /* Wrap a BASE64Encoder around a ByteArrayOutputstream
+ * to craft b64 encoded username and password strings
+ *
+ * Note that the encoded bytes should be sent "as-is" to the
+ * server, *not* as literals or quoted-strings.
+ *
+ * Also note that unlike the B64 definition in MIME, CRLFs
+ * should *not* be inserted during the encoding process. So, I
+ * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
+ * which should be sufficiently large !
+ *
+ * Finally, format the line in a buffer so it can be sent as
+ * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
+ * server caused by patch 105346.
+ */
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+ boolean first = true;
+
+ while (!done) { // loop till we are done
+ try {
+ r = readResponse();
+ if (r.isContinuation()) {
+ // Server challenge ..
+ String s;
+ if (first) { // Send encoded username
+ s = u;
+ first = false;
+ } else // Send encoded password
+ s = p;
+
+ // obtain b64 encoded bytes
+ b64os.write(s.getBytes(StandardCharsets.UTF_8));
+ b64os.flush(); // complete the encoding
+
+ bos.write(CRLF); // CRLF termination
+ os.write(bos.toByteArray()); // write out line
+ os.flush(); // flush the stream
+ bos.reset(); // reset buffer
+ } else if (r.isTagged() && r.getTag().equals(tag))
+ // Ah, our tagged response
+ done = true;
+ else if (r.isBYE()) // outta here
+ done = true;
+ // hmm .. unsolicited response here ?!
+ } catch (Exception ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ done = true;
+ }
+ v.add(r);
+ }
+
+ } finally {
+ resumeTracing();
+ }
+
+ Response[] responses = v.toArray(new Response[v.size()]);
+
+ // handle an illegal but not uncommon untagged CAPABILTY response
+ handleCapabilityResponse(responses);
+
+ /*
+ * Dispatch untagged responses.
+ * NOTE: in our current upper level IMAP classes, we add the
+ * responseHandler to the Protocol object only *after* the
+ * connection has been authenticated. So, for now, the below
+ * code really ends up being just a no-op.
+ */
+ notifyResponseHandlers(responses);
+
+ // Handle the final OK, NO, BAD or BYE response
+ if (noauthdebug && isTracing())
+ logger.fine("AUTHENTICATE LOGIN command result: " + r);
+ handleLoginResult(r);
+ // If the response includes a CAPABILITY response code, process it
+ setCapabilities(r);
+ // if we get this far without an exception, we're authenticated
+ authenticated = true;
+ }
+
+
+ /**
+ * The AUTHENTICATE command with AUTH=PLAIN authentication scheme.
+ * This is based heavly on the {@link #authlogin} method.
+ *
+ * @param authzid the authorization id
+ * @param u the username
+ * @param p the password
+ * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+ * @see "RFC3501, section 6.2.2"
+ * @see "RFC2595, section 6"
+ * @since JavaMail 1.3.2
+ */
+ public synchronized void authplain(String authzid, String u, String p)
+ throws ProtocolException {
+ List v = new ArrayList<>();
+ String tag = null;
+ Response r = null;
+ boolean done = false;
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("AUTHENTICATE PLAIN command trace suppressed");
+ suspendTracing();
+ }
+
+ try {
+ tag = writeCommand("AUTHENTICATE PLAIN", null);
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+
+ OutputStream os = getOutputStream(); // stream to IMAP server
+
+ /* Wrap a BASE64Encoder around a ByteArrayOutputstream
+ * to craft b64 encoded username and password strings
+ *
+ * Note that the encoded bytes should be sent "as-is" to the
+ * server, *not* as literals or quoted-strings.
+ *
+ * Also note that unlike the B64 definition in MIME, CRLFs
+ * should *not* be inserted during the encoding process. So, I
+ * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
+ * which should be sufficiently large !
+ *
+ * Finally, format the line in a buffer so it can be sent as
+ * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
+ * server caused by patch 105346.
+ */
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+
+ while (!done) { // loop till we are done
+ try {
+ r = readResponse();
+ if (r.isContinuation()) {
+ // Server challenge ..
+ final String nullByte = "\0";
+ String s = (authzid == null ? "" : authzid) +
+ nullByte + u + nullByte + p;
+
+ // obtain b64 encoded bytes
+ b64os.write(s.getBytes(StandardCharsets.UTF_8));
+ b64os.flush(); // complete the encoding
+
+ bos.write(CRLF); // CRLF termination
+ os.write(bos.toByteArray()); // write out line
+ os.flush(); // flush the stream
+ bos.reset(); // reset buffer
+ } else if (r.isTagged() && r.getTag().equals(tag))
+ // Ah, our tagged response
+ done = true;
+ else if (r.isBYE()) // outta here
+ done = true;
+ // hmm .. unsolicited response here ?!
+ } catch (Exception ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ done = true;
+ }
+ v.add(r);
+ }
+
+ } finally {
+ resumeTracing();
+ }
+
+ Response[] responses = v.toArray(new Response[v.size()]);
+
+ // handle an illegal but not uncommon untagged CAPABILTY response
+ handleCapabilityResponse(responses);
+
+ /*
+ * Dispatch untagged responses.
+ * NOTE: in our current upper level IMAP classes, we add the
+ * responseHandler to the Protocol object only *after* the
+ * connection has been authenticated. So, for now, the below
+ * code really ends up being just a no-op.
+ */
+ notifyResponseHandlers(responses);
+
+ // Handle the final OK, NO, BAD or BYE response
+ if (noauthdebug && isTracing())
+ logger.fine("AUTHENTICATE PLAIN command result: " + r);
+ handleLoginResult(r);
+ // If the response includes a CAPABILITY response code, process it
+ setCapabilities(r);
+ // if we get this far without an exception, we're authenticated
+ authenticated = true;
+ }
+
+ /**
+ * The AUTHENTICATE command with AUTH=NTLM authentication scheme.
+ * This is based heavly on the {@link #authlogin} method.
+ *
+ * @param authzid the authorization id
+ * @param u the username
+ * @param p the password
+ * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+ * @see "RFC3501, section 6.2.2"
+ * @see "RFC2595, section 6"
+ * @since JavaMail 1.4.3
+ */
+ public synchronized void authntlm(String authzid, String u, String p)
+ throws ProtocolException {
+ List v = new ArrayList<>();
+ String tag = null;
+ Response r = null;
+ boolean done = false;
+
+ String type1Msg = null;
+ int flags = PropUtil.getIntProperty(props,
+ "mail." + name + ".auth.ntlm.flags", 0);
+ boolean v2 = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".auth.ntlm.v2", true);
+ String domain = props.getProperty(
+ "mail." + name + ".auth.ntlm.domain", "");
+ Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger);
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("AUTHENTICATE NTLM command trace suppressed");
+ suspendTracing();
+ }
+
+ try {
+ tag = writeCommand("AUTHENTICATE NTLM", null);
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+
+ OutputStream os = getOutputStream(); // stream to IMAP server
+ boolean first = true;
+
+ while (!done) { // loop till we are done
+ try {
+ r = readResponse();
+ if (r.isContinuation()) {
+ // Server challenge ..
+ String s;
+ if (first) {
+ s = ntlm.generateType1Msg(flags, v2);
+ first = false;
+ } else {
+ s = ntlm.generateType3Msg(r.getRest());
+ }
+
+ os.write(s.getBytes(StandardCharsets.UTF_8));
+ os.write(CRLF); // CRLF termination
+ os.flush(); // flush the stream
+ } else if (r.isTagged() && r.getTag().equals(tag))
+ // Ah, our tagged response
+ done = true;
+ else if (r.isBYE()) // outta here
+ done = true;
+ // hmm .. unsolicited response here ?!
+ } catch (Exception ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ done = true;
+ }
+ v.add(r);
+ }
+
+ } finally {
+ resumeTracing();
+ }
+
+ Response[] responses = v.toArray(new Response[v.size()]);
+
+ // handle an illegal but not uncommon untagged CAPABILTY response
+ handleCapabilityResponse(responses);
+
+ /*
+ * Dispatch untagged responses.
+ * NOTE: in our current upper level IMAP classes, we add the
+ * responseHandler to the Protocol object only *after* the
+ * connection has been authenticated. So, for now, the below
+ * code really ends up being just a no-op.
+ */
+ notifyResponseHandlers(responses);
+
+ // Handle the final OK, NO, BAD or BYE response
+ if (noauthdebug && isTracing())
+ logger.fine("AUTHENTICATE NTLM command result: " + r);
+ handleLoginResult(r);
+ // If the response includes a CAPABILITY response code, process it
+ setCapabilities(r);
+ // if we get this far without an exception, we're authenticated
+ authenticated = true;
+ }
+
+ /**
+ * The AUTHENTICATE command with AUTH=XOAUTH2 authentication scheme.
+ * This is based heavly on the {@link #authlogin} method.
+ *
+ * @param u the username
+ * @param p the password
+ * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+ * @see "RFC3501, section 6.2.2"
+ * @see "RFC2595, section 6"
+ * @since JavaMail 1.5.5
+ */
+ public synchronized void authoauth2(String u, String p)
+ throws ProtocolException {
+ List v = new ArrayList<>();
+ String tag = null;
+ Response r = null;
+ boolean done = false;
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("AUTHENTICATE XOAUTH2 command trace suppressed");
+ suspendTracing();
+ }
+
+ try {
+ Argument args = new Argument();
+ args.writeAtom("XOAUTH2");
+ if (hasCapability("SASL-IR")) {
+ String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001";
+ byte[] ba = BASE64EncoderStream.encode(
+ resp.getBytes(StandardCharsets.UTF_8));
+ String irs = ASCIIUtility.toString(ba, 0, ba.length);
+ args.writeAtom(irs);
+ }
+ tag = writeCommand("AUTHENTICATE", args);
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+
+ OutputStream os = getOutputStream(); // stream to IMAP server
+
+ while (!done) { // loop till we are done
+ try {
+ r = readResponse();
+ if (r.isContinuation()) {
+ // Server challenge ..
+ String resp = "user=" + u + "\001auth=Bearer " +
+ p + "\001\001";
+ byte[] b = BASE64EncoderStream.encode(
+ resp.getBytes(StandardCharsets.UTF_8));
+ os.write(b); // write out response
+ os.write(CRLF); // CRLF termination
+ os.flush(); // flush the stream
+ } else if (r.isTagged() && r.getTag().equals(tag))
+ // Ah, our tagged response
+ done = true;
+ else if (r.isBYE()) // outta here
+ done = true;
+ // hmm .. unsolicited response here ?!
+ } catch (Exception ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ done = true;
+ }
+ v.add(r);
+ }
+
+ } finally {
+ resumeTracing();
+ }
+
+ Response[] responses = v.toArray(new Response[v.size()]);
+
+ // handle an illegal but not uncommon untagged CAPABILTY response
+ handleCapabilityResponse(responses);
+
+ /*
+ * Dispatch untagged responses.
+ * NOTE: in our current upper level IMAP classes, we add the
+ * responseHandler to the Protocol object only *after* the
+ * connection has been authenticated. So, for now, the below
+ * code really ends up being just a no-op.
+ */
+ notifyResponseHandlers(responses);
+
+ // Handle the final OK, NO, BAD or BYE response
+ if (noauthdebug && isTracing())
+ logger.fine("AUTHENTICATE XOAUTH2 command result: " + r);
+ handleLoginResult(r);
+ // If the response includes a CAPABILITY response code, process it
+ setCapabilities(r);
+ // if we get this far without an exception, we're authenticated
+ authenticated = true;
+ }
+
+ /**
+ * SASL-based login.
+ *
+ * @param allowed the SASL mechanisms we're allowed to use
+ * @param realm the SASL realm
+ * @param authzid the authorization id
+ * @param u the username
+ * @param p the password
+ * @exception ProtocolException for protocol failures
+ */
+ public void sasllogin(String[] allowed, String realm, String authzid,
+ String u, String p) throws ProtocolException {
+ boolean useCanonicalHostName = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".sasl.usecanonicalhostname", false);
+ String serviceHost;
+ if (useCanonicalHostName)
+ serviceHost = getInetAddress().getCanonicalHostName();
+ else
+ serviceHost = host;
+ if (saslAuthenticator == null) {
+ try {
+ Class> sac = Class.forName(
+ "com.sun.mail.imap.protocol.IMAPSaslAuthenticator");
+ Constructor> c = sac.getConstructor(new Class>[] {
+ IMAPProtocol.class,
+ String.class,
+ Properties.class,
+ MailLogger.class,
+ String.class
+ });
+ saslAuthenticator = (SaslAuthenticator)c.newInstance(
+ new Object[] {
+ this,
+ name,
+ props,
+ logger,
+ serviceHost
+ });
+ } catch (Exception ex) {
+ logger.log(Level.FINE, "Can't load SASL authenticator", ex);
+ // probably because we're running on a system without SASL
+ return; // not authenticated, try without SASL
+ }
+ }
+
+ // were any allowed mechanisms specified?
+ List v;
+ if (allowed != null && allowed.length > 0) {
+ // remove anything not supported by the server
+ v = new ArrayList<>(allowed.length);
+ for (int i = 0; i < allowed.length; i++)
+ if (authmechs.contains(allowed[i])) // XXX - case must match
+ v.add(allowed[i]);
+ } else {
+ // everything is allowed
+ v = authmechs;
+ }
+ String[] mechs = v.toArray(new String[v.size()]);
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("SASL authentication command trace suppressed");
+ suspendTracing();
+ }
+
+ if (saslAuthenticator.authenticate(mechs, realm, authzid, u, p)) {
+ if (noauthdebug && isTracing())
+ logger.fine("SASL authentication succeeded");
+ authenticated = true;
+ } else {
+ if (noauthdebug && isTracing())
+ logger.fine("SASL authentication failed");
+ }
+ } finally {
+ resumeTracing();
+ }
+ }
+
+ // XXX - for IMAPSaslAuthenticator access to protected method
+ OutputStream getIMAPOutputStream() {
+ return getOutputStream();
+ }
+
+ /**
+ * Handle the result response for a LOGIN or AUTHENTICATE command.
+ * Look for IMAP login REFERRAL.
+ *
+ * @param r the response
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.5
+ */
+ protected void handleLoginResult(Response r) throws ProtocolException {
+ if (hasCapability("LOGIN-REFERRALS") &&
+ (!r.isOK() || referralException))
+ checkReferral(r);
+ handleResult(r);
+ }
+
+ /**
+ * PROXYAUTH Command.
+ *
+ * @param u the PROXYAUTH user name
+ * @exception ProtocolException for protocol failures
+ * @see "Netscape/iPlanet/SunONE Messaging Server extension"
+ */
+ public void proxyauth(String u) throws ProtocolException {
+ Argument args = new Argument();
+ args.writeString(u);
+
+ simpleCommand("PROXYAUTH", args);
+ proxyAuthUser = u;
+ }
+
+ /**
+ * Get the user name used with the PROXYAUTH command.
+ * Returns null if PROXYAUTH was not used.
+ *
+ * @return the PROXYAUTH user name
+ * @since JavaMail 1.5.1
+ */
+ public String getProxyAuthUser() {
+ return proxyAuthUser;
+ }
+
+ /**
+ * UNAUTHENTICATE Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "Netscape/iPlanet/SunONE Messaging Server extension"
+ * @since JavaMail 1.5.1
+ */
+ public void unauthenticate() throws ProtocolException {
+ if (!hasCapability("X-UNAUTHENTICATE"))
+ throw new BadCommandException("UNAUTHENTICATE not supported");
+ simpleCommand("UNAUTHENTICATE", null);
+ authenticated = false;
+ }
+
+ /**
+ * ID Command, for Yahoo! Mail IMAP server.
+ *
+ * @param guid the GUID
+ * @exception ProtocolException for protocol failures
+ * @deprecated As of JavaMail 1.5.1, replaced by
+ * {@link #id(Map) id(Map<String,String>)}
+ * @since JavaMail 1.4.4
+ */
+ @Deprecated
+ public void id(String guid) throws ProtocolException {
+ // support this for now, but remove it soon
+ Map gmap = new HashMap<>();
+ gmap.put("GUID", guid);
+ id(gmap);
+ }
+
+ /**
+ * STARTTLS Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC3501, section 6.2.1"
+ */
+ public void startTLS() throws ProtocolException {
+ try {
+ super.startTLS("STARTTLS");
+ } catch (ProtocolException pex) {
+ logger.log(Level.FINE, "STARTTLS ProtocolException", pex);
+ // ProtocolException just means the command wasn't recognized,
+ // or failed. This should never happen if we check the
+ // CAPABILITY first.
+ throw pex;
+ } catch (Exception ex) {
+ logger.log(Level.FINE, "STARTTLS Exception", ex);
+ // any other exception means we have to shut down the connection
+ // generate an artificial BYE response and disconnect
+ Response[] r = { Response.byeResponse(ex) };
+ notifyResponseHandlers(r);
+ disconnect();
+ throw new ProtocolException("STARTTLS failure", ex);
+ }
+ }
+
+ /**
+ * COMPRESS Command. Only supports DEFLATE.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 4978"
+ */
+ public void compress() throws ProtocolException {
+ try {
+ super.startCompression("COMPRESS DEFLATE");
+ } catch (ProtocolException pex) {
+ logger.log(Level.FINE, "COMPRESS ProtocolException", pex);
+ // ProtocolException just means the command wasn't recognized,
+ // or failed. This should never happen if we check the
+ // CAPABILITY first.
+ throw pex;
+ } catch (Exception ex) {
+ logger.log(Level.FINE, "COMPRESS Exception", ex);
+ // any other exception means we have to shut down the connection
+ // generate an artificial BYE response and disconnect
+ Response[] r = { Response.byeResponse(ex) };
+ notifyResponseHandlers(r);
+ disconnect();
+ throw new ProtocolException("COMPRESS failure", ex);
+ }
+ }
+
+ /**
+ * Encode a mailbox name appropriately depending on whether or not
+ * the server supports UTF-8, and add the encoded name to the
+ * Argument.
+ *
+ * @param args the arguments
+ * @param name the name to encode
+ * @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
+ * @return MailboxInfo if successful
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.1"
+ */
+ public MailboxInfo select(String mbox) throws ProtocolException {
+ return select(mbox, null);
+ }
+
+ /**
+ * SELECT Command with QRESYNC data.
+ *
+ * @param mbox the mailbox name
+ * @param rd the ResyncData
+ * @return MailboxInfo if successful
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.1"
+ * @see "RFC5162, section 3.1"
+ * @since JavaMail 1.5.1
+ */
+ public MailboxInfo select(String mbox, ResyncData rd)
+ throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ if (rd != null) {
+ if (rd == ResyncData.CONDSTORE) {
+ if (!hasCapability("CONDSTORE"))
+ throw new BadCommandException("CONDSTORE not supported");
+ args.writeArgument(new Argument().writeAtom("CONDSTORE"));
+ } else {
+ if (!hasCapability("QRESYNC"))
+ throw new BadCommandException("QRESYNC not supported");
+ args.writeArgument(resyncArgs(rd));
+ }
+ }
+
+ Response[] r = command("SELECT", args);
+
+ // Note that MailboxInfo also removes those responses
+ // it knows about
+ MailboxInfo minfo = new MailboxInfo(r);
+
+ // dispatch any remaining untagged responses
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+
+ if (response.isOK()) { // command succesful
+ if (response.toString().indexOf("READ-ONLY") != -1)
+ minfo.mode = Folder.READ_ONLY;
+ else
+ minfo.mode = Folder.READ_WRITE;
+ }
+
+ handleResult(response);
+ return minfo;
+ }
+
+ /**
+ * EXAMINE Command.
+ *
+ * @param mbox the mailbox name
+ * @return MailboxInfo if successful
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.2"
+ */
+ public MailboxInfo examine(String mbox) throws ProtocolException {
+ return examine(mbox, null);
+ }
+
+ /**
+ * EXAMINE Command with QRESYNC data.
+ *
+ * @param mbox the mailbox name
+ * @param rd the ResyncData
+ * @return MailboxInfo if successful
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.2"
+ * @see "RFC5162, section 3.1"
+ * @since JavaMail 1.5.1
+ */
+ public MailboxInfo examine(String mbox, ResyncData rd)
+ throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ if (rd != null) {
+ if (rd == ResyncData.CONDSTORE) {
+ if (!hasCapability("CONDSTORE"))
+ throw new BadCommandException("CONDSTORE not supported");
+ args.writeArgument(new Argument().writeAtom("CONDSTORE"));
+ } else {
+ if (!hasCapability("QRESYNC"))
+ throw new BadCommandException("QRESYNC not supported");
+ args.writeArgument(resyncArgs(rd));
+ }
+ }
+
+ Response[] r = command("EXAMINE", args);
+
+ // Note that MailboxInfo also removes those responses
+ // it knows about
+ MailboxInfo minfo = new MailboxInfo(r);
+ minfo.mode = Folder.READ_ONLY; // Obviously
+
+ // dispatch any remaining untagged responses
+ notifyResponseHandlers(r);
+
+ handleResult(r[r.length-1]);
+ return minfo;
+ }
+
+ /**
+ * Generate a QRESYNC argument list based on the ResyncData.
+ */
+ private static Argument resyncArgs(ResyncData rd) {
+ Argument cmd = new Argument();
+ cmd.writeAtom("QRESYNC");
+ Argument args = new Argument();
+ args.writeNumber(rd.getUIDValidity());
+ args.writeNumber(rd.getModSeq());
+ UIDSet[] uids = Utility.getResyncUIDSet(rd);
+ if (uids != null)
+ args.writeString(UIDSet.toString(uids));
+ cmd.writeArgument(args);
+ return cmd;
+ }
+
+ /**
+ * ENABLE Command.
+ *
+ * @param cap the name of the capability to enable
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 5161"
+ * @since JavaMail 1.5.1
+ */
+ public void enable(String cap) throws ProtocolException {
+ if (!hasCapability("ENABLE"))
+ throw new BadCommandException("ENABLE not supported");
+ Argument args = new Argument();
+ args.writeAtom(cap);
+ simpleCommand("ENABLE", args);
+ if (enabled == null)
+ enabled = new HashSet<>();
+ enabled.add(cap.toUpperCase(Locale.ENGLISH));
+
+ // update the utf8 flag
+ utf8 = isEnabled("UTF8=ACCEPT");
+ }
+
+ /**
+ * Is the capability/extension enabled?
+ *
+ * @param cap the capability name
+ * @return true if enabled
+ * @see "RFC 5161"
+ * @since JavaMail 1.5.1
+ */
+ public boolean isEnabled(String cap) {
+ if (enabled == null)
+ return false;
+ else
+ return enabled.contains(cap.toUpperCase(Locale.ENGLISH));
+ }
+
+ /**
+ * UNSELECT Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 3691"
+ * @since JavaMail 1.4.4
+ */
+ public void unselect() throws ProtocolException {
+ if (!hasCapability("UNSELECT"))
+ throw new BadCommandException("UNSELECT not supported");
+ simpleCommand("UNSELECT", null);
+ }
+
+ /**
+ * STATUS Command.
+ *
+ * @param mbox the mailbox
+ * @param items the STATUS items to request
+ * @return STATUS results
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.10"
+ */
+ public Status status(String mbox, String[] items)
+ throws ProtocolException {
+ if (!isREV1() && !hasCapability("IMAP4SUNVERSION"))
+ // STATUS is rev1 only, however the non-rev1 SIMS2.0
+ // does support this.
+ throw new BadCommandException("STATUS not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ Argument itemArgs = new Argument();
+ if (items == null)
+ items = Status.standardItems;
+
+ for (int i = 0, len = items.length; i < len; i++)
+ itemArgs.writeAtom(items[i]);
+ args.writeArgument(itemArgs);
+
+ Response[] r = command("STATUS", args);
+
+ Status status = null;
+ Response response = r[r.length-1];
+
+ // Grab all STATUS responses
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("STATUS")) {
+ if (status == null)
+ status = new Status(ir);
+ else // collect 'em all
+ Status.add(status, new Status(ir));
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return status;
+ }
+
+ /**
+ * CREATE Command.
+ *
+ * @param mbox the mailbox to create
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.3"
+ */
+ public void create(String mbox) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ simpleCommand("CREATE", args);
+ }
+
+ /**
+ * DELETE Command.
+ *
+ * @param mbox the mailbox to delete
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.4"
+ */
+ public void delete(String mbox) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ simpleCommand("DELETE", args);
+ }
+
+ /**
+ * RENAME Command.
+ *
+ * @param o old mailbox name
+ * @param n new mailbox name
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.5"
+ */
+ public void rename(String o, String n) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, o);
+ writeMailboxName(args, n);
+
+ simpleCommand("RENAME", args);
+ }
+
+ /**
+ * SUBSCRIBE Command.
+ *
+ * @param mbox the mailbox
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.6"
+ */
+ public void subscribe(String mbox) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ simpleCommand("SUBSCRIBE", args);
+ }
+
+ /**
+ * UNSUBSCRIBE Command.
+ *
+ * @param mbox the mailbox
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.7"
+ */
+ public void unsubscribe(String mbox) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ simpleCommand("UNSUBSCRIBE", args);
+ }
+
+ /**
+ * LIST Command.
+ *
+ * @param ref reference string
+ * @param pattern pattern to list
+ * @return LIST results
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.8"
+ */
+ public ListInfo[] list(String ref, String pattern)
+ throws ProtocolException {
+ return doList("LIST", ref, pattern);
+ }
+
+ /**
+ * LSUB Command.
+ *
+ * @param ref reference string
+ * @param pattern pattern to list
+ * @return LSUB results
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.9"
+ */
+ public ListInfo[] lsub(String ref, String pattern)
+ throws ProtocolException {
+ return doList("LSUB", ref, pattern);
+ }
+
+ /**
+ * Execute the specified LIST-like command (e.g., "LIST" or "LSUB"),
+ * using the reference and pattern.
+ *
+ * @param cmd the list command
+ * @param ref the reference string
+ * @param pat the pattern
+ * @return array of ListInfo results
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.4.6
+ */
+ protected ListInfo[] doList(String cmd, String ref, String pat)
+ throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, ref);
+ writeMailboxName(args, pat);
+
+ Response[] r = command(cmd, args);
+
+ ListInfo[] linfo = null;
+ Response response = r[r.length-1];
+
+ if (response.isOK()) { // command succesful
+ List v = new ArrayList<>(1);
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals(cmd)) {
+ v.add(new ListInfo(ir));
+ r[i] = null;
+ }
+ }
+ if (v.size() > 0) {
+ linfo = v.toArray(new ListInfo[v.size()]);
+ }
+ }
+
+ // Dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return linfo;
+ }
+
+ /**
+ * APPEND Command.
+ *
+ * @param mbox the mailbox
+ * @param f the message Flags
+ * @param d the message date
+ * @param data the message data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.11"
+ */
+ public void append(String mbox, Flags f, Date d,
+ Literal data) throws ProtocolException {
+ appenduid(mbox, f, d, data, false); // ignore return value
+ }
+
+ /**
+ * APPEND Command, return uid from APPENDUID response code.
+ *
+ * @param mbox the mailbox
+ * @param f the message Flags
+ * @param d the message date
+ * @param data the message data
+ * @return APPENDUID data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.3.11"
+ */
+ public AppendUID appenduid(String mbox, Flags f, Date d,
+ Literal data) throws ProtocolException {
+ return appenduid(mbox, f, d, data, true);
+ }
+
+ public AppendUID appenduid(String mbox, Flags f, Date d,
+ Literal data, boolean uid) throws ProtocolException {
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ if (f != null) { // set Flags in appended message
+ // can't set the \Recent flag in APPEND
+ if (f.contains(Flags.Flag.RECENT)) {
+ f = new Flags(f); // copy, don't modify orig
+ f.remove(Flags.Flag.RECENT); // remove RECENT from copy
+ }
+
+ /*
+ * HACK ALERT: We want the flag_list to be written out
+ * without any checking/processing of the bytes in it. If
+ * I use writeString(), the flag_list will end up being
+ * quoted since it contains "illegal" characters. So I
+ * am depending on implementation knowledge that writeAtom()
+ * does not do any checking/processing - it just writes out
+ * the bytes. What we really need is a writeFoo() that just
+ * dumps out its argument.
+ */
+ args.writeAtom(createFlagList(f));
+ }
+ if (d != null) // set INTERNALDATE in appended message
+ args.writeString(INTERNALDATE.format(d));
+
+ args.writeBytes(data);
+
+ Response[] r = command("APPEND", args);
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ // Handle result of this command
+ handleResult(r[r.length-1]);
+
+ if (uid)
+ return getAppendUID(r[r.length-1]);
+ else
+ return null;
+ }
+
+ /**
+ * If the response contains an APPENDUID response code, extract
+ * it and return an AppendUID object with the information.
+ */
+ private AppendUID getAppendUID(Response r) {
+ if (!r.isOK())
+ return null;
+ byte b;
+ while ((b = r.readByte()) > 0 && b != (byte)'[')
+ ;
+ if (b == 0)
+ return null;
+ String s;
+ s = r.readAtom();
+ if (!s.equalsIgnoreCase("APPENDUID"))
+ return null;
+
+ long uidvalidity = r.readLong();
+ long uid = r.readLong();
+ return new AppendUID(uidvalidity, uid);
+ }
+
+ /**
+ * CHECK Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.4.1"
+ */
+ public void check() throws ProtocolException {
+ simpleCommand("CHECK", null);
+ }
+
+ /**
+ * CLOSE Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.4.2"
+ */
+ public void close() throws ProtocolException {
+ simpleCommand("CLOSE", null);
+ }
+
+ /**
+ * EXPUNGE Command.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2060, section 6.4.3"
+ */
+ public void expunge() throws ProtocolException {
+ simpleCommand("EXPUNGE", null);
+ }
+
+ /**
+ * UID EXPUNGE Command.
+ *
+ * @param set UIDs to expunge
+ * @exception ProtocolException for protocol failures
+ * @see "RFC4315, section 2"
+ */
+ public void uidexpunge(UIDSet[] set) throws ProtocolException {
+ if (!hasCapability("UIDPLUS"))
+ throw new BadCommandException("UID EXPUNGE not supported");
+ simpleCommand("UID EXPUNGE " + UIDSet.toString(set), null);
+ }
+
+ /**
+ * Fetch the BODYSTRUCTURE of the specified message.
+ *
+ * @param msgno the message number
+ * @return the BODYSTRUCTURE item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODYSTRUCTURE fetchBodyStructure(int msgno)
+ throws ProtocolException {
+ Response[] r = fetch(msgno, "BODYSTRUCTURE");
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+ if (response.isOK())
+ return FetchResponse.getItem(r, msgno, BODYSTRUCTURE.class);
+ else if (response.isNO())
+ return null;
+ else {
+ handleResult(response);
+ return null;
+ }
+ }
+
+ /**
+ * Fetch given BODY section, without marking the message
+ * as SEEN.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY peekBody(int msgno, String section)
+ throws ProtocolException {
+ return fetchBody(msgno, section, true);
+ }
+
+ /**
+ * Fetch given BODY section.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY fetchBody(int msgno, String section)
+ throws ProtocolException {
+ return fetchBody(msgno, section, false);
+ }
+
+ protected BODY fetchBody(int msgno, String section, boolean peek)
+ throws ProtocolException {
+ Response[] r;
+
+ if (section == null)
+ section = "";
+ String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]";
+ return fetchSectionBody(msgno, section, body);
+ }
+
+ /**
+ * Partial FETCH of given BODY section, without setting SEEN flag.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @param start starting byte count
+ * @param size number of bytes to fetch
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY peekBody(int msgno, String section, int start, int size)
+ throws ProtocolException {
+ return fetchBody(msgno, section, start, size, true, null);
+ }
+
+ /**
+ * Partial FETCH of given BODY section.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @param start starting byte count
+ * @param size number of bytes to fetch
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY fetchBody(int msgno, String section, int start, int size)
+ throws ProtocolException {
+ return fetchBody(msgno, section, start, size, false, null);
+ }
+
+ /**
+ * Partial FETCH of given BODY section, without setting SEEN flag.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @param start starting byte count
+ * @param size number of bytes to fetch
+ * @param ba the buffer into which to read the response
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY peekBody(int msgno, String section, int start, int size,
+ ByteArray ba) throws ProtocolException {
+ return fetchBody(msgno, section, start, size, true, ba);
+ }
+
+ /**
+ * Partial FETCH of given BODY section.
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @param start starting byte count
+ * @param size number of bytes to fetch
+ * @param ba the buffer into which to read the response
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ public BODY fetchBody(int msgno, String section, int start, int size,
+ ByteArray ba) throws ProtocolException {
+ return fetchBody(msgno, section, start, size, false, ba);
+ }
+
+ protected BODY fetchBody(int msgno, String section, int start, int size,
+ boolean peek, ByteArray ba) throws ProtocolException {
+ this.ba = ba; // save for later use by getResponseBuffer
+ if (section == null)
+ section = "";
+ String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]<" +
+ String.valueOf(start) + "." +
+ String.valueOf(size) + ">";
+ return fetchSectionBody(msgno, section, body);
+ }
+
+ /**
+ * Fetch the given body section of the given message, using the
+ * body string "body".
+ *
+ * @param msgno the message number
+ * @param section the body section
+ * @param body the body string
+ * @return the BODY item
+ * @exception ProtocolException for protocol failures
+ */
+ protected BODY fetchSectionBody(int msgno, String section, String body)
+ throws ProtocolException {
+ Response[] r;
+
+ r = fetch(msgno, body);
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+ if (response.isOK()) {
+ List bl = FetchResponse.getItems(r, msgno, BODY.class);
+ if (bl.size() == 1)
+ return bl.get(0); // the common case
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("got " + bl.size() +
+ " BODY responses for section " + section);
+ // more then one BODY response, have to find the right one
+ for (BODY br : bl) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("got BODY section " + br.getSection());
+ if (br.getSection().equalsIgnoreCase(section))
+ return br; // that's the one!
+ }
+ return null; // couldn't find it
+ } else if (response.isNO())
+ return null;
+ else {
+ handleResult(response);
+ return null;
+ }
+ }
+
+ /**
+ * Return a buffer to read a response into.
+ * The buffer is provided by fetchBody and is
+ * used only once.
+ *
+ * @return the buffer to use
+ */
+ @Override
+ protected ByteArray getResponseBuffer() {
+ ByteArray ret = ba;
+ ba = null;
+ return ret;
+ }
+
+ /**
+ * Fetch the specified RFC822 Data item. 'what' names
+ * the item to be fetched. 'what' can be null
+ * to fetch the whole message.
+ *
+ * @param msgno the message number
+ * @param what the item to fetch
+ * @return the RFC822DATA item
+ * @exception ProtocolException for protocol failures
+ */
+ public RFC822DATA fetchRFC822(int msgno, String what)
+ throws ProtocolException {
+ Response[] r = fetch(msgno,
+ what == null ? "RFC822" : "RFC822." + what
+ );
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+ if (response.isOK())
+ return FetchResponse.getItem(r, msgno, RFC822DATA.class);
+ else if (response.isNO())
+ return null;
+ else {
+ handleResult(response);
+ return null;
+ }
+ }
+
+ /**
+ * Fetch the FLAGS for the given message.
+ *
+ * @param msgno the message number
+ * @return the Flags
+ * @exception ProtocolException for protocol failures
+ */
+ public Flags fetchFlags(int msgno) throws ProtocolException {
+ Flags flags = null;
+ Response[] r = fetch(msgno, "FLAGS");
+
+ // Search for our FLAGS response
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (r[i] == null ||
+ !(r[i] instanceof FetchResponse) ||
+ ((FetchResponse)r[i]).getNumber() != msgno)
+ continue;
+
+ FetchResponse fr = (FetchResponse)r[i];
+ if ((flags = fr.getItem(FLAGS.class)) != null) {
+ r[i] = null; // remove this response
+ break;
+ }
+ }
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+ return flags;
+ }
+
+ /**
+ * Fetch the IMAP UID for the given message.
+ *
+ * @param msgno the message number
+ * @return the UID
+ * @exception ProtocolException for protocol failures
+ */
+ public UID fetchUID(int msgno) throws ProtocolException {
+ Response[] r = fetch(msgno, "UID");
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+ if (response.isOK())
+ return FetchResponse.getItem(r, msgno, UID.class);
+ else if (response.isNO()) // XXX: Issue NOOP ?
+ return null;
+ else {
+ handleResult(response);
+ return null; // NOTREACHED
+ }
+ }
+
+ /**
+ * Fetch the IMAP MODSEQ for the given message.
+ *
+ * @param msgno the message number
+ * @return the MODSEQ
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.1
+ */
+ public MODSEQ fetchMODSEQ(int msgno) throws ProtocolException {
+ Response[] r = fetch(msgno, "MODSEQ");
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ Response response = r[r.length-1];
+ if (response.isOK())
+ return FetchResponse.getItem(r, msgno, MODSEQ.class);
+ else if (response.isNO()) // XXX: Issue NOOP ?
+ return null;
+ else {
+ handleResult(response);
+ return null; // NOTREACHED
+ }
+ }
+
+ /**
+ * Get the sequence number for the given UID. Nothing is returned;
+ * the FETCH UID response must be handled by the reponse handler,
+ * along with any possible EXPUNGE responses, to ensure that the
+ * UID is matched with the correct sequence number.
+ *
+ * @param uid the UID
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.3
+ */
+ public void fetchSequenceNumber(long uid) throws ProtocolException {
+ Response[] r = fetch(String.valueOf(uid), "UID", true);
+
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+ }
+
+ /**
+ * Get the sequence numbers for UIDs ranging from start till end.
+ * Since the range may be large and sparse, an array of the UIDs actually
+ * found is returned. The caller must map these to messages after
+ * the FETCH UID responses have been handled by the reponse handler,
+ * along with any possible EXPUNGE responses, to ensure that the
+ * UIDs are matched with the correct sequence numbers.
+ *
+ * @param start first UID
+ * @param end last UID
+ * @return array of sequence numbers
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.3
+ */
+ public long[] fetchSequenceNumbers(long start, long end)
+ throws ProtocolException {
+ Response[] r = fetch(String.valueOf(start) + ":" +
+ (end == UIDFolder.LASTUID ? "*" :
+ String.valueOf(end)),
+ "UID", true);
+
+ UID u;
+ List v = new ArrayList<>();
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (r[i] == null || !(r[i] instanceof FetchResponse))
+ continue;
+
+ FetchResponse fr = (FetchResponse)r[i];
+ if ((u = fr.getItem(UID.class)) != null)
+ v.add(u);
+ }
+
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+
+ long[] lv = new long[v.size()];
+ for (int i = 0; i < v.size(); i++)
+ lv[i] = v.get(i).uid;
+ return lv;
+ }
+
+ /**
+ * Get the sequence numbers for UIDs specified in the array.
+ * Nothing is returned. The caller must map the UIDs to messages after
+ * the FETCH UID responses have been handled by the reponse handler,
+ * along with any possible EXPUNGE responses, to ensure that the
+ * UIDs are matched with the correct sequence numbers.
+ *
+ * @param uids the UIDs
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.5.3
+ */
+ public void fetchSequenceNumbers(long[] uids) throws ProtocolException {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < uids.length; i++) {
+ if (i > 0)
+ sb.append(",");
+ sb.append(String.valueOf(uids[i]));
+ }
+
+ Response[] r = fetch(sb.toString(), "UID", true);
+
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+ }
+
+ /**
+ * Get the sequence numbers for messages changed since the given
+ * modseq and with UIDs ranging from start till end.
+ * Also, prefetch the flags for the returned messages.
+ *
+ * @param start first UID
+ * @param end last UID
+ * @param modseq the MODSEQ
+ * @return array of sequence numbers
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 4551"
+ * @since JavaMail 1.5.1
+ */
+ public int[] uidfetchChangedSince(long start, long end, long modseq)
+ throws ProtocolException {
+ String msgSequence = String.valueOf(start) + ":" +
+ (end == UIDFolder.LASTUID ? "*" :
+ String.valueOf(end));
+ Response[] r = command("UID FETCH " + msgSequence +
+ " (FLAGS) (CHANGEDSINCE " + String.valueOf(modseq) + ")", null);
+
+ List v = new ArrayList<>();
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (r[i] == null || !(r[i] instanceof FetchResponse))
+ continue;
+
+ FetchResponse fr = (FetchResponse)r[i];
+ v.add(Integer.valueOf(fr.getNumber()));
+ }
+
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+
+ // Copy the list into 'matches'
+ int vsize = v.size();
+ int[] matches = new int[vsize];
+ for (int i = 0; i < vsize; i++)
+ matches[i] = v.get(i).intValue();
+ return matches;
+ }
+
+ public Response[] fetch(MessageSet[] msgsets, String what)
+ throws ProtocolException {
+ return fetch(MessageSet.toString(msgsets), what, false);
+ }
+
+ public Response[] fetch(int start, int end, String what)
+ throws ProtocolException {
+ return fetch(String.valueOf(start) + ":" + String.valueOf(end),
+ what, false);
+ }
+
+ public Response[] fetch(int msg, String what)
+ throws ProtocolException {
+ return fetch(String.valueOf(msg), what, false);
+ }
+
+ private Response[] fetch(String msgSequence, String what, boolean uid)
+ throws ProtocolException {
+ if (uid)
+ return command("UID FETCH " + msgSequence +" (" + what + ")",null);
+ else
+ return command("FETCH " + msgSequence + " (" + what + ")", null);
+ }
+
+ /**
+ * COPY command.
+ *
+ * @param msgsets the messages to copy
+ * @param mbox the mailbox to copy them to
+ * @exception ProtocolException for protocol failures
+ */
+ public void copy(MessageSet[] msgsets, String mbox)
+ throws ProtocolException {
+ copyuid(MessageSet.toString(msgsets), mbox, false);
+ }
+
+ /**
+ * COPY command.
+ *
+ * @param start start message number
+ * @param end end message number
+ * @param mbox the mailbox to copy them to
+ * @exception ProtocolException for protocol failures
+ */
+ public void copy(int start, int end, String mbox)
+ throws ProtocolException {
+ copyuid(String.valueOf(start) + ":" + String.valueOf(end),
+ mbox, false);
+ }
+
+ /**
+ * COPY command, return uid from COPYUID response code.
+ *
+ * @param msgsets the messages to copy
+ * @param mbox the mailbox to copy them to
+ * @return COPYUID response data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 4315, section 3"
+ */
+ public CopyUID copyuid(MessageSet[] msgsets, String mbox)
+ throws ProtocolException {
+ return copyuid(MessageSet.toString(msgsets), mbox, true);
+ }
+
+ /**
+ * COPY command, return uid from COPYUID response code.
+ *
+ * @param start start message number
+ * @param end end message number
+ * @param mbox the mailbox to copy them to
+ * @return COPYUID response data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 4315, section 3"
+ */
+ public CopyUID copyuid(int start, int end, String mbox)
+ throws ProtocolException {
+ return copyuid(String.valueOf(start) + ":" + String.valueOf(end),
+ mbox, true);
+ }
+
+ private CopyUID copyuid(String msgSequence, String mbox, boolean uid)
+ throws ProtocolException {
+ if (uid && !hasCapability("UIDPLUS"))
+ throw new BadCommandException("UIDPLUS not supported");
+
+ Argument args = new Argument();
+ args.writeAtom(msgSequence);
+ writeMailboxName(args, mbox);
+
+ Response[] r = command("COPY", args);
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ // Handle result of this command
+ handleResult(r[r.length-1]);
+
+ if (uid)
+ return getCopyUID(r);
+ else
+ return null;
+ }
+
+ /**
+ * MOVE command.
+ *
+ * @param msgsets the messages to move
+ * @param mbox the mailbox to move them to
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 6851"
+ * @since JavaMail 1.5.4
+ */
+ public void move(MessageSet[] msgsets, String mbox)
+ throws ProtocolException {
+ moveuid(MessageSet.toString(msgsets), mbox, false);
+ }
+
+ /**
+ * MOVE command.
+ *
+ * @param start start message number
+ * @param end end message number
+ * @param mbox the mailbox to move them to
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 6851"
+ * @since JavaMail 1.5.4
+ */
+ public void move(int start, int end, String mbox)
+ throws ProtocolException {
+ moveuid(String.valueOf(start) + ":" + String.valueOf(end),
+ mbox, false);
+ }
+
+ /**
+ * MOVE Command, return uid from COPYUID response code.
+ *
+ * @param msgsets the messages to move
+ * @param mbox the mailbox to move them to
+ * @return COPYUID response data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 6851"
+ * @see "RFC 4315, section 3"
+ * @since JavaMail 1.5.4
+ */
+ public CopyUID moveuid(MessageSet[] msgsets, String mbox)
+ throws ProtocolException {
+ return moveuid(MessageSet.toString(msgsets), mbox, true);
+ }
+
+ /**
+ * MOVE Command, return uid from COPYUID response code.
+ *
+ * @param start start message number
+ * @param end end message number
+ * @param mbox the mailbox to move them to
+ * @return COPYUID response data
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 6851"
+ * @see "RFC 4315, section 3"
+ * @since JavaMail 1.5.4
+ */
+ public CopyUID moveuid(int start, int end, String mbox)
+ throws ProtocolException {
+ return moveuid(String.valueOf(start) + ":" + String.valueOf(end),
+ mbox, true);
+ }
+
+ /**
+ * MOVE Command, return uid from COPYUID response code.
+ *
+ * @see "RFC 6851"
+ * @see "RFC 4315, section 3"
+ * @since JavaMail 1.5.4
+ */
+ private CopyUID moveuid(String msgSequence, String mbox, boolean uid)
+ throws ProtocolException {
+ if (!hasCapability("MOVE"))
+ throw new BadCommandException("MOVE not supported");
+ if (uid && !hasCapability("UIDPLUS"))
+ throw new BadCommandException("UIDPLUS not supported");
+
+ Argument args = new Argument();
+ args.writeAtom(msgSequence);
+ writeMailboxName(args, mbox);
+
+ Response[] r = command("MOVE", args);
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+
+ // Handle result of this command
+ handleResult(r[r.length-1]);
+
+ if (uid)
+ return getCopyUID(r);
+ else
+ return null;
+ }
+
+ /**
+ * If the response contains a COPYUID response code, extract
+ * it and return a CopyUID object with the information.
+ *
+ * @param rr the responses to examine
+ * @return the COPYUID response code data, or null if not found
+ * @since JavaMail 1.5.4
+ */
+ protected CopyUID getCopyUID(Response[] rr) {
+ // most likely in the last response, so start there and work backward
+ for (int i = rr.length - 1; i >= 0; i--) {
+ Response r = rr[i];
+ if (r == null || !r.isOK())
+ continue;
+ byte b;
+ while ((b = r.readByte()) > 0 && b != (byte)'[')
+ ;
+ if (b == 0)
+ continue;
+ String s;
+ s = r.readAtom();
+ if (!s.equalsIgnoreCase("COPYUID"))
+ continue;
+
+ // XXX - need to merge more than one response for MOVE?
+ long uidvalidity = r.readLong();
+ String src = r.readAtom();
+ String dst = r.readAtom();
+ return new CopyUID(uidvalidity,
+ UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
+ }
+ return null;
+ }
+
+ public void storeFlags(MessageSet[] msgsets, Flags flags, boolean set)
+ throws ProtocolException {
+ storeFlags(MessageSet.toString(msgsets), flags, set);
+ }
+
+ public void storeFlags(int start, int end, Flags flags, boolean set)
+ throws ProtocolException {
+ storeFlags(String.valueOf(start) + ":" + String.valueOf(end),
+ flags, set);
+ }
+
+ /**
+ * Set the specified flags on this message.
+ *
+ * @param msg the message number
+ * @param flags the flags
+ * @param set true to set, false to clear
+ * @exception ProtocolException for protocol failures
+ */
+ public void storeFlags(int msg, Flags flags, boolean set)
+ throws ProtocolException {
+ storeFlags(String.valueOf(msg), flags, set);
+ }
+
+ private void storeFlags(String msgset, Flags flags, boolean set)
+ throws ProtocolException {
+ Response[] r;
+ if (set)
+ r = command("STORE " + msgset + " +FLAGS " +
+ createFlagList(flags), null);
+ else
+ r = command("STORE " + msgset + " -FLAGS " +
+ createFlagList(flags), null);
+
+ // Dispatch untagged responses
+ notifyResponseHandlers(r);
+ handleResult(r[r.length-1]);
+ }
+
+ /**
+ * Creates an IMAP flag_list from the given Flags object.
+ *
+ * @param flags the flags
+ * @return the IMAP flag_list
+ * @since JavaMail 1.5.4
+ */
+ protected String createFlagList(Flags flags) {
+ StringBuilder sb = new StringBuilder("("); // start of flag_list
+
+ Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+ boolean first = true;
+ for (int i = 0; i < sf.length; i++) {
+ String s;
+ Flags.Flag f = sf[i];
+ if (f == Flags.Flag.ANSWERED)
+ s = "\\Answered";
+ else if (f == Flags.Flag.DELETED)
+ s = "\\Deleted";
+ else if (f == Flags.Flag.DRAFT)
+ s = "\\Draft";
+ else if (f == Flags.Flag.FLAGGED)
+ s = "\\Flagged";
+ else if (f == Flags.Flag.RECENT)
+ s = "\\Recent";
+ else if (f == Flags.Flag.SEEN)
+ s = "\\Seen";
+ else
+ continue; // skip it
+ if (first)
+ first = false;
+ else
+ sb.append(' ');
+ sb.append(s);
+ }
+
+ String[] uf = flags.getUserFlags(); // get the user flag strings
+ for (int i = 0; i < uf.length; i++) {
+ if (first)
+ first = false;
+ else
+ sb.append(' ');
+ sb.append(uf[i]);
+ }
+
+ sb.append(")"); // terminate flag_list
+ return sb.toString();
+ }
+
+ /**
+ * Issue the given search criterion on the specified message sets.
+ * Returns array of matching sequence numbers. An empty array
+ * is returned if no matches are found.
+ *
+ * @param msgsets array of MessageSets
+ * @param term SearchTerm
+ * @return array of matching sequence numbers.
+ * @exception ProtocolException for protocol failures
+ * @exception SearchException for search failures
+ */
+ public int[] search(MessageSet[] msgsets, SearchTerm term)
+ throws ProtocolException, SearchException {
+ return search(MessageSet.toString(msgsets), term);
+ }
+
+ /**
+ * Issue the given search criterion on all messages in this folder.
+ * Returns array of matching sequence numbers. An empty array
+ * is returned if no matches are found.
+ *
+ * @param term SearchTerm
+ * @return array of matching sequence numbers.
+ * @exception ProtocolException for protocol failures
+ * @exception SearchException for search failures
+ */
+ public int[] search(SearchTerm term)
+ throws ProtocolException, SearchException {
+ return search("ALL", term);
+ }
+
+ /*
+ * Apply the given SearchTerm on the specified sequence.
+ * Returns array of matching sequence numbers. Note that an empty
+ * array is returned for no matches.
+ */
+ private int[] search(String msgSequence, SearchTerm term)
+ throws ProtocolException, SearchException {
+ // Check if the search "text" terms contain only ASCII chars,
+ // or if utf8 support has been enabled (in which case CHARSET
+ // is not allowed; see RFC 6855, section 3, last paragraph)
+ if (supportsUtf8() || SearchSequence.isAscii(term)) {
+ try {
+ return issueSearch(msgSequence, term, null);
+ } catch (IOException ioex) { /* will not happen */ }
+ }
+
+ /*
+ * The search "text" terms do contain non-ASCII chars and utf8
+ * support has not been enabled. We need to use:
+ * "SEARCH CHARSET ..."
+ * The charsets we try to use are UTF-8 and the locale's
+ * default charset. If the server supports UTF-8, great,
+ * always use it. Else we try to use the default charset.
+ */
+
+ // Cycle thru the list of charsets
+ for (int i = 0; i < searchCharsets.length; i++) {
+ if (searchCharsets[i] == null)
+ continue;
+
+ try {
+ return issueSearch(msgSequence, term, searchCharsets[i]);
+ } catch (CommandFailedException cfx) {
+ /*
+ * Server returned NO. For now, I'll just assume that
+ * this indicates that this charset is unsupported.
+ * We can check the BADCHARSET response code once
+ * that's spec'd into the IMAP RFC ..
+ */
+ searchCharsets[i] = null;
+ continue;
+ } catch (IOException ioex) {
+ /* Charset conversion failed. Try the next one */
+ continue;
+ } catch (ProtocolException pex) {
+ throw pex;
+ } catch (SearchException sex) {
+ throw sex;
+ }
+ }
+
+ // No luck.
+ throw new SearchException("Search failed");
+ }
+
+ /* Apply the given SearchTerm on the specified sequence, using the
+ * given charset.
+ * Returns array of matching sequence numbers. Note that an empty
+ * array is returned for no matches.
+ */
+ private int[] issueSearch(String msgSequence, SearchTerm term,
+ String charset)
+ throws ProtocolException, SearchException, IOException {
+
+ // Generate a search-sequence with the given charset
+ Argument args = getSearchSequence().generateSequence(term,
+ charset == null ? null :
+ MimeUtility.javaCharset(charset)
+ );
+ args.writeAtom(msgSequence);
+
+ Response[] r;
+
+ if (charset == null) // text is all US-ASCII
+ r = command("SEARCH", args);
+ else
+ r = command("SEARCH CHARSET " + charset, args);
+
+ Response response = r[r.length-1];
+ int[] matches = null;
+
+ // Grab all SEARCH responses
+ if (response.isOK()) { // command succesful
+ List v = new ArrayList<>();
+ int num;
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ // There *will* be one SEARCH response.
+ if (ir.keyEquals("SEARCH")) {
+ while ((num = ir.readNumber()) != -1)
+ v.add(Integer.valueOf(num));
+ r[i] = null;
+ }
+ }
+
+ // Copy the list into 'matches'
+ int vsize = v.size();
+ matches = new int[vsize];
+ for (int i = 0; i < vsize; i++)
+ matches[i] = v.get(i).intValue();
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return matches;
+ }
+
+ /**
+ * Get the SearchSequence object.
+ * The SearchSequence object instance is saved in the searchSequence
+ * field. Subclasses of IMAPProtocol may override this method to
+ * return a subclass of SearchSequence, in order to add support for
+ * product-specific search terms.
+ *
+ * @return the SearchSequence
+ * @since JavaMail 1.4.6
+ */
+ protected SearchSequence getSearchSequence() {
+ if (searchSequence == null)
+ searchSequence = new SearchSequence(this);
+ return searchSequence;
+ }
+
+ /**
+ * Sort messages in the folder according to the specified sort criteria.
+ * If the search term is not null, limit the sort to only the messages
+ * that match the search term.
+ * Returns an array of sorted sequence numbers. An empty array
+ * is returned if no matches are found.
+ *
+ * @param term sort criteria
+ * @param sterm SearchTerm
+ * @return array of matching sequence numbers.
+ * @exception ProtocolException for protocol failures
+ * @exception SearchException for search failures
+ *
+ * @see "RFC 5256"
+ * @since JavaMail 1.4.4
+ */
+ public int[] sort(SortTerm[] term, SearchTerm sterm)
+ throws ProtocolException, SearchException {
+ if (!hasCapability("SORT*"))
+ throw new BadCommandException("SORT not supported");
+
+ if (term == null || term.length == 0)
+ throw new BadCommandException("Must have at least one sort term");
+
+ Argument args = new Argument();
+ Argument sargs = new Argument();
+ for (int i = 0; i < term.length; i++)
+ sargs.writeAtom(term[i].toString());
+ args.writeArgument(sargs); // sort criteria
+
+ args.writeAtom("UTF-8"); // charset specification
+ if (sterm != null) {
+ try {
+ args.append(
+ getSearchSequence().generateSequence(sterm, "UTF-8"));
+ } catch (IOException ioex) {
+ // should never happen
+ throw new SearchException(ioex.toString());
+ }
+ } else
+ args.writeAtom("ALL");
+
+ Response[] r = command("SORT", args);
+ Response response = r[r.length-1];
+ int[] matches = null;
+
+ // Grab all SORT responses
+ if (response.isOK()) { // command succesful
+ List v = new ArrayList<>();
+ int num;
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("SORT")) {
+ while ((num = ir.readNumber()) != -1)
+ v.add(Integer.valueOf(num));
+ r[i] = null;
+ }
+ }
+
+ // Copy the list into 'matches'
+ int vsize = v.size();
+ matches = new int[vsize];
+ for (int i = 0; i < vsize; i++)
+ matches[i] = v.get(i).intValue();
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return matches;
+ }
+
+ /**
+ * NAMESPACE Command.
+ *
+ * @return the namespaces
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2342"
+ */
+ public Namespaces namespace() throws ProtocolException {
+ if (!hasCapability("NAMESPACE"))
+ throw new BadCommandException("NAMESPACE not supported");
+
+ Response[] r = command("NAMESPACE", null);
+
+ Namespaces namespace = null;
+ Response response = r[r.length-1];
+
+ // Grab NAMESPACE response
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("NAMESPACE")) {
+ if (namespace == null)
+ namespace = new Namespaces(ir);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return namespace;
+ }
+
+ /**
+ * GETQUOTAROOT Command.
+ *
+ * Returns an array of Quota objects, representing the quotas
+ * for this mailbox and, indirectly, the quotaroots for this
+ * mailbox.
+ *
+ * @param mbox the mailbox
+ * @return array of Quota objects
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2087"
+ */
+ public Quota[] getQuotaRoot(String mbox) throws ProtocolException {
+ if (!hasCapability("QUOTA"))
+ throw new BadCommandException("GETQUOTAROOT not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ Response[] r = command("GETQUOTAROOT", args);
+
+ Response response = r[r.length-1];
+
+ Map tab = new HashMap<>();
+
+ // Grab all QUOTAROOT and QUOTA responses
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("QUOTAROOT")) {
+ // quotaroot_response
+ // ::= "QUOTAROOT" SP astring *(SP astring)
+
+ // read name of mailbox and throw away
+ ir.readAtomString();
+ // for each quotaroot add a placeholder quota
+ String root = null;
+ while ((root = ir.readAtomString()) != null &&
+ root.length() > 0)
+ tab.put(root, new Quota(root));
+ r[i] = null;
+ } else if (ir.keyEquals("QUOTA")) {
+ Quota quota = parseQuota(ir);
+ Quota q = tab.get(quota.quotaRoot);
+ if (q != null && q.resources != null) {
+ // merge resources
+ int newl = q.resources.length + quota.resources.length;
+ Quota.Resource[] newr = new Quota.Resource[newl];
+ System.arraycopy(q.resources, 0, newr, 0,
+ q.resources.length);
+ System.arraycopy(quota.resources, 0,
+ newr, q.resources.length, quota.resources.length);
+ quota.resources = newr;
+ }
+ tab.put(quota.quotaRoot, quota);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+
+ return tab.values().toArray(new Quota[tab.size()]);
+ }
+
+ /**
+ * GETQUOTA Command.
+ *
+ * Returns an array of Quota objects, representing the quotas
+ * for this quotaroot.
+ *
+ * @param root the quotaroot
+ * @return the quotas
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2087"
+ */
+ public Quota[] getQuota(String root) throws ProtocolException {
+ if (!hasCapability("QUOTA"))
+ throw new BadCommandException("QUOTA not supported");
+
+ Argument args = new Argument();
+ args.writeString(root); // XXX - could be UTF-8?
+
+ Response[] r = command("GETQUOTA", args);
+
+ Quota quota = null;
+ List v = new ArrayList<>();
+ Response response = r[r.length-1];
+
+ // Grab all QUOTA responses
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("QUOTA")) {
+ quota = parseQuota(ir);
+ v.add(quota);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return v.toArray(new Quota[v.size()]);
+ }
+
+ /**
+ * SETQUOTA Command.
+ *
+ * Set the indicated quota on the corresponding quotaroot.
+ *
+ * @param quota the quota to set
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2087"
+ */
+ public void setQuota(Quota quota) throws ProtocolException {
+ if (!hasCapability("QUOTA"))
+ throw new BadCommandException("QUOTA not supported");
+
+ Argument args = new Argument();
+ 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++) {
+ qargs.writeAtom(quota.resources[i].name);
+ qargs.writeNumber(quota.resources[i].limit);
+ }
+ }
+ args.writeArgument(qargs);
+
+ Response[] r = command("SETQUOTA", args);
+ Response response = r[r.length-1];
+
+ // XXX - It's not clear from the RFC whether the SETQUOTA command
+ // will provoke untagged QUOTA responses. If it does, perhaps
+ // we should grab them here and return them?
+
+ /*
+ Quota quota = null;
+ List v = new ArrayList();
+
+ // Grab all QUOTA responses
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("QUOTA")) {
+ quota = parseQuota(ir);
+ v.add(quota);
+ r[i] = null;
+ }
+ }
+ }
+ */
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ /*
+ return v.toArray(new Quota[v.size()]);
+ */
+ }
+
+ /**
+ * Parse a QUOTA response.
+ */
+ private Quota parseQuota(Response r) throws ParsingException {
+ // quota_response ::= "QUOTA" SP astring SP quota_list
+ String quotaRoot = r.readAtomString(); // quotaroot ::= astring
+ Quota q = new Quota(quotaRoot);
+ r.skipSpaces();
+ // quota_list ::= "(" #quota_resource ")"
+ if (r.readByte() != '(')
+ throw new ParsingException("parse error in QUOTA");
+
+ List v = new ArrayList<>();
+ while (!r.isNextNonSpace(')')) {
+ // quota_resource ::= atom SP number SP number
+ String name = r.readAtom();
+ if (name != null) {
+ long usage = r.readLong();
+ long limit = r.readLong();
+ Quota.Resource res = new Quota.Resource(name, usage, limit);
+ v.add(res);
+ }
+ }
+ q.resources = v.toArray(new Quota.Resource[v.size()]);
+ return q;
+ }
+
+
+ /**
+ * SETACL Command.
+ *
+ * @param mbox the mailbox
+ * @param modifier the ACL modifier
+ * @param acl the ACL
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2086"
+ */
+ public void setACL(String mbox, char modifier, ACL acl)
+ throws ProtocolException {
+ if (!hasCapability("ACL"))
+ throw new BadCommandException("ACL not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+ args.writeString(acl.getName());
+ String rights = acl.getRights().toString();
+ if (modifier == '+' || modifier == '-')
+ rights = modifier + rights;
+ args.writeString(rights);
+
+ Response[] r = command("SETACL", args);
+ Response response = r[r.length-1];
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ }
+
+ /**
+ * DELETEACL Command.
+ *
+ * @param mbox the mailbox
+ * @param user the user
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2086"
+ */
+ public void deleteACL(String mbox, String user) throws ProtocolException {
+ if (!hasCapability("ACL"))
+ throw new BadCommandException("ACL not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+ args.writeString(user); // XXX - could be UTF-8?
+
+ Response[] r = command("DELETEACL", args);
+ Response response = r[r.length-1];
+
+ // dispatch untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ }
+
+ /**
+ * GETACL Command.
+ *
+ * @param mbox the mailbox
+ * @return the ACL array
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2086"
+ */
+ public ACL[] getACL(String mbox) throws ProtocolException {
+ if (!hasCapability("ACL"))
+ throw new BadCommandException("ACL not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ Response[] r = command("GETACL", args);
+ Response response = r[r.length-1];
+
+ // Grab all ACL responses
+ List v = new ArrayList<>();
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("ACL")) {
+ // acl_data ::= "ACL" SPACE mailbox
+ // *(SPACE identifier SPACE rights)
+ // read name of mailbox and throw away
+ ir.readAtomString();
+ String name = null;
+ while ((name = ir.readAtomString()) != null) {
+ String rights = ir.readAtomString();
+ if (rights == null)
+ break;
+ ACL acl = new ACL(name, new Rights(rights));
+ v.add(acl);
+ }
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return v.toArray(new ACL[v.size()]);
+ }
+
+ /**
+ * LISTRIGHTS Command.
+ *
+ * @param mbox the mailbox
+ * @param user the user rights to return
+ * @return the rights array
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2086"
+ */
+ public Rights[] listRights(String mbox, String user)
+ throws ProtocolException {
+ if (!hasCapability("ACL"))
+ throw new BadCommandException("ACL not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+ args.writeString(user); // XXX - could be UTF-8?
+
+ Response[] r = command("LISTRIGHTS", args);
+ Response response = r[r.length-1];
+
+ // Grab LISTRIGHTS response
+ List v = new ArrayList<>();
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("LISTRIGHTS")) {
+ // listrights_data ::= "LISTRIGHTS" SPACE mailbox
+ // SPACE identifier SPACE rights *(SPACE rights)
+ // read name of mailbox and throw away
+ ir.readAtomString();
+ // read identifier and throw away
+ ir.readAtomString();
+ String rights;
+ while ((rights = ir.readAtomString()) != null)
+ v.add(new Rights(rights));
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return v.toArray(new Rights[v.size()]);
+ }
+
+ /**
+ * MYRIGHTS Command.
+ *
+ * @param mbox the mailbox
+ * @return the rights
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2086"
+ */
+ public Rights myRights(String mbox) throws ProtocolException {
+ if (!hasCapability("ACL"))
+ throw new BadCommandException("ACL not supported");
+
+ Argument args = new Argument();
+ writeMailboxName(args, mbox);
+
+ Response[] r = command("MYRIGHTS", args);
+ Response response = r[r.length-1];
+
+ // Grab MYRIGHTS response
+ Rights rights = null;
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("MYRIGHTS")) {
+ // myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights
+ // read name of mailbox and throw away
+ ir.readAtomString();
+ String rs = ir.readAtomString();
+ if (rights == null)
+ rights = new Rights(rs);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return rights;
+ }
+
+ /*
+ * The tag used on the IDLE command. Set by idleStart() and
+ * used in processIdleResponse() to determine if the response
+ * is the matching end tag.
+ */
+ private volatile String idleTag;
+
+ /**
+ * IDLE Command.
+ *
+ * If the server supports the IDLE command extension, the IDLE
+ * command is issued and this method blocks until a response has
+ * been received. Once the first response has been received, the
+ * IDLE command is terminated and all responses are collected and
+ * handled and this method returns.
+ *
+ * Note that while this method is blocked waiting for a response,
+ * no other threads may issue any commands to the server that would
+ * use this same connection.
+ *
+ * @exception ProtocolException for protocol failures
+ * @see "RFC2177"
+ * @since JavaMail 1.4.1
+ */
+ public synchronized void idleStart() throws ProtocolException {
+ if (!hasCapability("IDLE"))
+ throw new BadCommandException("IDLE not supported");
+
+ List v = new ArrayList<>();
+ boolean done = false;
+ Response r = null;
+
+ // write the command
+ try {
+ idleTag = writeCommand("IDLE", null);
+ } catch (LiteralException lex) {
+ v.add(lex.getResponse());
+ done = true;
+ } catch (Exception ex) {
+ // Convert this into a BYE response
+ v.add(Response.byeResponse(ex));
+ done = true;
+ }
+
+ while (!done) {
+ try {
+ r = readResponse();
+ } catch (IOException ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ } catch (ProtocolException pex) {
+ continue; // skip this response
+ }
+
+ v.add(r);
+
+ if (r.isContinuation() || r.isBYE())
+ done = true;
+ }
+
+ Response[] responses = v.toArray(new Response[v.size()]);
+ r = responses[responses.length-1];
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(responses);
+ if (!r.isContinuation())
+ handleResult(r);
+ }
+
+ /**
+ * While an IDLE command is in progress, read a response
+ * sent from the server. The response is read with no locks
+ * held so that when the read blocks waiting for the response
+ * from the server it's not holding locks that would prevent
+ * other threads from interrupting the IDLE command.
+ *
+ * @return the response
+ * @since JavaMail 1.4.1
+ */
+ public synchronized Response readIdleResponse() {
+ if (idleTag == null)
+ return null; // IDLE not in progress
+ Response r = null;
+ try {
+ r = readResponse();
+ } catch (IOException ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ } catch (ProtocolException pex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(pex);
+ }
+ return r;
+ }
+
+ /**
+ * Process a response returned by readIdleResponse().
+ * This method will be called with appropriate locks
+ * held so that the processing of the response is safe.
+ *
+ * @param r the response
+ * @return true if IDLE is done
+ * @exception ProtocolException for protocol failures
+ * @since JavaMail 1.4.1
+ */
+ public boolean processIdleResponse(Response r) throws ProtocolException {
+ Response[] responses = new Response[1];
+ responses[0] = r;
+ boolean done = false; // done reading responses?
+ notifyResponseHandlers(responses);
+
+ if (r.isBYE()) // shouldn't wait for command completion response
+ done = true;
+
+ // If this is a matching command completion response, we are done
+ if (r.isTagged() && r.getTag().equals(idleTag))
+ done = true;
+
+ if (done)
+ idleTag = null; // no longer in IDLE
+
+ handleResult(r);
+ return !done;
+ }
+
+ // the DONE command to break out of IDLE
+ private static final byte[] DONE = { 'D', 'O', 'N', 'E', '\r', '\n' };
+
+ /**
+ * Abort an IDLE command. While one thread is blocked in
+ * readIdleResponse(), another thread will use this method
+ * to abort the IDLE command, which will cause the server
+ * to send the closing tag for the IDLE command, which
+ * readIdleResponse() and processIdleResponse() will see
+ * and terminate the IDLE state.
+ *
+ * @since JavaMail 1.4.1
+ */
+ public void idleAbort() {
+ OutputStream os = getOutputStream();
+ try {
+ os.write(DONE);
+ os.flush();
+ } catch (Exception ex) {
+ // nothing to do, hope to detect it again later
+ logger.log(Level.FINEST, "Exception aborting IDLE", ex);
+ }
+ }
+
+ /**
+ * ID Command.
+ *
+ * @param clientParams map of names and values
+ * @return map of names and values from server
+ * @exception ProtocolException for protocol failures
+ * @see "RFC 2971"
+ * @since JavaMail 1.5.1
+ */
+ public Map id(Map clientParams)
+ throws ProtocolException {
+ if (!hasCapability("ID"))
+ throw new BadCommandException("ID not supported");
+
+ Response[] r = command("ID", ID.getArgumentList(clientParams));
+
+ ID id = null;
+ Response response = r[r.length-1];
+
+ // Grab ID response
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("ID")) {
+ if (id == null)
+ id = new ID(ir);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ notifyResponseHandlers(r);
+ handleResult(response);
+ return id == null ? null : id.getServerParams();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java b/app/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
new file mode 100644
index 0000000000..82f1ea39d1
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * A ProtocolException that includes IMAP login referral information.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class IMAPReferralException extends ProtocolException {
+
+ private String url;
+
+ private static final long serialVersionUID = 2578770669364251968L;
+
+ /**
+ * Constructs an IMAPReferralException with the specified detail message.
+ * and URL.
+ *
+ * @param s the detail message
+ * @param url the URL
+ */
+ public IMAPReferralException(String s, String url) {
+ super(s);
+ this.url = url;
+ }
+
+ /**
+ * Return the IMAP URL in the referral.
+ *
+ * @return the IMAP URL
+ */
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java b/app/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
new file mode 100644
index 0000000000..4e6be4e1b3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents a response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author John Mani
+ */
+
+public class IMAPResponse extends Response {
+ private String key;
+ private int number;
+
+ public IMAPResponse(Protocol c) throws IOException, ProtocolException {
+ super(c);
+ init();
+ }
+
+ private void init() throws IOException, ProtocolException {
+ // continue parsing if this is an untagged response
+ if (isUnTagged() && !isOK() && !isNO() && !isBAD() && !isBYE()) {
+ key = readAtom();
+
+ // Is this response of the form "* "
+ try {
+ number = Integer.parseInt(key);
+ key = readAtom();
+ } catch (NumberFormatException ne) { }
+ }
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param r the IMAPResponse to copy
+ */
+ public IMAPResponse(IMAPResponse r) {
+ super((Response)r);
+ key = r.key;
+ number = r.number;
+ }
+
+ /**
+ * For testing.
+ *
+ * @param r the response string
+ * @exception IOException for I/O errors
+ * @exception ProtocolException for protocol failures
+ */
+ public IMAPResponse(String r) throws IOException, ProtocolException {
+ 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
+ * return the list as a array of Strings. An empty list is returned
+ * 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
+ */
+ public String[] readSimpleList() {
+ skipSpaces();
+
+ if (buffer[index] != '(') // not what we expected
+ return null;
+ index++; // skip '('
+
+ List v = new ArrayList<>();
+ int start;
+ for (start = index; buffer[index] != ')'; index++) {
+ if (buffer[index] == ' ') { // got one item
+ v.add(ASCIIUtility.toString(buffer, start, index));
+ start = index+1; // index gets incremented at the top
+ }
+ }
+ if (index > start) // get the last item
+ v.add(ASCIIUtility.toString(buffer, start, index));
+ index++; // skip ')'
+
+ int size = v.size();
+ if (size > 0)
+ return v.toArray(new String[size]);
+ else // empty list
+ return null;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public boolean keyEquals(String k) {
+ if (key != null && key.equalsIgnoreCase(k))
+ return true;
+ else
+ return false;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java b/app/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java
new file mode 100644
index 0000000000..7f432ff51c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Locale;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.text.FieldPosition;
+
+import javax.mail.internet.MailDateFormat;
+
+import com.sun.mail.iap.*;
+
+
+/**
+ * An INTERNALDATE FETCH item.
+ *
+ * @author John Mani
+ */
+
+public class INTERNALDATE implements Item {
+
+ static final char[] name =
+ {'I','N','T','E','R','N','A','L','D','A','T','E'};
+ public int msgno;
+ protected Date date;
+
+ /*
+ * Used to parse dates only. The parse method is thread safe
+ * so we only need to create a single object for use by all
+ * instances. We depend on the fact that the MailDateFormat
+ * class will parse dates in INTERNALDATE format as well as
+ * dates in RFC 822 format.
+ */
+ private static final MailDateFormat mailDateFormat = new MailDateFormat();
+
+ /**
+ * Constructor.
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public INTERNALDATE(FetchResponse r) throws ParsingException {
+ msgno = r.getNumber();
+ r.skipSpaces();
+ String s = r.readString();
+ if (s == null)
+ throw new ParsingException("INTERNALDATE is NIL");
+ try {
+ synchronized (mailDateFormat) {
+ date = mailDateFormat.parse(s);
+ }
+ } catch (ParseException pex) {
+ throw new ParsingException("INTERNALDATE parse error");
+ }
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ // INTERNALDATE formatter
+
+ private static SimpleDateFormat df =
+ // Need Locale.US, the "MMM" field can produce unexpected values
+ // in non US locales !
+ new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss ", Locale.US);
+
+ /**
+ * Format given Date object into INTERNALDATE string
+ *
+ * @param d the Date
+ * @return INTERNALDATE string
+ */
+ public static String format(Date d) {
+ /*
+ * SimpleDateFormat objects aren't thread safe, so rather
+ * than create a separate such object for each request,
+ * we create one object and synchronize its use here
+ * so that only one thread is using it at a time. This
+ * trades off some potential concurrency for speed in the
+ * common case.
+ *
+ * This method is only used when formatting the date in a
+ * message that's being appended to a folder.
+ */
+ StringBuffer sb = new StringBuffer();
+ synchronized (df) {
+ df.format(d, sb, new FieldPosition(0));
+ }
+
+ // compute timezone offset string
+ TimeZone tz = TimeZone.getDefault();
+ int offset = tz.getOffset(d.getTime()); // get offset from GMT
+ int rawOffsetInMins = offset / 60 / 1000; // offset from GMT in mins
+ if (rawOffsetInMins < 0) {
+ sb.append('-');
+ rawOffsetInMins = (-rawOffsetInMins);
+ } else
+ sb.append('+');
+
+ int offsetInHrs = rawOffsetInMins / 60;
+ int offsetInMins = rawOffsetInMins % 60;
+
+ sb.append(Character.forDigit((offsetInHrs/10), 10));
+ sb.append(Character.forDigit((offsetInHrs%10), 10));
+ sb.append(Character.forDigit((offsetInMins/10), 10));
+ sb.append(Character.forDigit((offsetInMins%10), 10));
+
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/Item.java b/app/src/main/java/com/sun/mail/imap/protocol/Item.java
new file mode 100644
index 0000000000..8d604ab939
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/Item.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+/**
+ * A tagging interface for all IMAP data items.
+ * Note that the "name" field of all IMAP items MUST be in uppercase.
+ *
+ * See the BODY, BODYSTRUCTURE, ENVELOPE, FLAGS, INTERNALDATE, RFC822DATA,
+ * RFC822SIZE, and UID classes.
+ *
+ * @author John Mani
+ */
+
+public interface Item {
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/ListInfo.java b/app/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
new file mode 100644
index 0000000000..0db1f028e8
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import com.sun.mail.iap.*;
+
+/**
+ * A LIST response.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class ListInfo {
+ public String name = null;
+ public char separator = '/';
+ public boolean hasInferiors = true;
+ public boolean canOpen = true;
+ public int changeState = INDETERMINATE;
+ public String[] attrs;
+
+ public static final int CHANGED = 1;
+ public static final int UNCHANGED = 2;
+ public static final int INDETERMINATE = 3;
+
+ public ListInfo(IMAPResponse r) throws ParsingException {
+ String[] s = r.readSimpleList();
+
+ List v = new ArrayList<>(); // accumulate attributes
+ if (s != null) {
+ // non-empty attribute list
+ for (int i = 0; i < s.length; i++) {
+ if (s[i].equalsIgnoreCase("\\Marked"))
+ changeState = CHANGED;
+ else if (s[i].equalsIgnoreCase("\\Unmarked"))
+ changeState = UNCHANGED;
+ else if (s[i].equalsIgnoreCase("\\Noselect"))
+ canOpen = false;
+ else if (s[i].equalsIgnoreCase("\\Noinferiors"))
+ hasInferiors = false;
+ v.add(s[i]);
+ }
+ }
+ attrs = v.toArray(new String[v.size()]);
+
+ r.skipSpaces();
+ if (r.readByte() == '"') {
+ if ((separator = (char)r.readByte()) == '\\')
+ // escaped separator character
+ separator = (char)r.readByte();
+ r.skip(1); // skip <">
+ } else // NIL
+ r.skip(2);
+
+ r.skipSpaces();
+ name = r.readAtomString();
+
+ if (!r.supportsUtf8())
+ // decode the name (using RFC2060's modified UTF7)
+ name = BASE64MailboxDecoder.decode(name);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java b/app/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java
new file mode 100644
index 0000000000..f800569309
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents the MODSEQ data item.
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+
+public class MODSEQ implements Item {
+
+ static final char[] name = {'M','O','D','S','E','Q'};
+ public int seqnum;
+
+ public long modseq;
+
+ /**
+ * Constructor.
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public MODSEQ(FetchResponse r) throws ParsingException {
+ seqnum = r.getNumber();
+ r.skipSpaces();
+
+ if (r.readByte() != '(')
+ throw new ParsingException("MODSEQ parse error");
+
+ modseq = r.readLong();
+
+ if (!r.isNextNonSpace(')'))
+ throw new ParsingException("MODSEQ parse error");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java b/app/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java
new file mode 100644
index 0000000000..ccff0b6964
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import javax.mail.Flags;
+
+import com.sun.mail.iap.*;
+
+/**
+ * Information collected when opening a mailbox.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class MailboxInfo {
+ /** The available flags. */
+ public Flags availableFlags = null;
+ /** The permanent flags. */
+ public Flags permanentFlags = null;
+ /** The total number of messages. */
+ public int total = -1;
+ /** The number of recent messages. */
+ public int recent = -1;
+ /** The first unseen message. */
+ public int first = -1;
+ /** The UIDVALIDITY. */
+ public long uidvalidity = -1;
+ /** The next UID value to be assigned. */
+ public long uidnext = -1;
+ /** UIDs are not sticky. */
+ public boolean uidNotSticky = false; // RFC 4315
+ /** The highest MODSEQ value. */
+ public long highestmodseq = -1; // RFC 4551 - CONDSTORE
+ /** Folder.READ_WRITE or Folder.READ_ONLY, set by IMAPProtocol. */
+ public int mode;
+ /** VANISHED or FETCH responses received while opening the mailbox. */
+ public List responses;
+
+ /**
+ * Collect the information about this mailbox from the
+ * responses to a SELECT or EXAMINE.
+ *
+ * @param r the responses
+ * @exception ParsingException for errors parsing the responses
+ */
+ public MailboxInfo(Response[] r) throws ParsingException {
+ for (int i = 0; i < r.length; i++) {
+ if (r[i] == null || !(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+
+ if (ir.keyEquals("EXISTS")) {
+ total = ir.getNumber();
+ r[i] = null; // remove this response
+ } else if (ir.keyEquals("RECENT")) {
+ recent = ir.getNumber();
+ r[i] = null; // remove this response
+ } else if (ir.keyEquals("FLAGS")) {
+ availableFlags = new FLAGS(ir);
+ r[i] = null; // remove this response
+ } else if (ir.keyEquals("VANISHED")) {
+ if (responses == null)
+ responses = new ArrayList<>();
+ responses.add(ir);
+ r[i] = null; // remove this response
+ } else if (ir.keyEquals("FETCH")) {
+ if (responses == null)
+ responses = new ArrayList<>();
+ responses.add(ir);
+ r[i] = null; // remove this response
+ } else if (ir.isUnTagged() && ir.isOK()) {
+ /*
+ * should be one of:
+ * * OK [UNSEEN 12]
+ * * OK [UIDVALIDITY 3857529045]
+ * * OK [PERMANENTFLAGS (\Deleted)]
+ * * OK [UIDNEXT 44]
+ * * OK [HIGHESTMODSEQ 103]
+ */
+ ir.skipSpaces();
+
+ if (ir.readByte() != '[') { // huh ???
+ ir.reset();
+ continue;
+ }
+
+ boolean handled = true;
+ String s = ir.readAtom();
+ if (s.equalsIgnoreCase("UNSEEN"))
+ first = ir.readNumber();
+ else if (s.equalsIgnoreCase("UIDVALIDITY"))
+ uidvalidity = ir.readLong();
+ else if (s.equalsIgnoreCase("PERMANENTFLAGS"))
+ permanentFlags = new FLAGS(ir);
+ else if (s.equalsIgnoreCase("UIDNEXT"))
+ uidnext = ir.readLong();
+ else if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
+ highestmodseq = ir.readLong();
+ else
+ handled = false; // possibly an ALERT
+
+ if (handled)
+ r[i] = null; // remove this response
+ else
+ ir.reset(); // so ALERT can be read
+ } else if (ir.isUnTagged() && ir.isNO()) {
+ /*
+ * should be one of:
+ * * NO [UIDNOTSTICKY]
+ */
+ ir.skipSpaces();
+
+ if (ir.readByte() != '[') { // huh ???
+ ir.reset();
+ continue;
+ }
+
+ boolean handled = true;
+ String s = ir.readAtom();
+ if (s.equalsIgnoreCase("UIDNOTSTICKY"))
+ uidNotSticky = true;
+ else
+ handled = false; // possibly an ALERT
+
+ if (handled)
+ r[i] = null; // remove this response
+ else
+ ir.reset(); // so ALERT can be read
+ }
+ }
+
+ /*
+ * The PERMANENTFLAGS response code is optional, and if
+ * not present implies that all flags in the required FLAGS
+ * response can be changed permanently.
+ */
+ if (permanentFlags == null) {
+ if (availableFlags != null)
+ permanentFlags = new Flags(availableFlags);
+ else
+ permanentFlags = new Flags();
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/MessageSet.java b/app/src/main/java/com/sun/mail/imap/protocol/MessageSet.java
new file mode 100644
index 0000000000..a7e6cc6b5b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/MessageSet.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * This class holds the 'start' and 'end' for a range of messages.
+ */
+public class MessageSet {
+
+ public int start;
+ public int end;
+
+ public MessageSet() { }
+
+ public MessageSet(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Count the total number of elements in a MessageSet
+ *
+ * @return how many messages in this MessageSet
+ */
+ public int size() {
+ return end - start + 1;
+ }
+
+ /**
+ * Convert an array of integers into an array of MessageSets
+ *
+ * @param msgs the messages
+ * @return array of MessageSet objects
+ */
+ public static MessageSet[] createMessageSets(int[] msgs) {
+ List v = new ArrayList<>();
+ int i,j;
+
+ for (i=0; i < msgs.length; i++) {
+ MessageSet ms = new MessageSet();
+ ms.start = msgs[i];
+
+ // Look for contiguous elements
+ for (j=i+1; j < msgs.length; j++) {
+ if (msgs[j] != msgs[j-1] +1)
+ break;
+ }
+ ms.end = msgs[j-1];
+ v.add(ms);
+ i = j-1; // i gets incremented @ top of the loop
+ }
+ return v.toArray(new MessageSet[v.size()]);
+ }
+
+ /**
+ * Convert an array of MessageSets into an IMAP sequence range
+ *
+ * @param msgsets the MessageSets
+ * @return IMAP sequence string
+ */
+ public static String toString(MessageSet[] msgsets) {
+ if (msgsets == null || msgsets.length == 0) // Empty msgset
+ return null;
+
+ int i = 0; // msgset index
+ StringBuilder s = new StringBuilder();
+ int size = msgsets.length;
+ int start, end;
+
+ for (;;) {
+ start = msgsets[i].start;
+ end = msgsets[i].end;
+
+ if (end > start)
+ s.append(start).append(':').append(end);
+ else // end == start means only one element
+ s.append(start);
+
+ i++; // Next MessageSet
+ if (i >= size) // No more MessageSets
+ break;
+ else
+ s.append(',');
+ }
+ return s.toString();
+ }
+
+
+ /*
+ * Count the total number of elements in an array of MessageSets
+ */
+ public static int size(MessageSet[] msgsets) {
+ int count = 0;
+
+ if (msgsets == null) // Null msgset
+ return 0;
+
+ for (int i=0; i < msgsets.length; i++)
+ count += msgsets[i].size();
+
+ return count;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/Namespaces.java b/app/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
new file mode 100644
index 0000000000..df54e67e2c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import com.sun.mail.iap.*;
+
+/**
+ * This class and its inner class represent the response to the
+ * NAMESPACE command.
+ *
+ * See RFC 2342 .
+ *
+ * @author Bill Shannon
+ */
+
+public class Namespaces {
+
+ /**
+ * A single namespace entry.
+ */
+ public static class Namespace {
+ /**
+ * Prefix string for the namespace.
+ */
+ public String prefix;
+
+ /**
+ * Delimiter between names in this namespace.
+ */
+ public char delimiter;
+
+ /**
+ * Parse a namespace element out of the response.
+ *
+ * @param r the Response to parse
+ * @exception ProtocolException for any protocol errors
+ */
+ public Namespace(Response r) throws ProtocolException {
+ // Namespace_Element = "(" string SP (<"> QUOTED_CHAR <"> / nil)
+ // *(Namespace_Response_Extension) ")"
+ if (!r.isNextNonSpace('('))
+ throw new ProtocolException(
+ "Missing '(' at start of Namespace");
+ // first, the prefix
+ prefix = r.readString();
+ if (!r.supportsUtf8())
+ prefix = BASE64MailboxDecoder.decode(prefix);
+ r.skipSpaces();
+ // delimiter is a quoted character or NIL
+ if (r.peekByte() == '"') {
+ r.readByte();
+ delimiter = (char)r.readByte();
+ if (delimiter == '\\')
+ delimiter = (char)r.readByte();
+ if (r.readByte() != '"')
+ throw new ProtocolException(
+ "Missing '\"' at end of QUOTED_CHAR");
+ } else {
+ String s = r.readAtom();
+ if (s == null)
+ throw new ProtocolException("Expected NIL, got null");
+ if (!s.equalsIgnoreCase("NIL"))
+ throw new ProtocolException("Expected NIL, got " + s);
+ delimiter = 0;
+ }
+ // at end of Namespace data?
+ if (r.isNextNonSpace(')'))
+ return;
+
+ // otherwise, must be a Namespace_Response_Extension
+ // Namespace_Response_Extension = SP string SP
+ // "(" string *(SP string) ")"
+ r.readString();
+ r.skipSpaces();
+ r.readStringList();
+ if (!r.isNextNonSpace(')'))
+ throw new ProtocolException("Missing ')' at end of Namespace");
+ }
+ };
+
+ /**
+ * The personal namespaces.
+ * May be null.
+ */
+ public Namespace[] personal;
+
+ /**
+ * The namespaces for other users.
+ * May be null.
+ */
+ public Namespace[] otherUsers;
+
+ /**
+ * The shared namespace.
+ * May be null.
+ */
+ public Namespace[] shared;
+
+ /**
+ * Parse out all the namespaces.
+ *
+ * @param r the Response to parse
+ * @throws ProtocolException for any protocol errors
+ */
+ public Namespaces(Response r) throws ProtocolException {
+ personal = getNamespaces(r);
+ otherUsers = getNamespaces(r);
+ shared = getNamespaces(r);
+ }
+
+ /**
+ * Parse out one of the three sets of namespaces.
+ */
+ private Namespace[] getNamespaces(Response r) throws ProtocolException {
+ // Namespace = nil / "(" 1*( Namespace_Element) ")"
+ if (r.isNextNonSpace('(')) {
+ List v = new ArrayList<>();
+ do {
+ Namespace ns = new Namespace(r);
+ v.add(ns);
+ } while (!r.isNextNonSpace(')'));
+ return v.toArray(new Namespace[v.size()]);
+ } else {
+ String s = r.readAtom();
+ if (s == null)
+ throw new ProtocolException("Expected NIL, got null");
+ if (!s.equalsIgnoreCase("NIL"))
+ throw new ProtocolException("Expected NIL, got " + s);
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java b/app/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java
new file mode 100644
index 0000000000..bda754f61d
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.ByteArrayInputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * The RFC822 response data item.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class RFC822DATA implements Item {
+
+ static final char[] name = {'R','F','C','8','2','2'};
+ private final int msgno;
+ private final ByteArray data;
+ private final boolean isHeader;
+
+ /**
+ * Constructor, header flag is false.
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public RFC822DATA(FetchResponse r) throws ParsingException {
+ this(r, false);
+ }
+
+ /**
+ * Constructor, specifying header flag.
+ *
+ * @param r the FetchResponse
+ * @param isHeader just header information?
+ * @exception ParsingException for parsing failures
+ */
+ public RFC822DATA(FetchResponse r, boolean isHeader)
+ throws ParsingException {
+ this.isHeader = isHeader;
+ msgno = r.getNumber();
+ r.skipSpaces();
+ data = r.readByteArray();
+ }
+
+ public ByteArray getByteArray() {
+ return data;
+ }
+
+ public ByteArrayInputStream getByteArrayInputStream() {
+ if (data != null)
+ return data.toByteArrayInputStream();
+ else
+ return null;
+ }
+
+ public boolean isHeader() {
+ return isHeader;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java b/app/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java
new file mode 100644
index 0000000000..59118f1583
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*;
+
+/**
+ * An RFC822SIZE FETCH item.
+ *
+ * @author John Mani
+ */
+
+public class RFC822SIZE implements Item {
+
+ static final char[] name = {'R','F','C','8','2','2','.','S','I','Z','E'};
+ public int msgno;
+
+ public long size;
+
+ /**
+ * Constructor.
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public RFC822SIZE(FetchResponse r) throws ParsingException {
+ msgno = r.getNumber();
+ r.skipSpaces();
+ size = r.readLong();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java b/app/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java
new file mode 100644
index 0000000000..4c31c76715
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * Interface to make it easier to call IMAPSaslAuthenticator.
+ */
+
+public interface SaslAuthenticator {
+ public boolean authenticate(String[] mechs, String realm, String authzid,
+ String u, String p) throws ProtocolException;
+
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java b/app/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java
new file mode 100644
index 0000000000..c1388373ca
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import java.io.IOException;
+
+import javax.mail.*;
+import javax.mail.search.*;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.OlderTerm;
+import com.sun.mail.imap.YoungerTerm;
+import com.sun.mail.imap.ModifiedSinceTerm;
+
+/**
+ * This class traverses a search-tree and generates the
+ * corresponding IMAP search sequence.
+ *
+ * Each IMAPProtocol instance contains an instance of this class,
+ * which might be subclassed by subclasses of IMAPProtocol to add
+ * support for additional product-specific search terms.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+public class SearchSequence {
+
+ private IMAPProtocol protocol; // for hasCapability checks; may be null
+
+ /**
+ * Create a SearchSequence for this IMAPProtocol.
+ *
+ * @param p the IMAPProtocol object for the server
+ * @since JavaMail 1.6.0
+ */
+ public SearchSequence(IMAPProtocol p) {
+ protocol = p;
+ }
+
+ /**
+ * Create a SearchSequence.
+ */
+ @Deprecated
+ public SearchSequence() {
+ }
+
+ /**
+ * Generate the IMAP search sequence for the given search expression.
+ *
+ * @param term the search term
+ * @param charset charset for the search
+ * @return the SEARCH Argument
+ * @exception SearchException for failures
+ * @exception IOException for I/O errors
+ */
+ public Argument generateSequence(SearchTerm term, String charset)
+ throws SearchException, IOException {
+ /*
+ * Call the appropriate handler depending on the type of
+ * the search-term ...
+ */
+ if (term instanceof AndTerm) // AND
+ return and((AndTerm)term, charset);
+ else if (term instanceof OrTerm) // OR
+ return or((OrTerm)term, charset);
+ else if (term instanceof NotTerm) // NOT
+ return not((NotTerm)term, charset);
+ else if (term instanceof HeaderTerm) // HEADER
+ return header((HeaderTerm)term, charset);
+ else if (term instanceof FlagTerm) // FLAG
+ return flag((FlagTerm)term);
+ else if (term instanceof FromTerm) { // FROM
+ FromTerm fterm = (FromTerm)term;
+ return from(fterm.getAddress().toString(), charset);
+ }
+ else if (term instanceof FromStringTerm) { // FROM
+ FromStringTerm fterm = (FromStringTerm)term;
+ return from(fterm.getPattern(), charset);
+ }
+ else if (term instanceof RecipientTerm) { // RECIPIENT
+ RecipientTerm rterm = (RecipientTerm)term;
+ return recipient(rterm.getRecipientType(),
+ rterm.getAddress().toString(),
+ charset);
+ }
+ else if (term instanceof RecipientStringTerm) { // RECIPIENT
+ RecipientStringTerm rterm = (RecipientStringTerm)term;
+ return recipient(rterm.getRecipientType(),
+ rterm.getPattern(),
+ charset);
+ }
+ else if (term instanceof SubjectTerm) // SUBJECT
+ return subject((SubjectTerm)term, charset);
+ else if (term instanceof BodyTerm) // BODY
+ return body((BodyTerm)term, charset);
+ else if (term instanceof SizeTerm) // SIZE
+ return size((SizeTerm)term);
+ else if (term instanceof SentDateTerm) // SENTDATE
+ return sentdate((SentDateTerm)term);
+ else if (term instanceof ReceivedDateTerm) // INTERNALDATE
+ return receiveddate((ReceivedDateTerm)term);
+ else if (term instanceof OlderTerm) // RFC 5032 OLDER
+ return older((OlderTerm)term);
+ else if (term instanceof YoungerTerm) // RFC 5032 YOUNGER
+ return younger((YoungerTerm)term);
+ else if (term instanceof MessageIDTerm) // MessageID
+ return messageid((MessageIDTerm)term, charset);
+ else if (term instanceof ModifiedSinceTerm) // RFC 4551 MODSEQ
+ return modifiedSince((ModifiedSinceTerm)term);
+ else
+ throw new SearchException("Search too complex");
+ }
+
+ /**
+ * Check if the "text" terms in the given SearchTerm contain
+ * non US-ASCII characters.
+ *
+ * @param term the search term
+ * @return true if only ASCII
+ */
+ public static boolean isAscii(SearchTerm term) {
+ if (term instanceof AndTerm)
+ return isAscii(((AndTerm)term).getTerms());
+ else if (term instanceof OrTerm)
+ return isAscii(((OrTerm)term).getTerms());
+ else if (term instanceof NotTerm)
+ return isAscii(((NotTerm)term).getTerm());
+ else if (term instanceof StringTerm)
+ return isAscii(((StringTerm)term).getPattern());
+ else if (term instanceof AddressTerm)
+ return isAscii(((AddressTerm)term).getAddress().toString());
+
+ // Any other term returns true.
+ return true;
+ }
+
+ /**
+ * Check if any of the "text" terms in the given SearchTerms contain
+ * non US-ASCII characters.
+ *
+ * @param terms the search terms
+ * @return true if only ASCII
+ */
+ public static boolean isAscii(SearchTerm[] terms) {
+ for (int i = 0; i < terms.length; i++)
+ if (!isAscii(terms[i])) // outta here !
+ return false;
+ return true;
+ }
+
+ /**
+ * Does this string contain only ASCII characters?
+ *
+ * @param s the string
+ * @return true if only ASCII
+ */
+ public static boolean isAscii(String s) {
+ int l = s.length();
+
+ for (int i=0; i < l; i++) {
+ if ((int)s.charAt(i) > 0177) // non-ascii
+ return false;
+ }
+ return true;
+ }
+
+ protected Argument and(AndTerm term, String charset)
+ throws SearchException, IOException {
+ // Combine the sequences for both terms
+ SearchTerm[] terms = term.getTerms();
+ // Generate the search sequence for the first term
+ Argument result = generateSequence(terms[0], charset);
+ // Append other terms
+ for (int i = 1; i < terms.length; i++)
+ result.append(generateSequence(terms[i], charset));
+ return result;
+ }
+
+ protected Argument or(OrTerm term, String charset)
+ throws SearchException, IOException {
+ SearchTerm[] terms = term.getTerms();
+
+ /* The IMAP OR operator takes only two operands. So if
+ * we have more than 2 operands, group them into 2-operand
+ * OR Terms.
+ */
+ if (terms.length > 2) {
+ SearchTerm t = terms[0];
+
+ // Include rest of the terms
+ for (int i = 1; i < terms.length; i++)
+ t = new OrTerm(t, terms[i]);
+
+ term = (OrTerm)t; // set 'term' to the new jumbo OrTerm we
+ // just created
+ terms = term.getTerms();
+ }
+
+ // 'term' now has only two operands
+ Argument result = new Argument();
+
+ // Add the OR search-key, if more than one term
+ if (terms.length > 1)
+ result.writeAtom("OR");
+
+ /* If this term is an AND expression, we need to enclose it
+ * within paranthesis.
+ *
+ * AND expressions are either AndTerms or FlagTerms
+ */
+ if (terms[0] instanceof AndTerm || terms[0] instanceof FlagTerm)
+ result.writeArgument(generateSequence(terms[0], charset));
+ else
+ result.append(generateSequence(terms[0], charset));
+
+ // Repeat the above for the second term, if there is one
+ if (terms.length > 1) {
+ if (terms[1] instanceof AndTerm || terms[1] instanceof FlagTerm)
+ result.writeArgument(generateSequence(terms[1], charset));
+ else
+ result.append(generateSequence(terms[1], charset));
+ }
+
+ return result;
+ }
+
+ protected Argument not(NotTerm term, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+
+ // Add the NOT search-key
+ result.writeAtom("NOT");
+
+ /* If this term is an AND expression, we need to enclose it
+ * within paranthesis.
+ *
+ * AND expressions are either AndTerms or FlagTerms
+ */
+ SearchTerm nterm = term.getTerm();
+ if (nterm instanceof AndTerm || nterm instanceof FlagTerm)
+ result.writeArgument(generateSequence(nterm, charset));
+ else
+ result.append(generateSequence(nterm, charset));
+
+ return result;
+ }
+
+ protected Argument header(HeaderTerm term, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+ result.writeAtom("HEADER");
+ result.writeString(term.getHeaderName());
+ result.writeString(term.getPattern(), charset);
+ return result;
+ }
+
+ protected Argument messageid(MessageIDTerm term, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+ result.writeAtom("HEADER");
+ result.writeString("Message-ID");
+ // XXX confirm that charset conversion ought to be done
+ result.writeString(term.getPattern(), charset);
+ return result;
+ }
+
+ protected Argument flag(FlagTerm term) throws SearchException {
+ boolean set = term.getTestSet();
+
+ Argument result = new Argument();
+
+ Flags flags = term.getFlags();
+ Flags.Flag[] sf = flags.getSystemFlags();
+ String[] uf = flags.getUserFlags();
+ if (sf.length == 0 && uf.length == 0)
+ throw new SearchException("Invalid FlagTerm");
+
+ for (int i = 0; i < sf.length; i++) {
+ if (sf[i] == Flags.Flag.DELETED)
+ result.writeAtom(set ? "DELETED": "UNDELETED");
+ else if (sf[i] == Flags.Flag.ANSWERED)
+ result.writeAtom(set ? "ANSWERED": "UNANSWERED");
+ else if (sf[i] == Flags.Flag.DRAFT)
+ result.writeAtom(set ? "DRAFT": "UNDRAFT");
+ else if (sf[i] == Flags.Flag.FLAGGED)
+ result.writeAtom(set ? "FLAGGED": "UNFLAGGED");
+ else if (sf[i] == Flags.Flag.RECENT)
+ result.writeAtom(set ? "RECENT": "OLD");
+ else if (sf[i] == Flags.Flag.SEEN)
+ result.writeAtom(set ? "SEEN": "UNSEEN");
+ }
+
+ for (int i = 0; i < uf.length; i++) {
+ result.writeAtom(set ? "KEYWORD" : "UNKEYWORD");
+ result.writeAtom(uf[i]);
+ }
+
+ return result;
+ }
+
+ protected Argument from(String address, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+ result.writeAtom("FROM");
+ result.writeString(address, charset);
+ return result;
+ }
+
+ protected Argument recipient(Message.RecipientType type,
+ String address, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+
+ if (type == Message.RecipientType.TO)
+ result.writeAtom("TO");
+ else if (type == Message.RecipientType.CC)
+ result.writeAtom("CC");
+ else if (type == Message.RecipientType.BCC)
+ result.writeAtom("BCC");
+ else
+ throw new SearchException("Illegal Recipient type");
+
+ result.writeString(address, charset);
+ return result;
+ }
+
+ protected Argument subject(SubjectTerm term, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+
+ result.writeAtom("SUBJECT");
+ result.writeString(term.getPattern(), charset);
+ return result;
+ }
+
+ protected Argument body(BodyTerm term, String charset)
+ throws SearchException, IOException {
+ Argument result = new Argument();
+
+ result.writeAtom("BODY");
+ result.writeString(term.getPattern(), charset);
+ return result;
+ }
+
+ protected Argument size(SizeTerm term)
+ throws SearchException {
+ Argument result = new Argument();
+
+ switch (term.getComparison()) {
+ case ComparisonTerm.GT:
+ result.writeAtom("LARGER");
+ break;
+ case ComparisonTerm.LT:
+ result.writeAtom("SMALLER");
+ break;
+ default:
+ // GT and LT is all we get from IMAP for size
+ throw new SearchException("Cannot handle Comparison");
+ }
+
+ result.writeNumber(term.getNumber());
+ return result;
+ }
+
+ // Date SEARCH stuff ...
+
+ // NOTE: The built-in IMAP date comparisons are equivalent to
+ // "<" (BEFORE), "=" (ON), and ">=" (SINCE)!!!
+ // There is no built-in greater-than comparison!
+
+ /**
+ * Print an IMAP Date string, that is suitable for the Date
+ * SEARCH commands.
+ *
+ * The IMAP Date string is :
+ * date ::= date_day "-" date_month "-" date_year
+ *
+ * Note that this format does not contain the TimeZone
+ */
+ private static String monthTable[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ // A GregorianCalendar object in the current timezone
+ protected Calendar cal = new GregorianCalendar();
+
+ protected String toIMAPDate(Date date) {
+ StringBuilder s = new StringBuilder();
+
+ cal.setTime(date);
+
+ s.append(cal.get(Calendar.DATE)).append("-");
+ s.append(monthTable[cal.get(Calendar.MONTH)]).append('-');
+ s.append(cal.get(Calendar.YEAR));
+
+ return s.toString();
+ }
+
+ protected Argument sentdate(DateTerm term)
+ throws SearchException {
+ Argument result = new Argument();
+ String date = toIMAPDate(term.getDate());
+
+ switch (term.getComparison()) {
+ case ComparisonTerm.GT:
+ result.writeAtom("NOT SENTON " + date + " SENTSINCE " + date);
+ break;
+ case ComparisonTerm.EQ:
+ result.writeAtom("SENTON " + date);
+ break;
+ case ComparisonTerm.LT:
+ result.writeAtom("SENTBEFORE " + date);
+ break;
+ case ComparisonTerm.GE:
+ result.writeAtom("SENTSINCE " + date);
+ break;
+ case ComparisonTerm.LE:
+ result.writeAtom("OR SENTBEFORE " + date + " SENTON " + date);
+ break;
+ case ComparisonTerm.NE:
+ result.writeAtom("NOT SENTON " + date);
+ break;
+ default:
+ throw new SearchException("Cannot handle Date Comparison");
+ }
+
+ return result;
+ }
+
+ protected Argument receiveddate(DateTerm term)
+ throws SearchException {
+ Argument result = new Argument();
+ String date = toIMAPDate(term.getDate());
+
+ switch (term.getComparison()) {
+ case ComparisonTerm.GT:
+ result.writeAtom("NOT ON " + date + " SINCE " + date);
+ break;
+ case ComparisonTerm.EQ:
+ result.writeAtom("ON " + date);
+ break;
+ case ComparisonTerm.LT:
+ result.writeAtom("BEFORE " + date);
+ break;
+ case ComparisonTerm.GE:
+ result.writeAtom("SINCE " + date);
+ break;
+ case ComparisonTerm.LE:
+ result.writeAtom("OR BEFORE " + date + " ON " + date);
+ break;
+ case ComparisonTerm.NE:
+ result.writeAtom("NOT ON " + date);
+ break;
+ default:
+ throw new SearchException("Cannot handle Date Comparison");
+ }
+
+ return result;
+ }
+
+ /**
+ * Generate argument for OlderTerm.
+ *
+ * @param term the search term
+ * @return the SEARCH Argument
+ * @exception SearchException for failures
+ * @since JavaMail 1.5.1
+ */
+ protected Argument older(OlderTerm term) throws SearchException {
+ if (protocol != null && !protocol.hasCapability("WITHIN"))
+ throw new SearchException("Server doesn't support OLDER searches");
+ Argument result = new Argument();
+ result.writeAtom("OLDER");
+ result.writeNumber(term.getInterval());
+ return result;
+ }
+
+ /**
+ * Generate argument for YoungerTerm.
+ *
+ * @param term the search term
+ * @return the SEARCH Argument
+ * @exception SearchException for failures
+ * @since JavaMail 1.5.1
+ */
+ protected Argument younger(YoungerTerm term) throws SearchException {
+ if (protocol != null && !protocol.hasCapability("WITHIN"))
+ throw new SearchException("Server doesn't support YOUNGER searches");
+ Argument result = new Argument();
+ result.writeAtom("YOUNGER");
+ result.writeNumber(term.getInterval());
+ return result;
+ }
+
+ /**
+ * Generate argument for ModifiedSinceTerm.
+ *
+ * @param term the search term
+ * @return the SEARCH Argument
+ * @exception SearchException for failures
+ * @since JavaMail 1.5.1
+ */
+ protected Argument modifiedSince(ModifiedSinceTerm term)
+ throws SearchException {
+ if (protocol != null && !protocol.hasCapability("CONDSTORE"))
+ throw new SearchException("Server doesn't support MODSEQ searches");
+ Argument result = new Argument();
+ result.writeAtom("MODSEQ");
+ result.writeNumber(term.getModSeq());
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/Status.java b/app/src/main/java/com/sun/mail/imap/protocol/Status.java
new file mode 100644
index 0000000000..76d29c358b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/Status.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+
+import com.sun.mail.iap.*;
+
+/**
+ * STATUS response.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class Status {
+ public String mbox = null;
+ public int total = -1;
+ public int recent = -1;
+ public long uidnext = -1;
+ public long uidvalidity = -1;
+ public int unseen = -1;
+ public long highestmodseq = -1;
+ public Map items; // any unknown items
+
+ static final String[] standardItems =
+ { "MESSAGES", "RECENT", "UNSEEN", "UIDNEXT", "UIDVALIDITY" };
+
+ public Status(Response r) throws ParsingException {
+ // mailbox := astring
+ mbox = r.readAtomString();
+ if (!r.supportsUtf8())
+ mbox = BASE64MailboxDecoder.decode(mbox);
+
+ // Workaround buggy IMAP servers that don't quote folder names
+ // with spaces.
+ final StringBuilder buffer = new StringBuilder();
+ boolean onlySpaces = true;
+
+ while (r.peekByte() != '(' && r.peekByte() != 0) {
+ final char next = (char)r.readByte();
+
+ buffer.append(next);
+
+ if (next != ' ') {
+ onlySpaces = false;
+ }
+ }
+
+ if (!onlySpaces) {
+ mbox = (mbox + buffer).trim();
+ }
+
+ if (r.readByte() != '(')
+ throw new ParsingException("parse error in STATUS");
+
+ do {
+ String attr = r.readAtom();
+ if (attr == null)
+ throw new ParsingException("parse error in STATUS");
+ if (attr.equalsIgnoreCase("MESSAGES"))
+ total = r.readNumber();
+ else if (attr.equalsIgnoreCase("RECENT"))
+ recent = r.readNumber();
+ else if (attr.equalsIgnoreCase("UIDNEXT"))
+ uidnext = r.readLong();
+ else if (attr.equalsIgnoreCase("UIDVALIDITY"))
+ uidvalidity = r.readLong();
+ else if (attr.equalsIgnoreCase("UNSEEN"))
+ unseen = r.readNumber();
+ else if (attr.equalsIgnoreCase("HIGHESTMODSEQ"))
+ highestmodseq = r.readLong();
+ else {
+ if (items == null)
+ items = new HashMap<>();
+ items.put(attr.toUpperCase(Locale.ENGLISH),
+ Long.valueOf(r.readLong()));
+ }
+ } while (!r.isNextNonSpace(')'));
+ }
+
+ /**
+ * Get the value for the STATUS item.
+ *
+ * @param item the STATUS item
+ * @return the value
+ * @since JavaMail 1.5.2
+ */
+ public long getItem(String item) {
+ item = item.toUpperCase(Locale.ENGLISH);
+ Long v;
+ long ret = -1;
+ if (items != null && (v = items.get(item)) != null)
+ ret = v.longValue();
+ else if (item.equals("MESSAGES"))
+ ret = total;
+ else if (item.equals("RECENT"))
+ ret = recent;
+ else if (item.equals("UIDNEXT"))
+ ret = uidnext;
+ else if (item.equals("UIDVALIDITY"))
+ ret = uidvalidity;
+ else if (item.equals("UNSEEN"))
+ ret = unseen;
+ else if (item.equals("HIGHESTMODSEQ"))
+ ret = highestmodseq;
+ return ret;
+ }
+
+ public static void add(Status s1, Status s2) {
+ if (s2.total != -1)
+ s1.total = s2.total;
+ if (s2.recent != -1)
+ s1.recent = s2.recent;
+ if (s2.uidnext != -1)
+ s1.uidnext = s2.uidnext;
+ if (s2.uidvalidity != -1)
+ s1.uidvalidity = s2.uidvalidity;
+ if (s2.unseen != -1)
+ s1.unseen = s2.unseen;
+ if (s2.highestmodseq != -1)
+ s1.highestmodseq = s2.highestmodseq;
+ if (s1.items == null)
+ s1.items = s2.items;
+ else if (s2.items != null)
+ s1.items.putAll(s2.items);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/UID.java b/app/src/main/java/com/sun/mail/imap/protocol/UID.java
new file mode 100644
index 0000000000..58082c2104
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/UID.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents the UID data item.
+ *
+ * @author John Mani
+ */
+
+public class UID implements Item {
+
+ static final char[] name = {'U','I','D'};
+ public int seqnum;
+
+ public long uid;
+
+ /**
+ * Constructor.
+ *
+ * @param r the FetchResponse
+ * @exception ParsingException for parsing failures
+ */
+ public UID(FetchResponse r) throws ParsingException {
+ seqnum = r.getNumber();
+ r.skipSpaces();
+ uid = r.readLong();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/UIDSet.java b/app/src/main/java/com/sun/mail/imap/protocol/UIDSet.java
new file mode 100644
index 0000000000..222858155c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/UIDSet.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * This class holds the 'start' and 'end' for a range of UIDs.
+ * Just like MessageSet except using long instead of int.
+ */
+public class UIDSet {
+
+ public long start;
+ public long end;
+
+ public UIDSet() { }
+
+ public UIDSet(long start, long end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Count the total number of elements in a UIDSet
+ *
+ * @return the number of elements
+ */
+ public long size() {
+ return end - start + 1;
+ }
+
+ /**
+ * Convert an array of longs into an array of UIDSets
+ *
+ * @param uids the UIDs
+ * @return array of UIDSet objects
+ */
+ public static UIDSet[] createUIDSets(long[] uids) {
+ if (uids == null)
+ return null;
+ List v = new ArrayList<>();
+ int i,j;
+
+ for (i=0; i < uids.length; i++) {
+ UIDSet ms = new UIDSet();
+ ms.start = uids[i];
+
+ // Look for contiguous elements
+ for (j=i+1; j < uids.length; j++) {
+ if (uids[j] != uids[j-1] +1)
+ break;
+ }
+ ms.end = uids[j-1];
+ v.add(ms);
+ i = j-1; // i gets incremented @ top of the loop
+ }
+ UIDSet[] uidset = new UIDSet[v.size()];
+ return v.toArray(uidset);
+ }
+
+ /**
+ * Parse a string in IMAP UID range format.
+ *
+ * @param uids UID string
+ * @return array of UIDSet objects
+ * @since JavaMail 1.5.1
+ */
+ public static UIDSet[] parseUIDSets(String uids) {
+ if (uids == null)
+ return null;
+ List v = new ArrayList<>();
+ StringTokenizer st = new StringTokenizer(uids, ",:", true);
+ long start = -1;
+ UIDSet cur = null;
+ try {
+ while(st.hasMoreTokens()) {
+ String s = st.nextToken();
+ if (s.equals(",")) {
+ if (cur != null)
+ v.add(cur);
+ cur = null;
+ } else if (s.equals(":")) {
+ // nothing to do, wait for next number
+ } else { // better be a number
+ long n = Long.parseLong(s);
+ if (cur != null)
+ cur.end = n;
+ else
+ cur = new UIDSet(n, n);
+ }
+ }
+ } catch (NumberFormatException nex) {
+ // give up and return what we have so far
+ }
+ if (cur != null)
+ v.add(cur);
+ UIDSet[] uidset = new UIDSet[v.size()];
+ return v.toArray(uidset);
+ }
+
+ /**
+ * Convert an array of UIDSets into an IMAP sequence range.
+ *
+ * @param uidset the UIDSets
+ * @return the IMAP sequence string
+ */
+ public static String toString(UIDSet[] uidset) {
+ if (uidset == null)
+ return null;
+ if (uidset.length == 0) // Empty uidset
+ return "";
+
+ int i = 0; // uidset index
+ StringBuilder s = new StringBuilder();
+ int size = uidset.length;
+ long start, end;
+
+ for (;;) {
+ start = uidset[i].start;
+ end = uidset[i].end;
+
+ if (end > start)
+ s.append(start).append(':').append(end);
+ else // end == start means only one element
+ s.append(start);
+
+ i++; // Next UIDSet
+ if (i >= size) // No more UIDSets
+ break;
+ else
+ s.append(',');
+ }
+ return s.toString();
+ }
+
+ /**
+ * Convert an array of UIDSets into a array of long UIDs.
+ *
+ * @param uidset the UIDSets
+ * @return arrray of UIDs
+ * @since JavaMail 1.5.1
+ */
+ public static long[] toArray(UIDSet[] uidset) {
+ //return toArray(uidset, -1);
+ if (uidset == null)
+ return null;
+ long[] uids = new long[(int)UIDSet.size(uidset)];
+ int i = 0;
+ for (UIDSet u : uidset) {
+ for (long n = u.start; n <= u.end; n++)
+ uids[i++] = n;
+ }
+ return uids;
+ }
+
+ /**
+ * Convert an array of UIDSets into a array of long UIDs.
+ * Don't include any UIDs larger than uidmax.
+ *
+ * @param uidset the UIDSets
+ * @param uidmax maximum UID
+ * @return arrray of UIDs
+ * @since JavaMail 1.5.1
+ */
+ public static long[] toArray(UIDSet[] uidset, long uidmax) {
+ if (uidset == null)
+ return null;
+ long[] uids = new long[(int)UIDSet.size(uidset, uidmax)];
+ int i = 0;
+ for (UIDSet u : uidset) {
+ for (long n = u.start; n <= u.end; n++) {
+ if (uidmax >= 0 && n > uidmax)
+ break;
+ uids[i++] = n;
+ }
+ }
+ return uids;
+ }
+
+ /**
+ * Count the total number of elements in an array of UIDSets.
+ *
+ * @param uidset the UIDSets
+ * @return the number of elements
+ */
+ public static long size(UIDSet[] uidset) {
+ long count = 0;
+
+ if (uidset != null)
+ for (UIDSet u : uidset)
+ count += u.size();
+
+ return count;
+ }
+
+ /**
+ * Count the total number of elements in an array of UIDSets.
+ * Don't count UIDs greater then uidmax.
+ *
+ * @since JavaMail 1.5.1
+ */
+ private static long size(UIDSet[] uidset, long uidmax) {
+ long count = 0;
+
+ if (uidset != null)
+ for (UIDSet u : uidset) {
+ if (uidmax < 0)
+ count += u.size();
+ else if (u.start <= uidmax) {
+ if (u.end < uidmax)
+ count += u.end - u.start + 1;
+ else
+ count += uidmax - u.start + 1;
+ }
+ }
+
+ return count;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/imap/protocol/package.html b/app/src/main/java/com/sun/mail/imap/protocol/package.html
new file mode 100644
index 0000000000..370726e0fa
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/imap/protocol/package.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+com.sun.mail.imap.protocol package
+
+
+
+
+This package includes internal IMAP support classes and
+SHOULD NOT BE USED DIRECTLY BY APPLICATIONS .
+
+
+
+
diff --git a/app/src/main/java/com/sun/mail/pop3/AppendStream.java b/app/src/main/java/com/sun/mail/pop3/AppendStream.java
new file mode 100644
index 0000000000..cc56ce3569
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/AppendStream.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * A stream for writing to the temp file, and when done can return a stream for
+ * reading the data just written. NOTE: We assume that only one thread is
+ * writing to the file at a time.
+ */
+class AppendStream extends OutputStream {
+
+ private final WritableSharedFile tf;
+ private RandomAccessFile raf;
+ private final long start;
+ private long end;
+
+ public AppendStream(WritableSharedFile tf) throws IOException {
+ this.tf = tf;
+ raf = tf.getWritableFile();
+ start = raf.length();
+ raf.seek(start);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ raf.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ raf.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ raf.write(b, off, len);
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ end = tf.updateLength();
+ raf = null; // no more writing allowed
+ }
+
+ public synchronized InputStream getInputStream() throws IOException {
+ return tf.newStream(start, end);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/DefaultFolder.java b/app/src/main/java/com/sun/mail/pop3/DefaultFolder.java
new file mode 100644
index 0000000000..eaf9d6391c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/DefaultFolder.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+
+/**
+ * The POP3 DefaultFolder. Only contains the "INBOX" folder.
+ *
+ * @author Christopher Cotton
+ */
+public class DefaultFolder extends Folder {
+
+ DefaultFolder(POP3Store store) {
+ super(store);
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public String getFullName() {
+ return "";
+ }
+
+ @Override
+ public Folder getParent() {
+ return null;
+ }
+
+ @Override
+ public boolean exists() {
+ return true;
+ }
+
+ @Override
+ public Folder[] list(String pattern) throws MessagingException {
+ Folder[] f = { getInbox() };
+ return f;
+ }
+
+ @Override
+ public char getSeparator() {
+ return '/';
+ }
+
+ @Override
+ public int getType() {
+ return HOLDS_FOLDERS;
+ }
+
+ @Override
+ public boolean create(int type) throws MessagingException {
+ return false;
+ }
+
+ @Override
+ public boolean hasNewMessages() throws MessagingException {
+ return false;
+ }
+
+ @Override
+ public Folder getFolder(String name) throws MessagingException {
+ if (!name.equalsIgnoreCase("INBOX")) {
+ throw new MessagingException("only INBOX supported");
+ } else {
+ return getInbox();
+ }
+ }
+
+ protected Folder getInbox() throws MessagingException {
+ return getStore().getFolder("INBOX");
+ }
+
+
+ @Override
+ public boolean delete(boolean recurse) throws MessagingException {
+ throw new MethodNotSupportedException("delete");
+ }
+
+ @Override
+ public boolean renameTo(Folder f) throws MessagingException {
+ throw new MethodNotSupportedException("renameTo");
+ }
+
+ @Override
+ public void open(int mode) throws MessagingException {
+ throw new MethodNotSupportedException("open");
+ }
+
+ @Override
+ public void close(boolean expunge) throws MessagingException {
+ throw new MethodNotSupportedException("close");
+ }
+
+ @Override
+ public boolean isOpen() {
+ return false;
+ }
+
+ @Override
+ public Flags getPermanentFlags() {
+ return new Flags(); // empty flags object
+ }
+
+ @Override
+ public int getMessageCount() throws MessagingException {
+ return 0;
+ }
+
+ @Override
+ public Message getMessage(int msgno) throws MessagingException {
+ throw new MethodNotSupportedException("getMessage");
+ }
+
+ @Override
+ public void appendMessages(Message[] msgs) throws MessagingException {
+ throw new MethodNotSupportedException("Append not supported");
+ }
+
+ @Override
+ public Message[] expunge() throws MessagingException {
+ throw new MethodNotSupportedException("expunge");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3Folder.java b/app/src/main/java/com/sun/mail/pop3/POP3Folder.java
new file mode 100644
index 0000000000..a045a24ae2
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3Folder.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.lang.reflect.Constructor;
+
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.MailLogger;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A POP3 Folder (can only be "INBOX").
+ *
+ * See the com.sun.mail.pop3 package
+ * documentation for further information on the POP3 protocol provider.
+ *
+ * @author Bill Shannon
+ * @author John Mani (ported to the javax.mail APIs)
+ */
+public class POP3Folder extends Folder {
+
+ private String name;
+ private POP3Store store;
+ private volatile Protocol port;
+ private int total;
+ private int size;
+ private boolean exists = false;
+ private volatile boolean opened = false;
+ private POP3Message[] message_cache;
+ private boolean doneUidl = false;
+ private volatile TempFile fileCache = null;
+ private boolean forceClose;
+
+ MailLogger logger; // package private, for POP3Message
+
+ protected POP3Folder(POP3Store store, String name) {
+ super(store);
+ this.name = name;
+ this.store = store;
+ if (name.equalsIgnoreCase("INBOX"))
+ exists = true;
+ logger = new MailLogger(this.getClass(), "DEBUG POP3",
+ store.getSession().getDebug(), store.getSession().getDebugOut());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getFullName() {
+ return name;
+ }
+
+ @Override
+ public Folder getParent() {
+ return new DefaultFolder(store);
+ }
+
+ /**
+ * Always true for the folder "INBOX", always false for
+ * any other name.
+ *
+ * @return true for INBOX, false otherwise
+ */
+ @Override
+ public boolean exists() {
+ return exists;
+ }
+
+ /**
+ * Always throws MessagingException
because no POP3 folders
+ * can contain subfolders.
+ *
+ * @exception MessagingException always
+ */
+ @Override
+ public Folder[] list(String pattern) throws MessagingException {
+ throw new MessagingException("not a directory");
+ }
+
+ /**
+ * Always returns a NUL character because POP3 doesn't support a hierarchy.
+ *
+ * @return NUL
+ */
+ @Override
+ public char getSeparator() {
+ return '\0';
+ }
+
+ /**
+ * Always returns Folder.HOLDS_MESSAGES.
+ *
+ * @return Folder.HOLDS_MESSAGES
+ */
+ @Override
+ public int getType() {
+ return HOLDS_MESSAGES;
+ }
+
+ /**
+ * Always returns false
; the POP3 protocol doesn't
+ * support creating folders.
+ *
+ * @return false
+ */
+ @Override
+ public boolean create(int type) throws MessagingException {
+ return false;
+ }
+
+ /**
+ * Always returns false
; the POP3 protocol provides
+ * no way to determine when a new message arrives.
+ *
+ * @return false
+ */
+ @Override
+ public boolean hasNewMessages() throws MessagingException {
+ return false; // no way to know
+ }
+
+ /**
+ * Always throws MessagingException
because no POP3 folders
+ * can contain subfolders.
+ *
+ * @exception MessagingException always
+ */
+ @Override
+ public Folder getFolder(String name) throws MessagingException {
+ throw new MessagingException("not a directory");
+ }
+
+ /**
+ * Always throws MethodNotSupportedException
+ * because the POP3 protocol doesn't allow the INBOX to
+ * be deleted.
+ *
+ * @exception MethodNotSupportedException always
+ */
+ @Override
+ public boolean delete(boolean recurse) throws MessagingException {
+ throw new MethodNotSupportedException("delete");
+ }
+
+ /**
+ * Always throws MethodNotSupportedException
+ * because the POP3 protocol doesn't support multiple folders.
+ *
+ * @exception MethodNotSupportedException always
+ */
+ @Override
+ public boolean renameTo(Folder f) throws MessagingException {
+ throw new MethodNotSupportedException("renameTo");
+ }
+
+ /**
+ * Throws FolderNotFoundException
unless this
+ * folder is named "INBOX".
+ *
+ * @exception FolderNotFoundException if not INBOX
+ * @exception AuthenticationFailedException authentication failures
+ * @exception MessagingException other open failures
+ */
+ @Override
+ public synchronized void open(int mode) throws MessagingException {
+ checkClosed();
+ if (!exists)
+ throw new FolderNotFoundException(this, "folder is not INBOX");
+
+ try {
+ port = store.getPort(this);
+ Status s = port.stat();
+ total = s.total;
+ size = s.size;
+ this.mode = mode;
+ if (store.useFileCache) {
+ try {
+ fileCache = new TempFile(store.fileCacheDir);
+ } catch (IOException ex) {
+ logger.log(Level.FINE, "failed to create file cache", ex);
+ throw ex; // caught below
+ }
+ }
+ opened = true;
+ } catch (IOException ioex) {
+ try {
+ if (port != null)
+ port.quit();
+ } catch (IOException ioex2) {
+ // ignore
+ } finally {
+ port = null;
+ store.closePort(this);
+ }
+ throw new MessagingException("Open failed", ioex);
+ }
+
+ // Create the message cache array of appropriate size
+ message_cache = new POP3Message[total];
+ doneUidl = false;
+
+ notifyConnectionListeners(ConnectionEvent.OPENED);
+ }
+
+ @Override
+ public synchronized void close(boolean expunge) throws MessagingException {
+ checkOpen();
+
+ try {
+ /*
+ * Some POP3 servers will mark messages for deletion when
+ * they're read. To prevent such messages from being
+ * deleted before the client deletes them, you can set
+ * the mail.pop3.rsetbeforequit property to true. This
+ * causes us to issue a POP3 RSET command to clear all
+ * the "marked for deletion" flags. We can then explicitly
+ * delete messages as desired.
+ */
+ if (store.rsetBeforeQuit && !forceClose)
+ port.rset();
+ POP3Message m;
+ if (expunge && mode == READ_WRITE && !forceClose) {
+ // find all messages marked deleted and issue DELE commands
+ for (int i = 0; i < message_cache.length; i++) {
+ if ((m = message_cache[i]) != null) {
+ if (m.isSet(Flags.Flag.DELETED))
+ try {
+ port.dele(i + 1);
+ } catch (IOException ioex) {
+ throw new MessagingException(
+ "Exception deleting messages during close",
+ ioex);
+ }
+ }
+ }
+ }
+
+ /*
+ * Flush and free all cached data for the messages.
+ */
+ for (int i = 0; i < message_cache.length; i++) {
+ if ((m = message_cache[i]) != null)
+ m.invalidate(true);
+ }
+
+ if (forceClose)
+ port.close();
+ else
+ port.quit();
+ } catch (IOException ex) {
+ // do nothing
+ } finally {
+ port = null;
+ store.closePort(this);
+ message_cache = null;
+ opened = false;
+ notifyConnectionListeners(ConnectionEvent.CLOSED);
+ if (fileCache != null) {
+ fileCache.close();
+ fileCache = null;
+ }
+ }
+ }
+
+ @Override
+ public synchronized boolean isOpen() {
+ if (!opened)
+ return false;
+ try {
+ if (!port.noop())
+ throw new IOException("NOOP failed");
+ } catch (IOException ioex) {
+ try {
+ close(false);
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Always returns an empty Flags
object because
+ * the POP3 protocol doesn't support any permanent flags.
+ *
+ * @return empty Flags object
+ */
+ @Override
+ public Flags getPermanentFlags() {
+ return new Flags(); // empty flags object
+ }
+
+ /**
+ * Will not change while the folder is open because the POP3
+ * protocol doesn't support notification of new messages
+ * arriving in open folders.
+ */
+ @Override
+ public synchronized int getMessageCount() throws MessagingException {
+ if (!opened)
+ return -1;
+ checkReadable();
+ return total;
+ }
+
+ @Override
+ public synchronized Message getMessage(int msgno)
+ throws MessagingException {
+ checkOpen();
+
+ POP3Message m;
+
+ // Assuming that msgno is <= total
+ if ((m = message_cache[msgno-1]) == null) {
+ m = createMessage(this, msgno);
+ message_cache[msgno-1] = m;
+ }
+ return m;
+ }
+
+ protected POP3Message createMessage(Folder f, int msgno)
+ throws MessagingException {
+ POP3Message m = null;
+ Constructor> cons = store.messageConstructor;
+ if (cons != null) {
+ try {
+ Object[] o = { this, Integer.valueOf(msgno) };
+ m = (POP3Message)cons.newInstance(o);
+ } catch (Exception ex) {
+ // ignore
+ }
+ }
+ if (m == null)
+ m = new POP3Message(this, msgno);
+ return m;
+ }
+
+ /**
+ * Always throws MethodNotSupportedException
+ * because the POP3 protocol doesn't support appending messages.
+ *
+ * @exception MethodNotSupportedException always
+ */
+ @Override
+ public void appendMessages(Message[] msgs) throws MessagingException {
+ throw new MethodNotSupportedException("Append not supported");
+ }
+
+ /**
+ * Always throws MethodNotSupportedException
+ * because the POP3 protocol doesn't support expunging messages
+ * without closing the folder; call the {@link #close close} method
+ * with the expunge
argument set to true
+ * instead.
+ *
+ * @exception MethodNotSupportedException always
+ */
+ @Override
+ public Message[] expunge() throws MessagingException {
+ throw new MethodNotSupportedException("Expunge not supported");
+ }
+
+ /**
+ * Prefetch information about POP3 messages.
+ * If the FetchProfile contains UIDFolder.FetchProfileItem.UID
,
+ * POP3 UIDs for all messages in the folder are fetched using the POP3
+ * UIDL command.
+ * If the FetchProfile contains FetchProfile.Item.ENVELOPE
,
+ * the headers and size of all messages are fetched using the POP3 TOP
+ * and LIST commands.
+ */
+ @Override
+ public synchronized void fetch(Message[] msgs, FetchProfile fp)
+ throws MessagingException {
+ checkReadable();
+ if (!doneUidl && store.supportsUidl &&
+ fp.contains(UIDFolder.FetchProfileItem.UID)) {
+ /*
+ * Since the POP3 protocol only lets us fetch the UID
+ * for a single message or for all messages, we go ahead
+ * and fetch UIDs for all messages here, ignoring the msgs
+ * parameter. We could be more intelligent and base this
+ * decision on the number of messages fetched, or the
+ * percentage of the total number of messages fetched.
+ */
+ String[] uids = new String[message_cache.length];
+ try {
+ if (!port.uidl(uids))
+ return;
+ } catch (EOFException eex) {
+ close(false);
+ throw new FolderClosedException(this, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error getting UIDL", ex);
+ }
+ for (int i = 0; i < uids.length; i++) {
+ if (uids[i] == null)
+ continue;
+ POP3Message m = (POP3Message)getMessage(i + 1);
+ m.uid = uids[i];
+ }
+ doneUidl = true; // only do this once
+ }
+ if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+ for (int i = 0; i < msgs.length; i++) {
+ try {
+ POP3Message msg = (POP3Message)msgs[i];
+ // fetch headers
+ msg.getHeader("");
+ // fetch message size
+ msg.getSize();
+ } catch (MessageRemovedException mex) {
+ // should never happen, but ignore it if it does
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the unique ID string for this message, or null if
+ * not available. Uses the POP3 UIDL command.
+ *
+ * @param msg the message
+ * @return unique ID string
+ * @exception MessagingException for failures
+ */
+ public synchronized String getUID(Message msg) throws MessagingException {
+ checkOpen();
+ if (!(msg instanceof POP3Message))
+ throw new MessagingException("message is not a POP3Message");
+ POP3Message m = (POP3Message)msg;
+ try {
+ if (!store.supportsUidl)
+ return null;
+ if (m.uid == POP3Message.UNKNOWN)
+ m.uid = port.uidl(m.getMessageNumber());
+ return m.uid;
+ } catch (EOFException eex) {
+ close(false);
+ throw new FolderClosedException(this, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error getting UIDL", ex);
+ }
+ }
+
+ /**
+ * Return the size of this folder, as was returned by the POP3 STAT
+ * command when this folder was opened.
+ *
+ * @return folder size
+ * @exception IllegalStateException if the folder isn't open
+ * @exception MessagingException for other failures
+ */
+ public synchronized int getSize() throws MessagingException {
+ checkOpen();
+ return size;
+ }
+
+ /**
+ * Return the sizes of all messages in this folder, as returned
+ * by the POP3 LIST command. Each entry in the array corresponds
+ * to a message; entry i corresponds to message number i+1 .
+ *
+ * @return array of message sizes
+ * @exception IllegalStateException if the folder isn't open
+ * @exception MessagingException for other failures
+ * @since JavaMail 1.3.3
+ */
+ public synchronized int[] getSizes() throws MessagingException {
+ checkOpen();
+ int sizes[] = new int[total];
+ InputStream is = null;
+ LineInputStream lis = null;
+ try {
+ is = port.list();
+ lis = new LineInputStream(is);
+ String line;
+ while ((line = lis.readLine()) != null) {
+ try {
+ StringTokenizer st = new StringTokenizer(line);
+ int msgnum = Integer.parseInt(st.nextToken());
+ int size = Integer.parseInt(st.nextToken());
+ if (msgnum > 0 && msgnum <= total)
+ sizes[msgnum - 1] = size;
+ } catch (RuntimeException e) {
+ }
+ }
+ } catch (IOException ex) {
+ // ignore it?
+ } finally {
+ try {
+ if (lis != null)
+ lis.close();
+ } catch (IOException cex) { }
+ try {
+ if (is != null)
+ is.close();
+ } catch (IOException cex) { }
+ }
+ return sizes;
+ }
+
+ /**
+ * Return the raw results of the POP3 LIST command with no arguments.
+ *
+ * @return InputStream containing results
+ * @exception IllegalStateException if the folder isn't open
+ * @exception IOException for I/O errors talking to the server
+ * @exception MessagingException for other errors
+ * @since JavaMail 1.3.3
+ */
+ public synchronized InputStream listCommand()
+ throws MessagingException, IOException {
+ checkOpen();
+ return port.list();
+ }
+
+ /**
+ * Close the folder when we're finalized.
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ forceClose = !store.finalizeCleanClose;
+ try {
+ if (opened)
+ close(false);
+ } finally {
+ super.finalize();
+ forceClose = false;
+ }
+ }
+
+ /* Ensure the folder is open */
+ private void checkOpen() throws IllegalStateException {
+ if (!opened)
+ throw new IllegalStateException("Folder is not Open");
+ }
+
+ /* Ensure the folder is not open */
+ private void checkClosed() throws IllegalStateException {
+ if (opened)
+ throw new IllegalStateException("Folder is Open");
+ }
+
+ /* Ensure the folder is open & readable */
+ private void checkReadable() throws IllegalStateException {
+ if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
+ throw new IllegalStateException("Folder is not Readable");
+ }
+
+ /* Ensure the folder is open & writable */
+ /*
+ private void checkWritable() throws IllegalStateException {
+ if (!opened || mode != READ_WRITE)
+ throw new IllegalStateException("Folder is not Writable");
+ }
+ */
+
+ /**
+ * Centralize access to the Protocol object by POP3Message
+ * objects so that they will fail appropriately when the folder
+ * is closed.
+ */
+ Protocol getProtocol() throws MessagingException {
+ Protocol p = port; // read it before close() can set it to null
+ checkOpen();
+ // close() might happen here
+ return p;
+ }
+
+ /*
+ * Only here to make accessible to POP3Message.
+ */
+ @Override
+ protected void notifyMessageChangedListeners(int type, Message m) {
+ super.notifyMessageChangedListeners(type, m);
+ }
+
+ /**
+ * Used by POP3Message.
+ */
+ TempFile getFileCache() {
+ return fileCache;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3Message.java b/app/src/main/java/com/sun/mail/pop3/POP3Message.java
new file mode 100644
index 0000000000..7bc9c9fabe
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3Message.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.lang.ref.SoftReference;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.event.*;
+import com.sun.mail.util.ReadableMime;
+
+/**
+ * A POP3 Message. Just like a MimeMessage except that
+ * some things are not supported.
+ *
+ * @author Bill Shannon
+ */
+public class POP3Message extends MimeMessage implements ReadableMime {
+
+ /*
+ * Our locking strategy is to always lock the POP3Folder before the
+ * POP3Message so we have to be careful to drop our lock before calling
+ * back to the folder to close it and notify of connection lost events.
+ */
+
+ // flag to indicate we haven't tried to fetch the UID yet
+ static final String UNKNOWN = "UNKNOWN";
+
+ private POP3Folder folder; // overrides folder in MimeMessage
+ private int hdrSize = -1;
+ private int msgSize = -1;
+ String uid = UNKNOWN; // controlled by folder lock
+
+ // rawData itself is never null
+ private SoftReference rawData
+ = new SoftReference<>(null);
+
+ public POP3Message(Folder folder, int msgno)
+ throws MessagingException {
+ super(folder, msgno);
+ assert folder instanceof POP3Folder;
+ this.folder = (POP3Folder)folder;
+ }
+
+ /**
+ * Set the specified flags on this message to the specified value.
+ *
+ * @param newFlags the flags to be set
+ * @param set the value to be set
+ */
+ @Override
+ public synchronized void setFlags(Flags newFlags, boolean set)
+ throws MessagingException {
+ Flags oldFlags = (Flags)flags.clone();
+ super.setFlags(newFlags, set);
+ if (!flags.equals(oldFlags))
+ folder.notifyMessageChangedListeners(
+ MessageChangedEvent.FLAGS_CHANGED, this);
+ }
+
+ /**
+ * Return the size of the content of this message in bytes.
+ * Returns -1 if the size cannot be determined.
+ *
+ * Note that this number may not be an exact measure of the
+ * content size and may or may not account for any transfer
+ * encoding of the content.
+ *
+ * @return size of content in bytes
+ * @exception MessagingException for failures
+ */
+ @Override
+ public int getSize() throws MessagingException {
+ try {
+ synchronized (this) {
+ // if we already have the size, return it
+ if (msgSize > 0)
+ return msgSize;
+ }
+
+ /*
+ * Use LIST to determine the entire message
+ * size and subtract out the header size
+ * (which may involve loading the headers,
+ * which may load the content as a side effect).
+ * If the content is loaded as a side effect of
+ * loading the headers, it will set the size.
+ *
+ * Make sure to call loadHeaders() outside of the
+ * synchronization block. There's a potential race
+ * condition here but synchronization will occur in
+ * loadHeaders() to make sure the headers are only
+ * loaded once, and again in the following block to
+ * only compute msgSize once.
+ */
+ if (headers == null)
+ loadHeaders();
+
+ synchronized (this) {
+ if (msgSize < 0)
+ msgSize = folder.getProtocol().list(msgnum) - hdrSize;
+ return msgSize;
+ }
+ } catch (EOFException eex) {
+ folder.close(false);
+ throw new FolderClosedException(folder, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error getting size", ex);
+ }
+ }
+
+ /**
+ * Produce the raw bytes of the message. The data is fetched using
+ * the POP3 RETR command. If skipHeader is true, just the content
+ * is returned.
+ */
+ private InputStream getRawStream(boolean skipHeader)
+ throws MessagingException {
+ InputStream rawcontent = null;
+ try {
+ synchronized(this) {
+ rawcontent = rawData.get();
+ if (rawcontent == null) {
+ TempFile cache = folder.getFileCache();
+ if (cache != null) {
+ if (folder.logger.isLoggable(Level.FINE))
+ folder.logger.fine("caching message #" + msgnum +
+ " in temp file");
+ AppendStream os = cache.getAppendStream();
+ BufferedOutputStream bos = new BufferedOutputStream(os);
+ try {
+ folder.getProtocol().retr(msgnum, bos);
+ } finally {
+ bos.close();
+ }
+ rawcontent = os.getInputStream();
+ } else {
+ rawcontent = folder.getProtocol().retr(msgnum,
+ msgSize > 0 ? msgSize + hdrSize : 0);
+ }
+ if (rawcontent == null) {
+ expunged = true;
+ throw new MessageRemovedException(
+ "can't retrieve message #" + msgnum +
+ " in POP3Message.getContentStream"); // XXX - what else?
+ }
+
+ if (headers == null ||
+ ((POP3Store)(folder.getStore())).forgetTopHeaders) {
+ headers = new InternetHeaders(rawcontent);
+ hdrSize =
+ (int)((SharedInputStream)rawcontent).getPosition();
+ } else {
+ /*
+ * Already have the headers, have to skip the headers
+ * in the content array and return the body.
+ *
+ * XXX - It seems that some mail servers return slightly
+ * different headers in the RETR results than were returned
+ * in the TOP results, so we can't depend on remembering
+ * the size of the headers from the TOP command and just
+ * skipping that many bytes. Instead, we have to process
+ * the content, skipping over the header until we come to
+ * the empty line that separates the header from the body.
+ */
+ int offset = 0;
+ for (;;) {
+ int len = 0; // number of bytes in this line
+ int c1;
+ while ((c1 = rawcontent.read()) >= 0) {
+ if (c1 == '\n') // end of line
+ break;
+ else if (c1 == '\r') {
+ // got CR, is the next char LF?
+ if (rawcontent.available() > 0) {
+ rawcontent.mark(1);
+ if (rawcontent.read() != '\n')
+ rawcontent.reset();
+ }
+ break; // in any case, end of line
+ }
+
+ // not CR, NL, or CRLF, count the byte
+ len++;
+ }
+ // here when end of line or out of data
+
+ // if out of data, we're done
+ if (rawcontent.available() == 0)
+ break;
+
+ // if it was an empty line, we're done
+ if (len == 0)
+ break;
+ }
+ hdrSize =
+ (int)((SharedInputStream)rawcontent).getPosition();
+ }
+
+ // skipped the header, the message is what's left
+ msgSize = rawcontent.available();
+
+ rawData = new SoftReference<>(rawcontent);
+ }
+ }
+ } catch (EOFException eex) {
+ folder.close(false);
+ throw new FolderClosedException(folder, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error fetching POP3 content", ex);
+ }
+
+ /*
+ * We have a cached stream, but we need to return
+ * a fresh stream to read from the beginning and
+ * that can be safely closed.
+ */
+ rawcontent = ((SharedInputStream)rawcontent).newStream(
+ skipHeader ? hdrSize : 0, -1);
+ return rawcontent;
+ }
+
+ /**
+ * Produce the raw bytes of the content. The data is fetched using
+ * the POP3 RETR command.
+ *
+ * @see #contentStream
+ */
+ @Override
+ protected synchronized InputStream getContentStream()
+ throws MessagingException {
+ if (contentStream != null)
+ return ((SharedInputStream)contentStream).newStream(0, -1);
+
+ InputStream cstream = getRawStream(true);
+
+ /*
+ * Keep a hard reference to the data if we're using a file
+ * cache or if the "mail.pop3.keepmessagecontent" prop is set.
+ */
+ TempFile cache = folder.getFileCache();
+ if (cache != null ||
+ ((POP3Store)(folder.getStore())).keepMessageContent)
+ contentStream = ((SharedInputStream)cstream).newStream(0, -1);
+ return cstream;
+ }
+
+ /**
+ * Return the MIME format stream corresponding to this message part.
+ *
+ * @return the MIME format stream
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public InputStream getMimeStream() throws MessagingException {
+ return getRawStream(false);
+ }
+
+ /**
+ * Invalidate the cache of content for this message object, causing
+ * it to be fetched again from the server the next time it is needed.
+ * If invalidateHeaders
is true, invalidate the headers
+ * as well.
+ *
+ * @param invalidateHeaders invalidate the headers as well?
+ */
+ public synchronized void invalidate(boolean invalidateHeaders) {
+ content = null;
+ InputStream rstream = rawData.get();
+ if (rstream != null) {
+ // note that if the content is in the file cache, it will be lost
+ // and fetched from the server if it's needed again
+ try {
+ rstream.close();
+ } catch (IOException ex) {
+ // ignore it
+ }
+ rawData = new SoftReference<>(null);
+ }
+ if (contentStream != null) {
+ try {
+ contentStream.close();
+ } catch (IOException ex) {
+ // ignore it
+ }
+ contentStream = null;
+ }
+ msgSize = -1;
+ if (invalidateHeaders) {
+ headers = null;
+ hdrSize = -1;
+ }
+ }
+
+ /**
+ * Fetch the header of the message and the first n
lines
+ * of the raw content of the message. The headers and data are
+ * available in the returned InputStream.
+ *
+ * @param n number of lines of content to fetch
+ * @return InputStream containing the message headers and n content lines
+ * @exception MessagingException for failures
+ */
+ public InputStream top(int n) throws MessagingException {
+ try {
+ synchronized (this) {
+ return folder.getProtocol().top(msgnum, n);
+ }
+ } catch (EOFException eex) {
+ folder.close(false);
+ throw new FolderClosedException(folder, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error getting size", ex);
+ }
+ }
+
+ /**
+ * Get all the headers for this header_name. Note that certain
+ * headers may be encoded as per RFC 2047 if they contain
+ * non US-ASCII characters and these should be decoded.
+ *
+ * @param name name of header
+ * @return array of headers
+ * @exception MessagingException for failures
+ * @see javax.mail.internet.MimeUtility
+ */
+ @Override
+ public String[] getHeader(String name)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getHeader(name);
+ }
+
+ /**
+ * Get all the headers for this header name, returned as a single
+ * String, with headers separated by the delimiter. If the
+ * delimiter is null
, only the first header is
+ * returned.
+ *
+ * @param name the name of this header
+ * @param delimiter delimiter between returned headers
+ * @return the value fields for all headers with
+ * this name
+ * @exception MessagingException for failures
+ */
+ @Override
+ public String getHeader(String name, String delimiter)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getHeader(name, delimiter);
+ }
+
+ /**
+ * Set the value for this header_name. Throws IllegalWriteException
+ * because POP3 messages are read-only.
+ *
+ * @param name header name
+ * @param value header value
+ * @see javax.mail.internet.MimeUtility
+ * @exception IllegalWriteException because the underlying
+ * implementation does not support modification
+ * @exception IllegalStateException if this message is
+ * obtained from a READ_ONLY folder.
+ * @exception MessagingException for other failures
+ */
+ @Override
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ // XXX - should check for read-only folder?
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * Add this value to the existing values for this header_name.
+ * Throws IllegalWriteException because POP3 messages are read-only.
+ *
+ * @param name header name
+ * @param value header value
+ * @see javax.mail.internet.MimeUtility
+ * @exception IllegalWriteException because the underlying
+ * implementation does not support modification
+ * @exception IllegalStateException if this message is
+ * obtained from a READ_ONLY folder.
+ */
+ @Override
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ // XXX - should check for read-only folder?
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * Remove all headers with this name.
+ * Throws IllegalWriteException because POP3 messages are read-only.
+ *
+ * @exception IllegalWriteException because the underlying
+ * implementation does not support modification
+ * @exception IllegalStateException if this message is
+ * obtained from a READ_ONLY folder.
+ */
+ @Override
+ public void removeHeader(String name)
+ throws MessagingException {
+ // XXX - should check for read-only folder?
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * Return all the headers from this Message as an enumeration
+ * of Header objects.
+ *
+ * Note that certain headers may be encoded as per RFC 2047
+ * if they contain non US-ASCII characters and these should
+ * be decoded.
+ *
+ * @return array of header objects
+ * @exception MessagingException for failures
+ * @see javax.mail.internet.MimeUtility
+ */
+ @Override
+ public Enumeration getAllHeaders() throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getAllHeaders();
+ }
+
+ /**
+ * Return matching headers from this Message as an Enumeration of
+ * Header objects.
+ *
+ * @exception MessagingException for failures
+ */
+ @Override
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getMatchingHeaders(names);
+ }
+
+ /**
+ * Return non-matching headers from this Message as an
+ * Enumeration of Header objects.
+ *
+ * @exception MessagingException for failures
+ */
+ @Override
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getNonMatchingHeaders(names);
+ }
+
+ /**
+ * Add a raw RFC822 header-line.
+ * Throws IllegalWriteException because POP3 messages are read-only.
+ *
+ * @exception IllegalWriteException because the underlying
+ * implementation does not support modification
+ * @exception IllegalStateException if this message is
+ * obtained from a READ_ONLY folder.
+ */
+ @Override
+ public void addHeaderLine(String line) throws MessagingException {
+ // XXX - should check for read-only folder?
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * Get all header lines as an Enumeration of Strings. A Header
+ * line is a raw RFC822 header-line, containing both the "name"
+ * and "value" field.
+ *
+ * @exception MessagingException for failures
+ */
+ @Override
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getAllHeaderLines();
+ }
+
+ /**
+ * Get matching header lines as an Enumeration of Strings.
+ * A Header line is a raw RFC822 header-line, containing both
+ * the "name" and "value" field.
+ *
+ * @exception MessagingException for failures
+ */
+ @Override
+ public Enumeration getMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getMatchingHeaderLines(names);
+ }
+
+ /**
+ * Get non-matching header lines as an Enumeration of Strings.
+ * A Header line is a raw RFC822 header-line, containing both
+ * the "name" and "value" field.
+ *
+ * @exception MessagingException for failures
+ */
+ @Override
+ public Enumeration getNonMatchingHeaderLines(String[] names)
+ throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getNonMatchingHeaderLines(names);
+ }
+
+ /**
+ * POP3 message can't be changed. This method throws
+ * IllegalWriteException.
+ *
+ * @exception IllegalWriteException because the underlying
+ * implementation does not support modification
+ */
+ @Override
+ public void saveChanges() throws MessagingException {
+ // POP3 Messages are read-only
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * Output the message as an RFC 822 format stream, without
+ * specified headers. If the property "mail.pop3.cachewriteto"
+ * is set to "true", and ignoreList is null, and the message hasn't
+ * already been cached as a side effect of other operations, the message
+ * content is cached before being written. Otherwise, the message is
+ * streamed directly to the output stream without being cached.
+ *
+ * @exception IOException if an error occurs writing to the stream
+ * or if an error is generated by the
+ * javax.activation layer.
+ * @exception MessagingException for other failures
+ * @see javax.activation.DataHandler#writeTo
+ */
+ @Override
+ public synchronized void writeTo(OutputStream os, String[] ignoreList)
+ throws IOException, MessagingException {
+ InputStream rawcontent = rawData.get();
+ if (rawcontent == null && ignoreList == null &&
+ !((POP3Store)(folder.getStore())).cacheWriteTo) {
+ if (folder.logger.isLoggable(Level.FINE))
+ folder.logger.fine("streaming msg " + msgnum);
+ if (!folder.getProtocol().retr(msgnum, os)) {
+ expunged = true;
+ throw new MessageRemovedException("can't retrieve message #" +
+ msgnum + " in POP3Message.writeTo"); // XXX - what else?
+ }
+ } else if (rawcontent != null && ignoreList == null) {
+ // can just copy the cached data
+ InputStream in = ((SharedInputStream)rawcontent).newStream(0, -1);
+ try {
+ byte[] buf = new byte[16*1024];
+ int len;
+ while ((len = in.read(buf)) > 0)
+ os.write(buf, 0, len);
+ } finally {
+ try {
+ if (in != null)
+ in.close();
+ } catch (IOException ex) { }
+ }
+ } else
+ super.writeTo(os, ignoreList);
+ }
+
+ /**
+ * Load the headers for this message into the InternetHeaders object.
+ * The headers are fetched using the POP3 TOP command.
+ */
+ private void loadHeaders() throws MessagingException {
+ assert !Thread.holdsLock(this);
+ try {
+ boolean fetchContent = false;
+ synchronized (this) {
+ if (headers != null) // check again under lock
+ return;
+ InputStream hdrs = null;
+ if (((POP3Store)(folder.getStore())).disableTop ||
+ (hdrs = folder.getProtocol().top(msgnum, 0)) == null) {
+ // possibly because the TOP command isn't supported,
+ // load headers as a side effect of loading the entire
+ // content.
+ fetchContent = true;
+ } else {
+ try {
+ hdrSize = hdrs.available();
+ headers = new InternetHeaders(hdrs);
+ } finally {
+ hdrs.close();
+ }
+ }
+ }
+
+ /*
+ * Outside the synchronization block...
+ *
+ * Do we need to fetch the entire mesage content in order to
+ * load the headers as a side effect? Yes, there's a race
+ * condition here - multiple threads could decide that the
+ * content needs to be fetched. Fortunately, they'll all
+ * synchronize in the getContentStream method and the content
+ * will only be loaded once.
+ */
+ if (fetchContent) {
+ InputStream cs = null;
+ try {
+ cs = getContentStream();
+ } finally {
+ if (cs != null)
+ cs.close();
+ }
+ }
+ } catch (EOFException eex) {
+ folder.close(false);
+ throw new FolderClosedException(folder, eex.toString());
+ } catch (IOException ex) {
+ throw new MessagingException("error loading POP3 headers", ex);
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3Provider.java b/app/src/main/java/com/sun/mail/pop3/POP3Provider.java
new file mode 100644
index 0000000000..cf73a6c333
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3Provider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The POP3 protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class POP3Provider extends Provider {
+ public POP3Provider() {
+ super(Provider.Type.STORE, "pop3", POP3Store.class.getName(),
+ "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java b/app/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java
new file mode 100644
index 0000000000..4831c50e86
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The POP3 SSL protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class POP3SSLProvider extends Provider {
+ public POP3SSLProvider() {
+ super(Provider.Type.STORE, "pop3s", POP3SSLStore.class.getName(),
+ "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3SSLStore.java b/app/src/main/java/com/sun/mail/pop3/POP3SSLStore.java
new file mode 100644
index 0000000000..fa0054c508
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3SSLStore.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+
+/**
+ * A POP3 Message Store using SSL. Contains only one folder, "INBOX".
+ *
+ * @author Bill Shannon
+ */
+public class POP3SSLStore extends POP3Store {
+
+ public POP3SSLStore(Session session, URLName url) {
+ super(session, url, "pop3s", true);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/POP3Store.java b/app/src/main/java/com/sun/mail/pop3/POP3Store.java
new file mode 100644
index 0000000000..c9818a1de7
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/POP3Store.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.util.Properties;
+import java.util.logging.Level;
+import java.lang.reflect.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import java.io.File;
+import java.io.PrintStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.Collections;
+import java.util.Map;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.MailConnectException;
+
+/**
+ * A POP3 Message Store. Contains only one folder, "INBOX".
+ *
+ * See the com.sun.mail.pop3 package
+ * documentation for further information on the POP3 protocol provider.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public class POP3Store extends Store {
+
+ private String name = "pop3"; // my protocol name
+ private int defaultPort = 110; // default POP3 port
+ private boolean isSSL = false; // use SSL?
+
+ private Protocol port = null; // POP3 port for self
+ private POP3Folder portOwner = null; // folder owning port
+ private String host = null; // host
+ private int portNum = -1;
+ private String user = null;
+ private String passwd = null;
+ private boolean useStartTLS = false;
+ private boolean requireStartTLS = false;
+ private boolean usingSSL = false;
+ private Map capabilities;
+ private MailLogger logger;
+
+ // following set here and accessed by other classes in this package
+ volatile Constructor> messageConstructor = null;
+ volatile boolean rsetBeforeQuit = false;
+ volatile boolean disableTop = false;
+ volatile boolean forgetTopHeaders = false;
+ volatile boolean supportsUidl = true;
+ volatile boolean cacheWriteTo = false;
+ volatile boolean useFileCache = false;
+ volatile File fileCacheDir = null;
+ volatile boolean keepMessageContent = false;
+ volatile boolean finalizeCleanClose = false;
+
+ public POP3Store(Session session, URLName url) {
+ this(session, url, "pop3", false);
+ }
+
+ public POP3Store(Session session, URLName url,
+ String name, boolean isSSL) {
+ super(session, url);
+ if (url != null)
+ name = url.getProtocol();
+ this.name = name;
+ logger = new MailLogger(this.getClass(), "DEBUG POP3",
+ session.getDebug(), session.getDebugOut());
+
+ if (!isSSL)
+ isSSL = PropUtil.getBooleanProperty(session.getProperties(),
+ "mail." + name + ".ssl.enable", false);
+ if (isSSL)
+ this.defaultPort = 995;
+ else
+ this.defaultPort = 110;
+ this.isSSL = isSSL;
+
+ rsetBeforeQuit = getBoolProp("rsetbeforequit");
+ disableTop = getBoolProp("disabletop");
+ forgetTopHeaders = getBoolProp("forgettopheaders");
+ cacheWriteTo = getBoolProp("cachewriteto");
+ useFileCache = getBoolProp("filecache.enable");
+ String dir = session.getProperty("mail." + name + ".filecache.dir");
+ if (dir != null && logger.isLoggable(Level.CONFIG))
+ logger.config("mail." + name + ".filecache.dir: " + dir);
+ if (dir != null)
+ fileCacheDir = new File(dir);
+ keepMessageContent = getBoolProp("keepmessagecontent");
+
+ // mail.pop3.starttls.enable enables use of STLS command
+ useStartTLS = getBoolProp("starttls.enable");
+
+ // mail.pop3.starttls.required requires use of STLS command
+ requireStartTLS = getBoolProp("starttls.required");
+
+ // mail.pop3.finalizecleanclose requires clean close when finalizing
+ finalizeCleanClose = getBoolProp("finalizecleanclose");
+
+ String s = session.getProperty("mail." + name + ".message.class");
+ if (s != null) {
+ logger.log(Level.CONFIG, "message class: {0}", s);
+ try {
+ ClassLoader cl = this.getClass().getClassLoader();
+
+ // now load the class
+ Class> messageClass = null;
+ try {
+ // First try the "application's" class loader.
+ // This should eventually be replaced by
+ // Thread.currentThread().getContextClassLoader().
+ messageClass = Class.forName(s, false, cl);
+ } catch (ClassNotFoundException ex1) {
+ // That didn't work, now try the "system" class loader.
+ // (Need both of these because JDK 1.1 class loaders
+ // may not delegate to their parent class loader.)
+ messageClass = Class.forName(s);
+ }
+
+ Class>[] c = {javax.mail.Folder.class, int.class};
+ messageConstructor = messageClass.getConstructor(c);
+ } catch (Exception ex) {
+ logger.log(Level.CONFIG, "failed to load message class", ex);
+ }
+ }
+ }
+
+ /**
+ * Get the value of a boolean property.
+ * Print out the value if logging is enabled.
+ */
+ private final synchronized boolean getBoolProp(String prop) {
+ prop = "mail." + name + "." + prop;
+ boolean val = PropUtil.getBooleanProperty(session.getProperties(),
+ prop, false);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config(prop + ": " + val);
+ return val;
+ }
+
+ /**
+ * Get a reference to the session.
+ */
+ synchronized Session getSession() {
+ return session;
+ }
+
+ @Override
+ protected synchronized boolean protocolConnect(String host, int portNum,
+ String user, String passwd) throws MessagingException {
+
+ // check for non-null values of host, password, user
+ if (host == null || passwd == null || user == null)
+ return false;
+
+ // if port is not specified, set it to value of mail.pop3.port
+ // property if it exists, otherwise default to 110
+ if (portNum == -1)
+ portNum = PropUtil.getIntProperty(session.getProperties(),
+ "mail." + name + ".port", -1);
+
+ if (portNum == -1)
+ portNum = defaultPort;
+
+ this.host = host;
+ this.portNum = portNum;
+ this.user = user;
+ this.passwd = passwd;
+ try {
+ port = getPort(null);
+ } catch (EOFException eex) {
+ throw new AuthenticationFailedException(eex.getMessage());
+ } catch (SocketConnectException scex) {
+ throw new MailConnectException(scex);
+ } catch (IOException ioex) {
+ throw new MessagingException("Connect failed", ioex);
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether this store is connected. Override superclass
+ * method, to actually ping our server connection.
+ */
+ /*
+ * Note that we maintain somewhat of an illusion of being connected
+ * even if we're not really connected. This is because a Folder
+ * can use the connection and close it when it's done. If we then
+ * ask whether the Store's connected we want the answer to be true,
+ * as long as we can reconnect at that point. This means that we
+ * need to be able to reconnect the Store on demand.
+ */
+ @Override
+ public synchronized boolean isConnected() {
+ if (!super.isConnected())
+ // if we haven't been connected at all, don't bother with
+ // the NOOP.
+ return false;
+ try {
+ if (port == null)
+ port = getPort(null);
+ else if (!port.noop())
+ throw new IOException("NOOP failed");
+ return true;
+ } catch (IOException ioex) {
+ // no longer connected, close it down
+ try {
+ super.close(); // notifies listeners
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ return false;
+ }
+ }
+
+ synchronized Protocol getPort(POP3Folder owner) throws IOException {
+ Protocol p;
+
+ // if we already have a port, remember who's using it
+ if (port != null && portOwner == null) {
+ portOwner = owner;
+ return port;
+ }
+
+ // need a new port, create it and try to login
+ p = new Protocol(host, portNum, logger,
+ session.getProperties(), "mail." + name, isSSL);
+
+ if (useStartTLS || requireStartTLS) {
+ if (p.hasCapability("STLS")) {
+ if (p.stls()) {
+ // success, refresh capabilities
+ p.setCapabilities(p.capa());
+ } else if (requireStartTLS) {
+ logger.fine("STLS required but failed");
+ throw cleanupAndThrow(p,
+ new EOFException("STLS required but failed"));
+ }
+ } else if (requireStartTLS) {
+ logger.fine("STLS required but not supported");
+ throw cleanupAndThrow(p,
+ new EOFException("STLS required but not supported"));
+ }
+ }
+
+ capabilities = p.getCapabilities(); // save for later, may be null
+ usingSSL = p.isSSL(); // in case anyone asks
+
+ /*
+ * If we haven't explicitly disabled use of the TOP command,
+ * and the server has provided its capabilities,
+ * and the server doesn't support the TOP command,
+ * disable the TOP command.
+ */
+ if (!disableTop &&
+ capabilities != null && !capabilities.containsKey("TOP")) {
+ disableTop = true;
+ logger.fine("server doesn't support TOP, disabling it");
+ }
+
+ supportsUidl = capabilities == null || capabilities.containsKey("UIDL");
+
+ String msg = null;
+ if ((msg = p.login(user, passwd)) != null) {
+ throw cleanupAndThrow(p, new EOFException(msg));
+ }
+
+ /*
+ * If a Folder closes the port, and then a Folder
+ * is opened, the Store won't have a port. In that
+ * case, the getPort call will come from Folder.open,
+ * but we need to keep track of the port in the Store
+ * so that a later call to Folder.isOpen, which calls
+ * Store.isConnected, will use the same port.
+ */
+ if (port == null && owner != null) {
+ port = p;
+ portOwner = owner;
+ }
+ if (portOwner == null)
+ portOwner = owner;
+ return p;
+ }
+
+ private static IOException cleanupAndThrow(Protocol p, IOException ife) {
+ try {
+ p.quit();
+ } catch (Throwable thr) {
+ if (isRecoverable(thr)) {
+ ife.addSuppressed(thr);
+ } else {
+ thr.addSuppressed(ife);
+ if (thr instanceof Error) {
+ throw (Error) thr;
+ }
+ if (thr instanceof RuntimeException) {
+ throw (RuntimeException) thr;
+ }
+ throw new RuntimeException("unexpected exception", thr);
+ }
+ }
+ return ife;
+ }
+
+ private static boolean isRecoverable(Throwable t) {
+ return (t instanceof Exception) || (t instanceof LinkageError);
+ }
+
+ synchronized void closePort(POP3Folder owner) {
+ if (portOwner == owner) {
+ port = null;
+ portOwner = null;
+ }
+ }
+
+ @Override
+ public synchronized void close() throws MessagingException {
+ close(false);
+ }
+
+ synchronized void close(boolean force) throws MessagingException {
+ try {
+ if (port != null) {
+ if (force)
+ port.close();
+ else
+ port.quit();
+ }
+ } catch (IOException ioex) {
+ } finally {
+ port = null;
+
+ // to set the state and send the closed connection event
+ super.close();
+ }
+ }
+
+ @Override
+ public Folder getDefaultFolder() throws MessagingException {
+ checkConnected();
+ return new DefaultFolder(this);
+ }
+
+ /**
+ * Only the name "INBOX" is supported.
+ */
+ @Override
+ public Folder getFolder(String name) throws MessagingException {
+ checkConnected();
+ return new POP3Folder(this, name);
+ }
+
+ @Override
+ public Folder getFolder(URLName url) throws MessagingException {
+ checkConnected();
+ return new POP3Folder(this, url.getFile());
+ }
+
+ /**
+ * Return a Map of the capabilities the server provided,
+ * as per RFC 2449. If the server doesn't support RFC 2449,
+ * an emtpy Map is returned. The returned Map can not be modified.
+ * The key to the Map is the upper case capability name as
+ * a String. The value of the entry is the entire String
+ * capability line returned by the server.
+ *
+ * For example, to check if the server supports the STLS capability, use:
+ * if (store.capabilities().containsKey("STLS")) ...
+ *
+ * @return Map of capabilities
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.3
+ */
+ public Map capabilities() throws MessagingException {
+ Map c;
+ synchronized (this) {
+ c = capabilities;
+ }
+ if (c != null)
+ return Collections.unmodifiableMap(c);
+ else
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Is this POP3Store using SSL to connect to the server?
+ *
+ * @return true if using SSL
+ * @since JavaMail 1.4.6
+ */
+ public synchronized boolean isSSL() {
+ return usingSSL;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (port != null) // don't force a connection attempt
+ close(!finalizeCleanClose);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void checkConnected() throws MessagingException {
+ if (!super.isConnected())
+ throw new MessagingException("Not connected");
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/Protocol.java b/app/src/main/java/com/sun/mail/pop3/Protocol.java
new file mode 100644
index 0000000000..85c9a0f953
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/Protocol.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.security.*;
+import java.util.logging.Level;
+import javax.net.ssl.SSLSocket;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+import com.sun.mail.util.SharedByteArrayOutputStream;
+
+class Response {
+ boolean ok = false; // true if "+OK"
+ String data = null; // rest of line after "+OK" or "-ERR"
+ InputStream bytes = null; // all the bytes from a multi-line response
+}
+
+/**
+ * This class provides a POP3 connection and implements
+ * the POP3 protocol requests.
+ *
+ * APOP support courtesy of "chamness".
+ *
+ * @author Bill Shannon
+ */
+class Protocol {
+ private Socket socket; // POP3 socket
+ private String host; // host we're connected to
+ private Properties props; // session properties
+ private String prefix; // protocol name prefix, for props
+ private BufferedReader input; // input buf
+ private PrintWriter output; // output buf
+ private TraceInputStream traceInput;
+ private TraceOutputStream traceOutput;
+ private MailLogger logger;
+ private MailLogger traceLogger;
+ private String apopChallenge = null;
+ private Map capabilities = null;
+ private boolean pipelining;
+ private boolean noauthdebug = true; // hide auth info in debug output
+ private boolean traceSuspended; // temporarily suspend tracing
+
+ private static final int POP3_PORT = 110; // standard POP3 port
+ private static final String CRLF = "\r\n";
+ // sometimes the returned size isn't quite big enough
+ private static final int SLOP = 128;
+
+ /**
+ * Open a connection to the POP3 server.
+ */
+ Protocol(String host, int port, MailLogger logger,
+ Properties props, String prefix, boolean isSSL)
+ throws IOException {
+ this.host = host;
+ this.props = props;
+ this.prefix = prefix;
+ this.logger = logger;
+ traceLogger = logger.getSubLogger("protocol", null);
+ noauthdebug = !PropUtil.getBooleanProperty(props,
+ "mail.debug.auth", false);
+
+ Response r;
+ boolean enableAPOP = getBoolProp(props, prefix + ".apop.enable");
+ boolean disableCapa = getBoolProp(props, prefix + ".disablecapa");
+ try {
+ if (port == -1)
+ port = POP3_PORT;
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("connecting to host \"" + host +
+ "\", port " + port + ", isSSL " + isSSL);
+
+ socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
+ initStreams();
+ r = simpleCommand(null);
+ } catch (IOException ioe) {
+ throw cleanupAndThrow(socket, ioe);
+ }
+
+ if (!r.ok) {
+ throw cleanupAndThrow(socket, new IOException("Connect failed"));
+ }
+ if (enableAPOP && r.data != null) {
+ int challStart = r.data.indexOf('<'); // start of challenge
+ int challEnd = r.data.indexOf('>', challStart); // end of challenge
+ if (challStart != -1 && challEnd != -1)
+ apopChallenge = r.data.substring(challStart, challEnd + 1);
+ logger.log(Level.FINE, "APOP challenge: {0}", apopChallenge);
+ }
+
+ // if server supports RFC 2449, set capabilities
+ if (!disableCapa)
+ setCapabilities(capa());
+
+ pipelining = hasCapability("PIPELINING") ||
+ PropUtil.getBooleanProperty(props, prefix + ".pipelining", false);
+ if (pipelining)
+ logger.config("PIPELINING enabled");
+ }
+
+ private static IOException cleanupAndThrow(Socket socket, IOException ife) {
+ try {
+ socket.close();
+ } catch (Throwable thr) {
+ if (isRecoverable(thr)) {
+ ife.addSuppressed(thr);
+ } else {
+ thr.addSuppressed(ife);
+ if (thr instanceof Error) {
+ throw (Error) thr;
+ }
+ if (thr instanceof RuntimeException) {
+ throw (RuntimeException) thr;
+ }
+ throw new RuntimeException("unexpected exception", thr);
+ }
+ }
+ return ife;
+ }
+
+ private static boolean isRecoverable(Throwable t) {
+ return (t instanceof Exception) || (t instanceof LinkageError);
+ }
+
+ /**
+ * Get the value of a boolean property.
+ * Print out the value if logging is enabled.
+ */
+ private final synchronized boolean getBoolProp(Properties props,
+ String prop) {
+ boolean val = PropUtil.getBooleanProperty(props, prop, false);
+ if (logger.isLoggable(Level.CONFIG))
+ logger.config(prop + ": " + val);
+ return val;
+ }
+
+ private void initStreams() throws IOException {
+ boolean quote = PropUtil.getBooleanProperty(props,
+ "mail.debug.quote", false);
+ traceInput =
+ new TraceInputStream(socket.getInputStream(), traceLogger);
+ traceInput.setQuote(quote);
+
+ traceOutput =
+ new TraceOutputStream(socket.getOutputStream(), traceLogger);
+ traceOutput.setQuote(quote);
+
+ // should be US-ASCII, but not all JDK's support it so use iso-8859-1
+ input = new BufferedReader(new InputStreamReader(traceInput,
+ "iso-8859-1"));
+ output = new PrintWriter(
+ new BufferedWriter(
+ new OutputStreamWriter(traceOutput, "iso-8859-1")));
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (socket != null) // Forgot to logout ?!
+ quit();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Parse the capabilities from a CAPA response.
+ */
+ synchronized void setCapabilities(InputStream in) {
+ if (in == null) {
+ capabilities = null;
+ return;
+ }
+
+ capabilities = new HashMap<>(10);
+ BufferedReader r = null;
+ try {
+ r = new BufferedReader(new InputStreamReader(in, "us-ascii"));
+ } catch (UnsupportedEncodingException ex) {
+ // should never happen
+ assert false;
+ }
+ String s;
+ try {
+ while ((s = r.readLine()) != null) {
+ String cap = s;
+ int i = cap.indexOf(' ');
+ if (i > 0)
+ cap = cap.substring(0, i);
+ capabilities.put(cap.toUpperCase(Locale.ENGLISH), s);
+ }
+ } catch (IOException ex) {
+ // should never happen
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ex) { }
+ }
+ }
+
+ /**
+ * Check whether the given capability is supported by
+ * this server. Returns true
if so, otherwise
+ * returns false.
+ */
+ synchronized boolean hasCapability(String c) {
+ return capabilities != null &&
+ capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Return the map of capabilities returned by the server.
+ */
+ synchronized Map getCapabilities() {
+ return capabilities;
+ }
+
+ /**
+ * Login to the server, using the USER and PASS commands.
+ */
+ synchronized String login(String user, String password)
+ throws IOException {
+ Response r;
+ // only pipeline password if connection is secure
+ boolean batch = pipelining && socket instanceof SSLSocket;
+
+ try {
+
+ if (noauthdebug && isTracing()) {
+ logger.fine("authentication command trace suppressed");
+ suspendTracing();
+ }
+ String dpw = null;
+ if (apopChallenge != null)
+ dpw = getDigest(password);
+ if (apopChallenge != null && dpw != null) {
+ r = simpleCommand("APOP " + user + " " + dpw);
+ } else if (batch) {
+ String cmd = "USER " + user;
+ batchCommandStart(cmd);
+ issueCommand(cmd);
+ cmd = "PASS " + password;
+ batchCommandContinue(cmd);
+ issueCommand(cmd);
+ r = readResponse();
+ if (!r.ok) {
+ String err = r.data != null ? r.data : "USER command failed";
+ readResponse(); // read and ignore PASS response
+ batchCommandEnd();
+ return err;
+ }
+ r = readResponse();
+ batchCommandEnd();
+ } else {
+ r = simpleCommand("USER " + user);
+ if (!r.ok)
+ return r.data != null ? r.data : "USER command failed";
+ r = simpleCommand("PASS " + password);
+ }
+ if (noauthdebug && isTracing())
+ logger.log(Level.FINE, "authentication command {0}",
+ (r.ok ? "succeeded" : "failed"));
+ if (!r.ok)
+ return r.data != null ? r.data : "login failed";
+ return null;
+
+ } finally {
+ resumeTracing();
+ }
+ }
+
+ /**
+ * Gets the APOP message digest.
+ * From RFC 1939:
+ *
+ * The 'digest' parameter is calculated by applying the MD5
+ * algorithm [RFC1321] to a string consisting of the timestamp
+ * (including angle-brackets) followed by a shared secret.
+ * The 'digest' parameter itself is a 16-octet value which is
+ * sent in hexadecimal format, using lower-case ASCII characters.
+ *
+ * @param password The APOP password
+ * @return The APOP digest or an empty string if an error occurs.
+ */
+ private String getDigest(String password) {
+ String key = apopChallenge + password;
+ byte[] digest;
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ digest = md.digest(key.getBytes("iso-8859-1")); // XXX
+ } catch (NoSuchAlgorithmException nsae) {
+ return null;
+ } catch (UnsupportedEncodingException uee) {
+ return null;
+ }
+ return toHex(digest);
+ }
+
+ private static char[] digits = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ /**
+ * Convert a byte array to a string of hex digits representing the bytes.
+ */
+ private static String toHex(byte[] bytes) {
+ char[] result = new char[bytes.length * 2];
+
+ for (int index = 0, i = 0; index < bytes.length; index++) {
+ int temp = bytes[index] & 0xFF;
+ result[i++] = digits[temp >> 4];
+ result[i++] = digits[temp & 0xF];
+ }
+ return new String(result);
+ }
+
+ /**
+ * Close down the connection, sending the QUIT command.
+ */
+ synchronized boolean quit() throws IOException {
+ boolean ok = false;
+ try {
+ Response r = simpleCommand("QUIT");
+ ok = r.ok;
+ } finally {
+ close();
+ }
+ return ok;
+ }
+
+ /**
+ * Close the connection without sending any commands.
+ */
+ void close() {
+ try {
+ if (socket != null)
+ socket.close();
+ } catch (IOException ex) {
+ // ignore it
+ } finally {
+ socket = null;
+ input = null;
+ output = null;
+ }
+ }
+
+ /**
+ * Return the total number of messages and mailbox size,
+ * using the STAT command.
+ */
+ synchronized Status stat() throws IOException {
+ Response r = simpleCommand("STAT");
+ Status s = new Status();
+
+ /*
+ * Normally the STAT command shouldn't fail but apparently it
+ * does when accessing Hotmail too often, returning:
+ * -ERR login allowed only every 15 minutes
+ * (Why it doesn't just fail the login, I don't know.)
+ * This is a serious failure that we don't want to hide
+ * from the user.
+ */
+ if (!r.ok)
+ throw new IOException("STAT command failed: " + r.data);
+
+ if (r.data != null) {
+ try {
+ StringTokenizer st = new StringTokenizer(r.data);
+ s.total = Integer.parseInt(st.nextToken());
+ s.size = Integer.parseInt(st.nextToken());
+ } catch (RuntimeException e) {
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Return the size of the message using the LIST command.
+ */
+ synchronized int list(int msg) throws IOException {
+ Response r = simpleCommand("LIST " + msg);
+ int size = -1;
+ if (r.ok && r.data != null) {
+ try {
+ StringTokenizer st = new StringTokenizer(r.data);
+ st.nextToken(); // skip message number
+ size = Integer.parseInt(st.nextToken());
+ } catch (RuntimeException e) {
+ // ignore it
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Return the size of all messages using the LIST command.
+ */
+ synchronized InputStream list() throws IOException {
+ Response r = multilineCommand("LIST", 128); // 128 == output size est
+ return r.bytes;
+ }
+
+ /**
+ * Retrieve the specified message.
+ * Given an estimate of the message's size we can be more efficient,
+ * preallocating the array and returning a SharedInputStream to allow
+ * us to share the array.
+ */
+ synchronized InputStream retr(int msg, int size) throws IOException {
+ Response r;
+ String cmd;
+ boolean batch = size == 0 && pipelining;
+ if (batch) {
+ cmd = "LIST " + msg;
+ batchCommandStart(cmd);
+ issueCommand(cmd);
+ cmd = "RETR " + msg;
+ batchCommandContinue(cmd);
+ issueCommand(cmd);
+ r = readResponse();
+ if (r.ok && r.data != null) {
+ // parse the LIST response to get the message size
+ try {
+ StringTokenizer st = new StringTokenizer(r.data);
+ st.nextToken(); // skip message number
+ size = Integer.parseInt(st.nextToken());
+ // don't allow ridiculous sizes
+ if (size > 1024*1024*1024 || size < 0)
+ size = 0;
+ else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("pipeline message size " + size);
+ size += SLOP;
+ }
+ } catch (RuntimeException e) {
+ }
+ }
+ r = readResponse();
+ if (r.ok)
+ r.bytes = readMultilineResponse(size + SLOP);
+ batchCommandEnd();
+ } else {
+ cmd = "RETR " + msg;
+ multilineCommandStart(cmd);
+ issueCommand(cmd);
+ r = readResponse();
+ if (!r.ok) {
+ multilineCommandEnd();
+ return null;
+ }
+
+ /*
+ * Many servers return a response to the RETR command of the form:
+ * +OK 832 octets
+ * If we don't have a size guess already, try to parse the response
+ * for data in that format and use it if found. It's only a guess,
+ * but it might be a good guess.
+ */
+ if (size <= 0 && r.data != null) {
+ try {
+ StringTokenizer st = new StringTokenizer(r.data);
+ String s = st.nextToken();
+ String octets = st.nextToken();
+ if (octets.equals("octets")) {
+ size = Integer.parseInt(s);
+ // don't allow ridiculous sizes
+ if (size > 1024*1024*1024 || size < 0)
+ size = 0;
+ else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("guessing message size: " + size);
+ size += SLOP;
+ }
+ }
+ } catch (RuntimeException e) {
+ }
+ }
+ r.bytes = readMultilineResponse(size);
+ multilineCommandEnd();
+ }
+ if (r.ok) {
+ if (size > 0 && logger.isLoggable(Level.FINE))
+ logger.fine("got message size " + r.bytes.available());
+ }
+ return r.bytes;
+ }
+
+ /**
+ * Retrieve the specified message and stream the content to the
+ * specified OutputStream. Return true on success.
+ */
+ synchronized boolean retr(int msg, OutputStream os) throws IOException {
+ String cmd = "RETR " + msg;
+ multilineCommandStart(cmd);
+ issueCommand(cmd);
+ Response r = readResponse();
+ if (!r.ok) {
+ multilineCommandEnd();
+ return false;
+ }
+
+ Throwable terr = null;
+ int b, lastb = '\n';
+ try {
+ while ((b = input.read()) >= 0) {
+ if (lastb == '\n' && b == '.') {
+ b = input.read();
+ if (b == '\r') {
+ // end of response, consume LF as well
+ b = input.read();
+ break;
+ }
+ }
+
+ /*
+ * Keep writing unless we get an error while writing,
+ * which we defer until all of the data has been read.
+ */
+ if (terr == null) {
+ try {
+ os.write(b);
+ } catch (IOException ex) {
+ logger.log(Level.FINE, "exception while streaming", ex);
+ terr = ex;
+ } catch (RuntimeException ex) {
+ logger.log(Level.FINE, "exception while streaming", ex);
+ terr = ex;
+ }
+ }
+ lastb = b;
+ }
+ } catch (InterruptedIOException iioex) {
+ /*
+ * As above in simpleCommand, close the socket to recover.
+ */
+ try {
+ socket.close();
+ } catch (IOException cex) { }
+ throw iioex;
+ }
+ if (b < 0)
+ throw new EOFException("EOF on socket");
+
+ // was there a deferred error?
+ if (terr != null) {
+ if (terr instanceof IOException)
+ throw (IOException)terr;
+ if (terr instanceof RuntimeException)
+ throw (RuntimeException)terr;
+ assert false; // can't get here
+ }
+ multilineCommandEnd();
+ return true;
+ }
+
+ /**
+ * Return the message header and the first n lines of the message.
+ */
+ synchronized InputStream top(int msg, int n) throws IOException {
+ Response r = multilineCommand("TOP " + msg + " " + n, 0);
+ return r.bytes;
+ }
+
+ /**
+ * Delete (permanently) the specified message.
+ */
+ synchronized boolean dele(int msg) throws IOException {
+ Response r = simpleCommand("DELE " + msg);
+ return r.ok;
+ }
+
+ /**
+ * Return the UIDL string for the message.
+ */
+ synchronized String uidl(int msg) throws IOException {
+ Response r = simpleCommand("UIDL " + msg);
+ if (!r.ok)
+ return null;
+ int i = r.data.indexOf(' ');
+ if (i > 0)
+ return r.data.substring(i + 1);
+ else
+ return null;
+ }
+
+ /**
+ * Return the UIDL strings for all messages.
+ * The UID for msg #N is returned in uids[N-1].
+ */
+ synchronized boolean uidl(String[] uids) throws IOException {
+ Response r = multilineCommand("UIDL", 15 * uids.length);
+ if (!r.ok)
+ return false;
+ LineInputStream lis = new LineInputStream(r.bytes);
+ String line = null;
+ while ((line = lis.readLine()) != null) {
+ int i = line.indexOf(' ');
+ if (i < 1 || i >= line.length())
+ continue;
+ int n = Integer.parseInt(line.substring(0, i));
+ if (n > 0 && n <= uids.length)
+ uids[n - 1] = line.substring(i + 1);
+ }
+ try {
+ r.bytes.close();
+ } catch (IOException ex) {
+ // ignore it
+ }
+ return true;
+ }
+
+ /**
+ * Do a NOOP.
+ */
+ synchronized boolean noop() throws IOException {
+ Response r = simpleCommand("NOOP");
+ return r.ok;
+ }
+
+ /**
+ * Do an RSET.
+ */
+ synchronized boolean rset() throws IOException {
+ Response r = simpleCommand("RSET");
+ return r.ok;
+ }
+
+ /**
+ * Start TLS using STLS command specified by RFC 2595.
+ * If already using SSL, this is a nop and the STLS command is not issued.
+ */
+ synchronized boolean stls() throws IOException {
+ if (socket instanceof SSLSocket)
+ return true; // nothing to do
+ Response r = simpleCommand("STLS");
+ if (r.ok) {
+ // it worked, now switch the socket into TLS mode
+ try {
+ socket = SocketFetcher.startTLS(socket, host, props, prefix);
+ initStreams();
+ } catch (IOException ioex) {
+ try {
+ socket.close();
+ } finally {
+ socket = null;
+ input = null;
+ output = null;
+ }
+ IOException sioex =
+ new IOException("Could not convert socket to TLS");
+ sioex.initCause(ioex);
+ throw sioex;
+ }
+ }
+ return r.ok;
+ }
+
+ /**
+ * Is this connection using SSL?
+ */
+ synchronized boolean isSSL() {
+ return socket instanceof SSLSocket;
+ }
+
+ /**
+ * Get server capabilities using CAPA command specified by RFC 2449.
+ * Returns null if not supported.
+ */
+ synchronized InputStream capa() throws IOException {
+ Response r = multilineCommand("CAPA", 128); // 128 == output size est
+ if (!r.ok)
+ return null;
+ return r.bytes;
+ }
+
+ /**
+ * Issue a simple POP3 command and return the response.
+ */
+ private Response simpleCommand(String cmd) throws IOException {
+ simpleCommandStart(cmd);
+ issueCommand(cmd);
+ Response r = readResponse();
+ simpleCommandEnd();
+ return r;
+ }
+
+ /**
+ * Send the specified command.
+ */
+ private void issueCommand(String cmd) throws IOException {
+ if (socket == null)
+ throw new IOException("Folder is closed"); // XXX
+
+ if (cmd != null) {
+ cmd += CRLF;
+ output.print(cmd); // do it in one write
+ output.flush();
+ }
+ }
+
+ /**
+ * Read the response to a command.
+ */
+ private Response readResponse() throws IOException {
+ String line = null;
+ try {
+ line = input.readLine();
+ } catch (InterruptedIOException iioex) {
+ /*
+ * If we get a timeout while using the socket, we have no idea
+ * what state the connection is in. The server could still be
+ * alive, but slow, and could still be sending data. The only
+ * safe way to recover is to drop the connection.
+ */
+ try {
+ socket.close();
+ } catch (IOException cex) { }
+ throw new EOFException(iioex.getMessage());
+ } catch (SocketException ex) {
+ /*
+ * If we get an error while using the socket, we have no idea
+ * what state the connection is in. The server could still be
+ * alive, but slow, and could still be sending data. The only
+ * safe way to recover is to drop the connection.
+ */
+ try {
+ socket.close();
+ } catch (IOException cex) { }
+ throw new EOFException(ex.getMessage());
+ }
+
+ if (line == null) {
+ traceLogger.finest("");
+ throw new EOFException("EOF on socket");
+ }
+ Response r = new Response();
+ if (line.startsWith("+OK"))
+ r.ok = true;
+ else if (line.startsWith("-ERR"))
+ r.ok = false;
+ else
+ throw new IOException("Unexpected response: " + line);
+ int i;
+ if ((i = line.indexOf(' ')) >= 0)
+ r.data = line.substring(i + 1);
+ return r;
+ }
+
+ /**
+ * Issue a POP3 command that expects a multi-line response.
+ * size
is an estimate of the response size.
+ */
+ private Response multilineCommand(String cmd, int size) throws IOException {
+ multilineCommandStart(cmd);
+ issueCommand(cmd);
+ Response r = readResponse();
+ if (!r.ok) {
+ multilineCommandEnd();
+ return r;
+ }
+ r.bytes = readMultilineResponse(size);
+ multilineCommandEnd();
+ return r;
+ }
+
+ /**
+ * Read the response to a multiline command after the command response.
+ * The size parameter indicates the expected size of the response;
+ * the actual size can be different. Returns an InputStream to the
+ * response bytes.
+ */
+ private InputStream readMultilineResponse(int size) throws IOException {
+ SharedByteArrayOutputStream buf = new SharedByteArrayOutputStream(size);
+ int b, lastb = '\n';
+ try {
+ while ((b = input.read()) >= 0) {
+ if (lastb == '\n' && b == '.') {
+ b = input.read();
+ if (b == '\r') {
+ // end of response, consume LF as well
+ b = input.read();
+ break;
+ }
+ }
+ buf.write(b);
+ lastb = b;
+ }
+ } catch (InterruptedIOException iioex) {
+ /*
+ * As above in readResponse, close the socket to recover.
+ */
+ try {
+ socket.close();
+ } catch (IOException cex) { }
+ throw iioex;
+ }
+ if (b < 0)
+ throw new EOFException("EOF on socket");
+ return buf.toStream();
+ }
+
+ /**
+ * Is protocol tracing enabled?
+ */
+ protected boolean isTracing() {
+ return traceLogger.isLoggable(Level.FINEST);
+ }
+
+ /**
+ * Temporarily turn off protocol tracing, e.g., to prevent
+ * tracing the authentication sequence, including the password.
+ */
+ private void suspendTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(false);
+ traceOutput.setTrace(false);
+ }
+ }
+
+ /**
+ * Resume protocol tracing, if it was enabled to begin with.
+ */
+ private void resumeTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(true);
+ traceOutput.setTrace(true);
+ }
+ }
+
+ /*
+ * Probe points for GlassFish monitoring.
+ */
+ private void simpleCommandStart(String command) { }
+ private void simpleCommandEnd() { }
+ private void multilineCommandStart(String command) { }
+ private void multilineCommandEnd() { }
+ private void batchCommandStart(String command) { }
+ private void batchCommandContinue(String command) { }
+ private void batchCommandEnd() { }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/Status.java b/app/src/main/java/com/sun/mail/pop3/Status.java
new file mode 100644
index 0000000000..bbfbd9cd9c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/Status.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+/**
+ * Result of POP3 STAT command.
+ */
+class Status {
+ int total = 0; // number of messages in the mailbox
+ int size = 0; // size of the mailbox
+};
diff --git a/app/src/main/java/com/sun/mail/pop3/TempFile.java b/app/src/main/java/com/sun/mail/pop3/TempFile.java
new file mode 100644
index 0000000000..0d769e4aa5
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/TempFile.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.*;
+
+/**
+ * A temporary file used to cache POP3 messages.
+ */
+class TempFile {
+
+ private File file; // the temp file name
+ private WritableSharedFile sf;
+
+ /**
+ * Create a temp file in the specified directory (if not null).
+ * The file will be deleted when the JVM exits.
+ */
+ public TempFile(File dir) throws IOException {
+ file = File.createTempFile("pop3.", ".mbox", dir);
+ // XXX - need JDK 6 to set permissions on the file to owner-only
+ file.deleteOnExit();
+ sf = new WritableSharedFile(file);
+ }
+
+ /**
+ * Return a stream for appending to the temp file.
+ */
+ public AppendStream getAppendStream() throws IOException {
+ return sf.getAppendStream();
+ }
+
+ /**
+ * Close and remove this temp file.
+ */
+ public void close() {
+ try {
+ sf.close();
+ } catch (IOException ex) {
+ // ignore it
+ }
+ file.delete();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/WritableSharedFile.java b/app/src/main/java/com/sun/mail/pop3/WritableSharedFile.java
new file mode 100644
index 0000000000..8daa0c5378
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/WritableSharedFile.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import javax.mail.util.SharedFileInputStream;
+
+/**
+ * A subclass of SharedFileInputStream that also allows writing.
+ */
+class WritableSharedFile extends SharedFileInputStream {
+
+ private RandomAccessFile raf;
+ private AppendStream af;
+
+ public WritableSharedFile(File file) throws IOException {
+ super(file);
+ try {
+ raf = new RandomAccessFile(file, "rw");
+ } catch (IOException ex) {
+ // if anything goes wrong opening the writable file,
+ // close the readable file too
+ super.close();
+ }
+ }
+
+ /**
+ * Return the writable version of this file.
+ */
+ public RandomAccessFile getWritableFile() {
+ return raf;
+ }
+
+ /**
+ * Close the readable and writable files.
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } finally {
+ raf.close();
+ }
+ }
+
+ /**
+ * Update the size of the readable file after writing to the file. Updates
+ * the length to be the current size of the file.
+ */
+ synchronized long updateLength() throws IOException {
+ datalen = in.length();
+ af = null;
+ return datalen;
+ }
+
+ /**
+ * Return a new AppendStream, but only if one isn't in active use.
+ */
+ public synchronized AppendStream getAppendStream() throws IOException {
+ if (af != null) {
+ throw new IOException(
+ "POP3 file cache only supports single threaded access");
+ }
+ af = new AppendStream(this);
+ return af;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/pop3/package.html b/app/src/main/java/com/sun/mail/pop3/package.html
new file mode 100644
index 0000000000..a3ea13b8eb
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/pop3/package.html
@@ -0,0 +1,674 @@
+
+
+
+
+
+
+com.sun.mail.pop3 package
+
+
+
+A POP3 protocol provider for the Jakarta Mail API
+that provides access to a POP3 message store.
+Refer to
+RFC 1939
+for more information.
+
+The POP3 provider provides a Store object that contains a single Folder
+named "INBOX". Due to the limitations of the POP3 protocol, many of
+the Jakarta Mail API capabilities like event notification, folder management,
+flag management, etc. are not allowed. The corresponding methods throw
+the MethodNotSupportedException exception; see below for details.
+
+
+Note that Jakarta Mail does not include a local store into
+which messages can be downloaded and stored. See our
+
+Third Party Products
+web page for availability of "mbox" and "MH" local store providers.
+
+
+The POP3 provider is accessed through the Jakarta Mail APIs by using the protocol
+name "pop3" or a URL of the form "pop3://user:password@host:port/INBOX".
+
+
+POP3 supports only a single folder named "INBOX".
+
+
+POP3 supports no permanent flags (see
+{@link javax.mail.Folder#getPermanentFlags Folder.getPermanentFlags()}).
+In particular, the Flags.Flag.RECENT
flag will never be set
+for POP3
+messages. It's up to the application to determine which messages in a
+POP3 mailbox are "new". There are several strategies to accomplish
+this, depending on the needs of the application and the environment:
+
+
+
+A simple approach would be to keep track of the newest
+message seen by the application.
+
+
+An alternative would be to keep track of the UIDs (see below)
+of all messages that have been seen.
+
+
+Another approach is to download all messages into a local
+mailbox, so that all messages in the POP3 mailbox are, by
+definition, new.
+
+
+
+All approaches will require some permanent storage associated with the client.
+
+
+POP3 does not support the Folder.expunge()
method. To delete and
+expunge messages, set the Flags.Flag.DELETED
flag on the messages
+and close the folder using the Folder.close(true)
method. You
+cannot expunge without closing the folder.
+
+
+POP3 does not provide a "received date", so the getReceivedDate
+method will return null.
+It may be possible to examine other message headers (e.g., the
+"Received" headers) to estimate the received date, but these techniques
+are error-prone at best.
+
+
+The POP3 provider supports the POP3 UIDL command, see
+{@link com.sun.mail.pop3.POP3Folder#getUID POP3Folder.getUID()}.
+You can use it as follows:
+
+
+if (folder instanceof com.sun.mail.pop3.POP3Folder) {
+ com.sun.mail.pop3.POP3Folder pf =
+ (com.sun.mail.pop3.POP3Folder)folder;
+ String uid = pf.getUID(msg);
+ if (uid != null)
+ ... // use it
+}
+
+
+You can also pre-fetch all the UIDs for all messages like this:
+
+
+FetchProfile fp = new FetchProfile();
+fp.add(UIDFolder.FetchProfileItem.UID);
+folder.fetch(folder.getMessages(), fp);
+
+
+Then use the technique above to get the UID for each message. This is
+similar to the technique used with the UIDFolder interface supported by
+IMAP, but note that POP3 UIDs are strings, not integers like IMAP
+UIDs. See the POP3 spec for details.
+
+
+When the headers of a POP3 message are accessed, the POP3 provider uses
+the TOP command to fetch all headers, which are then cached. Use of the
+TOP command can be disabled with the mail.pop3.disabletop
+property, in which case the entire message content is fetched with the
+RETR command.
+
+
+When the content of a POP3 message is accessed, the POP3 provider uses
+the RETR command to fetch the entire message. Normally the message
+content is cached in memory. By setting the
+mail.pop3.filecache.enable
property, the message content
+will instead be cached in a temporary file. The file will be removed
+when the folder is closed. Caching message content in a file is generally
+slower, but uses substantially less memory and may be helpful when dealing
+with very large messages.
+
+
+The {@link com.sun.mail.pop3.POP3Message#invalidate POP3Message.invalidate}
+method can be used to invalidate cached data without closing the folder.
+Note that if the file cache is being used the data in the file will be
+forgotten and fetched from the server if it's needed again, and stored again
+in the file cache.
+
+
+The POP3 CAPA command (defined by
+RFC 2449 )
+will be used to determine the capabilities supported by the server.
+Some servers don't implement the CAPA command, and some servers don't
+return correct information, so various properties are available to
+disable use of certain POP3 commands, including CAPA.
+
+
+If the server advertises the PIPELINING capability (defined by
+RFC 2449 ),
+or the mail.pop3.pipelining
property is set, the POP3
+provider will send some commands in batches, which can significantly
+improve performance and memory use.
+Some servers that don't support the CAPA command or don't advertise
+PIPELINING may still support pipelining; experimentation may be required.
+
+
+If pipelining is supported and the connection is using
+SSL, the USER and PASS commands will be sent as a batch.
+(If SSL is not being used, the PASS command isn't sent
+until the user is verified to avoid exposing the password
+if the user name is bad.)
+
+
+If pipelining is supported, when fetching a message with the RETR command,
+the LIST command will be sent as well, and the result will be used to size
+the I/O buffer, greatly reducing memory usage when fetching messages.
+
+Properties
+
+The POP3 protocol provider supports the following properties,
+which may be set in the Jakarta Mail Session
object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted. For example, use
+
+
+ props.put("mail.pop3.port", "888");
+
+
+to set the mail.pop3.port
property, which is of type int.
+
+
+Note that if you're using the "pop3s" protocol to access POP3 over SSL,
+all the properties would be named "mail.pop3s.*".
+
+
+POP3 properties
+
+Name
+Type
+Description
+
+
+
+mail.pop3.user
+String
+Default user name for POP3.
+
+
+
+mail.pop3.host
+String
+The POP3 server to connect to.
+
+
+
+mail.pop3.port
+int
+The POP3 server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 110.
+
+
+
+mail.pop3.connectiontimeout
+int
+Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.pop3.timeout
+int
+Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.pop3.writetimeout
+int
+Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.
+
+
+
+mail.pop3.rsetbeforequit
+boolean
+
+Send a POP3 RSET command when closing the folder, before sending the
+QUIT command. Useful with POP3 servers that implicitly mark all
+messages that are read as "deleted"; this will prevent such messages
+from being deleted and expunged unless the client requests so. Default
+is false.
+
+
+
+
+mail.pop3.message.class
+String
+
+Class name of a subclass of com.sun.mail.pop3.POP3Message
.
+The subclass can be used to handle (for example) non-standard
+Content-Type headers. The subclass must have a public constructor
+of the form MyPOP3Message(Folder f, int msgno)
+throws MessagingException
.
+
+
+
+
+mail.pop3.localaddress
+String
+
+Local address (host name) to bind to when creating the POP3 socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+
+
+
+
+mail.pop3.localport
+int
+
+Local port number to bind to when creating the POP3 socket.
+Defaults to the port number picked by the Socket class.
+
+
+
+
+mail.pop3.apop.enable
+boolean
+
+If set to true, use APOP instead of USER/PASS to login to the
+POP3 server, if the POP3 server supports APOP. APOP sends a
+digest of the password rather than the clear text password.
+Defaults to false.
+
+
+
+
+mail.pop3.socketFactory
+SocketFactory
+
+If set to a class that implements the
+javax.net.SocketFactory
interface, this class
+will be used to create POP3 sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.pop3.socketFactory.class
+String
+
+If set, specifies the name of a class that implements the
+javax.net.SocketFactory
interface. This class
+will be used to create POP3 sockets.
+
+
+
+
+mail.pop3.socketFactory.fallback
+boolean
+
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the java.net.Socket
class.
+Defaults to true.
+
+
+
+
+mail.pop3.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.pop3.ssl.enable
+boolean
+
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "pop3" protocol and true for the "pop3s" protocol.
+
+
+
+
+mail.pop3.ssl.checkserveridentity
+boolean
+
+If set to true, check the server identity as specified by
+RFC 2595 .
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+
+
+
+
+mail.pop3.ssl.trust
+String
+
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+
+
+
+
+mail.pop3.ssl.socketFactory
+SSLSocketFactory
+
+If set to a class that extends the
+javax.net.ssl.SSLSocketFactory
class, this class
+will be used to create POP3 SSL sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.pop3.ssl.socketFactory.class
+String
+
+If set, specifies the name of a class that extends the
+javax.net.ssl.SSLSocketFactory
class. This class
+will be used to create POP3 SSL sockets.
+
+
+
+
+mail.pop3.ssl.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.pop3.ssl.protocols
+string
+
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledProtocols
method.
+
+
+
+
+mail.pop3.ssl.ciphersuites
+string
+
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledCipherSuites
method.
+
+
+
+
+mail.pop3.starttls.enable
+boolean
+
+If true, enables the use of the STLS
command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+mail.pop3.starttls.required
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Defaults to false.
+
+
+
+
+mail.pop3.starttls.required
+boolean
+
+If true, requires the use of the STLS
command.
+If the server doesn't support the STLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+
+
+
+
+mail.pop3.proxy.host
+string
+
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.pop3.proxy.port
+string
+
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+
+
+
+
+mail.pop3.proxy.user
+string
+
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.pop3.proxy.password
+string
+
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.pop3.socks.host
+string
+
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.pop3.socks.port
+string
+
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+
+
+
+
+mail.pop3.disabletop
+boolean
+
+If set to true, the POP3 TOP command will not be used to fetch
+message headers. This is useful for POP3 servers that don't
+properly implement the TOP command, or that provide incorrect
+information in the TOP command results.
+Defaults to false.
+
+
+
+
+mail.pop3.disablecapa
+boolean
+
+If set to true, the POP3 CAPA command will not be used to fetch
+server capabilities. This is useful for POP3 servers that don't
+properly implement the CAPA command, or that provide incorrect
+information in the CAPA command results.
+Defaults to false.
+
+
+
+
+
+boolean
+
+If set to true, the headers that might have been retrieved using
+the POP3 TOP command will be forgotten and replaced by headers
+retrieved as part of the POP3 RETR command. Some servers, such
+as some versions of Microsft Exchange and IBM Lotus Notes,
+will return slightly different
+headers each time the TOP or RETR command is used. To allow the
+POP3 provider to properly parse the message content returned from
+the RETR command, the headers also returned by the RETR command
+must be used. Setting this property to true will cause these
+headers to be used, even if they differ from the headers returned
+previously as a result of using the TOP command.
+Defaults to false.
+
+
+
+
+mail.pop3.filecache.enable
+boolean
+
+If set to true, the POP3 provider will cache message data in a temporary
+file rather than in memory. Messages are only added to the cache when
+accessing the message content. Message headers are always cached in
+memory (on demand). The file cache is removed when the folder is closed
+or the JVM terminates.
+Defaults to false.
+
+
+
+
+mail.pop3.filecache.dir
+String
+
+If the file cache is enabled, this property can be used to override the
+default directory used by the JDK for temporary files.
+
+
+
+
+mail.pop3.cachewriteto
+boolean
+
+Controls the behavior of the
+{@link com.sun.mail.pop3.POP3Message#writeTo writeTo} method
+on a POP3 message object.
+If set to true, and the message content hasn't yet been cached,
+and ignoreList is null, the message is cached before being written.
+Otherwise, the message is streamed directly
+to the output stream without being cached.
+Defaults to false.
+
+
+
+
+mail.pop3.keepmessagecontent
+boolean
+
+The content of a message is cached when it is first fetched.
+Normally this cache uses a {@link java.lang.ref.SoftReference SoftReference}
+to refer to the cached content. This allows the cached content to be purged
+if memory is low, in which case the content will be fetched again if it's
+needed.
+If this property is set to true, a hard reference to the cached content
+will be kept, preventing the memory from being reused until the folder
+is closed or the cached content is explicitly invalidated (using the
+{@link com.sun.mail.pop3.POP3Message#invalidate invalidate} method).
+(This was the behavior in previous versions of Jakarta Mail.)
+Defaults to false.
+
+
+
+
+mail.pop3.finalizecleanclose
+boolean
+
+When the finalizer for POP3Store or POP3Folder is called,
+should the connection to the server be closed cleanly, as if the
+application called the close method?
+Or should the connection to the server be closed without sending
+any commands to the server?
+Defaults to false, the connection is not closed cleanly.
+
+
+
+
+
+In general, applications should not need to use the classes in this
+package directly. Instead, they should use the APIs defined by
+javax.mail
package (and subpackages). Applications should
+never construct instances of POP3Store
or
+POP3Folder
directly. Instead, they should use the
+Session
method getStore
to acquire an
+appropriate Store
object, and from that acquire
+Folder
objects.
+
+
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.pop3 provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+
+
+POP3 Loggers
+
+Logger Name
+Logging Level
+Purpose
+
+
+
+com.sun.mail.pop3
+CONFIG
+Configuration of the POP3Store
+
+
+
+com.sun.mail.pop3
+FINE
+General debugging output
+
+
+
+com.sun.mail.pop3.protocol
+FINEST
+Complete protocol trace
+
+
+
+
+WARNING: The APIs unique to this package should be
+considered EXPERIMENTAL . They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+
+
+
+
diff --git a/app/src/main/java/com/sun/mail/smtp/DigestMD5.java b/app/src/main/java/com/sun/mail/smtp/DigestMD5.java
new file mode 100644
index 0000000000..401834a0ce
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/DigestMD5.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+import java.security.*;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * DIGEST-MD5 authentication support.
+ *
+ * @author Dean Gibson
+ * @author Bill Shannon
+ */
+
+public class DigestMD5 {
+
+ private MailLogger logger;
+ private MessageDigest md5;
+ private String uri;
+ private String clientResponse;
+
+ public DigestMD5(MailLogger logger) {
+ this.logger = logger.getLogger(this.getClass(), "DEBUG DIGEST-MD5");
+ logger.config("DIGEST-MD5 Loaded");
+ }
+
+ /**
+ * Return client's authentication response to server's challenge.
+ *
+ * @param host the host name
+ * @param user the user name
+ * @param passwd the user's password
+ * @param realm the security realm
+ * @param serverChallenge the challenge from the server
+ * @return byte array with client's response
+ * @exception IOException for I/O errors
+ */
+ public byte[] authClient(String host, String user, String passwd,
+ String realm, String serverChallenge)
+ throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+ SecureRandom random;
+ try {
+ //random = SecureRandom.getInstance("SHA1PRNG");
+ random = new SecureRandom();
+ md5 = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException ex) {
+ logger.log(Level.FINE, "NoSuchAlgorithmException", ex);
+ throw new IOException(ex.toString());
+ }
+ StringBuilder result = new StringBuilder();
+
+ uri = "smtp/" + host;
+ String nc = "00000001";
+ String qop = "auth";
+ byte[] bytes = new byte[32]; // arbitrary size ...
+ int resp;
+
+ logger.fine("Begin authentication ...");
+
+ // Code based on http://www.ietf.org/rfc/rfc2831.txt
+ Map map = tokenize(serverChallenge);
+
+ if (realm == null) {
+ String text = map.get("realm");
+ realm = text != null ? new StringTokenizer(text, ",").nextToken()
+ : host;
+ }
+
+ // 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();
+
+ // client challenge random value
+ String cnonce = bos.toString("iso-8859-1"); // really ASCII?
+ bos.reset();
+
+ // DIGEST-MD5 computation, common portion (order critical)
+ 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())
+ + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":";
+
+ // DIGEST-MD5 computation, client response (order critical)
+ md5.update(ASCIIUtility.getBytes("AUTHENTICATE:" + uri));
+ md5.update(ASCIIUtility.getBytes(clientResponse + toHex(md5.digest())));
+
+ // build response text (order not critical)
+ result.append("username=\"" + user + "\"");
+ result.append(",realm=\"" + realm + "\"");
+ result.append(",qop=" + qop);
+ result.append(",nc=" + nc);
+ 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))
+ logger.fine("Response => " + result.toString());
+ b64os.write(ASCIIUtility.getBytes(result.toString()));
+ b64os.flush();
+ return bos.toByteArray();
+ }
+
+ /**
+ * Allow the client to authenticate the server based on its
+ * response.
+ *
+ * @param serverResponse the response that was received from the server
+ * @return true if server is authenticated
+ * @exception IOException for character conversion failures
+ */
+ public boolean authServer(String serverResponse) throws IOException {
+ Map map = tokenize(serverResponse);
+ // DIGEST-MD5 computation, server response (order critical)
+ md5.update(ASCIIUtility.getBytes(":" + uri));
+ md5.update(ASCIIUtility.getBytes(clientResponse + toHex(md5.digest())));
+ String text = toHex(md5.digest());
+ if (!text.equals(map.get("rspauth"))) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("Expected => rspauth=" + text);
+ return false; // server NOT authenticated by client !!!
+ }
+ return true;
+ }
+
+ /**
+ * Tokenize a response from the server.
+ *
+ * @return Map containing key/value pairs from server
+ */
+ @SuppressWarnings("fallthrough")
+ private Map tokenize(String serverResponse)
+ throws IOException {
+ Map map = new HashMap<>();
+ byte[] bytes = serverResponse.getBytes("iso-8859-1"); // really ASCII?
+ String key = null;
+ int ttype;
+ StreamTokenizer tokens
+ = new StreamTokenizer(
+ new InputStreamReader(
+ new BASE64DecoderStream(
+ new ByteArrayInputStream(bytes, 4, bytes.length - 4)
+ ), "iso-8859-1" // really ASCII?
+ )
+ );
+
+ tokens.ordinaryChars('0', '9'); // reset digits
+ tokens.wordChars('0', '9'); // digits may start words
+ while ((ttype = tokens.nextToken()) != StreamTokenizer.TT_EOF) {
+ switch (ttype) {
+ case StreamTokenizer.TT_WORD:
+ if (key == null) {
+ key = tokens.sval;
+ break;
+ }
+ // fall-thru
+ case '"':
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("Received => " +
+ key + "='" + tokens.sval + "'");
+ if (map.containsKey(key)) { // concatenate multiple values
+ map.put(key, map.get(key) + "," + tokens.sval);
+ } else {
+ map.put(key, tokens.sval);
+ }
+ key = null;
+ break;
+ default: // XXX - should never happen?
+ break;
+ }
+ }
+ return map;
+ }
+
+ private static char[] digits = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ /**
+ * Convert a byte array to a string of hex digits representing the bytes.
+ */
+ private static String toHex(byte[] bytes) {
+ char[] result = new char[bytes.length * 2];
+
+ for (int index = 0, i = 0; index < bytes.length; index++) {
+ int temp = bytes[index] & 0xFF;
+ result[i++] = digits[temp >> 4];
+ result[i++] = digits[temp & 0xF];
+ }
+ return new String(result);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java b/app/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java
new file mode 100644
index 0000000000..234820e7cb
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent.
+ *
+ * The exception includes the address to which the message could not be
+ * sent. This will usually appear in a chained list of exceptions,
+ * one per address, attached to a top level SendFailedException that
+ * aggregates all the addresses.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPAddressFailedException extends SendFailedException {
+ protected InternetAddress addr; // address that failed
+ protected String cmd; // command issued to server
+ protected int rc; // return code from SMTP server
+
+ private static final long serialVersionUID = 804831199768630097L;
+
+ /**
+ * Constructs an SMTPAddressFailedException with the specified
+ * address, return code, and error string.
+ *
+ * @param addr the address that failed
+ * @param cmd the command that was sent to the SMTP server
+ * @param rc the SMTP return code indicating the failure
+ * @param err the error string from the SMTP server
+ */
+ public SMTPAddressFailedException(InternetAddress addr, String cmd, int rc,
+ String err) {
+ super(err);
+ this.addr = addr;
+ this.cmd = cmd;
+ this.rc = rc;
+ }
+
+ /**
+ * Return the address that failed.
+ *
+ * @return the address
+ */
+ public InternetAddress getAddress() {
+ return addr;
+ }
+
+ /**
+ * Return the command that failed.
+ *
+ * @return the command
+ */
+ public String getCommand() {
+ return cmd;
+ }
+
+
+ /**
+ * Return the return code from the SMTP server that indicates the
+ * reason for the failure. See
+ * RFC 821
+ * for interpretation of the return code.
+ *
+ * @return the return code
+ */
+ public int getReturnCode() {
+ return rc;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java b/app/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java
new file mode 100644
index 0000000000..2ddb29a7eb
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is chained off a SendFailedException when the
+ * mail.smtp.reportsuccess
property is true. It
+ * indicates an address to which the message was sent. The command
+ * will be an SMTP RCPT command and the return code will be the
+ * return code from that command.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPAddressSucceededException extends MessagingException {
+ protected InternetAddress addr; // address that succeeded
+ protected String cmd; // command issued to server
+ protected int rc; // return code from SMTP server
+
+ private static final long serialVersionUID = -1168335848623096749L;
+
+ /**
+ * Constructs an SMTPAddressSucceededException with the specified
+ * address, return code, and error string.
+ *
+ * @param addr the address that succeeded
+ * @param cmd the command that was sent to the SMTP server
+ * @param rc the SMTP return code indicating the success
+ * @param err the error string from the SMTP server
+ */
+ public SMTPAddressSucceededException(InternetAddress addr,
+ String cmd, int rc, String err) {
+ super(err);
+ this.addr = addr;
+ this.cmd = cmd;
+ this.rc = rc;
+ }
+
+ /**
+ * Return the address that succeeded.
+ *
+ * @return the address
+ */
+ public InternetAddress getAddress() {
+ return addr;
+ }
+
+ /**
+ * Return the command that succeeded.
+ *
+ * @return the command
+ */
+ public String getCommand() {
+ return cmd;
+ }
+
+
+ /**
+ * Return the return code from the SMTP server that indicates the
+ * reason for the success. See
+ * RFC 821
+ * for interpretation of the return code.
+ *
+ * @return the return code
+ */
+ public int getReturnCode() {
+ return rc;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPMessage.java b/app/src/main/java/com/sun/mail/smtp/SMTPMessage.java
new file mode 100644
index 0000000000..826ea44ce0
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPMessage.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * This class is a specialization of the MimeMessage class that allows
+ * you to specify various SMTP options and parameters that will be
+ * used when this message is sent over SMTP. Simply use this class
+ * instead of MimeMessage and set SMTP options using the methods on
+ * this class.
+ *
+ * See the com.sun.mail.smtp package
+ * documentation for further information on the SMTP protocol provider.
+ *
+ * @author Bill Shannon
+ * @see javax.mail.internet.MimeMessage
+ */
+
+public class SMTPMessage extends MimeMessage {
+
+ /** Never notify of delivery status */
+ public static final int NOTIFY_NEVER = -1;
+ /** Notify of delivery success */
+ public static final int NOTIFY_SUCCESS = 1;
+ /** Notify of delivery failure */
+ public static final int NOTIFY_FAILURE = 2;
+ /** Notify of delivery delay */
+ public static final int NOTIFY_DELAY = 4;
+
+ /** Return full message with delivery status notification */
+ public static final int RETURN_FULL = 1;
+ /** Return only message headers with delivery status notification */
+ public static final int RETURN_HDRS = 2;
+
+ private static final String[] returnOptionString = { null, "FULL", "HDRS" };
+
+ private String envelopeFrom; // the string to use in the MAIL FROM: command
+ private int notifyOptions = 0;
+ private int returnOption = 0;
+ private boolean sendPartial = false;
+ private boolean allow8bitMIME = false;
+ private String submitter = null; // RFC 2554 AUTH=submitter
+ private String extension = null; // extensions to use with MAIL command
+
+ /**
+ * Default constructor. An empty message object is created.
+ * The headers
field is set to an empty InternetHeaders
+ * object. The flags
field is set to an empty Flags
+ * object. The modified
flag is set to true.
+ *
+ * @param session the Session
+ */
+ public SMTPMessage(Session session) {
+ super(session);
+ }
+
+ /**
+ * Constructs an SMTPMessage by reading and parsing the data from the
+ * specified MIME InputStream. The InputStream will be left positioned
+ * at the end of the data for the message. Note that the input stream
+ * parse is done within this constructor itself.
+ *
+ * @param session Session object for this message
+ * @param is the message input stream
+ * @exception MessagingException for failures
+ */
+ public SMTPMessage(Session session, InputStream is)
+ throws MessagingException {
+ super(session, is);
+ }
+
+ /**
+ * Constructs a new SMTPMessage with content initialized from the
+ * source
MimeMessage. The new message is independent
+ * of the original.
+ *
+ * Note: The current implementation is rather inefficient, copying
+ * the data more times than strictly necessary.
+ *
+ * @param source the message to copy content from
+ * @exception MessagingException for failures
+ */
+ public SMTPMessage(MimeMessage source) throws MessagingException {
+ super(source);
+ }
+
+ /**
+ * Set the From address to appear in the SMTP envelope. Note that this
+ * is different than the From address that appears in the message itself.
+ * The envelope From address is typically used when reporting errors.
+ * See RFC 821 for
+ * details.
+ *
+ * If set, overrides the mail.smtp.from
property.
+ *
+ * @param from the envelope From address
+ */
+ public void setEnvelopeFrom(String from) {
+ envelopeFrom = from;
+ }
+
+ /**
+ * Return the envelope From address.
+ *
+ * @return the envelope From address, or null if not set
+ */
+ public String getEnvelopeFrom() {
+ return envelopeFrom;
+ }
+
+ /**
+ * Set notification options to be used if the server supports
+ * Delivery Status Notification
+ * (RFC 1891 ).
+ * Either NOTIFY_NEVER
or some combination of
+ * NOTIFY_SUCCESS
, NOTIFY_FAILURE
, and
+ * NOTIFY_DELAY
.
+ *
+ * If set, overrides the mail.smtp.dsn.notify
property.
+ *
+ * @param options notification options
+ */
+ public void setNotifyOptions(int options) {
+ if (options < -1 || options >= 8)
+ throw new IllegalArgumentException("Bad return option");
+ notifyOptions = options;
+ }
+
+ /**
+ * Get notification options. Returns zero if no options set.
+ *
+ * @return notification options
+ */
+ public int getNotifyOptions() {
+ return notifyOptions;
+ }
+
+ /**
+ * Return notification options as an RFC 1891 string.
+ * Returns null if no options set.
+ */
+ String getDSNNotify() {
+ if (notifyOptions == 0)
+ return null;
+ if (notifyOptions == NOTIFY_NEVER)
+ return "NEVER";
+ StringBuilder sb = new StringBuilder();
+ if ((notifyOptions & NOTIFY_SUCCESS) != 0)
+ sb.append("SUCCESS");
+ if ((notifyOptions & NOTIFY_FAILURE) != 0) {
+ if (sb.length() != 0)
+ sb.append(',');
+ sb.append("FAILURE");
+ }
+ if ((notifyOptions & NOTIFY_DELAY) != 0) {
+ if (sb.length() != 0)
+ sb.append(',');
+ sb.append("DELAY");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set return option to be used if server supports
+ * Delivery Status Notification
+ * (RFC 1891 ).
+ * Either RETURN_FULL
or RETURN_HDRS
.
+ *
+ * If set, overrides the mail.smtp.dsn.ret
property.
+ *
+ * @param option return option
+ */
+ public void setReturnOption(int option) {
+ if (option < 0 || option > RETURN_HDRS)
+ throw new IllegalArgumentException("Bad return option");
+ returnOption = option;
+ }
+
+ /**
+ * Return return option. Returns zero if no option set.
+ *
+ * @return return option
+ */
+ public int getReturnOption() {
+ return returnOption;
+ }
+
+ /**
+ * Return return option as an RFC 1891 string.
+ * Returns null if no option set.
+ */
+ String getDSNRet() {
+ return returnOptionString[returnOption];
+ }
+
+ /**
+ * If set to true, and the server supports the 8BITMIME extension, text
+ * parts of this message that use the "quoted-printable" or "base64"
+ * encodings are converted to use "8bit" encoding if they follow the
+ * RFC 2045 rules for 8bit text.
+ *
+ * If true, overrides the mail.smtp.allow8bitmime
property.
+ *
+ * @param allow allow 8-bit flag
+ */
+ public void setAllow8bitMIME(boolean allow) {
+ allow8bitMIME = allow;
+ }
+
+ /**
+ * Is use of the 8BITMIME extension is allowed?
+ *
+ * @return allow 8-bit flag
+ */
+ public boolean getAllow8bitMIME() {
+ return allow8bitMIME;
+ }
+
+ /**
+ * If set to true, and this message has some valid and some invalid
+ * addresses, send the message anyway, reporting the partial failure with
+ * a SendFailedException. If set to false (the default), the message is
+ * not sent to any of the recipients if there is an invalid recipient
+ * address.
+ *
+ * If true, overrides the mail.smtp.sendpartial
property.
+ *
+ * @param partial send partial flag
+ */
+ public void setSendPartial(boolean partial) {
+ sendPartial = partial;
+ }
+
+ /**
+ * Send message if some addresses are invalid?
+ *
+ * @return send partial flag
+ */
+ public boolean getSendPartial() {
+ return sendPartial;
+ }
+
+ /**
+ * Gets the submitter to be used for the RFC 2554 AUTH= value
+ * in the MAIL FROM command.
+ *
+ * @return the name of the submitter.
+ */
+ public String getSubmitter() {
+ return submitter;
+ }
+
+ /**
+ * Sets the submitter to be used for the RFC 2554 AUTH= value
+ * in the MAIL FROM command. Normally only used by a server
+ * that's relaying a message. Clients will typically not
+ * set a submitter. See
+ * RFC 2554
+ * for details.
+ *
+ * @param submitter the name of the submitter
+ */
+ public void setSubmitter(String submitter) {
+ this.submitter = submitter;
+ }
+
+ /**
+ * Gets the extension string to use with the MAIL command.
+ *
+ * @return the extension string
+ *
+ * @since JavaMail 1.3.2
+ */
+ public String getMailExtension() {
+ return extension;
+ }
+
+ /**
+ * Set the extension string to use with the MAIL command.
+ * The extension string can be used to specify standard SMTP
+ * service extensions as well as vendor-specific extensions.
+ * Typically the application should use the
+ * {@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+ * method {@link com.sun.mail.smtp.SMTPTransport#supportsExtension
+ * supportsExtension}
+ * to verify that the server supports the desired service extension.
+ * See RFC 1869
+ * and other RFCs that define specific extensions.
+ *
+ * For example:
+ *
+ *
+ * if (smtpTransport.supportsExtension("DELIVERBY"))
+ * smtpMsg.setMailExtension("BY=60;R");
+ *
+ *
+ * @param extension the extension string
+ * @since JavaMail 1.3.2
+ */
+ public void setMailExtension(String extension) {
+ this.extension = extension;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java b/app/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java
new file mode 100644
index 0000000000..d2fd75b911
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import com.sun.mail.util.CRLFOutputStream;
+
+/**
+ * In addition to converting lines into the canonical format,
+ * i.e., terminating lines with the CRLF sequence, escapes the "."
+ * by adding another "." to any "." that appears in the beginning
+ * of a line. See RFC821 section 4.5.2.
+ *
+ * @author Max Spivak
+ * @see CRLFOutputStream
+ */
+public class SMTPOutputStream extends CRLFOutputStream {
+ public SMTPOutputStream(OutputStream os) {
+ super(os);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ // if that last character was a newline, and the current
+ // character is ".", we always write out an extra ".".
+ if ((lastb == '\n' || lastb == '\r' || lastb == -1) && b == '.') {
+ out.write('.');
+ }
+
+ super.write(b);
+ }
+
+ /*
+ * This method has been added to improve performance.
+ */
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ int lastc = (lastb == -1) ? '\n' : lastb;
+ int start = off;
+
+ len += off;
+ for (int i = off; i < len; i++) {
+ if ((lastc == '\n' || lastc == '\r') && b[i] == '.') {
+ super.write(b, start, i - start);
+ out.write('.');
+ start = i;
+ }
+ lastc = b[i];
+ }
+ if ((len - start) > 0)
+ super.write(b, start, len - start);
+ }
+
+ /**
+ * Override flush method in FilterOutputStream.
+ *
+ * The MimeMessage writeTo method flushes its buffer at the end,
+ * but we don't want to flush data out to the socket until we've
+ * also written the terminating "\r\n.\r\n".
+ *
+ * We buffer nothing so there's nothing to flush. We depend
+ * on the fact that CRLFOutputStream also buffers nothing.
+ * SMTPTransport will manually flush the socket before reading
+ * the response.
+ */
+ @Override
+ public void flush() {
+ // do nothing
+ }
+
+ /**
+ * Ensure we're at the beginning of a line.
+ * Write CRLF if not.
+ *
+ * @exception IOException if the write fails
+ */
+ public void ensureAtBOL() throws IOException {
+ if (!atBOL)
+ super.writeln();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPProvider.java b/app/src/main/java/com/sun/mail/smtp/SMTPProvider.java
new file mode 100644
index 0000000000..71f5703f43
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The SMTP protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class SMTPProvider extends Provider {
+ public SMTPProvider() {
+ super(Provider.Type.TRANSPORT, "smtp", SMTPTransport.class.getName(),
+ "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java b/app/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java
new file mode 100644
index 0000000000..fe4d09e603
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Provider;
+
+import com.sun.mail.util.DefaultProvider;
+
+/**
+ * The SMTP SSL protocol provider.
+ */
+@DefaultProvider // Remove this annotation if you copy this provider
+public class SMTPSSLProvider extends Provider {
+ public SMTPSSLProvider() {
+ super(Provider.Type.TRANSPORT, "smtps",
+ SMTPSSLTransport.class.getName(), "Oracle", null);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java b/app/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java
new file mode 100644
index 0000000000..f3a7300849
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.*;
+
+/**
+ * This class implements the Transport abstract class using SMTP
+ * over SSL for message submission and transport.
+ *
+ * @author Bill Shannon
+ */
+
+public class SMTPSSLTransport extends SMTPTransport {
+
+ /**
+ * Constructor.
+ *
+ * @param session the Session
+ * @param urlname the URLName of this transport
+ */
+ public SMTPSSLTransport(Session session, URLName urlname) {
+ super(session, urlname, "smtps", true);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java b/app/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java
new file mode 100644
index 0000000000..d8cf3e66a8
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Address;
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent.
+ *
+ * This exception will usually appear first in a chained list of exceptions,
+ * followed by SMTPAddressFailedExceptions and/or
+ * SMTPAddressSucceededExceptions, * one per address.
+ * This exception corresponds to one of the SMTP commands used to
+ * send a message, such as the MAIL, DATA, and "end of data" commands,
+ * but not including the RCPT command.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPSendFailedException extends SendFailedException {
+ protected InternetAddress addr; // address that failed
+ protected String cmd; // command issued to server
+ protected int rc; // return code from SMTP server
+
+ private static final long serialVersionUID = 8049122628728932894L;
+
+ /**
+ * Constructs an SMTPSendFailedException with the specified
+ * address, return code, and error string.
+ *
+ * @param cmd the command that was sent to the SMTP server
+ * @param rc the SMTP return code indicating the failure
+ * @param err the error string from the SMTP server
+ * @param ex a chained exception
+ * @param vs the valid addresses the message was sent to
+ * @param vus the valid addresses the message was not sent to
+ * @param inv the invalid addresses
+ */
+ public SMTPSendFailedException(String cmd, int rc, String err, Exception ex,
+ Address[] vs, Address[] vus, Address[] inv) {
+ super(err, ex, vs, vus, inv);
+ this.cmd = cmd;
+ this.rc = rc;
+ }
+
+ /**
+ * Return the command that failed.
+ *
+ * @return the command
+ */
+ public String getCommand() {
+ return cmd;
+ }
+
+ /**
+ * Return the return code from the SMTP server that indicates the
+ * reason for the failure. See
+ * RFC 821
+ * for interpretation of the return code.
+ *
+ * @return the return code
+ */
+ public int getReturnCode() {
+ return rc;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java b/app/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java
new file mode 100644
index 0000000000..3139c79ff3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent.
+ *
+ * The exception includes the sender's address, which the mail server
+ * rejected.
+ *
+ * @since JavaMail 1.4.4
+ */
+
+public class SMTPSenderFailedException extends SendFailedException {
+ protected InternetAddress addr; // address that failed
+ protected String cmd; // command issued to server
+ protected int rc; // return code from SMTP server
+
+ private static final long serialVersionUID = 514540454964476947L;
+
+ /**
+ * Constructs an SMTPSenderFailedException with the specified
+ * address, return code, and error string.
+ *
+ * @param addr the address that failed
+ * @param cmd the command that was sent to the SMTP server
+ * @param rc the SMTP return code indicating the failure
+ * @param err the error string from the SMTP server
+ */
+ public SMTPSenderFailedException(InternetAddress addr, String cmd, int rc,
+ String err) {
+ super(err);
+ this.addr = addr;
+ this.cmd = cmd;
+ this.rc = rc;
+ }
+
+ /**
+ * Return the address that failed.
+ *
+ * @return the address
+ */
+ public InternetAddress getAddress() {
+ return addr;
+ }
+
+ /**
+ * Return the command that failed.
+ *
+ * @return the command
+ */
+ public String getCommand() {
+ return cmd;
+ }
+
+
+ /**
+ * Return the return code from the SMTP server that indicates the
+ * reason for the failure. See
+ * RFC 821
+ * for interpretation of the return code.
+ *
+ * @return the return code
+ */
+ public int getReturnCode() {
+ return rc;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/SMTPTransport.java b/app/src/main/java/com/sun/mail/smtp/SMTPTransport.java
new file mode 100644
index 0000000000..efbab723ab
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SMTPTransport.java
@@ -0,0 +1,2831 @@
+/*
+ * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.net.*;
+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.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.MailConnectException;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+import com.sun.mail.auth.Ntlm;
+
+/**
+ * This class implements the Transport abstract class using SMTP for
+ * message submission and transport.
+ *
+ * See the com.sun.mail.smtp package
+ * documentation for further information on the SMTP protocol provider.
+ *
+ * This class includes many protected methods that allow a subclass to
+ * extend this class and add support for non-standard SMTP commands.
+ * The {@link #issueCommand} and {@link #sendCommand} methods can be
+ * used to send simple SMTP commands. Other methods such as the
+ * {@link #mailFrom} and {@link #data} methods can be overridden to
+ * insert new commands before or after the corresponding SMTP commands.
+ * For example, a subclass could do this to send the XACT command
+ * before sending the DATA command:
+ *
+ * protected OutputStream data() throws MessagingException {
+ * if (supportsExtension("XACCOUNTING"))
+ * issueCommand("XACT", 25);
+ * return super.data();
+ * }
+ *
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ * @author Dean Gibson (DIGEST-MD5 authentication)
+ * @author Lu\u00EDs Serralheiro (NTLM authentication)
+ *
+ * @see javax.mail.event.ConnectionEvent
+ * @see javax.mail.event.TransportEvent
+ */
+
+public class SMTPTransport extends Transport {
+
+ private String name = "smtp"; // Name of this protocol
+ private int defaultPort = 25; // default SMTP port
+ private boolean isSSL = false; // use SSL?
+ private String host; // host we're connected to
+
+ // Following fields valid only during the sendMessage method.
+ private MimeMessage message; // Message to be sent
+ private Address[] addresses; // Addresses to which to send the msg
+ // Valid sent, valid unsent and invalid addresses
+ private Address[] validSentAddr, validUnsentAddr, invalidAddr;
+ // Did we send the message even though some addresses were invalid?
+ private boolean sendPartiallyFailed = false;
+ // If so, here's an exception we need to throw
+ private MessagingException exception;
+ // stream where message data is written
+ private SMTPOutputStream dataStream;
+
+ // Map of SMTP service extensions supported by server, if EHLO used.
+ private Hashtable extMap;
+
+ private Map authenticators
+ = new HashMap<>();
+ private String defaultAuthenticationMechanisms; // set in constructor
+
+ private boolean quitWait = false; // true if we should wait
+ private boolean quitOnSessionReject = false; // true if we should send quit when session initiation is rejected
+
+ private String saslRealm = UNKNOWN;
+ private String authorizationID = UNKNOWN;
+ private boolean enableSASL = false; // enable SASL authentication
+ private boolean useCanonicalHostName = false; // use canonical host name?
+ private String[] saslMechanisms = UNKNOWN_SA;
+
+ private String ntlmDomain = UNKNOWN; // for ntlm authentication
+
+ private boolean reportSuccess; // throw an exception even on success
+ private boolean useStartTLS; // use STARTTLS command
+ private boolean requireStartTLS; // require STARTTLS command
+ private boolean useRset; // use RSET instead of NOOP
+ private boolean noopStrict = true; // NOOP must return 250 for success
+
+ private MailLogger logger; // debug logger
+ private MailLogger traceLogger; // protocol trace logger
+ private String localHostName; // our own host name
+ private String lastServerResponse; // last SMTP response
+ private int lastReturnCode; // last SMTP return code
+ private boolean notificationDone; // only notify once per send
+
+ private SaslAuthenticator saslAuthenticator; // if SASL is being used
+
+ 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?
+ private int chunkSize; // chunk size if CHUNKING supported
+
+ /** Headers that should not be included when sending */
+ private static final String[] ignoreList = { "Bcc", "Content-Length" };
+ private static final byte[] CRLF = { (byte)'\r', (byte)'\n' };
+ private static final String UNKNOWN = "UNKNOWN"; // place holder
+ private static final String[] UNKNOWN_SA = new String[0]; // place holder
+
+ /**
+ * Constructor that takes a Session object and a URLName
+ * that represents a specific SMTP server.
+ *
+ * @param session the Session
+ * @param urlname the URLName of this transport
+ */
+ public SMTPTransport(Session session, URLName urlname) {
+ this(session, urlname, "smtp", false);
+ }
+
+ /**
+ * Constructor used by this class and by SMTPSSLTransport subclass.
+ *
+ * @param session the Session
+ * @param urlname the URLName of this transport
+ * @param name the protocol name of this transport
+ * @param isSSL use SSL to connect?
+ */
+ protected SMTPTransport(Session session, URLName urlname,
+ String name, boolean isSSL) {
+ super(session, urlname);
+ Properties props = session.getProperties();
+
+ logger = new MailLogger(this.getClass(), "DEBUG SMTP",
+ session.getDebug(), session.getDebugOut());
+ traceLogger = logger.getSubLogger("protocol", null);
+ noauthdebug = !PropUtil.getBooleanProperty(props,
+ "mail.debug.auth", false);
+ debugusername = PropUtil.getBooleanProperty(props,
+ "mail.debug.auth.username", true);
+ debugpassword = PropUtil.getBooleanProperty(props,
+ "mail.debug.auth.password", false);
+ if (urlname != null)
+ name = urlname.getProtocol();
+ this.name = name;
+ if (!isSSL)
+ isSSL = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".ssl.enable", false);
+ if (isSSL)
+ this.defaultPort = 465;
+ else
+ this.defaultPort = 25;
+ this.isSSL = isSSL;
+
+ // setting mail.smtp.quitwait to false causes us to not wait for the
+ // response from the QUIT command
+ quitWait = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".quitwait", true);
+
+ // setting mail.smtp.quitonsessionreject to false causes us to directly
+ // close the socket without sending a QUIT command
+ quitOnSessionReject = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".quitonsessionreject", false);
+
+ // mail.smtp.reportsuccess causes us to throw an exception on success
+ reportSuccess = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".reportsuccess", false);
+
+ // mail.smtp.starttls.enable enables use of STARTTLS command
+ useStartTLS = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".starttls.enable", false);
+
+ // mail.smtp.starttls.required requires use of STARTTLS command
+ requireStartTLS = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".starttls.required", false);
+
+ // mail.smtp.userset causes us to use RSET instead of NOOP
+ // for isConnected
+ useRset = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".userset", false);
+
+ // mail.smtp.noop.strict requires 250 response to indicate success
+ noopStrict = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".noop.strict", true);
+
+ // check if SASL is enabled
+ enableSASL = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".sasl.enable", false);
+ if (enableSASL)
+ logger.config("enable SASL");
+ useCanonicalHostName = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".sasl.usecanonicalhostname", false);
+ if (useCanonicalHostName)
+ logger.config("use canonical host name");
+
+ allowutf8 = PropUtil.getBooleanProperty(props,
+ "mail.mime.allowutf8", false);
+ if (allowutf8)
+ logger.config("allow UTF-8");
+
+ chunkSize = PropUtil.getIntProperty(props,
+ "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(),
+ new PlainAuthenticator(),
+ new DigestMD5Authenticator(),
+ new NtlmAuthenticator(),
+ new OAuth2Authenticator()
+ };
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < a.length; i++) {
+ authenticators.put(a[i].getMechanism(), a[i]);
+ sb.append(a[i].getMechanism()).append(' ');
+ }
+ defaultAuthenticationMechanisms = sb.toString();
+ }
+
+ /**
+ * Get the name of the local host, for use in the EHLO and HELO commands.
+ * The property mail.smtp.localhost overrides mail.smtp.localaddress,
+ * which overrides what InetAddress would tell us.
+ *
+ * @return the local host name
+ */
+ public synchronized String getLocalHost() {
+ // get our hostname and cache it for future use
+ if (localHostName == null || localHostName.length() <= 0)
+ localHostName =
+ session.getProperty("mail." + name + ".localhost");
+ if (localHostName == null || localHostName.length() <= 0)
+ localHostName =
+ session.getProperty("mail." + name + ".localaddress");
+ try {
+ if (localHostName == null || localHostName.length() <= 0) {
+ InetAddress localHost = InetAddress.getLocalHost();
+ localHostName = localHost.getCanonicalHostName();
+ // if we can't get our name, use local address literal
+ if (localHostName == null)
+ // XXX - not correct for IPv6
+ localHostName = "[" + localHost.getHostAddress() + "]";
+ }
+ } catch (UnknownHostException uhex) {
+ }
+
+ // last chance, try to get our address from our socket
+ if (localHostName == null || localHostName.length() <= 0) {
+ if (serverSocket != null && serverSocket.isBound()) {
+ InetAddress localHost = serverSocket.getLocalAddress();
+ localHostName = localHost.getCanonicalHostName();
+ // if we can't get our name, use local address literal
+ if (localHostName == null)
+ // XXX - not correct for IPv6
+ localHostName = "[" + localHost.getHostAddress() + "]";
+ }
+ }
+ return localHostName;
+ }
+
+ /**
+ * Set the name of the local host, for use in the EHLO and HELO commands.
+ *
+ * @param localhost the local host name
+ * @since JavaMail 1.3.1
+ */
+ public synchronized void setLocalHost(String localhost) {
+ localHostName = localhost;
+ }
+
+ /**
+ * Start the SMTP protocol on the given socket, which was already
+ * connected by the caller. Useful for implementing the SMTP ATRN
+ * command (RFC 2645) where an existing connection is used when
+ * the server reverses roles and becomes the client.
+ *
+ * @param socket the already connected socket
+ * @exception MessagingException for failures
+ * @since JavaMail 1.3.3
+ */
+ public synchronized void connect(Socket socket) throws MessagingException {
+ serverSocket = socket;
+ super.connect();
+ }
+
+ /**
+ * Gets the authorization ID to be used for authentication.
+ *
+ * @return the authorization ID to use for authentication.
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized String getAuthorizationId() {
+ if (authorizationID == UNKNOWN) {
+ authorizationID =
+ session.getProperty("mail." + name + ".sasl.authorizationid");
+ }
+ return authorizationID;
+ }
+
+ /**
+ * Sets the authorization ID to be used for authentication.
+ *
+ * @param authzid the authorization ID to use for
+ * authentication.
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized void setAuthorizationID(String authzid) {
+ this.authorizationID = authzid;
+ }
+
+ /**
+ * Is SASL authentication enabled?
+ *
+ * @return true if SASL authentication is enabled
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized boolean getSASLEnabled() {
+ return enableSASL;
+ }
+
+ /**
+ * Set whether SASL authentication is enabled.
+ *
+ * @param enableSASL should we enable SASL authentication?
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized void setSASLEnabled(boolean enableSASL) {
+ this.enableSASL = enableSASL;
+ }
+
+ /**
+ * Gets the SASL realm to be used for DIGEST-MD5 authentication.
+ *
+ * @return the name of the realm to use for SASL authentication.
+ *
+ * @since JavaMail 1.3.1
+ */
+ public synchronized String getSASLRealm() {
+ if (saslRealm == UNKNOWN) {
+ saslRealm = session.getProperty("mail." + name + ".sasl.realm");
+ if (saslRealm == null) // try old name
+ saslRealm = session.getProperty("mail." + name + ".saslrealm");
+ }
+ return saslRealm;
+ }
+
+ /**
+ * Sets the SASL realm to be used for DIGEST-MD5 authentication.
+ *
+ * @param saslRealm the name of the realm to use for
+ * SASL authentication.
+ *
+ * @since JavaMail 1.3.1
+ */
+ public synchronized void setSASLRealm(String saslRealm) {
+ this.saslRealm = saslRealm;
+ }
+
+ /**
+ * Should SASL use the canonical host name?
+ *
+ * @return true if SASL should use the canonical host name
+ *
+ * @since JavaMail 1.5.2
+ */
+ public synchronized boolean getUseCanonicalHostName() {
+ return useCanonicalHostName;
+ }
+
+ /**
+ * Set whether SASL should use the canonical host name.
+ *
+ * @param useCanonicalHostName should SASL use the canonical host name?
+ *
+ * @since JavaMail 1.5.2
+ */
+ public synchronized void setUseCanonicalHostName(
+ boolean useCanonicalHostName) {
+ this.useCanonicalHostName = useCanonicalHostName;
+ }
+
+ /**
+ * Get the list of SASL mechanisms to consider if SASL authentication
+ * is enabled. If the list is empty or null, all available SASL mechanisms
+ * are considered.
+ *
+ * @return the array of SASL mechanisms to consider
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized String[] getSASLMechanisms() {
+ if (saslMechanisms == UNKNOWN_SA) {
+ List v = new ArrayList<>(5);
+ String s = session.getProperty("mail." + name + ".sasl.mechanisms");
+ if (s != null && s.length() > 0) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("SASL mechanisms allowed: " + s);
+ StringTokenizer st = new StringTokenizer(s, " ,");
+ while (st.hasMoreTokens()) {
+ String m = st.nextToken();
+ if (m.length() > 0)
+ v.add(m);
+ }
+ }
+ saslMechanisms = new String[v.size()];
+ v.toArray(saslMechanisms);
+ }
+ if (saslMechanisms == null)
+ return null;
+ return saslMechanisms.clone();
+ }
+
+ /**
+ * Set the list of SASL mechanisms to consider if SASL authentication
+ * is enabled. If the list is empty or null, all available SASL mechanisms
+ * are considered.
+ *
+ * @param mechanisms the array of SASL mechanisms to consider
+ *
+ * @since JavaMail 1.4.4
+ */
+ public synchronized void setSASLMechanisms(String[] mechanisms) {
+ if (mechanisms != null)
+ mechanisms = mechanisms.clone();
+ this.saslMechanisms = mechanisms;
+ }
+
+ /**
+ * Gets the NTLM domain to be used for NTLM authentication.
+ *
+ * @return the name of the domain to use for NTLM authentication.
+ *
+ * @since JavaMail 1.4.3
+ */
+ public synchronized String getNTLMDomain() {
+ if (ntlmDomain == UNKNOWN) {
+ ntlmDomain =
+ session.getProperty("mail." + name + ".auth.ntlm.domain");
+ }
+ return ntlmDomain;
+ }
+
+ /**
+ * Sets the NTLM domain to be used for NTLM authentication.
+ *
+ * @param ntlmDomain the name of the domain to use for
+ * NTLM authentication.
+ *
+ * @since JavaMail 1.4.3
+ */
+ public synchronized void setNTLMDomain(String ntlmDomain) {
+ this.ntlmDomain = ntlmDomain;
+ }
+
+ /**
+ * Should we report even successful sends by throwing an exception?
+ * If so, a SendFailedException
will always be thrown and
+ * an {@link com.sun.mail.smtp.SMTPAddressSucceededException
+ * SMTPAddressSucceededException} will be included in the exception
+ * chain for each successful address, along with the usual
+ * {@link com.sun.mail.smtp.SMTPAddressFailedException
+ * SMTPAddressFailedException} for each unsuccessful address.
+ *
+ * @return true if an exception will be thrown on successful sends.
+ *
+ * @since JavaMail 1.3.2
+ */
+ public synchronized boolean getReportSuccess() {
+ return reportSuccess;
+ }
+
+ /**
+ * Set whether successful sends should be reported by throwing
+ * an exception.
+ *
+ * @param reportSuccess should we throw an exception on success?
+ *
+ * @since JavaMail 1.3.2
+ */
+ public synchronized void setReportSuccess(boolean reportSuccess) {
+ this.reportSuccess = reportSuccess;
+ }
+
+ /**
+ * Should we use the STARTTLS command to secure the connection
+ * if the server supports it?
+ *
+ * @return true if the STARTTLS command will be used
+ *
+ * @since JavaMail 1.3.2
+ */
+ public synchronized boolean getStartTLS() {
+ return useStartTLS;
+ }
+
+ /**
+ * Set whether the STARTTLS command should be used.
+ *
+ * @param useStartTLS should we use the STARTTLS command?
+ *
+ * @since JavaMail 1.3.2
+ */
+ public synchronized void setStartTLS(boolean useStartTLS) {
+ this.useStartTLS = useStartTLS;
+ }
+
+ /**
+ * Should we require the STARTTLS command to secure the connection?
+ *
+ * @return true if the STARTTLS command will be required
+ *
+ * @since JavaMail 1.4.2
+ */
+ public synchronized boolean getRequireStartTLS() {
+ return requireStartTLS;
+ }
+
+ /**
+ * Set whether the STARTTLS command should be required.
+ *
+ * @param requireStartTLS should we require the STARTTLS command?
+ *
+ * @since JavaMail 1.4.2
+ */
+ public synchronized void setRequireStartTLS(boolean requireStartTLS) {
+ this.requireStartTLS = requireStartTLS;
+ }
+
+ /**
+ * Is this Transport using SSL to connect to the server?
+ *
+ * @return true if using SSL
+ * @since JavaMail 1.4.6
+ */
+ public synchronized boolean isSSL() {
+ return serverSocket instanceof SSLSocket;
+ }
+
+ /**
+ * Should we use the RSET command instead of the NOOP command
+ * in the @{link #isConnected isConnected} method?
+ *
+ * @return true if RSET will be used
+ *
+ * @since JavaMail 1.4
+ */
+ public synchronized boolean getUseRset() {
+ return useRset;
+ }
+
+ /**
+ * Set whether the RSET command should be used instead of the
+ * NOOP command in the @{link #isConnected isConnected} method.
+ *
+ * @param useRset should we use the RSET command?
+ *
+ * @since JavaMail 1.4
+ */
+ public synchronized void setUseRset(boolean useRset) {
+ this.useRset = useRset;
+ }
+
+ /**
+ * Is the NOOP command required to return a response code
+ * of 250 to indicate success?
+ *
+ * @return true if NOOP must return 250
+ *
+ * @since JavaMail 1.4.3
+ */
+ public synchronized boolean getNoopStrict() {
+ return noopStrict;
+ }
+
+ /**
+ * Set whether the NOOP command is required to return a response code
+ * of 250 to indicate success.
+ *
+ * @param noopStrict is NOOP required to return 250?
+ *
+ * @since JavaMail 1.4.3
+ */
+ public synchronized void setNoopStrict(boolean noopStrict) {
+ this.noopStrict = noopStrict;
+ }
+
+ /**
+ * Return the last response we got from the server.
+ * A failed send is often followed by an RSET command,
+ * but the response from the RSET command is not saved.
+ * Instead, this returns the response from the command
+ * before the RSET command.
+ *
+ * @return last response from server
+ *
+ * @since JavaMail 1.3.2
+ */
+ public synchronized String getLastServerResponse() {
+ return lastServerResponse;
+ }
+
+ /**
+ * Return the return code from the last response we got from the server.
+ *
+ * @return return code from last response from server
+ *
+ * @since JavaMail 1.4.1
+ */
+ public synchronized int getLastReturnCode() {
+ return lastReturnCode;
+ }
+
+ /**
+ * Performs the actual protocol-specific connection attempt.
+ * Will attempt to connect to "localhost" if the host was null.
+ *
+ * Unless mail.smtp.ehlo is set to false, we'll try to identify
+ * ourselves using the ESMTP command EHLO.
+ *
+ * If mail.smtp.auth is set to true, we insist on having a username
+ * and password, and will try to authenticate ourselves if the server
+ * supports the AUTH extension (RFC 2554).
+ *
+ * @param host the name of the host to connect to
+ * @param port the port to use (-1 means use default port)
+ * @param user the name of the user to login as
+ * @param password the user's password
+ * @return true if connection successful, false if authentication failed
+ * @exception MessagingException for non-authentication failures
+ */
+ @Override
+ protected synchronized boolean protocolConnect(String host, int port,
+ String user, String password)
+ throws MessagingException {
+ Properties props = session.getProperties();
+
+ // setting mail.smtp.auth to true enables attempts to use AUTH
+ boolean useAuth = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".auth", false);
+
+ /*
+ * If mail.smtp.auth is set, make sure we have a valid username
+ * and password, even if we might not end up using it (e.g.,
+ * because the server doesn't support ESMTP or doesn't support
+ * the AUTH extension).
+ */
+ if (useAuth && (user == null || password == null)) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("need username and password for authentication");
+ logger.fine("protocolConnect returning false" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ }
+ return false;
+ }
+
+ // setting mail.smtp.ehlo to false disables attempts to use EHLO
+ boolean useEhlo = PropUtil.getBooleanProperty(props,
+ "mail." + name + ".ehlo", true);
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("useEhlo " + useEhlo + ", useAuth " + useAuth);
+
+ /*
+ * If port is not specified, set it to value of mail.smtp.port
+ * property if it exists, otherwise default to 25.
+ */
+ if (port == -1)
+ port = PropUtil.getIntProperty(props,
+ "mail." + name + ".port", -1);
+ if (port == -1)
+ port = defaultPort;
+
+ if (host == null || host.length() == 0)
+ host = "localhost";
+
+ /*
+ * If anything goes wrong, we need to be sure
+ * to close the connection.
+ */
+ boolean connected = false;
+ try {
+
+ if (serverSocket != null)
+ openServer(); // only happens from connect(socket)
+ else
+ openServer(host, port);
+
+ boolean succeed = false;
+ if (useEhlo)
+ succeed = ehlo(getLocalHost());
+ if (!succeed)
+ helo(getLocalHost());
+
+ if (useStartTLS || requireStartTLS) {
+ if (serverSocket instanceof SSLSocket) {
+ logger.fine("STARTTLS requested but already using SSL");
+ } else if (supportsExtension("STARTTLS")) {
+ startTLS();
+ /*
+ * Have to issue another EHLO to update list of extensions
+ * supported, especially authentication mechanisms.
+ * Don't know if this could ever fail, but we ignore
+ * failure.
+ */
+ ehlo(getLocalHost());
+ } else if (requireStartTLS) {
+ logger.fine("STARTTLS required but not supported");
+ throw new MessagingException(
+ "STARTTLS is required but " +
+ "host does not support STARTTLS");
+ }
+ }
+
+ 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"))) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("protocolConnect login" +
+ ", host=" + host +
+ ", user=" + traceUser(user) +
+ ", password=" + tracePassword(password));
+ connected = authenticate(user, password);
+ return connected;
+ }
+
+ // we connected correctly
+ connected = true;
+ return true;
+
+ } finally {
+ // if we didn't connect successfully,
+ // make sure the connection is closed
+ if (!connected) {
+ try {
+ closeConnection();
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * Authenticate to the server.
+ */
+ private boolean authenticate(String user, String passwd)
+ throws MessagingException {
+ // setting mail.smtp.auth.mechanisms controls which mechanisms will
+ // be used, and in what order they'll be considered. only the first
+ // match is used.
+ String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
+ if (mechs == null)
+ mechs = defaultAuthenticationMechanisms;
+
+ String authzid = getAuthorizationId();
+ if (authzid == null)
+ authzid = user;
+ if (enableSASL) {
+ logger.fine("Authenticate with SASL");
+ try {
+ if (sasllogin(getSASLMechanisms(), getSASLRealm(), authzid,
+ user, passwd)) {
+ return true; // success
+ } else {
+ logger.fine("SASL authentication failed");
+ return false;
+ }
+ } catch (UnsupportedOperationException ex) {
+ logger.log(Level.FINE, "SASL support failed", ex);
+ // if the SASL support fails, fall back to non-SASL
+ }
+ }
+
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("Attempt to authenticate using mechanisms: " + mechs);
+
+ /*
+ * Loop through the list of mechanisms supplied by the user
+ * (or defaulted) and try each in turn. If the server supports
+ * the mechanism and we have an authenticator for the mechanism,
+ * and it hasn't been disabled, use it.
+ */
+ StringTokenizer st = new StringTokenizer(mechs);
+ while (st.hasMoreTokens()) {
+ String m = st.nextToken();
+ m = m.toUpperCase(Locale.ENGLISH);
+ Authenticator a = authenticators.get(m);
+ if (a == null) {
+ logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
+ continue;
+ }
+
+ if (!supportsAuthentication(m)) {
+ logger.log(Level.FINE, "mechanism {0} not supported by server",
+ m);
+ continue;
+ }
+
+ /*
+ * If using the default mechanisms, check if this one is disabled.
+ */
+ if (mechs == defaultAuthenticationMechanisms) {
+ String dprop = "mail." + name + ".auth." +
+ m.toLowerCase(Locale.ENGLISH) + ".disable";
+ boolean disabled = PropUtil.getBooleanProperty(
+ session.getProperties(),
+ dprop, !a.enabled());
+ if (disabled) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("mechanism " + m +
+ " disabled by property: " + dprop);
+ continue;
+ }
+ }
+
+ // only the first supported and enabled mechanism is used
+ logger.log(Level.FINE, "Using mechanism {0}", m);
+ return a.authenticate(host, authzid, user, passwd);
+ }
+
+ // if no authentication mechanism found, fail
+ throw new AuthenticationFailedException(
+ "No authentication mechanisms supported by both server and client");
+ }
+
+ /**
+ * Abstract base class for SMTP authentication mechanism implementations.
+ */
+ private abstract class Authenticator {
+ protected int resp; // the response code, used by subclasses
+ private final String mech; // the mechanism name, set in the constructor
+ private final boolean enabled; // is this mechanism enabled by default?
+
+ Authenticator(String mech) {
+ this(mech, true);
+ }
+
+ Authenticator(String mech, boolean enabled) {
+ this.mech = mech.toUpperCase(Locale.ENGLISH);
+ this.enabled = enabled;
+ }
+
+ String getMechanism() {
+ return mech;
+ }
+
+ boolean enabled() {
+ return enabled;
+ }
+
+ /**
+ * Start the authentication handshake by issuing the AUTH command.
+ * Delegate to the doAuth method to do the mechanism-specific
+ * part of the handshake.
+ */
+ boolean authenticate(String host, String authzid,
+ String user, String passwd) throws MessagingException {
+ Throwable thrown = null;
+ try {
+ // use "initial response" capability, if supported
+ String ir = getInitialResponse(host, authzid, user, passwd);
+ if (noauthdebug && isTracing()) {
+ logger.fine("AUTH " + mech + " command trace suppressed");
+ suspendTracing();
+ }
+ if (ir != null)
+ resp = simpleCommand("AUTH " + mech + " " +
+ (ir.length() == 0 ? "=" : ir));
+ else
+ resp = simpleCommand("AUTH " + mech);
+
+ /*
+ * A 530 response indicates that the server wants us to
+ * issue a STARTTLS command first. Do that and try again.
+ */
+ if (resp == 530) {
+ startTLS();
+ if (ir != null)
+ resp = simpleCommand("AUTH " + mech + " " + ir);
+ else
+ resp = simpleCommand("AUTH " + mech);
+ }
+ if (resp == 334)
+ doAuth(host, authzid, user, passwd);
+ } catch (IOException ex) { // should never happen, ignore
+ logger.log(Level.FINE, "AUTH " + mech + " failed", ex);
+ } catch (Throwable t) { // crypto can't be initialized?
+ logger.log(Level.FINE, "AUTH " + mech + " failed", t);
+ thrown = t;
+ } finally {
+ if (noauthdebug && isTracing())
+ logger.fine("AUTH " + mech + " " +
+ (resp == 235 ? "succeeded" : "failed"));
+ resumeTracing();
+ if (resp != 235) {
+ closeConnection();
+ if (thrown != null) {
+ if (thrown instanceof Error)
+ throw (Error)thrown;
+ if (thrown instanceof Exception)
+ throw new AuthenticationFailedException(
+ getLastServerResponse(),
+ (Exception)thrown);
+ assert false : "unknown Throwable"; // can't happen
+ }
+ throw new AuthenticationFailedException(
+ getLastServerResponse());
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Provide the initial response to use in the AUTH command,
+ * or null if not supported. Subclasses that support the
+ * initial response capability will override this method.
+ */
+ String getInitialResponse(String host, String authzid, String user,
+ String passwd) throws MessagingException, IOException {
+ return null;
+ }
+
+ abstract void doAuth(String host, String authzid, String user,
+ String passwd) throws MessagingException, IOException;
+ }
+
+ /**
+ * Perform the authentication handshake for LOGIN authentication.
+ */
+ private class LoginAuthenticator extends Authenticator {
+ LoginAuthenticator() {
+ super("LOGIN");
+ }
+
+ @Override
+ void doAuth(String host, String authzid, String user, String passwd)
+ throws MessagingException, IOException {
+ // send username
+ resp = simpleCommand(BASE64EncoderStream.encode(
+ user.getBytes(StandardCharsets.UTF_8)));
+ if (resp == 334) {
+ // send passwd
+ resp = simpleCommand(BASE64EncoderStream.encode(
+ passwd.getBytes(StandardCharsets.UTF_8)));
+ }
+ }
+ }
+
+ /**
+ * Perform the authentication handshake for PLAIN authentication.
+ */
+ private class PlainAuthenticator extends Authenticator {
+ PlainAuthenticator() {
+ super("PLAIN");
+ }
+
+ @Override
+ String getInitialResponse(String host, String authzid, String user,
+ String passwd) throws MessagingException, IOException {
+ // return "authziduserpasswd"
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ OutputStream b64os =
+ new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+ if (authzid != null)
+ b64os.write(authzid.getBytes(StandardCharsets.UTF_8));
+ b64os.write(0);
+ b64os.write(user.getBytes(StandardCharsets.UTF_8));
+ b64os.write(0);
+ b64os.write(passwd.getBytes(StandardCharsets.UTF_8));
+ b64os.flush(); // complete the encoding
+
+ return ASCIIUtility.toString(bos.toByteArray());
+ }
+
+ @Override
+ void doAuth(String host, String authzid, String user, String passwd)
+ throws MessagingException, IOException {
+ // should never get here
+ throw new AuthenticationFailedException("PLAIN asked for more");
+ }
+ }
+
+ /**
+ * Perform the authentication handshake for DIGEST-MD5 authentication.
+ */
+ private class DigestMD5Authenticator extends Authenticator {
+ private DigestMD5 md5support; // only create if needed
+
+ DigestMD5Authenticator() {
+ super("DIGEST-MD5");
+ }
+
+ private synchronized DigestMD5 getMD5() {
+ if (md5support == null)
+ md5support = new DigestMD5(logger);
+ return md5support;
+ }
+
+ @Override
+ void doAuth(String host, String authzid, String user, String passwd)
+ throws MessagingException, IOException {
+ DigestMD5 md5 = getMD5();
+ assert md5 != null;
+
+ byte[] b = md5.authClient(host, user, passwd, getSASLRealm(),
+ getLastServerResponse());
+ resp = simpleCommand(b);
+ if (resp == 334) { // client authenticated by server
+ if (!md5.authServer(getLastServerResponse())) {
+ // server NOT authenticated by client !!!
+ resp = -1;
+ } else {
+ // send null response
+ resp = simpleCommand(new byte[0]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Perform the authentication handshake for NTLM authentication.
+ */
+ private class NtlmAuthenticator extends Authenticator {
+ private Ntlm ntlm;
+
+ NtlmAuthenticator() {
+ super("NTLM");
+ }
+
+ @Override
+ String getInitialResponse(String host, String authzid, String user,
+ String passwd) throws MessagingException, IOException {
+ ntlm = new Ntlm(getNTLMDomain(), getLocalHost(),
+ user, passwd, logger);
+
+ int flags = PropUtil.getIntProperty(
+ session.getProperties(),
+ "mail." + name + ".auth.ntlm.flags", 0);
+ boolean v2 = PropUtil.getBooleanProperty(
+ session.getProperties(),
+ "mail." + name + ".auth.ntlm.v2", true);
+
+ String type1 = ntlm.generateType1Msg(flags, v2);
+ return type1;
+ }
+
+ @Override
+ void doAuth(String host, String authzid, String user, String passwd)
+ throws MessagingException, IOException {
+ assert ntlm != null;
+ String type3 = ntlm.generateType3Msg(
+ getLastServerResponse().substring(4).trim());
+
+ resp = simpleCommand(type3);
+ }
+ }
+
+ /**
+ * Perform the authentication handshake for XOAUTH2 authentication.
+ */
+ private class OAuth2Authenticator extends Authenticator {
+
+ OAuth2Authenticator() {
+ super("XOAUTH2", false); // disabled by default
+ }
+
+ @Override
+ String getInitialResponse(String host, String authzid, String user,
+ String passwd) throws MessagingException, IOException {
+ String resp = "user=" + user + "\001auth=Bearer " +
+ passwd + "\001\001";
+ byte[] b = BASE64EncoderStream.encode(
+ resp.getBytes(StandardCharsets.UTF_8));
+ return ASCIIUtility.toString(b);
+ }
+
+ @Override
+ void doAuth(String host, String authzid, String user, String passwd)
+ throws MessagingException, IOException {
+ // should never get here
+ throw new AuthenticationFailedException("OAUTH2 asked for more");
+ }
+ }
+
+ /**
+ * SASL-based login.
+ *
+ * @param allowed the allowed SASL mechanisms
+ * @param realm the SASL realm
+ * @param authzid the authorization ID
+ * @param u the user name for authentication
+ * @param p the password for authentication
+ * @return true for success
+ * @exception MessagingException for failures
+ */
+ private boolean sasllogin(String[] allowed, String realm, String authzid,
+ String u, String p) throws MessagingException {
+ String serviceHost;
+ if (useCanonicalHostName)
+ serviceHost = serverSocket.getInetAddress().getCanonicalHostName();
+ else
+ serviceHost = host;
+ if (saslAuthenticator == null) {
+ try {
+ Class> sac = Class.forName(
+ "com.sun.mail.smtp.SMTPSaslAuthenticator");
+ Constructor> c = sac.getConstructor(new Class>[] {
+ SMTPTransport.class,
+ String.class,
+ Properties.class,
+ MailLogger.class,
+ String.class
+ });
+ saslAuthenticator = (SaslAuthenticator)c.newInstance(
+ new Object[] {
+ this,
+ name,
+ session.getProperties(),
+ logger,
+ serviceHost
+ });
+ } catch (Exception ex) {
+ logger.log(Level.FINE, "Can't load SASL authenticator", ex);
+ // probably because we're running on a system without SASL
+ return false; // not authenticated, try without SASL
+ }
+ }
+
+ // were any allowed mechanisms specified?
+ List v;
+ if (allowed != null && allowed.length > 0) {
+ // remove anything not supported by the server
+ v = new ArrayList<>(allowed.length);
+ for (int i = 0; i < allowed.length; i++)
+ if (supportsAuthentication(allowed[i])) // XXX - case must match
+ v.add(allowed[i]);
+ } else {
+ // everything is allowed
+ v = new ArrayList<>();
+ if (extMap != null) {
+ String a = extMap.get("AUTH");
+ if (a != null) {
+ StringTokenizer st = new StringTokenizer(a);
+ while (st.hasMoreTokens())
+ v.add(st.nextToken());
+ }
+ }
+ }
+ String[] mechs = v.toArray(new String[v.size()]);
+ try {
+ if (noauthdebug && isTracing()) {
+ logger.fine("SASL AUTH command trace suppressed");
+ suspendTracing();
+ }
+ return saslAuthenticator.authenticate(mechs, realm, authzid, u, p);
+ } finally {
+ resumeTracing();
+ }
+ }
+
+ /**
+ * Send the Message to the specified list of addresses.
+ *
+ * If all the addresses
succeed the SMTP check
+ * using the RCPT TO:
command, we attempt to send the message.
+ * A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
+ * successful submission of a message to the SMTP host.
+ *
+ * If some of the addresses
fail the SMTP check,
+ * and the mail.smtp.sendpartial
property is not set,
+ * sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
+ * is fired containing the valid and invalid addresses. The
+ * SendFailedException is also thrown.
+ *
+ * If some of the addresses
fail the SMTP check,
+ * and the mail.smtp.sendpartial
property is set to true,
+ * the message is sent. The TransportEvent of type
+ * MESSAGE_PARTIALLY_DELIVERED
+ * is fired containing the valid and invalid addresses. The
+ * SMTPSendFailedException is also thrown.
+ *
+ * MessagingException is thrown if the message can't write out
+ * an RFC822-compliant stream using its writeTo
method.
+ *
+ * @param message The MimeMessage to be sent
+ * @param addresses List of addresses to send this message to
+ * @see javax.mail.event.TransportEvent
+ * @exception SMTPSendFailedException if the send failed because of
+ * an SMTP command error
+ * @exception SendFailedException if the send failed because of
+ * invalid addresses.
+ * @exception MessagingException if the connection is dead
+ * or not in the connected state or if the message is
+ * not a MimeMessage.
+ */
+ @Override
+ public synchronized void sendMessage(Message message, Address[] addresses)
+ throws MessagingException, SendFailedException {
+
+ sendMessageStart(message != null ? message.getSubject() : "");
+ checkConnected();
+
+ // check if the message is a valid MIME/RFC822 message and that
+ // it has all valid InternetAddresses; fail if not
+ if (!(message instanceof MimeMessage)) {
+ logger.fine("Can only send RFC822 msgs");
+ throw new MessagingException("SMTP can only send RFC822 messages");
+ }
+ if (addresses == null || addresses.length == 0) {
+ throw new SendFailedException("No recipient addresses");
+ }
+ for (int i = 0; i < addresses.length; i++) {
+ if (!(addresses[i] instanceof InternetAddress)) {
+ throw new MessagingException(addresses[i] +
+ " is not an InternetAddress");
+ }
+ }
+
+ this.message = (MimeMessage)message;
+ this.addresses = addresses;
+ validUnsentAddr = addresses; // until we know better
+ expandGroups();
+
+ boolean use8bit = false;
+ if (message instanceof SMTPMessage)
+ use8bit = ((SMTPMessage)message).getAllow8bitMIME();
+ if (!use8bit)
+ use8bit = PropUtil.getBooleanProperty(session.getProperties(),
+ "mail." + name + ".allow8bitmime", false);
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("use8bit " + use8bit);
+ if (use8bit && supportsExtension("8BITMIME")) {
+ if (convertTo8Bit(this.message)) {
+ // in case we made any changes, save those changes
+ // XXX - this will change the Message-ID
+ try {
+ this.message.saveChanges();
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ }
+ }
+
+ try {
+ mailFrom();
+ rcptTo();
+ 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
+ logger.fine("Sending partially failed " +
+ "because of invalid destination addresses");
+ notifyTransportListeners(
+ TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
+ validSentAddr, validUnsentAddr, invalidAddr,
+ this.message);
+
+ throw new SMTPSendFailedException(".", lastReturnCode,
+ lastServerResponse, exception,
+ validSentAddr, validUnsentAddr, invalidAddr);
+ }
+ logger.fine("message successfully delivered to mail server");
+ notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
+ validSentAddr, validUnsentAddr,
+ invalidAddr, this.message);
+ } catch (MessagingException mex) {
+ logger.log(Level.FINE, "MessagingException while sending", mex);
+ // the MessagingException might be wrapping an IOException
+ if (mex.getNextException() instanceof IOException) {
+ // if we catch an IOException, it means that we want
+ // to drop the connection so that the message isn't sent
+ logger.fine("nested IOException, closing");
+ try {
+ closeConnection();
+ } catch (MessagingException cex) { /* ignore it */ }
+ }
+ addressesFailed();
+ notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+ validSentAddr, validUnsentAddr,
+ invalidAddr, this.message);
+
+ throw mex;
+ } catch (IOException ex) {
+ logger.log(Level.FINE, "IOException while sending, closing", ex);
+ // if we catch an IOException, it means that we want
+ // to drop the connection so that the message isn't sent
+ try {
+ closeConnection();
+ } catch (MessagingException mex) { /* ignore it */ }
+ addressesFailed();
+ notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+ validSentAddr, validUnsentAddr,
+ invalidAddr, this.message);
+
+ throw new MessagingException("IOException while sending message",
+ ex);
+ } finally {
+ // no reason to keep this data around
+ validSentAddr = validUnsentAddr = invalidAddr = null;
+ this.addresses = null;
+ this.message = null;
+ this.exception = null;
+ sendPartiallyFailed = false;
+ notificationDone = false; // reset for next send
+ }
+ sendMessageEnd();
+ }
+
+ /**
+ * The send failed, fix the address arrays to report the failure correctly.
+ */
+ private void addressesFailed() {
+ if (validSentAddr != null) {
+ if (validUnsentAddr != null) {
+ Address newa[] =
+ new Address[validSentAddr.length + validUnsentAddr.length];
+ System.arraycopy(validSentAddr, 0,
+ newa, 0, validSentAddr.length);
+ System.arraycopy(validUnsentAddr, 0,
+ newa, validSentAddr.length, validUnsentAddr.length);
+ validSentAddr = null;
+ validUnsentAddr = newa;
+ } else {
+ validUnsentAddr = validSentAddr;
+ validSentAddr = null;
+ }
+ }
+ }
+
+ /**
+ * Close the Transport and terminate the connection to the server.
+ */
+ @Override
+ public synchronized void close() throws MessagingException {
+ if (!super.isConnected()) // Already closed.
+ return;
+ try {
+ if (serverSocket != null) {
+ sendCommand("QUIT");
+ if (quitWait) {
+ int resp = readServerResponse();
+ if (resp != 221 && resp != -1 &&
+ logger.isLoggable(Level.FINE))
+ logger.fine("QUIT failed with " + resp);
+ }
+ }
+ } finally {
+ closeConnection();
+ }
+ }
+
+ private void closeConnection() throws MessagingException {
+ try {
+ if (serverSocket != null)
+ serverSocket.close();
+ } catch (IOException ioex) { // shouldn't happen
+ throw new MessagingException("Server Close Failed", ioex);
+ } finally {
+ serverSocket = null;
+ serverOutput = null;
+ serverInput = null;
+ lineInputStream = null;
+ if (super.isConnected()) // only notify if already connected
+ super.close();
+ }
+ }
+
+ /**
+ * Check whether the transport is connected. Override superclass
+ * method, to actually ping our server connection.
+ */
+ @Override
+ public synchronized boolean isConnected() {
+ if (!super.isConnected())
+ // if we haven't been connected at all, don't bother with NOOP
+ return false;
+
+ try {
+ // sendmail may respond slowly to NOOP after many requests
+ // so if mail.smtp.userset is set we use RSET instead of NOOP.
+ if (useRset)
+ sendCommand("RSET");
+ else
+ sendCommand("NOOP");
+ int resp = readServerResponse();
+
+ /*
+ * NOOP should return 250 on success, however, SIMS 3.2 returns
+ * 200, so we work around it.
+ *
+ * Hotmail didn't used to implement the NOOP command at all so
+ * assume any kind of response means we're still connected.
+ * That is, any response except 421, which means the server
+ * is shutting down the connection.
+ *
+ * Some versions of Exchange return 451 instead of 421 when
+ * timing out a connection.
+ *
+ * Argh!
+ *
+ * If mail.smtp.noop.strict is set to false, be tolerant of
+ * servers that return the wrong response code for success.
+ */
+ if (resp >= 0 && (noopStrict ? resp == 250 : resp != 421)) {
+ return true;
+ } else {
+ try {
+ closeConnection();
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ return false;
+ }
+ } catch (Exception ex) {
+ try {
+ closeConnection();
+ } catch (MessagingException mex) {
+ // ignore it
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Notify all TransportListeners. Keep track of whether notification
+ * has been done so as to only notify once per send.
+ *
+ * @since JavaMail 1.4.2
+ */
+ @Override
+ protected void notifyTransportListeners(int type, Address[] validSent,
+ Address[] validUnsent,
+ Address[] invalid, Message msg) {
+
+ if (!notificationDone) {
+ super.notifyTransportListeners(type, validSent, validUnsent,
+ invalid, msg);
+ notificationDone = true;
+ }
+ }
+
+ /**
+ * Expand any group addresses.
+ */
+ private void expandGroups() {
+ List
groups = null;
+ for (int i = 0; i < addresses.length; i++) {
+ InternetAddress a = (InternetAddress)addresses[i];
+ if (a.isGroup()) {
+ if (groups == null) {
+ // first group, catch up with where we are
+ groups = new ArrayList<>();
+ for (int k = 0; k < i; k++)
+ groups.add(addresses[k]);
+ }
+ // parse it and add each individual address
+ try {
+ InternetAddress[] ia = a.getGroup(true);
+ if (ia != null) {
+ for (int j = 0; j < ia.length; j++)
+ groups.add(ia[j]);
+ } else
+ groups.add(a);
+ } catch (ParseException pex) {
+ // parse failed, add the whole thing
+ groups.add(a);
+ }
+ } else {
+ // if we've started accumulating a list, add this to it
+ if (groups != null)
+ groups.add(a);
+ }
+ }
+
+ // if we have a new list, convert it back to an array
+ if (groups != null) {
+ InternetAddress[] newa = new InternetAddress[groups.size()];
+ groups.toArray(newa);
+ addresses = newa;
+ }
+ }
+
+ /**
+ * If the Part is a text part and has a Content-Transfer-Encoding
+ * of "quoted-printable" or "base64", and it obeys the rules for
+ * "8bit" encoding, change the encoding to "8bit". If the part is
+ * a multipart, recursively process all its parts.
+ *
+ * @return true if any changes were made
+ *
+ * XXX - This is really quite a hack.
+ */
+ private boolean convertTo8Bit(MimePart part) {
+ boolean changed = false;
+ try {
+ if (part.isMimeType("text/*")) {
+ String enc = part.getEncoding();
+ if (enc != null && (enc.equalsIgnoreCase("quoted-printable") ||
+ enc.equalsIgnoreCase("base64"))) {
+ InputStream is = null;
+ try {
+ is = part.getInputStream();
+ if (is8Bit(is)) {
+ /*
+ * If the message was created using an InputStream
+ * then we have to extract the content as an object
+ * and set it back as an object so that the content
+ * will be re-encoded.
+ *
+ * If the message was not created using an
+ * InputStream, the following should have no effect.
+ */
+ part.setContent(part.getContent(),
+ part.getContentType());
+ part.setHeader("Content-Transfer-Encoding", "8bit");
+ changed = true;
+ }
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ex2) {
+ // ignore it
+ }
+ }
+ }
+ }
+ } else if (part.isMimeType("multipart/*")) {
+ MimeMultipart mp = (MimeMultipart)part.getContent();
+ int count = mp.getCount();
+ for (int i = 0; i < count; i++) {
+ if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
+ changed = true;
+ }
+ }
+ } catch (IOException ioex) {
+ // any exception causes us to give up
+ } catch (MessagingException mex) {
+ // any exception causes us to give up
+ }
+ return changed;
+ }
+
+ /**
+ * Check whether the data in the given InputStream follows the
+ * rules for 8bit text. Lines have to be 998 characters or less
+ * and no NULs are allowed. CR and LF must occur in pairs but we
+ * don't check that because we assume this is text and we convert
+ * all CR/LF combinations into canonical CRLF later.
+ */
+ private boolean is8Bit(InputStream is) {
+ int b;
+ int linelen = 0;
+ boolean need8bit = false;
+ try {
+ while ((b = is.read()) >= 0) {
+ b &= 0xff;
+ if (b == '\r' || b == '\n')
+ linelen = 0;
+ else if (b == 0)
+ return false;
+ else {
+ linelen++;
+ if (linelen > 998) // 1000 - CRLF
+ return false;
+ }
+ if (b > 0x7f)
+ need8bit = true;
+ }
+ } catch (IOException ex) {
+ return false;
+ }
+ if (need8bit)
+ logger.fine("found an 8bit part");
+ return need8bit;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ closeConnection();
+ } catch (MessagingException mex) {
+ // ignore it
+ } finally {
+ super.finalize();
+ }
+ }
+
+ ///////////////////// smtp stuff ///////////////////////
+ private BufferedInputStream serverInput;
+ private LineInputStream lineInputStream;
+ private OutputStream serverOutput;
+ private Socket serverSocket;
+ private TraceInputStream traceInput;
+ private TraceOutputStream traceOutput;
+
+ /////// smtp protocol //////
+
+ /**
+ * Issue the HELO
command.
+ *
+ * @param domain our domain
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected void helo(String domain) throws MessagingException {
+ if (domain != null)
+ issueCommand("HELO " + domain, 250);
+ else
+ issueCommand("HELO", 250);
+ }
+
+ /**
+ * Issue the EHLO
command.
+ * Collect the returned list of service extensions.
+ *
+ * @param domain our domain
+ * @return true if command succeeds
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected boolean ehlo(String domain) throws MessagingException {
+ String cmd;
+ if (domain != null)
+ cmd = "EHLO " + domain;
+ else
+ cmd = "EHLO";
+ sendCommand(cmd);
+ int resp = readServerResponse();
+ if (resp == 250) {
+ // extract the supported service extensions
+ BufferedReader rd =
+ new BufferedReader(new StringReader(lastServerResponse));
+ String line;
+ extMap = new Hashtable<>();
+ try {
+ boolean first = true;
+ while ((line = rd.readLine()) != null) {
+ if (first) { // skip first line which is the greeting
+ first = false;
+ continue;
+ }
+ if (line.length() < 5)
+ continue; // shouldn't happen
+ line = line.substring(4); // skip response code
+ int i = line.indexOf(' ');
+ String arg = "";
+ if (i > 0) {
+ arg = line.substring(i + 1);
+ line = line.substring(0, i);
+ }
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("Found extension \"" +
+ line + "\", arg \"" + arg + "\"");
+ extMap.put(line.toUpperCase(Locale.ENGLISH), arg);
+ }
+ } catch (IOException ex) { } // can't happen
+ }
+ return resp == 250;
+ }
+
+ /**
+ * Issue the MAIL FROM:
command to start sending a message.
+ *
+ * Gets the sender's address in the following order:
+ *
+ * SMTPMessage.getEnvelopeFrom()
+ * mail.smtp.from property
+ * From: header in the message
+ * System username using the
+ * InternetAddress.getLocalAddress() method
+ *
+ *
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected void mailFrom() throws MessagingException {
+ String from = null;
+ if (message instanceof SMTPMessage)
+ from = ((SMTPMessage)message).getEnvelopeFrom();
+ if (from == null || from.length() <= 0)
+ from = session.getProperty("mail." + name + ".from");
+ if (from == null || from.length() <= 0) {
+ Address[] fa;
+ Address me;
+ if (message != null && (fa = message.getFrom()) != null &&
+ fa.length > 0)
+ me = fa[0];
+ else
+ me = InternetAddress.getLocalAddress(session);
+
+ if (me != null)
+ from = ((InternetAddress)me).getAddress();
+ else
+ throw new MessagingException(
+ "can't determine local email address");
+ }
+
+ String cmd = "MAIL FROM:" + normalizeAddress(from);
+
+ if (allowutf8 && supportsExtension("SMTPUTF8"))
+ cmd += " SMTPUTF8";
+
+ // request delivery status notification?
+ if (supportsExtension("DSN")) {
+ String ret = null;
+ if (message instanceof SMTPMessage)
+ ret = ((SMTPMessage)message).getDSNRet();
+ if (ret == null)
+ ret = session.getProperty("mail." + name + ".dsn.ret");
+ // XXX - check for legal syntax?
+ if (ret != null)
+ cmd += " RET=" + ret;
+ }
+
+ /*
+ * If an RFC 2554 submitter has been specified, and the server
+ * supports the AUTH extension, include the AUTH= element on
+ * the MAIL FROM command.
+ */
+ if (supportsExtension("AUTH")) {
+ String submitter = null;
+ if (message instanceof SMTPMessage)
+ submitter = ((SMTPMessage)message).getSubmitter();
+ if (submitter == null)
+ submitter = session.getProperty("mail." + name + ".submitter");
+ // XXX - check for legal syntax?
+ if (submitter != null) {
+ try {
+ String s = xtext(submitter,
+ allowutf8 && supportsExtension("SMTPUTF8"));
+ cmd += " AUTH=" + s;
+ } catch (IllegalArgumentException ex) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, "ignoring invalid submitter: " +
+ submitter, ex);
+ }
+ }
+ }
+
+ /*
+ * Have any extensions to the MAIL command been specified?
+ */
+ String ext = null;
+ if (message instanceof SMTPMessage)
+ ext = ((SMTPMessage)message).getMailExtension();
+ if (ext == null)
+ ext = session.getProperty("mail." + name + ".mailextension");
+ if (ext != null && ext.length() > 0)
+ cmd += " " + ext;
+
+ try {
+ issueSendCommand(cmd, 250);
+ } catch (SMTPSendFailedException ex) {
+ int retCode = ex.getReturnCode();
+ switch (retCode) {
+ case 550: case 553: case 503: case 551: case 501:
+ // given address is invalid
+ try {
+ ex.setNextException(new SMTPSenderFailedException(
+ new InternetAddress(from), cmd,
+ retCode, ex.getMessage()));
+ } catch (AddressException aex) {
+ // oh well...
+ }
+ break;
+ default:
+ break;
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Sends each address to the SMTP host using the RCPT TO:
+ * command and copies the address either into
+ * the validSentAddr or invalidAddr arrays.
+ * Sets the sendFailed
+ * flag to true if any addresses failed.
+ *
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ /*
+ * success/failure/error possibilities from the RCPT command
+ * from rfc821, section 4.3
+ * S: 250, 251
+ * F: 550, 551, 552, 553, 450, 451, 452
+ * E: 500, 501, 503, 421
+ *
+ * and how we map the above error/failure conditions to valid/invalid
+ * address lists that are reported in the thrown exception:
+ * invalid addr: 550, 501, 503, 551, 553
+ * valid addr: 552 (quota), 450, 451, 452 (quota), 421 (srvr abort)
+ */
+ protected void rcptTo() throws MessagingException {
+ List valid = new ArrayList<>();
+ List validUnsent = new ArrayList<>();
+ List invalid = new ArrayList<>();
+ int retCode = -1;
+ MessagingException mex = null;
+ boolean sendFailed = false;
+ MessagingException sfex = null;
+ validSentAddr = validUnsentAddr = invalidAddr = null;
+ boolean sendPartial = false;
+ if (message instanceof SMTPMessage)
+ sendPartial = ((SMTPMessage)message).getSendPartial();
+ if (!sendPartial)
+ sendPartial = PropUtil.getBooleanProperty(session.getProperties(),
+ "mail." + name + ".sendpartial", false);
+ if (sendPartial)
+ logger.fine("sendPartial set");
+
+ boolean dsn = false;
+ String notify = null;
+ if (supportsExtension("DSN")) {
+ if (message instanceof SMTPMessage)
+ notify = ((SMTPMessage)message).getDSNNotify();
+ if (notify == null)
+ notify = session.getProperty("mail." + name + ".dsn.notify");
+ // XXX - check for legal syntax?
+ if (notify != null)
+ dsn = true;
+ }
+
+ // try the addresses one at a time
+ for (int i = 0; i < addresses.length; i++) {
+
+ sfex = null;
+ InternetAddress ia = (InternetAddress)addresses[i];
+ String cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
+ if (dsn)
+ cmd += " NOTIFY=" + notify;
+ // send the addresses to the SMTP server
+ sendCommand(cmd);
+ // check the server's response for address validity
+ retCode = readServerResponse();
+ switch (retCode) {
+ case 250: case 251:
+ valid.add(ia);
+ if (!reportSuccess)
+ break;
+
+ // user wants exception even when successful, including
+ // details of the return code
+
+ // create and chain the exception
+ sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
+ lastServerResponse);
+ if (mex == null)
+ mex = sfex;
+ else
+ mex.setNextException(sfex);
+ break;
+
+ case 550: case 553: case 503: case 551: case 501:
+ // given address is invalid
+ if (!sendPartial)
+ sendFailed = true;
+ invalid.add(ia);
+ // create and chain the exception
+ sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+ lastServerResponse);
+ if (mex == null)
+ mex = sfex;
+ else
+ mex.setNextException(sfex);
+ break;
+
+ case 552: case 450: case 451: case 452:
+ // given address is valid
+ if (!sendPartial)
+ sendFailed = true;
+ validUnsent.add(ia);
+ // create and chain the exception
+ sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+ lastServerResponse);
+ if (mex == null)
+ mex = sfex;
+ else
+ mex.setNextException(sfex);
+ break;
+
+ default:
+ // handle remaining 4xy & 5xy codes
+ if (retCode >= 400 && retCode <= 499) {
+ // assume address is valid, although we don't really know
+ validUnsent.add(ia);
+ } else if (retCode >= 500 && retCode <= 599) {
+ // assume address is invalid, although we don't really know
+ invalid.add(ia);
+ } else {
+ // completely unexpected response, just give up
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("got response code " + retCode +
+ ", with response: " + lastServerResponse);
+ String _lsr = lastServerResponse; // else rset will nuke it
+ int _lrc = lastReturnCode;
+ if (serverSocket != null) // hasn't already been closed
+ issueCommand("RSET", -1);
+ lastServerResponse = _lsr; // restore, for get
+ lastReturnCode = _lrc;
+ throw new SMTPAddressFailedException(ia, cmd, retCode,
+ _lsr);
+ }
+ if (!sendPartial)
+ sendFailed = true;
+ // create and chain the exception
+ sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+ lastServerResponse);
+ if (mex == null)
+ mex = sfex;
+ else
+ mex.setNextException(sfex);
+ break;
+ }
+ }
+
+ // if we're willing to send to a partial list, and we found no
+ // valid addresses, that's complete failure
+ if (sendPartial && valid.size() == 0)
+ sendFailed = true;
+
+ // copy the lists into appropriate arrays
+ if (sendFailed) {
+ // copy invalid addrs
+ invalidAddr = new Address[invalid.size()];
+ invalid.toArray(invalidAddr);
+
+ // copy all valid addresses to validUnsent, since something failed
+ validUnsentAddr = new Address[valid.size() + validUnsent.size()];
+ int i = 0;
+ for (int j = 0; j < valid.size(); j++)
+ validUnsentAddr[i++] = (Address)valid.get(j);
+ for (int j = 0; j < validUnsent.size(); j++)
+ validUnsentAddr[i++] = (Address)validUnsent.get(j);
+ } else if (reportSuccess || (sendPartial &&
+ (invalid.size() > 0 || validUnsent.size() > 0))) {
+ // we'll go on to send the message, but after sending we'll
+ // throw an exception with this exception nested
+ sendPartiallyFailed = true;
+ exception = mex;
+
+ // copy invalid addrs
+ invalidAddr = new Address[invalid.size()];
+ invalid.toArray(invalidAddr);
+
+ // copy valid unsent addresses to validUnsent
+ validUnsentAddr = new Address[validUnsent.size()];
+ validUnsent.toArray(validUnsentAddr);
+
+ // copy valid addresses to validSent
+ validSentAddr = new Address[valid.size()];
+ valid.toArray(validSentAddr);
+ } else { // all addresses pass
+ validSentAddr = addresses;
+ }
+
+
+ // print out the debug info
+ if (logger.isLoggable(Level.FINE)) {
+ if (validSentAddr != null && validSentAddr.length > 0) {
+ logger.fine("Verified Addresses");
+ for (int l = 0; l < validSentAddr.length; l++) {
+ logger.fine(" " + validSentAddr[l]);
+ }
+ }
+ if (validUnsentAddr != null && validUnsentAddr.length > 0) {
+ logger.fine("Valid Unsent Addresses");
+ for (int j = 0; j < validUnsentAddr.length; j++) {
+ logger.fine(" " + validUnsentAddr[j]);
+ }
+ }
+ if (invalidAddr != null && invalidAddr.length > 0) {
+ logger.fine("Invalid Addresses");
+ for (int k = 0; k < invalidAddr.length; k++) {
+ logger.fine(" " + invalidAddr[k]);
+ }
+ }
+ }
+
+ // throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
+ if (sendFailed) {
+ logger.fine(
+ "Sending failed because of invalid destination addresses");
+ notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+ validSentAddr, validUnsentAddr,
+ invalidAddr, this.message);
+
+ // reset the connection so more sends are allowed
+ String lsr = lastServerResponse; // save, for get
+ int lrc = lastReturnCode;
+ try {
+ if (serverSocket != null)
+ issueCommand("RSET", -1);
+ } catch (MessagingException ex) {
+ // if can't reset, best to close the connection
+ try {
+ close();
+ } catch (MessagingException ex2) {
+ // thrown by close()--ignore, will close() later anyway
+ logger.log(Level.FINE, "close failed", ex2);
+ }
+ } finally {
+ lastServerResponse = lsr; // restore
+ lastReturnCode = lrc;
+ }
+
+ throw new SendFailedException("Invalid Addresses", mex,
+ validSentAddr,
+ validUnsentAddr, invalidAddr);
+ }
+ }
+
+ /**
+ * Send the DATA
command to the SMTP host and return
+ * an OutputStream to which the data is to be written.
+ *
+ * @return the stream to write to
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected OutputStream data() throws MessagingException {
+ assert Thread.holdsLock(this);
+ issueSendCommand("DATA", 354);
+ dataStream = new SMTPOutputStream(serverOutput);
+ return dataStream;
+ }
+
+ /**
+ * Terminate the sent data.
+ *
+ * @exception IOException for I/O errors
+ * @exception MessagingException for other failures
+ * @since JavaMail 1.4.1
+ */
+ protected void finishData() throws IOException, MessagingException {
+ assert Thread.holdsLock(this);
+ dataStream.ensureAtBOL();
+ issueSendCommand(".", 250);
+ }
+
+ /**
+ * 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 STARTTLS
command and switch the socket to
+ * TLS mode if it succeeds.
+ *
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected void startTLS() throws MessagingException {
+ issueCommand("STARTTLS", 220);
+ // it worked, now switch the socket into TLS mode
+ try {
+ serverSocket = SocketFetcher.startTLS(serverSocket, host,
+ session.getProperties(), "mail." + name);
+ initStreams();
+ } catch (IOException ioex) {
+ closeConnection();
+ throw new MessagingException("Could not convert socket to TLS",
+ ioex);
+ }
+ }
+
+ /////// primitives ///////
+
+ /**
+ * Connect to host on port and start the SMTP protocol.
+ */
+ private void openServer(String host, int port)
+ throws MessagingException {
+
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("trying to connect to host \"" + host +
+ "\", port " + port + ", isSSL " + isSSL);
+
+ try {
+ Properties props = session.getProperties();
+
+ serverSocket = SocketFetcher.getSocket(host, port,
+ props, "mail." + name, isSSL);
+
+ // socket factory may've chosen a different port,
+ // update it for the debug messages that follow
+ port = serverSocket.getPort();
+ // save host name for startTLS
+ this.host = host;
+
+ initStreams();
+
+ int r = -1;
+ if ((r = readServerResponse()) != 220) {
+ String failResponse = lastServerResponse;
+ try {
+ if (quitOnSessionReject) {
+ sendCommand("QUIT");
+ if (quitWait) {
+ int resp = readServerResponse();
+ if (resp != 221 && resp != -1 &&
+ logger.isLoggable(Level.FINE))
+ logger.fine("QUIT failed with " + resp);
+ }
+ }
+ } catch (Exception e) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, "QUIT failed", e);
+ } finally {
+ serverSocket.close();
+ serverSocket = null;
+ serverOutput = null;
+ serverInput = null;
+ lineInputStream = null;
+ }
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("got bad greeting from host \"" +
+ host + "\", port: " + port +
+ ", response: " + failResponse);
+ throw new MessagingException(
+ "Got bad greeting from SMTP host: " + host +
+ ", port: " + port +
+ ", response: " + failResponse);
+ } else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("connected to host \"" +
+ host + "\", port: " + port);
+ }
+ } catch (UnknownHostException uhex) {
+ throw new MessagingException("Unknown SMTP host: " + host, uhex);
+ } catch (SocketConnectException scex) {
+ throw new MailConnectException(scex);
+ } catch (IOException ioe) {
+ throw new MessagingException("Could not connect to SMTP host: " +
+ host + ", port: " + port, ioe);
+ }
+ }
+
+ /**
+ * Start the protocol to the server on serverSocket,
+ * assumed to be provided and connected by the caller.
+ */
+ private void openServer() throws MessagingException {
+ int port = -1;
+ host = "UNKNOWN";
+ try {
+ port = serverSocket.getPort();
+ host = serverSocket.getInetAddress().getHostName();
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("starting protocol to host \"" +
+ host + "\", port " + port);
+
+ initStreams();
+
+ int r = -1;
+ if ((r = readServerResponse()) != 220) {
+ try {
+ if (quitOnSessionReject) {
+ sendCommand("QUIT");
+ if (quitWait) {
+ int resp = readServerResponse();
+ if (resp != 221 && resp != -1 &&
+ logger.isLoggable(Level.FINE))
+ logger.fine("QUIT failed with " + resp);
+ }
+ }
+ } catch (Exception e) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, "QUIT failed", e);
+ } finally {
+ serverSocket.close();
+ serverSocket = null;
+ serverOutput = null;
+ serverInput = null;
+ lineInputStream = null;
+ }
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("got bad greeting from host \"" +
+ host + "\", port: " + port +
+ ", response: " + r);
+ throw new MessagingException(
+ "Got bad greeting from SMTP host: " + host +
+ ", port: " + port +
+ ", response: " + r);
+ } else {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("protocol started to host \"" +
+ host + "\", port: " + port);
+ }
+ } catch (IOException ioe) {
+ throw new MessagingException(
+ "Could not start protocol to SMTP host: " +
+ host + ", port: " + port, ioe);
+ }
+ }
+
+
+ private void initStreams() throws IOException {
+ boolean quote = PropUtil.getBooleanProperty(session.getProperties(),
+ "mail.debug.quote", false);
+
+ traceInput =
+ new TraceInputStream(serverSocket.getInputStream(), traceLogger);
+ traceInput.setQuote(quote);
+
+ traceOutput =
+ new TraceOutputStream(serverSocket.getOutputStream(), traceLogger);
+ traceOutput.setQuote(quote);
+
+ serverOutput =
+ new BufferedOutputStream(traceOutput);
+ serverInput =
+ new BufferedInputStream(traceInput);
+ lineInputStream = new LineInputStream(serverInput);
+ }
+
+ /**
+ * Is protocol tracing enabled?
+ */
+ private boolean isTracing() {
+ return traceLogger.isLoggable(Level.FINEST);
+ }
+
+ /**
+ * Temporarily turn off protocol tracing, e.g., to prevent
+ * tracing the authentication sequence, including the password.
+ */
+ private void suspendTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(false);
+ traceOutput.setTrace(false);
+ }
+ }
+
+ /**
+ * Resume protocol tracing, if it was enabled to begin with.
+ */
+ private void resumeTracing() {
+ if (traceLogger.isLoggable(Level.FINEST)) {
+ traceInput.setTrace(true);
+ traceOutput.setTrace(true);
+ }
+ }
+
+ /**
+ * Send the command to the server. If the expected response code
+ * is not received, throw a MessagingException.
+ *
+ * @param cmd the command to send
+ * @param expect the expected response code (-1 means don't care)
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ public synchronized void issueCommand(String cmd, int expect)
+ throws MessagingException {
+ sendCommand(cmd);
+
+ // if server responded with an unexpected return code,
+ // throw the exception, notifying the client of the response
+ int resp = readServerResponse();
+ if (expect != -1 && resp != expect)
+ throw new MessagingException(lastServerResponse);
+ }
+
+ /**
+ * Issue a command that's part of sending a message.
+ */
+ private void issueSendCommand(String cmd, int expect)
+ throws MessagingException {
+ sendCommand(cmd);
+
+ // if server responded with an unexpected return code,
+ // throw the exception, notifying the client of the response
+ int ret;
+ if ((ret = readServerResponse()) != expect) {
+ // assume message was not sent to anyone,
+ // combine valid sent & unsent addresses
+ int vsl = validSentAddr == null ? 0 : validSentAddr.length;
+ int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
+ Address[] valid = new Address[vsl + vul];
+ if (vsl > 0)
+ System.arraycopy(validSentAddr, 0, valid, 0, vsl);
+ if (vul > 0)
+ System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
+ validSentAddr = null;
+ validUnsentAddr = valid;
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("got response code " + ret +
+ ", with response: " + lastServerResponse);
+ String _lsr = lastServerResponse; // else rset will nuke it
+ int _lrc = lastReturnCode;
+ if (serverSocket != null) // hasn't already been closed
+ issueCommand("RSET", -1);
+ lastServerResponse = _lsr; // restore, for get
+ lastReturnCode = _lrc;
+ throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
+ exception, validSentAddr, validUnsentAddr, invalidAddr);
+ }
+ }
+
+ /**
+ * Send the command to the server and return the response code
+ * from the server.
+ *
+ * @param cmd the command
+ * @return the response code
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ public synchronized int simpleCommand(String cmd)
+ throws MessagingException {
+ sendCommand(cmd);
+ return readServerResponse();
+ }
+
+ /**
+ * Send the command to the server and return the response code
+ * from the server.
+ *
+ * @param cmd the command
+ * @return the response code
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected int simpleCommand(byte[] cmd) throws MessagingException {
+ assert Thread.holdsLock(this);
+ sendCommand(cmd);
+ return readServerResponse();
+ }
+
+ /**
+ * Sends command cmd
to the server terminating
+ * it with CRLF
.
+ *
+ * @param cmd the command
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected void sendCommand(String cmd) throws MessagingException {
+ sendCommand(toBytes(cmd));
+ }
+
+ private void sendCommand(byte[] cmdBytes) throws MessagingException {
+ assert Thread.holdsLock(this);
+ //if (logger.isLoggable(Level.FINE))
+ //logger.fine("SENT: " + new String(cmdBytes, 0));
+
+ try {
+ serverOutput.write(cmdBytes);
+ serverOutput.write(CRLF);
+ serverOutput.flush();
+ } catch (IOException ex) {
+ throw new MessagingException("Can't send command to SMTP host", ex);
+ }
+ }
+
+ /**
+ * Reads server reponse returning the returnCode
+ * as the number. Returns -1 on failure. Sets
+ * lastServerResponse
and lastReturnCode
.
+ *
+ * @return server response code
+ * @exception MessagingException for failures
+ * @since JavaMail 1.4.1
+ */
+ protected int readServerResponse() throws MessagingException {
+ assert Thread.holdsLock(this);
+ String serverResponse = "";
+ int returnCode = 0;
+ StringBuilder buf = new StringBuilder(100);
+
+ // read the server response line(s) and add them to the buffer
+ // that stores the response
+ try {
+ String line = null;
+
+ do {
+ line = lineInputStream.readLine();
+ if (line == null) {
+ serverResponse = buf.toString();
+ if (serverResponse.length() == 0)
+ serverResponse = "[EOF]";
+ lastServerResponse = serverResponse;
+ lastReturnCode = -1;
+ logger.log(Level.FINE, "EOF: {0}", serverResponse);
+ return -1;
+ }
+ buf.append(line);
+ buf.append("\n");
+ } while (isNotLastLine(line));
+
+ serverResponse = buf.toString();
+ } catch (IOException ioex) {
+ logger.log(Level.FINE, "exception reading response", ioex);
+ //ioex.printStackTrace(out);
+ lastServerResponse = "";
+ lastReturnCode = 0;
+ throw new MessagingException("Exception reading response", ioex);
+ //returnCode = -1;
+ }
+
+ // print debug info
+ //if (logger.isLoggable(Level.FINE))
+ //logger.fine("RCVD: " + serverResponse);
+
+ // parse out the return code
+ if (serverResponse.length() >= 3) {
+ try {
+ returnCode = Integer.parseInt(serverResponse.substring(0, 3));
+ } catch (NumberFormatException nfe) {
+ try {
+ close();
+ } catch (MessagingException mex) {
+ // thrown by close()--ignore, will close() later anyway
+ logger.log(Level.FINE, "close failed", mex);
+ }
+ returnCode = -1;
+ } catch (StringIndexOutOfBoundsException ex) {
+ try {
+ close();
+ } catch (MessagingException mex) {
+ // thrown by close()--ignore, will close() later anyway
+ logger.log(Level.FINE, "close failed", mex);
+ }
+ returnCode = -1;
+ }
+ } else {
+ returnCode = -1;
+ }
+ if (returnCode == -1)
+ logger.log(Level.FINE, "bad server response: {0}", serverResponse);
+
+ lastServerResponse = serverResponse;
+ lastReturnCode = returnCode;
+ return returnCode;
+ }
+
+ /**
+ * Check if we're in the connected state. Don't bother checking
+ * whether the server is still alive, that will be detected later.
+ *
+ * @exception IllegalStateException if not connected
+ *
+ * @since JavaMail 1.4.1
+ */
+ protected void checkConnected() {
+ if (!super.isConnected())
+ throw new IllegalStateException("Not connected");
+ }
+
+ // tests if the line
is an intermediate line according to SMTP
+ private boolean isNotLastLine(String line) {
+ return line != null && line.length() >= 4 && line.charAt(3) == '-';
+ }
+
+ // wraps an address in "<>"'s if necessary
+ private String normalizeAddress(String addr) {
+ if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
+ return "<" + addr + ">";
+ else
+ return addr;
+ }
+
+ /**
+ * Return true if the SMTP server supports the specified service
+ * extension. Extensions are reported as results of the EHLO
+ * command when connecting to the server. See
+ * RFC 1869
+ * and other RFCs that define specific extensions.
+ *
+ * @param ext the service extension name
+ * @return true if the extension is supported
+ *
+ * @since JavaMail 1.3.2
+ */
+ public boolean supportsExtension(String ext) {
+ return extMap != null &&
+ extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
+ }
+
+ /**
+ * Return the parameter the server provided for the specified
+ * service extension, or null if the extension isn't supported.
+ *
+ * @param ext the service extension name
+ * @return the extension parameter
+ *
+ * @since JavaMail 1.3.2
+ */
+ public String getExtensionParameter(String ext) {
+ return extMap == null ? null :
+ extMap.get(ext.toUpperCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Does the server we're connected to support the specified
+ * authentication mechanism? Uses the extension information
+ * returned by the server from the EHLO command.
+ *
+ * @param auth the authentication mechanism
+ * @return true if the authentication mechanism is supported
+ *
+ * @since JavaMail 1.4.1
+ */
+ protected boolean supportsAuthentication(String auth) {
+ assert Thread.holdsLock(this);
+ if (extMap == null)
+ return false;
+ String a = extMap.get("AUTH");
+ if (a == null)
+ return false;
+ StringTokenizer st = new StringTokenizer(a);
+ while (st.hasMoreTokens()) {
+ String tok = st.nextToken();
+ if (tok.equalsIgnoreCase(auth))
+ return true;
+ }
+ // hack for buggy servers that advertise capability incorrectly
+ if (auth.equalsIgnoreCase("LOGIN") && supportsExtension("AUTH=LOGIN")) {
+ logger.fine("use AUTH=LOGIN hack");
+ return true;
+ }
+ return false;
+ }
+
+ private static char[] hexchar = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ /**
+ * Convert a string to RFC 1891 xtext format.
+ *
+ *
+ * xtext = *( xchar / hexchar )
+ *
+ * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
+ * except for "+" and "=".
+ *
+ * ; "hexchar"s are intended to encode octets that cannot appear
+ * ; as ASCII characters within an esmtp-value.
+ *
+ * hexchar = ASCII "+" immediately followed by two upper case
+ * hexadecimal digits
+ *
+ *
+ * @param s the string to convert
+ * @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) {
+ StringBuilder sb = null;
+ 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 == '=') {
+ // not printable ASCII
+ if (sb == null) {
+ sb = new StringBuilder(s.length() + 4);
+ sb.append(s.substring(0, i));
+ }
+ sb.append('+');
+ sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
+ sb.append(hexchar[((int)c)& 0x0f]);
+ } else {
+ if (sb != null)
+ sb.append(c);
+ }
+ }
+ return sb != null ? sb.toString() : s;
+ }
+
+ private String traceUser(String user) {
+ return debugusername ? user : "";
+ }
+
+ private String tracePassword(String password) {
+ return debugpassword ? password :
+ (password == null ? "" : "");
+ }
+
+ /**
+ * Convert the String to either ASCII or UTF-8 bytes
+ * depending on allowutf8.
+ */
+ private byte[] toBytes(String s) {
+ if (allowutf8)
+ return s.getBytes(StandardCharsets.UTF_8);
+ else
+ // don't use StandardCharsets.US_ASCII because it rejects non-ASCII
+ return ASCIIUtility.getBytes(s);
+ }
+
+ /*
+ * Probe points for GlassFish monitoring.
+ */
+ 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 byte
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 --git a/app/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java b/app/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java
new file mode 100644
index 0000000000..2c2a645ebd
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.MessagingException;
+
+/**
+ * Interface to make it easier to call SMTPSaslAuthenticator.
+ */
+
+public interface SaslAuthenticator {
+ public boolean authenticate(String[] mechs, String realm, String authzid,
+ String u, String p) throws MessagingException;
+
+}
diff --git a/app/src/main/java/com/sun/mail/smtp/package.html b/app/src/main/java/com/sun/mail/smtp/package.html
new file mode 100644
index 0000000000..197755c64c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/smtp/package.html
@@ -0,0 +1,835 @@
+
+
+
+
+
+
+com.sun.mail.smtp package
+
+
+
+An SMTP protocol provider for the Jakarta Mail API
+that provides access to an SMTP server.
+Refer to RFC 821
+for more information.
+
+When sending a message, detailed information on each address that
+fails is available in an
+{@link com.sun.mail.smtp.SMTPAddressFailedException SMTPAddressFailedException}
+chained off the top level
+{@link javax.mail.SendFailedException SendFailedException}
+that is thrown.
+In addition, if the mail.smtp.reportsuccess
property
+is set, an
+{@link com.sun.mail.smtp.SMTPAddressSucceededException
+SMTPAddressSucceededException}
+will be included in the list for each address that is successful.
+Note that this will cause a top level
+{@link javax.mail.SendFailedException SendFailedException}
+to be thrown even though the send was successful.
+
+
+The SMTP provider also supports ESMTP
+(RFC 1651 ).
+It can optionally use SMTP Authentication
+(RFC 2554 )
+using the LOGIN, PLAIN, DIGEST-MD5, and NTLM mechanisms
+(RFC 4616
+and RFC 2831 ).
+
+
+To use SMTP authentication you'll need to set the mail.smtp.auth
+property (see below) or provide the SMTP Transport
+with a username and password when connecting to the SMTP server. You
+can do this using one of the following approaches:
+
+
+
+
+Provide an Authenticator object when creating your mail Session
+and provide the username and password information during the
+Authenticator callback.
+
+
+Note that the mail.smtp.user
property can be set to provide a
+default username for the callback, but the password will still need to be
+supplied explicitly.
+
+
+This approach allows you to use the static Transport send
method
+to send messages.
+
+
+
+
+Call the Transport connect
method explicitly with username and
+password arguments.
+
+
+This approach requires you to explicitly manage a Transport object
+and use the Transport sendMessage
method to send the message.
+The transport.java demo program demonstrates how to manage a Transport
+object. The following is roughly equivalent to the static
+Transport send
method, but supplies the needed username and
+password:
+
+
+Transport tr = session.getTransport("smtp");
+tr.connect(smtphost, username, password);
+msg.saveChanges(); // don't forget this
+tr.sendMessage(msg, msg.getAllRecipients());
+tr.close();
+
+
+
+
+When using DIGEST-MD5 authentication,
+you'll also need to supply an appropriate realm;
+your mail server administrator can supply this information.
+You can set this using the mail.smtp.sasl.realm
property,
+or the setSASLRealm
method on SMTPTransport
.
+
+
+The SMTP protocol provider can use SASL
+(RFC 2222 )
+authentication mechanisms on systems that support the
+javax.security.sasl
APIs, such as J2SE 5.0.
+In addition to the SASL mechanisms that are built into
+the SASL implementation, users can also provide additional
+SASL mechanisms of their own design to support custom authentication
+schemes. See the
+
+Java SASL API Programming and Deployment Guide for details.
+Note that the current implementation doesn't support SASL mechanisms
+that provide their own integrity or confidentiality layer.
+
+
+Support for OAuth 2.0 authentication via the
+
+XOAUTH2 authentication mechanism is provided either through the SASL
+support described above or as a built-in authentication mechanism in the
+SMTP provider.
+The OAuth 2.0 Access Token should be passed as the password for this mechanism.
+See
+OAuth2 Support for details.
+
+
+SMTP can also optionally request Delivery Status Notifications
+(RFC 1891 ).
+The delivery status will typically be reported using
+a "multipart/report"
+(RFC 1892 )
+message type with a "message/delivery-status"
+(RFC 1894 )
+part.
+You can use the classes in the com.sun.mail.dsn
package to
+handle these MIME types.
+Note that you'll need to include dsn.jar
in your CLASSPATH
+as this support is not included in mail.jar
.
+
+
+See below for the properties to enable these features.
+
+
+Note also that THERE IS NOT SUFFICIENT DOCUMENTATION HERE TO USE THESE
+FEATURES!!! You will need to read the appropriate RFCs mentioned above
+to understand what these features do and how to use them. Don't just
+start setting properties and then complain to us when it doesn't work
+like you expect it to work. READ THE RFCs FIRST!!!
+
+
+The SMTP protocol provider supports the CHUNKING extension defined in
+RFC 3030 .
+Set the mail.smtp.chunksize
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.
+
+Properties
+
+The SMTP protocol provider supports the following properties,
+which may be set in the Jakarta Mail Session
object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted. For example, use
+
+
+ props.put("mail.smtp.port", "888");
+
+
+to set the mail.smtp.port
property, which is of type int.
+
+
+Note that if you're using the "smtps" protocol to access SMTP over SSL,
+all the properties would be named "mail.smtps.*".
+
+
+SMTP properties
+
+Name
+Type
+Description
+
+
+
+mail.smtp.user
+String
+Default user name for SMTP.
+
+
+
+mail.smtp.host
+String
+The SMTP server to connect to.
+
+
+
+mail.smtp.port
+int
+The SMTP server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 25.
+
+
+
+mail.smtp.connectiontimeout
+int
+Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.smtp.timeout
+int
+Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.
+
+
+
+mail.smtp.writetimeout
+int
+Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.
+
+
+
+mail.smtp.from
+String
+
+Email address to use for SMTP MAIL command. This sets the envelope
+return address. Defaults to msg.getFrom() or
+InternetAddress.getLocalAddress(). NOTE: mail.smtp.user was previously
+used for this.
+
+
+
+
+mail.smtp.localhost
+String
+
+Local host name used in the SMTP HELO or EHLO command.
+Defaults to InetAddress.getLocalHost().getHostName()
.
+Should not normally need to
+be set if your JDK and your name service are configured properly.
+
+
+
+
+mail.smtp.localaddress
+String
+
+Local address (host name) to bind to when creating the SMTP socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+
+
+
+
+mail.smtp.localport
+int
+
+Local port number to bind to when creating the SMTP socket.
+Defaults to the port number picked by the Socket class.
+
+
+
+
+mail.smtp.ehlo
+boolean
+
+If false, do not attempt to sign on with the EHLO command. Defaults to
+true. Normally failure of the EHLO command will fallback to the HELO
+command; this property exists only for servers that don't fail EHLO
+properly or don't implement EHLO properly.
+
+
+
+
+mail.smtp.auth
+boolean
+If true, attempt to authenticate the user using the AUTH command.
+Defaults to false.
+
+
+
+mail.smtp.auth.mechanisms
+String
+
+If set, lists the authentication mechanisms to consider, and the order
+in which to consider them. Only mechanisms supported by the server and
+supported by the current implementation will be used.
+The default is "LOGIN PLAIN DIGEST-MD5 NTLM"
, which includes all
+the authentication mechanisms supported by the current implementation
+except XOAUTH2.
+
+
+
+
+mail.smtp.auth.login.disable
+boolean
+If true, prevents use of the AUTH LOGIN
command.
+Default is false.
+
+
+
+mail.smtp.auth.plain.disable
+boolean
+If true, prevents use of the AUTH PLAIN
command.
+Default is false.
+
+
+
+mail.smtp.auth.digest-md5.disable
+boolean
+If true, prevents use of the AUTH DIGEST-MD5
command.
+Default is false.
+
+
+
+mail.smtp.auth.ntlm.disable
+boolean
+If true, prevents use of the AUTH NTLM
command.
+Default is false.
+
+
+
+mail.smtp.auth.ntlm.domain
+String
+
+The NTLM authentication domain.
+
+
+
+
+mail.smtp.auth.ntlm.flags
+int
+
+NTLM protocol-specific flags.
+See
+http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags for details.
+
+
+
+
+
+
+mail.smtp.auth.xoauth2.disable
+boolean
+If true, prevents use of the AUTHENTICATE XOAUTH2
command.
+Because the OAuth 2.0 protocol requires a special access token instead of
+a password, this mechanism is disabled by default. Enable it by explicitly
+setting this property to "false" or by setting the "mail.smtp.auth.mechanisms"
+property to "XOAUTH2".
+
+
+
+mail.smtp.submitter
+String
+The submitter to use in the AUTH tag in the MAIL FROM command.
+Typically used by a mail relay to pass along information about the
+original submitter of the message.
+See also the {@link com.sun.mail.smtp.SMTPMessage#setSubmitter setSubmitter}
+method of {@link com.sun.mail.smtp.SMTPMessage SMTPMessage}.
+Mail clients typically do not use this.
+
+
+
+
+mail.smtp.dsn.notify
+String
+The NOTIFY option to the RCPT command. Either NEVER, or some
+combination of SUCCESS, FAILURE, and DELAY (separated by commas).
+
+
+
+mail.smtp.dsn.ret
+String
+The RET option to the MAIL command. Either FULL or HDRS.
+
+
+
+mail.smtp.allow8bitmime
+boolean
+
+If set to true, and the server supports the 8BITMIME extension, text
+parts of messages that use the "quoted-printable" or "base64" encodings
+are converted to use "8bit" encoding if they follow the RFC2045 rules
+for 8bit text.
+
+
+
+
+mail.smtp.sendpartial
+boolean
+
+If set to true, and a message has some valid and some invalid
+addresses, send the message anyway, reporting the partial failure with
+a SendFailedException. If set to false (the default), the message is
+not sent to any of the recipients if there is an invalid recipient
+address.
+
+
+
+
+mail.smtp.sasl.enable
+boolean
+
+If set to true, attempt to use the javax.security.sasl package to
+choose an authentication mechanism for login.
+Defaults to false.
+
+
+
+
+mail.smtp.sasl.mechanisms
+String
+
+A space or comma separated list of SASL mechanism names to try
+to use.
+
+
+
+
+mail.smtp.sasl.authorizationid
+String
+
+The authorization ID to use in the SASL authentication.
+If not set, the authentication ID (user name) is used.
+
+
+
+
+mail.smtp.sasl.realm
+String
+The realm to use with DIGEST-MD5 authentication.
+
+
+
+mail.smtp.sasl.usecanonicalhostname
+boolean
+
+If set to true, the canonical host name returned by
+{@link java.net.InetAddress#getCanonicalHostName InetAddress.getCanonicalHostName}
+is passed to the SASL mechanism, instead of the host name used to connect.
+Defaults to false.
+
+
+
+
+mail.smtp.quitwait
+boolean
+
+If set to false, the QUIT command is sent
+and the connection is immediately closed.
+If set to true (the default), causes the transport to wait
+for the response to the QUIT command.
+
+
+
+
+mail.smtp.quitonsessionreject
+boolean
+
+If set to false (the default), on session initiation rejection the QUIT
+command is not sent and the connection is immediately closed.
+If set to true, causes the transport to send the QUIT command prior to
+closing the connection.
+
+
+
+
+mail.smtp.reportsuccess
+boolean
+
+If set to true, causes the transport to include an
+{@link com.sun.mail.smtp.SMTPAddressSucceededException
+SMTPAddressSucceededException}
+for each address that is successful.
+Note also that this will cause a
+{@link javax.mail.SendFailedException SendFailedException}
+to be thrown from the
+{@link com.sun.mail.smtp.SMTPTransport#sendMessage sendMessage}
+method of
+{@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+even if all addresses were correct and the message was sent
+successfully.
+
+
+
+
+mail.smtp.socketFactory
+SocketFactory
+
+If set to a class that implements the
+javax.net.SocketFactory
interface, this class
+will be used to create SMTP sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.smtp.socketFactory.class
+String
+
+If set, specifies the name of a class that implements the
+javax.net.SocketFactory
interface. This class
+will be used to create SMTP sockets.
+
+
+
+
+mail.smtp.socketFactory.fallback
+boolean
+
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the java.net.Socket
class.
+Defaults to true.
+
+
+
+
+mail.smtp.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.smtp.ssl.enable
+boolean
+
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "smtp" protocol and true for the "smtps" protocol.
+
+
+
+
+mail.smtp.ssl.checkserveridentity
+boolean
+
+If set to true, check the server identity as specified by
+RFC 2595 .
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+
+
+
+
+mail.smtp.ssl.trust
+String
+
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+
+
+
+
+mail.smtp.ssl.socketFactory
+SSLSocketFactory
+
+If set to a class that extends the
+javax.net.ssl.SSLSocketFactory
class, this class
+will be used to create SMTP SSL sockets. Note that this is an
+instance of a class, not a name, and must be set using the
+put
method, not the setProperty
method.
+
+
+
+
+mail.smtp.ssl.socketFactory.class
+String
+
+If set, specifies the name of a class that extends the
+javax.net.ssl.SSLSocketFactory
class. This class
+will be used to create SMTP SSL sockets.
+
+
+
+
+mail.smtp.ssl.socketFactory.port
+int
+
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+
+
+
+
+mail.smtp.ssl.protocols
+string
+
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledProtocols
method.
+
+
+
+
+mail.smtp.ssl.ciphersuites
+string
+
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the javax.net.ssl.SSLSocket.setEnabledCipherSuites
method.
+
+
+
+
+mail.smtp.starttls.enable
+boolean
+
+If true, enables the use of the STARTTLS
command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+mail.smtp.starttls.required
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Defaults to false.
+
+
+
+
+mail.smtp.starttls.required
+boolean
+
+If true, requires the use of the STARTTLS
command.
+If the server doesn't support the STARTTLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+
+
+
+
+mail.smtp.proxy.host
+string
+
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.smtp.proxy.port
+string
+
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+
+
+
+
+mail.smtp.proxy.user
+string
+
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.smtp.proxy.password
+string
+
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+
+
+
+
+mail.smtp.socks.host
+string
+
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+
+
+
+
+mail.smtp.socks.port
+string
+
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+
+
+
+
+mail.smtp.mailextension
+String
+
+Extension string to append to the MAIL command.
+The extension string can be used to specify standard SMTP
+service extensions as well as vendor-specific extensions.
+Typically the application should use the
+{@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+method {@link com.sun.mail.smtp.SMTPTransport#supportsExtension
+supportsExtension}
+to verify that the server supports the desired service extension.
+See RFC 1869
+and other RFCs that define specific extensions.
+
+
+
+
+mail.smtp.userset
+boolean
+
+If set to true, use the RSET command instead of the NOOP command
+in the {@link javax.mail.Transport#isConnected isConnected} method.
+In some cases sendmail will respond slowly after many NOOP commands;
+use of RSET avoids this sendmail issue.
+Defaults to false.
+
+
+
+
+mail.smtp.noop.strict
+boolean
+
+If set to true (the default), insist on a 250 response code from the NOOP
+command to indicate success. The NOOP command is used by the
+{@link javax.mail.Transport#isConnected isConnected} method to determine
+if the connection is still alive.
+Some older servers return the wrong response code on success, some
+servers don't implement the NOOP command at all and so always return
+a failure code. Set this property to false to handle servers
+that are broken in this way.
+Normally, when a server times out a connection, it will send a 421
+response code, which the client will see as the response to the next
+command it issues.
+Some servers send the wrong failure response code when timing out a
+connection.
+Do not set this property to false when dealing with servers that are
+broken in this way.
+
+
+
+
+
+In general, applications should not need to use the classes in this
+package directly. Instead, they should use the APIs defined by
+javax.mail
package (and subpackages). Applications should
+never construct instances of SMTPTransport
directly.
+Instead, they should use the
+Session
method getTransport
to acquire an
+appropriate Transport
object.
+
+
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.smtp provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+
+
+SMTP Loggers
+
+Logger Name
+Logging Level
+Purpose
+
+
+
+com.sun.mail.smtp
+CONFIG
+Configuration of the SMTPTransport
+
+
+
+com.sun.mail.smtp
+FINE
+General debugging output
+
+
+
+com.sun.mail.smtp.protocol
+FINEST
+Complete protocol trace
+
+
+
+WARNING: The APIs unique to this package should be
+considered EXPERIMENTAL . They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+
+
+
+
diff --git a/app/src/main/java/com/sun/mail/util/ASCIIUtility.java b/app/src/main/java/com/sun/mail/util/ASCIIUtility.java
new file mode 100644
index 0000000000..4ab7a5576d
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/ASCIIUtility.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class ASCIIUtility {
+
+ // Private constructor so that this class is not instantiated
+ private ASCIIUtility() { }
+
+ /**
+ * Convert the bytes within the specified range of the given byte
+ * array into a signed integer in the given radix . The range extends
+ * from start
till, but not including end
.
+ *
+ * Based on java.lang.Integer.parseInt()
+ *
+ * @param b the bytes
+ * @param start the first byte offset
+ * @param end the last byte offset
+ * @param radix the radix
+ * @return the integer value
+ * @exception NumberFormatException for conversion errors
+ */
+ public static int parseInt(byte[] b, int start, int end, int radix)
+ throws NumberFormatException {
+ if (b == null)
+ throw new NumberFormatException("null");
+
+ int result = 0;
+ boolean negative = false;
+ int i = start;
+ int limit;
+ int multmin;
+ int digit;
+
+ if (end > start) {
+ if (b[i] == '-') {
+ negative = true;
+ limit = Integer.MIN_VALUE;
+ i++;
+ } else {
+ limit = -Integer.MAX_VALUE;
+ }
+ multmin = limit / radix;
+ if (i < end) {
+ digit = Character.digit((char)b[i++], radix);
+ if (digit < 0) {
+ throw new NumberFormatException(
+ "illegal number: " + toString(b, start, end)
+ );
+ } else {
+ result = -digit;
+ }
+ }
+ while (i < end) {
+ // Accumulating negatively avoids surprises near MAX_VALUE
+ digit = Character.digit((char)b[i++], radix);
+ if (digit < 0) {
+ throw new NumberFormatException("illegal number");
+ }
+ if (result < multmin) {
+ throw new NumberFormatException("illegal number");
+ }
+ result *= radix;
+ if (result < limit + digit) {
+ throw new NumberFormatException("illegal number");
+ }
+ result -= digit;
+ }
+ } else {
+ throw new NumberFormatException("illegal number");
+ }
+ if (negative) {
+ if (i > start + 1) {
+ return result;
+ } else { /* Only got "-" */
+ throw new NumberFormatException("illegal number");
+ }
+ } else {
+ return -result;
+ }
+ }
+
+ /**
+ * Convert the bytes within the specified range of the given byte
+ * array into a signed integer . The range extends from
+ * start
till, but not including end
.
+ *
+ * @param b the bytes
+ * @param start the first byte offset
+ * @param end the last byte offset
+ * @return the integer value
+ * @exception NumberFormatException for conversion errors
+ */
+ public static int parseInt(byte[] b, int start, int end)
+ throws NumberFormatException {
+ return parseInt(b, start, end, 10);
+ }
+
+ /**
+ * Convert the bytes within the specified range of the given byte
+ * array into a signed long in the given radix . The range extends
+ * from start
till, but not including end
.
+ *
+ * Based on java.lang.Long.parseLong()
+ *
+ * @param b the bytes
+ * @param start the first byte offset
+ * @param end the last byte offset
+ * @param radix the radix
+ * @return the long value
+ * @exception NumberFormatException for conversion errors
+ */
+ public static long parseLong(byte[] b, int start, int end, int radix)
+ throws NumberFormatException {
+ if (b == null)
+ throw new NumberFormatException("null");
+
+ long result = 0;
+ boolean negative = false;
+ int i = start;
+ long limit;
+ long multmin;
+ int digit;
+
+ if (end > start) {
+ if (b[i] == '-') {
+ negative = true;
+ limit = Long.MIN_VALUE;
+ i++;
+ } else {
+ limit = -Long.MAX_VALUE;
+ }
+ multmin = limit / radix;
+ if (i < end) {
+ digit = Character.digit((char)b[i++], radix);
+ if (digit < 0) {
+ throw new NumberFormatException(
+ "illegal number: " + toString(b, start, end)
+ );
+ } else {
+ result = -digit;
+ }
+ }
+ while (i < end) {
+ // Accumulating negatively avoids surprises near MAX_VALUE
+ digit = Character.digit((char)b[i++], radix);
+ if (digit < 0) {
+ throw new NumberFormatException("illegal number");
+ }
+ if (result < multmin) {
+ throw new NumberFormatException("illegal number");
+ }
+ result *= radix;
+ if (result < limit + digit) {
+ throw new NumberFormatException("illegal number");
+ }
+ result -= digit;
+ }
+ } else {
+ throw new NumberFormatException("illegal number");
+ }
+ if (negative) {
+ if (i > start + 1) {
+ return result;
+ } else { /* Only got "-" */
+ throw new NumberFormatException("illegal number");
+ }
+ } else {
+ return -result;
+ }
+ }
+
+ /**
+ * Convert the bytes within the specified range of the given byte
+ * array into a signed long . The range extends from
+ * start
till, but not including end
.
+ *
+ * @param b the bytes
+ * @param start the first byte offset
+ * @param end the last byte offset
+ * @return the long value
+ * @exception NumberFormatException for conversion errors
+ */
+ public static long parseLong(byte[] b, int start, int end)
+ throws NumberFormatException {
+ return parseLong(b, start, end, 10);
+ }
+
+ /**
+ * Convert the bytes within the specified range of the given byte
+ * array into a String. The range extends from start
+ * till, but not including end
.
+ *
+ * @param b the bytes
+ * @param start the first byte offset
+ * @param end the last byte offset
+ * @return the String
+ */
+ public static String toString(byte[] b, int start, int end) {
+ int size = end - start;
+ char[] theChars = new char[size];
+
+ for (int i = 0, j = start; i < size; )
+ theChars[i++] = (char)(b[j++]&0xff);
+
+ return new String(theChars);
+ }
+
+ /**
+ * Convert the bytes into a String.
+ *
+ * @param b the bytes
+ * @return the String
+ * @since JavaMail 1.4.4
+ */
+ public static String toString(byte[] b) {
+ return toString(b, 0, b.length);
+ }
+
+ public static String toString(ByteArrayInputStream is) {
+ int size = is.available();
+ char[] theChars = new char[size];
+ byte[] bytes = new byte[size];
+
+ is.read(bytes, 0, size);
+ for (int i = 0; i < size;)
+ theChars[i] = (char)(bytes[i++]&0xff);
+
+ return new String(theChars);
+ }
+
+
+ public static byte[] getBytes(String s) {
+ char [] chars= s.toCharArray();
+ int size = chars.length;
+ byte[] bytes = new byte[size];
+
+ for (int i = 0; i < size;)
+ bytes[i] = (byte) chars[i++];
+ return bytes;
+ }
+
+ public static byte[] getBytes(InputStream is) throws IOException {
+
+ int len;
+ int size = 1024;
+ byte [] buf;
+
+
+ if (is instanceof ByteArrayInputStream) {
+ size = is.available();
+ buf = new byte[size];
+ len = is.read(buf, 0, size);
+ }
+ else {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ buf = new byte[size];
+ while ((len = is.read(buf, 0, size)) != -1)
+ bos.write(buf, 0, len);
+ buf = bos.toByteArray();
+ }
+ return buf;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/BASE64DecoderStream.java b/app/src/main/java/com/sun/mail/util/BASE64DecoderStream.java
new file mode 100644
index 0000000000..2559c748a7
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/BASE64DecoderStream.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a BASE64 Decoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BASE64DecoderStream extends FilterInputStream {
+ // buffer of decoded bytes for single byte reads
+ private byte[] buffer = new byte[3];
+ private int bufsize = 0; // size of the cache
+ private int index = 0; // index into the cache
+
+ // buffer for almost 8K of typical 76 chars + CRLF lines,
+ // used by getByte method. this buffer contains encoded bytes.
+ private byte[] input_buffer = new byte[78*105];
+ private int input_pos = 0;
+ private int input_len = 0;;
+
+ private boolean ignoreErrors = false;
+
+ /**
+ * Create a BASE64 decoder that decodes the specified input stream.
+ * The System property mail.mime.base64.ignoreerrors
+ * controls whether errors in the encoded data cause an exception
+ * or are ignored. The default is false (errors cause exception).
+ *
+ * @param in the input stream
+ */
+ public BASE64DecoderStream(InputStream in) {
+ super(in);
+ // default to false
+ ignoreErrors = PropUtil.getBooleanSystemProperty(
+ "mail.mime.base64.ignoreerrors", false);
+ }
+
+ /**
+ * Create a BASE64 decoder that decodes the specified input stream.
+ *
+ * @param in the input stream
+ * @param ignoreErrors ignore errors in encoded data?
+ */
+ public BASE64DecoderStream(InputStream in, boolean ignoreErrors) {
+ super(in);
+ this.ignoreErrors = ignoreErrors;
+ }
+
+ /**
+ * Read the next decoded byte from this input stream. The byte
+ * is returned as an int
in the range 0
+ * to 255
. If no byte is available because the end of
+ * the stream has been reached, the value -1
is returned.
+ * This method blocks until input data is available, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return next byte of data, or -1
if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read() throws IOException {
+ if (index >= bufsize) {
+ bufsize = decode(buffer, 0, buffer.length);
+ if (bufsize <= 0) // buffer is empty
+ return -1;
+ index = 0; // reset index into buffer
+ }
+ return buffer[index++] & 0xff; // Zero off the MSB
+ }
+
+ /**
+ * Reads up to len
decoded bytes of data from this input stream
+ * into an array of bytes. This method blocks until some input is
+ * available.
+ *
+ *
+ * @param buf the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the stream has been reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ if (len == 0)
+ return 0;
+ // empty out single byte read buffer
+ int off0 = off;
+ while (index < bufsize && len > 0) {
+ buf[off++] = buffer[index++];
+ len--;
+ }
+ if (index >= bufsize)
+ bufsize = index = 0;
+
+ int bsize = (len / 3) * 3; // round down to multiple of 3 bytes
+ if (bsize > 0) {
+ int size = decode(buf, off, bsize);
+ off += size;
+ len -= size;
+
+ if (size != bsize) { // hit EOF?
+ if (off == off0) // haven't returned any data
+ return -1;
+ else // returned some data before hitting EOF
+ return off - off0;
+ }
+ }
+
+ // finish up with a partial read if necessary
+ for (; len > 0; len--) {
+ int c = read();
+ if (c == -1) // EOF
+ break;
+ buf[off++] = (byte)c;
+ }
+
+ if (off == off0) // haven't returned any data
+ return -1;
+ else // returned some data before hitting EOF
+ return off - off0;
+ }
+
+ /**
+ * Skips over and discards n bytes of data from this stream.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ long skipped = 0;
+ while (n-- > 0 && read() >= 0)
+ skipped++;
+ return skipped;
+ }
+
+ /**
+ * Tests if this input stream supports marks. Currently this class
+ * does not support marks
+ */
+ @Override
+ public boolean markSupported() {
+ return false; // Maybe later ..
+ }
+
+ /**
+ * Returns the number of bytes that can be read from this input
+ * stream without blocking. However, this figure is only
+ * a close approximation in case the original encoded stream
+ * contains embedded CRLFs; since the CRLFs are discarded, not decoded
+ */
+ @Override
+ public int available() throws IOException {
+ // This is only an estimate, since in.available()
+ // might include CRLFs too ..
+ return ((in.available() * 3)/4 + (bufsize-index));
+ }
+
+ /**
+ * This character array provides the character to value map
+ * based on RFC1521.
+ */
+ private final static char pem_array[] = {
+ 'A','B','C','D','E','F','G','H', // 0
+ 'I','J','K','L','M','N','O','P', // 1
+ 'Q','R','S','T','U','V','W','X', // 2
+ 'Y','Z','a','b','c','d','e','f', // 3
+ 'g','h','i','j','k','l','m','n', // 4
+ 'o','p','q','r','s','t','u','v', // 5
+ 'w','x','y','z','0','1','2','3', // 6
+ '4','5','6','7','8','9','+','/' // 7
+ };
+
+ private final static byte pem_convert_array[] = new byte[256];
+
+ static {
+ for (int i = 0; i < 255; i++)
+ pem_convert_array[i] = -1;
+ for (int i = 0; i < pem_array.length; i++)
+ pem_convert_array[pem_array[i]] = (byte)i;
+ }
+
+ /**
+ * The decoder algorithm. Most of the complexity here is dealing
+ * with error cases. Returns the number of bytes decoded, which
+ * may be zero. Decoding is done by filling an int with 4 6-bit
+ * values by shifting them in from the bottom and then extracting
+ * 3 8-bit bytes from the int by shifting them out from the bottom.
+ *
+ * @param outbuf the buffer into which to put the decoded bytes
+ * @param pos position in the buffer to start filling
+ * @param len the number of bytes to fill
+ * @return the number of bytes filled, always a multiple
+ * of three, and may be zero
+ * @exception IOException if the data is incorrectly formatted
+ */
+ private int decode(byte[] outbuf, int pos, int len) throws IOException {
+ int pos0 = pos;
+ while (len >= 3) {
+ /*
+ * We need 4 valid base64 characters before we start decoding.
+ * We skip anything that's not a valid base64 character (usually
+ * just CRLF).
+ */
+ int got = 0;
+ int val = 0;
+ while (got < 4) {
+ int i = getByte();
+ if (i == -1 || i == -2) {
+ boolean atEOF;
+ if (i == -1) {
+ if (got == 0)
+ return pos - pos0;
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "BASE64Decoder: Error in encoded stream: " +
+ "needed 4 valid base64 characters " +
+ "but only got " + got + " before EOF" +
+ recentChars());
+ atEOF = true; // don't read any more
+ } else { // i == -2
+ // found a padding character, we're at EOF
+ // XXX - should do something to make EOF "sticky"
+ if (got < 2 && !ignoreErrors)
+ throw new DecodingException(
+ "BASE64Decoder: Error in encoded stream: " +
+ "needed at least 2 valid base64 characters," +
+ " but only got " + got +
+ " before padding character (=)" +
+ recentChars());
+
+ // didn't get any characters before padding character?
+ if (got == 0)
+ return pos - pos0;
+ atEOF = false; // need to keep reading
+ }
+
+ // pad partial result with zeroes
+
+ // how many bytes will we produce on output?
+ // (got always < 4, so size always < 3)
+ int size = got - 1;
+ if (size == 0)
+ size = 1;
+
+ // handle the one padding character we've seen
+ got++;
+ val <<= 6;
+
+ while (got < 4) {
+ if (!atEOF) {
+ // consume the rest of the padding characters,
+ // filling with zeroes
+ i = getByte();
+ if (i == -1) {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "BASE64Decoder: Error in encoded " +
+ "stream: hit EOF while looking for " +
+ "padding characters (=)" +
+ recentChars());
+ } else if (i != -2) {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "BASE64Decoder: Error in encoded " +
+ "stream: found valid base64 " +
+ "character after a padding character " +
+ "(=)" + recentChars());
+ }
+ }
+ val <<= 6;
+ got++;
+ }
+
+ // now pull out however many valid bytes we got
+ val >>= 8; // always skip first one
+ if (size == 2)
+ outbuf[pos + 1] = (byte)(val & 0xff);
+ val >>= 8;
+ outbuf[pos] = (byte)(val & 0xff);
+ // len -= size; // not needed, return below
+ pos += size;
+ return pos - pos0;
+ } else {
+ // got a valid byte
+ val <<= 6;
+ got++;
+ val |= i;
+ }
+ }
+
+ // read 4 valid characters, now extract 3 bytes
+ outbuf[pos + 2] = (byte)(val & 0xff);
+ val >>= 8;
+ outbuf[pos + 1] = (byte)(val & 0xff);
+ val >>= 8;
+ outbuf[pos] = (byte)(val & 0xff);
+ len -= 3;
+ pos += 3;
+ }
+ return pos - pos0;
+ }
+
+ /**
+ * Read the next valid byte from the input stream.
+ * Buffer lots of data from underlying stream in input_buffer,
+ * for efficiency.
+ *
+ * @return the next byte, -1 on EOF, or -2 if next byte is '='
+ * (padding at end of encoded data)
+ */
+ private int getByte() throws IOException {
+ int c;
+ do {
+ if (input_pos >= input_len) {
+ try {
+ input_len = in.read(input_buffer);
+ } catch (EOFException ex) {
+ return -1;
+ }
+ if (input_len <= 0)
+ return -1;
+ input_pos = 0;
+ }
+ // get the next byte in the buffer
+ c = input_buffer[input_pos++] & 0xff;
+ // is it a padding byte?
+ if (c == '=')
+ return -2;
+ // no, convert it
+ c = pem_convert_array[c];
+ // loop until we get a legitimate byte
+ } while (c == -1);
+ return c;
+ }
+
+ /**
+ * Return the most recent characters, for use in an error message.
+ */
+ private String recentChars() {
+ // reach into the input buffer and extract up to 10
+ // recent characters, to help in debugging.
+ String errstr = "";
+ int nc = input_pos > 10 ? 10 : input_pos;
+ if (nc > 0) {
+ errstr += ", the " + nc +
+ " most recent characters were: \"";
+ for (int k = input_pos - nc; k < input_pos; k++) {
+ char c = (char)(input_buffer[k] & 0xff);
+ switch (c) {
+ case '\r': errstr += "\\r"; break;
+ case '\n': errstr += "\\n"; break;
+ case '\t': errstr += "\\t"; break;
+ default:
+ if (c >= ' ' && c < 0177)
+ errstr += c;
+ else
+ errstr += ("\\" + (int)c);
+ }
+ }
+ errstr += "\"";
+ }
+ return errstr;
+ }
+
+ /**
+ * Base64 decode a byte array. No line breaks are allowed.
+ * This method is suitable for short strings, such as those
+ * in the IMAP AUTHENTICATE protocol, but not to decode the
+ * entire content of a MIME part.
+ *
+ * NOTE: inbuf may only contain valid base64 characters.
+ * Whitespace is not ignored.
+ *
+ * @param inbuf the byte array
+ * @return the decoded byte array
+ */
+ public static byte[] decode(byte[] inbuf) {
+ int size = (inbuf.length / 4) * 3;
+ if (size == 0)
+ return inbuf;
+
+ if (inbuf[inbuf.length - 1] == '=') {
+ size--;
+ if (inbuf[inbuf.length - 2] == '=')
+ size--;
+ }
+ byte[] outbuf = new byte[size];
+
+ int inpos = 0, outpos = 0;
+ size = inbuf.length;
+ while (size > 0) {
+ int val;
+ int osize = 3;
+ val = pem_convert_array[inbuf[inpos++] & 0xff];
+ val <<= 6;
+ val |= pem_convert_array[inbuf[inpos++] & 0xff];
+ val <<= 6;
+ if (inbuf[inpos] != '=') // End of this BASE64 encoding
+ val |= pem_convert_array[inbuf[inpos++] & 0xff];
+ else
+ osize--;
+ val <<= 6;
+ if (inbuf[inpos] != '=') // End of this BASE64 encoding
+ val |= pem_convert_array[inbuf[inpos++] & 0xff];
+ else
+ osize--;
+ if (osize > 2)
+ outbuf[outpos + 2] = (byte)(val & 0xff);
+ val >>= 8;
+ if (osize > 1)
+ outbuf[outpos + 1] = (byte)(val & 0xff);
+ val >>= 8;
+ outbuf[outpos] = (byte)(val & 0xff);
+ outpos += osize;
+ size -= 4;
+ }
+ return outbuf;
+ }
+
+ /*** begin TEST program ***
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ BASE64DecoderStream decoder = new BASE64DecoderStream(infile);
+ int c;
+
+ while ((c = decoder.read()) != -1)
+ System.out.print((char)c);
+ System.out.flush();
+ }
+ *** end TEST program ***/
+}
diff --git a/app/src/main/java/com/sun/mail/util/BASE64EncoderStream.java b/app/src/main/java/com/sun/mail/util/BASE64EncoderStream.java
new file mode 100644
index 0000000000..036b534821
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/BASE64EncoderStream.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a BASE64 encoder. It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter. The encoding
+ * is done as the bytes are written out.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BASE64EncoderStream extends FilterOutputStream {
+ private byte[] buffer; // cache of bytes that are yet to be encoded
+ private int bufsize = 0; // size of the cache
+ private byte[] outbuf; // line size output buffer
+ private int count = 0; // number of bytes that have been output
+ private int bytesPerLine; // number of bytes per line
+ private int lineLimit; // number of input bytes to output bytesPerLine
+ private boolean noCRLF = false;
+
+ private static byte[] newline = new byte[] { '\r', '\n' };
+
+ /**
+ * Create a BASE64 encoder that encodes the specified output stream.
+ *
+ * @param out the output stream
+ * @param bytesPerLine number of bytes per line. The encoder inserts
+ * a CRLF sequence after the specified number of bytes,
+ * unless bytesPerLine is Integer.MAX_VALUE, in which
+ * case no CRLF is inserted. bytesPerLine is rounded
+ * down to a multiple of 4.
+ */
+ public BASE64EncoderStream(OutputStream out, int bytesPerLine) {
+ super(out);
+ buffer = new byte[3];
+ if (bytesPerLine == Integer.MAX_VALUE || bytesPerLine < 4) {
+ noCRLF = true;
+ bytesPerLine = 76;
+ }
+ bytesPerLine = (bytesPerLine / 4) * 4; // Rounded down to 4n
+ this.bytesPerLine = bytesPerLine; // save it
+ lineLimit = bytesPerLine / 4 * 3;
+
+ if (noCRLF) {
+ outbuf = new byte[bytesPerLine];
+ } else {
+ outbuf = new byte[bytesPerLine + 2];
+ outbuf[bytesPerLine] = (byte)'\r';
+ outbuf[bytesPerLine + 1] = (byte)'\n';
+ }
+ }
+
+ /**
+ * Create a BASE64 encoder that encodes the specified input stream.
+ * Inserts the CRLF sequence after outputting 76 bytes.
+ *
+ * @param out the output stream
+ */
+ public BASE64EncoderStream(OutputStream out) {
+ this(out, 76);
+ }
+
+ /**
+ * Encodes len
bytes from the specified
+ * byte
array starting at offset off
to
+ * this output stream.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public synchronized void write(byte[] b, int off, int len)
+ throws IOException {
+ int end = off + len;
+
+ // finish off incomplete coding unit
+ while (bufsize != 0 && off < end)
+ write(b[off++]);
+
+ // finish off line
+ int blen = ((bytesPerLine - count) / 4) * 3;
+ if (off + blen <= end) {
+ // number of bytes that will be produced in outbuf
+ int outlen = encodedSize(blen);
+ if (!noCRLF) {
+ outbuf[outlen++] = (byte)'\r';
+ outbuf[outlen++] = (byte)'\n';
+ }
+ out.write(encode(b, off, blen, outbuf), 0, outlen);
+ off += blen;
+ count = 0;
+ }
+
+ // do bulk encoding a line at a time.
+ for (; off + lineLimit <= end; off += lineLimit)
+ out.write(encode(b, off, lineLimit, outbuf));
+
+ // handle remaining partial line
+ if (off + 3 <= end) {
+ blen = end - off;
+ blen = (blen / 3) * 3; // round down
+ // number of bytes that will be produced in outbuf
+ int outlen = encodedSize(blen);
+ out.write(encode(b, off, blen, outbuf), 0, outlen);
+ off += blen;
+ count += outlen;
+ }
+
+ // start next coding unit
+ for (; off < end; off++)
+ write(b[off]);
+ }
+
+ /**
+ * Encodes b.length
bytes to this output stream.
+ *
+ * @param b the data to be written.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ /**
+ * Encodes the specified byte
to this output stream.
+ *
+ * @param c the byte
.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public synchronized void write(int c) throws IOException {
+ buffer[bufsize++] = (byte)c;
+ if (bufsize == 3) { // Encoding unit = 3 bytes
+ encode();
+ bufsize = 0;
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be encoded out to the stream.
+ *
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public synchronized void flush() throws IOException {
+ if (bufsize > 0) { // If there's unencoded characters in the buffer ..
+ encode(); // .. encode them
+ bufsize = 0;
+ }
+ out.flush();
+ }
+
+ /**
+ * Forces any buffered output bytes to be encoded out to the stream
+ * and closes this output stream
+ */
+ @Override
+ public synchronized void close() throws IOException {
+ flush();
+ if (count > 0 && !noCRLF) {
+ out.write(newline);
+ out.flush();
+ }
+ out.close();
+ }
+
+ /** This array maps the characters to their 6 bit values */
+ private final static char pem_array[] = {
+ 'A','B','C','D','E','F','G','H', // 0
+ 'I','J','K','L','M','N','O','P', // 1
+ 'Q','R','S','T','U','V','W','X', // 2
+ 'Y','Z','a','b','c','d','e','f', // 3
+ 'g','h','i','j','k','l','m','n', // 4
+ 'o','p','q','r','s','t','u','v', // 5
+ 'w','x','y','z','0','1','2','3', // 6
+ '4','5','6','7','8','9','+','/' // 7
+ };
+
+ /**
+ * Encode the data stored in buffer
.
+ * Uses outbuf
to store the encoded
+ * data before writing.
+ *
+ * @exception IOException if an I/O error occurs.
+ */
+ private void encode() throws IOException {
+ int osize = encodedSize(bufsize);
+ out.write(encode(buffer, 0, bufsize, outbuf), 0, osize);
+ // increment count
+ count += osize;
+ // If writing out this encoded unit caused overflow,
+ // start a new line.
+ if (count >= bytesPerLine) {
+ if (!noCRLF)
+ out.write(newline);
+ count = 0;
+ }
+ }
+
+ /**
+ * Base64 encode a byte array. No line breaks are inserted.
+ * This method is suitable for short strings, such as those
+ * in the IMAP AUTHENTICATE protocol, but not to encode the
+ * entire content of a MIME part.
+ *
+ * @param inbuf the byte array
+ * @return the encoded byte array
+ */
+ public static byte[] encode(byte[] inbuf) {
+ if (inbuf.length == 0)
+ return inbuf;
+ return encode(inbuf, 0, inbuf.length, null);
+ }
+
+ /**
+ * Internal use only version of encode. Allow specifying which
+ * part of the input buffer to encode. If outbuf is non-null,
+ * it's used as is. Otherwise, a new output buffer is allocated.
+ */
+ private static byte[] encode(byte[] inbuf, int off, int size,
+ byte[] outbuf) {
+ if (outbuf == null)
+ outbuf = new byte[encodedSize(size)];
+ int inpos, outpos;
+ int val;
+ for (inpos = off, outpos = 0; size >= 3; size -= 3, outpos += 4) {
+ val = inbuf[inpos++] & 0xff;
+ val <<= 8;
+ val |= inbuf[inpos++] & 0xff;
+ val <<= 8;
+ val |= inbuf[inpos++] & 0xff;
+ outbuf[outpos+3] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+2] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+ }
+ // done with groups of three, finish up any odd bytes left
+ if (size == 1) {
+ val = inbuf[inpos++] & 0xff;
+ val <<= 4;
+ outbuf[outpos+3] = (byte)'='; // pad character;
+ outbuf[outpos+2] = (byte)'='; // pad character;
+ outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+ } else if (size == 2) {
+ val = inbuf[inpos++] & 0xff;
+ val <<= 8;
+ val |= inbuf[inpos++] & 0xff;
+ val <<= 2;
+ outbuf[outpos+3] = (byte)'='; // pad character;
+ outbuf[outpos+2] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+ val >>= 6;
+ outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+ }
+ return outbuf;
+ }
+
+ /**
+ * Return the corresponding encoded size for the given number
+ * of bytes, not including any CRLF.
+ */
+ private static int encodedSize(int size) {
+ return ((size + 2) / 3) * 4;
+ }
+
+ /*** begin TEST program
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ BASE64EncoderStream encoder = new BASE64EncoderStream(System.out);
+ int c;
+
+ while ((c = infile.read()) != -1)
+ encoder.write(c);
+ encoder.close();
+ }
+ *** end TEST program **/
+}
diff --git a/app/src/main/java/com/sun/mail/util/BEncoderStream.java b/app/src/main/java/com/sun/mail/util/BEncoderStream.java
new file mode 100644
index 0000000000..5a43dd688c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/BEncoderStream.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a 'B' Encoder as defined by RFC2047 for
+ * encoding MIME headers. It subclasses the BASE64EncoderStream
+ * class.
+ *
+ * @author John Mani
+ */
+
+public class BEncoderStream extends BASE64EncoderStream {
+
+ /**
+ * Create a 'B' encoder that encodes the specified input stream.
+ * @param out the output stream
+ */
+ public BEncoderStream(OutputStream out) {
+ super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should
+ // suffice (!) to indicate that
+ // CRLFs should not be inserted
+ }
+
+ /**
+ * Returns the length of the encoded version of this byte array.
+ *
+ * @param b the byte array
+ * @return the length
+ */
+ public static int encodedLength(byte[] b) {
+ return ((b.length + 2)/3) * 4;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/CRLFOutputStream.java b/app/src/main/java/com/sun/mail/util/CRLFOutputStream.java
new file mode 100644
index 0000000000..7cca3d7cc8
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/CRLFOutputStream.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+
+/**
+ * Convert lines into the canonical format, that is, terminate lines with the
+ * CRLF sequence.
+ *
+ * @author John Mani
+ */
+public class CRLFOutputStream extends FilterOutputStream {
+ protected int lastb = -1;
+ protected boolean atBOL = true; // at beginning of line?
+ private static final byte[] newline = { (byte)'\r', (byte)'\n' };
+
+ public CRLFOutputStream(OutputStream os) {
+ super(os);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (b == '\r') {
+ writeln();
+ } else if (b == '\n') {
+ if (lastb != '\r')
+ writeln();
+ } else {
+ out.write(b);
+ atBOL = false;
+ }
+ lastb = b;
+ }
+
+ @Override
+ public void write(byte b[]) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ int start = off;
+
+ len += off;
+ for (int i = start; i < len ; i++) {
+ if (b[i] == '\r') {
+ out.write(b, start, i - start);
+ writeln();
+ start = i + 1;
+ } else if (b[i] == '\n') {
+ if (lastb != '\r') {
+ out.write(b, start, i - start);
+ writeln();
+ }
+ start = i + 1;
+ }
+ lastb = b[i];
+ }
+ if ((len - start) > 0) {
+ out.write(b, start, len - start);
+ atBOL = false;
+ }
+ }
+
+ /*
+ * Just write out a new line, something similar to out.println()
+ */
+ public void writeln() throws IOException {
+ out.write(newline);
+ atBOL = true;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/DecodingException.java b/app/src/main/java/com/sun/mail/util/DecodingException.java
new file mode 100644
index 0000000000..fddd84f8fc
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/DecodingException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * A special IOException that indicates a failure to decode data due
+ * to an error in the formatting of the data. This allows applications
+ * to distinguish decoding errors from other I/O errors.
+ *
+ * @author Bill Shannon
+ */
+
+public class DecodingException extends IOException {
+
+ private static final long serialVersionUID = -6913647794421459390L;
+
+ /**
+ * Constructor.
+ *
+ * @param s the exception message
+ */
+ public DecodingException(String s) {
+ super(s);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/DefaultProvider.java b/app/src/main/java/com/sun/mail/util/DefaultProvider.java
new file mode 100644
index 0000000000..c47e128782
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/DefaultProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to mark the default providers that are part of Jakarta Mail.
+ * DO NOT use this on any provider made available independently.
+ *
+ * @author Bill Shannon
+ * @since Jakarta Mail 1.6.4
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DefaultProvider {
+}
diff --git a/app/src/main/java/com/sun/mail/util/FolderClosedIOException.java b/app/src/main/java/com/sun/mail/util/FolderClosedIOException.java
new file mode 100644
index 0000000000..5a4ef10ae4
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/FolderClosedIOException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+import javax.mail.Folder;
+
+/**
+ * A variant of FolderClosedException that can be thrown from methods
+ * that only throw IOException. The getContent method will catch this
+ * exception and translate it back to FolderClosedException.
+ *
+ * @author Bill Shannon
+ */
+
+public class FolderClosedIOException extends IOException {
+ transient private Folder folder;
+
+ private static final long serialVersionUID = 4281122580365555735L;
+
+ /**
+ * Constructor
+ * @param folder the Folder
+ */
+ public FolderClosedIOException(Folder folder) {
+ this(folder, null);
+ }
+
+ /**
+ * Constructor
+ * @param folder the Folder
+ * @param message the detailed error message
+ */
+ public FolderClosedIOException(Folder folder, String message) {
+ super(message);
+ this.folder = folder;
+ }
+
+ /**
+ * Returns the dead Folder object
+ *
+ * @return the dead Folder
+ */
+ public Folder getFolder() {
+ return folder;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/LineInputStream.java b/app/src/main/java/com/sun/mail/util/LineInputStream.java
new file mode 100644
index 0000000000..cb068f3ffb
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/LineInputStream.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.CharacterCodingException;
+
+/**
+ * LineInputStream supports reading CRLF terminated lines that
+ * contain only US-ASCII characters from an input stream. Provides
+ * functionality that is similar to the deprecated
+ * DataInputStream.readLine()
. Expected use is to read
+ * lines as String objects from an IMAP/SMTP/etc. stream.
+ *
+ * This class also supports UTF-8 data by calling the appropriate
+ * constructor. Or, if the System property mail.mime.allowutf8
+ * is set to true, an attempt will be made to interpret the data as UTF-8,
+ * falling back to treating it as an 8-bit charset if that fails.
+ *
+ * LineInputStream is implemented as a FilterInputStream, so one can just
+ * wrap it around any input stream and read bytes from this filter.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class LineInputStream extends FilterInputStream {
+
+ private boolean allowutf8;
+ private byte[] lineBuffer = null; // reusable byte buffer
+ private CharsetDecoder decoder;
+
+ private static boolean defaultutf8 =
+ PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", false);
+ private static int MAX_INCR = 1024*1024; // 1MB
+
+ public LineInputStream(InputStream in) {
+ this(in, false);
+ }
+
+ /**
+ * @param in the InputStream
+ * @param allowutf8 allow UTF-8 characters?
+ * @since JavaMail 1.6
+ */
+ public LineInputStream(InputStream in, boolean allowutf8) {
+ super(in);
+ this.allowutf8 = allowutf8;
+ if (!allowutf8 && defaultutf8) {
+ decoder = StandardCharsets.UTF_8.newDecoder();
+ decoder.onMalformedInput(CodingErrorAction.REPORT);
+ decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+ }
+ }
+
+ /**
+ * Read a line containing only ASCII characters from the input
+ * stream. A line is terminated by a CR or NL or CR-NL sequence.
+ * A common error is a CR-CR-NL sequence, which will also terminate
+ * a line.
+ * The line terminator is not returned as part of the returned
+ * String. Returns null if no data is available.
+ *
+ * This class is similar to the deprecated
+ * DataInputStream.readLine()
+ *
+ * @return the line
+ * @exception IOException for I/O errors
+ */
+ @SuppressWarnings("deprecation") // for old String constructor
+ public String readLine() throws IOException {
+ //InputStream in = this.in;
+ byte[] buf = lineBuffer;
+
+ if (buf == null)
+ buf = lineBuffer = new byte[128];
+
+ int c1;
+ int room = buf.length;
+ int offset = 0;
+
+ while ((c1 = in.read()) != -1) {
+ if (c1 == '\n') // Got NL, outa here.
+ break;
+ else if (c1 == '\r') {
+ // Got CR, is the next char NL ?
+ boolean twoCRs = false;
+ if (in.markSupported())
+ in.mark(2);
+ int c2 = in.read();
+ if (c2 == '\r') { // discard extraneous CR
+ twoCRs = true;
+ c2 = in.read();
+ }
+ if (c2 != '\n') {
+ /*
+ * If the stream supports it (which we hope will always
+ * be the case), reset to after the first CR. Otherwise,
+ * we wrap a PushbackInputStream around the stream so we
+ * can unread the characters we don't need. The only
+ * problem with that is that the caller might stop
+ * reading from this LineInputStream, throw it away,
+ * and then start reading from the underlying stream.
+ * If that happens, the pushed back characters will be
+ * lost forever.
+ */
+ if (in.markSupported())
+ in.reset();
+ else {
+ if (!(in instanceof PushbackInputStream))
+ in /*= this.in*/ = new PushbackInputStream(in, 2);
+ if (c2 != -1)
+ ((PushbackInputStream)in).unread(c2);
+ if (twoCRs)
+ ((PushbackInputStream)in).unread('\r');
+ }
+ }
+ break; // outa here.
+ }
+
+ // Not CR, NL or CR-NL ...
+ // .. Insert the byte into our byte buffer
+ if (--room < 0) { // No room, need to grow.
+ if (buf.length < MAX_INCR)
+ buf = new byte[buf.length * 2];
+ else
+ buf = new byte[buf.length + MAX_INCR];
+ room = buf.length - offset - 1;
+ System.arraycopy(lineBuffer, 0, buf, 0, offset);
+ lineBuffer = buf;
+ }
+ buf[offset++] = (byte)c1;
+ }
+
+ if ((c1 == -1) && (offset == 0))
+ return null;
+
+ if (allowutf8)
+ return new String(buf, 0, offset, StandardCharsets.UTF_8);
+ else {
+ if (defaultutf8) {
+ // try to decode it as UTF-8
+ try {
+ return decoder.decode(ByteBuffer.wrap(buf, 0, offset)).
+ toString();
+ } catch (CharacterCodingException cex) {
+ // looks like it's not valid UTF-8 data,
+ // fall through and treat it as an 8-bit charset
+ }
+ }
+ return new String(buf, 0, 0, offset);
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/LineOutputStream.java b/app/src/main/java/com/sun/mail/util/LineOutputStream.java
new file mode 100644
index 0000000000..d86f5ec22a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/LineOutputStream.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class is to support writing out Strings as a sequence of bytes
+ * terminated by a CRLF sequence. The String must contain only US-ASCII
+ * characters.
+ *
+ * The expected use is to write out RFC822 style headers to an output
+ * stream.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class LineOutputStream extends FilterOutputStream {
+ private boolean allowutf8;
+
+ private static byte[] newline;
+
+ static {
+ newline = new byte[2];
+ newline[0] = (byte)'\r';
+ newline[1] = (byte)'\n';
+ }
+
+ public LineOutputStream(OutputStream out) {
+ this(out, false);
+ }
+
+ /**
+ * @param out the OutputStream
+ * @param allowutf8 allow UTF-8 characters?
+ * @since JavaMail 1.6
+ */
+ public LineOutputStream(OutputStream out, boolean allowutf8) {
+ super(out);
+ this.allowutf8 = allowutf8;
+ }
+
+ public void writeln(String s) throws IOException {
+ byte[] bytes;
+ if (allowutf8)
+ bytes = s.getBytes(StandardCharsets.UTF_8);
+ else
+ bytes = ASCIIUtility.getBytes(s);
+ out.write(bytes);
+ out.write(newline);
+ }
+
+ public void writeln() throws IOException {
+ out.write(newline);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/LogOutputStream.java b/app/src/main/java/com/sun/mail/util/LogOutputStream.java
new file mode 100644
index 0000000000..c1c31531f9
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/LogOutputStream.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2008, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.logging.Level;
+
+/**
+ * Capture output lines and send them to the mail logger.
+ */
+public class LogOutputStream extends OutputStream {
+ protected MailLogger logger;
+ protected Level level;
+
+ private int lastb = -1;
+ private byte[] buf = new byte[80];
+ private int pos = 0;
+
+ /**
+ * Log to the specified logger.
+ *
+ * @param logger the MailLogger
+ */
+ public LogOutputStream(MailLogger logger) {
+ this.logger = logger;
+ this.level = Level.FINEST;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (!logger.isLoggable(level))
+ return;
+
+ if (b == '\r') {
+ logBuf();
+ } else if (b == '\n') {
+ if (lastb != '\r')
+ logBuf();
+ } else {
+ expandCapacity(1);
+ buf[pos++] = (byte)b;
+ }
+ lastb = b;
+ }
+
+ @Override
+ public void write(byte b[]) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ int start = off;
+
+ if (!logger.isLoggable(level))
+ return;
+ len += off;
+ for (int i = start; i < len ; i++) {
+ if (b[i] == '\r') {
+ expandCapacity(i - start);
+ System.arraycopy(b, start, buf, pos, i - start);
+ pos += i - start;
+ logBuf();
+ start = i + 1;
+ } else if (b[i] == '\n') {
+ if (lastb != '\r') {
+ expandCapacity(i - start);
+ System.arraycopy(b, start, buf, pos, i - start);
+ pos += i - start;
+ logBuf();
+ }
+ start = i + 1;
+ }
+ lastb = b[i];
+ }
+ if ((len - start) > 0) {
+ expandCapacity(len - start);
+ System.arraycopy(b, start, buf, pos, len - start);
+ pos += len - start;
+ }
+ }
+
+ /**
+ * Log the specified message.
+ * Can be overridden by subclass to do different logging.
+ *
+ * @param msg the message to log
+ */
+ protected void log(String msg) {
+ logger.log(level, msg);
+ }
+
+ /**
+ * Convert the buffer to a string and log it.
+ */
+ private void logBuf() {
+ String msg = new String(buf, 0, pos);
+ pos = 0;
+ log(msg);
+ }
+
+ /**
+ * Ensure that the buffer can hold at least len bytes
+ * beyond the current position.
+ */
+ private void expandCapacity(int len) {
+ while (pos + len > buf.length) {
+ byte[] nb = new byte[buf.length * 2];
+ System.arraycopy(buf, 0, nb, 0, pos);
+ buf = nb;
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/MailConnectException.java b/app/src/main/java/com/sun/mail/util/MailConnectException.java
new file mode 100644
index 0000000000..768e90f4f7
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/MailConnectException.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import javax.mail.MessagingException;
+
+/**
+ * A MessagingException that indicates a socket connection attempt failed.
+ * Unlike java.net.ConnectException, it includes details of what we
+ * were trying to connect to. The underlying exception is available
+ * as the "cause" of this exception.
+ *
+ * @see java.net.ConnectException
+ * @author Bill Shannon
+ * @since JavaMail 1.5.0
+ */
+
+public class MailConnectException extends MessagingException {
+ private String host;
+ private int port;
+ private int cto;
+
+ private static final long serialVersionUID = -3818807731125317729L;
+
+ /**
+ * Constructs a MailConnectException.
+ *
+ * @param cex the SocketConnectException with the details
+ */
+ public MailConnectException(SocketConnectException cex) {
+ super(
+ "Couldn't connect to host, port: " +
+ cex.getHost() + ", " + cex.getPort() +
+ "; timeout " + cex.getConnectionTimeout() +
+ (cex.getMessage() != null ? ("; " + cex.getMessage()) : ""));
+ // extract the details and save them here
+ this.host = cex.getHost();
+ this.port = cex.getPort();
+ this.cto = cex.getConnectionTimeout();
+ setNextException(cex.getException());
+ }
+
+ /**
+ * The host we were trying to connect to.
+ *
+ * @return the host
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * The port we were trying to connect to.
+ *
+ * @return the port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * The timeout used for the connection attempt.
+ *
+ * @return the connection timeout
+ */
+ public int getConnectionTimeout() {
+ return cto;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/MailLogger.java b/app/src/main/java/com/sun/mail/util/MailLogger.java
new file mode 100644
index 0000000000..686f5cab6f
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/MailLogger.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.mail.Session;
+
+/**
+ * A simplified logger used by Jakarta Mail to handle logging to a
+ * PrintStream and logging through a java.util.logging.Logger.
+ * If debug is set, messages are written to the PrintStream and
+ * prefixed by the specified prefix (which is not included in
+ * Logger messages).
+ * Messages are logged by the Logger based on the configuration
+ * of the logging system.
+ */
+
+/*
+ * It would be so much simpler to just subclass Logger and override
+ * the log(LogRecord) method, as the javadocs suggest, but that doesn't
+ * work because Logger makes the decision about whether to log the message
+ * or not before calling the log(LogRecord) method. Instead, we just
+ * provide the few log methods we need here.
+ */
+
+public final class MailLogger {
+ /**
+ * For log messages.
+ */
+ private final Logger logger;
+ /**
+ * For debug output.
+ */
+ private final String prefix;
+ /**
+ * Produce debug output?
+ */
+ private final boolean debug;
+ /**
+ * Stream for debug output.
+ */
+ private final PrintStream out;
+
+ /**
+ * Construct a new MailLogger using the specified Logger name,
+ * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+ *
+ * @param name the Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @param debug if true, write to PrintStream
+ * @param out the PrintStream to write to
+ */
+ public MailLogger(String name, String prefix, boolean debug,
+ PrintStream out) {
+ logger = Logger.getLogger(name);
+ this.prefix = prefix;
+ this.debug = debug;
+ this.out = out != null ? out : System.out;
+ }
+
+ /**
+ * Construct a new MailLogger using the specified class' package
+ * name as the Logger name,
+ * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+ *
+ * @param clazz the Logger name is the package name of this class
+ * @param prefix the prefix for debug output, or null for none
+ * @param debug if true, write to PrintStream
+ * @param out the PrintStream to write to
+ */
+ public MailLogger(Class> clazz, String prefix, boolean debug,
+ PrintStream out) {
+ String name = packageOf(clazz);
+ logger = Logger.getLogger(name);
+ this.prefix = prefix;
+ this.debug = debug;
+ this.out = out != null ? out : System.out;
+ }
+
+ /**
+ * Construct a new MailLogger using the specified class' package
+ * name combined with the specified subname as the Logger name,
+ * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+ *
+ * @param clazz the Logger name is the package name of this class
+ * @param subname the Logger name relative to this Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @param debug if true, write to PrintStream
+ * @param out the PrintStream to write to
+ */
+ public MailLogger(Class> clazz, String subname, String prefix, boolean debug,
+ PrintStream out) {
+ String name = packageOf(clazz) + "." + subname;
+ logger = Logger.getLogger(name);
+ this.prefix = prefix;
+ this.debug = debug;
+ this.out = out != null ? out : System.out;
+ }
+
+ /**
+ * Construct a new MailLogger using the specified Logger name and
+ * debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
+ * from the Session.
+ *
+ * @param name the Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @param session where to get the debug flag and PrintStream
+ */
+ @Deprecated
+ public MailLogger(String name, String prefix, Session session) {
+ this(name, prefix, session.getDebug(), session.getDebugOut());
+ }
+
+ /**
+ * Construct a new MailLogger using the specified class' package
+ * name as the Logger name and the specified
+ * debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
+ * from the Session.
+ *
+ * @param clazz the Logger name is the package name of this class
+ * @param prefix the prefix for debug output, or null for none
+ * @param session where to get the debug flag and PrintStream
+ */
+ @Deprecated
+ public MailLogger(Class> clazz, String prefix, Session session) {
+ this(clazz, prefix, session.getDebug(), session.getDebugOut());
+ }
+
+ /**
+ * Create a MailLogger that uses a Logger with the specified name
+ * and prefix. The new MailLogger uses the same debug flag and
+ * PrintStream as this MailLogger.
+ *
+ * @param name the Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @return a MailLogger for the given name and prefix.
+ */
+ public MailLogger getLogger(String name, String prefix) {
+ return new MailLogger(name, prefix, debug, out);
+ }
+
+ /**
+ * Create a MailLogger using the specified class' package
+ * name as the Logger name and the specified prefix.
+ * The new MailLogger uses the same debug flag and
+ * PrintStream as this MailLogger.
+ *
+ * @param clazz the Logger name is the package name of this class
+ * @param prefix the prefix for debug output, or null for none
+ * @return a MailLogger for the given name and prefix.
+ */
+ public MailLogger getLogger(Class> clazz, String prefix) {
+ return new MailLogger(clazz, prefix, debug, out);
+ }
+
+ /**
+ * Create a MailLogger that uses a Logger whose name is composed
+ * of this MailLogger's name plus the specified sub-name, separated
+ * by a dot. The new MailLogger uses the new prefix for debug output.
+ * This is used primarily by the protocol trace code that wants a
+ * different prefix (none).
+ *
+ * @param subname the Logger name relative to this Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @return a MailLogger for the given name and prefix.
+ */
+ public MailLogger getSubLogger(String subname, String prefix) {
+ return new MailLogger(logger.getName() + "." + subname, prefix,
+ debug, out);
+ }
+
+ /**
+ * Create a MailLogger that uses a Logger whose name is composed
+ * of this MailLogger's name plus the specified sub-name, separated
+ * by a dot. The new MailLogger uses the new prefix for debug output.
+ * This is used primarily by the protocol trace code that wants a
+ * different prefix (none).
+ *
+ * @param subname the Logger name relative to this Logger name
+ * @param prefix the prefix for debug output, or null for none
+ * @param debug the debug flag for the sub-logger
+ * @return a MailLogger for the given name and prefix.
+ */
+ public MailLogger getSubLogger(String subname, String prefix,
+ boolean debug) {
+ return new MailLogger(logger.getName() + "." + subname, prefix,
+ debug, out);
+ }
+
+ /**
+ * Log the message at the specified level.
+ * @param level the log level.
+ * @param msg the message.
+ */
+ public void log(Level level, String msg) {
+ ifDebugOut(msg);
+ if (logger.isLoggable(level)) {
+ final StackTraceElement frame = inferCaller();
+ logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
+ }
+ }
+
+ /**
+ * Log the message at the specified level.
+ * @param level the log level.
+ * @param msg the message.
+ * @param param1 the additional parameter.
+ */
+ public void log(Level level, String msg, Object param1) {
+ if (debug) {
+ msg = MessageFormat.format(msg, new Object[] { param1 });
+ debugOut(msg);
+ }
+
+ if (logger.isLoggable(level)) {
+ final StackTraceElement frame = inferCaller();
+ logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
+ }
+ }
+
+ /**
+ * Log the message at the specified level.
+ * @param level the log level.
+ * @param msg the message.
+ * @param params the message parameters.
+ */
+ public void log(Level level, String msg, Object... params) {
+ if (debug) {
+ msg = MessageFormat.format(msg, params);
+ debugOut(msg);
+ }
+
+ if (logger.isLoggable(level)) {
+ final StackTraceElement frame = inferCaller();
+ logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
+ }
+ }
+
+ /**
+ * Log the message at the specified level using a format string.
+ * @param level the log level.
+ * @param msg the message format string.
+ * @param params the message parameters.
+ *
+ * @since JavaMail 1.5.4
+ */
+ public void logf(Level level, String msg, Object... params) {
+ msg = String.format(msg, params);
+ ifDebugOut(msg);
+ logger.log(level, msg);
+ }
+
+ /**
+ * Log the message at the specified level.
+ * @param level the log level.
+ * @param msg the message.
+ * @param thrown the throwable to log.
+ */
+ public void log(Level level, String msg, Throwable thrown) {
+ if (debug) {
+ if (thrown != null) {
+ debugOut(msg + ", THROW: ");
+ thrown.printStackTrace(out);
+ } else {
+ debugOut(msg);
+ }
+ }
+
+ if (logger.isLoggable(level)) {
+ final StackTraceElement frame = inferCaller();
+ logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
+ }
+ }
+
+ /**
+ * Log a message at the CONFIG level.
+ * @param msg the message.
+ */
+ public void config(String msg) {
+ log(Level.CONFIG, msg);
+ }
+
+ /**
+ * Log a message at the FINE level.
+ * @param msg the message.
+ */
+ public void fine(String msg) {
+ log(Level.FINE, msg);
+ }
+
+ /**
+ * Log a message at the FINER level.
+ * @param msg the message.
+ */
+ public void finer(String msg) {
+ log(Level.FINER, msg);
+ }
+
+ /**
+ * Log a message at the FINEST level.
+ * @param msg the message.
+ */
+ public void finest(String msg) {
+ log(Level.FINEST, msg);
+ }
+
+ /**
+ * If "debug" is set, or our embedded Logger is loggable at the
+ * given level, return true.
+ * @param level the log level.
+ * @return true if loggable.
+ */
+ public boolean isLoggable(Level level) {
+ return debug || logger.isLoggable(level);
+ }
+
+ /**
+ * Common code to conditionally log debug statements.
+ * @param msg the message to log.
+ */
+ private void ifDebugOut(String msg) {
+ if (debug)
+ debugOut(msg);
+ }
+
+ /**
+ * Common formatting for debug output.
+ * @param msg the message to log.
+ */
+ private void debugOut(String msg) {
+ if (prefix != null)
+ out.println(prefix + ": " + msg);
+ else
+ out.println(msg);
+ }
+
+ /**
+ * Return the package name of the class.
+ * Sometimes there will be no Package object for the class,
+ * e.g., if the class loader hasn't created one (see Class.getPackage()).
+ * @param clazz the class source.
+ * @return the package name or an empty string.
+ */
+ private String packageOf(Class> clazz) {
+ Package p = clazz.getPackage();
+ if (p != null)
+ return p.getName(); // hopefully the common case
+ String cname = clazz.getName();
+ int i = cname.lastIndexOf('.');
+ if (i > 0)
+ return cname.substring(0, i);
+ // no package name, now what?
+ return "";
+ }
+
+ /**
+ * A disadvantage of not being able to use Logger directly in Jakarta Mail
+ * code is that the "source class" information that Logger guesses will
+ * always refer to this class instead of our caller. This method
+ * duplicates what Logger does to try to find *our* caller, so that
+ * Logger doesn't have to do it (and get the wrong answer), and because
+ * our caller is what's wanted.
+ * @return StackTraceElement that logged the message. Treat as read-only.
+ */
+ private StackTraceElement inferCaller() {
+ // Get the stack trace.
+ StackTraceElement stack[] = (new Throwable()).getStackTrace();
+ // First, search back to a method in the Logger class.
+ int ix = 0;
+ while (ix < stack.length) {
+ StackTraceElement frame = stack[ix];
+ String cname = frame.getClassName();
+ if (isLoggerImplFrame(cname)) {
+ break;
+ }
+ ix++;
+ }
+ // Now search for the first frame before the "Logger" class.
+ while (ix < stack.length) {
+ StackTraceElement frame = stack[ix];
+ String cname = frame.getClassName();
+ if (!isLoggerImplFrame(cname)) {
+ // We've found the relevant frame.
+ return frame;
+ }
+ ix++;
+ }
+ // We haven't found a suitable frame, so just punt. This is
+ // OK as we are only committed to making a "best effort" here.
+ return new StackTraceElement(MailLogger.class.getName(), "log",
+ MailLogger.class.getName(), -1);
+ }
+
+ /**
+ * Frames to ignore as part of the MailLogger to JUL bridge.
+ * @param cname the class name.
+ * @return true if the class name is part of the MailLogger bridge.
+ */
+ private boolean isLoggerImplFrame(String cname) {
+ return MailLogger.class.getName().equals(cname);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java b/app/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java
new file mode 100644
index 0000000000..2f6363244f
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.security.cert.*;
+import java.util.*;
+
+import javax.net.ssl.*;
+
+/**
+ * An SSL socket factory that makes it easier to specify trust.
+ * This socket factory can be configured to trust all hosts or
+ * trust a specific set of hosts, in which case the server's
+ * certificate isn't verified. Alternatively, a custom TrustManager
+ * can be supplied.
+ *
+ * An instance of this factory can be set as the value of the
+ * mail.<protocol>.ssl.socketFactory
property.
+ *
+ * @since JavaMail 1.4.2
+ * @author Stephan Sann
+ * @author Bill Shannon
+ */
+public class MailSSLSocketFactory extends SSLSocketFactory {
+
+ /** Should all hosts be trusted? */
+ private boolean trustAllHosts;
+
+ /** String-array of trusted hosts */
+ private String[] trustedHosts = null;
+
+ /** Holds a SSLContext to get SSLSocketFactories from */
+ private SSLContext sslcontext;
+
+ /** Holds the KeyManager array to use */
+ private KeyManager[] keyManagers;
+
+ /** Holds the TrustManager array to use */
+ private TrustManager[] trustManagers;
+
+ /** Holds the SecureRandom to use */
+ private SecureRandom secureRandom;
+
+ /** Holds a SSLSocketFactory to pass all API-method-calls to */
+ private SSLSocketFactory adapteeFactory = null;
+
+ /**
+ * Initializes a new MailSSLSocketFactory.
+ *
+ * @throws GeneralSecurityException for security errors
+ */
+ public MailSSLSocketFactory() throws GeneralSecurityException {
+ this("TLS");
+ }
+
+ /**
+ * Initializes a new MailSSLSocketFactory with a given protocol.
+ * Normally the protocol will be specified as "TLS".
+ *
+ * @param protocol The protocol to use
+ * @throws NoSuchAlgorithmException if given protocol is not supported
+ * @throws GeneralSecurityException for security errors
+ */
+ public MailSSLSocketFactory(String protocol)
+ throws GeneralSecurityException {
+
+ // By default we do NOT trust all hosts.
+ trustAllHosts = false;
+
+ // Get an instance of an SSLContext.
+ sslcontext = SSLContext.getInstance(protocol);
+
+ // Default properties to init the SSLContext
+ keyManagers = null;
+ trustManagers = new TrustManager[] { new MailTrustManager() };
+ secureRandom = null;
+
+ // Assemble a default SSLSocketFactory to delegate all API-calls to.
+ newAdapteeFactory();
+ }
+
+
+ /**
+ * Gets an SSLSocketFactory based on the given (or default)
+ * KeyManager array, TrustManager array and SecureRandom and
+ * sets it to the instance var adapteeFactory.
+ *
+ * @throws KeyManagementException for key manager errors
+ */
+ private synchronized void newAdapteeFactory()
+ throws KeyManagementException {
+ sslcontext.init(keyManagers, trustManagers, secureRandom);
+
+ // Get SocketFactory and save it in our instance var
+ adapteeFactory = sslcontext.getSocketFactory();
+ }
+
+ /**
+ * @return the keyManagers
+ */
+ public synchronized KeyManager[] getKeyManagers() {
+ return keyManagers.clone();
+ }
+
+ /**
+ * @param keyManagers the keyManagers to set
+ * @throws GeneralSecurityException for security errors
+ */
+ public synchronized void setKeyManagers(KeyManager... keyManagers)
+ throws GeneralSecurityException {
+ this.keyManagers = keyManagers.clone();
+ newAdapteeFactory();
+ }
+
+ /**
+ * @return the secureRandom
+ */
+ public synchronized SecureRandom getSecureRandom() {
+ return secureRandom;
+ }
+
+ /**
+ * @param secureRandom the secureRandom to set
+ * @throws GeneralSecurityException for security errors
+ */
+ public synchronized void setSecureRandom(SecureRandom secureRandom)
+ throws GeneralSecurityException {
+ this.secureRandom = secureRandom;
+ newAdapteeFactory();
+ }
+
+ /**
+ * @return the trustManagers
+ */
+ public synchronized TrustManager[] getTrustManagers() {
+ return trustManagers;
+ }
+
+ /**
+ * @param trustManagers the trustManagers to set
+ * @throws GeneralSecurityException for security errors
+ */
+ public synchronized void setTrustManagers(TrustManager... trustManagers)
+ throws GeneralSecurityException {
+ this.trustManagers = trustManagers;
+ newAdapteeFactory();
+ }
+
+ /**
+ * @return true if all hosts should be trusted
+ */
+ public synchronized boolean isTrustAllHosts() {
+ return trustAllHosts;
+ }
+
+ /**
+ * @param trustAllHosts should all hosts be trusted?
+ */
+ public synchronized void setTrustAllHosts(boolean trustAllHosts) {
+ this.trustAllHosts = trustAllHosts;
+ }
+
+ /**
+ * @return the trusted hosts
+ */
+ public synchronized String[] getTrustedHosts() {
+ if (trustedHosts == null)
+ return null;
+ else
+ return trustedHosts.clone();
+ }
+
+ /**
+ * @param trustedHosts the hosts to trust
+ */
+ public synchronized void setTrustedHosts(String... trustedHosts) {
+ if (trustedHosts == null)
+ this.trustedHosts = null;
+ else
+ this.trustedHosts = trustedHosts.clone();
+ }
+
+ /**
+ * After a successful conection to the server, this method is
+ * called to ensure that the server should be trusted.
+ *
+ * @param server name of the server we connected to
+ * @param sslSocket SSLSocket connected to the server
+ * @return true if "trustAllHosts" is set to true OR the server
+ * is contained in the "trustedHosts" array;
+ */
+ public synchronized boolean isServerTrusted(String server,
+ SSLSocket sslSocket) {
+
+ //System.out.println("DEBUG: isServerTrusted host " + server);
+
+ // If "trustAllHosts" is set to true, we return true
+ if (trustAllHosts)
+ return true;
+
+ // If the socket host is contained in the "trustedHosts" array,
+ // we return true
+ if (trustedHosts != null)
+ return Arrays.asList(trustedHosts).contains(server); // ignore case?
+
+ // If we get here, trust of the server was verified by the trust manager
+ return true;
+ }
+
+
+ // SocketFactory methods
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket,
+ * java.lang.String, int, boolean)
+ */
+ @Override
+ public synchronized Socket createSocket(Socket socket, String s, int i,
+ boolean flag) throws IOException {
+ return adapteeFactory.createSocket(socket, s, i, flag);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites()
+ */
+ @Override
+ public synchronized String[] getDefaultCipherSuites() {
+ return adapteeFactory.getDefaultCipherSuites();
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites()
+ */
+ @Override
+ public synchronized String[] getSupportedCipherSuites() {
+ return adapteeFactory.getSupportedCipherSuites();
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.SocketFactory#createSocket()
+ */
+ @Override
+ public synchronized Socket createSocket() throws IOException {
+ return adapteeFactory.createSocket();
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int,
+ * java.net.InetAddress, int)
+ */
+ @Override
+ public synchronized Socket createSocket(InetAddress inetaddress, int i,
+ InetAddress inetaddress1, int j) throws IOException {
+ return adapteeFactory.createSocket(inetaddress, i, inetaddress1, j);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int)
+ */
+ @Override
+ public synchronized Socket createSocket(InetAddress inetaddress, int i)
+ throws IOException {
+ return adapteeFactory.createSocket(inetaddress, i);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.SocketFactory#createSocket(java.lang.String, int,
+ * java.net.InetAddress, int)
+ */
+ @Override
+ public synchronized Socket createSocket(String s, int i,
+ InetAddress inetaddress, int j)
+ throws IOException, UnknownHostException {
+ return adapteeFactory.createSocket(s, i, inetaddress, j);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.SocketFactory#createSocket(java.lang.String, int)
+ */
+ @Override
+ public synchronized Socket createSocket(String s, int i)
+ throws IOException, UnknownHostException {
+ return adapteeFactory.createSocket(s, i);
+ }
+
+
+ // inner classes
+
+ /**
+ * A default Trustmanager.
+ *
+ * @author Stephan Sann
+ */
+ private class MailTrustManager implements X509TrustManager {
+
+ /** A TrustManager to pass method calls to */
+ private X509TrustManager adapteeTrustManager = null;
+
+ /**
+ * Initializes a new TrustManager instance.
+ */
+ private MailTrustManager() throws GeneralSecurityException {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ tmf.init((KeyStore)null);
+ adapteeTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.X509TrustManager#checkClientTrusted(
+ * java.security.cert.X509Certificate[], java.lang.String)
+ */
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+ if (!(isTrustAllHosts() || getTrustedHosts() != null))
+ adapteeTrustManager.checkClientTrusted(certs, authType);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.X509TrustManager#checkServerTrusted(
+ * java.security.cert.X509Certificate[], java.lang.String)
+ */
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+
+ if (!(isTrustAllHosts() || getTrustedHosts() != null))
+ adapteeTrustManager.checkServerTrusted(certs, authType);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
+ */
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return adapteeTrustManager.getAcceptedIssuers();
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/MessageRemovedIOException.java b/app/src/main/java/com/sun/mail/util/MessageRemovedIOException.java
new file mode 100644
index 0000000000..96840f773f
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/MessageRemovedIOException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * A variant of MessageRemovedException that can be thrown from methods
+ * that only throw IOException. The getContent method will catch this
+ * exception and translate it back to MessageRemovedException.
+ *
+ * @see javax.mail.Message#isExpunged()
+ * @see javax.mail.Message#getMessageNumber()
+ * @author Bill Shannon
+ */
+
+public class MessageRemovedIOException extends IOException {
+
+ private static final long serialVersionUID = 4280468026581616424L;
+
+ /**
+ * Constructs a MessageRemovedIOException with no detail message.
+ */
+ public MessageRemovedIOException() {
+ super();
+ }
+
+ /**
+ * Constructs a MessageRemovedIOException with the specified detail message.
+ * @param s the detail message
+ */
+ public MessageRemovedIOException(String s) {
+ super(s);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/MimeUtil.java b/app/src/main/java/com/sun/mail/util/MimeUtil.java
new file mode 100644
index 0000000000..6944249857
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/MimeUtil.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.lang.reflect.*;
+import java.security.*;
+
+import javax.mail.internet.MimePart;
+
+/**
+ * General MIME-related utility methods.
+ *
+ * @author Bill Shannon
+ * @since JavaMail 1.4.4
+ */
+public class MimeUtil {
+
+ private static final Method cleanContentType;
+
+ static {
+ Method meth = null;
+ try {
+ String cth = System.getProperty("mail.mime.contenttypehandler");
+ if (cth != null) {
+ ClassLoader cl = getContextClassLoader();
+ Class> clsHandler = null;
+ if (cl != null) {
+ try {
+ clsHandler = Class.forName(cth, false, cl);
+ } catch (ClassNotFoundException cex) { }
+ }
+ if (clsHandler == null)
+ clsHandler = Class.forName(cth);
+ meth = clsHandler.getMethod("cleanContentType",
+ new Class>[] { MimePart.class, String.class });
+ }
+ } catch (ClassNotFoundException ex) {
+ // ignore it
+ } catch (NoSuchMethodException ex) {
+ // ignore it
+ } catch (RuntimeException ex) {
+ // ignore it
+ } finally {
+ cleanContentType = meth;
+ }
+ }
+
+ // No one should instantiate this class.
+ private MimeUtil() {
+ }
+
+ /**
+ * If a Content-Type handler has been specified,
+ * call it to clean up the Content-Type value.
+ *
+ * @param mp the MimePart
+ * @param contentType the Content-Type value
+ * @return the cleaned Content-Type value
+ */
+ public static String cleanContentType(MimePart mp, String contentType) {
+ if (cleanContentType != null) {
+ try {
+ return (String)cleanContentType.invoke(null,
+ new Object[] { mp, contentType });
+ } catch (Exception ex) {
+ return contentType;
+ }
+ } else
+ return contentType;
+ }
+
+ /**
+ * Convenience method to get our context class loader.
+ * Assert any privileges we might have and then call the
+ * Thread.getContextClassLoader method.
+ */
+ private static ClassLoader getContextClassLoader() {
+ return
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public ClassLoader run() {
+ ClassLoader cl = null;
+ try {
+ cl = Thread.currentThread().getContextClassLoader();
+ } catch (SecurityException ex) { }
+ return cl;
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/PropUtil.java b/app/src/main/java/com/sun/mail/util/PropUtil.java
new file mode 100644
index 0000000000..430b378692
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/PropUtil.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.util.*;
+import javax.mail.Session;
+
+/**
+ * Utilities to make it easier to get property values.
+ * Properties can be strings or type-specific value objects.
+ *
+ * @author Bill Shannon
+ */
+public class PropUtil {
+
+ // No one should instantiate this class.
+ private PropUtil() {
+ }
+
+ /**
+ * Get an integer valued property.
+ *
+ * @param props the properties
+ * @param name the property name
+ * @param def default value if property not found
+ * @return the property value
+ */
+ public static int getIntProperty(Properties props, String name, int def) {
+ return getInt(getProp(props, name), def);
+ }
+
+ /**
+ * Get a boolean valued property.
+ *
+ * @param props the properties
+ * @param name the property name
+ * @param def default value if property not found
+ * @return the property value
+ */
+ public static boolean getBooleanProperty(Properties props,
+ String name, boolean def) {
+ return getBoolean(getProp(props, name), def);
+ }
+
+ /**
+ * Get an integer valued property.
+ *
+ * @param session the Session
+ * @param name the property name
+ * @param def default value if property not found
+ * @return the property value
+ */
+ @Deprecated
+ public static int getIntSessionProperty(Session session,
+ String name, int def) {
+ return getInt(getProp(session.getProperties(), name), def);
+ }
+
+ /**
+ * Get a boolean valued property.
+ *
+ * @param session the Session
+ * @param name the property name
+ * @param def default value if property not found
+ * @return the property value
+ */
+ @Deprecated
+ public static boolean getBooleanSessionProperty(Session session,
+ String name, boolean def) {
+ return getBoolean(getProp(session.getProperties(), name), def);
+ }
+
+ /**
+ * Get a boolean valued System property.
+ *
+ * @param name the property name
+ * @param def default value if property not found
+ * @return the property value
+ */
+ public static boolean getBooleanSystemProperty(String name, boolean def) {
+ try {
+ return getBoolean(getProp(System.getProperties(), name), def);
+ } catch (SecurityException sex) {
+ // fall through...
+ }
+
+ /*
+ * If we can't get the entire System Properties object because
+ * of a SecurityException, just ask for the specific property.
+ */
+ try {
+ String value = System.getProperty(name);
+ if (value == null)
+ return def;
+ if (def)
+ return !value.equalsIgnoreCase("false");
+ else
+ return value.equalsIgnoreCase("true");
+ } catch (SecurityException sex) {
+ return def;
+ }
+ }
+
+ /**
+ * Get the value of the specified property.
+ * If the "get" method returns null, use the getProperty method,
+ * which might cascade to a default Properties object.
+ */
+ private static Object getProp(Properties props, String name) {
+ Object val = props.get(name);
+ if (val != null)
+ return val;
+ else
+ return props.getProperty(name);
+ }
+
+ /**
+ * Interpret the value object as an integer,
+ * returning def if unable.
+ */
+ private static int getInt(Object value, int def) {
+ if (value == null)
+ return def;
+ if (value instanceof String) {
+ try {
+ String s = (String)value;
+ if (s.startsWith("0x"))
+ return Integer.parseInt(s.substring(2), 16);
+ else
+ return Integer.parseInt(s);
+ } catch (NumberFormatException nfex) { }
+ }
+ if (value instanceof Integer)
+ return ((Integer)value).intValue();
+ return def;
+ }
+
+ /**
+ * Interpret the value object as a boolean,
+ * returning def if unable.
+ */
+ private static boolean getBoolean(Object value, boolean def) {
+ if (value == null)
+ return def;
+ if (value instanceof String) {
+ /*
+ * If the default is true, only "false" turns it off.
+ * If the default is false, only "true" turns it on.
+ */
+ if (def)
+ return !((String)value).equalsIgnoreCase("false");
+ else
+ return ((String)value).equalsIgnoreCase("true");
+ }
+ if (value instanceof Boolean)
+ return ((Boolean)value).booleanValue();
+ return def;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/QDecoderStream.java b/app/src/main/java/com/sun/mail/util/QDecoderStream.java
new file mode 100644
index 0000000000..19ea0c6785
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/QDecoderStream.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Q Decoder as defined in RFC 2047
+ * for decoding MIME headers. It subclasses the QPDecoderStream class.
+ *
+ * @author John Mani
+ */
+
+public class QDecoderStream extends QPDecoderStream {
+
+ /**
+ * Create a Q-decoder that decodes the specified input stream.
+ * @param in the input stream
+ */
+ public QDecoderStream(InputStream in) {
+ super(in);
+ }
+
+ /**
+ * Read the next decoded byte from this input stream. The byte
+ * is returned as an int
in the range 0
+ * to 255
. If no byte is available because the end of
+ * the stream has been reached, the value -1
is returned.
+ * This method blocks until input data is available, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next byte of data, or -1
if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public int read() throws IOException {
+ int c = in.read();
+
+ if (c == '_') // Return '_' as ' '
+ return ' ';
+ else if (c == '=') {
+ // QP Encoded atom. Get the next two bytes ..
+ ba[0] = (byte)in.read();
+ ba[1] = (byte)in.read();
+ // .. and decode them
+ try {
+ return ASCIIUtility.parseInt(ba, 0, 2, 16);
+ } catch (NumberFormatException nex) {
+ throw new DecodingException(
+ "QDecoder: Error in QP stream " + nex.getMessage());
+ }
+ } else
+ return c;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/QEncoderStream.java b/app/src/main/java/com/sun/mail/util/QEncoderStream.java
new file mode 100644
index 0000000000..659897aec3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/QEncoderStream.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Q Encoder as defined by RFC 2047 for
+ * encoding MIME headers. It subclasses the QPEncoderStream class.
+ *
+ * @author John Mani
+ */
+
+public class QEncoderStream extends QPEncoderStream {
+
+ private String specials;
+ private static String WORD_SPECIALS = "=_?\"#$%&'(),.:;<>@[\\]^`{|}~";
+ private static String TEXT_SPECIALS = "=_?";
+
+ /**
+ * Create a Q encoder that encodes the specified input stream
+ * @param out the output stream
+ * @param encodingWord true if we are Q-encoding a word within a
+ * phrase.
+ */
+ public QEncoderStream(OutputStream out, boolean encodingWord) {
+ super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should
+ // suffice (!) to indicate that
+ // CRLFs should not be inserted
+ // when encoding rfc822 headers
+
+ // a RFC822 "word" token has more restrictions than a
+ // RFC822 "text" token.
+ specials = encodingWord ? WORD_SPECIALS : TEXT_SPECIALS;
+ }
+
+ /**
+ * Encodes the specified byte
to this output stream.
+ * @param c the byte
.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void write(int c) throws IOException {
+ c = c & 0xff; // Turn off the MSB.
+ if (c == ' ')
+ output('_', false);
+ else if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0)
+ // Encoding required.
+ output(c, true);
+ else // No encoding required
+ output(c, false);
+ }
+
+ /**
+ * Returns the length of the encoded version of this byte array.
+ *
+ * @param b the byte array
+ * @param encodingWord true if encoding words, false if encoding text
+ * @return the length
+ */
+ public static int encodedLength(byte[] b, boolean encodingWord) {
+ int len = 0;
+ String specials = encodingWord ? WORD_SPECIALS: TEXT_SPECIALS;
+ for (int i = 0; i < b.length; i++) {
+ int c = b[i] & 0xff; // Mask off MSB
+ if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0)
+ // needs encoding
+ len += 3; // Q-encoding is 1 -> 3 conversion
+ else
+ len++;
+ }
+ return len;
+ }
+
+ /**** begin TEST program ***
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ QEncoderStream encoder = new QEncoderStream(System.out);
+ int c;
+
+ while ((c = infile.read()) != -1)
+ encoder.write(c);
+ encoder.close();
+ }
+ *** end TEST program ***/
+}
diff --git a/app/src/main/java/com/sun/mail/util/QPDecoderStream.java b/app/src/main/java/com/sun/mail/util/QPDecoderStream.java
new file mode 100644
index 0000000000..284c6145ca
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/QPDecoderStream.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a QP Decoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ *
+ * @author John Mani
+ */
+
+public class QPDecoderStream extends FilterInputStream {
+ protected byte[] ba = new byte[2];
+ protected int spaces = 0;
+
+ /**
+ * Create a Quoted Printable decoder that decodes the specified
+ * input stream.
+ * @param in the input stream
+ */
+ public QPDecoderStream(InputStream in) {
+ super(new PushbackInputStream(in, 2)); // pushback of size=2
+ }
+
+ /**
+ * Read the next decoded byte from this input stream. The byte
+ * is returned as an int
in the range 0
+ * to 255
. If no byte is available because the end of
+ * the stream has been reached, the value -1
is returned.
+ * This method blocks until input data is available, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return the next byte of data, or -1
if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public int read() throws IOException {
+ if (spaces > 0) {
+ // We have cached space characters, return one
+ spaces--;
+ return ' ';
+ }
+
+ int c = in.read();
+
+ if (c == ' ') {
+ // Got space, keep reading till we get a non-space char
+ while ((c = in.read()) == ' ')
+ spaces++;
+
+ if (c == '\r' || c == '\n' || c == -1)
+ // If the non-space char is CR/LF/EOF, the spaces we got
+ // so far is junk introduced during transport. Junk 'em.
+ spaces = 0;
+ else {
+ // The non-space char is NOT CR/LF, the spaces are valid.
+ ((PushbackInputStream)in).unread(c);
+ c = ' ';
+ }
+ return c; // return either or
+ }
+ else if (c == '=') {
+ // QP Encoded atom. Decode the next two bytes
+ int a = in.read();
+
+ if (a == '\n') {
+ /* Hmm ... not really confirming QP encoding, but lets
+ * allow this as a LF terminated encoded line .. and
+ * consider this a soft linebreak and recurse to fetch
+ * the next char.
+ */
+ return read();
+ } else if (a == '\r') {
+ // Expecting LF. This forms a soft linebreak to be ignored.
+ int b = in.read();
+ if (b != '\n')
+ /* Not really confirming QP encoding, but
+ * lets allow this as well.
+ */
+ ((PushbackInputStream)in).unread(b);
+ return read();
+ } else if (a == -1) {
+ // Not valid QP encoding, but we be nice and tolerant here !
+ return -1;
+ } else {
+ ba[0] = (byte)a;
+ ba[1] = (byte)in.read();
+ try {
+ return ASCIIUtility.parseInt(ba, 0, 2, 16);
+ } catch (NumberFormatException nex) {
+ /*
+ System.err.println(
+ "Illegal characters in QP encoded stream: " +
+ ASCIIUtility.toString(ba, 0, 2)
+ );
+ */
+
+ ((PushbackInputStream)in).unread(ba);
+ return c;
+ }
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Reads up to len
decoded bytes of data from this input stream
+ * into an array of bytes. This method blocks until some input is
+ * available.
+ *
+ *
+ * @param buf the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the stream has been reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ int i, c;
+ for (i = 0; i < len; i++) {
+ if ((c = read()) == -1) {
+ if (i == 0) // At end of stream, so we should
+ i = -1; // return -1 , NOT 0.
+ break;
+ }
+ buf[off+i] = (byte)c;
+ }
+ return i;
+ }
+
+ /**
+ * Skips over and discards n bytes of data from this stream.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ long skipped = 0;
+ while (n-- > 0 && read() >= 0)
+ skipped++;
+ return skipped;
+ }
+
+ /**
+ * Tests if this input stream supports marks. Currently this class
+ * does not support marks
+ */
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Returns the number of bytes that can be read from this input
+ * stream without blocking. The QP algorithm does not permit
+ * a priori knowledge of the number of bytes after decoding, so
+ * this method just invokes the available
method
+ * of the original input stream.
+ */
+ @Override
+ public int available() throws IOException {
+ // This is bogus ! We don't really know how much
+ // bytes are available *after* decoding
+ return in.available();
+ }
+
+ /**** begin TEST program
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ QPDecoderStream decoder = new QPDecoderStream(infile);
+ int c;
+
+ while ((c = decoder.read()) != -1)
+ System.out.print((char)c);
+ System.out.println();
+ }
+ *** end TEST program ****/
+}
diff --git a/app/src/main/java/com/sun/mail/util/QPEncoderStream.java b/app/src/main/java/com/sun/mail/util/QPEncoderStream.java
new file mode 100644
index 0000000000..d39102dae6
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/QPEncoderStream.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Quoted Printable Encoder. It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter. The Encoding
+ * is done as the bytes are written out.
+ *
+ * @author John Mani
+ */
+
+public class QPEncoderStream extends FilterOutputStream {
+ private int count = 0; // number of bytes that have been output
+ private int bytesPerLine; // number of bytes per line
+ private boolean gotSpace = false;
+ private boolean gotCR = false;
+
+ /**
+ * Create a QP encoder that encodes the specified input stream
+ * @param out the output stream
+ * @param bytesPerLine the number of bytes per line. The encoder
+ * inserts a CRLF sequence after this many number
+ * of bytes.
+ */
+ public QPEncoderStream(OutputStream out, int bytesPerLine) {
+ super(out);
+ // Subtract 1 to account for the '=' in the soft-return
+ // at the end of a line
+ this.bytesPerLine = bytesPerLine - 1;
+ }
+
+ /**
+ * Create a QP encoder that encodes the specified input stream.
+ * Inserts the CRLF sequence after outputting 76 bytes.
+ * @param out the output stream
+ */
+ public QPEncoderStream(OutputStream out) {
+ this(out, 76);
+ }
+
+ /**
+ * Encodes len
bytes from the specified
+ * byte
array starting at offset off
to
+ * this output stream.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ for (int i = 0; i < len; i++)
+ write(b[off + i]);
+ }
+
+ /**
+ * Encodes b.length
bytes to this output stream.
+ * @param b the data to be written.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ /**
+ * Encodes the specified byte
to this output stream.
+ * @param c the byte
.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void write(int c) throws IOException {
+ c = c & 0xff; // Turn off the MSB.
+ if (gotSpace) { // previous character was
+ if (c == '\r' || c == '\n')
+ // if CR/LF, we need to encode the char
+ output(' ', true);
+ else // no encoding required, just output the char
+ output(' ', false);
+ gotSpace = false;
+ }
+
+ if (c == '\r') {
+ gotCR = true;
+ outputCRLF();
+ } else {
+ if (c == '\n') {
+ if (gotCR)
+ // This is a CRLF sequence, we already output the
+ // corresponding CRLF when we got the CR, so ignore this
+ ;
+ else
+ outputCRLF();
+ } else if (c == ' ') {
+ gotSpace = true;
+ } else if (c < 040 || c >= 0177 || c == '=')
+ // Encoding required.
+ output(c, true);
+ else // No encoding required
+ output(c, false);
+ // whatever it was, it wasn't a CR
+ gotCR = false;
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be encoded out to the stream.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public void flush() throws IOException {
+ if (gotSpace) {
+ output(' ', true);
+ gotSpace = false;
+ }
+ out.flush();
+ }
+
+ /**
+ * Forces any buffered output bytes to be encoded out to the stream
+ * and closes this output stream.
+ *
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void close() throws IOException {
+ flush();
+ out.close();
+ }
+
+ private void outputCRLF() throws IOException {
+ out.write('\r');
+ out.write('\n');
+ count = 0;
+ }
+
+ // The encoding table
+ private final static char hex[] = {
+ '0','1', '2', '3', '4', '5', '6', '7',
+ '8','9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ protected void output(int c, boolean encode) throws IOException {
+ if (encode) {
+ if ((count += 3) > bytesPerLine) {
+ out.write('=');
+ out.write('\r');
+ out.write('\n');
+ count = 3; // set the next line's length
+ }
+ out.write('=');
+ out.write(hex[c >> 4]);
+ out.write(hex[c & 0xf]);
+ } else {
+ if (++count > bytesPerLine) {
+ out.write('=');
+ out.write('\r');
+ out.write('\n');
+ count = 1; // set the next line's length
+ }
+ out.write(c);
+ }
+ }
+
+ /**** begin TEST program ***
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ QPEncoderStream encoder = new QPEncoderStream(System.out);
+ int c;
+
+ while ((c = infile.read()) != -1)
+ encoder.write(c);
+ encoder.close();
+ }
+ *** end TEST program ***/
+}
diff --git a/app/src/main/java/com/sun/mail/util/ReadableMime.java b/app/src/main/java/com/sun/mail/util/ReadableMime.java
new file mode 100644
index 0000000000..ecba7e2a49
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/ReadableMime.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+
+/**
+ * A Message or message Part whose data can be read as a MIME format
+ * stream. Note that the MIME stream will include both the headers
+ * and the body of the message or part. This should be the same data
+ * that is produced by the writeTo method, but in a readable form.
+ *
+ * @author Bill Shannon
+ * @since JavaMail 1.4.5
+ */
+public interface ReadableMime {
+ /**
+ * Return the MIME format stream corresponding to this message part.
+ *
+ * @return the MIME format stream
+ * @exception MessagingException for failures
+ */
+ public InputStream getMimeStream() throws MessagingException;
+}
diff --git a/app/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java b/app/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java
new file mode 100644
index 0000000000..9fd617ea9b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+
+import javax.mail.util.SharedByteArrayInputStream;
+
+/**
+ * A ByteArrayOutputStream that allows us to share the byte array
+ * rather than copy it. Eventually could replace this with something
+ * that doesn't require a single contiguous byte array.
+ *
+ * @author Bill Shannon
+ * @since JavaMail 1.4.5
+ */
+public class SharedByteArrayOutputStream extends ByteArrayOutputStream {
+ public SharedByteArrayOutputStream(int size) {
+ super(size);
+ }
+
+ public InputStream toStream() {
+ return new SharedByteArrayInputStream(buf, 0, count);
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/SocketConnectException.java b/app/src/main/java/com/sun/mail/util/SocketConnectException.java
new file mode 100644
index 0000000000..6b3c16e082
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/SocketConnectException.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * An IOException that indicates a socket connection attempt failed.
+ * Unlike java.net.ConnectException, it includes details of what we
+ * were trying to connect to.
+ *
+ * @see java.net.ConnectException
+ * @author Bill Shannon
+ * @since JavaMail 1.5.0
+ */
+
+public class SocketConnectException extends IOException {
+ /**
+ * The socket host name.
+ */
+ private String host;
+ /**
+ * The socket port.
+ */
+ private int port;
+ /**
+ * The connection timeout.
+ */
+ private int cto;
+ /**
+ * The generated serial id.
+ */
+ private static final long serialVersionUID = 3997871560538755463L;
+
+ /**
+ * Constructs a SocketConnectException.
+ *
+ * @param msg error message detail
+ * @param cause the underlying exception that indicates the failure
+ * @param host the host we were trying to connect to
+ * @param port the port we were trying to connect to
+ * @param cto the timeout for the connection attempt
+ */
+ public SocketConnectException(String msg, Exception cause,
+ String host, int port, int cto) {
+ super(msg);
+ initCause(cause);
+ this.host = host;
+ this.port = port;
+ this.cto = cto;
+ }
+
+ /**
+ * The exception that caused the failure.
+ *
+ * @return the exception
+ */
+ public Exception getException() {
+ // the "cause" is always an Exception; see constructor above
+ Throwable t = getCause();
+ assert t == null || t instanceof Exception;
+ return (Exception) t;
+ }
+
+ /**
+ * The host we were trying to connect to.
+ *
+ * @return the host
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * The port we were trying to connect to.
+ *
+ * @return the port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * The timeout used for the connection attempt.
+ *
+ * @return the connection timeout
+ */
+ public int getConnectionTimeout() {
+ return cto;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/SocketFetcher.java b/app/src/main/java/com/sun/mail/util/SocketFetcher.java
new file mode 100644
index 0000000000..d6af220615
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/SocketFetcher.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.security.*;
+import java.net.*;
+import java.io.*;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.logging.Level;
+import java.security.cert.*;
+import javax.net.*;
+import javax.net.ssl.*;
+
+/**
+ * This class is used to get Sockets. Depending on the arguments passed
+ * it will either return a plain java.net.Socket or dynamically load
+ * the SocketFactory class specified in the classname param and return
+ * a socket created by that SocketFactory.
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+public class SocketFetcher {
+
+ private static MailLogger logger = new MailLogger(
+ SocketFetcher.class,
+ "socket",
+ "DEBUG SocketFetcher",
+ PropUtil.getBooleanSystemProperty("mail.socket.debug", false),
+ System.out);
+
+ // No one should instantiate this class.
+ private SocketFetcher() {
+ }
+
+ /**
+ * This method returns a Socket. Properties control the use of
+ * socket factories and other socket characteristics. The properties
+ * used are:
+ *
+ * prefix .socketFactory
+ * prefix .socketFactory.class
+ * prefix .socketFactory.fallback
+ * prefix .socketFactory.port
+ * prefix .ssl.socketFactory
+ * prefix .ssl.socketFactory.class
+ * prefix .ssl.socketFactory.port
+ * prefix .timeout
+ * prefix .connectiontimeout
+ * prefix .localaddress
+ * prefix .localport
+ * prefix .usesocketchannels
+ *
+ * If we're making an SSL connection, the ssl.socketFactory
+ * properties are used first, if set.
+ *
+ * If the socketFactory property is set, the value is an
+ * instance of a SocketFactory class, not a string. The
+ * instance is used directly. If the socketFactory property
+ * is not set, the socketFactory.class property is considered.
+ * (Note that the SocketFactory property must be set using the
+ * put
method, not the setProperty
+ * method.)
+ *
+ * If the socketFactory.class property isn't set, the socket
+ * returned is an instance of java.net.Socket connected to the
+ * given host and port. If the socketFactory.class property is set,
+ * it is expected to contain a fully qualified classname of a
+ * javax.net.SocketFactory subclass. In this case, the class is
+ * dynamically instantiated and a socket created by that
+ * SocketFactory is returned.
+ *
+ * If the socketFactory.fallback property is set to false, don't
+ * fall back to using regular sockets if the socket factory fails.
+ *
+ * The socketFactory.port specifies a port to use when connecting
+ * through the socket factory. If unset, the port argument will be
+ * used.
+ *
+ * If the connectiontimeout property is set, the timeout is passed
+ * to the socket connect method.
+ *
+ * If the timeout property is set, it is used to set the socket timeout.
+ *
+ *
+ * If the localaddress property is set, it's used as the local address
+ * to bind to. If the localport property is also set, it's used as the
+ * local port number to bind to.
+ *
+ * If the usesocketchannels property is set, and we create the Socket
+ * ourself, and the selection of other properties allows, create a
+ * SocketChannel and get the Socket from it. This allows us to later
+ * retrieve the SocketChannel from the Socket and use it with Select.
+ *
+ * @param host The host to connect to
+ * @param port The port to connect to at the host
+ * @param props Properties object containing socket properties
+ * @param prefix Property name prefix, e.g., "mail.imap"
+ * @param useSSL use the SSL socket factory as the default
+ * @return the Socket
+ * @exception IOException for I/O errors
+ */
+ public static Socket getSocket(String host, int port, Properties props,
+ String prefix, boolean useSSL)
+ throws IOException {
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("getSocket" + ", host " + host + ", port " + port +
+ ", prefix " + prefix + ", useSSL " + useSSL);
+ if (prefix == null)
+ prefix = "socket";
+ if (props == null)
+ props = new Properties(); // empty
+ int cto = PropUtil.getIntProperty(props,
+ prefix + ".connectiontimeout", -1);
+ Socket socket = null;
+ String localaddrstr = props.getProperty(prefix + ".localaddress", null);
+ InetAddress localaddr = null;
+ if (localaddrstr != null)
+ localaddr = InetAddress.getByName(localaddrstr);
+ int localport = PropUtil.getIntProperty(props,
+ prefix + ".localport", 0);
+
+ boolean fb = PropUtil.getBooleanProperty(props,
+ prefix + ".socketFactory.fallback", true);
+
+ int sfPort = -1;
+ String sfErr = "unknown socket factory";
+ int to = PropUtil.getIntProperty(props, prefix + ".timeout", -1);
+ try {
+ /*
+ * If using SSL, first look for SSL-specific class name or
+ * factory instance.
+ */
+ SocketFactory sf = null;
+ String sfPortName = null;
+ if (useSSL) {
+ Object sfo = props.get(prefix + ".ssl.socketFactory");
+ if (sfo instanceof SocketFactory) {
+ sf = (SocketFactory)sfo;
+ sfErr = "SSL socket factory instance " + sf;
+ }
+ if (sf == null) {
+ String sfClass =
+ props.getProperty(prefix + ".ssl.socketFactory.class");
+ sf = getSocketFactory(sfClass);
+ sfErr = "SSL socket factory class " + sfClass;
+ }
+ sfPortName = ".ssl.socketFactory.port";
+ }
+
+ if (sf == null) {
+ Object sfo = props.get(prefix + ".socketFactory");
+ if (sfo instanceof SocketFactory) {
+ sf = (SocketFactory)sfo;
+ sfErr = "socket factory instance " + sf;
+ }
+ if (sf == null) {
+ String sfClass =
+ props.getProperty(prefix + ".socketFactory.class");
+ sf = getSocketFactory(sfClass);
+ sfErr = "socket factory class " + sfClass;
+ }
+ sfPortName = ".socketFactory.port";
+ }
+
+ // if we now have a socket factory, use it
+ if (sf != null) {
+ sfPort = PropUtil.getIntProperty(props,
+ prefix + sfPortName, -1);
+
+ // if port passed in via property isn't valid, use param
+ if (sfPort == -1)
+ sfPort = port;
+ socket = createSocket(localaddr, localport,
+ host, sfPort, cto, to, props, prefix, sf, useSSL);
+ }
+ } catch (SocketTimeoutException sex) {
+ throw sex;
+ } catch (Exception ex) {
+ if (!fb) {
+ if (ex instanceof InvocationTargetException) {
+ Throwable t =
+ ((InvocationTargetException)ex).getTargetException();
+ if (t instanceof Exception)
+ ex = (Exception)t;
+ }
+ if (ex instanceof IOException)
+ throw (IOException)ex;
+ throw new SocketConnectException("Using " + sfErr, ex,
+ host, sfPort, cto);
+ }
+ }
+
+ if (socket == null) {
+ socket = createSocket(localaddr, localport,
+ host, port, cto, to, props, prefix, null, useSSL);
+
+ } else {
+ if (to >= 0) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("set socket read timeout " + to);
+ socket.setSoTimeout(to);
+ }
+ }
+
+ return socket;
+ }
+
+ public static Socket getSocket(String host, int port, Properties props,
+ String prefix) throws IOException {
+ return getSocket(host, port, props, prefix, false);
+ }
+
+ /**
+ * Create a socket with the given local address and connected to
+ * the given host and port. Use the specified connection timeout
+ * and read timeout.
+ * If a socket factory is specified, use it. Otherwise, use the
+ * SSLSocketFactory if useSSL is true.
+ */
+ private static Socket createSocket(InetAddress localaddr, int localport,
+ String host, int port, int cto, int to,
+ Properties props, String prefix,
+ SocketFactory sf, boolean useSSL)
+ throws IOException {
+ Socket socket = null;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("create socket: prefix " + prefix +
+ ", localaddr " + localaddr + ", localport " + localport +
+ ", host " + host + ", port " + port +
+ ", connection timeout " + cto + ", timeout " + to +
+ ", socket factory " + sf + ", useSSL " + useSSL);
+
+ String proxyHost = props.getProperty(prefix + ".proxy.host", null);
+ String proxyUser = props.getProperty(prefix + ".proxy.user", null);
+ String proxyPassword = props.getProperty(prefix + ".proxy.password", null);
+ int proxyPort = 80;
+ String socksHost = null;
+ int socksPort = 1080;
+ String err = null;
+
+ if (proxyHost != null) {
+ int i = proxyHost.indexOf(':');
+ if (i >= 0) {
+ try {
+ proxyPort = Integer.parseInt(proxyHost.substring(i + 1));
+ } catch (NumberFormatException ex) {
+ // ignore it
+ }
+ proxyHost = proxyHost.substring(0, i);
+ }
+ proxyPort = PropUtil.getIntProperty(props,
+ prefix + ".proxy.port", proxyPort);
+ err = "Using web proxy host, port: " + proxyHost + ", " + proxyPort;
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("web proxy host " + proxyHost + ", port " + proxyPort);
+ if (proxyUser != null)
+ logger.finer("web proxy user " + proxyUser + ", password " +
+ (proxyPassword == null ? "" : ""));
+ }
+ } else if ((socksHost =
+ props.getProperty(prefix + ".socks.host", null)) != null) {
+ int i = socksHost.indexOf(':');
+ if (i >= 0) {
+ try {
+ socksPort = Integer.parseInt(socksHost.substring(i + 1));
+ } catch (NumberFormatException ex) {
+ // ignore it
+ }
+ socksHost = socksHost.substring(0, i);
+ }
+ socksPort = PropUtil.getIntProperty(props,
+ prefix + ".socks.port", socksPort);
+ err = "Using SOCKS host, port: " + socksHost + ", " + socksPort;
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("socks host " + socksHost + ", port " + socksPort);
+ }
+
+ if (sf != null && !(sf instanceof SSLSocketFactory))
+ socket = sf.createSocket();
+ if (socket == null) {
+ if (socksHost != null) {
+ socket = new Socket(
+ new java.net.Proxy(java.net.Proxy.Type.SOCKS,
+ new InetSocketAddress(socksHost, socksPort)));
+ } else if (PropUtil.getBooleanProperty(props,
+ prefix + ".usesocketchannels", false)) {
+ logger.finer("using SocketChannels");
+ socket = SocketChannel.open().socket();
+ } else
+ socket = new Socket();
+ }
+ if (to >= 0) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("set socket read timeout " + to);
+ socket.setSoTimeout(to);
+ }
+ int writeTimeout = PropUtil.getIntProperty(props,
+ prefix + ".writetimeout", -1);
+ if (writeTimeout != -1) { // wrap original
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest("set socket write timeout " + writeTimeout);
+ socket = new WriteTimeoutSocket(socket, writeTimeout);
+ }
+ if (localaddr != null)
+ socket.bind(new InetSocketAddress(localaddr, localport));
+ try {
+ logger.finest("connecting...");
+ if (proxyHost != null)
+ proxyConnect(socket, proxyHost, proxyPort,
+ proxyUser, proxyPassword, host, port, cto);
+ else if (cto >= 0)
+ socket.connect(new InetSocketAddress(host, port), cto);
+ else
+ socket.connect(new InetSocketAddress(host, port));
+ logger.finest("success!");
+ } catch (IOException ex) {
+ logger.log(Level.FINEST, "connection failed", ex);
+ throw new SocketConnectException(err, ex, host, port, cto);
+ }
+
+ /*
+ * If we want an SSL connection and we didn't get an SSLSocket,
+ * wrap our plain Socket with an SSLSocket.
+ */
+ if ((useSSL || sf instanceof SSLSocketFactory) &&
+ !(socket instanceof SSLSocket)) {
+ String trusted;
+ SSLSocketFactory ssf;
+ if ((trusted = props.getProperty(prefix + ".ssl.trust")) != null) {
+ try {
+ MailSSLSocketFactory msf = new MailSSLSocketFactory();
+ if (trusted.equals("*"))
+ msf.setTrustAllHosts(true);
+ else
+ msf.setTrustedHosts(trusted.split("\\s+"));
+ ssf = msf;
+ } catch (GeneralSecurityException gex) {
+ IOException ioex = new IOException(
+ "Can't create MailSSLSocketFactory");
+ ioex.initCause(gex);
+ throw ioex;
+ }
+ } else if (sf instanceof SSLSocketFactory)
+ ssf = (SSLSocketFactory)sf;
+ else
+ ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+ socket = ssf.createSocket(socket, host, port, true);
+ sf = ssf;
+ }
+
+ /*
+ * No matter how we created the socket, if it turns out to be an
+ * SSLSocket, configure it.
+ */
+ configureSSLSocket(socket, host, props, prefix, sf);
+
+ return socket;
+ }
+
+ /**
+ * Return a socket factory of the specified class.
+ */
+ private static SocketFactory getSocketFactory(String sfClass)
+ throws ClassNotFoundException,
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (sfClass == null || sfClass.length() == 0)
+ return null;
+
+ // dynamically load the class
+
+ ClassLoader cl = getContextClassLoader();
+ Class> clsSockFact = null;
+ if (cl != null) {
+ try {
+ clsSockFact = Class.forName(sfClass, false, cl);
+ } catch (ClassNotFoundException cex) { }
+ }
+ if (clsSockFact == null)
+ clsSockFact = Class.forName(sfClass);
+ // get & invoke the getDefault() method
+ Method mthGetDefault = clsSockFact.getMethod("getDefault",
+ new Class>[]{});
+ SocketFactory sf = (SocketFactory)
+ mthGetDefault.invoke(new Object(), new Object[]{});
+ return sf;
+ }
+
+ /**
+ * Start TLS on an existing socket.
+ * Supports the "STARTTLS" command in many protocols.
+ * This version for compatibility with possible third party code
+ * that might've used this API even though it shouldn't.
+ *
+ * @param socket the existing socket
+ * @return the wrapped Socket
+ * @exception IOException for I/O errors
+ * @deprecated
+ */
+ @Deprecated
+ public static Socket startTLS(Socket socket) throws IOException {
+ return startTLS(socket, new Properties(), "socket");
+ }
+
+ /**
+ * Start TLS on an existing socket.
+ * Supports the "STARTTLS" command in many protocols.
+ * This version for compatibility with possible third party code
+ * that might've used this API even though it shouldn't.
+ *
+ * @param socket the existing socket
+ * @param props the properties
+ * @param prefix the property prefix
+ * @return the wrapped Socket
+ * @exception IOException for I/O errors
+ * @deprecated
+ */
+ @Deprecated
+ public static Socket startTLS(Socket socket, Properties props,
+ String prefix) throws IOException {
+ InetAddress a = socket.getInetAddress();
+ String host = a.getHostName();
+ return startTLS(socket, host, props, prefix);
+ }
+
+ /**
+ * Start TLS on an existing socket.
+ * Supports the "STARTTLS" command in many protocols.
+ *
+ * @param socket the existing socket
+ * @param host the host the socket is connected to
+ * @param props the properties
+ * @param prefix the property prefix
+ * @return the wrapped Socket
+ * @exception IOException for I/O errors
+ */
+ public static Socket startTLS(Socket socket, String host, Properties props,
+ String prefix) throws IOException {
+ int port = socket.getPort();
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("startTLS host " + host + ", port " + port);
+
+ String sfErr = "unknown socket factory";
+ try {
+ SSLSocketFactory ssf = null;
+ SocketFactory sf = null;
+
+ // first, look for an SSL socket factory
+ Object sfo = props.get(prefix + ".ssl.socketFactory");
+ if (sfo instanceof SocketFactory) {
+ sf = (SocketFactory)sfo;
+ sfErr = "SSL socket factory instance " + sf;
+ }
+ if (sf == null) {
+ String sfClass =
+ props.getProperty(prefix + ".ssl.socketFactory.class");
+ sf = getSocketFactory(sfClass);
+ sfErr = "SSL socket factory class " + sfClass;
+ }
+ if (sf != null && sf instanceof SSLSocketFactory)
+ ssf = (SSLSocketFactory)sf;
+
+ // next, look for a regular socket factory that happens to be
+ // an SSL socket factory
+ if (ssf == null) {
+ sfo = props.get(prefix + ".socketFactory");
+ if (sfo instanceof SocketFactory) {
+ sf = (SocketFactory)sfo;
+ sfErr = "socket factory instance " + sf;
+ }
+ if (sf == null) {
+ String sfClass =
+ props.getProperty(prefix + ".socketFactory.class");
+ sf = getSocketFactory(sfClass);
+ sfErr = "socket factory class " + sfClass;
+ }
+ if (sf != null && sf instanceof SSLSocketFactory)
+ ssf = (SSLSocketFactory)sf;
+ }
+
+ // finally, use the default SSL socket factory
+ if (ssf == null) {
+ String trusted;
+ if ((trusted = props.getProperty(prefix + ".ssl.trust")) !=
+ null) {
+ try {
+ MailSSLSocketFactory msf = new MailSSLSocketFactory();
+ if (trusted.equals("*"))
+ msf.setTrustAllHosts(true);
+ else
+ msf.setTrustedHosts(trusted.split("\\s+"));
+ ssf = msf;
+ sfErr = "mail SSL socket factory";
+ } catch (GeneralSecurityException gex) {
+ IOException ioex = new IOException(
+ "Can't create MailSSLSocketFactory");
+ ioex.initCause(gex);
+ throw ioex;
+ }
+ } else {
+ ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+ sfErr = "default SSL socket factory";
+ }
+ }
+
+ socket = ssf.createSocket(socket, host, port, true);
+ configureSSLSocket(socket, host, props, prefix, ssf);
+ } catch (Exception ex) {
+ if (ex instanceof InvocationTargetException) {
+ Throwable t =
+ ((InvocationTargetException)ex).getTargetException();
+ if (t instanceof Exception)
+ ex = (Exception)t;
+ }
+ if (ex instanceof IOException)
+ throw (IOException)ex;
+ // wrap anything else before sending it on
+ IOException ioex = new IOException(
+ "Exception in startTLS using " + sfErr +
+ ": host, port: " +
+ host + ", " + port +
+ "; Exception: " + ex);
+ ioex.initCause(ex);
+ throw ioex;
+ }
+ return socket;
+ }
+
+ /**
+ * Configure the SSL options for the socket (if it's an SSL socket),
+ * based on the mail..ssl.protocols and
+ * mail..ssl.ciphersuites properties.
+ * Check the identity of the server as specified by the
+ * mail..ssl.checkserveridentity property.
+ */
+ private static void configureSSLSocket(Socket socket, String host,
+ Properties props, String prefix, SocketFactory sf)
+ throws IOException {
+ if (!(socket instanceof SSLSocket))
+ return;
+ SSLSocket sslsocket = (SSLSocket)socket;
+
+ String protocols = props.getProperty(prefix + ".ssl.protocols", null);
+ if (protocols != null)
+ sslsocket.setEnabledProtocols(stringArray(protocols));
+ else {
+ /*
+ * The UW IMAP server insists on at least the TLSv1
+ * protocol for STARTTLS, and won't accept the old SSLv2
+ * or SSLv3 protocols. Here we enable only the non-SSL
+ * protocols. XXX - this should probably be parameterized.
+ */
+ String[] prots = sslsocket.getEnabledProtocols();
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("SSL enabled protocols before " +
+ Arrays.asList(prots));
+ List eprots = new ArrayList<>();
+ for (int i = 0; i < prots.length; i++) {
+ if (prots[i] != null && !prots[i].startsWith("SSL"))
+ eprots.add(prots[i]);
+ }
+ sslsocket.setEnabledProtocols(
+ eprots.toArray(new String[eprots.size()]));
+ }
+ String ciphers = props.getProperty(prefix + ".ssl.ciphersuites", null);
+ if (ciphers != null)
+ sslsocket.setEnabledCipherSuites(stringArray(ciphers));
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("SSL enabled protocols after " +
+ Arrays.asList(sslsocket.getEnabledProtocols()));
+ logger.finer("SSL enabled ciphers after " +
+ Arrays.asList(sslsocket.getEnabledCipherSuites()));
+ }
+
+ /*
+ * Force the handshake to be done now so that we can report any
+ * errors (e.g., certificate errors) to the caller of the startTLS
+ * method.
+ */
+ sslsocket.startHandshake();
+
+ /*
+ * Check server identity and trust.
+ */
+ boolean idCheck = PropUtil.getBooleanProperty(props,
+ prefix + ".ssl.checkserveridentity", false);
+ if (idCheck)
+ checkServerIdentity(host, sslsocket);
+ if (sf instanceof MailSSLSocketFactory) {
+ MailSSLSocketFactory msf = (MailSSLSocketFactory)sf;
+ if (!msf.isServerTrusted(host, sslsocket)) {
+ throw cleanupAndThrow(sslsocket,
+ new IOException("Server is not trusted: " + host));
+ }
+ }
+ }
+
+ private static IOException cleanupAndThrow(Socket socket, IOException ife) {
+ try {
+ socket.close();
+ } catch (Throwable thr) {
+ if (isRecoverable(thr)) {
+ ife.addSuppressed(thr);
+ } else {
+ thr.addSuppressed(ife);
+ if (thr instanceof Error) {
+ throw (Error) thr;
+ }
+ if (thr instanceof RuntimeException) {
+ throw (RuntimeException) thr;
+ }
+ throw new RuntimeException("unexpected exception", thr);
+ }
+ }
+ return ife;
+ }
+
+ private static boolean isRecoverable(Throwable t) {
+ return (t instanceof Exception) || (t instanceof LinkageError);
+ }
+
+ /**
+ * Check the server from the Socket connection against the server name(s)
+ * as expressed in the server certificate (RFC 2595 check).
+ *
+ * @param server name of the server expected
+ * @param sslSocket SSLSocket connected to the server
+ * @exception IOException if we can't verify identity of server
+ */
+ private static void checkServerIdentity(String server, SSLSocket sslSocket)
+ throws IOException {
+
+ // Check against the server name(s) as expressed in server certificate
+ try {
+ java.security.cert.Certificate[] certChain =
+ sslSocket.getSession().getPeerCertificates();
+ if (certChain != null && certChain.length > 0 &&
+ certChain[0] instanceof X509Certificate &&
+ matchCert(server, (X509Certificate)certChain[0]))
+ return;
+ } catch (SSLPeerUnverifiedException e) {
+ sslSocket.close();
+ IOException ioex = new IOException(
+ "Can't verify identity of server: " + server);
+ ioex.initCause(e);
+ throw ioex;
+ }
+
+ // If we get here, there is nothing to consider the server as trusted.
+ sslSocket.close();
+ throw new IOException("Can't verify identity of server: " + server);
+ }
+
+ /**
+ * Do any of the names in the cert match the server name?
+ *
+ * @param server name of the server expected
+ * @param cert X509Certificate to get the subject's name from
+ * @return true if it matches
+ */
+ private static boolean matchCert(String server, X509Certificate cert) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("matchCert server " +
+ server + ", cert " + cert);
+
+ /*
+ * First, try to use sun.security.util.HostnameChecker,
+ * which exists in Sun's JDK starting with 1.4.1.
+ * We use reflection to access it in case it's not available
+ * in the JDK we're running on.
+ */
+ try {
+ Class> hnc = Class.forName("sun.security.util.HostnameChecker");
+ // invoke HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP)
+ // HostnameChecker.TYPE_LDAP == 2
+ // LDAP requires the same regex handling as we need
+ Method getInstance = hnc.getMethod("getInstance",
+ new Class>[] { byte.class });
+ Object hostnameChecker = getInstance.invoke(new Object(),
+ new Object[] { Byte.valueOf((byte)2) });
+
+ // invoke hostnameChecker.match( server, cert)
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("using sun.security.util.HostnameChecker");
+ Method match = hnc.getMethod("match",
+ new Class>[] { String.class, X509Certificate.class });
+ try {
+ match.invoke(hostnameChecker, new Object[] { server, cert });
+ return true;
+ } catch (InvocationTargetException cex) {
+ logger.log(Level.FINER, "HostnameChecker FAIL", cex);
+ return false;
+ }
+ } catch (Exception ex) {
+ logger.log(Level.FINER, "NO sun.security.util.HostnameChecker", ex);
+ // ignore failure and continue below
+ }
+
+ /*
+ * Lacking HostnameChecker, we implement a crude version of
+ * the same checks ourselves.
+ */
+ try {
+ /*
+ * Check each of the subjectAltNames.
+ * XXX - only checks DNS names, should also handle
+ * case where server name is a literal IP address
+ */
+ Collection> names = cert.getSubjectAlternativeNames();
+ if (names != null) {
+ boolean foundName = false;
+ for (Iterator> it = names.iterator(); it.hasNext(); ) {
+ List> nameEnt = it.next();
+ Integer type = (Integer)nameEnt.get(0);
+ if (type.intValue() == 2) { // 2 == dNSName
+ foundName = true;
+ String name = (String)nameEnt.get(1);
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("found name: " + name);
+ if (matchServer(server, name))
+ return true;
+ }
+ }
+ if (foundName) // found a name, but no match
+ return false;
+ }
+ } catch (CertificateParsingException ex) {
+ // ignore it
+ }
+
+ // XXX - following is a *very* crude parse of the name and ignores
+ // all sorts of important issues such as quoting
+ Pattern p = Pattern.compile("CN=([^,]*)");
+ Matcher m = p.matcher(cert.getSubjectX500Principal().getName());
+ if (m.find() && matchServer(server, m.group(1).trim()))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Does the server we're expecting to connect to match the
+ * given name from the server's certificate?
+ *
+ * @param server name of the server expected
+ * @param name name from the server's certificate
+ */
+ private static boolean matchServer(String server, String name) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("match server " + server + " with " + name);
+ if (name.startsWith("*.")) {
+ // match "foo.example.com" with "*.example.com"
+ String tail = name.substring(2);
+ if (tail.length() == 0)
+ return false;
+ int off = server.length() - tail.length();
+ if (off < 1)
+ return false;
+ // if tail matches and is preceeded by "."
+ return server.charAt(off - 1) == '.' &&
+ server.regionMatches(true, off, tail, 0, tail.length());
+ } else
+ return server.equalsIgnoreCase(name);
+ }
+
+ /**
+ * Use the HTTP CONNECT protocol to connect to a
+ * site through an HTTP proxy server.
+ *
+ * Protocol is roughly:
+ *
+ * CONNECT : HTTP/1.1
+ * Host: :
+ *
+ *
+ * HTTP/1.1 200 Connect established
+ *
+ *
+ *
+ */
+ private static void proxyConnect(Socket socket,
+ String proxyHost, int proxyPort,
+ String proxyUser, String proxyPassword,
+ String host, int port, int cto)
+ throws IOException {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine("connecting through proxy " +
+ proxyHost + ":" + proxyPort + " to " +
+ host + ":" + port);
+ if (cto >= 0)
+ socket.connect(new InetSocketAddress(proxyHost, proxyPort), cto);
+ else
+ socket.connect(new InetSocketAddress(proxyHost, proxyPort));
+ PrintStream os = new PrintStream(socket.getOutputStream(), false,
+ StandardCharsets.UTF_8.name());
+ StringBuilder requestBuilder = new StringBuilder();
+ requestBuilder.append("CONNECT ").append(host).append(":").append(port).
+ append(" HTTP/1.1\r\n");
+ requestBuilder.append("Host: ").append(host).append(":").append(port).
+ append("\r\n");
+ if (proxyUser != null && proxyPassword != null) {
+ byte[] upbytes = (proxyUser + ':' + proxyPassword).
+ getBytes(StandardCharsets.UTF_8);
+ String proxyHeaderValue = new String(
+ BASE64EncoderStream.encode(upbytes),
+ StandardCharsets.US_ASCII);
+ requestBuilder.append("Proxy-Authorization: Basic ").
+ append(proxyHeaderValue).append("\r\n");
+ }
+ requestBuilder.append("Proxy-Connection: keep-alive\r\n\r\n");
+ os.print(requestBuilder.toString());
+ os.flush();
+ BufferedReader r = new BufferedReader(new InputStreamReader(
+ socket.getInputStream(), StandardCharsets.UTF_8));
+ String line;
+ boolean first = true;
+ while ((line = r.readLine()) != null) {
+ if (line.length() == 0)
+ break;
+ logger.finest(line);
+ if (first) {
+ StringTokenizer st = new StringTokenizer(line);
+ String http = st.nextToken();
+ String code = st.nextToken();
+ if (!code.equals("200")) {
+ try {
+ socket.close();
+ } catch (IOException ioex) {
+ // ignored
+ }
+ ConnectException ex = new ConnectException(
+ "connection through proxy " +
+ proxyHost + ":" + proxyPort + " to " +
+ host + ":" + port + " failed: " + line);
+ logger.log(Level.FINE, "connect failed", ex);
+ throw ex;
+ }
+ first = false;
+ }
+ }
+ }
+
+ /**
+ * Parse a string into whitespace separated tokens
+ * and return the tokens in an array.
+ */
+ private static String[] stringArray(String s) {
+ StringTokenizer st = new StringTokenizer(s);
+ List tokens = new ArrayList<>();
+ while (st.hasMoreTokens())
+ tokens.add(st.nextToken());
+ return tokens.toArray(new String[tokens.size()]);
+ }
+
+ /**
+ * Convenience method to get our context class loader.
+ * Assert any privileges we might have and then call the
+ * Thread.getContextClassLoader method.
+ */
+ private static ClassLoader getContextClassLoader() {
+ return
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public ClassLoader run() {
+ ClassLoader cl = null;
+ try {
+ cl = Thread.currentThread().getContextClassLoader();
+ } catch (SecurityException ex) { }
+ return cl;
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/TraceInputStream.java b/app/src/main/java/com/sun/mail/util/TraceInputStream.java
new file mode 100644
index 0000000000..fceb7ac45a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/TraceInputStream.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.logging.Level;
+
+/**
+ * This class is a FilterInputStream that writes the bytes
+ * being read from the given input stream into the given output
+ * stream. This class is typically used to provide a trace of
+ * the data that is being retrieved from an input stream.
+ *
+ * @author John Mani
+ */
+
+public class TraceInputStream extends FilterInputStream {
+ private boolean trace = false;
+ private boolean quote = false;
+ private OutputStream traceOut;
+
+ /**
+ * Creates an input stream filter built on top of the specified
+ * input stream.
+ *
+ * @param in the underlying input stream.
+ * @param logger log trace here
+ */
+ public TraceInputStream(InputStream in, MailLogger logger) {
+ super(in);
+ this.trace = logger.isLoggable(Level.FINEST);
+ this.traceOut = new LogOutputStream(logger);
+ }
+
+ /**
+ * Creates an input stream filter built on top of the specified
+ * input stream.
+ *
+ * @param in the underlying input stream.
+ * @param traceOut the trace stream.
+ */
+ public TraceInputStream(InputStream in, OutputStream traceOut) {
+ super(in);
+ this.traceOut = traceOut;
+ }
+
+ /**
+ * Set trace mode.
+ * @param trace the trace mode
+ */
+ public void setTrace(boolean trace) {
+ this.trace = trace;
+ }
+
+ /**
+ * Set quote mode.
+ * @param quote the quote mode
+ */
+ public void setQuote(boolean quote) {
+ this.quote = quote;
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. Returns
+ * -1
if no data is available. Writes out the read
+ * byte into the trace stream, if trace mode is true
+ */
+ @Override
+ public int read() throws IOException {
+ int b = in.read();
+ if (trace && b != -1) {
+ if (quote)
+ writeByte(b);
+ else
+ traceOut.write(b);
+ }
+ return b;
+ }
+
+ /**
+ * Reads up to len
bytes of data from this input stream
+ * into an array of bytes. Returns -1
if no more data
+ * is available. Writes out the read bytes into the trace stream, if
+ * trace mode is true
+ */
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ int count = in.read(b, off, len);
+ if (trace && count != -1) {
+ if (quote) {
+ for (int i = 0; i < count; i++)
+ writeByte(b[off + i]);
+ } else
+ traceOut.write(b, off, count);
+ }
+ return count;
+ }
+
+ /**
+ * Write a byte in a way that every byte value is printable ASCII.
+ */
+ private final void writeByte(int b) throws IOException {
+ b &= 0xff;
+ if (b > 0x7f) {
+ traceOut.write('M');
+ traceOut.write('-');
+ b &= 0x7f;
+ }
+ if (b == '\r') {
+ traceOut.write('\\');
+ traceOut.write('r');
+ } else if (b == '\n') {
+ traceOut.write('\\');
+ traceOut.write('n');
+ traceOut.write('\n');
+ } else if (b == '\t') {
+ traceOut.write('\\');
+ traceOut.write('t');
+ } else if (b < ' ') {
+ traceOut.write('^');
+ traceOut.write('@' + b);
+ } else {
+ traceOut.write(b);
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/TraceOutputStream.java b/app/src/main/java/com/sun/mail/util/TraceOutputStream.java
new file mode 100644
index 0000000000..28f282015a
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/TraceOutputStream.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.logging.Level;
+
+/**
+ * This class is a subclass of DataOutputStream that copies the
+ * data being written into the DataOutputStream into another output
+ * stream. This class is used here to provide a debug trace of the
+ * stuff thats being written out into the DataOutputStream.
+ *
+ * @author John Mani
+ */
+
+public class TraceOutputStream extends FilterOutputStream {
+ private boolean trace = false;
+ private boolean quote = false;
+ private OutputStream traceOut;
+
+ /**
+ * Creates an output stream filter built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream.
+ * @param logger log trace here
+ */
+ public TraceOutputStream(OutputStream out, MailLogger logger) {
+ super(out);
+ this.trace = logger.isLoggable(Level.FINEST);
+ this.traceOut = new LogOutputStream(logger);;
+ }
+
+ /**
+ * Creates an output stream filter built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream.
+ * @param traceOut the trace stream.
+ */
+ public TraceOutputStream(OutputStream out, OutputStream traceOut) {
+ super(out);
+ this.traceOut = traceOut;
+ }
+
+ /**
+ * Set the trace mode.
+ *
+ * @param trace the trace mode
+ */
+ public void setTrace(boolean trace) {
+ this.trace = trace;
+ }
+
+ /**
+ * Set quote mode.
+ * @param quote the quote mode
+ */
+ public void setQuote(boolean quote) {
+ this.quote = quote;
+ }
+
+ /**
+ * Writes the specified byte
to this output stream.
+ * Writes out the byte into the trace stream if the trace mode
+ * is true
+ *
+ * @param b the byte to write
+ * @exception IOException for I/O errors
+ */
+ @Override
+ public void write(int b) throws IOException {
+ if (trace) {
+ if (quote)
+ writeByte(b);
+ else
+ traceOut.write(b);
+ }
+ out.write(b);
+ }
+
+ /**
+ * Writes b.length
bytes to this output stream.
+ * Writes out the bytes into the trace stream if the trace
+ * mode is true
+ *
+ * @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 {
+ if (trace) {
+ if (quote) {
+ for (int i = 0; i < len; i++)
+ writeByte(b[off + i]);
+ } else
+ traceOut.write(b, off, len);
+ }
+ out.write(b, off, len);
+ }
+
+ /**
+ * Write a byte in a way that every byte value is printable ASCII.
+ */
+ private final void writeByte(int b) throws IOException {
+ b &= 0xff;
+ if (b > 0x7f) {
+ traceOut.write('M');
+ traceOut.write('-');
+ b &= 0x7f;
+ }
+ if (b == '\r') {
+ traceOut.write('\\');
+ traceOut.write('r');
+ } else if (b == '\n') {
+ traceOut.write('\\');
+ traceOut.write('n');
+ traceOut.write('\n');
+ } else if (b == '\t') {
+ traceOut.write('\\');
+ traceOut.write('t');
+ } else if (b < ' ') {
+ traceOut.write('^');
+ traceOut.write('@' + b);
+ } else {
+ traceOut.write(b);
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/UUDecoderStream.java b/app/src/main/java/com/sun/mail/util/UUDecoderStream.java
new file mode 100644
index 0000000000..82f957cc0b
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/UUDecoderStream.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a UUDecoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class UUDecoderStream extends FilterInputStream {
+ private String name;
+ private int mode;
+
+ private byte[] buffer = new byte[45]; // max decoded chars in a line = 45
+ private int bufsize = 0; // size of the cache
+ private int index = 0; // index into the cache
+ private boolean gotPrefix = false;
+ private boolean gotEnd = false;
+ private LineInputStream lin;
+ private boolean ignoreErrors;
+ private boolean ignoreMissingBeginEnd;
+ private String readAhead;
+
+ /**
+ * Create a UUdecoder that decodes the specified input stream.
+ * The System property mail.mime.uudecode.ignoreerrors
+ * controls whether errors in the encoded data cause an exception
+ * or are ignored. The default is false (errors cause exception).
+ * The System property mail.mime.uudecode.ignoremissingbeginend
+ * controls whether a missing begin or end line cause an exception
+ * or are ignored. The default is false (errors cause exception).
+ * @param in the input stream
+ */
+ public UUDecoderStream(InputStream in) {
+ super(in);
+ lin = new LineInputStream(in);
+ // default to false
+ ignoreErrors = PropUtil.getBooleanSystemProperty(
+ "mail.mime.uudecode.ignoreerrors", false);
+ // default to false
+ ignoreMissingBeginEnd = PropUtil.getBooleanSystemProperty(
+ "mail.mime.uudecode.ignoremissingbeginend", false);
+ }
+
+ /**
+ * Create a UUdecoder that decodes the specified input stream.
+ * @param in the input stream
+ * @param ignoreErrors ignore errors?
+ * @param ignoreMissingBeginEnd ignore missing begin or end?
+ */
+ public UUDecoderStream(InputStream in, boolean ignoreErrors,
+ boolean ignoreMissingBeginEnd) {
+ super(in);
+ lin = new LineInputStream(in);
+ this.ignoreErrors = ignoreErrors;
+ this.ignoreMissingBeginEnd = ignoreMissingBeginEnd;
+ }
+
+ /**
+ * Read the next decoded byte from this input stream. The byte
+ * is returned as an int
in the range 0
+ * to 255
. If no byte is available because the end of
+ * the stream has been reached, the value -1
is returned.
+ * This method blocks until input data is available, the end of the
+ * stream is detected, or an exception is thrown.
+ *
+ * @return next byte of data, or -1
if the end of
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read() throws IOException {
+ if (index >= bufsize) {
+ readPrefix();
+ if (!decode())
+ return -1;
+ index = 0; // reset index into buffer
+ }
+ return buffer[index++] & 0xff; // return lower byte
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ int i, c;
+ for (i = 0; i < len; i++) {
+ if ((c = read()) == -1) {
+ if (i == 0) // At end of stream, so we should
+ i = -1; // return -1, NOT 0.
+ break;
+ }
+ buf[off+i] = (byte)c;
+ }
+ return i;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int available() throws IOException {
+ // This is only an estimate, since in.available()
+ // might include CRLFs too ..
+ return ((in.available() * 3)/4 + (bufsize-index));
+ }
+
+ /**
+ * Get the "name" field from the prefix. This is meant to
+ * be the pathname of the decoded file
+ *
+ * @return name of decoded file
+ * @exception IOException if an I/O error occurs.
+ */
+ public String getName() throws IOException {
+ readPrefix();
+ return name;
+ }
+
+ /**
+ * Get the "mode" field from the prefix. This is the permission
+ * mode of the source file.
+ *
+ * @return permission mode of source file
+ * @exception IOException if an I/O error occurs.
+ */
+ public int getMode() throws IOException {
+ readPrefix();
+ return mode;
+ }
+
+ /**
+ * UUencoded streams start off with the line:
+ * "begin "
+ * Search for this prefix and gobble it up.
+ */
+ private void readPrefix() throws IOException {
+ if (gotPrefix) // got the prefix
+ return;
+
+ mode = 0666; // defaults, overridden below
+ name = "encoder.buf"; // same default used by encoder
+ String line;
+ for (;;) {
+ // read till we get the prefix: "begin MODE FILENAME"
+ line = lin.readLine(); // NOTE: readLine consumes CRLF pairs too
+ if (line == null) {
+ if (!ignoreMissingBeginEnd)
+ throw new DecodingException("UUDecoder: Missing begin");
+ // at EOF, fake it
+ gotPrefix = true;
+ gotEnd = true;
+ break;
+ }
+ if (line.regionMatches(false, 0, "begin", 0, 5)) {
+ try {
+ mode = Integer.parseInt(line.substring(6,9));
+ } catch (NumberFormatException ex) {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "UUDecoder: Error in mode: " + ex.toString());
+ }
+ if (line.length() > 10) {
+ name = line.substring(10);
+ } else {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "UUDecoder: Missing name: " + line);
+ }
+ gotPrefix = true;
+ break;
+ } else if (ignoreMissingBeginEnd && line.length() != 0) {
+ int count = line.charAt(0);
+ count = (count - ' ') & 0x3f;
+ int need = ((count * 8)+5)/6;
+ if (need == 0 || line.length() >= need + 1) {
+ /*
+ * Looks like a legitimate encoded line.
+ * Pretend we saw the "begin" line and
+ * save this line for later processing in
+ * decode().
+ */
+ readAhead = line;
+ gotPrefix = true; // fake it
+ break;
+ }
+ }
+ }
+ }
+
+ private boolean decode() throws IOException {
+
+ if (gotEnd)
+ return false;
+ bufsize = 0;
+ int count = 0;
+ String line;
+ for (;;) {
+ /*
+ * If we ignored a missing "begin", the first line
+ * will be saved in readAhead.
+ */
+ if (readAhead != null) {
+ line = readAhead;
+ readAhead = null;
+ } else
+ line = lin.readLine();
+
+ /*
+ * Improperly encoded data sometimes omits the zero length
+ * line that starts with a space character, we detect the
+ * following "end" line here.
+ */
+ if (line == null) {
+ if (!ignoreMissingBeginEnd)
+ throw new DecodingException(
+ "UUDecoder: Missing end at EOF");
+ gotEnd = true;
+ return false;
+ }
+ if (line.equals("end")) {
+ gotEnd = true;
+ return false;
+ }
+ if (line.length() == 0)
+ continue;
+ count = line.charAt(0);
+ if (count < ' ') {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "UUDecoder: Buffer format error");
+ continue;
+ }
+
+ /*
+ * The first character in a line is the number of original (not
+ * the encoded atoms) characters in the line. Note that all the
+ * code below has to handle the character that indicates
+ * end of encoded stream.
+ */
+ count = (count - ' ') & 0x3f;
+
+ if (count == 0) {
+ line = lin.readLine();
+ if (line == null || !line.equals("end")) {
+ if (!ignoreMissingBeginEnd)
+ throw new DecodingException(
+ "UUDecoder: Missing End after count 0 line");
+ }
+ gotEnd = true;
+ return false;
+ }
+
+ int need = ((count * 8)+5)/6;
+//System.out.println("count " + count + ", need " + need + ", len " + line.length());
+ if (line.length() < need + 1) {
+ if (!ignoreErrors)
+ throw new DecodingException(
+ "UUDecoder: Short buffer error");
+ continue;
+ }
+
+ // got a line we're committed to, break out and decode it
+ break;
+ }
+
+ int i = 1;
+ byte a, b;
+ /*
+ * A correct uuencoder always encodes 3 characters at a time, even
+ * if there aren't 3 characters left. But since some people out
+ * there have broken uuencoders we handle the case where they
+ * don't include these "unnecessary" characters.
+ */
+ while (bufsize < count) {
+ // continue decoding until we get 'count' decoded chars
+ a = (byte)((line.charAt(i++) - ' ') & 0x3f);
+ b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+ buffer[bufsize++] = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
+
+ if (bufsize < count) {
+ a = b;
+ b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+ buffer[bufsize++] =
+ (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
+ }
+
+ if (bufsize < count) {
+ a = b;
+ b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+ buffer[bufsize++] = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
+ }
+ }
+ return true;
+ }
+
+ /*** begin TEST program *****
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ UUDecoderStream decoder = new UUDecoderStream(infile);
+ int c;
+
+ try {
+ while ((c = decoder.read()) != -1)
+ System.out.write(c);
+ System.out.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ **** end TEST program ****/
+}
diff --git a/app/src/main/java/com/sun/mail/util/UUEncoderStream.java b/app/src/main/java/com/sun/mail/util/UUEncoderStream.java
new file mode 100644
index 0000000000..183cc34737
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/UUEncoderStream.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a UUEncoder. It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter. The Encoding
+ * is done as the bytes are written out.
+ *
+ * @author John Mani
+ */
+
+public class UUEncoderStream extends FilterOutputStream {
+ private byte[] buffer; // cache of bytes that are yet to be encoded
+ private int bufsize = 0; // size of the cache
+ private boolean wrotePrefix = false;
+ private boolean wroteSuffix = false;
+
+ private String name; // name of file
+ private int mode; // permissions mode
+
+ /**
+ * Create a UUencoder that encodes the specified input stream
+ * @param out the output stream
+ */
+ public UUEncoderStream(OutputStream out) {
+ this(out, "encoder.buf", 0644);
+ }
+
+ /**
+ * Create a UUencoder that encodes the specified input stream
+ * @param out the output stream
+ * @param name Specifies a name for the encoded buffer
+ */
+ public UUEncoderStream(OutputStream out, String name) {
+ this(out, name, 0644);
+ }
+
+ /**
+ * Create a UUencoder that encodes the specified input stream
+ * @param out the output stream
+ * @param name Specifies a name for the encoded buffer
+ * @param mode Specifies permission mode for the encoded buffer
+ */
+ public UUEncoderStream(OutputStream out, String name, int mode) {
+ super(out);
+ this.name = name;
+ this.mode = mode;
+ buffer = new byte[45];
+ }
+
+ /**
+ * Set up the buffer name and permission mode.
+ * This method has any effect only if it is invoked before
+ * you start writing into the output stream
+ *
+ * @param name the buffer name
+ * @param mode the permission mode
+ */
+ public void setNameMode(String name, int mode) {
+ this.name = name;
+ this.mode = mode;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ for (int i = 0; i < len; i++)
+ write(b[off + i]);
+ }
+
+ @Override
+ public void write(byte[] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ /* buffer up characters till we get a line's worth, then encode
+ * and write them out. Max number of characters allowed per
+ * line is 45.
+ */
+ buffer[bufsize++] = (byte)c;
+ if (bufsize == 45) {
+ writePrefix();
+ encode();
+ bufsize = 0;
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (bufsize > 0) { // If there's unencoded characters in the buffer
+ writePrefix();
+ encode(); // .. encode them
+ bufsize = 0;
+ }
+ writeSuffix();
+ out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ flush();
+ out.close();
+ }
+
+ /**
+ * Write out the prefix: "begin "
+ */
+ private void writePrefix() throws IOException {
+ if (!wrotePrefix) {
+ // name should be ASCII, but who knows...
+ PrintStream ps = new PrintStream(out, false, "utf-8");
+ ps.format("begin %o %s%n", mode, name);
+ ps.flush();
+ wrotePrefix = true;
+ }
+ }
+
+ /**
+ * Write a single line containing space and the suffix line
+ * containing the single word "end" (terminated by a newline)
+ */
+ private void writeSuffix() throws IOException {
+ if (!wroteSuffix) {
+ PrintStream ps = new PrintStream(out, false, "us-ascii");
+ ps.println(" \nend");
+ ps.flush();
+ wroteSuffix = true;
+ }
+ }
+
+ /**
+ * Encode a line.
+ * Start off with the character count, followed by the encoded atoms
+ * and terminate with LF. (or is it CRLF or the local line-terminator ?)
+ * Take three bytes and encodes them into 4 characters
+ * If bufsize if not a multiple of 3, the remaining bytes are filled
+ * with '1'. This insures that the last line won't end in spaces
+ * and potentiallly be truncated.
+ */
+ private void encode() throws IOException {
+ byte a, b, c;
+ int c1, c2, c3, c4;
+ int i = 0;
+
+ // Start off with the count of characters in the line
+ out.write((bufsize & 0x3f) + ' ');
+
+ while (i < bufsize) {
+ a = buffer[i++];
+ if (i < bufsize) {
+ b = buffer[i++];
+ if (i < bufsize)
+ c = buffer[i++];
+ else // default c to 1
+ c = 1;
+ }
+ else { // default b & c to 1
+ b = 1;
+ c = 1;
+ }
+
+ c1 = (a >>> 2) & 0x3f;
+ c2 = ((a << 4) & 0x30) | ((b >>> 4) & 0xf);
+ c3 = ((b << 2) & 0x3c) | ((c >>> 6) & 0x3);
+ c4 = c & 0x3f;
+ out.write(c1 + ' ');
+ out.write(c2 + ' ');
+ out.write(c3 + ' ');
+ out.write(c4 + ' ');
+ }
+ // Terminate with LF. (should it be CRLF or local line-terminator ?)
+ out.write('\n');
+ }
+
+ /**** begin TEST program *****
+ public static void main(String argv[]) throws Exception {
+ FileInputStream infile = new FileInputStream(argv[0]);
+ UUEncoderStream encoder = new UUEncoderStream(System.out);
+ int c;
+
+ while ((c = infile.read()) != -1)
+ encoder.write(c);
+ encoder.close();
+ }
+ **** end TEST program *****/
+}
diff --git a/app/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java b/app/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java
new file mode 100644
index 0000000000..1c84b52985
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.*;
+import java.util.Collections;
+import java.util.Set;
+import java.nio.channels.SocketChannel;
+import java.lang.reflect.*;
+
+/**
+ * A special Socket that uses a ScheduledExecutorService to
+ * implement timeouts for writes. The write timeout is specified
+ * (in milliseconds) when the WriteTimeoutSocket is created.
+ *
+ * @author Bill Shannon
+ */
+public class WriteTimeoutSocket extends Socket {
+
+ // delegate all operations to this socket
+ private final Socket socket;
+ // to schedule task to cancel write after timeout
+ private final ScheduledExecutorService ses;
+ // the timeout, in milliseconds
+ private final int timeout;
+
+ public WriteTimeoutSocket(Socket socket, int timeout) throws IOException {
+ this.socket = socket;
+ // XXX - could share executor with all instances?
+ this.ses = Executors.newScheduledThreadPool(1);
+ this.timeout = timeout;
+ }
+
+ public WriteTimeoutSocket(int timeout) throws IOException {
+ this(new Socket(), timeout);
+ }
+
+ public WriteTimeoutSocket(InetAddress address, int port, int timeout)
+ throws IOException {
+ this(timeout);
+ socket.connect(new InetSocketAddress(address, port));
+ }
+
+ public WriteTimeoutSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort, int timeout)
+ throws IOException {
+ this(timeout);
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(address, port));
+ }
+
+ public WriteTimeoutSocket(String host, int port, int timeout)
+ throws IOException {
+ this(timeout);
+ socket.connect(new InetSocketAddress(host, port));
+ }
+
+ public WriteTimeoutSocket(String host, int port,
+ InetAddress localAddress, int localPort, int timeout)
+ throws IOException {
+ this(timeout);
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(host, port));
+ }
+
+ // override all Socket methods and delegate to underlying Socket
+
+ @Override
+ public void connect(SocketAddress remote) throws IOException {
+ socket.connect(remote, 0);
+ }
+
+ @Override
+ public void connect(SocketAddress remote, int timeout) throws IOException {
+ socket.connect(remote, timeout);
+ }
+
+ @Override
+ public void bind(SocketAddress local) throws IOException {
+ socket.bind(local);
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return socket.getRemoteSocketAddress();
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ return socket.getLocalSocketAddress();
+ }
+
+ @Override
+ public void setPerformancePreferences(int connectionTime, int latency,
+ int bandwidth) {
+ socket.setPerformancePreferences(connectionTime, latency, bandwidth);
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ return socket.getChannel();
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ return socket.getInetAddress();
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ return socket.getLocalAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return socket.getPort();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return socket.getLocalPort();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return socket.getInputStream();
+ }
+
+ @Override
+ public synchronized OutputStream getOutputStream() throws IOException {
+ // wrap the returned stream to implement write timeout
+ return new TimeoutOutputStream(socket.getOutputStream(), ses, timeout);
+ }
+
+ @Override
+ public void setTcpNoDelay(boolean on) throws SocketException {
+ socket.setTcpNoDelay(on);
+ }
+
+ @Override
+ public boolean getTcpNoDelay() throws SocketException {
+ return socket.getTcpNoDelay();
+ }
+
+ @Override
+ public void setSoLinger(boolean on, int linger) throws SocketException {
+ socket.setSoLinger(on, linger);
+ }
+
+ @Override
+ public int getSoLinger() throws SocketException {
+ return socket.getSoLinger();
+ }
+
+ @Override
+ public void sendUrgentData(int data) throws IOException {
+ socket.sendUrgentData(data);
+ }
+
+ @Override
+ public void setOOBInline(boolean on) throws SocketException {
+ socket.setOOBInline(on);
+ }
+
+ @Override
+ public boolean getOOBInline() throws SocketException {
+ return socket.getOOBInline();
+ }
+
+ @Override
+ public void setSoTimeout(int timeout) throws SocketException {
+ socket.setSoTimeout(timeout);
+ }
+
+ @Override
+ public int getSoTimeout() throws SocketException {
+ return socket.getSoTimeout();
+ }
+
+ @Override
+ public void setSendBufferSize(int size) throws SocketException {
+ socket.setSendBufferSize(size);
+ }
+
+ @Override
+ public int getSendBufferSize() throws SocketException {
+ return socket.getSendBufferSize();
+ }
+
+ @Override
+ public void setReceiveBufferSize(int size) throws SocketException {
+ socket.setReceiveBufferSize(size);
+ }
+
+ @Override
+ public int getReceiveBufferSize() throws SocketException {
+ return socket.getReceiveBufferSize();
+ }
+
+ @Override
+ public void setKeepAlive(boolean on) throws SocketException {
+ socket.setKeepAlive(on);
+ }
+
+ @Override
+ public boolean getKeepAlive() throws SocketException {
+ return socket.getKeepAlive();
+ }
+
+ @Override
+ public void setTrafficClass(int tc) throws SocketException {
+ socket.setTrafficClass(tc);
+ }
+
+ @Override
+ public int getTrafficClass() throws SocketException {
+ return socket.getTrafficClass();
+ }
+
+ @Override
+ public void setReuseAddress(boolean on) throws SocketException {
+ socket.setReuseAddress(on);
+ }
+
+ @Override
+ public boolean getReuseAddress() throws SocketException {
+ return socket.getReuseAddress();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ socket.close();
+ } finally {
+ ses.shutdownNow();
+ }
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ socket.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ socket.shutdownOutput();
+ }
+
+ @Override
+ public String toString() {
+ return socket.toString();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return socket.isConnected();
+ }
+
+ @Override
+ public boolean isBound() {
+ return socket.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return socket.isClosed();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return socket.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return socket.isOutputShutdown();
+ }
+
+ /*
+ * The following three methods were added to java.net.Socket in Java SE 9.
+ * Since they're not supported on Android, and since we know that we
+ * never use them in Jakarta Mail, we just stub them out here.
+ */
+ //@Override
+ public Socket setOption(SocketOption so, T val) throws IOException {
+ // socket.setOption(so, val);
+ // return this;
+ throw new UnsupportedOperationException("WriteTimeoutSocket.setOption");
+ }
+
+ //@Override
+ public T getOption(SocketOption so) throws IOException {
+ // return socket.getOption(so);
+ throw new UnsupportedOperationException("WriteTimeoutSocket.getOption");
+ }
+
+ //@Override
+ public Set> supportedOptions() {
+ // return socket.supportedOptions();
+ return Collections.emptySet();
+ }
+
+ /**
+ * KLUDGE for Android, which has this illegal non-Java Compatible method.
+ *
+ * @return the FileDescriptor object
+ */
+ public FileDescriptor getFileDescriptor$() {
+ try {
+ Method m = Socket.class.getDeclaredMethod("getFileDescriptor$");
+ return (FileDescriptor)m.invoke(socket);
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+}
+
+
+/**
+ * An OutputStream that wraps the Socket's OutputStream and uses
+ * the ScheduledExecutorService to schedule a task to close the
+ * socket (aborting the write) if the timeout expires.
+ */
+class TimeoutOutputStream extends OutputStream {
+ private final OutputStream os;
+ private final ScheduledExecutorService ses;
+ private final Callable timeoutTask;
+ private final int timeout;
+ private byte[] b1;
+
+ public TimeoutOutputStream(OutputStream os0, ScheduledExecutorService ses,
+ int timeout) throws IOException {
+ this.os = os0;
+ this.ses = ses;
+ this.timeout = timeout;
+ timeoutTask = new Callable() {
+ @Override
+ public Object call() throws Exception {
+ os.close(); // close the stream to abort the write
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public synchronized void write(int b) throws IOException {
+ if (b1 == null)
+ b1 = new byte[1];
+ b1[0] = (byte)b;
+ this.write(b1);
+ }
+
+ @Override
+ public synchronized void write(byte[] bs, int off, int len)
+ throws IOException {
+ if ((off < 0) || (off > bs.length) || (len < 0) ||
+ ((off + len) > bs.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+
+ // Implement timeout with a scheduled task
+ ScheduledFuture sf = null;
+ try {
+ try {
+ if (timeout > 0)
+ sf = ses.schedule(timeoutTask,
+ timeout, TimeUnit.MILLISECONDS);
+ } catch (RejectedExecutionException ex) {
+ // ignore it; Executor was shut down by another thread,
+ // the following write should fail with IOException
+ }
+ os.write(bs, off, len);
+ } finally {
+ if (sf != null)
+ sf.cancel(true);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ os.close();
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java b/app/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
new file mode 100644
index 0000000000..d2d889c215
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.text.MessageFormat;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+/**
+ * A LogRecord formatter that takes a sequence of LogRecords and combines them
+ * into a single summary result. Formating of the head, LogRecord, and tail are
+ * delegated to the wrapped formatter.
+ *
+ *
+ * By default each CollectorFormatter
is initialized using the
+ * following LogManager configuration properties where
+ * <formatter-name>
refers to the fully qualified class name
+ * or the fully qualified derived class name of the formatter. If properties
+ * are not defined, or contain invalid values, then the specified default values
+ * are used.
+ *
+ * <formatter-name>.comparator name of a
+ * {@linkplain java.util.Comparator} class used to choose the collected
+ * LogRecord
. If a comparator is specified then the max
+ * LogRecord
is chosen. If comparator is set to the string literal
+ * null, then the last record is chosen. (defaults to
+ * {@linkplain SeverityComparator})
+ *
+ * <formatter-name>.comparator.reverse a boolean
+ * true
to collect the min LogRecord
or
+ * false
to collect the max LogRecord
.
+ * (defaults to false
)
+ *
+ * <formatter-name>.format the
+ * {@linkplain java.text.MessageFormat MessageFormat} string used to format the
+ * collected summary statistics. The arguments are explained in detail in the
+ * {@linkplain #getTail(java.util.logging.Handler) getTail} documentation.
+ * (defaults to {0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer}
+ * more}\n
)
+ *
+ * <formatter-name>.formatter name of a Formatter
class
+ * used to format the collected LogRecord.
+ * (defaults to {@linkplain CompactFormatter})
+ *
+ *
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.2
+ */
+public class CollectorFormatter extends Formatter {
+ /**
+ * Avoid depending on JMX runtime bean to get the start time.
+ */
+ private static final long INIT_TIME = System.currentTimeMillis();
+ /**
+ * The message format string used as the formatted output.
+ */
+ private final String fmt;
+ /**
+ * The formatter used to format the chosen log record.
+ */
+ private final Formatter formatter;
+ /**
+ * The comparator used to pick the log record to format.
+ */
+ private final Comparator super LogRecord> comparator;
+ /**
+ * The last accepted record. Synchronized access is preferred over volatile
+ * for this class.
+ */
+ private LogRecord last;
+ /**
+ * The number of log records that have been formatted.
+ */
+ private long count;
+ /**
+ * The number of log produced containing at least one log record.
+ * Only incremented when this formatter is reset.
+ */
+ private long generation = 1L;
+ /**
+ * The number of log records that have been formatted with a thrown object.
+ */
+ private long thrown;
+ /**
+ * The eldest log record time or eldest time possible for this instance.
+ */
+ private long minMillis = INIT_TIME;
+ /**
+ * The newest log record time.
+ */
+ private long maxMillis = Long.MIN_VALUE;
+
+ /**
+ * Creates the formatter using the LogManager defaults.
+ *
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have LoggingPermission("control")
.
+ * @throws UndeclaredThrowableException if there are problems loading from
+ * the LogManager.
+ */
+ public CollectorFormatter() {
+ final String p = getClass().getName();
+ this.fmt = initFormat(p);
+ this.formatter = initFormatter(p);
+ this.comparator = initComparator(p);
+ }
+
+ /**
+ * Creates the formatter using the given format.
+ *
+ * @param format the message format or null to use the LogManager default.
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have LoggingPermission("control")
.
+ * @throws UndeclaredThrowableException if there are problems loading from
+ * the LogManager.
+ */
+ public CollectorFormatter(String format) {
+ final String p = getClass().getName();
+ this.fmt = format == null ? initFormat(p) : format;
+ this.formatter = initFormatter(p);
+ this.comparator = initComparator(p);
+ }
+
+ /**
+ * Creates the formatter using the given values.
+ *
+ * @param format the format string or null to use the LogManager default.
+ * @param f the formatter used on the collected log record or null to
+ * specify no formatter.
+ * @param c the comparator used to determine which log record to format or
+ * null to specify no comparator.
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have LoggingPermission("control")
.
+ * @throws UndeclaredThrowableException if there are problems loading from
+ * the LogManager.
+ */
+ public CollectorFormatter(String format, Formatter f,
+ Comparator super LogRecord> c) {
+ final String p = getClass().getName();
+ this.fmt = format == null ? initFormat(p) : format;
+ this.formatter = f;
+ this.comparator = c;
+ }
+
+ /**
+ * Accumulates log records which will be used to produce the final output.
+ * The output is generated using the {@link #getTail} method which also
+ * resets this formatter back to its original state.
+ *
+ * @param record the record to store.
+ * @return an empty string.
+ * @throws NullPointerException if the given record is null.
+ */
+ @Override
+ public String format(final LogRecord record) {
+ if (record == null) {
+ throw new NullPointerException();
+ }
+
+ boolean accepted;
+ do {
+ final LogRecord peek = peek();
+ //The self compare of the first record acts like a type check.
+ LogRecord update = apply(peek != null ? peek : record, record);
+ if (peek != update) { //Not identical.
+ update.getSourceMethodName(); //Infer caller, null check.
+ accepted = acceptAndUpdate(peek, update);
+ } else {
+ accepted = accept(peek, record);
+ }
+ } while (!accepted);
+ return "";
+ }
+
+ /**
+ * Formats the collected LogRecord and summary statistics. The collected
+ * results are reset after calling this method. The
+ * {@linkplain java.text.MessageFormat java.text} argument indexes are
+ * assigned to the following properties:
+ *
+ *
+ * {@code head} the
+ * {@linkplain Formatter#getHead(java.util.logging.Handler) head} string
+ * returned from the target formatter and
+ * {@linkplain #finish(java.lang.String) finished} by this formatter.
+ * {@code formatted} the current log record
+ * {@linkplain Formatter#format(java.util.logging.LogRecord) formatted} by
+ * the target formatter and {@linkplain #finish(java.lang.String) finished}
+ * by this formatter. If the formatter is null then record is formatted by
+ * this {@linkplain #formatMessage(java.util.logging.LogRecord) formatter}.
+ * {@code tail} the
+ * {@linkplain Formatter#getTail(java.util.logging.Handler) tail} string
+ * returned from the target formatter and
+ * {@linkplain #finish(java.lang.String) finished} by this formatter.
+ * {@code count} the total number of log records
+ * {@linkplain #format consumed} by this formatter.
+ * {@code remaining} the count minus one.
+ * {@code thrown} the total number of log records
+ * {@linkplain #format consumed} by this formatter with an assigned
+ * {@linkplain java.util.logging.LogRecord#getThrown throwable}.
+ * {@code normal messages} the count minus the thrown.
+ * {@code minMillis} the eldest log record
+ * {@linkplain java.util.logging.LogRecord#getMillis event time}
+ * {@linkplain #format consumed} by this formatter. If the count is zero
+ * then this is set to the previous max or approximate start time if there
+ * was no previous max. By default this parameter is defined as a number.
+ * The format type and format style rules from the
+ * {@linkplain java.text.MessageFormat} should be used to convert this from
+ * milliseconds to a date or time.
+ * {@code maxMillis} the most recent log record
+ * {@linkplain java.util.logging.LogRecord#getMillis event time}
+ * {@linkplain #format consumed} by this formatter. If the count is zero
+ * then this is set to the {@linkplain System#currentTimeMillis() current time}.
+ * By default this parameter is defined as a number. The format type and
+ * format style rules from the {@linkplain java.text.MessageFormat} should
+ * be used to convert this from milliseconds to a date or time.
+ * {@code elapsed} the elapsed time in milliseconds between the
+ * {@code maxMillis} and {@code minMillis}.
+ * {@code startTime} the approximate start time in milliseconds. By
+ * default this parameter is defined as a number. The format type and format
+ * style rules from the {@linkplain java.text.MessageFormat} should be used
+ * to convert this from milliseconds to a date or time.
+ * {@code currentTime} the
+ * {@linkplain System#currentTimeMillis() current time} in milliseconds. By
+ * default this parameter is defined as a number. The format type and format
+ * style rules from the {@linkplain java.text.MessageFormat} should be used
+ * to convert this from milliseconds to a date or time.
+ * {@code uptime} the elapsed time in milliseconds between the
+ * {@code currentTime} and {@code startTime}.
+ * {@code generation} the number times this method produced output with
+ * at least one {@linkplain #format consumed} log record. This can be used
+ * to track the number of complete reports this formatter has produced.
+ *
+ *
+ *
+ * Some example formats:
+ *
+ * {@code com.sun.mail.util.logging.CollectorFormatter.format={0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n}
+ *
+ * This prints the head ({@code {0}}), format ({@code {1}}), and tail
+ * ({@code {2}}) from the target formatter followed by the number of
+ * remaining ({@code {4}}) log records consumed by this formatter if there
+ * are any remaining records.
+ *
+ * Encoding failed.|NullPointerException: null String.getBytes(:913)... 3 more
+ *
+ * {@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between\n{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n}
+ *
+ * This prints the count ({@code {3}}) followed by the date and time of the
+ * eldest log record ({@code {7}}) and the date and time of the most recent
+ * log record ({@code {8}}).
+ *
+ * These 292 messages occurred between
+ * Tue, Jul 21 14:11:42:449 -0500 2009 and Fri, Nov 20 07:29:24:0 -0600 2009
+ *
+ * {@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between {9,choice,86400000#{7,date} {7,time} and {8,time}|86400000<{7,date} and {8,date}}\n}
+ *
+ * This prints the count ({@code {3}}) and then chooses the format based on
+ * the elapsed time ({@code {9}}). If the elapsed time is less than one day
+ * then the eldest log record ({@code {7}}) date and time is formatted
+ * followed by just the time of the most recent log record ({@code {8}}.
+ * Otherwise, the just the date of the eldest log record ({@code {7}}) and
+ * just the date of most recent log record ({@code {8}} is formatted.
+ *
+ * These 73 messages occurred between Jul 21, 2009 2:11:42 PM and 2:13:32 PM
+ *
+ * These 116 messages occurred between Jul 21, 2009 and Aug 20, 2009
+ *
+ * {@code com.sun.mail.util.logging.CollectorFormatter.format={13} alert reports since {10,date}.\n}
+ *
+ * This prints the generation ({@code {13}}) followed by the start time
+ * ({@code {10}}) formatted as a date.
+ *
+ * 4,320 alert reports since Jul 21, 2012.
+ *
+ *
+ *
+ * @param h the handler or null.
+ * @return the output string.
+ */
+ @Override
+ public String getTail(final Handler h) {
+ super.getTail(h); //Be forward compatible with super.getHead.
+ return formatRecord(h, true);
+ }
+
+ /**
+ * Formats the collected LogRecord and summary statistics. The LogRecord and
+ * summary statistics are not changed by calling this method.
+ *
+ * @return the current record formatted or the default toString.
+ * @see #getTail(java.util.logging.Handler)
+ */
+ @Override
+ public String toString() {
+ String result;
+ try {
+ result = formatRecord((Handler) null, false);
+ } catch (final RuntimeException ignore) {
+ result = super.toString();
+ }
+ return result;
+ }
+
+ /**
+ * Used to choose the collected LogRecord. This implementation returns the
+ * greater of two LogRecords.
+ *
+ * @param t the current record.
+ * @param u the record that could replace the current.
+ * @return the greater of the given log records.
+ * @throws NullPointerException may occur if either record is null.
+ */
+ protected LogRecord apply(final LogRecord t, final LogRecord u) {
+ if (t == null || u == null) {
+ throw new NullPointerException();
+ }
+
+ if (comparator != null) {
+ return comparator.compare(t, u) >= 0 ? t : u;
+ } else {
+ return u;
+ }
+ }
+
+ /**
+ * Updates the summary statistics only if the expected record matches the
+ * last record. The update record is not stored.
+ *
+ * @param e the LogRecord that is expected.
+ * @param u the LogRecord used to collect statistics.
+ * @return true if the last record was the expected record.
+ * @throws NullPointerException if the update record is null.
+ */
+ private synchronized boolean accept(final LogRecord e, final LogRecord u) {
+ /**
+ * LogRecord methods must be called before the check of the last stored
+ * record to guard against subclasses of LogRecord that might attempt to
+ * reset the state by triggering a call to getTail.
+ */
+ final long millis = u.getMillis(); //Null check.
+ final Throwable ex = u.getThrown();
+ if (last == e) { //Only if the exact same reference.
+ if (++count != 1L) {
+ minMillis = Math.min(minMillis, millis);
+ } else { //Show single records as instant and not a time period.
+ minMillis = millis;
+ }
+ maxMillis = Math.max(maxMillis, millis);
+
+ if (ex != null) {
+ ++thrown;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Resets all of the collected summary statistics including the LogRecord.
+ * @param min the current min milliseconds.
+ */
+ private synchronized void reset(final long min) {
+ if (last != null) {
+ last = null;
+ ++generation;
+ }
+
+ count = 0L;
+ thrown = 0L;
+ minMillis = min;
+ maxMillis = Long.MIN_VALUE;
+ }
+
+ /**
+ * Formats the given record with the head and tail.
+ *
+ * @param h the Handler or null.
+ * @param reset true if the summary statistics and LogRecord should be reset
+ * back to initial values.
+ * @return the formatted string.
+ * @see #getTail(java.util.logging.Handler)
+ */
+ private String formatRecord(final Handler h, final boolean reset) {
+ final LogRecord record;
+ final long c;
+ final long t;
+ final long g;
+ long msl;
+ long msh;
+ long now;
+ synchronized (this) {
+ record = last;
+ c = count;
+ g = generation;
+ t = thrown;
+ msl = minMillis;
+ msh = maxMillis;
+ now = System.currentTimeMillis();
+ if (c == 0L) {
+ msh = now;
+ }
+
+ if (reset) { //BUG ID 6351685
+ reset(msh);
+ }
+ }
+
+ final String head;
+ final String msg;
+ final String tail;
+ final Formatter f = this.formatter;
+ if (f != null) {
+ synchronized (f) {
+ head = f.getHead(h);
+ msg = record != null ? f.format(record) : "";
+ tail = f.getTail(h);
+ }
+ } else {
+ head = "";
+ msg = record != null ? formatMessage(record) : "";
+ tail = "";
+ }
+
+ Locale l = null;
+ if (record != null) {
+ ResourceBundle rb = record.getResourceBundle();
+ l = rb == null ? null : rb.getLocale();
+ }
+
+ final MessageFormat mf;
+ if (l == null) { //BUG ID 8039165
+ mf = new MessageFormat(fmt);
+ } else {
+ mf = new MessageFormat(fmt, l);
+ }
+
+ /**
+ * These arguments are described in the getTail documentation.
+ */
+ return mf.format(new Object[]{finish(head), finish(msg), finish(tail),
+ c, (c - 1L), t, (c - t), msl, msh, (msh - msl), INIT_TIME, now,
+ (now - INIT_TIME), g});
+ }
+
+ /**
+ * Applied to the head, format, and tail returned by the target formatter.
+ * This implementation trims all input strings.
+ *
+ * @param s the string to transform.
+ * @return the transformed string.
+ * @throws NullPointerException if the given string is null.
+ */
+ protected String finish(String s) {
+ return s.trim();
+ }
+
+ /**
+ * Peek at the current log record.
+ *
+ * @return null or the current log record.
+ */
+ private synchronized LogRecord peek() {
+ return this.last;
+ }
+
+ /**
+ * Updates the summary statistics and stores given LogRecord if the expected
+ * record matches the current record.
+ *
+ * @param e the expected record.
+ * @param u the update record.
+ * @return true if the update was performed.
+ * @throws NullPointerException if the update record is null.
+ */
+ private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) {
+ if (accept(e, u)) {
+ this.last = u;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the message format string from the LogManager or creates the default
+ * message format string.
+ *
+ * @param p the class name prefix.
+ * @return the format string.
+ * @throws NullPointerException if the given argument is null.
+ */
+ private String initFormat(final String p) {
+ String v = fromLogManager(p.concat(".format"));
+ if (v == null || v.length() == 0) {
+ v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n";
+ }
+ return v;
+ }
+
+ /**
+ * Gets and creates the formatter from the LogManager or creates the default
+ * formatter.
+ *
+ * @param p the class name prefix.
+ * @return the formatter.
+ * @throws NullPointerException if the given argument is null.
+ * @throws UndeclaredThrowableException if the formatter can not be created.
+ */
+ private Formatter initFormatter(final String p) {
+ Formatter f;
+ String v = fromLogManager(p.concat(".formatter"));
+ if (v != null && v.length() != 0) {
+ if (!"null".equalsIgnoreCase(v)) {
+ try {
+ f = LogManagerProperties.newFormatter(v);
+ } catch (final RuntimeException re) {
+ throw re;
+ } catch (final Exception e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ } else {
+ f = null;
+ }
+ } else {
+ //Don't force the byte code verifier to load the formatter.
+ f = Formatter.class.cast(new CompactFormatter());
+ }
+ return f;
+ }
+
+ /**
+ * Gets and creates the comparator from the LogManager or returns the
+ * default comparator.
+ *
+ * @param p the class name prefix.
+ * @return the comparator or null.
+ * @throws IllegalArgumentException if it was specified that the comparator
+ * should be reversed but no initial comparator was specified.
+ * @throws NullPointerException if the given argument is null.
+ * @throws UndeclaredThrowableException if the comparator can not be
+ * created.
+ */
+ @SuppressWarnings("unchecked")
+ private Comparator super LogRecord> initComparator(final String p) {
+ Comparator super LogRecord> c;
+ final String name = fromLogManager(p.concat(".comparator"));
+ final String reverse = fromLogManager(p.concat(".comparator.reverse"));
+ try {
+ if (name != null && name.length() != 0) {
+ if (!"null".equalsIgnoreCase(name)) {
+ c = LogManagerProperties.newComparator(name);
+ if (Boolean.parseBoolean(reverse)) {
+ assert c != null;
+ c = LogManagerProperties.reverseOrder(c);
+ }
+ } else {
+ if (reverse != null) {
+ throw new IllegalArgumentException(
+ "No comparator to reverse.");
+ } else {
+ c = null; //No ordering.
+ }
+ }
+ } else {
+ if (reverse != null) {
+ throw new IllegalArgumentException(
+ "No comparator to reverse.");
+ } else {
+ //Don't force the byte code verifier to load the comparator.
+ c = Comparator.class.cast(SeverityComparator.getInstance());
+ }
+ }
+ } catch (final RuntimeException re) {
+ throw re; //Avoid catch all.
+ } catch (final Exception e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ return c;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/CompactFormatter.java b/app/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
new file mode 100644
index 0000000000..7a1a2fb961
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.util.*;
+import java.util.logging.LogRecord;
+
+/**
+ * A plain text formatter that can produce fixed width output. By default this
+ * formatter will produce output no greater than 160 characters wide plus the
+ * separator and newline characters. Only specified fields support an
+ * {@linkplain #toAlternate(java.lang.String) alternate} fixed width format.
+ *
+ * By default each CompactFormatter
is initialized using the
+ * following LogManager configuration properties where
+ * <formatter-name>
refers to the fully qualified class name
+ * or the fully qualified derived class name of the formatter. If properties are
+ * not defined, or contain invalid values, then the specified default values are
+ * used.
+ *
+ * <formatter-name>.format - the {@linkplain java.util.Formatter
+ * format} string used to transform the output. The format string can be
+ * used to fix the output size. (defaults to %7$#.160s%n
)
+ *
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.2
+ */
+public class CompactFormatter extends java.util.logging.Formatter {
+
+ /**
+ * Load any declared classes to workaround GLASSFISH-21258.
+ */
+ static {
+ loadDeclaredClasses();
+ }
+
+ /**
+ * Used to load declared classes encase class loader doesn't allow loading
+ * during JVM termination. This method is used with unit testing.
+ *
+ * @return an array of classes never null.
+ */
+ private static Class>[] loadDeclaredClasses() {
+ return new Class>[]{Alternate.class};
+ }
+
+ /**
+ * Holds the java.util.Formatter pattern.
+ */
+ private final String fmt;
+
+ /**
+ * Creates an instance with a default format pattern.
+ */
+ public CompactFormatter() {
+ String p = getClass().getName();
+ this.fmt = initFormat(p);
+ }
+
+ /**
+ * Creates an instance with the given format pattern.
+ *
+ * @param format the {@linkplain java.util.Formatter pattern} or null to use
+ * the LogManager default. The arguments are described in the
+ * {@linkplain #format(java.util.logging.LogRecord) format} method.
+ */
+ public CompactFormatter(final String format) {
+ String p = getClass().getName();
+ this.fmt = format == null ? initFormat(p) : format;
+ }
+
+ /**
+ * Format the given log record and returns the formatted string. The
+ * {@linkplain java.util.Formatter#format(java.lang.String, java.lang.Object...)
+ * java.util} argument indexes are assigned to the following properties:
+ *
+ *
+ * {@code format} - the {@linkplain java.util.Formatter
+ * java.util.Formatter} format string specified in the
+ * <formatter-name>.format property or the format that was given when
+ * this formatter was created.
+ * {@code date} - if the log record supports nanoseconds then a
+ * ZonedDateTime object representing the event time of the log record in the
+ * system time zone. Otherwise, a {@linkplain Date} object representing
+ * {@linkplain LogRecord#getMillis event time} of the log record.
+ * {@code source} - a string representing the caller, if available;
+ * otherwise, the logger's name.
+ * {@code logger} - the logger's
+ * {@linkplain Class#getSimpleName() simple}
+ * {@linkplain LogRecord#getLoggerName() name}.
+ * {@code level} - the
+ * {@linkplain java.util.logging.Level#getLocalizedName log level}.
+ * {@code message} - the formatted log message returned from the
+ * {@linkplain #formatMessage(LogRecord)} method.
+ * {@code thrown} - a string representing the
+ * {@linkplain LogRecord#getThrown throwable} associated with the log record
+ * and a relevant stack trace element if available; otherwise, an empty
+ * string is used.
+ * {@code message|thrown} The message and the thrown properties joined
+ * as one parameter. This parameter supports
+ * {@linkplain #toAlternate(java.lang.String) alternate} form.
+ * {@code thrown|message} The thrown and message properties joined as
+ * one parameter. This parameter supports
+ * {@linkplain #toAlternate(java.lang.String) alternate} form.
+ * {@code sequence} the
+ * {@linkplain LogRecord#getSequenceNumber() sequence number} if the given
+ * log record.
+ * {@code thread id} the {@linkplain LogRecord#getThreadID() thread id}
+ * of the given log record. By default this is formatted as a {@code long}
+ * by an unsigned conversion.
+ * {@code error} the throwable
+ * {@linkplain Class#getSimpleName() simple class name} and
+ * {@linkplain #formatError(LogRecord) error message} without any stack
+ * trace.
+ * {@code message|error} The message and error properties joined as one
+ * parameter. This parameter supports
+ * {@linkplain #toAlternate(java.lang.String) alternate} form.
+ * {@code error|message} The error and message properties joined as one
+ * parameter. This parameter supports
+ * {@linkplain #toAlternate(java.lang.String) alternate} form.
+ * {@code backtrace} only the
+ * {@linkplain #formatBackTrace(LogRecord) stack trace} of the given
+ * throwable.
+ * {@code bundlename} the resource bundle
+ * {@linkplain LogRecord#getResourceBundleName() name} of the given log
+ * record.
+ * {@code key} the {@linkplain LogRecord#getMessage() raw message}
+ * before localization or formatting.
+ *
+ *
+ *
+ * Some example formats:
+ *
+ * {@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.160s%n}
+ *
+ * This prints only 160 characters of the message|thrown ({@code 7$}) using
+ * the {@linkplain #toAlternate(java.lang.String) alternate} form. The
+ * separator is not included as part of the total width.
+ *
+ * Encoding failed.|NullPointerException: null String.getBytes(:913)
+ *
+ *
+ * {@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.20s%n}
+ *
+ * This prints only 20 characters of the message|thrown ({@code 7$}) using
+ * the {@linkplain #toAlternate(java.lang.String) alternate} form. This will
+ * perform a weighted truncation of both the message and thrown properties
+ * of the log record. The separator is not included as part of the total
+ * width.
+ *
+ * Encoding|NullPointerE
+ *
+ *
+ * {@code com.sun.mail.util.logging.CompactFormatter.format=%1$tc %2$s%n%4$s: %5$s%6$s%n}
+ *
+ * This prints the timestamp ({@code 1$}) and the source ({@code 2$}) on the
+ * first line. The second line is the log level ({@code 4$}), log message
+ * ({@code 5$}), and the throwable with a relevant stack trace element
+ * ({@code 6$}) if one is available.
+ *
+ * Fri Nov 20 07:29:24 CST 2009 MyClass fatal
+ * SEVERE: Encoding failed.NullPointerException: null String.getBytes(:913)
+ *
+ *
+ * {@code com.sun.mail.util.logging.CompactFormatter.format=%4$s: %12$#.160s%n}
+ *
+ * This prints the log level ({@code 4$}) and only 160 characters of the
+ * message|error ({@code 12$}) using the alternate form.
+ *
+ * SEVERE: Unable to send notification.|SocketException: Permission denied: connect
+ *
+ *
+ * {@code com.sun.mail.util.logging.CompactFormatter.format=[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n}
+ *
+ * This prints the sequence ({@code 9$}), event time ({@code 1$}) as 24 hour
+ * time, thread id ({@code 10$}), source ({@code 2$}), log message
+ * ({@code 5$}), and the throwable with back trace ({@code 6$}).
+ *
+ * [125][14:11:42][38][MyClass fatal] Unable to send notification.
+ * SocketException: Permission denied: connect SMTPTransport.openServer(:1949)
+ *
+ *
+ *
+ *
+ * @param record the log record to format.
+ * @return the formatted record.
+ * @throws NullPointerException if the given record is null.
+ */
+ @Override
+ public String format(final LogRecord record) {
+ //LogRecord is mutable so define local vars.
+ ResourceBundle rb = record.getResourceBundle();
+ Locale l = rb == null ? null : rb.getLocale();
+
+ String msg = formatMessage(record);
+ String thrown = formatThrown(record);
+ String err = formatError(record);
+ Object[] params = {
+ formatZonedDateTime(record),
+ formatSource(record),
+ formatLoggerName(record),
+ formatLevel(record),
+ msg,
+ thrown,
+ new Alternate(msg, thrown),
+ new Alternate(thrown, msg),
+ record.getSequenceNumber(),
+ formatThreadID(record),
+ err,
+ new Alternate(msg, err),
+ new Alternate(err, msg),
+ formatBackTrace(record),
+ record.getResourceBundleName(),
+ record.getMessage()};
+
+ if (l == null) { //BUG ID 6282094
+ return String.format(fmt, params);
+ } else {
+ return String.format(l, fmt, params);
+ }
+ }
+
+ /**
+ * Formats message for the log record. This method removes any fully
+ * qualified throwable class names from the message.
+ *
+ * @param record the log record.
+ * @return the formatted message string.
+ */
+ @Override
+ public String formatMessage(final LogRecord record) {
+ String msg = super.formatMessage(record);
+ msg = replaceClassName(msg, record.getThrown());
+ msg = replaceClassName(msg, record.getParameters());
+ return msg;
+ }
+
+ /**
+ * Formats the message from the thrown property of the log record. This
+ * method replaces fully qualified throwable class names from the message
+ * cause chain with simple class names.
+ *
+ * @param t the throwable to format or null.
+ * @return the empty string if null was given or the formatted message
+ * string from the throwable which may be null.
+ */
+ public String formatMessage(final Throwable t) {
+ String r;
+ if (t != null) {
+ final Throwable apply = apply(t);
+ final String m = apply.getLocalizedMessage();
+ final String s = apply.toString();
+ final String sn = simpleClassName(apply.getClass());
+ if (!isNullOrSpaces(m)) {
+ if (s.contains(m)) {
+ if (s.startsWith(apply.getClass().getName())
+ || s.startsWith(sn)) {
+ r = replaceClassName(m, t);
+ } else {
+ r = replaceClassName(simpleClassName(s), t);
+ }
+ } else {
+ r = replaceClassName(simpleClassName(s) + ": " + m, t);
+ }
+ } else {
+ r = replaceClassName(simpleClassName(s), t);
+ }
+
+ if (!r.contains(sn)) {
+ r = sn + ": " + r;
+ }
+ } else {
+ r = "";
+ }
+ return r;
+ }
+
+ /**
+ * Formats the level property of the given log record.
+ *
+ * @param record the record.
+ * @return the formatted logger name.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatLevel(final LogRecord record) {
+ return record.getLevel().getLocalizedName();
+ }
+
+ /**
+ * Formats the source from the given log record.
+ *
+ * @param record the record.
+ * @return the formatted source of the log record.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatSource(final LogRecord record) {
+ String source = record.getSourceClassName();
+ if (source != null) {
+ if (record.getSourceMethodName() != null) {
+ source = simpleClassName(source) + " "
+ + record.getSourceMethodName();
+ } else {
+ source = simpleClassName(source);
+ }
+ } else {
+ source = simpleClassName(record.getLoggerName());
+ }
+ return source;
+ }
+
+ /**
+ * Formats the logger name property of the given log record.
+ *
+ * @param record the record.
+ * @return the formatted logger name.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatLoggerName(final LogRecord record) {
+ return simpleClassName(record.getLoggerName());
+ }
+
+ /**
+ * Formats the thread id property of the given log record. By default this
+ * is formatted as a {@code long} by an unsigned conversion.
+ *
+ * @param record the record.
+ * @return the formatted thread id as a number.
+ * @throws NullPointerException if the given record is null.
+ * @since JavaMail 1.5.4
+ */
+ public Number formatThreadID(final LogRecord record) {
+ /**
+ * Thread.getID is defined as long and LogRecord.getThreadID is defined
+ * as int. Convert to unsigned as a means to better map the two types of
+ * thread identifiers.
+ */
+ return (((long) record.getThreadID()) & 0xffffffffL);
+ }
+
+ /**
+ * Formats the thrown property of a LogRecord. The returned string will
+ * contain a throwable message with a back trace.
+ *
+ * @param record the record.
+ * @return empty string if nothing was thrown or formatted string.
+ * @throws NullPointerException if the given record is null.
+ * @see #apply(java.lang.Throwable)
+ * @see #formatBackTrace(java.util.logging.LogRecord)
+ */
+ public String formatThrown(final LogRecord record) {
+ String msg;
+ final Throwable t = record.getThrown();
+ if (t != null) {
+ String site = formatBackTrace(record);
+ msg = formatMessage(t) + (isNullOrSpaces(site) ? "" : ' ' + site);
+ } else {
+ msg = "";
+ }
+ return msg;
+ }
+
+ /**
+ * Formats the thrown property of a LogRecord as an error message. The
+ * returned string will not contain a back trace.
+ *
+ * @param record the record.
+ * @return empty string if nothing was thrown or formatted string.
+ * @throws NullPointerException if the given record is null.
+ * @see Throwable#toString()
+ * @see #apply(java.lang.Throwable)
+ * @see #formatMessage(java.lang.Throwable)
+ * @since JavaMail 1.5.4
+ */
+ public String formatError(final LogRecord record) {
+ return formatMessage(record.getThrown());
+ }
+
+ /**
+ * Formats the back trace for the given log record.
+ *
+ * @param record the log record to format.
+ * @return the formatted back trace.
+ * @throws NullPointerException if the given record is null.
+ * @see #apply(java.lang.Throwable)
+ * @see #formatThrown(java.util.logging.LogRecord)
+ * @see #ignore(java.lang.StackTraceElement)
+ */
+ public String formatBackTrace(final LogRecord record) {
+ String site = "";
+ final Throwable t = record.getThrown();
+ if (t != null) {
+ final Throwable root = apply(t);
+ StackTraceElement[] trace = root.getStackTrace();
+ site = findAndFormat(trace);
+ if (isNullOrSpaces(site)) {
+ int limit = 0;
+ for (Throwable c = t; c != null; c = c.getCause()) {
+ StackTraceElement[] ste = c.getStackTrace();
+ site = findAndFormat(ste);
+ if (!isNullOrSpaces(site)) {
+ break;
+ } else {
+ if (trace.length == 0) {
+ trace = ste;
+ }
+ }
+
+ //Deal with excessive cause chains
+ //and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+
+ //Punt.
+ if (isNullOrSpaces(site) && trace.length != 0) {
+ site = formatStackTraceElement(trace[0]);
+ }
+ }
+ }
+ return site;
+ }
+
+ /**
+ * Finds and formats the first stack frame of interest.
+ *
+ * @param trace the fill stack to examine.
+ * @return a String that best describes the call site.
+ * @throws NullPointerException if stack trace element array is null.
+ */
+ private String findAndFormat(final StackTraceElement[] trace) {
+ String site = "";
+ for (StackTraceElement s : trace) {
+ if (!ignore(s)) {
+ site = formatStackTraceElement(s);
+ break;
+ }
+ }
+
+ //Check if all code was compiled with no debugging info.
+ if (isNullOrSpaces(site)) {
+ for (StackTraceElement s : trace) {
+ if (!defaultIgnore(s)) {
+ site = formatStackTraceElement(s);
+ break;
+ }
+ }
+ }
+ return site;
+ }
+
+ /**
+ * Formats a stack trace element into a simple call site.
+ *
+ * @param s the stack trace element to format.
+ * @return the formatted stack trace element.
+ * @throws NullPointerException if stack trace element is null.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ private String formatStackTraceElement(final StackTraceElement s) {
+ String v = simpleClassName(s.getClassName());
+ String result;
+ if (v != null) {
+ result = s.toString().replace(s.getClassName(), v);
+ } else {
+ result = s.toString();
+ }
+
+ //If the class name contains the simple file name then remove file name.
+ v = simpleFileName(s.getFileName());
+ if (v != null && result.startsWith(v)) {
+ result = result.replace(s.getFileName(), "");
+ }
+ return result;
+ }
+
+ /**
+ * Chooses a single throwable from the cause chain that will be formatted.
+ * This implementation chooses the throwable that best describes the chain.
+ * Subclasses can override this method to choose an alternate throwable for
+ * formatting.
+ *
+ * @param t the throwable from the log record.
+ * @return the chosen throwable or null only if the given argument is null.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ protected Throwable apply(final Throwable t) {
+ return SeverityComparator.getInstance().apply(t);
+ }
+
+ /**
+ * Determines if a stack frame should be ignored as the cause of an error.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ protected boolean ignore(final StackTraceElement s) {
+ return isUnknown(s) || defaultIgnore(s);
+ }
+
+ /**
+ * Defines the alternate format. This implementation removes all control
+ * characters from the given string.
+ *
+ * @param s any string or null.
+ * @return null if the argument was null otherwise, an alternate string.
+ */
+ protected String toAlternate(final String s) {
+ return s != null ? s.replaceAll("[\\x00-\\x1F\\x7F]+", "") : null;
+ }
+
+ /**
+ * Gets the zoned date time from the given log record.
+ *
+ * @param record the current log record.
+ * @return a zoned date time or a legacy date object.
+ * @throws NullPointerException if the given record is null.
+ * @since JavaMail 1.5.6
+ */
+ private Comparable> formatZonedDateTime(final LogRecord record) {
+ Comparable> zdt = LogManagerProperties.getZonedDateTime(record);
+ if (zdt == null) {
+ zdt = new java.util.Date(record.getMillis());
+ }
+ return zdt;
+ }
+
+ /**
+ * Determines if a stack frame should be ignored as the cause of an error.
+ * This does not check for unknown line numbers because code can be compiled
+ * without debugging info.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ */
+ private boolean defaultIgnore(final StackTraceElement s) {
+ return isSynthetic(s) || isStaticUtility(s) || isReflection(s);
+ }
+
+ /**
+ * Determines if a stack frame is for a static utility class.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ */
+ private boolean isStaticUtility(final StackTraceElement s) {
+ try {
+ return LogManagerProperties.isStaticUtilityClass(s.getClassName());
+ } catch (RuntimeException ignore) {
+ } catch (Exception | LinkageError ignore) {
+ }
+ final String cn = s.getClassName();
+ return (cn.endsWith("s") && !cn.endsWith("es"))
+ || cn.contains("Util") || cn.endsWith("Throwables");
+ }
+
+ /**
+ * Determines if a stack trace element is for a synthetic method.
+ *
+ * @param s the stack trace element.
+ * @return true if synthetic.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isSynthetic(final StackTraceElement s) {
+ return s.getMethodName().indexOf('$') > -1;
+ }
+
+ /**
+ * Determines if a stack trace element has an unknown line number or a
+ * native line number.
+ *
+ * @param s the stack trace element.
+ * @return true if the line number is unknown.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isUnknown(final StackTraceElement s) {
+ return s.getLineNumber() < 0;
+ }
+
+ /**
+ * Determines if a stack trace element represents a reflection frame.
+ *
+ * @param s the stack trace element.
+ * @return true if the line number is unknown.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isReflection(final StackTraceElement s) {
+ try {
+ return LogManagerProperties.isReflectionClass(s.getClassName());
+ } catch (RuntimeException ignore) {
+ } catch (Exception | LinkageError ignore) {
+ }
+ return s.getClassName().startsWith("java.lang.reflect.")
+ || s.getClassName().startsWith("sun.reflect.");
+ }
+
+ /**
+ * Creates the format pattern for this formatter.
+ *
+ * @param p the class name prefix.
+ * @return the java.util.Formatter format string.
+ * @throws NullPointerException if the given class name is null.
+ */
+ private String initFormat(final String p) {
+ String v = LogManagerProperties.fromLogManager(p.concat(".format"));
+ if (isNullOrSpaces(v)) {
+ v = "%7$#.160s%n"; //160 chars split between message and thrown.
+ }
+ return v;
+ }
+
+ /**
+ * Searches the given message for all instances fully qualified class name
+ * with simple class name based off of the types contained in the given
+ * parameter array.
+ *
+ * @param msg the message.
+ * @param t the throwable cause chain to search or null.
+ * @return the modified message string.
+ */
+ private static String replaceClassName(String msg, Throwable t) {
+ if (!isNullOrSpaces(msg)) {
+ int limit = 0;
+ for (Throwable c = t; c != null; c = c.getCause()) {
+ final Class> k = c.getClass();
+ msg = msg.replace(k.getName(), simpleClassName(k));
+
+ //Deal with excessive cause chains and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Searches the given message for all instances fully qualified class name
+ * with simple class name based off of the types contained in the given
+ * parameter array.
+ *
+ * @param msg the message or null.
+ * @param p the parameter array or null.
+ * @return the modified message string.
+ */
+ private static String replaceClassName(String msg, Object[] p) {
+ if (!isNullOrSpaces(msg) && p != null) {
+ for (Object o : p) {
+ if (o != null) {
+ final Class> k = o.getClass();
+ msg = msg.replace(k.getName(), simpleClassName(k));
+ }
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Gets the simple class name from the given class. This is a workaround for
+ * BUG ID JDK-8057919.
+ *
+ * @param k the class object.
+ * @return the simple class name or null.
+ * @since JavaMail 1.5.3
+ */
+ private static String simpleClassName(final Class> k) {
+ try {
+ return k.getSimpleName();
+ } catch (final InternalError JDK8057919) {
+ }
+ return simpleClassName(k.getName());
+ }
+
+ /**
+ * Converts a fully qualified class name to a simple class name. If the
+ * leading part of the given string is not a legal class name then the given
+ * string is returned.
+ *
+ * @param name the fully qualified class name prefix or null.
+ * @return the simple class name or given input.
+ */
+ private static String simpleClassName(String name) {
+ if (name != null) {
+ int cursor = 0;
+ int sign = -1;
+ int dot = -1;
+ for (int c, prev = dot; cursor < name.length();
+ cursor += Character.charCount(c)) {
+ c = name.codePointAt(cursor);
+ if (!Character.isJavaIdentifierPart(c)) {
+ if (c == ((int) '.')) {
+ if ((dot + 1) != cursor && (dot + 1) != sign) {
+ prev = dot;
+ dot = cursor;
+ } else {
+ return name;
+ }
+ } else {
+ if ((dot + 1) == cursor) {
+ dot = prev;
+ }
+ break;
+ }
+ } else {
+ if (c == ((int) '$')) {
+ sign = cursor;
+ }
+ }
+ }
+
+ if (dot > -1 && ++dot < cursor && ++sign < cursor) {
+ name = name.substring(sign > dot ? sign : dot);
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Converts a file name with an extension to a file name without an
+ * extension.
+ *
+ * @param name the full file name or null.
+ * @return the simple file name or null.
+ */
+ private static String simpleFileName(String name) {
+ if (name != null) {
+ final int index = name.lastIndexOf('.');
+ name = index > -1 ? name.substring(0, index) : name;
+ }
+ return name;
+ }
+
+ /**
+ * Determines is the given string is null or spaces.
+ *
+ * @param s the string or null.
+ * @return true if null or spaces.
+ */
+ private static boolean isNullOrSpaces(final String s) {
+ return s == null || s.trim().length() == 0;
+ }
+
+ /**
+ * Used to format two arguments as fixed length message.
+ */
+ private class Alternate implements java.util.Formattable {
+
+ /**
+ * The left side of the output.
+ */
+ private final String left;
+ /**
+ * The right side of the output.
+ */
+ private final String right;
+
+ /**
+ * Creates an alternate output.
+ *
+ * @param left the left side or null.
+ * @param right the right side or null.
+ */
+ Alternate(final String left, final String right) {
+ this.left = String.valueOf(left);
+ this.right = String.valueOf(right);
+ }
+
+ @SuppressWarnings("override") //JDK-6954234
+ public void formatTo(java.util.Formatter formatter, int flags,
+ int width, int precision) {
+
+ String l = left;
+ String r = right;
+ if ((flags & java.util.FormattableFlags.UPPERCASE)
+ == java.util.FormattableFlags.UPPERCASE) {
+ l = l.toUpperCase(formatter.locale());
+ r = r.toUpperCase(formatter.locale());
+ }
+
+ if ((flags & java.util.FormattableFlags.ALTERNATE)
+ == java.util.FormattableFlags.ALTERNATE) {
+ l = toAlternate(l);
+ r = toAlternate(r);
+ }
+
+ if (precision <= 0) {
+ precision = Integer.MAX_VALUE;
+ }
+
+ int fence = Math.min(l.length(), precision);
+ if (fence > (precision >> 1)) {
+ fence = Math.max(fence - r.length(), fence >> 1);
+ }
+
+ if (fence > 0) {
+ if (fence > l.length()
+ && Character.isHighSurrogate(l.charAt(fence - 1))) {
+ --fence;
+ }
+ l = l.substring(0, fence);
+ }
+ r = r.substring(0, Math.min(precision - fence, r.length()));
+
+ if (width > 0) {
+ final int half = width >> 1;
+ if (l.length() < half) {
+ l = pad(flags, l, half);
+ }
+
+ if (r.length() < half) {
+ r = pad(flags, r, half);
+ }
+ }
+
+ Object[] empty = Collections.emptySet().toArray();
+ formatter.format(l, empty);
+ if (l.length() != 0 && r.length() != 0) {
+ formatter.format("|", empty);
+ }
+ formatter.format(r, empty);
+ }
+
+ /**
+ * Pad the given input string.
+ *
+ * @param flags the formatter flags.
+ * @param s the string to pad.
+ * @param length the final string length.
+ * @return the padded string.
+ */
+ private String pad(int flags, String s, int length) {
+ final int padding = length - s.length();
+ final StringBuilder b = new StringBuilder(length);
+ if ((flags & java.util.FormattableFlags.LEFT_JUSTIFY)
+ == java.util.FormattableFlags.LEFT_JUSTIFY) {
+ for (int i = 0; i < padding; ++i) {
+ b.append('\u0020');
+ }
+ b.append(s);
+ } else {
+ b.append(s);
+ for (int i = 0; i < padding; ++i) {
+ b.append('\u0020');
+ }
+ }
+ return b.toString();
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/DurationFilter.java b/app/src/main/java/com/sun/mail/util/logging/DurationFilter.java
new file mode 100644
index 0000000000..b85101468c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/DurationFilter.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
+import java.util.logging.*;
+
+/**
+ * A filter used to limit log records based on a maximum generation rate.
+ *
+ * The duration specified is used to compute the record rate and the amount of
+ * time the filter will reject records once the rate has been exceeded. Once the
+ * rate is exceeded records are not allowed until the duration has elapsed.
+ *
+ *
+ * By default each {@code DurationFilter} is initialized using the following
+ * LogManager configuration properties where {@code } refers to the
+ * fully qualified class name of the handler. If properties are not defined, or
+ * contain invalid values, then the specified default values are used.
+ *
+ *
+ * {@literal }.records the max number of records per duration.
+ * A numeric long integer or a multiplication expression can be used as the
+ * value. (defaults to {@code 1000})
+ *
+ * {@literal }.duration the number of milliseconds to suppress
+ * log records from being published. This is also used as duration to determine
+ * the log record rate. A numeric long integer or a multiplication expression
+ * can be used as the value. If the {@code java.time} package is available then
+ * an ISO-8601 duration format of {@code PnDTnHnMn.nS} can be used as the value.
+ * The suffixes of "D", "H", "M" and "S" are for days, hours, minutes and
+ * seconds. The suffixes must occur in order. The seconds can be specified with
+ * a fractional component to declare milliseconds. (defaults to {@code PT15M})
+ *
+ *
+ *
+ * For example, the settings to limit {@code MailHandler} with a default
+ * capacity to only send a maximum of two email messages every six minutes would
+ * be as follows:
+ *
+ * {@code
+ * com.sun.mail.util.logging.MailHandler.filter = com.sun.mail.util.logging.DurationFilter
+ * com.sun.mail.util.logging.MailHandler.capacity = 1000
+ * com.sun.mail.util.logging.DurationFilter.records = 2L * 1000L
+ * com.sun.mail.util.logging.DurationFilter.duration = PT6M
+ * }
+ *
+ *
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.5
+ */
+public class DurationFilter implements Filter {
+
+ /**
+ * The number of expected records per duration.
+ */
+ private final long records;
+ /**
+ * The duration in milliseconds used to determine the rate. The duration is
+ * also used as the amount of time that the filter will not allow records
+ * when saturated.
+ */
+ private final long duration;
+ /**
+ * The number of records seen for the current duration. This value negative
+ * if saturated. Zero is considered saturated but is reserved for recording
+ * the first duration.
+ */
+ private long count;
+ /**
+ * The most recent record time seen for the current duration.
+ */
+ private long peak;
+ /**
+ * The start time for the current duration.
+ */
+ private long start;
+
+ /**
+ * Creates the filter using the default properties.
+ */
+ public DurationFilter() {
+ this.records = checkRecords(initLong(".records"));
+ this.duration = checkDuration(initLong(".duration"));
+ }
+
+ /**
+ * Creates the filter using the given properties. Default values are used if
+ * any of the given values are outside the allowed range.
+ *
+ * @param records the number of records per duration.
+ * @param duration the number of milliseconds to suppress log records from
+ * being published.
+ */
+ public DurationFilter(final long records, final long duration) {
+ this.records = checkRecords(records);
+ this.duration = checkDuration(duration);
+ }
+
+ /**
+ * Determines if this filter is equal to another filter.
+ *
+ * @param obj the given object.
+ * @return true if equal otherwise false.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) { //Avoid locks and deal with rapid state changes.
+ return true;
+ }
+
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ final DurationFilter other = (DurationFilter) obj;
+ if (this.records != other.records) {
+ return false;
+ }
+
+ if (this.duration != other.duration) {
+ return false;
+ }
+
+ final long c;
+ final long p;
+ final long s;
+ synchronized (this) {
+ c = this.count;
+ p = this.peak;
+ s = this.start;
+ }
+
+ synchronized (other) {
+ if (c != other.count || p != other.peak || s != other.start) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determines if this filter is able to accept the maximum number of log
+ * records for this instant in time. The result is a best-effort estimate
+ * and should be considered out of date as soon as it is produced. This
+ * method is designed for use in monitoring the state of this filter.
+ *
+ * @return true if the filter is idle; false otherwise.
+ */
+ public boolean isIdle() {
+ return test(0L, System.currentTimeMillis());
+ }
+
+ /**
+ * Returns a hash code value for this filter.
+ *
+ * @return hash code for this filter.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 89 * hash + (int) (this.records ^ (this.records >>> 32));
+ hash = 89 * hash + (int) (this.duration ^ (this.duration >>> 32));
+ return hash;
+ }
+
+ /**
+ * Check if the given log record should be published. This method will
+ * modify the internal state of this filter.
+ *
+ * @param record the log record to check.
+ * @return true if allowed; false otherwise.
+ * @throws NullPointerException if given record is null.
+ */
+ @SuppressWarnings("override") //JDK-6954234
+ public boolean isLoggable(final LogRecord record) {
+ return accept(record.getMillis());
+ }
+
+ /**
+ * Determines if this filter will accept log records for this instant in
+ * time. The result is a best-effort estimate and should be considered out
+ * of date as soon as it is produced. This method is designed for use in
+ * monitoring the state of this filter.
+ *
+ * @return true if the filter is not saturated; false otherwise.
+ */
+ public boolean isLoggable() {
+ return test(records, System.currentTimeMillis());
+ }
+
+ /**
+ * Returns a string representation of this filter. The result is a
+ * best-effort estimate and should be considered out of date as soon as it
+ * is produced.
+ *
+ * @return a string representation of this filter.
+ */
+ @Override
+ public String toString() {
+ boolean idle;
+ boolean loggable;
+ synchronized (this) {
+ final long millis = System.currentTimeMillis();
+ idle = test(0L, millis);
+ loggable = test(records, millis);
+ }
+
+ return getClass().getName() + "{records=" + records
+ + ", duration=" + duration
+ + ", idle=" + idle
+ + ", loggable=" + loggable + '}';
+ }
+
+ /**
+ * Creates a copy of this filter that retains the filter settings but does
+ * not include the current filter state. The newly create clone acts as if
+ * it has never seen any records.
+ *
+ * @return a copy of this filter.
+ * @throws CloneNotSupportedException if this filter is not allowed to be
+ * cloned.
+ */
+ @Override
+ protected DurationFilter clone() throws CloneNotSupportedException {
+ final DurationFilter clone = (DurationFilter) super.clone();
+ clone.count = 0L; //Reset the filter state.
+ clone.peak = 0L;
+ clone.start = 0L;
+ return clone;
+ }
+
+ /**
+ * Checks if this filter is not saturated or bellow a maximum rate.
+ *
+ * @param limit the number of records allowed to be under the rate.
+ * @param millis the current time in milliseconds.
+ * @return true if not saturated or bellow the rate.
+ */
+ private boolean test(final long limit, final long millis) {
+ assert limit >= 0L : limit;
+ final long c;
+ final long s;
+ synchronized (this) {
+ c = count;
+ s = start;
+ }
+
+ if (c > 0L) { //If not saturated.
+ if ((millis - s) >= duration || c < limit) {
+ return true;
+ }
+ } else { //Subtraction is used to deal with numeric overflow.
+ if ((millis - s) >= 0L || c == 0L) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines if the record is loggable by time.
+ *
+ * @param millis the log record milliseconds.
+ * @return true if accepted false otherwise.
+ */
+ private synchronized boolean accept(final long millis) {
+ //Subtraction is used to deal with numeric overflow of millis.
+ boolean allow;
+ if (count > 0L) { //If not saturated.
+ if ((millis - peak) > 0L) {
+ peak = millis; //Record the new peak.
+ }
+
+ //Under the rate if the count has not been reached.
+ if (count != records) {
+ ++count;
+ allow = true;
+ } else {
+ if ((peak - start) >= duration) {
+ count = 1L; //Start a new duration.
+ start = peak;
+ allow = true;
+ } else {
+ count = -1L; //Saturate for the duration.
+ start = peak + duration;
+ allow = false;
+ }
+ }
+ } else {
+ //If the saturation period has expired or this is the first record
+ //then start a new duration and allow records.
+ if ((millis - start) >= 0L || count == 0L) {
+ count = 1L;
+ start = millis;
+ peak = millis;
+ allow = true;
+ } else {
+ allow = false; //Remain in a saturated state.
+ }
+ }
+ return allow;
+ }
+
+ /**
+ * Reads a long value or multiplication expression from the LogManager. If
+ * the value can not be parsed or is not defined then Long.MIN_VALUE is
+ * returned.
+ *
+ * @param suffix a dot character followed by the key name.
+ * @return a long value or Long.MIN_VALUE if unable to parse or undefined.
+ * @throws NullPointerException if suffix is null.
+ */
+ private long initLong(final String suffix) {
+ long result = 0L;
+ final String p = getClass().getName();
+ String value = fromLogManager(p.concat(suffix));
+ if (value != null && value.length() != 0) {
+ value = value.trim();
+ if (isTimeEntry(suffix, value)) {
+ try {
+ result = LogManagerProperties.parseDurationToMillis(value);
+ } catch (final RuntimeException ignore) {
+ } catch (final Exception ignore) {
+ } catch (final LinkageError ignore) {
+ }
+ }
+
+ if (result == 0L) { //Zero is invalid.
+ try {
+ result = 1L;
+ for (String s : tokenizeLongs(value)) {
+ if (s.endsWith("L") || s.endsWith("l")) {
+ s = s.substring(0, s.length() - 1);
+ }
+ result = multiplyExact(result, Long.parseLong(s));
+ }
+ } catch (final RuntimeException ignore) {
+ result = Long.MIN_VALUE;
+ }
+ }
+ } else {
+ result = Long.MIN_VALUE;
+ }
+ return result;
+ }
+
+ /**
+ * Determines if the given suffix can be a time unit and the value is
+ * encoded as an ISO ISO-8601 duration format.
+ *
+ * @param suffix the suffix property.
+ * @param value the value of the property.
+ * @return true if the entry is a time entry.
+ * @throws IndexOutOfBoundsException if value is empty.
+ * @throws NullPointerException if either argument is null.
+ */
+ private boolean isTimeEntry(final String suffix, final String value) {
+ return (value.charAt(0) == 'P' || value.charAt(0) == 'p')
+ && suffix.equals(".duration");
+ }
+
+ /**
+ * Parse any long value or multiplication expressions into tokens.
+ *
+ * @param value the expression or value.
+ * @return an array of long tokens, never empty.
+ * @throws NullPointerException if the given value is null.
+ * @throws NumberFormatException if the expression is invalid.
+ */
+ private static String[] tokenizeLongs(final String value) {
+ String[] e;
+ final int i = value.indexOf('*');
+ if (i > -1 && (e = value.split("\\s*\\*\\s*")).length != 0) {
+ if (i == 0 || value.charAt(value.length() - 1) == '*') {
+ throw new NumberFormatException(value);
+ }
+
+ if (e.length == 1) {
+ throw new NumberFormatException(e[0]);
+ }
+ } else {
+ e = new String[]{value};
+ }
+ return e;
+ }
+
+ /**
+ * Multiply and check for overflow. This can be replaced with
+ * {@code java.lang.Math.multiplyExact} when Jakarta Mail requires JDK 8.
+ *
+ * @param x the first value.
+ * @param y the second value.
+ * @return x times y.
+ * @throws ArithmeticException if overflow is detected.
+ */
+ private static long multiplyExact(final long x, final long y) {
+ long r = x * y;
+ if (((Math.abs(x) | Math.abs(y)) >>> 31L != 0L)) {
+ if (((y != 0L) && (r / y != x))
+ || (x == Long.MIN_VALUE && y == -1L)) {
+ throw new ArithmeticException();
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Converts record count to a valid record count. If the value is out of
+ * bounds then the default record count is used.
+ *
+ * @param records the record count.
+ * @return a valid number of record count.
+ */
+ private static long checkRecords(final long records) {
+ return records > 0L ? records : 1000L;
+ }
+
+ /**
+ * Converts the duration to a valid duration. If the value is out of bounds
+ * then the default duration is used.
+ *
+ * @param duration the duration to check.
+ * @return a valid duration.
+ */
+ private static long checkDuration(final long duration) {
+ return duration > 0L ? duration : 15L * 60L * 1000L;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java b/app/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
new file mode 100644
index 0000000000..e2ec6c575e
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
@@ -0,0 +1,1090 @@
+/*
+ * Copyright (c) 2009, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2019 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+
+/**
+ * An adapter class to allow the Mail API to access the LogManager properties.
+ * The LogManager properties are treated as the root of all properties. First,
+ * the parent properties are searched. If no value is found, then, the
+ * LogManager is searched with prefix value. If not found, then, just the key
+ * itself is searched in the LogManager. If a value is found in the LogManager
+ * it is then copied to this properties object with no key prefix. If no value
+ * is found in the LogManager or the parent properties, then this properties
+ * object is searched only by passing the key value.
+ *
+ *
+ * This class also emulates the LogManager functions for creating new objects
+ * from string class names. This is to support initial setup of objects such as
+ * log filters, formatters, error managers, etc.
+ *
+ *
+ * This class should never be exposed outside of this package. Keep this class
+ * package private (default access).
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.4.3
+ */
+final class LogManagerProperties extends Properties {
+
+ /**
+ * Generated serial id.
+ */
+ private static final long serialVersionUID = -2239983349056806252L;
+ /**
+ * Holds the method used to get the LogRecord instant if running on JDK 9 or
+ * later.
+ */
+ private static final Method LR_GET_INSTANT;
+
+ /**
+ * Holds the method used to get the default time zone if running on JDK 9 or
+ * later.
+ */
+ private static final Method ZI_SYSTEM_DEFAULT;
+
+ /**
+ * Holds the method used to convert and instant to a zoned date time if
+ * running on JDK 9 later.
+ */
+ private static final Method ZDT_OF_INSTANT;
+
+ static {
+ Method lrgi = null;
+ Method zisd = null;
+ Method zdtoi = null;
+ try {
+ lrgi = LogRecord.class.getMethod("getInstant");
+ assert Comparable.class
+ .isAssignableFrom(lrgi.getReturnType()) : lrgi;
+ zisd = findClass("java.time.ZoneId")
+ .getMethod("systemDefault");
+ if (!Modifier.isStatic(zisd.getModifiers())) {
+ throw new NoSuchMethodException(zisd.toString());
+ }
+
+ zdtoi = findClass("java.time.ZonedDateTime")
+ .getMethod("ofInstant", findClass("java.time.Instant"),
+ findClass("java.time.ZoneId"));
+ if (!Modifier.isStatic(zdtoi.getModifiers())) {
+ throw new NoSuchMethodException(zdtoi.toString());
+ }
+
+ if (!Comparable.class.isAssignableFrom(zdtoi.getReturnType())) {
+ throw new NoSuchMethodException(zdtoi.toString());
+ }
+ } catch (final RuntimeException ignore) {
+ } catch (final Exception ignore) { //No need for specific catch.
+ } catch (final LinkageError ignore) {
+ } finally {
+ if (lrgi == null || zisd == null || zdtoi == null) {
+ lrgi = null; //If any are null then clear all.
+ zisd = null;
+ zdtoi = null;
+ }
+ }
+
+ LR_GET_INSTANT = lrgi;
+ ZI_SYSTEM_DEFAULT = zisd;
+ ZDT_OF_INSTANT = zdtoi;
+ }
+ /**
+ * Caches the read only reflection class names string array. Declared
+ * volatile for safe publishing only. The VO_VOLATILE_REFERENCE_TO_ARRAY
+ * warning is a false positive.
+ */
+ @SuppressWarnings("VolatileArrayField")
+ private static volatile String[] REFLECT_NAMES;
+ /**
+ * Caches the LogManager or Properties so we only read the configuration
+ * once.
+ */
+ private static final Object LOG_MANAGER = loadLogManager();
+
+ /**
+ * Get the LogManager or loads a Properties object to use as the LogManager.
+ *
+ * @return the LogManager or a loaded Properties object.
+ * @since JavaMail 1.5.3
+ */
+ private static Object loadLogManager() {
+ Object m;
+ try {
+ m = LogManager.getLogManager();
+ } catch (final LinkageError restricted) {
+ m = readConfiguration();
+ } catch (final RuntimeException unexpected) {
+ m = readConfiguration();
+ }
+ return m;
+ }
+
+ /**
+ * Create a properties object from the default logging configuration file.
+ * Since the LogManager is not available in restricted environments, only
+ * the default configuration is applicable.
+ *
+ * @return a properties object loaded with the default configuration.
+ * @since JavaMail 1.5.3
+ */
+ private static Properties readConfiguration() {
+ /**
+ * Load the properties file so the default settings are available when
+ * user code creates a logging object. The class loader for the
+ * restricted LogManager can't access these classes to attach them to a
+ * logger or handler on startup. Creating logging objects at this point
+ * is both useless and risky.
+ */
+ final Properties props = new Properties();
+ try {
+ String n = System.getProperty("java.util.logging.config.file");
+ if (n != null) {
+ final File f = new File(n).getCanonicalFile();
+ final InputStream in = new FileInputStream(f);
+ try {
+ props.load(in);
+ } finally {
+ in.close();
+ }
+ }
+ } catch (final RuntimeException permissionsOrMalformed) {
+ } catch (final Exception ioe) {
+ } catch (final LinkageError unexpected) {
+ }
+ return props;
+ }
+
+ /**
+ * Gets LogManger property for the running JVM. If the LogManager doesn't
+ * exist then the default LogManger properties are used.
+ *
+ * @param name the property name.
+ * @return the LogManager.
+ * @throws NullPointerException if the given name is null.
+ * @since JavaMail 1.5.3
+ */
+ static String fromLogManager(final String name) {
+ if (name == null) {
+ throw new NullPointerException();
+ }
+
+ final Object m = LOG_MANAGER;
+ try {
+ if (m instanceof Properties) {
+ return ((Properties) m).getProperty(name);
+ }
+ } catch (final RuntimeException unexpected) {
+ }
+
+ if (m != null) {
+ try {
+ if (m instanceof LogManager) {
+ return ((LogManager) m).getProperty(name);
+ }
+ } catch (final LinkageError restricted) {
+ } catch (final RuntimeException unexpected) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check that the current context is trusted to modify the logging
+ * configuration. This requires LoggingPermission("control").
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have {@code LoggingPermission("control")}.
+ * @since JavaMail 1.5.3
+ */
+ static void checkLogManagerAccess() {
+ boolean checked = false;
+ final Object m = LOG_MANAGER;
+ if (m != null) {
+ try {
+ if (m instanceof LogManager) {
+ checked = true;
+ ((LogManager) m).checkAccess();
+ }
+ } catch (final SecurityException notAllowed) {
+ if (checked) {
+ throw notAllowed;
+ }
+ } catch (final LinkageError restricted) {
+ } catch (final RuntimeException unexpected) {
+ }
+ }
+
+ if (!checked) {
+ checkLoggingAccess();
+ }
+ }
+
+ /**
+ * Check that the current context is trusted to modify the logging
+ * configuration when the LogManager is not present. This requires
+ * LoggingPermission("control").
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have {@code LoggingPermission("control")}.
+ * @since JavaMail 1.5.3
+ */
+ private static void checkLoggingAccess() {
+ /**
+ * Some environments selectively enforce logging permissions by allowing
+ * access to loggers but not allowing access to handlers. This is an
+ * indirect way of checking for LoggingPermission when the LogManager is
+ * not present. The root logger will lazy create handlers so the global
+ * logger is used instead as it is a known named logger with well
+ * defined behavior. If the global logger is a subclass then fallback to
+ * using the SecurityManager.
+ */
+ boolean checked = false;
+ final Logger global = Logger.getLogger("global");
+ try {
+ if (Logger.class == global.getClass()) {
+ global.removeHandler((Handler) null);
+ checked = true;
+ }
+ } catch (final NullPointerException unexpected) {
+ }
+
+ if (!checked) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new LoggingPermission("control", null));
+ }
+ }
+ }
+
+ /**
+ * Determines if access to the {@code java.util.logging.LogManager} class is
+ * restricted by the class loader.
+ *
+ * @return true if a LogManager is present.
+ * @since JavaMail 1.5.3
+ */
+ static boolean hasLogManager() {
+ final Object m = LOG_MANAGER;
+ return m != null && !(m instanceof Properties);
+ }
+
+ /**
+ * Gets the ZonedDateTime from the given log record.
+ *
+ * @param record used to generate the zoned date time.
+ * @return null if LogRecord doesn't support nanoseconds otherwise a new
+ * zoned date time is returned.
+ * @throws NullPointerException if record is null.
+ * @since JavaMail 1.5.6
+ */
+ @SuppressWarnings("UseSpecificCatch")
+ static Comparable> getZonedDateTime(LogRecord record) {
+ if (record == null) {
+ throw new NullPointerException();
+ }
+ final Method m = ZDT_OF_INSTANT;
+ if (m != null) {
+ try {
+ return (Comparable>) m.invoke((Object) null,
+ LR_GET_INSTANT.invoke(record),
+ ZI_SYSTEM_DEFAULT.invoke((Object) null));
+ } catch (final RuntimeException ignore) {
+ assert LR_GET_INSTANT != null
+ && ZI_SYSTEM_DEFAULT != null : ignore;
+ } catch (final InvocationTargetException ite) {
+ final Throwable cause = ite.getCause();
+ if (cause instanceof Error) {
+ throw (Error) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else { //Should never happen.
+ throw new UndeclaredThrowableException(ite);
+ }
+ } catch (final Exception ignore) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the local host name from the given service.
+ *
+ * @param s the service to examine.
+ * @return the local host name or null.
+ * @throws IllegalAccessException if the method is inaccessible.
+ * @throws InvocationTargetException if the method throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws NullPointerException if the given service is null.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception if there is a problem.
+ * @throws NoSuchMethodException if the given service does not have a method
+ * to get the local host name as a string.
+ * @throws SecurityException if unable to inspect properties of object.
+ * @since JavaMail 1.5.3
+ */
+ static String getLocalHost(final Object s) throws Exception {
+ try {
+ final Method m = s.getClass().getMethod("getLocalHost");
+ if (!Modifier.isStatic(m.getModifiers())
+ && m.getReturnType() == String.class) {
+ return (String) m.invoke(s);
+ } else {
+ throw new NoSuchMethodException(m.toString());
+ }
+ } catch (final ExceptionInInitializerError EIIE) {
+ throw wrapOrThrow(EIIE);
+ } catch (final InvocationTargetException ite) {
+ throw paramOrError(ite);
+ }
+ }
+
+ /**
+ * Used to parse an ISO-8601 duration format of {@code PnDTnHnMn.nS}.
+ *
+ * @param value an ISO-8601 duration character sequence.
+ * @return the number of milliseconds parsed from the duration.
+ * @throws ClassNotFoundException if the java.time classes are not present.
+ * @throws IllegalAccessException if the method is inaccessible.
+ * @throws InvocationTargetException if the method throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws NullPointerException if the given duration is null.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception if there is a problem.
+ * @throws NoSuchMethodException if the correct time methods are missing.
+ * @throws SecurityException if reflective access to the java.time classes
+ * are not allowed.
+ * @since JavaMail 1.5.5
+ */
+ static long parseDurationToMillis(final CharSequence value) throws Exception {
+ try {
+ final Class> k = findClass("java.time.Duration");
+ final Method parse = k.getMethod("parse", CharSequence.class);
+ if (!k.isAssignableFrom(parse.getReturnType())
+ || !Modifier.isStatic(parse.getModifiers())) {
+ throw new NoSuchMethodException(parse.toString());
+ }
+
+ final Method toMillis = k.getMethod("toMillis");
+ if (!Long.TYPE.isAssignableFrom(toMillis.getReturnType())
+ || Modifier.isStatic(toMillis.getModifiers())) {
+ throw new NoSuchMethodException(toMillis.toString());
+ }
+ return (Long) toMillis.invoke(parse.invoke(null, value));
+ } catch (final ExceptionInInitializerError EIIE) {
+ throw wrapOrThrow(EIIE);
+ } catch (final InvocationTargetException ite) {
+ throw paramOrError(ite);
+ }
+ }
+
+ /**
+ * Converts a locale to a language tag.
+ *
+ * @param locale the locale to convert.
+ * @return the language tag.
+ * @throws NullPointerException if the given locale is null.
+ * @since JavaMail 1.4.5
+ */
+ static String toLanguageTag(final Locale locale) {
+ final String l = locale.getLanguage();
+ final String c = locale.getCountry();
+ final String v = locale.getVariant();
+ final char[] b = new char[l.length() + c.length() + v.length() + 2];
+ int count = l.length();
+ l.getChars(0, count, b, 0);
+ if (c.length() != 0 || (l.length() != 0 && v.length() != 0)) {
+ b[count] = '-';
+ ++count; //be nice to the client compiler.
+ c.getChars(0, c.length(), b, count);
+ count += c.length();
+ }
+
+ if (v.length() != 0 && (l.length() != 0 || c.length() != 0)) {
+ b[count] = '-';
+ ++count; //be nice to the client compiler.
+ v.getChars(0, v.length(), b, count);
+ count += v.length();
+ }
+ return String.valueOf(b, 0, count);
+ }
+
+ /**
+ * Creates a new filter from the given class name.
+ *
+ * @param name the fully qualified class name.
+ * @return a new filter.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static Filter newFilter(String name) throws Exception {
+ return newObjectFrom(name, Filter.class);
+ }
+
+ /**
+ * Creates a new formatter from the given class name.
+ *
+ * @param name the fully qualified class name.
+ * @return a new formatter.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static Formatter newFormatter(String name) throws Exception {
+ return newObjectFrom(name, Formatter.class);
+ }
+
+ /**
+ * Creates a new log record comparator from the given class name.
+ *
+ * @param name the fully qualified class name.
+ * @return a new comparator.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ * @see java.util.logging.LogRecord
+ */
+ @SuppressWarnings("unchecked")
+ static Comparator super LogRecord> newComparator(String name) throws Exception {
+ return newObjectFrom(name, Comparator.class);
+ }
+
+ /**
+ * Returns a comparator that imposes the reverse ordering of the specified
+ * {@link Comparator}. If the given comparator declares a public
+ * reverseOrder method that method is called first and the return value is
+ * used. If that method is not declared or the caller does not have access
+ * then a comparator wrapping the given comparator is returned.
+ *
+ * @param the element type to be compared
+ * @param c a comparator whose ordering is to be reversed by the returned
+ * comparator
+ * @return A comparator that imposes the reverse ordering of the specified
+ * comparator.
+ * @throws NullPointerException if the given comparator is null.
+ * @since JavaMail 1.5.0
+ */
+ @SuppressWarnings({"unchecked", "ThrowableResultIgnored"})
+ static Comparator reverseOrder(final Comparator c) {
+ if (c == null) {
+ throw new NullPointerException();
+ }
+
+ Comparator reverse = null;
+ //Comparator in Java 1.8 has 'reversed' as a default method.
+ //This code calls that method first to allow custom
+ //code to define what reverse order means.
+ try {
+ //assert Modifier.isPublic(c.getClass().getModifiers()) :
+ // Modifier.toString(c.getClass().getModifiers());
+ final Method m = c.getClass().getMethod("reversed");
+ if (!Modifier.isStatic(m.getModifiers())
+ && Comparator.class.isAssignableFrom(m.getReturnType())) {
+ try {
+ reverse = (Comparator) m.invoke(c);
+ } catch (final ExceptionInInitializerError eiie) {
+ throw wrapOrThrow(eiie);
+ }
+ }
+ } catch (final NoSuchMethodException ignore) {
+ } catch (final IllegalAccessException ignore) {
+ } catch (final RuntimeException ignore) {
+ } catch (final InvocationTargetException ite) {
+ paramOrError(ite); //Ignore invocation bugs (returned values).
+ }
+
+ if (reverse == null) {
+ reverse = Collections.reverseOrder(c);
+ }
+ return reverse;
+ }
+
+ /**
+ * Creates a new error manager from the given class name.
+ *
+ * @param name the fully qualified class name.
+ * @return a new error manager.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static ErrorManager newErrorManager(String name) throws Exception {
+ return newObjectFrom(name, ErrorManager.class);
+ }
+
+ /**
+ * Determines if the given class name identifies a utility class.
+ *
+ * @param name the fully qualified class name.
+ * @return true if the given class name
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws SecurityException if unable to inspect properties of class.
+ * @since JavaMail 1.5.2
+ */
+ static boolean isStaticUtilityClass(String name) throws Exception {
+ final Class> c = findClass(name);
+ final Class> obj = Object.class;
+ Method[] methods;
+ boolean util;
+ if (c != obj && (methods = c.getMethods()).length != 0) {
+ util = true;
+ for (Method m : methods) {
+ if (m.getDeclaringClass() != obj
+ && !Modifier.isStatic(m.getModifiers())) {
+ util = false;
+ break;
+ }
+ }
+ } else {
+ util = false;
+ }
+ return util;
+ }
+
+ /**
+ * Determines if the given class name is a reflection class name responsible
+ * for invoking methods and or constructors.
+ *
+ * @param name the fully qualified class name.
+ * @return true if the given class name
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws SecurityException if unable to inspect properties of class.
+ * @since JavaMail 1.5.2
+ */
+ static boolean isReflectionClass(String name) throws Exception {
+ String[] names = REFLECT_NAMES;
+ if (names == null) { //Benign data race.
+ REFLECT_NAMES = names = reflectionClassNames();
+ }
+
+ for (String rf : names) { //The set of names is small.
+ if (name.equals(rf)) {
+ return true;
+ }
+ }
+
+ findClass(name); //Fail late instead of normal return.
+ return false;
+ }
+
+ /**
+ * Determines all of the reflection class names used to invoke methods.
+ *
+ * This method performs indirect and direct calls on a throwable to capture
+ * the standard class names and the implementation class names.
+ *
+ * @return a string array containing the fully qualified class names.
+ * @throws Exception if there is a problem.
+ */
+ private static String[] reflectionClassNames() throws Exception {
+ final Class> thisClass = LogManagerProperties.class;
+ assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
+ try {
+ final HashSet traces = new HashSet<>();
+ Throwable t = Throwable.class.getConstructor().newInstance();
+ for (StackTraceElement ste : t.getStackTrace()) {
+ if (!thisClass.getName().equals(ste.getClassName())) {
+ traces.add(ste.getClassName());
+ } else {
+ break;
+ }
+ }
+
+ Throwable.class.getMethod("fillInStackTrace").invoke(t);
+ for (StackTraceElement ste : t.getStackTrace()) {
+ if (!thisClass.getName().equals(ste.getClassName())) {
+ traces.add(ste.getClassName());
+ } else {
+ break;
+ }
+ }
+ return traces.toArray(new String[traces.size()]);
+ } catch (final InvocationTargetException ITE) {
+ throw paramOrError(ITE);
+ }
+ }
+
+ /**
+ * Creates a new object from the given class name.
+ *
+ * @param The generic class type.
+ * @param name the fully qualified class name.
+ * @param type the assignable type for the given name.
+ * @return a new object assignable to the given type.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static T newObjectFrom(String name, Class type) throws Exception {
+ try {
+ final Class> clazz = LogManagerProperties.findClass(name);
+ //This check avoids additional side effects when the name parameter
+ //is a literal name and not a class name.
+ if (type.isAssignableFrom(clazz)) {
+ try {
+ return type.cast(clazz.getConstructor().newInstance());
+ } catch (final InvocationTargetException ITE) {
+ throw paramOrError(ITE);
+ }
+ } else {
+ throw new ClassCastException(clazz.getName()
+ + " cannot be cast to " + type.getName());
+ }
+ } catch (final NoClassDefFoundError NCDFE) {
+ //No class def found can occur on filesystems that are
+ //case insensitive (BUG ID 6196068). In some cases, we allow class
+ //names or literal names, this code guards against the case where a
+ //literal name happens to match a class name in a different case.
+ //This is also a nice way to adapt this error for the error manager.
+ throw new ClassNotFoundException(NCDFE.toString(), NCDFE);
+ } catch (final ExceptionInInitializerError EIIE) {
+ throw wrapOrThrow(EIIE);
+ }
+ }
+
+ /**
+ * Returns the given exception or throws the escaping cause.
+ *
+ * @param ite any invocation target.
+ * @return the exception.
+ * @throws VirtualMachineError if present as cause.
+ * @throws ThreadDeath if present as cause.
+ * @since JavaMail 1.4.5
+ */
+ private static Exception paramOrError(InvocationTargetException ite) {
+ final Throwable cause = ite.getCause();
+ if (cause != null) {
+ //Bitwise inclusive OR produces tighter bytecode for instanceof
+ //and matches with multicatch syntax.
+ if (cause instanceof VirtualMachineError
+ | cause instanceof ThreadDeath) {
+ throw (Error) cause;
+ }
+ }
+ return ite;
+ }
+
+ /**
+ * Throws the given error if the cause is an error otherwise the given error
+ * is wrapped.
+ *
+ * @param eiie the error.
+ * @return an InvocationTargetException.
+ * @since JavaMail 1.5.0
+ */
+ private static InvocationTargetException wrapOrThrow(
+ ExceptionInInitializerError eiie) {
+ //This linkage error will escape the constructor new instance call.
+ //If the cause is an error, rethrow to skip any error manager.
+ if (eiie.getCause() instanceof Error) {
+ throw eiie;
+ } else {
+ //Considered a bug in the code, wrap the error so it can be
+ //reported to the error manager.
+ return new InvocationTargetException(eiie);
+ }
+ }
+
+ /**
+ * This code is modified from the LogManager, which explictly states
+ * searching the system class loader first, then the context class loader.
+ * There is resistance (compatibility) to change this behavior to simply
+ * searching the context class loader.
+ *
+ * @param name full class name
+ * @return the class.
+ * @throws LinkageError if the linkage fails.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws ExceptionInInitializerError if static initializer fails.
+ */
+ private static Class> findClass(String name) throws ClassNotFoundException {
+ ClassLoader[] loaders = getClassLoaders();
+ assert loaders.length == 2 : loaders.length;
+ Class> clazz;
+ if (loaders[0] != null) {
+ try {
+ clazz = Class.forName(name, false, loaders[0]);
+ } catch (ClassNotFoundException tryContext) {
+ clazz = tryLoad(name, loaders[1]);
+ }
+ } else {
+ clazz = tryLoad(name, loaders[1]);
+ }
+ return clazz;
+ }
+
+ /**
+ * Loads a class using the given loader or the class loader of this class.
+ *
+ * @param name the class name.
+ * @param l any class loader or null.
+ * @return the raw class.
+ * @throws ClassNotFoundException if not found.
+ */
+ private static Class> tryLoad(String name, ClassLoader l) throws ClassNotFoundException {
+ if (l != null) {
+ return Class.forName(name, false, l);
+ } else {
+ return Class.forName(name);
+ }
+ }
+
+ /**
+ * Gets the class loaders using elevated privileges.
+ *
+ * @return any array of class loaders. Indexes may be null.
+ */
+ private static ClassLoader[] getClassLoaders() {
+ return AccessController.doPrivileged(new PrivilegedAction() {
+
+ @SuppressWarnings("override") //JDK-6954234
+ public ClassLoader[] run() {
+ final ClassLoader[] loaders = new ClassLoader[2];
+ try {
+ loaders[0] = ClassLoader.getSystemClassLoader();
+ } catch (SecurityException ignore) {
+ loaders[0] = null;
+ }
+
+ try {
+ loaders[1] = Thread.currentThread().getContextClassLoader();
+ } catch (SecurityException ignore) {
+ loaders[1] = null;
+ }
+ return loaders;
+ }
+ });
+ }
+ /**
+ * The namespace prefix to search LogManager and defaults.
+ */
+ private final String prefix;
+
+ /**
+ * Creates a log manager properties object.
+ *
+ * @param parent the parent properties.
+ * @param prefix the namespace prefix.
+ * @throws NullPointerException if prefix
or
+ * parent
is null
.
+ */
+ LogManagerProperties(final Properties parent, final String prefix) {
+ super(parent);
+ if (parent == null || prefix == null) {
+ throw new NullPointerException();
+ }
+ this.prefix = prefix;
+ }
+
+ /**
+ * Returns a properties object that contains a snapshot of the current
+ * state. This method violates the clone contract so that no instances of
+ * LogManagerProperties is exported for public use.
+ *
+ * @return the snapshot.
+ * @since JavaMail 1.4.4
+ */
+ @Override
+ @SuppressWarnings("CloneDoesntCallSuperClone")
+ public synchronized Object clone() {
+ return exportCopy(defaults);
+ }
+
+ /**
+ * Searches defaults, then searches the log manager if available or the
+ * system properties by the prefix property, and then by the key itself.
+ *
+ * @param key a non null key.
+ * @return the value for that key.
+ */
+ @Override
+ public synchronized String getProperty(final String key) {
+ String value = defaults.getProperty(key);
+ if (value == null) {
+ if (key.length() > 0) {
+ value = fromLogManager(prefix + '.' + key);
+ }
+
+ if (value == null) {
+ value = fromLogManager(key);
+ }
+
+ /**
+ * Copy the log manager properties as we read them. If a value is no
+ * longer present in the LogManager read it from here. The reason
+ * this works is because LogManager.reset() closes all attached
+ * handlers therefore, stale values only exist in closed handlers.
+ */
+ if (value != null) {
+ super.put(key, value);
+ } else {
+ Object v = super.get(key); //defaults are not used.
+ value = v instanceof String ? (String) v : null;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Calls getProperty directly. If getProperty returns null the default value
+ * is returned.
+ *
+ * @param key a key to search for.
+ * @param def the default value to use if not found.
+ * @return the value for the key.
+ * @since JavaMail 1.4.4
+ */
+ @Override
+ public String getProperty(final String key, final String def) {
+ final String value = this.getProperty(key);
+ return value == null ? def : value;
+ }
+
+ /**
+ * Required to work with PropUtil. Calls getProperty directly if the given
+ * key is a string. Otherwise, performs a get operation on the defaults
+ * followed by the normal hash table get.
+ *
+ * @param key any key.
+ * @return the value for the key or null.
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public synchronized Object get(final Object key) {
+ Object value;
+ if (key instanceof String) {
+ value = getProperty((String) key);
+ } else {
+ value = null;
+ }
+
+ //Search for non-string value.
+ if (value == null) {
+ value = defaults.get(key);
+ if (value == null && !defaults.containsKey(key)) {
+ value = super.get(key);
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager if the key doesn't exist in this properties.
+ *
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public synchronized Object put(final Object key, final Object value) {
+ if (key instanceof String && value instanceof String) {
+ final Object def = preWrite(key);
+ final Object man = super.put(key, value);
+ return man == null ? def : man;
+ } else {
+ return super.put(key, value);
+ }
+ }
+
+ /**
+ * Calls the put method directly.
+ *
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public Object setProperty(String key, String value) {
+ return this.put(key, value);
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager prior to returning.
+ *
+ * @param key any key.
+ * @return the value for the key or null.
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public synchronized boolean containsKey(final Object key) {
+ boolean found = key instanceof String
+ && getProperty((String) key) != null;
+ if (!found) {
+ found = defaults.containsKey(key) || super.containsKey(key);
+ }
+ return found;
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager if the key doesn't exist in this properties.
+ *
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ @Override
+ public synchronized Object remove(final Object key) {
+ final Object def = preWrite(key);
+ final Object man = super.remove(key);
+ return man == null ? def : man;
+ }
+
+ /**
+ * It is assumed that this method will never be called. No way to get the
+ * property names from LogManager.
+ *
+ * @return the property names
+ */
+ @Override
+ public Enumeration> propertyNames() {
+ assert false;
+ return super.propertyNames();
+ }
+
+ /**
+ * It is assumed that this method will never be called. The prefix value is
+ * not used for the equals method.
+ *
+ * @param o any object or null.
+ * @return true if equal, otherwise false.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof Properties == false) {
+ return false;
+ }
+ assert false : prefix;
+ return super.equals(o);
+ }
+
+ /**
+ * It is assumed that this method will never be called. See equals.
+ *
+ * @return the hash code.
+ */
+ @Override
+ public int hashCode() {
+ assert false : prefix.hashCode();
+ return super.hashCode();
+ }
+
+ /**
+ * Called before a write operation of a key. Caches a key read from the log
+ * manager in this properties object. The key is only cached if it is an
+ * instance of a String and this properties doesn't contain a copy of the
+ * key.
+ *
+ * @param key the key to search.
+ * @return the default value for the key.
+ */
+ private Object preWrite(final Object key) {
+ assert Thread.holdsLock(this);
+ return get(key);
+ }
+
+ /**
+ * Creates a public snapshot of this properties object using the given
+ * parent properties.
+ *
+ * @param parent the defaults to use with the snapshot.
+ * @return the safe snapshot.
+ */
+ private Properties exportCopy(final Properties parent) {
+ Thread.holdsLock(this);
+ final Properties child = new Properties(parent);
+ child.putAll(this);
+ return child;
+ }
+
+ /**
+ * It is assumed that this method will never be called. We return a safe
+ * copy for export to avoid locking this properties object or the defaults
+ * during write.
+ *
+ * @return the parent properties.
+ * @throws ObjectStreamException if there is a problem.
+ */
+ private synchronized Object writeReplace() throws ObjectStreamException {
+ assert false;
+ return exportCopy((Properties) defaults.clone());
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/MailHandler.java b/app/src/main/java/com/sun/mail/util/logging/MailHandler.java
new file mode 100644
index 0000000000..ecfe0684a4
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/MailHandler.java
@@ -0,0 +1,4416 @@
+/*
+ * Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2020 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util.logging;
+
+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.util.ByteArrayDataSource;
+
+/**
+ * Handler
that formats log records as an email message.
+ *
+ *
+ * This Handler
will store a fixed number of log records used to
+ * generate a single email message. When the internal buffer reaches capacity,
+ * all log records are formatted and placed in an email which is sent to an
+ * email server. The code to manually setup this handler can be as simple as
+ * the following:
+ *
+ *
+ * Properties props = new Properties();
+ * props.put("mail.smtp.host", "my-mail-server");
+ * props.put("mail.to", "me@example.com");
+ * props.put("verify", "local");
+ * MailHandler h = new MailHandler(props);
+ * h.setLevel(Level.WARNING);
+ *
+ *
+ *
+ * Configuration:
+ * The LogManager should define at least one or more recipient addresses and a
+ * mail host for outgoing email. The code to setup this handler via the
+ * logging properties can be as simple as the following:
+ *
+ *
+ * #Default MailHandler settings.
+ * com.sun.mail.util.logging.MailHandler.mail.smtp.host = my-mail-server
+ * com.sun.mail.util.logging.MailHandler.mail.to = me@example.com
+ * com.sun.mail.util.logging.MailHandler.level = WARNING
+ * com.sun.mail.util.logging.MailHandler.verify = local
+ *
+ *
+ * For a custom handler, e.g. com.foo.MyHandler
, the properties
+ * would be:
+ *
+ *
+ * #Subclass com.foo.MyHandler settings.
+ * com.foo.MyHandler.mail.smtp.host = my-mail-server
+ * com.foo.MyHandler.mail.to = me@example.com
+ * com.foo.MyHandler.level = WARNING
+ * com.foo.MyHandler.verify = local
+ *
+ *
+ * All mail properties documented in the Java Mail API
cascade to
+ * the LogManager by prefixing a key using the fully qualified class name of
+ * this MailHandler
or the fully qualified derived class name dot
+ * mail property. If the prefixed property is not found, then the mail property
+ * itself is searched in the LogManager. By default each
+ * MailHandler
is initialized using the following LogManager
+ * configuration properties where <handler-name>
refers to
+ * the fully qualified class name of the handler. If properties are not
+ * defined, or contain invalid values, then the specified default values are
+ * used.
+ *
+ *
+ * <handler-name>.attachment.filters a comma
+ * separated list of Filter
class names used to create each
+ * attachment. The literal null
is reserved for attachments that
+ * do not require filtering. (defaults to the
+ * {@linkplain java.util.logging.Handler#getFilter() body} filter)
+ *
+ * <handler-name>.attachment.formatters a comma
+ * separated list of Formatter
class names used to create each
+ * attachment. (default is no attachments)
+ *
+ * <handler-name>.attachment.names a comma separated
+ * list of names or Formatter
class names of each attachment. All
+ * control characters are removed from the attachment names.
+ * (default is {@linkplain java.util.logging.Formatter#toString() toString}
+ * of the attachment formatter)
+ *
+ * <handler-name>.authenticator name of an
+ * {@linkplain javax.mail.Authenticator} class used to provide login credentials
+ * to the email server or string literal that is the password used with the
+ * {@linkplain Authenticator#getDefaultUserName() default} user name.
+ * (default is null
)
+ *
+ * <handler-name>.capacity the max number of
+ * LogRecord
objects include in each email message.
+ * (defaults to 1000
)
+ *
+ * <handler-name>.comparator name of a
+ * {@linkplain java.util.Comparator} class used to sort the published
+ * LogRecord
objects prior to all formatting.
+ * (defaults to null
meaning records are unsorted).
+ *
+ * <handler-name>.comparator.reverse a boolean
+ * true
to reverse the order of the specified comparator or
+ * false
to retain the original order.
+ * (defaults to false
)
+ *
+ * <handler-name>.encoding the name of the Java
+ * {@linkplain java.nio.charset.Charset#name() character set} to use for the
+ * email message. (defaults to null
, the
+ * {@linkplain javax.mail.internet.MimeUtility#getDefaultJavaCharset() default}
+ * platform encoding).
+ *
+ * <handler-name>.errorManager name of an
+ * ErrorManager
class used to handle any configuration or mail
+ * transport problems. (defaults to java.util.logging.ErrorManager
)
+ *
+ * <handler-name>.filter name of a Filter
+ * class used for the body of the message. (defaults to null
,
+ * allow all records)
+ *
+ * <handler-name>.formatter name of a
+ * Formatter
class used to format the body of this message.
+ * (defaults to java.util.logging.SimpleFormatter
)
+ *
+ * <handler-name>.level specifies the default level
+ * for this Handler
(defaults to Level.WARNING
).
+ *
+ * <handler-name>.mail.bcc a comma separated list of
+ * addresses which will be blind carbon copied. Typically, this is set to the
+ * recipients that may need to be privately notified of a log message or
+ * notified that a log message was sent to a third party such as a support team.
+ * The empty string can be used to specify no blind carbon copied address.
+ * (defaults to null
, none)
+ *
+ * <handler-name>.mail.cc a comma separated list of
+ * addresses which will be carbon copied. Typically, this is set to the
+ * recipients that may need to be notified of a log message but, are not
+ * required to provide direct support. The empty string can be used to specify
+ * no carbon copied address. (defaults to null
, none)
+ *
+ * <handler-name>.mail.from a comma separated list of
+ * addresses which will be from addresses. Typically, this is set to the email
+ * address identifying the user running the application. The empty string can
+ * be used to override the default behavior and specify no from address.
+ * (defaults to the {@linkplain javax.mail.Message#setFrom() local address})
+ *
+ * <handler-name>.mail.host the host name or IP
+ * address of the email server. (defaults to null
, use
+ * {@linkplain Transport#protocolConnect default}
+ * Java Mail
behavior)
+ *
+ * <handler-name>.mail.reply.to a comma separated
+ * list of addresses which will be reply-to addresses. Typically, this is set
+ * to the recipients that provide support for the application itself. The empty
+ * string can be used to specify no reply-to address.
+ * (defaults to null
, none)
+ *
+ * <handler-name>.mail.to a comma separated list of
+ * addresses which will be send-to addresses. Typically, this is set to the
+ * recipients that provide support for the application, system, and/or
+ * supporting infrastructure. The empty string can be used to specify no
+ * send-to address which overrides the default behavior. (defaults to
+ * {@linkplain javax.mail.internet.InternetAddress#getLocalAddress
+ * local address}.)
+ *
+ * <handler-name>.mail.sender a single address
+ * identifying sender of the email; never equal to the from address. Typically,
+ * this is set to the email address identifying the application itself. The
+ * empty string can be used to specify no sender address.
+ * (defaults to null
, none)
+ *
+ * <handler-name>.subject the name of a
+ * Formatter
class or string literal used to create the subject
+ * line. The empty string can be used to specify no subject. All control
+ * characters are removed from the subject line. (defaults to {@linkplain
+ * com.sun.mail.util.logging.CollectorFormatter CollectorFormatter}.)
+ *
+ * <handler-name>.pushFilter the name of a
+ * Filter
class used to trigger an early push.
+ * (defaults to null
, no early push)
+ *
+ * <handler-name>.pushLevel the level which will
+ * trigger an early push. (defaults to Level.OFF
, only push when
+ * full)
+ *
+ * <handler-name>.verify used to
+ * verify the Handler
configuration prior to a push.
+ *
+ * If the value is not set, equal to an empty string, or equal to the
+ * literal null
then no settings are verified prior to a push.
+ * If set to a value of limited
then the
+ * Handler
will verify minimal local machine settings.
+ * If set to a value of local
the Handler
+ * will verify all of settings of the local machine.
+ * If set to a value of resolve
, the Handler
+ * will verify all local settings and try to resolve the remote host name
+ * with the domain name server.
+ * If set to a value of login
, the Handler
+ * will verify all local settings and try to establish a connection with
+ * the email server.
+ * If set to a value of remote
, the Handler
+ * will verify all local settings, try to establish a connection with the
+ * email server, and try to verify the envelope of the email message.
+ *
+ * If this Handler
is only implicitly closed by the
+ * LogManager
, then verification should be turned on.
+ * (defaults to null
, no verify).
+ *
+ *
+ *
+ * Normalization:
+ * The error manager, filters, and formatters when loaded from the LogManager
+ * are converted into canonical form inside the MailHandler. The pool of
+ * interned values is limited to each MailHandler object such that no two
+ * MailHandler objects created by the LogManager will be created sharing
+ * identical error managers, filters, or formatters. If a filter or formatter
+ * should not be interned then it is recommended to retain the identity
+ * equals and identity hashCode methods as the implementation. For a filter or
+ * formatter to be interned the class must implement the
+ * {@linkplain java.lang.Object#equals(java.lang.Object) equals}
+ * and {@linkplain java.lang.Object#hashCode() hashCode} methods.
+ * The recommended code to use for stateless filters and formatters is:
+ *
+ * public boolean equals(Object obj) {
+ * return obj == null ? false : obj.getClass() == getClass();
+ * }
+ *
+ * public int hashCode() {
+ * return 31 * getClass().hashCode();
+ * }
+ *
+ *
+ *
+ * Sorting:
+ * All LogRecord
objects are ordered prior to formatting if this
+ * Handler
has a non null comparator. Developers might be
+ * interested in sorting the formatted email by thread id, time, and sequence
+ * properties of a LogRecord
. Where as system administrators might
+ * be interested in sorting the formatted email by thrown, level, time, and
+ * sequence properties of a LogRecord
. If comparator for this
+ * handler is null
then the order is unspecified.
+ *
+ *
+ * Formatting:
+ * The main message body is formatted using the Formatter
returned
+ * by getFormatter()
. Only records that pass the filter returned
+ * by getFilter()
will be included in the message body. The
+ * subject Formatter
will see all LogRecord
objects
+ * that were published regardless of the current Filter
. The MIME
+ * type of the message body can be
+ * {@linkplain FileTypeMap#setDefaultFileTypeMap overridden}
+ * by adding a MIME {@linkplain MimetypesFileTypeMap entry} using the simple
+ * class name of the body formatter as the file extension. The MIME type of the
+ * attachments can be overridden by changing the attachment file name extension
+ * or by editing the default MIME entry for a specific file name extension.
+ *
+ *
+ * Attachments:
+ * This Handler
allows multiple attachments per each email message.
+ * The presence of an attachment formatter will change the content type of the
+ * email message to a multi-part message. The attachment order maps directly to
+ * the array index order in this Handler
with zero index being the
+ * first attachment. The number of attachment formatters controls the number of
+ * attachments per email and the content type of each attachment. The
+ * attachment filters determine if a LogRecord
will be included in
+ * an attachment. If an attachment filter is null
then all records
+ * are included for that attachment. Attachments without content will be
+ * omitted from email message. The attachment name formatters create the file
+ * name for an attachment. Custom attachment name formatters can be used to
+ * generate an attachment name based on the contents of the attachment.
+ *
+ *
+ * Push Level and Push Filter:
+ * The push method, push level, and optional push filter can be used to
+ * conditionally trigger a push at or prior to full capacity. When a push
+ * occurs, the current buffer is formatted into an email and is sent to the
+ * email server. If the push method, push level, or push filter trigger a push
+ * then the outgoing email is flagged as high importance with urgent priority.
+ *
+ *
+ * Buffering:
+ * Log records that are published are stored in an internal buffer. When this
+ * buffer reaches capacity the existing records are formatted and sent in an
+ * email. Any published records can be sent before reaching capacity by
+ * explictly calling the flush
, push
, or
+ * close
methods. If a circular buffer is required then this
+ * handler can be wrapped with a {@linkplain java.util.logging.MemoryHandler}
+ * typically with an equivalent capacity, level, and push level.
+ *
+ *
+ * Error Handling:
+ * If the transport of an email message fails, the email is converted to
+ * a {@linkplain javax.mail.internet.MimeMessage#writeTo raw}
+ * {@linkplain java.io.ByteArrayOutputStream#toString(java.lang.String) string}
+ * and is then passed as the msg
parameter to
+ * {@linkplain Handler#reportError reportError} along with the exception
+ * describing the cause of the failure. This allows custom error managers to
+ * store, {@linkplain javax.mail.internet.MimeMessage#MimeMessage(
+ * javax.mail.Session, java.io.InputStream) reconstruct}, and resend the
+ * original MimeMessage. The message parameter string is not a raw email
+ * if it starts with value returned from Level.SEVERE.getName()
.
+ * Custom error managers can use the following test to determine if the
+ * msg
parameter from this handler is a raw email:
+ *
+ *
+ * public void error(String msg, Exception ex, int code) {
+ * if (msg == null || msg.length() == 0 || msg.startsWith(Level.SEVERE.getName())) {
+ * super.error(msg, ex, code);
+ * } else {
+ * //The 'msg' parameter is a raw email.
+ * }
+ * }
+ *
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.4.3
+ */
+public class MailHandler extends Handler {
+ /**
+ * Use the emptyFilterArray method.
+ */
+ private static final Filter[] EMPTY_FILTERS = new Filter[0];
+ /**
+ * Use the emptyFormatterArray method.
+ */
+ private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
+ /**
+ * Min byte size for header data. Used for initial arrays sizing.
+ */
+ private static final int MIN_HEADER_SIZE = 1024;
+ /**
+ * Cache the off value.
+ */
+ private static final int offValue = Level.OFF.intValue();
+ /**
+ * The action to set the context class loader for use with the Jakarta Mail API.
+ * Load and pin this before it is loaded in the close method. The field is
+ * declared as java.security.PrivilegedAction so
+ * WebappClassLoader.clearReferencesStaticFinal() method will ignore this
+ * field.
+ */
+ private static final PrivilegedAction MAILHANDLER_LOADER
+ = new GetAndSetContext(MailHandler.class);
+ /**
+ * A thread local mutex used to prevent logging loops. This code has to be
+ * prepared to deal with unexpected null values since the
+ * WebappClassLoader.clearReferencesThreadLocals() and
+ * InnocuousThread.eraseThreadLocals() can remove thread local values.
+ * The MUTEX has 5 states:
+ * 1. A null value meaning default state of not publishing.
+ * 2. MUTEX_PUBLISH on first entry of a push or publish.
+ * 3. The index of the first filter to accept a log record.
+ * 4. MUTEX_REPORT when cycle of records is detected.
+ * 5. MUTEXT_LINKAGE when a linkage error is reported.
+ */
+ private static final ThreadLocal MUTEX = new ThreadLocal<>();
+ /**
+ * The marker object used to report a publishing state.
+ * This must be less than the body filter index (-1).
+ */
+ private static final Integer MUTEX_PUBLISH = -2;
+ /**
+ * The used for the error reporting state.
+ * This must be less than the PUBLISH state.
+ */
+ private static final Integer MUTEX_REPORT = -4;
+ /**
+ * The used for linkage error reporting.
+ * This must be less than the REPORT state.
+ */
+ private static final Integer MUTEX_LINKAGE = -8;
+ /**
+ * Used to turn off security checks.
+ */
+ private volatile boolean sealed;
+ /**
+ * Determines if we are inside of a push.
+ * Makes the handler properties read-only during a push.
+ */
+ private boolean isWriting;
+ /**
+ * Holds all of the email server properties.
+ */
+ private Properties mailProps;
+ /**
+ * Holds the authenticator required to login to the email server.
+ */
+ private Authenticator auth;
+ /**
+ * Holds the session object used to generate emails.
+ * Sessions can be shared by multiple threads.
+ * See JDK-6228391 and K 6278.
+ */
+ private Session session;
+ /**
+ * A mapping of log record to matching filter index. Negative one is used
+ * to track the body filter. Zero and greater is used to track the
+ * attachment parts. All indexes less than or equal to the matched value
+ * have already seen the given log record.
+ */
+ private int[] matched;
+ /**
+ * Holds all of the log records that will be used to create the email.
+ */
+ private LogRecord[] data;
+ /**
+ * The number of log records in the buffer.
+ */
+ private int size;
+ /**
+ * The maximum number of log records to format per email.
+ * Used to roughly bound the size of an email.
+ * Every time the capacity is reached, the handler will push.
+ * The capacity will be negative if this handler is closed.
+ * Negative values are used to ensure all records are pushed.
+ */
+ private int capacity;
+ /**
+ * Used to order all log records prior to formatting. The main email body
+ * and all attachments use the order determined by this comparator. If no
+ * comparator is present the log records will be in no specified order.
+ */
+ private Comparator super LogRecord> comparator;
+ /**
+ * Holds the formatter used to create the subject line of the email.
+ * A subject formatter is not required for the email message.
+ * All published records pass through the subject formatter.
+ */
+ private Formatter subjectFormatter;
+ /**
+ * Holds the push level for this handler.
+ * This is only required if an email must be sent prior to shutdown
+ * or before the buffer is full.
+ */
+ private Level pushLevel;
+ /**
+ * Holds the push filter for trigger conditions requiring an early push.
+ * Only gets called if the given log record is greater than or equal
+ * to the push level and the push level is not Level.OFF.
+ */
+ private Filter pushFilter;
+ /**
+ * Holds the entry and body filter for this handler.
+ * There is no way to un-seal the super handler.
+ */
+ private volatile Filter filter;
+ /**
+ * Holds the level for this handler.
+ * There is no way to un-seal the super handler.
+ */
+ private volatile Level logLevel = Level.ALL;
+ /**
+ * Holds the filters for each attachment. Filters are optional for
+ * each attachment. This is declared volatile because this is treated as
+ * copy-on-write. The VO_VOLATILE_REFERENCE_TO_ARRAY warning is a false
+ * positive.
+ */
+ @SuppressWarnings("VolatileArrayField")
+ private volatile Filter[] attachmentFilters;
+ /**
+ * Holds the encoding name for this handler.
+ * There is no way to un-seal the super handler.
+ */
+ private String encoding;
+ /**
+ * Holds the entry and body filter for this handler.
+ * There is no way to un-seal the super handler.
+ */
+ private Formatter formatter;
+ /**
+ * Holds the formatters that create the content for each attachment.
+ * Each formatter maps directly to an attachment. The formatters
+ * getHead, format, and getTail methods are only called if one or more
+ * log records pass through the attachment filters.
+ */
+ private Formatter[] attachmentFormatters;
+ /**
+ * Holds the formatters that create the file name for each attachment.
+ * Each formatter must produce a non null and non empty name.
+ * The final file name will be the concatenation of one getHead call, plus
+ * all of the format calls, plus one getTail call.
+ */
+ private Formatter[] attachmentNames;
+ /**
+ * Used to override the content type for the body and set the content type
+ * for each attachment.
+ */
+ private FileTypeMap contentTypes;
+ /**
+ * Holds the error manager for this handler.
+ * There is no way to un-seal the super handler.
+ */
+ private volatile ErrorManager errorManager = defaultErrorManager();
+
+ /**
+ * Creates a MailHandler
that is configured by the
+ * LogManager
configuration properties.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ public MailHandler() {
+ init((Properties) null);
+ sealed = true;
+ checkAccess();
+ }
+
+ /**
+ * Creates a MailHandler
that is configured by the
+ * LogManager
configuration properties but overrides the
+ * LogManager
capacity with the given capacity.
+ * @param capacity of the internal buffer.
+ * @throws IllegalArgumentException if capacity
less than one.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ public MailHandler(final int capacity) {
+ init((Properties) null);
+ sealed = true;
+ setCapacity0(capacity);
+ }
+
+ /**
+ * Creates a mail handler with the given mail properties.
+ * The key/value pairs are defined in the Java Mail API
+ * documentation. This Handler
will also search the
+ * LogManager
for defaults if needed.
+ * @param props a non null
properties object.
+ * @throws NullPointerException if props
is null
.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ public MailHandler(final Properties props) {
+ if (props == null) {
+ throw new NullPointerException();
+ }
+ init(props);
+ sealed = true;
+ setMailProperties0(props);
+ }
+
+ /**
+ * Check if this Handler
would actually log a given
+ * LogRecord
into its internal buffer.
+ *
+ * This method checks if the LogRecord
has an appropriate level
+ * and whether it satisfies any Filter
including any
+ * attachment filters.
+ * However it does not check whether the LogRecord
would
+ * result in a "push" of the buffer contents.
+ *
+ * @param record a LogRecord
or null.
+ * @return true if the LogRecord
would be logged.
+ */
+ @Override
+ public boolean isLoggable(final LogRecord record) {
+ if (record == null) { //JDK-8233979
+ return false;
+ }
+
+ int levelValue = getLevel().intValue();
+ if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
+ return false;
+ }
+
+ Filter body = getFilter();
+ if (body == null || body.isLoggable(record)) {
+ setMatchedPart(-1);
+ return true;
+ }
+
+ return isAttachmentLoggable(record);
+ }
+
+ /**
+ * Stores a LogRecord
in the internal buffer.
+ *
+ * The isLoggable
method is called to check if the given log
+ * record is loggable. If the given record is loggable, it is copied into
+ * an internal buffer. Then the record's level property is compared with
+ * the push level. If the given level of the LogRecord
+ * is greater than or equal to the push level then the push filter is
+ * called. If no push filter exists, the push filter returns true,
+ * or the capacity of the internal buffer has been reached then all buffered
+ * records are formatted into one email and sent to the server.
+ *
+ * @param record description of the log event or null.
+ */
+ @Override
+ public void publish(final LogRecord record) {
+ /**
+ * It is possible for the handler to be closed after the
+ * call to isLoggable. In that case, the current thread
+ * will push to ensure that all published records are sent.
+ * See close().
+ */
+
+ if (tryMutex()) {
+ try {
+ if (isLoggable(record)) {
+ if (record != null) {
+ record.getSourceMethodName(); //Infer caller.
+ publish0(record);
+ } else { //Override of isLoggable is broken.
+ reportNullError(ErrorManager.WRITE_FAILURE);
+ }
+ }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.WRITE_FAILURE);
+ } finally {
+ releaseMutex();
+ }
+ } else {
+ reportUnPublishedError(record);
+ }
+ }
+
+ /**
+ * Performs the publish after the record has been filtered.
+ * @param record the record which must not be null.
+ * @since JavaMail 1.4.5
+ */
+ private void publish0(final LogRecord record) {
+ Message msg;
+ boolean priority;
+ synchronized (this) {
+ if (size == data.length && size < capacity) {
+ grow();
+ }
+
+ if (size < data.length) {
+ //assert data.length == matched.length;
+ matched[size] = getMatchedPart();
+ data[size] = record;
+ ++size; //Be nice to client compiler.
+ priority = isPushable(record);
+ if (priority || size >= capacity) {
+ msg = writeLogRecords(ErrorManager.WRITE_FAILURE);
+ } else {
+ msg = null;
+ }
+ } else {
+ priority = false;
+ msg = null;
+ }
+ }
+
+ if (msg != null) {
+ send(msg, priority, ErrorManager.WRITE_FAILURE);
+ }
+ }
+
+ /**
+ * Report to the error manager that a logging loop was detected and
+ * we are going to break the cycle of messages. It is possible that
+ * a custom error manager could continue the cycle in which case
+ * we will stop trying to report errors.
+ * @param record the record or null.
+ * @since JavaMail 1.4.6
+ */
+ private void reportUnPublishedError(LogRecord record) {
+ final Integer idx = MUTEX.get();
+ if (idx == null || idx > MUTEX_REPORT) {
+ MUTEX.set(MUTEX_REPORT);
+ try {
+ final String msg;
+ if (record != null) {
+ final Formatter f = createSimpleFormatter();
+ msg = "Log record " + record.getSequenceNumber()
+ + " was not published. "
+ + head(f) + format(f, record) + tail(f, "");
+ } else {
+ msg = null;
+ }
+ Exception e = new IllegalStateException(
+ "Recursive publish detected by thread "
+ + Thread.currentThread());
+ reportError(msg, e, ErrorManager.WRITE_FAILURE);
+ } finally {
+ if (idx != null) {
+ MUTEX.set(idx);
+ } else {
+ MUTEX.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Used to detect reentrance by the current thread to the publish method.
+ * This mutex is thread local scope and will not block other threads.
+ * The state is advanced on if the current thread is in a reset state.
+ * @return true if the mutex was acquired.
+ * @since JavaMail 1.4.6
+ */
+ private boolean tryMutex() {
+ if (MUTEX.get() == null) {
+ MUTEX.set(MUTEX_PUBLISH);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Releases the mutex held by the current thread.
+ * This mutex is thread local scope and will not block other threads.
+ * @since JavaMail 1.4.6
+ */
+ private void releaseMutex() {
+ MUTEX.remove();
+ }
+
+ /**
+ * This is used to get the filter index from when {@code isLoggable} and
+ * {@code isAttachmentLoggable} was invoked by {@code publish} method.
+ *
+ * @return the filter index or MUTEX_PUBLISH if unknown.
+ * @since JavaMail 1.5.5
+ * @throws NullPointerException if tryMutex was not called.
+ */
+ private int getMatchedPart() {
+ //assert Thread.holdsLock(this);
+ Integer idx = MUTEX.get();
+ if (idx == null || idx >= readOnlyAttachmentFilters().length) {
+ idx = MUTEX_PUBLISH;
+ }
+ return idx;
+ }
+
+ /**
+ * This is used to record the filter index when {@code isLoggable} and
+ * {@code isAttachmentLoggable} was invoked by {@code publish} method.
+ *
+ * @param index the filter index.
+ * @since JavaMail 1.5.5
+ */
+ private void setMatchedPart(int index) {
+ if (MUTEX_PUBLISH.equals(MUTEX.get())) {
+ MUTEX.set(index);
+ }
+ }
+
+ /**
+ * Clear previous matches when the filters are modified and there are
+ * existing log records that were matched.
+ * @param index the lowest filter index to clear.
+ * @since JavaMail 1.5.5
+ */
+ private void clearMatches(int index) {
+ assert Thread.holdsLock(this);
+ for (int r = 0; r < size; ++r) {
+ if (matched[r] >= index) {
+ matched[r] = MUTEX_PUBLISH;
+ }
+ }
+ }
+
+ /**
+ * A callback method for when this object is about to be placed into
+ * commission. This contract is defined by the
+ * {@code org.glassfish.hk2.api.PostConstruct} interface. If this class is
+ * loaded via a lifecycle managed environment other than HK2 then it is
+ * recommended that this method is called either directly or through
+ * extending this class to signal that this object is ready for use.
+ *
+ * @since JavaMail 1.5.3
+ */
+ //@javax.annotation.PostConstruct
+ public void postConstruct() {
+ }
+
+ /**
+ * A callback method for when this object is about to be decommissioned.
+ * This contract is defined by the {@code org.glassfish.hk2.api.PreDestory}
+ * interface. If this class is loaded via a lifecycle managed environment
+ * other than HK2 then it is recommended that this method is called either
+ * directly or through extending this class to signal that this object will
+ * be destroyed.
+ *
+ * @since JavaMail 1.5.3
+ */
+ //@javax.annotation.PreDestroy
+ public void preDestroy() {
+ /**
+ * Close can require permissions so just trigger a push.
+ */
+ push(false, ErrorManager.CLOSE_FAILURE);
+ }
+
+ /**
+ * Pushes any buffered records to the email server as high importance with
+ * urgent priority. The internal buffer is then cleared. Does nothing if
+ * called from inside a push.
+ * @see #flush()
+ */
+ public void push() {
+ push(true, ErrorManager.FLUSH_FAILURE);
+ }
+
+ /**
+ * Pushes any buffered records to the email server as normal priority.
+ * The internal buffer is then cleared. Does nothing if called from inside
+ * a push.
+ * @see #push()
+ */
+ @Override
+ public void flush() {
+ push(false, ErrorManager.FLUSH_FAILURE);
+ }
+
+ /**
+ * Prevents any other records from being published.
+ * Pushes any buffered records to the email server as normal priority.
+ * The internal buffer is then cleared. Once this handler is closed it
+ * will remain closed.
+ *
+ * If this Handler
is only implicitly closed by the
+ * LogManager
, then verification should
+ * be turned on.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @see #flush()
+ */
+ @Override
+ public void close() {
+ try {
+ checkAccess(); //Ensure setLevel works before clearing the buffer.
+ Message msg = null;
+ synchronized (this) {
+ try {
+ msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
+ } finally { //Change level after formatting.
+ this.logLevel = Level.OFF;
+ /**
+ * The sign bit of the capacity is set to ensure that
+ * records that have passed isLoggable, but have yet to be
+ * added to the internal buffer, are immediately pushed as
+ * an email.
+ */
+ if (this.capacity > 0) {
+ this.capacity = -this.capacity;
+ }
+
+ //Ensure not inside a push.
+ if (size == 0 && data.length != 1) {
+ this.data = new LogRecord[1];
+ this.matched = new int[this.data.length];
+ }
+ }
+ }
+
+ if (msg != null) {
+ send(msg, false, ErrorManager.CLOSE_FAILURE);
+ }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.CLOSE_FAILURE);
+ }
+ }
+
+ /**
+ * Set the log level specifying which message levels will be
+ * logged by this Handler
. Message levels lower than this
+ * value will be discarded.
+ * @param newLevel the new value for the log level
+ * @throws NullPointerException if newLevel
is
+ * null
.
+ * @throws SecurityException if a security manager exists and
+ * the caller does not have
+ * LoggingPermission("control")
.
+ */
+ @Override
+ public void setLevel(final Level newLevel) {
+ if (newLevel == null) {
+ throw new NullPointerException();
+ }
+ checkAccess();
+
+ //Don't allow a closed handler to be opened (half way).
+ synchronized (this) { //Wait for writeLogRecords.
+ if (this.capacity > 0) {
+ this.logLevel = newLevel;
+ }
+ }
+ }
+
+ /**
+ * Get the log level specifying which messages will be logged by this
+ * Handler
. Message levels lower than this level will be
+ * discarded.
+ *
+ * @return the level of messages being logged.
+ */
+ @Override
+ public Level getLevel() {
+ return logLevel; //Volatile access.
+ }
+
+ /**
+ * Retrieves the ErrorManager for this Handler.
+ *
+ * @return the ErrorManager for this Handler
+ * @throws SecurityException if a security manager exists and if the caller
+ * does not have LoggingPermission("control")
.
+ */
+ @Override
+ public ErrorManager getErrorManager() {
+ checkAccess();
+ return this.errorManager; //Volatile access.
+ }
+
+ /**
+ * Define an ErrorManager for this Handler.
+ *
+ * The ErrorManager's "error" method will be invoked if any errors occur
+ * while using this Handler.
+ *
+ * @param em the new ErrorManager
+ * @throws SecurityException if a security manager exists and if the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if the given error manager is null.
+ */
+ @Override
+ public void setErrorManager(final ErrorManager em) {
+ checkAccess();
+ setErrorManager0(em);
+ }
+
+ /**
+ * Sets the error manager on this handler and the super handler. In secure
+ * environments the super call may not be allowed which is not a failure
+ * condition as it is an attempt to free the unused handler error manager.
+ *
+ * @param em a non null error manager.
+ * @throws NullPointerException if the given error manager is null.
+ * @since JavaMail 1.5.6
+ */
+ private void setErrorManager0(final ErrorManager em) {
+ if (em == null) {
+ throw new NullPointerException();
+ }
+ try {
+ synchronized (this) { //Wait for writeLogRecords.
+ this.errorManager = em;
+ super.setErrorManager(em); //Try to free super error manager.
+ }
+ } catch (RuntimeException | LinkageError ignore) {
+ }
+ }
+
+ /**
+ * Get the current Filter
for this Handler
.
+ *
+ * @return a Filter
object (may be null)
+ */
+ @Override
+ public Filter getFilter() {
+ return this.filter; //Volatile access.
+ }
+
+ /**
+ * Set a Filter
to control output on this Handler
.
+ *
+ * For each call of publish
the Handler
will call
+ * this Filter
(if it is non-null) to check if the
+ * LogRecord
should be published or discarded.
+ *
+ * @param newFilter a Filter
object (may be null)
+ * @throws SecurityException if a security manager exists and if the caller
+ * does not have LoggingPermission("control")
.
+ */
+ @Override
+ public void setFilter(final Filter newFilter) {
+ checkAccess();
+ synchronized (this) { //Wait for writeLogRecords.
+ if (newFilter != filter) {
+ clearMatches(-1);
+ }
+ this.filter = newFilter; //Volatile access.
+ }
+ }
+
+ /**
+ * Return the character encoding for this Handler
.
+ *
+ * @return The encoding name. May be null, which indicates the default
+ * encoding should be used.
+ */
+ @Override
+ public synchronized String getEncoding() {
+ return this.encoding;
+ }
+
+ /**
+ * Set the character encoding used by this Handler
.
+ *
+ * The encoding should be set before any LogRecords
are written
+ * to the Handler
.
+ *
+ * @param encoding The name of a supported character encoding. May be
+ * null, to indicate the default platform encoding.
+ * @throws SecurityException if a security manager exists and if the caller
+ * does not have LoggingPermission("control")
.
+ * @throws UnsupportedEncodingException if the named encoding is not
+ * supported.
+ */
+ @Override
+ public void setEncoding(String encoding) throws UnsupportedEncodingException {
+ checkAccess();
+ setEncoding0(encoding);
+ }
+
+ /**
+ * Set the character encoding used by this handler. This method does not
+ * check permissions of the caller.
+ *
+ * @param e any encoding name or null for the default.
+ * @throws UnsupportedEncodingException if the given encoding is not supported.
+ */
+ private void setEncoding0(String e) throws UnsupportedEncodingException {
+ if (e != null) {
+ try {
+ if (!java.nio.charset.Charset.isSupported(e)) {
+ throw new UnsupportedEncodingException(e);
+ }
+ } catch (java.nio.charset.IllegalCharsetNameException icne) {
+ throw new UnsupportedEncodingException(e);
+ }
+ }
+
+ synchronized (this) { //Wait for writeLogRecords.
+ this.encoding = e;
+ }
+ }
+
+ /**
+ * Return the Formatter
for this Handler
.
+ *
+ * @return the Formatter
(may be null).
+ */
+ @Override
+ public synchronized Formatter getFormatter() {
+ return this.formatter;
+ }
+
+ /**
+ * Set a Formatter
. This Formatter
will be used
+ * to format LogRecords
for this Handler
.
+ *
+ * Some Handlers
may not use Formatters
, in which
+ * case the Formatter
will be remembered, but not used.
+ *
+ * @param newFormatter the Formatter
to use (may not be null)
+ * @throws SecurityException if a security manager exists and if the caller
+ * does not have LoggingPermission("control")
.
+ * @throws NullPointerException if the given formatter is null.
+ */
+ @Override
+ public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
+ checkAccess();
+ if (newFormatter == null) {
+ throw new NullPointerException();
+ }
+ this.formatter = newFormatter;
+ }
+
+ /**
+ * Gets the push level. The default is Level.OFF
meaning that
+ * this Handler
will only push when the internal buffer is full.
+ * @return the push level.
+ */
+ public final synchronized Level getPushLevel() {
+ return this.pushLevel;
+ }
+
+ /**
+ * Sets the push level. This level is used to trigger a push so that
+ * all pending records are formatted and sent to the email server. When
+ * the push level triggers a send, the resulting email is flagged as
+ * high importance with urgent priority.
+ * @param level Level object.
+ * @throws NullPointerException if level
is null
.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final synchronized void setPushLevel(final Level level) {
+ checkAccess();
+ if (level == null) {
+ throw new NullPointerException();
+ }
+
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.pushLevel = level;
+ }
+
+ /**
+ * Gets the push filter. The default is null
.
+ * @return the push filter or null
.
+ */
+ public final synchronized Filter getPushFilter() {
+ return this.pushFilter;
+ }
+
+ /**
+ * Sets the push filter. This filter is only called if the given
+ * LogRecord
level was greater than the push level. If this
+ * filter returns true
, all pending records are formatted and
+ * sent to the email server. When the push filter triggers a send, the
+ * resulting email is flagged as high importance with urgent priority.
+ * @param filter push filter or null
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final synchronized void setPushFilter(final Filter filter) {
+ checkAccess();
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.pushFilter = filter;
+ }
+
+ /**
+ * Gets the comparator used to order all LogRecord
objects
+ * prior to formatting. If null
then the order is unspecified.
+ * @return the LogRecord
comparator.
+ */
+ public final synchronized Comparator super LogRecord> getComparator() {
+ return this.comparator;
+ }
+
+ /**
+ * Sets the comparator used to order all LogRecord
objects
+ * prior to formatting. If null
then the order is unspecified.
+ * @param c the LogRecord
comparator.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final synchronized void setComparator(Comparator super LogRecord> c) {
+ checkAccess();
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.comparator = c;
+ }
+
+ /**
+ * Gets the number of log records the internal buffer can hold. When
+ * capacity is reached, Handler
will format all
+ * LogRecord
objects into one email message.
+ * @return the capacity.
+ */
+ public final synchronized int getCapacity() {
+ assert capacity != Integer.MIN_VALUE && capacity != 0 : capacity;
+ return Math.abs(capacity);
+ }
+
+ /**
+ * Gets the Authenticator
used to login to the email server.
+ * @return an Authenticator
or null
if none is
+ * required.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ public final synchronized Authenticator getAuthenticator() {
+ checkAccess();
+ return this.auth;
+ }
+
+ /**
+ * Sets the Authenticator
used to login to the email server.
+ * @param auth an Authenticator
object or null if none is
+ * required.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final void setAuthenticator(final Authenticator auth) {
+ this.setAuthenticator0(auth);
+ }
+
+ /**
+ * Sets the Authenticator
used to login to the email server.
+ * @param password a password, empty array can be used to only supply a
+ * user name set by mail.user
property, or null if no
+ * credentials are required.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ * @see String#toCharArray()
+ * @since JavaMail 1.4.6
+ */
+ public final void setAuthenticator(final char... password) {
+ if (password == null) {
+ setAuthenticator0((Authenticator) null);
+ } else {
+ setAuthenticator0(DefaultAuthenticator.of(new String(password)));
+ }
+ }
+
+ /**
+ * A private hook to handle possible future overrides. See public method.
+ * @param auth see public method.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ private void setAuthenticator0(final Authenticator auth) {
+ checkAccess();
+
+ Session settings;
+ synchronized (this) {
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.auth = auth;
+ settings = updateSession();
+ }
+ verifySettings(settings);
+ }
+
+ /**
+ * Sets the mail properties used for the session. The key/value pairs
+ * are defined in the Java Mail API
documentation. This
+ * Handler
will also search the LogManager
for
+ * defaults if needed.
+ * @param props a non null
properties object.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if props
is null
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final void setMailProperties(Properties props) {
+ this.setMailProperties0(props);
+ }
+
+ /**
+ * A private hook to handle overrides when the public method is declared
+ * non final. See public method for details.
+ * @param props see public method.
+ */
+ private void setMailProperties0(Properties props) {
+ checkAccess();
+ props = (Properties) props.clone(); //Allow subclass.
+ Session settings;
+ synchronized (this) {
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.mailProps = props;
+ settings = updateSession();
+ }
+ verifySettings(settings);
+ }
+
+ /**
+ * Gets a copy of the mail properties used for the session.
+ * @return a non null properties object.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ public final Properties getMailProperties() {
+ checkAccess();
+ final Properties props;
+ synchronized (this) {
+ props = this.mailProps;
+ }
+ return (Properties) props.clone();
+ }
+
+ /**
+ * Gets the attachment filters. If the attachment filter does not
+ * allow any LogRecord
to be formatted, the attachment may
+ * be omitted from the email.
+ * @return a non null array of attachment filters.
+ */
+ public final Filter[] getAttachmentFilters() {
+ return readOnlyAttachmentFilters().clone();
+ }
+
+ /**
+ * Sets the attachment filters.
+ * @param filters a non null
array of filters. A
+ * null
index value is allowed. A null
value
+ * means that all records are allowed for the attachment at that index.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if filters
is null
+ * @throws IndexOutOfBoundsException if the number of attachment
+ * name formatters do not match the number of attachment formatters.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final void setAttachmentFilters(Filter... filters) {
+ checkAccess();
+ if (filters.length == 0) {
+ filters = emptyFilterArray();
+ } else {
+ filters = Arrays.copyOf(filters, filters.length, Filter[].class);
+ }
+ synchronized (this) {
+ if (this.attachmentFormatters.length != filters.length) {
+ throw attachmentMismatch(this.attachmentFormatters.length, filters.length);
+ }
+
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+
+ if (size != 0) {
+ for (int i = 0; i < filters.length; ++i) {
+ if (filters[i] != attachmentFilters[i]) {
+ clearMatches(i);
+ break;
+ }
+ }
+ }
+ this.attachmentFilters = filters;
+ }
+ }
+
+ /**
+ * Gets the attachment formatters. This Handler
is using
+ * attachments only if the returned array length is non zero.
+ * @return a non null
array of formatters.
+ */
+ public final Formatter[] getAttachmentFormatters() {
+ Formatter[] formatters;
+ synchronized (this) {
+ formatters = this.attachmentFormatters;
+ }
+ return formatters.clone();
+ }
+
+ /**
+ * Sets the attachment Formatter
object for this handler.
+ * The number of formatters determines the number of attachments per
+ * email. This method should be the first attachment method called.
+ * To remove all attachments, call this method with empty array.
+ * @param formatters a non null array of formatters.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if the given array or any array index is
+ * null
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ public final void setAttachmentFormatters(Formatter... formatters) {
+ checkAccess();
+ if (formatters.length == 0) { //Null check and length check.
+ formatters = emptyFormatterArray();
+ } else {
+ formatters = Arrays.copyOf(formatters,
+ formatters.length, Formatter[].class);
+ for (int i = 0; i < formatters.length; ++i) {
+ if (formatters[i] == null) {
+ throw new NullPointerException(atIndexMsg(i));
+ }
+ }
+ }
+
+ synchronized (this) {
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+
+ this.attachmentFormatters = formatters;
+ this.alignAttachmentFilters();
+ this.alignAttachmentNames();
+ }
+ }
+
+ /**
+ * Gets the attachment name formatters.
+ * If the attachment names were set using explicit names then
+ * the names can be returned by calling toString
on each
+ * attachment name formatter.
+ * @return non null
array of attachment name formatters.
+ */
+ public final Formatter[] getAttachmentNames() {
+ final Formatter[] formatters;
+ synchronized (this) {
+ formatters = this.attachmentNames;
+ }
+ return formatters.clone();
+ }
+
+ /**
+ * Sets the attachment file name for each attachment. All control
+ * characters are removed from the attachment names.
+ * This method will create a set of custom formatters.
+ * @param names an array of names.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IndexOutOfBoundsException if the number of attachment
+ * names do not match the number of attachment formatters.
+ * @throws IllegalArgumentException if any name is empty.
+ * @throws NullPointerException if any given array or name is
+ * null
.
+ * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
+ * @see Character#isISOControl(int)
+ */
+ public final void setAttachmentNames(final String... names) {
+ checkAccess();
+
+ final Formatter[] formatters;
+ if (names.length == 0) {
+ formatters = emptyFormatterArray();
+ } else {
+ formatters = new Formatter[names.length];
+ }
+
+ for (int i = 0; i < names.length; ++i) {
+ final String name = names[i];
+ if (name != null) {
+ if (name.length() > 0) {
+ formatters[i] = TailNameFormatter.of(name);
+ } else {
+ throw new IllegalArgumentException(atIndexMsg(i));
+ }
+ } else {
+ throw new NullPointerException(atIndexMsg(i));
+ }
+ }
+
+ synchronized (this) {
+ if (this.attachmentFormatters.length != names.length) {
+ throw attachmentMismatch(this.attachmentFormatters.length, names.length);
+ }
+
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.attachmentNames = formatters;
+ }
+ }
+
+ /**
+ * Sets the attachment file name formatters. The format method of each
+ * attachment formatter will see only the LogRecord
objects
+ * that passed its attachment filter during formatting. The format method
+ * will typically return an empty string. Instead of being used to format
+ * records, it is used to gather information about the contents of an
+ * attachment. The getTail
method should be used to construct
+ * the attachment file name and reset any formatter collected state. All
+ * control characters will be removed from the output of the formatter. The
+ * toString
method of the given formatter should be overridden
+ * to provide a useful attachment file name, if possible.
+ * @param formatters and array of attachment name formatters.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IndexOutOfBoundsException if the number of attachment
+ * name formatters do not match the number of attachment formatters.
+ * @throws NullPointerException if any given array or name is
+ * null
.
+ * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
+ * @see Character#isISOControl(int)
+ */
+ public final void setAttachmentNames(Formatter... formatters) {
+ checkAccess();
+
+ if (formatters.length == 0) {
+ formatters = emptyFormatterArray();
+ } else {
+ formatters = Arrays.copyOf(formatters, formatters.length,
+ Formatter[].class);
+ }
+
+ for (int i = 0; i < formatters.length; ++i) {
+ if (formatters[i] == null) {
+ throw new NullPointerException(atIndexMsg(i));
+ }
+ }
+
+ synchronized (this) {
+ if (this.attachmentFormatters.length != formatters.length) {
+ throw attachmentMismatch(this.attachmentFormatters.length,
+ formatters.length);
+ }
+
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+
+ this.attachmentNames = formatters;
+ }
+ }
+
+ /**
+ * Gets the formatter used to create the subject line.
+ * If the subject was created using a literal string then
+ * the toString
method can be used to get the subject line.
+ * @return the formatter.
+ */
+ public final synchronized Formatter getSubject() {
+ return this.subjectFormatter;
+ }
+
+ /**
+ * Sets a literal string for the email subject. All control characters are
+ * removed from the subject line.
+ * @param subject a non null
string.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if subject
is
+ * null
.
+ * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
+ * @see Character#isISOControl(int)
+ */
+ public final void setSubject(final String subject) {
+ if (subject != null) {
+ this.setSubject(TailNameFormatter.of(subject));
+ } else {
+ checkAccess();
+ throw new NullPointerException();
+ }
+ }
+
+ /**
+ * Sets the subject formatter for email. The format method of the subject
+ * formatter will see all LogRecord
objects that were published
+ * to this Handler
during formatting and will typically return
+ * an empty string. This formatter is used to gather information to create
+ * a summary about what information is contained in the email. The
+ * getTail
method should be used to construct the subject and
+ * reset any formatter collected state. All control characters
+ * will be removed from the formatter output. The toString
+ * method of the given formatter should be overridden to provide a useful
+ * subject, if possible.
+ * @param format the subject formatter.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws NullPointerException if format
is null
.
+ * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
+ * @see Character#isISOControl(int)
+ */
+ public final void setSubject(final Formatter format) {
+ checkAccess();
+ if (format == null) {
+ throw new NullPointerException();
+ }
+
+ synchronized (this) {
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.subjectFormatter = format;
+ }
+ }
+
+ /**
+ * Protected convenience method to report an error to this Handler's
+ * ErrorManager. This method will prefix all non null error messages with
+ * Level.SEVERE.getName()
. This allows the receiving error
+ * manager to determine if the msg
parameter is a simple error
+ * message or a raw email message.
+ * @param msg a descriptive string (may be null)
+ * @param ex an exception (may be null)
+ * @param code an error code defined in ErrorManager
+ */
+ @Override
+ protected void reportError(String msg, Exception ex, int code) {
+ try {
+ if (msg != null) {
+ errorManager.error(Level.SEVERE.getName()
+ .concat(": ").concat(msg), ex, code);
+ } else {
+ errorManager.error(null, ex, code);
+ }
+ } catch (RuntimeException | LinkageError GLASSFISH_21258) {
+ reportLinkageError(GLASSFISH_21258, code);
+ }
+ }
+
+ /**
+ * Calls log manager checkAccess if this is sealed.
+ */
+ private void checkAccess() {
+ if (sealed) {
+ LogManagerProperties.checkLogManagerAccess();
+ }
+ }
+
+ /**
+ * Determines the mimeType of a formatter from the getHead call.
+ * This could be made protected, or a new class could be created to do
+ * this type of conversion. Currently, this is only used for the body
+ * since the attachments are computed by filename.
+ * Package-private for unit testing.
+ * @param chunk any char sequence or null.
+ * @return return the mime type or null for text/plain.
+ */
+ final String contentTypeOf(CharSequence chunk) {
+ if (!isEmpty(chunk)) {
+ final int MAX_CHARS = 25;
+ if (chunk.length() > MAX_CHARS) {
+ chunk = chunk.subSequence(0, MAX_CHARS);
+ }
+ try {
+ final String charset = getEncodingName();
+ final byte[] b = chunk.toString().getBytes(charset);
+ final ByteArrayInputStream in = new ByteArrayInputStream(b);
+ assert in.markSupported() : in.getClass().getName();
+ return URLConnection.guessContentTypeFromStream(in);
+ } catch (final IOException IOE) {
+ reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ return null; //text/plain
+ }
+
+ /**
+ * Determines the mimeType of a formatter by the class name. This method
+ * avoids calling getHead and getTail of content formatters during verify
+ * because they might trigger side effects or excessive work. The name
+ * formatters and subject are usually safe to call.
+ * Package-private for unit testing.
+ *
+ * @param f the formatter or null.
+ * @return return the mime type or null, meaning text/plain.
+ * @since JavaMail 1.5.6
+ */
+ final String contentTypeOf(final Formatter f) {
+ assert Thread.holdsLock(this);
+ if (f != null) {
+ String type = getContentType(f.getClass().getName());
+ if (type != null) {
+ return type;
+ }
+
+ for (Class> k = f.getClass(); k != Formatter.class;
+ k = k.getSuperclass()) {
+ String name;
+ try {
+ name = k.getSimpleName();
+ } catch (final InternalError JDK8057919) {
+ name = k.getName();
+ }
+ name = name.toLowerCase(Locale.ENGLISH);
+ for (int idx = name.indexOf('$') + 1;
+ (idx = name.indexOf("ml", idx)) > -1; idx += 2) {
+ if (idx > 0) {
+ if (name.charAt(idx - 1) == 'x') {
+ return "application/xml";
+ }
+ if (idx > 1 && name.charAt(idx - 2) == 'h'
+ && name.charAt(idx - 1) == 't') {
+ return "text/html";
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determines if the given throwable is a no content exception. It is
+ * assumed Transport.sendMessage will call Message.writeTo so we need to
+ * ignore any exceptions that could be layered on top of that call chain to
+ * infer that sendMessage is failing because of writeTo. Package-private
+ * for unit testing.
+ * @param msg the message without content.
+ * @param t the throwable chain to test.
+ * @return true if the throwable is a missing content exception.
+ * @throws NullPointerException if any of the arguments are null.
+ * @since JavaMail 1.4.5
+ */
+ @SuppressWarnings({"UseSpecificCatch", "ThrowableResultIgnored"})
+ final boolean isMissingContent(Message msg, Throwable t) {
+ final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try {
+ msg.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+ } catch (final RuntimeException RE) {
+ throw RE; //Avoid catch all.
+ } catch (final Exception noContent) {
+ final String txt = noContent.getMessage();
+ if (!isEmpty(txt)) {
+ int limit = 0;
+ while (t != null) {
+ if (noContent.getClass() == t.getClass()
+ && txt.equals(t.getMessage())) {
+ return true;
+ }
+
+ //Not all Jakarta Mail implementations support JDK 1.4
+ //exception chaining.
+ final Throwable cause = t.getCause();
+ if (cause == null && t instanceof MessagingException) {
+ t = ((MessagingException) t).getNextException();
+ } else {
+ t = cause;
+ }
+
+ //Deal with excessive cause chains and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+ }
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+ return false;
+ }
+
+ /**
+ * Converts a mime message to a raw string or formats the reason
+ * why message can't be changed to raw string and reports it.
+ * @param msg the mime message.
+ * @param ex the original exception.
+ * @param code the ErrorManager code.
+ * @since JavaMail 1.4.5
+ */
+ @SuppressWarnings("UseSpecificCatch")
+ private void reportError(Message msg, Exception ex, int code) {
+ try {
+ try { //Use direct call so we do not prefix raw email.
+ errorManager.error(toRawString(msg), ex, code);
+ } catch (final RuntimeException re) {
+ reportError(toMsgString(re), ex, code);
+ } catch (final Exception e) {
+ reportError(toMsgString(e), ex, code);
+ }
+ } catch (final LinkageError GLASSFISH_21258) {
+ reportLinkageError(GLASSFISH_21258, code);
+ }
+ }
+
+ /**
+ * Reports the given linkage error or runtime exception.
+ *
+ * The current LogManager code will stop closing all remaining handlers if
+ * an error is thrown during resetLogger. This is a workaround for
+ * GLASSFISH-21258 and JDK-8152515.
+ * @param le the linkage error or a RuntimeException.
+ * @param code the ErrorManager code.
+ * @throws NullPointerException if error is null.
+ * @since JavaMail 1.5.3
+ */
+ private void reportLinkageError(final Throwable le, final int code) {
+ if (le == null) {
+ throw new NullPointerException(String.valueOf(code));
+ }
+
+ final Integer idx = MUTEX.get();
+ if (idx == null || idx > MUTEX_LINKAGE) {
+ MUTEX.set(MUTEX_LINKAGE);
+ try {
+ Thread.currentThread().getUncaughtExceptionHandler()
+ .uncaughtException(Thread.currentThread(), le);
+ } catch (RuntimeException | LinkageError ignore) {
+ } finally {
+ if (idx != null) {
+ MUTEX.set(idx);
+ } else {
+ MUTEX.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines the mimeType from the given file name.
+ * Used to override the body content type and used for all attachments.
+ * @param name the file name or class name.
+ * @return the mime type or null for text/plain.
+ */
+ private String getContentType(final String name) {
+ assert Thread.holdsLock(this);
+ final String type = contentTypes.getContentType(name);
+ if ("application/octet-stream".equalsIgnoreCase(type)) {
+ return null; //Formatters return strings, default to text/plain.
+ }
+ return type;
+ }
+
+ /**
+ * Gets the encoding set for this handler, mime encoding, or file encoding.
+ * @return the java charset name, never null.
+ * @since JavaMail 1.4.5
+ */
+ private String getEncodingName() {
+ String charset = getEncoding();
+ if (charset == null) {
+ charset = MimeUtility.getDefaultJavaCharset();
+ }
+ return charset;
+ }
+
+ /**
+ * Set the content for a part using the encoding assigned to the handler.
+ * @param part the part to assign.
+ * @param buf the formatted data.
+ * @param type the mime type or null, meaning text/plain.
+ * @throws MessagingException if there is a problem.
+ */
+ private void setContent(MimePart part, CharSequence buf, String type) throws MessagingException {
+ final String charset = getEncodingName();
+ if (type != null && !"text/plain".equalsIgnoreCase(type)) {
+ type = contentWithEncoding(type, charset);
+ try {
+ DataSource source = new ByteArrayDataSource(buf.toString(), type);
+ part.setDataHandler(new DataHandler(source));
+ } catch (final IOException IOE) {
+ reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
+ part.setText(buf.toString(), charset);
+ }
+ } else {
+ part.setText(buf.toString(), MimeUtility.mimeCharset(charset));
+ }
+ }
+
+ /**
+ * Replaces the charset parameter with the current encoding.
+ * @param type the content type.
+ * @param encoding the java charset name.
+ * @return the type with a specified encoding.
+ */
+ private String contentWithEncoding(String type, String encoding) {
+ assert encoding != null;
+ try {
+ final ContentType ct = new ContentType(type);
+ ct.setParameter("charset", MimeUtility.mimeCharset(encoding));
+ encoding = ct.toString(); //See javax.mail.internet.ContentType.
+ if (!isEmpty(encoding)) { //Support pre K5687.
+ type = encoding;
+ }
+ } catch (final MessagingException ME) {
+ reportError(type, ME, ErrorManager.FORMAT_FAILURE);
+ }
+ return type;
+ }
+
+ /**
+ * Sets the capacity for this handler. This method is kept private
+ * because we would have to define a public policy for when the size is
+ * greater than the capacity.
+ * E.G. do nothing, flush now, truncate now, push now and resize.
+ * @param newCapacity the max number of records.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ * @throws IllegalStateException if called from inside a push.
+ */
+ private synchronized void setCapacity0(final int newCapacity) {
+ checkAccess();
+ if (newCapacity <= 0) {
+ throw new IllegalArgumentException("Capacity must be greater than zero.");
+ }
+
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+
+ if (this.capacity < 0) { //If closed, remain closed.
+ this.capacity = -newCapacity;
+ } else {
+ this.capacity = newCapacity;
+ }
+ }
+
+ /**
+ * Gets the attachment filters using a happens-before relationship between
+ * this method and setAttachmentFilters. The attachment filters are treated
+ * as copy-on-write, so the returned array must never be modified or
+ * published outside this class.
+ * @return a read only array of filters.
+ */
+ private Filter[] readOnlyAttachmentFilters() {
+ return this.attachmentFilters;
+ }
+
+ /**
+ * Factory for empty formatter arrays.
+ * @return an empty array.
+ */
+ private static Formatter[] emptyFormatterArray() {
+ return EMPTY_FORMATTERS;
+ }
+
+ /**
+ * Factory for empty filter arrays.
+ * @return an empty array.
+ */
+ private static Filter[] emptyFilterArray() {
+ return EMPTY_FILTERS;
+ }
+
+ /**
+ * Expand or shrink the attachment name formatters with the attachment
+ * formatters.
+ * @return true if size was changed.
+ */
+ private boolean alignAttachmentNames() {
+ assert Thread.holdsLock(this);
+ boolean fixed = false;
+ final int expect = this.attachmentFormatters.length;
+ final int current = this.attachmentNames.length;
+ if (current != expect) {
+ this.attachmentNames = Arrays.copyOf(attachmentNames, expect,
+ Formatter[].class);
+ fixed = current != 0;
+ }
+
+ //Copy of zero length array is cheap, warm up copyOf.
+ if (expect == 0) {
+ this.attachmentNames = emptyFormatterArray();
+ assert this.attachmentNames.length == 0;
+ } else {
+ for (int i = 0; i < expect; ++i) {
+ if (this.attachmentNames[i] == null) {
+ this.attachmentNames[i] = TailNameFormatter.of(
+ toString(this.attachmentFormatters[i]));
+ }
+ }
+ }
+ return fixed;
+ }
+
+ /**
+ * Expand or shrink the attachment filters with the attachment formatters.
+ * @return true if the size was changed.
+ */
+ private boolean alignAttachmentFilters() {
+ assert Thread.holdsLock(this);
+
+ boolean fixed = false;
+ final int expect = this.attachmentFormatters.length;
+ final int current = this.attachmentFilters.length;
+ if (current != expect) {
+ this.attachmentFilters = Arrays.copyOf(attachmentFilters, expect,
+ Filter[].class);
+ clearMatches(current);
+ fixed = current != 0;
+
+ //Array elements default to null so skip filling if body filter
+ //is null. If not null then only assign to expanded elements.
+ final Filter body = this.filter;
+ if (body != null) {
+ for (int i = current; i < expect; ++i) {
+ this.attachmentFilters[i] = body;
+ }
+ }
+ }
+
+ //Copy of zero length array is cheap, warm up copyOf.
+ if (expect == 0) {
+ this.attachmentFilters = emptyFilterArray();
+ assert this.attachmentFilters.length == 0;
+ }
+ return fixed;
+ }
+
+ /**
+ * Sets the size to zero and clears the current buffer.
+ */
+ private void reset() {
+ assert Thread.holdsLock(this);
+ if (size < data.length) {
+ Arrays.fill(data, 0, size, null);
+ } else {
+ Arrays.fill(data, null);
+ }
+ this.size = 0;
+ }
+
+ /**
+ * Expands the internal buffer up to the capacity.
+ */
+ private void grow() {
+ assert Thread.holdsLock(this);
+ final int len = data.length;
+ int newCapacity = len + (len >> 1) + 1;
+ if (newCapacity > capacity || newCapacity < len) {
+ newCapacity = capacity;
+ }
+ assert len != capacity : len;
+ this.data = Arrays.copyOf(data, newCapacity, LogRecord[].class);
+ this.matched = Arrays.copyOf(matched, newCapacity);
+ }
+
+ /**
+ * Configures the handler properties from the log manager.
+ * @param props the given mail properties. Maybe null and are never
+ * captured by this handler.
+ * @throws SecurityException if a security manager exists and the
+ * caller does not have LoggingPermission("control")
.
+ */
+ private synchronized void init(final Properties props) {
+ assert this.errorManager != null;
+ final String p = getClass().getName();
+ this.mailProps = new Properties(); //See method param comments.
+ final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try {
+ this.contentTypes = FileTypeMap.getDefaultFileTypeMap();
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+
+ //Assign any custom error manager first so it can detect all failures.
+ initErrorManager(p);
+
+ initLevel(p);
+ initFilter(p);
+ initCapacity(p);
+ initAuthenticator(p);
+
+ initEncoding(p);
+ initFormatter(p);
+ initComparator(p);
+ initPushLevel(p);
+ initPushFilter(p);
+
+ initSubject(p);
+
+ initAttachmentFormaters(p);
+ initAttachmentFilters(p);
+ initAttachmentNames(p);
+
+ if (props == null && fromLogManager(p.concat(".verify")) != null) {
+ verifySettings(initSession());
+ }
+ intern(); //Show verify warnings first.
+ }
+
+ /**
+ * Interns the error manager, formatters, and filters contained in this
+ * handler. The comparator is not interned. This method can only be
+ * called from init after all of formatters and filters are in a constructed
+ * and in a consistent state.
+ * @since JavaMail 1.5.0
+ */
+ private void intern() {
+ assert Thread.holdsLock(this);
+ try {
+ Object canidate;
+ Object result;
+ final Map seen = new HashMap<>();
+ try {
+ intern(seen, this.errorManager);
+ } catch (final SecurityException se) {
+ reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
+ }
+
+ try {
+ canidate = this.filter;
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Filter) {
+ this.filter = (Filter) result;
+ }
+
+ canidate = this.formatter;
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Formatter) {
+ this.formatter = (Formatter) result;
+ }
+ } catch (final SecurityException se) {
+ reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
+ }
+
+ canidate = this.subjectFormatter;
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Formatter) {
+ this.subjectFormatter = (Formatter) result;
+ }
+
+ canidate = this.pushFilter;
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Filter) {
+ this.pushFilter = (Filter) result;
+ }
+
+ for (int i = 0; i < attachmentFormatters.length; ++i) {
+ canidate = attachmentFormatters[i];
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Formatter) {
+ attachmentFormatters[i] = (Formatter) result;
+ }
+
+ canidate = attachmentFilters[i];
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Filter) {
+ attachmentFilters[i] = (Filter) result;
+ }
+
+ canidate = attachmentNames[i];
+ result = intern(seen, canidate);
+ if (result != canidate && result instanceof Formatter) {
+ attachmentNames[i] = (Formatter) result;
+ }
+ }
+ } catch (final Exception skip) {
+ reportError(skip.getMessage(), skip, ErrorManager.OPEN_FAILURE);
+ } catch (final LinkageError skip) {
+ reportError(skip.getMessage(), new InvocationTargetException(skip),
+ ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * If possible performs an intern of the given object into the
+ * map. If the object can not be interned the given object is returned.
+ * @param m the map used to record the interned values.
+ * @param o the object to try an intern.
+ * @return the original object or an intern replacement.
+ * @throws SecurityException if this operation is not allowed by the
+ * security manager.
+ * @throws Exception if there is an unexpected problem.
+ * @since JavaMail 1.5.0
+ */
+ private Object intern(Map m, Object o) throws Exception {
+ if (o == null) {
+ return null;
+ }
+
+ /**
+ * The common case is that most objects will not intern. The given
+ * object has a public no argument constructor or is an instance of a
+ * TailNameFormatter. TailNameFormatter is safe use as a map key.
+ * For everything else we create a clone of the given object.
+ * This is done because of the following:
+ * 1. Clones can be used to test that a class provides an equals method
+ * and that the equals method works correctly.
+ * 2. Calling equals on the given object is assumed to be cheap.
+ * 3. The intern map can be filtered so it only contains objects that
+ * can be interned, which reduces the memory footprint.
+ * 4. Clones are method local garbage.
+ * 5. Hash code is only called on the clones so bias locking is not
+ * disabled on the objects the handler will use.
+ */
+ final Object key;
+ if (o.getClass().getName().equals(TailNameFormatter.class.getName())) {
+ key = o;
+ } else {
+ //This call was already made in the LogManagerProperties so this
+ //shouldn't trigger loading of any lazy reflection code.
+ key = o.getClass().getConstructor().newInstance();
+ }
+
+ final Object use;
+ //Check the classloaders of each object avoiding the security manager.
+ if (key.getClass() == o.getClass()) {
+ Object found = m.get(key); //Transitive equals test.
+ if (found == null) {
+ //Ensure that equals is symmetric to prove intern is safe.
+ final boolean right = key.equals(o);
+ final boolean left = o.equals(key);
+ if (right && left) {
+ //Assume hashCode is defined at this point.
+ found = m.put(o, o);
+ if (found != null) {
+ reportNonDiscriminating(key, found);
+ found = m.remove(key);
+ if (found != o) {
+ reportNonDiscriminating(key, found);
+ m.clear(); //Try to restore order.
+ }
+ }
+ } else {
+ if (right != left) {
+ reportNonSymmetric(o, key);
+ }
+ }
+ use = o;
+ } else {
+ //Check for a discriminating equals method.
+ if (o.getClass() == found.getClass()) {
+ use = found;
+ } else {
+ reportNonDiscriminating(o, found);
+ use = o;
+ }
+ }
+ } else {
+ use = o;
+ }
+ return use;
+ }
+
+ /**
+ * Factory method used to create a java.util.logging.SimpleFormatter.
+ * @return a new SimpleFormatter.
+ * @since JavaMail 1.5.6
+ */
+ private static Formatter createSimpleFormatter() {
+ //Don't force the byte code verifier to load the formatter.
+ return Formatter.class.cast(new SimpleFormatter());
+ }
+
+ /**
+ * Checks a char sequence value for null or empty.
+ * @param s the char sequence.
+ * @return true if the given string is null or zero length.
+ */
+ private static boolean isEmpty(final CharSequence s) {
+ return s == null || s.length() == 0;
+ }
+
+ /**
+ * Checks that a string is not empty and not equal to the literal "null".
+ * @param name the string to check for a value.
+ * @return true if the string has a valid value.
+ */
+ private static boolean hasValue(final String name) {
+ return !isEmpty(name) && !"null".equalsIgnoreCase(name);
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initAttachmentFilters(final String p) {
+ assert Thread.holdsLock(this);
+ assert this.attachmentFormatters != null;
+ final String list = fromLogManager(p.concat(".attachment.filters"));
+ if (!isEmpty(list)) {
+ final String[] names = list.split(",");
+ Filter[] a = new Filter[names.length];
+ for (int i = 0; i < a.length; ++i) {
+ names[i] = names[i].trim();
+ if (!"null".equalsIgnoreCase(names[i])) {
+ try {
+ a[i] = LogManagerProperties.newFilter(names[i]);
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+ }
+
+ this.attachmentFilters = a;
+ if (alignAttachmentFilters()) {
+ reportError("Attachment filters.",
+ attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ this.attachmentFilters = emptyFilterArray();
+ alignAttachmentFilters();
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initAttachmentFormaters(final String p) {
+ assert Thread.holdsLock(this);
+ final String list = fromLogManager(p.concat(".attachment.formatters"));
+ if (!isEmpty(list)) {
+ final Formatter[] a;
+ final String[] names = list.split(",");
+ if (names.length == 0) {
+ a = emptyFormatterArray();
+ } else {
+ a = new Formatter[names.length];
+ }
+
+ for (int i = 0; i < a.length; ++i) {
+ names[i] = names[i].trim();
+ if (!"null".equalsIgnoreCase(names[i])) {
+ try {
+ a[i] = LogManagerProperties.newFormatter(names[i]);
+ if (a[i] instanceof TailNameFormatter) {
+ final Exception CNFE = new ClassNotFoundException(a[i].toString());
+ reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
+ a[i] = createSimpleFormatter();
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ a[i] = createSimpleFormatter();
+ }
+ } else {
+ final Exception NPE = new NullPointerException(atIndexMsg(i));
+ reportError("Attachment formatter.", NPE, ErrorManager.OPEN_FAILURE);
+ a[i] = createSimpleFormatter();
+ }
+ }
+
+ this.attachmentFormatters = a;
+ } else {
+ this.attachmentFormatters = emptyFormatterArray();
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initAttachmentNames(final String p) {
+ assert Thread.holdsLock(this);
+ assert this.attachmentFormatters != null;
+
+ final String list = fromLogManager(p.concat(".attachment.names"));
+ if (!isEmpty(list)) {
+ final String[] names = list.split(",");
+ final Formatter[] a = new Formatter[names.length];
+ for (int i = 0; i < a.length; ++i) {
+ names[i] = names[i].trim();
+ if (!"null".equalsIgnoreCase(names[i])) {
+ try {
+ try {
+ a[i] = LogManagerProperties.newFormatter(names[i]);
+ } catch (ClassNotFoundException
+ | ClassCastException literal) {
+ a[i] = TailNameFormatter.of(names[i]);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ final Exception NPE = new NullPointerException(atIndexMsg(i));
+ reportError("Attachment names.", NPE, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ this.attachmentNames = a;
+ if (alignAttachmentNames()) { //Any null indexes are repaired.
+ reportError("Attachment names.",
+ attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ this.attachmentNames = emptyFormatterArray();
+ alignAttachmentNames();
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initAuthenticator(final String p) {
+ assert Thread.holdsLock(this);
+ String name = fromLogManager(p.concat(".authenticator"));
+ if (name != null && !"null".equalsIgnoreCase(name)) {
+ if (name.length() != 0) {
+ try {
+ this.auth = LogManagerProperties
+ .newObjectFrom(name, Authenticator.class);
+ } catch (final SecurityException SE) {
+ throw SE;
+ } catch (final ClassNotFoundException
+ | ClassCastException literalAuth) {
+ this.auth = DefaultAuthenticator.of(name);
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else { //Authenticator is installed to provide the user name.
+ this.auth = DefaultAuthenticator.of(name);
+ }
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initLevel(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ final String val = fromLogManager(p.concat(".level"));
+ if (val != null) {
+ logLevel = Level.parse(val);
+ } else {
+ logLevel = Level.WARNING;
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ logLevel = Level.WARNING;
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initFilter(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String name = fromLogManager(p.concat(".filter"));
+ if (hasValue(name)) {
+ filter = LogManagerProperties.newFilter(name);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initCapacity(final String p) {
+ assert Thread.holdsLock(this);
+ final int DEFAULT_CAPACITY = 1000;
+ try {
+ final String value = fromLogManager(p.concat(".capacity"));
+ if (value != null) {
+ this.setCapacity0(Integer.parseInt(value));
+ } else {
+ this.setCapacity0(DEFAULT_CAPACITY);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ }
+
+ if (capacity <= 0) {
+ capacity = DEFAULT_CAPACITY;
+ }
+
+ this.data = new LogRecord[1];
+ this.matched = new int[this.data.length];
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initEncoding(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String e = fromLogManager(p.concat(".encoding"));
+ if (e != null) {
+ setEncoding0(e);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (UnsupportedEncodingException | RuntimeException UEE) {
+ reportError(UEE.getMessage(), UEE, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Used to get or create the default ErrorManager used before init.
+ * @return the super error manager or a new ErrorManager.
+ * @since JavaMail 1.5.3
+ */
+ private ErrorManager defaultErrorManager() {
+ ErrorManager em;
+ try { //Try to share the super error manager.
+ em = super.getErrorManager();
+ } catch (RuntimeException | LinkageError ignore) {
+ em = null;
+ }
+
+ //Don't assume that the super call is not null.
+ if (em == null) {
+ em = new ErrorManager();
+ }
+ return em;
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initErrorManager(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String name = fromLogManager(p.concat(".errorManager"));
+ if (name != null) {
+ setErrorManager0(LogManagerProperties.newErrorManager(name));
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initFormatter(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String name = fromLogManager(p.concat(".formatter"));
+ if (hasValue(name)) {
+ final Formatter f
+ = LogManagerProperties.newFormatter(name);
+ assert f != null;
+ if (f instanceof TailNameFormatter == false) {
+ formatter = f;
+ } else {
+ formatter = createSimpleFormatter();
+ }
+ } else {
+ formatter = createSimpleFormatter();
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ formatter = createSimpleFormatter();
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initComparator(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String name = fromLogManager(p.concat(".comparator"));
+ String reverse = fromLogManager(p.concat(".comparator.reverse"));
+ if (hasValue(name)) {
+ comparator = LogManagerProperties.newComparator(name);
+ if (Boolean.parseBoolean(reverse)) {
+ assert comparator != null : "null";
+ comparator = LogManagerProperties.reverseOrder(comparator);
+ }
+ } else {
+ if (!isEmpty(reverse)) {
+ throw new IllegalArgumentException(
+ "No comparator to reverse.");
+ }
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initPushLevel(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ final String val = fromLogManager(p.concat(".pushLevel"));
+ if (val != null) {
+ this.pushLevel = Level.parse(val);
+ }
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ }
+
+ if (this.pushLevel == null) {
+ this.pushLevel = Level.OFF;
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initPushFilter(final String p) {
+ assert Thread.holdsLock(this);
+ try {
+ String name = fromLogManager(p.concat(".pushFilter"));
+ if (hasValue(name)) {
+ this.pushFilter = LogManagerProperties.newFilter(name);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Parses LogManager string values into objects used by this handler.
+ * @param p the handler class name used as the prefix.
+ * @throws NullPointerException if the given argument is null.
+ * @throws SecurityException if not allowed.
+ */
+ private void initSubject(final String p) {
+ assert Thread.holdsLock(this);
+ String name = fromLogManager(p.concat(".subject"));
+ if (name == null) { //Soft dependency on CollectorFormatter.
+ name = "com.sun.mail.util.logging.CollectorFormatter";
+ }
+
+ if (hasValue(name)) {
+ try {
+ this.subjectFormatter = LogManagerProperties.newFormatter(name);
+ } catch (final SecurityException SE) {
+ throw SE; //Avoid catch all.
+ } catch (ClassNotFoundException
+ | ClassCastException literalSubject) {
+ this.subjectFormatter = TailNameFormatter.of(name);
+ } catch (final Exception E) {
+ this.subjectFormatter = TailNameFormatter.of(name);
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else { //User has forced empty or literal null.
+ this.subjectFormatter = TailNameFormatter.of(name);
+ }
+ }
+
+ /**
+ * Check if any attachment would actually format the given
+ * LogRecord
. This method does not check if the handler
+ * is level is set to OFF or if the handler is closed.
+ * @param record a LogRecord
+ * @return true if the LogRecord
would be formatted.
+ */
+ private boolean isAttachmentLoggable(final LogRecord record) {
+ final Filter[] filters = readOnlyAttachmentFilters();
+ for (int i = 0; i < filters.length; ++i) {
+ final Filter f = filters[i];
+ if (f == null || f.isLoggable(record)) {
+ setMatchedPart(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if this Handler
would push after storing the
+ * LogRecord
into its internal buffer.
+ * @param record a LogRecord
+ * @return true if the LogRecord
triggers an email push.
+ * @throws NullPointerException if tryMutex was not called.
+ */
+ private boolean isPushable(final LogRecord record) {
+ assert Thread.holdsLock(this);
+ final int value = getPushLevel().intValue();
+ if (value == offValue || record.getLevel().intValue() < value) {
+ return false;
+ }
+
+ final Filter push = getPushFilter();
+ if (push == null) {
+ return true;
+ }
+
+ final int match = getMatchedPart();
+ if ((match == -1 && getFilter() == push)
+ || (match >= 0 && attachmentFilters[match] == push)) {
+ return true;
+ } else {
+ return push.isLoggable(record);
+ }
+ }
+
+ /**
+ * Used to perform push or flush.
+ * @param priority true for high priority otherwise false for normal.
+ * @param code the error manager code.
+ */
+ private void push(final boolean priority, final int code) {
+ if (tryMutex()) {
+ try {
+ final Message msg = writeLogRecords(code);
+ if (msg != null) {
+ send(msg, priority, code);
+ }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, code);
+ } finally {
+ releaseMutex();
+ }
+ } else {
+ reportUnPublishedError(null);
+ }
+ }
+
+ /**
+ * Used to send the generated email or write its contents to the
+ * error manager for this handler. This method does not hold any
+ * locks so new records can be added to this handler during a send or
+ * failure.
+ * @param msg the message or null.
+ * @param priority true for high priority or false for normal.
+ * @param code the ErrorManager code.
+ * @throws NullPointerException if message is null.
+ */
+ private void send(Message msg, boolean priority, int code) {
+ try {
+ envelopeFor(msg, priority);
+ final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try { //JDK-8025251
+ Transport.send(msg); //Calls save changes.
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+ } catch (final RuntimeException re) {
+ reportError(msg, re, code);
+ } catch (final Exception e) {
+ reportError(msg, e, code);
+ }
+ }
+
+ /**
+ * Performs a sort on the records if needed.
+ * Any exception thrown during a sort is considered a formatting error.
+ */
+ private void sort() {
+ assert Thread.holdsLock(this);
+ if (comparator != null) {
+ try {
+ if (size != 1) {
+ Arrays.sort(data, 0, size, comparator);
+ } else {
+ if (comparator.compare(data[0], data[0]) != 0) {
+ throw new IllegalArgumentException(
+ comparator.getClass().getName());
+ }
+ }
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Formats all records in the buffer and places the output in a Message.
+ * This method under most conditions will catch, report, and continue when
+ * exceptions occur. This method holds a lock on this handler.
+ * @param code the error manager code.
+ * @return null if there are no records or is currently in a push.
+ * Otherwise a new message is created with a formatted message and
+ * attached session.
+ */
+ private Message writeLogRecords(final int code) {
+ try {
+ synchronized (this) {
+ if (size > 0 && !isWriting) {
+ isWriting = true;
+ try {
+ return writeLogRecords0();
+ } finally {
+ isWriting = false;
+ if (size > 0) {
+ reset();
+ }
+ }
+ }
+ }
+ } catch (final RuntimeException re) {
+ reportError(re.getMessage(), re, code);
+ } catch (final Exception e) {
+ reportError(e.getMessage(), e, code);
+ }
+ return null;
+ }
+
+ /**
+ * Formats all records in the buffer and places the output in a Message.
+ * This method under most conditions will catch, report, and continue when
+ * exceptions occur.
+ *
+ * @return null if there are no records or is currently in a push. Otherwise
+ * a new message is created with a formatted message and attached session.
+ * @throws MessagingException if there is a problem.
+ * @throws IOException if there is a problem.
+ * @throws RuntimeException if there is an unexpected problem.
+ * @since JavaMail 1.5.3
+ */
+ private Message writeLogRecords0() throws Exception {
+ assert Thread.holdsLock(this);
+ sort();
+ if (session == null) {
+ initSession();
+ }
+ MimeMessage msg = new MimeMessage(session);
+
+ /**
+ * Parts are lazily created when an attachment performs a getHead
+ * call. Therefore, a null part at an index means that the head is
+ * required.
+ */
+ MimeBodyPart[] parts = new MimeBodyPart[attachmentFormatters.length];
+
+ /**
+ * The buffers are lazily created when the part requires a getHead.
+ */
+ StringBuilder[] buffers = new StringBuilder[parts.length];
+ StringBuilder buf = null;
+ final MimePart body;
+ if (parts.length == 0) {
+ msg.setDescription(descriptionFrom(
+ getFormatter(), getFilter(), subjectFormatter));
+ body = msg;
+ } else {
+ msg.setDescription(descriptionFrom(
+ comparator, pushLevel, pushFilter));
+ body = createBodyPart();
+ }
+
+ appendSubject(msg, head(subjectFormatter));
+ final Formatter bodyFormat = getFormatter();
+ final Filter bodyFilter = getFilter();
+
+ Locale lastLocale = null;
+ for (int ix = 0; ix < size; ++ix) {
+ boolean formatted = false;
+ final int match = matched[ix];
+ final LogRecord r = data[ix];
+ data[ix] = null; //Clear while formatting.
+
+ final Locale locale = localeFor(r);
+ appendSubject(msg, format(subjectFormatter, r));
+ Filter lmf = null; //Identity of last matched filter.
+ if (bodyFilter == null || match == -1 || parts.length == 0
+ || (match < -1 && bodyFilter.isLoggable(r))) {
+ lmf = bodyFilter;
+ if (buf == null) {
+ buf = new StringBuilder();
+ buf.append(head(bodyFormat));
+ }
+ formatted = true;
+ buf.append(format(bodyFormat, r));
+ if (locale != null && !locale.equals(lastLocale)) {
+ appendContentLang(body, locale);
+ }
+ }
+
+ for (int i = 0; i < parts.length; ++i) {
+ //A match index less than the attachment index means that
+ //the filter has not seen this record.
+ final Filter af = attachmentFilters[i];
+ if (af == null || lmf == af || match == i
+ || (match < i && af.isLoggable(r))) {
+ if (lmf == null && af != null) {
+ lmf = af;
+ }
+ if (parts[i] == null) {
+ parts[i] = createBodyPart(i);
+ buffers[i] = new StringBuilder();
+ buffers[i].append(head(attachmentFormatters[i]));
+ appendFileName(parts[i], head(attachmentNames[i]));
+ }
+ formatted = true;
+ appendFileName(parts[i], format(attachmentNames[i], r));
+ buffers[i].append(format(attachmentFormatters[i], r));
+ if (locale != null && !locale.equals(lastLocale)) {
+ appendContentLang(parts[i], locale);
+ }
+ }
+ }
+
+ if (formatted) {
+ if (body != msg && locale != null
+ && !locale.equals(lastLocale)) {
+ appendContentLang(msg, locale);
+ }
+ } else { //Belongs to no mime part.
+ reportFilterError(r);
+ }
+ lastLocale = locale;
+ }
+ this.size = 0;
+
+ for (int i = parts.length - 1; i >= 0; --i) {
+ if (parts[i] != null) {
+ appendFileName(parts[i], tail(attachmentNames[i], "err"));
+ buffers[i].append(tail(attachmentFormatters[i], ""));
+
+ if (buffers[i].length() > 0) {
+ String name = parts[i].getFileName();
+ if (isEmpty(name)) { //Exceptional case.
+ name = toString(attachmentFormatters[i]);
+ parts[i].setFileName(name);
+ }
+ setContent(parts[i], buffers[i], getContentType(name));
+ } else {
+ setIncompleteCopy(msg);
+ parts[i] = null; //Skip this part.
+ }
+ buffers[i] = null;
+ }
+ }
+
+ if (buf != null) {
+ buf.append(tail(bodyFormat, ""));
+ //This body part is always added, even if the buffer is empty,
+ //so the body is never considered an incomplete-copy.
+ } else {
+ buf = new StringBuilder(0);
+ }
+
+ appendSubject(msg, tail(subjectFormatter, ""));
+
+ String contentType = contentTypeOf(buf);
+ String altType = contentTypeOf(bodyFormat);
+ setContent(body, buf, altType == null ? contentType : altType);
+ if (body != msg) {
+ final MimeMultipart multipart = new MimeMultipart();
+ //assert body instanceof BodyPart : body;
+ multipart.addBodyPart((BodyPart) body);
+
+ for (int i = 0; i < parts.length; ++i) {
+ if (parts[i] != null) {
+ multipart.addBodyPart(parts[i]);
+ }
+ }
+ msg.setContent(multipart);
+ }
+
+ return msg;
+ }
+
+ /**
+ * Checks all of the settings if the caller requests a verify and a verify
+ * was not performed yet and no verify is in progress. A verify is
+ * performed on create because this handler may be at the end of a handler
+ * chain and therefore may not see any log records until LogManager.reset()
+ * is called and at that time all of the settings have been cleared.
+ * @param session the current session or null.
+ * @since JavaMail 1.4.4
+ */
+ private void verifySettings(final Session session) {
+ try {
+ if (session != null) {
+ final Properties props = session.getProperties();
+ final Object check = props.put("verify", "");
+ if (check instanceof String) {
+ String value = (String) check;
+ //Perform the verify if needed.
+ if (hasValue(value)) {
+ verifySettings0(session, value);
+ }
+ } else {
+ if (check != null) { //Pass some invalid string.
+ verifySettings0(session, check.getClass().toString());
+ }
+ }
+ }
+ } catch (final LinkageError JDK8152515) {
+ reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Checks all of the settings using the given setting.
+ * This triggers the LogManagerProperties to copy all of the mail
+ * settings without explictly knowing them. Once all of the properties
+ * are copied this handler can handle LogManager.reset clearing all of the
+ * properties. It is expected that this method is, at most, only called
+ * once per session.
+ * @param session the current session.
+ * @param verify the type of verify to perform.
+ * @since JavaMail 1.4.4
+ */
+ private void verifySettings0(Session session, String verify) {
+ assert verify != null : (String) null;
+ if (!"local".equals(verify) && !"remote".equals(verify)
+ && !"limited".equals(verify) && !"resolve".equals(verify)
+ && !"login".equals(verify)) {
+ reportError("Verify must be 'limited', local', "
+ + "'resolve', 'login', or 'remote'.",
+ new IllegalArgumentException(verify),
+ ErrorManager.OPEN_FAILURE);
+ return;
+ }
+
+ final MimeMessage abort = new MimeMessage(session);
+ final String msg;
+ if (!"limited".equals(verify)) {
+ msg = "Local address is "
+ + InternetAddress.getLocalAddress(session) + '.';
+
+ try { //Verify subclass or declared mime charset.
+ Charset.forName(getEncodingName());
+ } catch (final RuntimeException RE) {
+ UnsupportedEncodingException UEE =
+ new UnsupportedEncodingException(RE.toString());
+ UEE.initCause(RE);
+ reportError(msg, UEE, ErrorManager.FORMAT_FAILURE);
+ }
+ } else {
+ msg = "Skipping local address check.";
+ }
+
+ //Perform all of the copy actions first.
+ String[] atn;
+ synchronized (this) { //Create the subject.
+ appendSubject(abort, head(subjectFormatter));
+ appendSubject(abort, tail(subjectFormatter, ""));
+ atn = new String[attachmentNames.length];
+ for (int i = 0; i < atn.length; ++i) {
+ atn[i] = head(attachmentNames[i]);
+ if (atn[i].length() == 0) {
+ atn[i] = tail(attachmentNames[i], "");
+ } else {
+ atn[i] = atn[i].concat(tail(attachmentNames[i], ""));
+ }
+ }
+ }
+
+ setIncompleteCopy(abort); //Original body part is never added.
+ envelopeFor(abort, true);
+ saveChangesNoContent(abort, msg);
+ try {
+ //Ensure transport provider is installed.
+ Address[] all = abort.getAllRecipients();
+ if (all == null) { //Don't pass null to sendMessage.
+ all = new InternetAddress[0];
+ }
+ Transport t;
+ try {
+ final Address[] any = all.length != 0 ? all : abort.getFrom();
+ if (any != null && any.length != 0) {
+ t = session.getTransport(any[0]);
+ session.getProperty("mail.transport.protocol"); //Force copy
+ } else {
+ MessagingException me = new MessagingException(
+ "No recipient or from address.");
+ reportError(msg, me, ErrorManager.OPEN_FAILURE);
+ throw me;
+ }
+ } catch (final MessagingException protocol) {
+ //Switching the CCL emulates the current send behavior.
+ Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try {
+ t = session.getTransport();
+ } catch (final MessagingException fail) {
+ throw attach(protocol, fail);
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+ }
+
+ String local = null;
+ if ("remote".equals(verify) || "login".equals(verify)) {
+ MessagingException closed = null;
+ t.connect();
+ try {
+ try {
+ //Capture localhost while connection is open.
+ local = getLocalHost(t);
+
+ //A message without content will fail at message writeTo
+ //when sendMessage is called. This allows the handler
+ //to capture all mail properties set in the LogManager.
+ if ("remote".equals(verify)) {
+ t.sendMessage(abort, all);
+ }
+ } finally {
+ try {
+ t.close();
+ } catch (final MessagingException ME) {
+ closed = ME;
+ }
+ }
+ //Close the transport before reporting errors.
+ if ("remote".equals(verify)) {
+ reportUnexpectedSend(abort, verify, null);
+ } else {
+ final String protocol = t.getURLName().getProtocol();
+ verifyProperties(session, protocol);
+ }
+ } catch (final SendFailedException sfe) {
+ Address[] recip = sfe.getInvalidAddresses();
+ if (recip != null && recip.length != 0) {
+ setErrorContent(abort, verify, sfe);
+ reportError(abort, sfe, ErrorManager.OPEN_FAILURE);
+ }
+
+ recip = sfe.getValidSentAddresses();
+ if (recip != null && recip.length != 0) {
+ reportUnexpectedSend(abort, verify, sfe);
+ }
+ } catch (final MessagingException ME) {
+ if (!isMissingContent(abort, ME)) {
+ setErrorContent(abort, verify, ME);
+ reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ if (closed != null) {
+ setErrorContent(abort, verify, closed);
+ reportError(abort, closed, ErrorManager.CLOSE_FAILURE);
+ }
+ } else {
+ //Force a property copy, JDK-7092981.
+ final String protocol = t.getURLName().getProtocol();
+ verifyProperties(session, protocol);
+ String mailHost = session.getProperty("mail."
+ + protocol + ".host");
+ if (isEmpty(mailHost)) {
+ mailHost = session.getProperty("mail.host");
+ } else {
+ session.getProperty("mail.host");
+ }
+
+ local = session.getProperty("mail." + protocol + ".localhost");
+ if (isEmpty(local)) {
+ local = session.getProperty("mail."
+ + protocol + ".localaddress");
+ } else {
+ session.getProperty("mail." + protocol + ".localaddress");
+ }
+
+ if ("resolve".equals(verify)) {
+ try { //Resolve the remote host name.
+ String transportHost = t.getURLName().getHost();
+ if (!isEmpty(transportHost)) {
+ verifyHost(transportHost);
+ if (!transportHost.equalsIgnoreCase(mailHost)) {
+ verifyHost(mailHost);
+ }
+ } else {
+ verifyHost(mailHost);
+ }
+ } catch (final RuntimeException | IOException IOE) {
+ MessagingException ME =
+ new MessagingException(msg, IOE);
+ setErrorContent(abort, verify, ME);
+ reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+ }
+ }
+ }
+
+ if (!"limited".equals(verify)) {
+ try { //Verify host name and hit the host name cache.
+ if (!"remote".equals(verify) && !"login".equals(verify)) {
+ local = getLocalHost(t);
+ }
+ verifyHost(local);
+ } catch (final RuntimeException | IOException IOE) {
+ MessagingException ME = new MessagingException(msg, IOE);
+ setErrorContent(abort, verify, ME);
+ reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+ }
+
+ try { //Verify that the DataHandler can be loaded.
+ Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try {
+ //Always load the multipart classes.
+ MimeMultipart multipart = new MimeMultipart();
+ MimeBodyPart[] ambp = new MimeBodyPart[atn.length];
+ final MimeBodyPart body;
+ final String bodyContentType;
+ synchronized (this) {
+ bodyContentType = contentTypeOf(getFormatter());
+ body = createBodyPart();
+ for (int i = 0; i < atn.length; ++i) {
+ ambp[i] = createBodyPart(i);
+ ambp[i].setFileName(atn[i]);
+ //Convert names to mime type under lock.
+ atn[i] = getContentType(atn[i]);
+ }
+ }
+
+ body.setDescription(verify);
+ setContent(body, "", bodyContentType);
+ multipart.addBodyPart(body);
+ for (int i = 0; i < ambp.length; ++i) {
+ ambp[i].setDescription(verify);
+ setContent(ambp[i], "", atn[i]);
+ }
+
+ abort.setContent(multipart);
+ abort.saveChanges();
+ abort.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+ } catch (final IOException IOE) {
+ MessagingException ME = new MessagingException(msg, IOE);
+ setErrorContent(abort, verify, ME);
+ reportError(abort, ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ //Verify all recipients.
+ if (all.length != 0) {
+ verifyAddresses(all);
+ } else {
+ throw new MessagingException("No recipient addresses.");
+ }
+
+ //Verify from and sender addresses.
+ Address[] from = abort.getFrom();
+ Address sender = abort.getSender();
+ if (sender instanceof InternetAddress) {
+ ((InternetAddress) sender).validate();
+ }
+
+ //If from address is declared then check sender.
+ if (abort.getHeader("From", ",") != null && from.length != 0) {
+ verifyAddresses(from);
+ for (int i = 0; i < from.length; ++i) {
+ if (from[i].equals(sender)) {
+ MessagingException ME = new MessagingException(
+ "Sender address '" + sender
+ + "' equals from address.");
+ throw new MessagingException(msg, ME);
+ }
+ }
+ } else {
+ if (sender == null) {
+ MessagingException ME = new MessagingException(
+ "No from or sender address.");
+ throw new MessagingException(msg, ME);
+ }
+ }
+
+ //Verify reply-to addresses.
+ verifyAddresses(abort.getReplyTo());
+ } catch (final RuntimeException RE) {
+ setErrorContent(abort, verify, RE);
+ reportError(abort, RE, ErrorManager.OPEN_FAILURE);
+ } catch (final Exception ME) {
+ setErrorContent(abort, verify, ME);
+ reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Handles all exceptions thrown when save changes is called on a message
+ * that doesn't have any content.
+ *
+ * @param abort the message requiring save changes.
+ * @param msg the error description.
+ * @since JavaMail 1.6.0
+ */
+ private void saveChangesNoContent(final Message abort, final String msg) {
+ if (abort != null) {
+ try {
+ try {
+ abort.saveChanges();
+ } catch (final NullPointerException xferEncoding) {
+ //Workaround GNU JavaMail bug in MimeUtility.getEncoding
+ //when the mime message has no content.
+ try {
+ String cte = "Content-Transfer-Encoding";
+ if (abort.getHeader(cte) == null) {
+ abort.setHeader(cte, "base64");
+ abort.saveChanges();
+ } else {
+ throw xferEncoding;
+ }
+ } catch (RuntimeException | MessagingException e) {
+ if (e != xferEncoding) {
+ e.addSuppressed(xferEncoding);
+ }
+ throw e;
+ }
+ }
+ } catch (RuntimeException | MessagingException ME) {
+ reportError(msg, ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Cache common session properties into the LogManagerProperties. This is
+ * a workaround for JDK-7092981.
+ *
+ * @param session the session.
+ * @param protocol the mail protocol.
+ * @throws NullPointerException if session is null.
+ * @since JavaMail 1.6.0
+ */
+ private static void verifyProperties(Session session, String protocol) {
+ session.getProperty("mail.from");
+ session.getProperty("mail." + protocol + ".from");
+ session.getProperty("mail.dsn.ret");
+ session.getProperty("mail." + protocol + ".dsn.ret");
+ session.getProperty("mail.dsn.notify");
+ session.getProperty("mail." + protocol + ".dsn.notify");
+ session.getProperty("mail." + protocol + ".port");
+ session.getProperty("mail.user");
+ session.getProperty("mail." + protocol + ".user");
+ session.getProperty("mail." + protocol + ".localport");
+ }
+
+ /**
+ * Perform a lookup of the host address or FQDN.
+ * @param host the host or null.
+ * @return the address.
+ * @throws IOException if the host name is not valid.
+ * @throws SecurityException if security manager is present and doesn't
+ * allow access to check connect permission.
+ * @since JavaMail 1.5.0
+ */
+ private static InetAddress verifyHost(String host) throws IOException {
+ InetAddress a;
+ if (isEmpty(host)) {
+ a = InetAddress.getLocalHost();
+ } else {
+ a = InetAddress.getByName(host);
+ }
+ if (a.getCanonicalHostName().length() == 0) {
+ throw new UnknownHostException();
+ }
+ return a;
+ }
+
+ /**
+ * Calls validate for every address given.
+ * If the addresses given are null, empty or not an InternetAddress then
+ * the check is skipped.
+ * @param all any address array, null or empty.
+ * @throws AddressException if there is a problem.
+ * @since JavaMail 1.4.5
+ */
+ private static void verifyAddresses(Address[] all) throws AddressException {
+ if (all != null) {
+ for (int i = 0; i < all.length; ++i) {
+ final Address a = all[i];
+ if (a instanceof InternetAddress) {
+ ((InternetAddress) a).validate();
+ }
+ }
+ }
+ }
+
+ /**
+ * Reports that an empty content message was sent and should not have been.
+ * @param msg the MimeMessage.
+ * @param verify the verify enum.
+ * @param cause the exception that caused the problem or null.
+ * @since JavaMail 1.4.5
+ */
+ private void reportUnexpectedSend(MimeMessage msg, String verify, Exception cause) {
+ final MessagingException write = new MessagingException(
+ "An empty message was sent.", cause);
+ setErrorContent(msg, verify, write);
+ reportError(msg, write, ErrorManager.OPEN_FAILURE);
+ }
+
+ /**
+ * Creates and sets the message content from the given Throwable.
+ * When verify fails, this method fixes the 'abort' message so that any
+ * created envelope data can be used in the error manager.
+ * @param msg the message with or without content.
+ * @param verify the verify enum.
+ * @param t the throwable or null.
+ * @since JavaMail 1.4.5
+ */
+ private void setErrorContent(MimeMessage msg, String verify, Throwable t) {
+ try { //Add content so toRawString doesn't fail.
+ final MimeBodyPart body;
+ final String subjectType;
+ final String msgDesc;
+ synchronized (this) {
+ body = createBodyPart();
+ msgDesc = descriptionFrom(comparator, pushLevel, pushFilter);
+ subjectType = getClassId(subjectFormatter);
+ }
+
+ body.setDescription("Formatted using "
+ + (t == null ? Throwable.class.getName()
+ : t.getClass().getName()) + ", filtered with "
+ + verify + ", and named by "
+ + subjectType + '.');
+ setContent(body, toMsgString(t), "text/plain");
+ final MimeMultipart multipart = new MimeMultipart();
+ multipart.addBodyPart(body);
+ msg.setContent(multipart);
+ msg.setDescription(msgDesc);
+ setAcceptLang(msg);
+ msg.saveChanges();
+ } catch (MessagingException | RuntimeException ME) {
+ reportError("Unable to create body.", ME, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ /**
+ * Used to update the cached session object based on changes in
+ * mail properties or authenticator.
+ * @return the current session or null if no verify is required.
+ */
+ private Session updateSession() {
+ assert Thread.holdsLock(this);
+ final Session settings;
+ if (mailProps.getProperty("verify") != null) {
+ settings = initSession();
+ assert settings == session : session;
+ } else {
+ session = null; //Remove old session.
+ settings = null;
+ }
+ return settings;
+ }
+
+ /**
+ * Creates a session using a proxy properties object.
+ * @return the session that was created and assigned.
+ */
+ private Session initSession() {
+ assert Thread.holdsLock(this);
+ final String p = getClass().getName();
+ LogManagerProperties proxy = new LogManagerProperties(mailProps, p);
+ session = Session.getInstance(proxy, auth);
+ return session;
+ }
+
+ /**
+ * Creates all of the envelope information for a message.
+ * This method is safe to call outside of a lock because the message
+ * provides the safe snapshot of the mail properties.
+ * @param msg the Message to write the envelope information.
+ * @param priority true for high priority.
+ */
+ private void envelopeFor(Message msg, boolean priority) {
+ setAcceptLang(msg);
+ setFrom(msg);
+ if (!setRecipient(msg, "mail.to", Message.RecipientType.TO)) {
+ setDefaultRecipient(msg, Message.RecipientType.TO);
+ }
+ setRecipient(msg, "mail.cc", Message.RecipientType.CC);
+ setRecipient(msg, "mail.bcc", Message.RecipientType.BCC);
+ setReplyTo(msg);
+ setSender(msg);
+ setMailer(msg);
+ setAutoSubmitted(msg);
+ if (priority) {
+ setPriority(msg);
+ }
+
+ try {
+ msg.setSentDate(new java.util.Date());
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Factory to create the in-line body part.
+ * @return a body part with default headers set.
+ * @throws MessagingException if there is a problem.
+ */
+ private MimeBodyPart createBodyPart() throws MessagingException {
+ assert Thread.holdsLock(this);
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setDisposition(Part.INLINE);
+ part.setDescription(descriptionFrom(getFormatter(),
+ getFilter(), subjectFormatter));
+ setAcceptLang(part);
+ return part;
+ }
+
+ /**
+ * Factory to create the attachment body part.
+ * @param index the attachment index.
+ * @return a body part with default headers set.
+ * @throws MessagingException if there is a problem.
+ * @throws IndexOutOfBoundsException if the given index is not an valid
+ * attachment index.
+ */
+ private MimeBodyPart createBodyPart(int index) throws MessagingException {
+ assert Thread.holdsLock(this);
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setDisposition(Part.ATTACHMENT);
+ part.setDescription(descriptionFrom(
+ attachmentFormatters[index],
+ attachmentFilters[index],
+ attachmentNames[index]));
+ setAcceptLang(part);
+ return part;
+ }
+
+ /**
+ * Gets the description for the MimeMessage itself.
+ * The push level and filter are included because they play a role in
+ * formatting of a message when triggered or not triggered.
+ * @param c the comparator.
+ * @param l the pushLevel.
+ * @param f the pushFilter
+ * @return the description.
+ * @throws NullPointerException if level is null.
+ * @since JavaMail 1.4.5
+ */
+ private String descriptionFrom(Comparator> c, Level l, Filter f) {
+ return "Sorted using "+ (c == null ? "no comparator"
+ : c.getClass().getName()) + ", pushed when "+ l.getName()
+ + ", and " + (f == null ? "no push filter"
+ : f.getClass().getName()) + '.';
+ }
+
+ /**
+ * Creates a description for a body part.
+ * @param f the content formatter.
+ * @param filter the content filter.
+ * @param name the naming formatter.
+ * @return the description for the body part.
+ */
+ private String descriptionFrom(Formatter f, Filter filter, Formatter name) {
+ return "Formatted using " + getClassId(f)
+ + ", filtered with " + (filter == null ? "no filter"
+ : filter.getClass().getName()) +", and named by "
+ + getClassId(name) + '.';
+ }
+
+ /**
+ * Gets a class name represents the behavior of the formatter.
+ * The class name may not be assignable to a Formatter.
+ * @param f the formatter.
+ * @return a class name that represents the given formatter.
+ * @throws NullPointerException if the parameter is null.
+ * @since JavaMail 1.4.5
+ */
+ private String getClassId(final Formatter f) {
+ if (f instanceof TailNameFormatter) {
+ return String.class.getName(); //Literal string.
+ } else {
+ return f.getClass().getName();
+ }
+ }
+
+ /**
+ * Ensure that a formatter creates a valid string for a part name.
+ * @param f the formatter.
+ * @return the to string value or the class name.
+ */
+ private String toString(final Formatter f) {
+ //Should never be null but, guard against formatter bugs.
+ final String name = f.toString();
+ if (!isEmpty(name)) {
+ return name;
+ } else {
+ return getClassId(f);
+ }
+ }
+
+ /**
+ * Constructs a file name from a formatter. This method is called often
+ * but, rarely does any work.
+ * @param part to append to.
+ * @param chunk non null string to append.
+ */
+ private void appendFileName(final Part part, final String chunk) {
+ if (chunk != null) {
+ if (chunk.length() > 0) {
+ appendFileName0(part, chunk);
+ }
+ } else {
+ reportNullError(ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * It is assumed that file names are short and that in most cases
+ * getTail will be the only method that will produce a result.
+ * @param part to append to.
+ * @param chunk non null string to append.
+ */
+ private void appendFileName0(final Part part, String chunk) {
+ try {
+ //Remove all control character groups.
+ chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
+ final String old = part.getFileName();
+ part.setFileName(old != null ? old.concat(chunk) : chunk);
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Constructs a subject line from a formatter.
+ * @param msg to append to.
+ * @param chunk non null string to append.
+ */
+ private void appendSubject(final Message msg, final String chunk) {
+ if (chunk != null) {
+ if (chunk.length() > 0) {
+ appendSubject0(msg, chunk);
+ }
+ } else {
+ reportNullError(ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * It is assumed that subject lines are short and that in most cases
+ * getTail will be the only method that will produce a result.
+ * @param msg to append to.
+ * @param chunk non null string to append.
+ */
+ private void appendSubject0(final Message msg, String chunk) {
+ try {
+ //Remove all control character groups.
+ chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
+ final String charset = getEncodingName();
+ final String old = msg.getSubject();
+ assert msg instanceof MimeMessage : msg;
+ ((MimeMessage) msg).setSubject(old != null ? old.concat(chunk)
+ : chunk, MimeUtility.mimeCharset(charset));
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Gets the locale for the given log record from the resource bundle.
+ * If the resource bundle is using the root locale then the default locale
+ * is returned.
+ * @param r the log record.
+ * @return null if not localized otherwise, the locale of the record.
+ * @since JavaMail 1.4.5
+ */
+ private Locale localeFor(final LogRecord r) {
+ Locale l;
+ final ResourceBundle rb = r.getResourceBundle();
+ if (rb != null) {
+ l = rb.getLocale();
+ if (l == null || isEmpty(l.getLanguage())) {
+ //The language of the fallback bundle (root) is unknown.
+ //1. Use default locale. Should only be wrong if the app is
+ // used with a langauge that was unintended. (unlikely)
+ //2. Mark it as not localized (force null, info loss).
+ //3. Use the bundle name (encoded) as an experimental language.
+ l = Locale.getDefault();
+ }
+ } else {
+ l = null;
+ }
+ return l;
+ }
+
+ /**
+ * Appends the content language to the given mime part.
+ * The language tag is only appended if the given language has not been
+ * specified. This method is only used when we have LogRecords that are
+ * localized with an assigned resource bundle.
+ * @param p the mime part.
+ * @param l the locale to append.
+ * @throws NullPointerException if any argument is null.
+ * @since JavaMail 1.4.5
+ */
+ private void appendContentLang(final MimePart p, final Locale l) {
+ try {
+ String lang = LogManagerProperties.toLanguageTag(l);
+ if (lang.length() != 0) {
+ String header = p.getHeader("Content-Language", null);
+ if (isEmpty(header)) {
+ p.setHeader("Content-Language", lang);
+ } else if (!header.equalsIgnoreCase(lang)) {
+ lang = ",".concat(lang);
+ int idx = 0;
+ while ((idx = header.indexOf(lang, idx)) > -1) {
+ idx += lang.length();
+ if (idx == header.length()
+ || header.charAt(idx) == ',') {
+ break;
+ }
+ }
+
+ if (idx < 0) {
+ int len = header.lastIndexOf("\r\n\t");
+ if (len < 0) { //If not folded.
+ len = (18 + 2) + header.length();
+ } else {
+ len = (header.length() - len) + 8;
+ }
+
+ //Perform folding of header if needed.
+ if ((len + lang.length()) > 76) {
+ header = header.concat("\r\n\t".concat(lang));
+ } else {
+ header = header.concat(lang);
+ }
+ p.setHeader("Content-Language", header);
+ }
+ }
+ }
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Sets the accept language to the default locale of the JVM.
+ * If the locale is the root locale the header is not added.
+ * @param p the part to set.
+ * @since JavaMail 1.4.5
+ */
+ private void setAcceptLang(final Part p) {
+ try {
+ final String lang = LogManagerProperties
+ .toLanguageTag(Locale.getDefault());
+ if (lang.length() != 0) {
+ p.setHeader("Accept-Language", lang);
+ }
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Used when a log record was loggable prior to being inserted
+ * into the buffer but at the time of formatting was no longer loggable.
+ * Filters were changed after publish but prior to a push or a bug in the
+ * body filter or one of the attachment filters.
+ * @param record that was not formatted.
+ * @since JavaMail 1.4.5
+ */
+ private void reportFilterError(final LogRecord record) {
+ assert Thread.holdsLock(this);
+ final Formatter f = createSimpleFormatter();
+ final String msg = "Log record " + record.getSequenceNumber()
+ + " was filtered from all message parts. "
+ + head(f) + format(f, record) + tail(f, "");
+ final String txt = getFilter() + ", "
+ + Arrays.asList(readOnlyAttachmentFilters());
+ reportError(msg, new IllegalArgumentException(txt),
+ ErrorManager.FORMAT_FAILURE);
+ }
+
+ /**
+ * Reports symmetric contract violations an equals implementation.
+ * @param o the test object must be non null.
+ * @param found the possible intern, must be non null.
+ * @throws NullPointerException if any argument is null.
+ * @since JavaMail 1.5.0
+ */
+ private void reportNonSymmetric(final Object o, final Object found) {
+ reportError("Non symmetric equals implementation."
+ , new IllegalArgumentException(o.getClass().getName()
+ + " is not equal to " + found.getClass().getName())
+ , ErrorManager.OPEN_FAILURE);
+ }
+
+ /**
+ * Reports equals implementations that do not discriminate between objects
+ * of different types or subclass types.
+ * @param o the test object must be non null.
+ * @param found the possible intern, must be non null.
+ * @throws NullPointerException if any argument is null.
+ * @since JavaMail 1.5.0
+ */
+ private void reportNonDiscriminating(final Object o, final Object found) {
+ reportError("Non discriminating equals implementation."
+ , new IllegalArgumentException(o.getClass().getName()
+ + " should not be equal to " + found.getClass().getName())
+ , ErrorManager.OPEN_FAILURE);
+ }
+
+ /**
+ * Used to outline the bytes to report a null pointer exception.
+ * See BUD ID 6533165.
+ * @param code the ErrorManager code.
+ */
+ private void reportNullError(final int code) {
+ reportError("null", new NullPointerException(), code);
+ }
+
+ /**
+ * Creates the head or reports a formatting error.
+ * @param f the formatter.
+ * @return the head string or an empty string.
+ */
+ private String head(final Formatter f) {
+ try {
+ return f.getHead(this);
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+ return "";
+ }
+ }
+
+ /**
+ * Creates the formatted log record or reports a formatting error.
+ * @param f the formatter.
+ * @param r the log record.
+ * @return the formatted string or an empty string.
+ */
+ private String format(final Formatter f, final LogRecord r) {
+ try {
+ return f.format(r);
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+ return "";
+ }
+ }
+
+ /**
+ * Creates the tail or reports a formatting error.
+ * @param f the formatter.
+ * @param def the default string to use when there is an error.
+ * @return the tail string or the given default string.
+ */
+ private String tail(final Formatter f, final String def) {
+ try {
+ return f.getTail(this);
+ } catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+ return def;
+ }
+ }
+
+ /**
+ * Sets the x-mailer header.
+ * @param msg the target message.
+ */
+ private void setMailer(final Message msg) {
+ try {
+ final Class> mail = MailHandler.class;
+ final Class> k = getClass();
+ String value;
+ if (k == mail) {
+ value = mail.getName();
+ } else {
+ try {
+ value = MimeUtility.encodeText(k.getName());
+ } catch (final UnsupportedEncodingException E) {
+ reportError(E.getMessage(), E, ErrorManager.FORMAT_FAILURE);
+ value = k.getName().replaceAll("[^\\x00-\\x7F]", "\uu001A");
+ }
+ value = MimeUtility.fold(10, mail.getName() + " using the "
+ + value + " extension.");
+ }
+ msg.setHeader("X-Mailer", value);
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Sets the priority and importance headers.
+ * @param msg the target message.
+ */
+ private void setPriority(final Message msg) {
+ try {
+ msg.setHeader("Importance", "High");
+ msg.setHeader("Priority", "urgent");
+ msg.setHeader("X-Priority", "2"); //High
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Used to signal that body parts are missing from a message. Also used
+ * when LogRecords were passed to an attachment formatter but the formatter
+ * produced no output, which is allowed. Used during a verify because all
+ * parts are omitted, none of the content formatters are used. This is
+ * not used when a filter prevents LogRecords from being formatted.
+ * This header is defined in RFC 2156 and RFC 4021.
+ * @param msg the message.
+ * @since JavaMail 1.4.5
+ */
+ private void setIncompleteCopy(final Message msg) {
+ try {
+ msg.setHeader("Incomplete-Copy", "");
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Signals that this message was generated by automatic process.
+ * This header is defined in RFC 3834 section 5.
+ * @param msg the message.
+ * @since JavaMail 1.4.6
+ */
+ private void setAutoSubmitted(final Message msg) {
+ if (allowRestrictedHeaders()) {
+ try { //RFC 3834 (5.2)
+ msg.setHeader("auto-submitted", "auto-generated");
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Sets from address header.
+ * @param msg the target message.
+ */
+ private void setFrom(final Message msg) {
+ final String from = getSession(msg).getProperty("mail.from");
+ if (from != null) {
+ try {
+ final Address[] address = InternetAddress.parse(from, false);
+ if (address.length > 0) {
+ if (address.length == 1) {
+ msg.setFrom(address[0]);
+ } else { //Greater than 1 address.
+ msg.addFrom(address);
+ }
+ }
+ //Can't place an else statement here because the 'from' is
+ //not null which causes the local address computation
+ //to fail. Assume the user wants to omit the from address
+ //header.
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ setDefaultFrom(msg);
+ }
+ } else {
+ setDefaultFrom(msg);
+ }
+ }
+
+ /**
+ * Sets the from header to the local address.
+ * @param msg the target message.
+ */
+ private void setDefaultFrom(final Message msg) {
+ try {
+ msg.setFrom();
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Computes the default to-address if none was specified. This can
+ * fail if the local address can't be computed.
+ * @param msg the message
+ * @param type the recipient type.
+ * @since JavaMail 1.5.0
+ */
+ private void setDefaultRecipient(final Message msg,
+ final Message.RecipientType type) {
+ try {
+ Address a = InternetAddress.getLocalAddress(getSession(msg));
+ if (a != null) {
+ msg.setRecipient(type, a);
+ } else {
+ final MimeMessage m = new MimeMessage(getSession(msg));
+ m.setFrom(); //Should throw an exception with a cause.
+ Address[] from = m.getFrom();
+ if (from.length > 0) {
+ msg.setRecipients(type, from);
+ } else {
+ throw new MessagingException("No local address.");
+ }
+ }
+ } catch (MessagingException | RuntimeException ME) {
+ reportError("Unable to compute a default recipient.",
+ ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+
+ /**
+ * Sets reply-to address header.
+ * @param msg the target message.
+ */
+ private void setReplyTo(final Message msg) {
+ final String reply = getSession(msg).getProperty("mail.reply.to");
+ if (!isEmpty(reply)) {
+ try {
+ final Address[] address = InternetAddress.parse(reply, false);
+ if (address.length > 0) {
+ msg.setReplyTo(address);
+ }
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Sets sender address header.
+ * @param msg the target message.
+ */
+ private void setSender(final Message msg) {
+ assert msg instanceof MimeMessage : msg;
+ final String sender = getSession(msg).getProperty("mail.sender");
+ if (!isEmpty(sender)) {
+ try {
+ final InternetAddress[] address =
+ InternetAddress.parse(sender, false);
+ if (address.length > 0) {
+ ((MimeMessage) msg).setSender(address[0]);
+ if (address.length > 1) {
+ reportError("Ignoring other senders.",
+ tooManyAddresses(address, 1),
+ ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * A common factory used to create the too many addresses exception.
+ * @param address the addresses, never null.
+ * @param offset the starting address to display.
+ * @return the too many addresses exception.
+ */
+ private AddressException tooManyAddresses(Address[] address, int offset) {
+ Object l = Arrays.asList(address).subList(offset, address.length);
+ return new AddressException(l.toString());
+ }
+
+ /**
+ * Sets the recipient for the given message.
+ * @param msg the message.
+ * @param key the key to search in the session.
+ * @param type the recipient type.
+ * @return true if the key was contained in the session.
+ */
+ private boolean setRecipient(final Message msg,
+ final String key, final Message.RecipientType type) {
+ boolean containsKey;
+ final String value = getSession(msg).getProperty(key);
+ containsKey = value != null;
+ if (!isEmpty(value)) {
+ try {
+ final Address[] address = InternetAddress.parse(value, false);
+ if (address.length > 0) {
+ msg.setRecipients(type, address);
+ }
+ } catch (final MessagingException ME) {
+ reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+ }
+ }
+ return containsKey;
+ }
+
+ /**
+ * Converts an email message to a raw string. This raw string
+ * is passed to the error manager to allow custom error managers
+ * to recreate the original MimeMessage object.
+ * @param msg a Message object.
+ * @return the raw string or null if msg was null.
+ * @throws MessagingException if there was a problem with the message.
+ * @throws IOException if there was a problem.
+ */
+ private String toRawString(final Message msg) throws MessagingException, IOException {
+ if (msg != null) {
+ Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+ try { //JDK-8025251
+ int nbytes = Math.max(msg.getSize() + MIN_HEADER_SIZE, MIN_HEADER_SIZE);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(nbytes);
+ msg.writeTo(out); //Headers can be UTF-8 or US-ASCII.
+ return out.toString("UTF-8");
+ } finally {
+ getAndSetContextClassLoader(ccl);
+ }
+ } else { //Must match this.reportError behavior, see push method.
+ return null; //Null is the safe choice.
+ }
+ }
+
+ /**
+ * Converts a throwable to a message string.
+ * @param t any throwable or null.
+ * @return the throwable with a stack trace or the literal null.
+ */
+ private String toMsgString(final Throwable t) {
+ if (t == null) {
+ return "null";
+ }
+
+ final String charset = getEncodingName();
+ try {
+ final ByteArrayOutputStream out =
+ new ByteArrayOutputStream(MIN_HEADER_SIZE);
+
+ //Create an output stream writer so streams are not double buffered.
+ try (OutputStreamWriter ows = new OutputStreamWriter(out, charset);
+ PrintWriter pw = new PrintWriter(ows)) {
+ pw.println(t.getMessage());
+ t.printStackTrace(pw);
+ pw.flush();
+ } //Close OSW before generating string. JDK-6995537
+ return out.toString(charset);
+ } catch (final RuntimeException unexpected) {
+ return t.toString() + ' ' + unexpected.toString();
+ } catch (final Exception badMimeCharset) {
+ return t.toString() + ' ' + badMimeCharset.toString();
+ }
+ }
+
+ /**
+ * Replaces the current context class loader with our class loader.
+ * @param ccl null for boot class loader, a class loader, a class used to
+ * get the class loader, or a source object to get the class loader.
+ * @return null for the boot class loader, a class loader, or a marker
+ * object to signal that no modification was required.
+ * @since JavaMail 1.5.3
+ */
+ private Object getAndSetContextClassLoader(final Object ccl) {
+ if (ccl != GetAndSetContext.NOT_MODIFIED) {
+ try {
+ final PrivilegedAction> pa;
+ if (ccl instanceof PrivilegedAction) {
+ pa = (PrivilegedAction>) ccl;
+ } else {
+ pa = new GetAndSetContext(ccl);
+ }
+ return AccessController.doPrivileged(pa);
+ } catch (final SecurityException ignore) {
+ }
+ }
+ return GetAndSetContext.NOT_MODIFIED;
+ }
+
+ /**
+ * A factory used to create a common attachment mismatch type.
+ * @param msg the exception message.
+ * @return a RuntimeException to represent the type of error.
+ */
+ private static RuntimeException attachmentMismatch(final String msg) {
+ return new IndexOutOfBoundsException(msg);
+ }
+
+ /**
+ * Outline the attachment mismatch message. See Bug ID 6533165.
+ * @param expected the expected array length.
+ * @param found the array length that was given.
+ * @return a RuntimeException populated with a message.
+ */
+ private static RuntimeException attachmentMismatch(int expected, int found) {
+ return attachmentMismatch("Attachments mismatched, expected "
+ + expected + " but given " + found + '.');
+ }
+
+ /**
+ * Try to attach a suppressed exception to a MessagingException in any order
+ * that is possible.
+ * @param required the exception expected to see as a reported failure.
+ * @param optional the suppressed exception.
+ * @return either the required or the optional exception.
+ */
+ private static MessagingException attach(
+ MessagingException required, Exception optional) {
+ if (optional != null && !required.setNextException(optional)) {
+ if (optional instanceof MessagingException) {
+ final MessagingException head = (MessagingException) optional;
+ if (head.setNextException(required)) {
+ return head;
+ }
+ }
+
+ if (optional != required) {
+ required.addSuppressed(optional);
+ }
+ }
+ return required;
+ }
+
+ /**
+ * Gets the local host from the given service object.
+ * @param s the service to check.
+ * @return the local host or null.
+ * @since JavaMail 1.5.3
+ */
+ private String getLocalHost(final Service s) {
+ try {
+ return LogManagerProperties.getLocalHost(s);
+ } catch (SecurityException | NoSuchMethodException
+ | LinkageError ignore) {
+ } catch (final Exception ex) {
+ reportError(s.toString(), ex, ErrorManager.OPEN_FAILURE);
+ }
+ return null;
+ }
+
+ /**
+ * Google App Engine doesn't support Message.getSession.
+ * @param msg the message.
+ * @return the session from the given message.
+ * @throws NullPointerException if the given message is null.
+ * @since JavaMail 1.5.3
+ */
+ private Session getSession(final Message msg) {
+ if (msg == null) {
+ throw new NullPointerException();
+ }
+ return new MessageContext(msg).getSession();
+ }
+
+ /**
+ * Determines if restricted headers are allowed in the current environment.
+ *
+ * @return true if restricted headers are allowed.
+ * @since JavaMail 1.5.3
+ */
+ private boolean allowRestrictedHeaders() {
+ //GAE will prevent delivery of email with forbidden headers.
+ //Assume the environment is GAE if access to the LogManager is
+ //forbidden.
+ return LogManagerProperties.hasLogManager();
+ }
+
+ /**
+ * Outline the creation of the index error message. See JDK-6533165.
+ * @param i the index.
+ * @return the error message.
+ */
+ private static String atIndexMsg(final int i) {
+ return "At index: " + i + '.';
+ }
+
+ /**
+ * Used for storing a password from the LogManager or literal string.
+ * @since JavaMail 1.4.6
+ */
+ private static final class DefaultAuthenticator extends Authenticator {
+
+ /**
+ * Creates an Authenticator for the given password. This method is used
+ * so class verification of assignments in MailHandler doesn't require
+ * loading this class which otherwise can occur when using the
+ * constructor. Default access to avoid generating extra class files.
+ *
+ * @param pass the password.
+ * @return an Authenticator for the password.
+ * @since JavaMail 1.5.6
+ */
+ static Authenticator of(final String pass) {
+ return new DefaultAuthenticator(pass);
+ }
+
+ /**
+ * The password to use.
+ */
+ private final String pass;
+
+ /**
+ * Use the factory method instead of this constructor.
+ * @param pass the password.
+ */
+ private DefaultAuthenticator(final String pass) {
+ assert pass != null;
+ this.pass = pass;
+ }
+
+ @Override
+ protected final PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(getDefaultUserName(), pass);
+ }
+ }
+
+ /**
+ * Performs a get and set of the context class loader with privileges
+ * enabled.
+ * @since JavaMail 1.4.6
+ */
+ private static final class GetAndSetContext implements PrivilegedAction {
+ /**
+ * A marker object used to signal that the class loader was not
+ * modified.
+ */
+ public static final Object NOT_MODIFIED = GetAndSetContext.class;
+ /**
+ * The source containing the class loader.
+ */
+ private final Object source;
+ /**
+ * Create the action.
+ * @param source null for boot class loader, a class loader, a class
+ * used to get the class loader, or a source object to get the class
+ * loader. Default access to avoid generating extra class files.
+ */
+ GetAndSetContext(final Object source) {
+ this.source = source;
+ }
+
+ /**
+ * Gets the class loader from the source and sets the CCL only if
+ * the source and CCL are not the same.
+ * @return the replaced context class loader which can be null or
+ * NOT_MODIFIED to indicate that nothing was modified.
+ */
+ @SuppressWarnings("override") //JDK-6954234
+ public final Object run() {
+ final Thread current = Thread.currentThread();
+ final ClassLoader ccl = current.getContextClassLoader();
+ final ClassLoader loader;
+ if (source == null) {
+ loader = null; //boot class loader
+ } else if (source instanceof ClassLoader) {
+ loader = (ClassLoader) source;
+ } else if (source instanceof Class) {
+ loader = ((Class>) source).getClassLoader();
+ } else if (source instanceof Thread) {
+ loader = ((Thread) source).getContextClassLoader();
+ } else {
+ assert !(source instanceof Class) : source;
+ loader = source.getClass().getClassLoader();
+ }
+
+ if (ccl != loader) {
+ current.setContextClassLoader(loader);
+ return ccl;
+ } else {
+ return NOT_MODIFIED;
+ }
+ }
+ }
+
+ /**
+ * Used for naming attachment file names and the main subject line.
+ */
+ private static final class TailNameFormatter extends Formatter {
+
+ /**
+ * Creates or gets a formatter from the given name. This method is used
+ * so class verification of assignments in MailHandler doesn't require
+ * loading this class which otherwise can occur when using the
+ * constructor. Default access to avoid generating extra class files.
+ *
+ * @param name any not null string.
+ * @return a formatter for that string.
+ * @since JavaMail 1.5.6
+ */
+ static Formatter of(final String name) {
+ return new TailNameFormatter(name);
+ }
+
+ /**
+ * The value used as the output.
+ */
+ private final String name;
+
+ /**
+ * Use the factory method instead of this constructor.
+ * @param name any not null string.
+ */
+ private TailNameFormatter(final String name) {
+ assert name != null;
+ this.name = name;
+ }
+
+ @Override
+ public final String format(LogRecord record) {
+ return "";
+ }
+
+ @Override
+ public final String getTail(Handler h) {
+ return name;
+ }
+
+ /**
+ * Equals method.
+ * @param o the other object.
+ * @return true if equal
+ * @since JavaMail 1.4.4
+ */
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof TailNameFormatter) {
+ return name.equals(((TailNameFormatter) o).name);
+ }
+ return false;
+ }
+
+ /**
+ * Hash code method.
+ * @return the hash code.
+ * @since JavaMail 1.4.4
+ */
+ @Override
+ public final int hashCode() {
+ return getClass().hashCode() + name.hashCode();
+ }
+
+ @Override
+ public final String toString() {
+ return name;
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/SeverityComparator.java b/app/src/main/java/com/sun/mail/util/logging/SeverityComparator.java
new file mode 100644
index 0000000000..338a780bff
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/SeverityComparator.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * Orders log records by level, thrown, sequence, and time.
+ *
+ * This comparator orders LogRecords by how severely each is attributed to
+ * failures in a program. The primary ordering is determined by the use of the
+ * logging API throughout a program by specifying a level to each log message.
+ * The secondary ordering is determined at runtime by the type of errors and
+ * exceptions generated by the program. The remaining ordering assumes that
+ * older log records are less severe than newer log records.
+ *
+ *
+ * The following LogRecord properties determine severity ordering:
+ *
+ * The natural comparison of the LogRecord
+ * {@linkplain Level#intValue level}.
+ * The expected recovery order of {@linkplain LogRecord#getThrown() thrown}
+ * property of a LogRecord and its cause chain. This ordering is derived from
+ * the JLS 11.1.1. The Kinds of Exceptions and JLS 11.5 The Exception Hierarchy.
+ * This is performed by {@linkplain #apply(java.lang.Throwable) finding} the
+ * throwable that best describes the entire cause chain. Once a specific
+ * throwable of each chain is identified it is then ranked lowest to highest by
+ * the following rules:
+ *
+ *
+ * All LogRecords with a {@code Throwable} defined as
+ * "{@link #isNormal(java.lang.Throwable) normal occurrence}".
+ * All LogRecords that do not have a thrown object.
+ * All checked exceptions. This is any class that is assignable to the
+ * {@code java.lang.Throwable} class and is not a
+ * {@code java.lang.RuntimeException} or a {@code java.lang.Error}.
+ * All unchecked exceptions. This is all {@code java.lang.RuntimeException}
+ * objects.
+ * All errors that indicate a serious problem. This is all
+ * {@code java.lang.Error} objects.
+ *
+ * The natural comparison of the LogRecord
+ * {@linkplain LogRecord#getSequenceNumber() sequence}.
+ * The natural comparison of the LogRecord
+ * {@linkplain LogRecord#getMillis() millis}.
+ *
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.2
+ */
+public class SeverityComparator implements Comparator, Serializable {
+
+ /**
+ * The generated serial version UID.
+ */
+ private static final long serialVersionUID = -2620442245251791965L;
+
+ /**
+ * A single instance that is shared among the logging package.
+ * The field is declared as java.util.Comparator so
+ * WebappClassLoader.clearReferencesStaticFinal() method will ignore this
+ * field.
+ */
+ private static final Comparator INSTANCE
+ = new SeverityComparator();
+
+ /**
+ * A shared instance of a SeverityComparator. This is package private so the
+ * public API is not polluted with more methods.
+ *
+ * @return a shared instance of a SeverityComparator.
+ */
+ static SeverityComparator getInstance() {
+ return (SeverityComparator) INSTANCE;
+ }
+
+ /**
+ * Identifies a single throwable that best describes the given throwable and
+ * the entire {@linkplain Throwable#getCause() cause} chain. This method can
+ * be overridden to change the behavior of
+ * {@link #compare(java.util.logging.LogRecord, java.util.logging.LogRecord)}.
+ *
+ * @param chain the throwable or null.
+ * @return null if null was given, otherwise the throwable that best
+ * describes the entire chain.
+ * @see #isNormal(java.lang.Throwable)
+ */
+ public Throwable apply(final Throwable chain) {
+ //Matches the j.u.f.UnaryOperator interface.
+ int limit = 0;
+ Throwable root = chain;
+ Throwable high = null;
+ Throwable normal = null;
+ for (Throwable cause = chain; cause != null; cause = cause.getCause()) {
+ root = cause; //Find the deepest cause.
+
+ //Find the deepest nomral occurrance.
+ if (isNormal(cause)) {
+ normal = cause;
+ }
+
+ //Find the deepest error that happened before a normal occurance.
+ if (normal == null && cause instanceof Error) {
+ high = cause;
+ }
+
+ //Deal with excessive cause chains and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+ return high != null ? high : normal != null ? normal : root;
+ }
+
+ /**
+ * {@link #apply(java.lang.Throwable) Reduces} each throwable chain argument
+ * then compare each throwable result.
+ *
+ * @param tc1 the first throwable chain or null.
+ * @param tc2 the second throwable chain or null.
+ * @return a negative integer, zero, or a positive integer as the first
+ * argument is less than, equal to, or greater than the second.
+ * @see #apply(java.lang.Throwable)
+ * @see #compareThrowable(java.lang.Throwable, java.lang.Throwable)
+ */
+ public final int applyThenCompare(Throwable tc1, Throwable tc2) {
+ return tc1 == tc2 ? 0 : compareThrowable(apply(tc1), apply(tc2));
+ }
+
+ /**
+ * Compares two throwable objects or null. This method does not
+ * {@link #apply(java.lang.Throwable) reduce} each argument before
+ * comparing. This is method can be overridden to change the behavior of
+ * {@linkplain #compare(LogRecord, LogRecord)}.
+ *
+ * @param t1 the first throwable or null.
+ * @param t2 the second throwable or null.
+ * @return a negative integer, zero, or a positive integer as the first
+ * argument is less than, equal to, or greater than the second.
+ * @see #isNormal(java.lang.Throwable)
+ */
+ public int compareThrowable(final Throwable t1, final Throwable t2) {
+ if (t1 == t2) { //Reflexive test including null.
+ return 0;
+ } else {
+ //Only one or the other is null at this point.
+ //Force normal occurrence to be lower than null.
+ if (t1 == null) {
+ return isNormal(t2) ? 1 : -1;
+ } else {
+ if (t2 == null) {
+ return isNormal(t1) ? -1 : 1;
+ }
+ }
+
+ //From this point on neither are null.
+ //Follow the shortcut if we can.
+ if (t1.getClass() == t2.getClass()) {
+ return 0;
+ }
+
+ //Ensure normal occurrence flow control is ordered low.
+ if (isNormal(t1)) {
+ return isNormal(t2) ? 0 : -1;
+ } else {
+ if (isNormal(t2)) {
+ return 1;
+ }
+ }
+
+ //Rank the two unidenticial throwables using the rules from
+ //JLS 11.1.1. The Kinds of Exceptions and
+ //JLS 11.5 The Exception Hierarchy.
+ if (t1 instanceof Error) {
+ return t2 instanceof Error ? 0 : 1;
+ } else if (t1 instanceof RuntimeException) {
+ return t2 instanceof Error ? -1
+ : t2 instanceof RuntimeException ? 0 : 1;
+ } else {
+ return t2 instanceof Error
+ || t2 instanceof RuntimeException ? -1 : 0;
+ }
+ }
+ }
+
+ /**
+ * Compares two log records based on severity.
+ *
+ * @param o1 the first log record.
+ * @param o2 the second log record.
+ * @return a negative integer, zero, or a positive integer as the first
+ * argument is less than, equal to, or greater than the second.
+ * @throws NullPointerException if either argument is null.
+ */
+ @SuppressWarnings("override") //JDK-6954234
+ public int compare(final LogRecord o1, final LogRecord o2) {
+ if (o1 == null || o2 == null) { //Don't allow null.
+ throw new NullPointerException(toString(o1, o2));
+ }
+
+ /**
+ * LogRecords are mutable so a reflexive relationship test is a safety
+ * requirement.
+ */
+ if (o1 == o2) {
+ return 0;
+ }
+
+ int cmp = compare(o1.getLevel(), o2.getLevel());
+ if (cmp == 0) {
+ cmp = applyThenCompare(o1.getThrown(), o2.getThrown());
+ if (cmp == 0) {
+ cmp = compare(o1.getSequenceNumber(), o2.getSequenceNumber());
+ if (cmp == 0) {
+ cmp = compare(o1.getMillis(), o2.getMillis());
+ }
+ }
+ }
+ return cmp;
+ }
+
+ /**
+ * Determines if the given object is also a comparator and it imposes the
+ * same ordering as this comparator.
+ *
+ * @param o the reference object with which to compare.
+ * @return true if this object equal to the argument; false otherwise.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ return o == null ? false : o.getClass() == getClass();
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return Returns a hash code value for the object.
+ */
+ @Override
+ public int hashCode() {
+ return 31 * getClass().hashCode();
+ }
+
+ /**
+ * Determines if the given throwable instance is "normal occurrence". This
+ * is any checked or unchecked exception with 'Interrupt' in the class name
+ * or ancestral class name. Any {@code java.lang.ThreadDeath} object or
+ * subclasses.
+ *
+ * This method can be overridden to change the behavior of the
+ * {@linkplain #apply(java.lang.Throwable)} method.
+ *
+ * @param t a throwable or null.
+ * @return true the given throwable is a "normal occurrence".
+ */
+ public boolean isNormal(final Throwable t) {
+ if (t == null) { //This is only needed when called directly.
+ return false;
+ }
+
+ /**
+ * Use the class names to avoid loading more classes.
+ */
+ final Class> root = Throwable.class;
+ final Class> error = Error.class;
+ for (Class> c = t.getClass(); c != root; c = c.getSuperclass()) {
+ if (error.isAssignableFrom(c)) {
+ if (c.getName().equals("java.lang.ThreadDeath")) {
+ return true;
+ }
+ } else {
+ //Interrupt, Interrupted or Interruption.
+ if (c.getName().contains("Interrupt")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compare two level objects.
+ *
+ * @param a the first level.
+ * @param b the second level.
+ * @return a negative integer, zero, or a positive integer as the first
+ * argument is less than, equal to, or greater than the second.
+ */
+ private int compare(final Level a, final Level b) {
+ return a == b ? 0 : compare(a.intValue(), b.intValue());
+ }
+
+ /**
+ * Outline the message create string.
+ *
+ * @param o1 argument one.
+ * @param o2 argument two.
+ * @return the message string.
+ */
+ private static String toString(final Object o1, final Object o2) {
+ return o1 + ", " + o2;
+ }
+
+ /**
+ * Compare two longs. Can be removed when JDK 1.7 is required.
+ *
+ * @param x the first long.
+ * @param y the second long.
+ * @return a negative integer, zero, or a positive integer as the first
+ * argument is less than, equal to, or greater than the second.
+ */
+ private int compare(final long x, final long y) {
+ return x < y ? -1 : x > y ? 1 : 0;
+ }
+}
diff --git a/app/src/main/java/com/sun/mail/util/logging/package.html b/app/src/main/java/com/sun/mail/util/logging/package.html
new file mode 100644
index 0000000000..fcde4a4ac3
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/logging/package.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+ Contains Jakarta Mail extensions for
+ the Java™ platform's core logging
+ facilities. This package contains classes used to export log messages
+ as a formatted email message. Classes in this package typically use
+ LogManager properties to set default values; see the specific
+ documentation for each concrete class.
+
+
diff --git a/app/src/main/java/com/sun/mail/util/package.html b/app/src/main/java/com/sun/mail/util/package.html
new file mode 100644
index 0000000000..6f809d1f5c
--- /dev/null
+++ b/app/src/main/java/com/sun/mail/util/package.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+com.sun.mail.util package
+
+
+
+
+Utility classes for use with the Jakarta Mail API.
+These utility classes are not part of the Jakarta Mail specification.
+While this package contains many classes used by the Jakarta Mail implementation
+and not intended for direct use by applications, the classes documented
+here may be of use to applications.
+
+
+Classes in this package log debugging information using
+{@link java.util.logging} as described in the following table:
+
+
+com.sun.mail.util Loggers
+
+Logger Name
+Logging Level
+Purpose
+
+
+
+com.sun.mail.util.socket
+FINER
+Debugging output related to creating sockets
+
+
+
+
+WARNING: The APIs in this package should be
+considered EXPERIMENTAL . They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+
+
+
+
diff --git a/app/src/main/java/javax/mail/Address.java b/app/src/main/java/javax/mail/Address.java
new file mode 100644
index 0000000000..2b87cf4e30
--- /dev/null
+++ b/app/src/main/java/javax/mail/Address.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.Serializable;
+
+/**
+ * This abstract class models the addresses in a message.
+ * Subclasses provide specific implementations. Subclasses
+ * will typically be serializable so that (for example) the
+ * use of Address objects in search terms can be serialized
+ * along with the search terms.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class Address implements Serializable {
+
+ private static final long serialVersionUID = -5822459626751992278L;
+
+ /**
+ * Return a type string that identifies this address type.
+ *
+ * @return address type
+ * @see javax.mail.internet.InternetAddress
+ */
+ public abstract String getType();
+
+ /**
+ * Return a String representation of this address object.
+ *
+ * @return string representation of this address
+ */
+ @Override
+ public abstract String toString();
+
+ /**
+ * The equality operator. Subclasses should provide an
+ * implementation of this method that supports value equality
+ * (do the two Address objects represent the same destination?),
+ * not object reference equality. A subclass must also provide
+ * a corresponding implementation of the hashCode
+ * method that preserves the general contract of
+ * equals
and hashCode
- objects that
+ * compare as equal must have the same hashCode.
+ *
+ * @param address Address object
+ */
+ @Override
+ public abstract boolean equals(Object address);
+}
diff --git a/app/src/main/java/javax/mail/AuthenticationFailedException.java b/app/src/main/java/javax/mail/AuthenticationFailedException.java
new file mode 100644
index 0000000000..e0de809e6f
--- /dev/null
+++ b/app/src/main/java/javax/mail/AuthenticationFailedException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when the connect method on a Store or
+ * Transport object fails due to an authentication failure (e.g.,
+ * bad user name or password).
+ *
+ * @author Bill Shannon
+ */
+
+public class AuthenticationFailedException extends MessagingException {
+
+ private static final long serialVersionUID = 492080754054436511L;
+
+ /**
+ * Constructs an AuthenticationFailedException.
+ */
+ public AuthenticationFailedException() {
+ super();
+ }
+
+ /**
+ * Constructs an AuthenticationFailedException with the specified
+ * detail message.
+ *
+ * @param message The detailed error message
+ */
+ public AuthenticationFailedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an AuthenticationFailedException with the specified
+ * detail message and embedded exception. The exception is chained
+ * to this exception.
+ *
+ * @param message The detailed error message
+ * @param e The embedded exception
+ * @since JavaMail 1.5
+ */
+ public AuthenticationFailedException(String message, Exception e) {
+ super(message, e);
+ }
+}
diff --git a/app/src/main/java/javax/mail/Authenticator.java b/app/src/main/java/javax/mail/Authenticator.java
new file mode 100644
index 0000000000..5fa783801c
--- /dev/null
+++ b/app/src/main/java/javax/mail/Authenticator.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.net.InetAddress;
+
+/**
+ * The class Authenticator represents an object that knows how to obtain
+ * authentication for a network connection. Usually, it will do this
+ * by prompting the user for information.
+ *
+ * Applications use this class by creating a subclass, and registering
+ * an instance of that subclass with the session when it is created.
+ * When authentication is required, the system will invoke a method
+ * on the subclass (like getPasswordAuthentication). The subclass's
+ * method can query about the authentication being requested with a
+ * number of inherited methods (getRequestingXXX()), and form an
+ * appropriate message for the user.
+ *
+ * All methods that request authentication have a default implementation
+ * that fails.
+ *
+ * @see java.net.Authenticator
+ * @see javax.mail.Session#getInstance(java.util.Properties,
+ * javax.mail.Authenticator)
+ * @see javax.mail.Session#getDefaultInstance(java.util.Properties,
+ * javax.mail.Authenticator)
+ * @see javax.mail.Session#requestPasswordAuthentication
+ * @see javax.mail.PasswordAuthentication
+ *
+ * @author Bill Foote
+ * @author Bill Shannon
+ */
+
+// There are no abstract methods, but to be useful the user must
+// subclass.
+public abstract class Authenticator {
+
+ private InetAddress requestingSite;
+ private int requestingPort;
+ private String requestingProtocol;
+ private String requestingPrompt;
+ private String requestingUserName;
+
+ /**
+ * Ask the authenticator for a password.
+ *
+ *
+ * @param addr The InetAddress of the site requesting authorization,
+ * or null if not known.
+ * @param port the port for the requested connection
+ * @param protocol The protocol that's requesting the connection
+ * (@see java.net.Authenticator.getProtocol())
+ * @param prompt A prompt string for the user
+ *
+ * @return The username/password, or null if one can't be gotten.
+ */
+ final synchronized PasswordAuthentication requestPasswordAuthentication(
+ InetAddress addr, int port, String protocol,
+ String prompt, String defaultUserName) {
+ requestingSite = addr;
+ requestingPort = port;
+ requestingProtocol = protocol;
+ requestingPrompt = prompt;
+ requestingUserName = defaultUserName;
+ return getPasswordAuthentication();
+ }
+
+ /**
+ * @return the InetAddress of the site requesting authorization, or null
+ * if it's not available.
+ */
+ protected final InetAddress getRequestingSite() {
+ return requestingSite;
+ }
+
+ /**
+ * @return the port for the requested connection
+ */
+ protected final int getRequestingPort() {
+ return requestingPort;
+ }
+
+ /**
+ * Give the protocol that's requesting the connection. Often this
+ * will be based on a URLName.
+ *
+ * @return the protcol
+ *
+ * @see javax.mail.URLName#getProtocol
+ */
+ protected final String getRequestingProtocol() {
+ return requestingProtocol;
+ }
+
+ /**
+ * @return the prompt string given by the requestor
+ */
+ protected final String getRequestingPrompt() {
+ return requestingPrompt;
+ }
+
+ /**
+ * @return the default user name given by the requestor
+ */
+ protected final String getDefaultUserName() {
+ return requestingUserName;
+ }
+
+ /**
+ * Called when password authentication is needed. Subclasses should
+ * override the default implementation, which returns null.
+ *
+ * Note that if this method uses a dialog to prompt the user for this
+ * information, the dialog needs to block until the user supplies the
+ * information. This method can not simply return after showing the
+ * dialog.
+ * @return The PasswordAuthentication collected from the
+ * user, or null if none is provided.
+ */
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return null;
+ }
+}
diff --git a/app/src/main/java/javax/mail/BodyPart.java b/app/src/main/java/javax/mail/BodyPart.java
new file mode 100644
index 0000000000..bb94f75e71
--- /dev/null
+++ b/app/src/main/java/javax/mail/BodyPart.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This class models a Part that is contained within a Multipart.
+ * This is an abstract class. Subclasses provide actual implementations.
+ *
+ * BodyPart implements the Part interface. Thus, it contains a set of
+ * attributes and a "content".
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class BodyPart implements Part {
+
+ /**
+ * The Multipart
object containing this BodyPart
,
+ * if known.
+ * @since JavaMail 1.1
+ */
+ protected Multipart parent;
+
+ /**
+ * Return the containing Multipart
object,
+ * or null
if not known.
+ *
+ * @return the parent Multipart
+ */
+ public Multipart getParent() {
+ return parent;
+ }
+
+ /**
+ * Set the parent of this BodyPart
to be the specified
+ * Multipart
. Normally called by Multipart
's
+ * addBodyPart
method. parent
may be
+ * null
if the BodyPart
is being removed
+ * from its containing Multipart
.
+ * @since JavaMail 1.1
+ */
+ void setParent(Multipart parent) {
+ this.parent = parent;
+ }
+}
diff --git a/app/src/main/java/javax/mail/EncodingAware.java b/app/src/main/java/javax/mail/EncodingAware.java
new file mode 100644
index 0000000000..27efc70051
--- /dev/null
+++ b/app/src/main/java/javax/mail/EncodingAware.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * A {@link javax.activation.DataSource DataSource} that also implements
+ * EncodingAware
may specify the Content-Transfer-Encoding
+ * to use for its data. Valid Content-Transfer-Encoding values specified
+ * by RFC 2045 are "7bit", "8bit", "quoted-printable", "base64", and "binary".
+ *
+ * For example, a {@link javax.activation.FileDataSource FileDataSource}
+ * could be created that forces all files to be base64 encoded:
+ *
+ * public class Base64FileDataSource extends FileDataSource
+ * implements EncodingAware {
+ * public Base64FileDataSource(File file) {
+ * super(file);
+ * }
+ *
+ * // implements EncodingAware.getEncoding()
+ * public String getEncoding() {
+ * return "base64";
+ * }
+ * }
+ *
+ *
+ * @since JavaMail 1.5
+ * @author Bill Shannon
+ */
+
+public interface EncodingAware {
+
+ /**
+ * Return the MIME Content-Transfer-Encoding to use for this data,
+ * or null to indicate that an appropriate value should be chosen
+ * by the caller.
+ *
+ * @return the Content-Transfer-Encoding value, or null
+ */
+ public String getEncoding();
+}
diff --git a/app/src/main/java/javax/mail/EventQueue.java b/app/src/main/java/javax/mail/EventQueue.java
new file mode 100644
index 0000000000..618f872c8e
--- /dev/null
+++ b/app/src/main/java/javax/mail/EventQueue.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.EventListener;
+import java.util.Vector;
+import java.util.Queue;
+import java.util.WeakHashMap;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executor;
+import javax.mail.event.MailEvent;
+
+/**
+ * Package private class used by Store & Folder to dispatch events.
+ * This class implements an event queue, and a dispatcher thread that
+ * dequeues and dispatches events from the queue.
+ *
+ * @author Bill Shannon
+ */
+class EventQueue implements Runnable {
+
+ private volatile BlockingQueue q;
+ private Executor executor;
+
+ private static WeakHashMap appq;
+
+ /**
+ * A special event that causes the queue processing task to terminate.
+ */
+ static class TerminatorEvent extends MailEvent {
+ private static final long serialVersionUID = -2481895000841664111L;
+
+ TerminatorEvent() {
+ super(new Object());
+ }
+
+ @Override
+ public void dispatch(Object listener) {
+ // Kill the event dispatching thread.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * A "struct" to put on the queue.
+ */
+ static class QueueElement {
+ MailEvent event = null;
+ Vector extends EventListener> vector = null;
+
+ QueueElement(MailEvent event, Vector extends EventListener> vector) {
+ this.event = event;
+ this.vector = vector;
+ }
+ }
+
+ /**
+ * Construct an EventQueue using the specified Executor.
+ * If the Executor is null, threads will be created as needed.
+ */
+ EventQueue(Executor ex) {
+ this.executor = ex;
+ }
+
+ /**
+ * Enqueue an event.
+ */
+ synchronized void enqueue(MailEvent event,
+ Vector extends EventListener> vector) {
+ // if this is the first event, create the queue and start the event task
+ if (q == null) {
+ q = new LinkedBlockingQueue<>();
+ if (executor != null) {
+ executor.execute(this);
+ } else {
+ Thread qThread = new Thread(this, "Jakarta-Mail-EventQueue");
+ qThread.setDaemon(true); // not a user thread
+ qThread.start();
+ }
+ }
+ q.add(new QueueElement(event, vector));
+ }
+
+ /**
+ * Terminate the task running the queue, but only if there is a queue.
+ */
+ synchronized void terminateQueue() {
+ if (q != null) {
+ Vector dummyListeners = new Vector<>();
+ dummyListeners.setSize(1); // need atleast one listener
+ q.add(new QueueElement(new TerminatorEvent(), dummyListeners));
+ q = null;
+ }
+ }
+
+ /**
+ * Create (if necessary) an application-scoped event queue.
+ * Application scoping is based on the thread's context class loader.
+ */
+ static synchronized EventQueue getApplicationEventQueue(Executor ex) {
+ ClassLoader cl = Session.getContextClassLoader();
+ if (appq == null)
+ appq = new WeakHashMap<>();
+ EventQueue q = appq.get(cl);
+ if (q == null) {
+ q = new EventQueue(ex);
+ appq.put(cl, q);
+ }
+ return q;
+ }
+
+ /**
+ * Pull events off the queue and dispatch them.
+ */
+ @Override
+ public void run() {
+
+ BlockingQueue bq = q;
+ if (bq == null)
+ return;
+ try {
+ loop:
+ for (;;) {
+ // block until an item is available
+ QueueElement qe = bq.take();
+ MailEvent e = qe.event;
+ Vector extends EventListener> v = qe.vector;
+
+ for (int i = 0; i < v.size(); i++)
+ try {
+ e.dispatch(v.elementAt(i));
+ } catch (Throwable t) {
+ if (t instanceof InterruptedException)
+ break loop;
+ // ignore anything else thrown by the listener
+ }
+
+ qe = null; e = null; v = null;
+ }
+ } catch (InterruptedException e) {
+ // just die
+ }
+ }
+}
diff --git a/app/src/main/java/javax/mail/FetchProfile.java b/app/src/main/java/javax/mail/FetchProfile.java
new file mode 100644
index 0000000000..1b494545fe
--- /dev/null
+++ b/app/src/main/java/javax/mail/FetchProfile.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.Vector;
+
+/**
+ * Clients use a FetchProfile to list the Message attributes that
+ * it wishes to prefetch from the server for a range of messages.
+ *
+ * Messages obtained from a Folder are light-weight objects that
+ * typically start off as empty references to the actual messages.
+ * Such a Message object is filled in "on-demand" when the appropriate
+ * get*() methods are invoked on that particular Message. Certain
+ * server-based message access protocols (Ex: IMAP) allow batch
+ * fetching of message attributes for a range of messages in a single
+ * request. Clients that want to use message attributes for a range of
+ * Messages (Example: to display the top-level headers in a headerlist)
+ * might want to use the optimization provided by such servers. The
+ * FetchProfile
allows the client to indicate this desire
+ * to the server.
+ *
+ * Note that implementations are not obligated to support
+ * FetchProfiles, since there might be cases where the backend service
+ * does not allow easy, efficient fetching of such profiles.
+ *
+ * Sample code that illustrates the use of a FetchProfile is given
+ * below:
+ *
+ *
+ *
+ * Message[] msgs = folder.getMessages();
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(FetchProfile.Item.ENVELOPE);
+ * fp.add("X-mailer");
+ * folder.fetch(msgs, fp);
+ *
+ *
+ *
+ * @see javax.mail.Folder#fetch
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class FetchProfile {
+
+ private Vector- specials; // specials
+ private Vector
headers; // vector of header names
+
+ /**
+ * This inner class is the base class of all items that
+ * can be requested in a FetchProfile. The items currently
+ * defined here are ENVELOPE
, CONTENT_INFO
+ * and FLAGS
. The UIDFolder
interface
+ * defines the UID
Item as well.
+ *
+ * Note that this class only has a protected constructor, therby
+ * restricting new Item types to either this class or subclasses.
+ * This effectively implements a enumeration of allowed Item types.
+ *
+ * @see UIDFolder
+ */
+
+ public static class Item {
+ /**
+ * This is the Envelope item.
+ *
+ * The Envelope is an aggregration of the common attributes
+ * of a Message. Implementations should include the following
+ * attributes: From, To, Cc, Bcc, ReplyTo, Subject and Date.
+ * More items may be included as well.
+ *
+ * For implementations of the IMAP4 protocol (RFC 2060), the
+ * Envelope should include the ENVELOPE data item. More items
+ * may be included too.
+ */
+ public static final Item ENVELOPE = new Item("ENVELOPE");
+
+ /**
+ * This item is for fetching information about the
+ * content of the message.
+ *
+ * This includes all the attributes that describe the content
+ * of the message. Implementations should include the following
+ * attributes: ContentType, ContentDisposition,
+ * ContentDescription, Size and LineCount. Other items may be
+ * included as well.
+ */
+ public static final Item CONTENT_INFO = new Item("CONTENT_INFO");
+
+ /**
+ * SIZE is a fetch profile item that can be included in a
+ * FetchProfile
during a fetch request to a Folder.
+ * This item indicates that the sizes of the messages in the specified
+ * range should be prefetched.
+ *
+ * @since JavaMail 1.5
+ */
+ public static final Item SIZE = new Item("SIZE");
+
+ /**
+ * This is the Flags item.
+ */
+ public static final Item FLAGS = new Item("FLAGS");
+
+ private String name;
+
+ /**
+ * Constructor for an item. The name is used only for debugging.
+ *
+ * @param name the item name
+ */
+ protected Item(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Include the name in the toString return value for debugging.
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + "[" + name + "]";
+ }
+ }
+
+ /**
+ * Create an empty FetchProfile.
+ */
+ public FetchProfile() {
+ specials = null;
+ headers = null;
+ }
+
+ /**
+ * Add the given special item as one of the attributes to
+ * be prefetched.
+ *
+ * @param item the special item to be fetched
+ * @see FetchProfile.Item#ENVELOPE
+ * @see FetchProfile.Item#CONTENT_INFO
+ * @see FetchProfile.Item#FLAGS
+ */
+ public void add(Item item) {
+ if (specials == null)
+ specials = new Vector<>();
+ specials.addElement(item);
+ }
+
+ /**
+ * Add the specified header-field to the list of attributes
+ * to be prefetched.
+ *
+ * @param headerName header to be prefetched
+ */
+ public void add(String headerName) {
+ if (headers == null)
+ headers = new Vector<>();
+ headers.addElement(headerName);
+ }
+
+ /**
+ * Returns true if the fetch profile contains the given special item.
+ *
+ * @param item the Item to test
+ * @return true if the fetch profile contains the given special item
+ */
+ public boolean contains(Item item) {
+ return specials != null && specials.contains(item);
+ }
+
+ /**
+ * Returns true if the fetch profile contains the given header name.
+ *
+ * @param headerName the header to test
+ * @return true if the fetch profile contains the given header name
+ */
+ public boolean contains(String headerName) {
+ return headers != null && headers.contains(headerName);
+ }
+
+ /**
+ * Get the items set in this profile.
+ *
+ * @return items set in this profile
+ */
+ public Item[] getItems() {
+ if (specials == null)
+ return new Item[0];
+
+ Item[] s = new Item[specials.size()];
+ specials.copyInto(s);
+ return s;
+ }
+
+ /**
+ * Get the names of the header-fields set in this profile.
+ *
+ * @return headers set in this profile
+ */
+ public String[] getHeaderNames() {
+ if (headers == null)
+ return new String[0];
+
+ String[] s = new String[headers.size()];
+ headers.copyInto(s);
+ return s;
+ }
+}
diff --git a/app/src/main/java/javax/mail/Flags.java b/app/src/main/java/javax/mail/Flags.java
new file mode 100644
index 0000000000..68e88b76f8
--- /dev/null
+++ b/app/src/main/java/javax/mail/Flags.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * The Flags class represents the set of flags on a Message. Flags
+ * are composed of predefined system flags, and user defined flags.
+ *
+ * A System flag is represented by the Flags.Flag
+ * inner class. A User defined flag is represented as a String.
+ * User flags are case-independent.
+ *
+ * A set of standard system flags are predefined. Most folder
+ * implementations are expected to support these flags. Some
+ * implementations may also support arbitrary user-defined flags. The
+ * getPermanentFlags
method on a Folder returns a Flags
+ * object that holds all the flags that are supported by that folder
+ * implementation.
+ *
+ * A Flags object is serializable so that (for example) the
+ * use of Flags objects in search terms can be serialized
+ * along with the search terms.
+ *
+ * Warning:
+ * Serialized objects of this class may not be compatible with future
+ * Jakarta Mail API releases. The current serialization support is
+ * appropriate for short term storage.
+ *
+ * The below code sample illustrates how to set, examine, and get the
+ * flags for a message.
+ *
+ *
+ * Message m = folder.getMessage(1);
+ * m.setFlag(Flags.Flag.DELETED, true); // set the DELETED flag
+ *
+ * // Check if DELETED flag is set on this message
+ * if (m.isSet(Flags.Flag.DELETED))
+ * System.out.println("DELETED message");
+ *
+ * // Examine ALL system flags for this message
+ * Flags flags = m.getFlags();
+ * Flags.Flag[] sf = flags.getSystemFlags();
+ * for (int i = 0; i < sf.length; i++) {
+ * if (sf[i] == Flags.Flag.DELETED)
+ * System.out.println("DELETED message");
+ * else if (sf[i] == Flags.Flag.SEEN)
+ * System.out.println("SEEN message");
+ * ......
+ * ......
+ * }
+ *
+ *
+ *
+ * @see Folder#getPermanentFlags
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class Flags implements Cloneable, Serializable {
+
+ private int system_flags = 0;
+ // used as a case-independent Set that preserves the original case,
+ // the key is the lowercase flag name and the value is the original
+ private Hashtable user_flags = null;
+
+ private final static int ANSWERED_BIT = 0x01;
+ private final static int DELETED_BIT = 0x02;
+ private final static int DRAFT_BIT = 0x04;
+ private final static int FLAGGED_BIT = 0x08;
+ private final static int RECENT_BIT = 0x10;
+ private final static int SEEN_BIT = 0x20;
+ private final static int USER_BIT = 0x80000000;
+
+ private static final long serialVersionUID = 6243590407214169028L;
+
+ /**
+ * This inner class represents an individual system flag. A set
+ * of standard system flag objects are predefined here.
+ */
+ public static final class Flag {
+ /**
+ * This message has been answered. This flag is set by clients
+ * to indicate that this message has been answered to.
+ */
+ public static final Flag ANSWERED = new Flag(ANSWERED_BIT);
+
+ /**
+ * This message is marked deleted. Clients set this flag to
+ * mark a message as deleted. The expunge operation on a folder
+ * removes all messages in that folder that are marked for deletion.
+ */
+ public static final Flag DELETED = new Flag(DELETED_BIT);
+
+ /**
+ * This message is a draft. This flag is set by clients
+ * to indicate that the message is a draft message.
+ */
+ public static final Flag DRAFT = new Flag(DRAFT_BIT);
+
+ /**
+ * This message is flagged. No semantic is defined for this flag.
+ * Clients alter this flag.
+ */
+ public static final Flag FLAGGED = new Flag(FLAGGED_BIT);
+
+ /**
+ * This message is recent. Folder implementations set this flag
+ * to indicate that this message is new to this folder, that is,
+ * it has arrived since the last time this folder was opened.
+ *
+ * Clients cannot alter this flag.
+ */
+ public static final Flag RECENT = new Flag(RECENT_BIT);
+
+ /**
+ * This message is seen. This flag is implicitly set by the
+ * implementation when this Message's content is returned
+ * to the client in some form. The getInputStream
+ * and getContent
methods on Message cause this
+ * flag to be set.
+ *
+ * Clients can alter this flag.
+ */
+ public static final Flag SEEN = new Flag(SEEN_BIT);
+
+ /**
+ * A special flag that indicates that this folder supports
+ * user defined flags.
+ *
+ * The implementation sets this flag. Clients cannot alter
+ * this flag but can use it to determine if a folder supports
+ * user defined flags by using
+ * folder.getPermanentFlags().contains(Flags.Flag.USER)
.
+ */
+ public static final Flag USER = new Flag(USER_BIT);
+
+ // flags are stored as bits for efficiency
+ private int bit;
+ private Flag(int bit) {
+ this.bit = bit;
+ }
+ }
+
+
+ /**
+ * Construct an empty Flags object.
+ */
+ public Flags() { }
+
+ /**
+ * Construct a Flags object initialized with the given flags.
+ *
+ * @param flags the flags for initialization
+ */
+ @SuppressWarnings("unchecked")
+ public Flags(Flags flags) {
+ this.system_flags = flags.system_flags;
+ if (flags.user_flags != null)
+ this.user_flags = (Hashtable)flags.user_flags.clone();
+ }
+
+ /**
+ * Construct a Flags object initialized with the given system flag.
+ *
+ * @param flag the flag for initialization
+ */
+ public Flags(Flag flag) {
+ this.system_flags |= flag.bit;
+ }
+
+ /**
+ * Construct a Flags object initialized with the given user flag.
+ *
+ * @param flag the flag for initialization
+ */
+ public Flags(String flag) {
+ user_flags = new Hashtable<>(1);
+ user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
+ }
+
+ /**
+ * Add the specified system flag to this Flags object.
+ *
+ * @param flag the flag to add
+ */
+ public void add(Flag flag) {
+ system_flags |= flag.bit;
+ }
+
+ /**
+ * Add the specified user flag to this Flags object.
+ *
+ * @param flag the flag to add
+ */
+ public void add(String flag) {
+ if (user_flags == null)
+ user_flags = new Hashtable<>(1);
+ user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
+ }
+
+ /**
+ * Add all the flags in the given Flags object to this
+ * Flags object.
+ *
+ * @param f Flags object
+ */
+ public void add(Flags f) {
+ system_flags |= f.system_flags; // add system flags
+
+ if (f.user_flags != null) { // add user-defined flags
+ if (user_flags == null)
+ user_flags = new Hashtable<>(1);
+
+ Enumeration e = f.user_flags.keys();
+
+ while (e.hasMoreElements()) {
+ String s = e.nextElement();
+ user_flags.put(s, f.user_flags.get(s));
+ }
+ }
+ }
+
+ /**
+ * Remove the specified system flag from this Flags object.
+ *
+ * @param flag the flag to be removed
+ */
+ public void remove(Flag flag) {
+ system_flags &= ~flag.bit;
+ }
+
+ /**
+ * Remove the specified user flag from this Flags object.
+ *
+ * @param flag the flag to be removed
+ */
+ public void remove(String flag) {
+ if (user_flags != null)
+ user_flags.remove(flag.toLowerCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Remove all flags in the given Flags object from this
+ * Flags object.
+ *
+ * @param f the flag to be removed
+ */
+ public void remove(Flags f) {
+ system_flags &= ~f.system_flags; // remove system flags
+
+ if (f.user_flags != null) {
+ if (user_flags == null)
+ return;
+
+ Enumeration e = f.user_flags.keys();
+ while (e.hasMoreElements())
+ user_flags.remove(e.nextElement());
+ }
+ }
+
+ /**
+ * Remove any flags not in the given Flags object.
+ * Useful for clearing flags not supported by a server. If the
+ * given Flags object includes the Flags.Flag.USER flag, all user
+ * flags in this Flags object are retained.
+ *
+ * @param f the flags to keep
+ * @return true if this Flags object changed
+ * @since JavaMail 1.6
+ */
+ public boolean retainAll(Flags f) {
+ boolean changed = false;
+ int sf = system_flags & f.system_flags;
+ if (system_flags != sf) {
+ system_flags = sf;
+ changed = true;
+ }
+
+ // if we have user flags, and the USER flag is not set in "f",
+ // determine which user flags to clear
+ if (user_flags != null && (f.system_flags & USER_BIT) == 0) {
+ if (f.user_flags != null) {
+ Enumeration e = user_flags.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ if (!f.user_flags.containsKey(key)) {
+ user_flags.remove(key);
+ changed = true;
+ }
+ }
+ } else {
+ // if anything in user_flags, throw them away
+ changed = user_flags.size() > 0;
+ user_flags = null;
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Check whether the specified system flag is present in this Flags object.
+ *
+ * @param flag the flag to test
+ * @return true of the given flag is present, otherwise false.
+ */
+ public boolean contains(Flag flag) {
+ return (system_flags & flag.bit) != 0;
+ }
+
+ /**
+ * Check whether the specified user flag is present in this Flags object.
+ *
+ * @param flag the flag to test
+ * @return true of the given flag is present, otherwise false.
+ */
+ public boolean contains(String flag) {
+ if (user_flags == null)
+ return false;
+ else
+ return user_flags.containsKey(flag.toLowerCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Check whether all the flags in the specified Flags object are
+ * present in this Flags object.
+ *
+ * @param f the flags to test
+ * @return true if all flags in the given Flags object are present,
+ * otherwise false.
+ */
+ public boolean contains(Flags f) {
+ // Check system flags
+ if ((f.system_flags & system_flags) != f.system_flags)
+ return false;
+
+ // Check user flags
+ if (f.user_flags != null) {
+ if (user_flags == null)
+ return false;
+ Enumeration e = f.user_flags.keys();
+
+ while (e.hasMoreElements()) {
+ if (!user_flags.containsKey(e.nextElement()))
+ return false;
+ }
+ }
+
+ // If we've made it till here, return true
+ return true;
+ }
+
+ /**
+ * Check whether the two Flags objects are equal.
+ *
+ * @return true if they're equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Flags))
+ return false;
+
+ Flags f = (Flags)obj;
+
+ // Check system flags
+ if (f.system_flags != this.system_flags)
+ return false;
+
+ // Check user flags
+ int size = this.user_flags == null ? 0 : this.user_flags.size();
+ int fsize = f.user_flags == null ? 0 : f.user_flags.size();
+ if (size == 0 && fsize == 0)
+ return true;
+ if (f.user_flags != null && this.user_flags != null && fsize == size)
+ return user_flags.keySet().equals(f.user_flags.keySet());
+
+ return false;
+ }
+
+ /**
+ * Compute a hash code for this Flags object.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ int hash = system_flags;
+ if (user_flags != null) {
+ Enumeration e = user_flags.keys();
+ while (e.hasMoreElements())
+ hash += e.nextElement().hashCode();
+ }
+ return hash;
+ }
+
+ /**
+ * Return all the system flags in this Flags object. Returns
+ * an array of size zero if no flags are set.
+ *
+ * @return array of Flags.Flag objects representing system flags
+ */
+ public Flag[] getSystemFlags() {
+ Vector v = new Vector<>();
+ if ((system_flags & ANSWERED_BIT) != 0)
+ v.addElement(Flag.ANSWERED);
+ if ((system_flags & DELETED_BIT) != 0)
+ v.addElement(Flag.DELETED);
+ if ((system_flags & DRAFT_BIT) != 0)
+ v.addElement(Flag.DRAFT);
+ if ((system_flags & FLAGGED_BIT) != 0)
+ v.addElement(Flag.FLAGGED);
+ if ((system_flags & RECENT_BIT) != 0)
+ v.addElement(Flag.RECENT);
+ if ((system_flags & SEEN_BIT) != 0)
+ v.addElement(Flag.SEEN);
+ if ((system_flags & USER_BIT) != 0)
+ v.addElement(Flag.USER);
+
+ Flag[] f = new Flag[v.size()];
+ v.copyInto(f);
+ return f;
+ }
+
+ /**
+ * Return all the user flags in this Flags object. Returns
+ * an array of size zero if no flags are set.
+ *
+ * @return array of Strings, each String represents a flag.
+ */
+ public String[] getUserFlags() {
+ Vector v = new Vector<>();
+ if (user_flags != null) {
+ Enumeration e = user_flags.elements();
+
+ while (e.hasMoreElements())
+ v.addElement(e.nextElement());
+ }
+
+ String[] f = new String[v.size()];
+ v.copyInto(f);
+ return f;
+ }
+
+ /**
+ * Clear all of the system flags.
+ *
+ * @since JavaMail 1.6
+ */
+ public void clearSystemFlags() {
+ system_flags = 0;
+ }
+
+ /**
+ * Clear all of the user flags.
+ *
+ * @since JavaMail 1.6
+ */
+ public void clearUserFlags() {
+ user_flags = null;
+ }
+
+ /**
+ * Returns a clone of this Flags object.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object clone() {
+ Flags f = null;
+ try {
+ f = (Flags)super.clone();
+ } catch (CloneNotSupportedException cex) {
+ // ignore, can't happen
+ }
+ if (this.user_flags != null)
+ f.user_flags = (Hashtable)this.user_flags.clone();
+ return f;
+ }
+
+ /**
+ * Return a string representation of this Flags object.
+ * Note that the exact format of the string is subject to change.
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ if ((system_flags & ANSWERED_BIT) != 0)
+ sb.append("\\Answered ");
+ if ((system_flags & DELETED_BIT) != 0)
+ sb.append("\\Deleted ");
+ if ((system_flags & DRAFT_BIT) != 0)
+ sb.append("\\Draft ");
+ if ((system_flags & FLAGGED_BIT) != 0)
+ sb.append("\\Flagged ");
+ if ((system_flags & RECENT_BIT) != 0)
+ sb.append("\\Recent ");
+ if ((system_flags & SEEN_BIT) != 0)
+ sb.append("\\Seen ");
+ if ((system_flags & USER_BIT) != 0)
+ sb.append("\\* ");
+
+ boolean first = true;
+ if (user_flags != null) {
+ Enumeration e = user_flags.elements();
+
+ while (e.hasMoreElements()) {
+ if (first)
+ first = false;
+ else
+ sb.append(' ');
+ sb.append(e.nextElement());
+ }
+ }
+
+ if (first && sb.length() > 0)
+ sb.setLength(sb.length() - 1); // smash trailing space
+
+ return sb.toString();
+ }
+
+ /*****
+ public static void main(String argv[]) throws Exception {
+ // a new flags object
+ Flags f1 = new Flags();
+ f1.add(Flags.Flag.DELETED);
+ f1.add(Flags.Flag.SEEN);
+ f1.add(Flags.Flag.RECENT);
+ f1.add(Flags.Flag.ANSWERED);
+
+ // check copy constructor with only system flags
+ Flags fc = new Flags(f1);
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check clone with only system flags
+ fc = (Flags)f1.clone();
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // add a user flag and make sure it still works right
+ f1.add("MyFlag");
+
+ // shouldn't be equal here
+ if (!f1.equals(fc) && !fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check clone
+ fc = (Flags)f1.clone();
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // make sure user flag hash tables are separate
+ fc.add("AnotherFlag");
+ if (!f1.equals(fc) && !fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // check copy constructor
+ fc = new Flags(f1);
+ if (f1.equals(fc) && fc.equals(f1))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ // another new flags object
+ Flags f2 = new Flags(Flags.Flag.ANSWERED);
+ f2.add("MyFlag");
+
+ if (f1.contains(Flags.Flag.DELETED))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains(Flags.Flag.SEEN))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains(Flags.Flag.RECENT))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f1.contains("MyFlag"))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (f2.contains(Flags.Flag.ANSWERED))
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+
+ System.out.println("----------------");
+
+ String[] s = f1.getUserFlags();
+ for (int i = 0; i < s.length; i++)
+ System.out.println(s[i]);
+ System.out.println("----------------");
+ s = f2.getUserFlags();
+ for (int i = 0; i < s.length; i++)
+ System.out.println(s[i]);
+
+ System.out.println("----------------");
+
+ if (f1.contains(f2)) // this should be true
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ if (!f2.contains(f1)) // this should be false
+ System.out.println("success");
+ else
+ System.out.println("fail");
+
+ Flags f3 = new Flags();
+ f3.add(Flags.Flag.DELETED);
+ f3.add(Flags.Flag.SEEN);
+ f3.add(Flags.Flag.RECENT);
+ f3.add(Flags.Flag.ANSWERED);
+ f3.add("ANOTHERFLAG");
+ f3.add("MYFLAG");
+
+ f1.add("AnotherFlag");
+
+ if (f1.equals(f3))
+ System.out.println("equals success");
+ else
+ System.out.println("fail");
+ if (f3.equals(f1))
+ System.out.println("equals success");
+ else
+ System.out.println("fail");
+ System.out.println("f1 hash code " + f1.hashCode());
+ System.out.println("f3 hash code " + f3.hashCode());
+ if (f1.hashCode() == f3.hashCode())
+ System.out.println("success");
+ else
+ System.out.println("fail");
+ }
+ ****/
+}
diff --git a/app/src/main/java/javax/mail/Folder.java b/app/src/main/java/javax/mail/Folder.java
new file mode 100644
index 0000000000..0ddce216fc
--- /dev/null
+++ b/app/src/main/java/javax/mail/Folder.java
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.*;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.List;
+import java.util.Vector;
+import java.util.concurrent.Executor;
+import javax.mail.search.SearchTerm;
+import javax.mail.event.*;
+
+/**
+ * Folder is an abstract class that represents a folder for mail
+ * messages. Subclasses implement protocol specific Folders.
+ *
+ * Folders can contain Messages, other Folders or both, thus providing
+ * a tree-like hierarchy rooted at the Store's default folder. (Note
+ * that some Folder implementations may not allow both Messages and
+ * other Folders in the same Folder).
+ *
+ * The interpretation of folder names is implementation dependent.
+ * The different levels of hierarchy in a folder's full name
+ * are separated from each other by the hierarchy delimiter
+ * character.
+ *
+ * The case-insensitive full folder name (that is, the full name
+ * relative to the default folder for a Store) INBOX
+ * is reserved to mean the "primary folder for this user on this
+ * server". Not all Stores will provide an INBOX folder, and not
+ * all users will have an INBOX folder at all times. The name
+ * INBOX is reserved to refer to this folder,
+ * when it exists, in Stores that provide it.
+ *
+ * A Folder object obtained from a Store need not actually exist
+ * in the backend store. The exists
method tests whether
+ * the folder exists or not. The create
method
+ * creates a Folder.
+ *
+ * A Folder is initially in the closed state. Certain methods are valid
+ * in this state; the documentation for those methods note this. A
+ * Folder is opened by calling its 'open' method. All Folder methods,
+ * except open
, delete
and
+ * renameTo
, are valid in this state.
+ *
+ * The only way to get a Folder is by invoking the
+ * getFolder
method on Store, Folder, or Session, or by invoking
+ * the list
or listSubscribed
methods
+ * on Folder. Folder objects returned by the above methods are not
+ * cached by the Store. Thus, invoking the getFolder
method
+ * with the same folder name multiple times will return distinct Folder
+ * objects. Likewise for the list
and listSubscribed
+ * methods.
+ *
+ * The Message objects within the Folder are cached by the Folder.
+ * Thus, invoking getMessage(msgno)
on the same message number
+ * multiple times will return the same Message object, until an
+ * expunge is done on this Folder.
+ *
+ * Message objects from a Folder are only valid while a Folder is open
+ * and should not be accessed after the Folder is closed, even if the
+ * Folder is subsequently reopened. Instead, new Message objects must
+ * be fetched from the Folder after the Folder is reopened.
+ *
+ * Note that a Message's message number can change within a
+ * session if the containing Folder is expunged using the expunge
+ * method. Clients that use message numbers as references to messages
+ * should be aware of this and should be prepared to deal with this
+ * situation (probably by flushing out existing message number references
+ * and reloading them). Because of this complexity, it is better for
+ * clients to use Message objects as references to messages, rather than
+ * message numbers. Expunged Message objects still have to be
+ * pruned, but other Message objects in that folder are not affected by the
+ * expunge.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class Folder implements AutoCloseable {
+
+ /**
+ * The parent store.
+ */
+ protected Store store;
+
+ /**
+ * The open mode of this folder. The open mode is
+ * Folder.READ_ONLY
, Folder.READ_WRITE
,
+ * or -1 if not known.
+ * @since JavaMail 1.1
+ */
+ protected int mode = -1;
+
+ /*
+ * The queue of events to be delivered.
+ */
+ private final EventQueue q;
+
+ /**
+ * Constructor that takes a Store object.
+ *
+ * @param store the Store that holds this folder
+ */
+ protected Folder(Store store) {
+ this.store = store;
+
+ // create or choose the appropriate event queue
+ Session session = store.getSession();
+ String scope =
+ session.getProperties().getProperty("mail.event.scope", "folder");
+ Executor executor =
+ (Executor)session.getProperties().get("mail.event.executor");
+ if (scope.equalsIgnoreCase("application"))
+ q = EventQueue.getApplicationEventQueue(executor);
+ else if (scope.equalsIgnoreCase("session"))
+ q = session.getEventQueue();
+ else if (scope.equalsIgnoreCase("store"))
+ q = store.getEventQueue();
+ else // if (scope.equalsIgnoreCase("folder"))
+ q = new EventQueue(executor);
+ }
+
+ /**
+ * Returns the name of this Folder.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * @return name of the Folder
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the full name of this Folder. If the folder resides under
+ * the root hierarchy of this Store, the returned name is relative
+ * to the root. Otherwise an absolute name, starting with the
+ * hierarchy delimiter, is returned.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * @return full name of the Folder
+ */
+ public abstract String getFullName();
+
+ /**
+ * Return a URLName representing this folder. The returned URLName
+ * does not include the password used to access the store.
+ *
+ * @return the URLName representing this folder
+ * @exception MessagingException for failures
+ * @see URLName
+ * @since JavaMail 1.1
+ */
+ public URLName getURLName() throws MessagingException {
+ URLName storeURL = getStore().getURLName();
+ String fullname = getFullName();
+ StringBuilder encodedName = new StringBuilder();
+
+ if (fullname != null) {
+ /*
+ // We need to encode each of the folder's names.
+ char separator = getSeparator();
+ StringTokenizer tok = new StringTokenizer(
+ fullname, new Character(separator).toString(), true);
+
+ while (tok.hasMoreTokens()) {
+ String s = tok.nextToken();
+ if (s.charAt(0) == separator)
+ encodedName.append(separator);
+ else
+ // XXX - should encode, but since there's no decoder...
+ //encodedName.append(java.net.URLEncoder.encode(s));
+ encodedName.append(s);
+ }
+ */
+ // append the whole thing, until we can encode
+ encodedName.append(fullname);
+ }
+
+ /*
+ * Sure would be convenient if URLName had a
+ * constructor that took a base URLName.
+ */
+ return new URLName(storeURL.getProtocol(), storeURL.getHost(),
+ storeURL.getPort(), encodedName.toString(),
+ storeURL.getUsername(),
+ null /* no password */);
+ }
+
+ /**
+ * Returns the Store that owns this Folder object.
+ * This method can be invoked on a closed Folder.
+ *
+ * @return the Store
+ */
+ public Store getStore() {
+ return store;
+ }
+
+ /**
+ * Returns the parent folder of this folder.
+ * This method can be invoked on a closed Folder. If this folder
+ * is the top of a folder hierarchy, this method returns null.
+ *
+ * Note that since Folder objects are not cached, invoking this method
+ * returns a new distinct Folder object.
+ *
+ * @return Parent folder
+ * @exception MessagingException for failures
+ */
+ public abstract Folder getParent() throws MessagingException;
+
+ /**
+ * Tests if this folder physically exists on the Store.
+ * This method can be invoked on a closed Folder.
+ *
+ * @return true if the folder exists, otherwise false
+ * @see #create
+ * @exception MessagingException typically if the connection
+ * to the server is lost.
+ */
+ public abstract boolean exists() throws MessagingException;
+
+ /**
+ * Returns a list of Folders belonging to this Folder's namespace
+ * that match the specified pattern. Patterns may contain the wildcard
+ * characters "%"
, which matches any character except hierarchy
+ * delimiters, and "*"
, which matches any character.
+ *
+ * As an example, given the folder hierarchy:
+ * Personal/
+ * Finance/
+ * Stocks
+ * Bonus
+ * StockOptions
+ * Jokes
+ *
+ * list("*")
on "Personal" will return the whole
+ * hierarchy.
+ * list("%")
on "Personal" will return "Finance" and
+ * "Jokes".
+ * list("Jokes")
on "Personal" will return "Jokes".
+ * list("Stock*")
on "Finance" will return "Stocks"
+ * and "StockOptions".
+ *
+ * Folder objects are not cached by the Store, so invoking this
+ * method on the same pattern multiple times will return that many
+ * distinct Folder objects.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * @param pattern the match pattern
+ * @return array of matching Folder objects. An empty
+ * array is returned if no matching Folders exist.
+ * @see #listSubscribed
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public abstract Folder[] list(String pattern) throws MessagingException;
+
+ /**
+ * Returns a list of subscribed Folders belonging to this Folder's
+ * namespace that match the specified pattern. If the folder does
+ * not support subscription, this method should resolve to
+ * list
.
+ * (The default implementation provided here, does just this).
+ * The pattern can contain wildcards as for list
.
+ *
+ * Note that, at a given level of the folder hierarchy, a particular
+ * folder may not be subscribed, but folders underneath that folder
+ * in the folder hierarchy may be subscribed. In order to allow
+ * walking the folder hierarchy, such unsubscribed folders may be
+ * returned, indicating that a folder lower in the hierarchy is
+ * subscribed. The isSubscribed
method on a folder will
+ * tell whether any particular folder is actually subscribed.
+ *
+ * Folder objects are not cached by the Store, so invoking this
+ * method on the same pattern multiple times will return that many
+ * distinct Folder objects.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * @param pattern the match pattern
+ * @return array of matching subscribed Folder objects. An
+ * empty array is returned if no matching
+ * subscribed folders exist.
+ * @see #list
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public Folder[] listSubscribed(String pattern) throws MessagingException {
+ return list(pattern);
+ }
+
+ /**
+ * Convenience method that returns the list of folders under this
+ * Folder. This method just calls the list(String pattern)
+ * method with "%"
as the match pattern. This method can
+ * be invoked on a closed Folder.
+ *
+ * @return array of Folder objects under this Folder. An
+ * empty array is returned if no subfolders exist.
+ * @see #list
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+
+ public Folder[] list() throws MessagingException {
+ return list("%");
+ }
+
+ /**
+ * Convenience method that returns the list of subscribed folders
+ * under this Folder. This method just calls the
+ * listSubscribed(String pattern)
method with "%"
+ * as the match pattern. This method can be invoked on a closed Folder.
+ *
+ * @return array of subscribed Folder objects under this
+ * Folder. An empty array is returned if no subscribed
+ * subfolders exist.
+ * @see #listSubscribed
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public Folder[] listSubscribed() throws MessagingException {
+ return listSubscribed("%");
+ }
+
+ /**
+ * Return the delimiter character that separates this Folder's pathname
+ * from the names of immediate subfolders. This method can be invoked
+ * on a closed Folder.
+ *
+ * @exception FolderNotFoundException if the implementation
+ * requires the folder to exist, but it does not
+ * @return Hierarchy separator character
+ */
+ public abstract char getSeparator() throws MessagingException;
+
+ /**
+ * This folder can contain messages
+ */
+ public final static int HOLDS_MESSAGES = 0x01;
+
+ /**
+ * This folder can contain other folders
+ */
+ public final static int HOLDS_FOLDERS = 0x02;
+
+ /**
+ * Returns the type of this Folder, that is, whether this folder can hold
+ * messages or subfolders or both. The returned value is an integer
+ * bitfield with the appropriate bits set. This method can be invoked
+ * on a closed folder.
+ *
+ * @return integer with appropriate bits set
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @see #HOLDS_FOLDERS
+ * @see #HOLDS_MESSAGES
+ */
+ public abstract int getType() throws MessagingException;
+
+ /**
+ * Create this folder on the Store. When this folder is created, any
+ * folders in its path that do not exist are also created.
+ *
+ * If the creation is successful, a CREATED FolderEvent is delivered
+ * to any FolderListeners registered on this Folder and this Store.
+ *
+ * @param type The type of this folder.
+ *
+ * @return true if the creation succeeds, else false.
+ * @exception MessagingException for failures
+ * @see #HOLDS_FOLDERS
+ * @see #HOLDS_MESSAGES
+ * @see javax.mail.event.FolderEvent
+ */
+ public abstract boolean create(int type) throws MessagingException;
+
+ /**
+ * Returns true if this Folder is subscribed.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * The default implementation provided here just returns true.
+ *
+ * @return true if this Folder is subscribed
+ */
+ public boolean isSubscribed() {
+ return true;
+ }
+
+ /**
+ * Subscribe or unsubscribe this Folder. Not all Stores support
+ * subscription.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * The implementation provided here just throws the
+ * MethodNotSupportedException.
+ *
+ * @param subscribe true to subscribe, false to unsubscribe
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MethodNotSupportedException if this store
+ * does not support subscription
+ * @exception MessagingException for other failures
+ */
+ public void setSubscribed(boolean subscribe)
+ throws MessagingException {
+ throw new MethodNotSupportedException();
+ }
+
+ /**
+ * Returns true if this Folder has new messages since the last time
+ * this indication was reset. When this indication is set or reset
+ * depends on the Folder implementation (and in the case of IMAP,
+ * depends on the server). This method can be used to implement
+ * a lightweight "check for new mail" operation on a Folder without
+ * opening it. (For example, a thread that monitors a mailbox and
+ * flags when it has new mail.) This method should indicate whether
+ * any messages in the Folder have the RECENT
flag set.
+ *
+ * Note that this is not an incremental check for new mail, i.e.,
+ * it cannot be used to determine whether any new messages have
+ * arrived since the last time this method was invoked. To
+ * implement incremental checks, the Folder needs to be opened.
+ *
+ * This method can be invoked on a closed Folder that can contain
+ * Messages.
+ *
+ * @return true if the Store has new Messages
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public abstract boolean hasNewMessages() throws MessagingException;
+
+ /**
+ * Return the Folder object corresponding to the given name. Note that
+ * this folder does not physically have to exist in the Store. The
+ * exists()
method on a Folder indicates whether it really
+ * exists on the Store.
+ *
+ * In some Stores, name can be an absolute path if it starts with the
+ * hierarchy delimiter. Otherwise, it is interpreted relative to
+ * this Folder.
+ *
+ * Folder objects are not cached by the Store, so invoking this
+ * method on the same name multiple times will return that many
+ * distinct Folder objects.
+ *
+ * This method can be invoked on a closed Folder.
+ *
+ * @param name name of the Folder
+ * @return Folder object
+ * @exception MessagingException for failures
+ */
+ public abstract Folder getFolder(String name)
+ throws MessagingException;
+
+ /**
+ * Delete this Folder. This method will succeed only on a closed
+ * Folder.
+ *
+ * The recurse
flag controls whether the deletion affects
+ * subfolders or not. If true, all subfolders are deleted, then this
+ * folder itself is deleted. If false, the behaviour is dependent on
+ * the folder type and is elaborated below:
+ *
+ *
+ *
+ * @param recurse also delete subfolders?
+ * @return true if the Folder is deleted successfully
+ * @exception FolderNotFoundException if this folder does
+ * not exist
+ * @exception IllegalStateException if this folder is not in
+ * the closed state.
+ * @exception MessagingException for other failures
+ * @see javax.mail.event.FolderEvent
+ */
+ public abstract boolean delete(boolean recurse)
+ throws MessagingException;
+
+ /**
+ * Rename this Folder. This method will succeed only on a closed
+ * Folder.
+ *
+ * If the rename is successful, a RENAMED FolderEvent is delivered
+ * to FolderListeners registered on this folder and its containing
+ * Store.
+ *
+ * @param f a folder representing the new name for this Folder
+ * @return true if the Folder is renamed successfully
+ * @exception FolderNotFoundException if this folder does
+ * not exist
+ * @exception IllegalStateException if this folder is not in
+ * the closed state.
+ * @exception MessagingException for other failures
+ * @see javax.mail.event.FolderEvent
+ */
+ public abstract boolean renameTo(Folder f) throws MessagingException;
+
+ /**
+ * The Folder is read only. The state and contents of this
+ * folder cannot be modified.
+ */
+ public static final int READ_ONLY = 1;
+
+ /**
+ * The state and contents of this folder can be modified.
+ */
+ public static final int READ_WRITE = 2;
+
+ /**
+ * Open this Folder. This method is valid only on Folders that
+ * can contain Messages and that are closed.
+ *
+ * If this folder is opened successfully, an OPENED ConnectionEvent
+ * is delivered to any ConnectionListeners registered on this
+ * Folder.
+ *
+ * The effect of opening multiple connections to the same folder
+ * on a specifc Store is implementation dependent. Some implementations
+ * allow multiple readers, but only one writer. Others allow
+ * multiple writers as well as readers.
+ *
+ * @param mode open the Folder READ_ONLY or READ_WRITE
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not in
+ * the closed state.
+ * @exception MessagingException for other failures
+ * @see #READ_ONLY
+ * @see #READ_WRITE
+ * @see #getType()
+ * @see javax.mail.event.ConnectionEvent
+ */
+ public abstract void open(int mode) throws MessagingException;
+
+ /**
+ * Close this Folder. This method is valid only on open Folders.
+ *
+ * A CLOSED ConnectionEvent is delivered to any ConnectionListeners
+ * registered on this Folder. Note that the folder is closed even
+ * if this method terminates abnormally by throwing a
+ * MessagingException.
+ *
+ * @param expunge expunges all deleted messages if this flag is true
+ * @exception IllegalStateException if this folder is not opened
+ * @exception MessagingException for other failures
+ * @see javax.mail.event.ConnectionEvent
+ */
+ public abstract void close(boolean expunge) throws MessagingException;
+
+ /**
+ * Close this Folder and expunge deleted messages.
+ *
+ * A CLOSED ConnectionEvent is delivered to any ConnectionListeners
+ * registered on this Folder. Note that the folder is closed even
+ * if this method terminates abnormally by throwing a
+ * MessagingException.
+ *
+ * This method supports the {@link java.lang.AutoCloseable AutoCloseable}
+ * interface.
+ *
+ * This implementation calls close(true)
.
+ *
+ * @exception IllegalStateException if this folder is not opened
+ * @exception MessagingException for other failures
+ * @see javax.mail.event.ConnectionEvent
+ * @since JavaMail 1.6
+ */
+ @Override
+ public void close() throws MessagingException {
+ close(true);
+ }
+
+ /**
+ * Indicates whether this Folder is in the 'open' state.
+ * @return true if this Folder is in the 'open' state.
+ */
+ public abstract boolean isOpen();
+
+ /**
+ * Return the open mode of this folder. Returns
+ * Folder.READ_ONLY
, Folder.READ_WRITE
,
+ * or -1 if the open mode is not known (usually only because an older
+ * Folder
provider has not been updated to use this new
+ * method).
+ *
+ * @exception IllegalStateException if this folder is not opened
+ * @return the open mode of this folder
+ * @since JavaMail 1.1
+ */
+ public synchronized int getMode() {
+ if (!isOpen())
+ throw new IllegalStateException("Folder not open");
+ return mode;
+ }
+
+ /**
+ * Get the permanent flags supported by this Folder. Returns a Flags
+ * object that contains all the flags supported.
+ *
+ * The special flag Flags.Flag.USER
indicates that this Folder
+ * supports arbitrary user-defined flags.
+ *
+ * The supported permanent flags for a folder may not be available
+ * until the folder is opened.
+ *
+ * @return permanent flags, or null if not known
+ */
+ public abstract Flags getPermanentFlags();
+
+ /**
+ * Get total number of messages in this Folder.
+ *
+ * This method can be invoked on a closed folder. However, note
+ * that for some folder implementations, getting the total message
+ * count can be an expensive operation involving actually opening
+ * the folder. In such cases, a provider can choose not to support
+ * this functionality in the closed state, in which case this method
+ * must return -1.
+ *
+ * Clients invoking this method on a closed folder must be aware
+ * that this is a potentially expensive operation. Clients must
+ * also be prepared to handle a return value of -1 in this case.
+ *
+ * @return total number of messages. -1 may be returned
+ * by certain implementations if this method is
+ * invoked on a closed folder.
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public abstract int getMessageCount() throws MessagingException;
+
+ /**
+ * Get the number of new messages in this Folder.
+ *
+ * This method can be invoked on a closed folder. However, note
+ * that for some folder implementations, getting the new message
+ * count can be an expensive operation involving actually opening
+ * the folder. In such cases, a provider can choose not to support
+ * this functionality in the closed state, in which case this method
+ * must return -1.
+ *
+ * Clients invoking this method on a closed folder must be aware
+ * that this is a potentially expensive operation. Clients must
+ * also be prepared to handle a return value of -1 in this case.
+ *
+ * This implementation returns -1 if this folder is closed. Else
+ * this implementation gets each Message in the folder using
+ * getMessage(int)
and checks whether its
+ * RECENT
flag is set. The total number of messages
+ * that have this flag set is returned.
+ *
+ * @return number of new messages. -1 may be returned
+ * by certain implementations if this method is
+ * invoked on a closed folder.
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public synchronized int getNewMessageCount()
+ throws MessagingException {
+ if (!isOpen())
+ return -1;
+
+ int newmsgs = 0;
+ int total = getMessageCount();
+ for (int i = 1; i <= total; i++) {
+ try {
+ if (getMessage(i).isSet(Flags.Flag.RECENT))
+ newmsgs++;
+ } catch (MessageRemovedException me) {
+ // This is an expunged message, ignore it.
+ continue;
+ }
+ }
+ return newmsgs;
+ }
+
+ /**
+ * Get the total number of unread messages in this Folder.
+ *
+ * This method can be invoked on a closed folder. However, note
+ * that for some folder implementations, getting the unread message
+ * count can be an expensive operation involving actually opening
+ * the folder. In such cases, a provider can choose not to support
+ * this functionality in the closed state, in which case this method
+ * must return -1.
+ *
+ * Clients invoking this method on a closed folder must be aware
+ * that this is a potentially expensive operation. Clients must
+ * also be prepared to handle a return value of -1 in this case.
+ *
+ * This implementation returns -1 if this folder is closed. Else
+ * this implementation gets each Message in the folder using
+ * getMessage(int)
and checks whether its
+ * SEEN
flag is set. The total number of messages
+ * that do not have this flag set is returned.
+ *
+ * @return total number of unread messages. -1 may be returned
+ * by certain implementations if this method is
+ * invoked on a closed folder.
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ */
+ public synchronized int getUnreadMessageCount()
+ throws MessagingException {
+ if (!isOpen())
+ return -1;
+
+ int unread = 0;
+ int total = getMessageCount();
+ for (int i = 1; i <= total; i++) {
+ try {
+ if (!getMessage(i).isSet(Flags.Flag.SEEN))
+ unread++;
+ } catch (MessageRemovedException me) {
+ // This is an expunged message, ignore it.
+ continue;
+ }
+ }
+ return unread;
+ }
+
+ /**
+ * Get the number of deleted messages in this Folder.
+ *
+ * This method can be invoked on a closed folder. However, note
+ * that for some folder implementations, getting the deleted message
+ * count can be an expensive operation involving actually opening
+ * the folder. In such cases, a provider can choose not to support
+ * this functionality in the closed state, in which case this method
+ * must return -1.
+ *
+ * Clients invoking this method on a closed folder must be aware
+ * that this is a potentially expensive operation. Clients must
+ * also be prepared to handle a return value of -1 in this case.
+ *
+ * This implementation returns -1 if this folder is closed. Else
+ * this implementation gets each Message in the folder using
+ * getMessage(int)
and checks whether its
+ * DELETED
flag is set. The total number of messages
+ * that have this flag set is returned.
+ *
+ * @return number of deleted messages. -1 may be returned
+ * by certain implementations if this method is
+ * invoked on a closed folder.
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException for other failures
+ * @since JavaMail 1.3
+ */
+ public synchronized int getDeletedMessageCount() throws MessagingException {
+ if (!isOpen())
+ return -1;
+
+ int deleted = 0;
+ int total = getMessageCount();
+ for (int i = 1; i <= total; i++) {
+ try {
+ if (getMessage(i).isSet(Flags.Flag.DELETED))
+ deleted++;
+ } catch (MessageRemovedException me) {
+ // This is an expunged message, ignore it.
+ continue;
+ }
+ }
+ return deleted;
+ }
+
+ /**
+ * Get the Message object corresponding to the given message
+ * number. A Message object's message number is the relative
+ * position of this Message in its Folder. Messages are numbered
+ * starting at 1 through the total number of message in the folder.
+ * Note that the message number for a particular Message can change
+ * during a session if other messages in the Folder are deleted and
+ * the Folder is expunged.
+ *
+ * Message objects are light-weight references to the actual message
+ * that get filled up on demand. Hence Folder implementations are
+ * expected to provide light-weight Message objects.
+ *
+ * Unlike Folder objects, repeated calls to getMessage with the
+ * same message number will return the same Message object, as
+ * long as no messages in this folder have been expunged.
+ *
+ * Since message numbers can change within a session if the folder
+ * is expunged , clients are advised not to use message numbers as
+ * references to messages. Use Message objects instead.
+ *
+ * @param msgnum the message number
+ * @return the Message object
+ * @see #getMessageCount
+ * @see #fetch
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not opened
+ * @exception IndexOutOfBoundsException if the message number
+ * is out of range.
+ * @exception MessagingException for other failures
+ */
+ public abstract Message getMessage(int msgnum)
+ throws MessagingException;
+
+ /**
+ * Get the Message objects for message numbers ranging from start
+ * through end, both start and end inclusive. Note that message
+ * numbers start at 1, not 0.
+ *
+ * Message objects are light-weight references to the actual message
+ * that get filled up on demand. Hence Folder implementations are
+ * expected to provide light-weight Message objects.
+ *
+ * This implementation uses getMessage(index) to obtain the required
+ * Message objects. Note that the returned array must contain
+ * (end-start+1)
Message objects.
+ *
+ * @param start the number of the first message
+ * @param end the number of the last message
+ * @return the Message objects
+ * @see #fetch
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception IndexOutOfBoundsException if the start or end
+ * message numbers are out of range.
+ * @exception MessagingException for other failures
+ */
+ public synchronized Message[] getMessages(int start, int end)
+ throws MessagingException {
+ Message[] msgs = new Message[end - start +1];
+ for (int i = start; i <= end; i++)
+ msgs[i - start] = getMessage(i);
+ return msgs;
+ }
+
+ /**
+ * Get the Message objects for message numbers specified in
+ * the array.
+ *
+ * Message objects are light-weight references to the actual message
+ * that get filled up on demand. Hence Folder implementations are
+ * expected to provide light-weight Message objects.
+ *
+ * This implementation uses getMessage(index) to obtain the required
+ * Message objects. Note that the returned array must contain
+ * msgnums.length
Message objects
+ *
+ * @param msgnums the array of message numbers
+ * @return the array of Message objects.
+ * @see #fetch
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception IndexOutOfBoundsException if any message number
+ * in the given array is out of range.
+ * @exception MessagingException for other failures
+ */
+ public synchronized Message[] getMessages(int[] msgnums)
+ throws MessagingException {
+ int len = msgnums.length;
+ Message[] msgs = new Message[len];
+ for (int i = 0; i < len; i++)
+ msgs[i] = getMessage(msgnums[i]);
+ return msgs;
+ }
+
+ /**
+ * Get all Message objects from this Folder. Returns an empty array
+ * if the folder is empty.
+ *
+ * Clients can use Message objects (instead of sequence numbers)
+ * as references to the messages within a folder; this method supplies
+ * the Message objects to the client. Folder implementations are
+ * expected to provide light-weight Message objects, which get
+ * filled on demand.
+ *
+ * This implementation invokes getMessageCount()
to get
+ * the current message count and then uses getMessage()
+ * to get Message objects from 1 till the message count.
+ *
+ * @return array of Message objects, empty array if folder
+ * is empty.
+ * @see #fetch
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception MessagingException for other failures
+ */
+ public synchronized Message[] getMessages() throws MessagingException {
+ if (!isOpen()) // otherwise getMessageCount might return -1
+ throw new IllegalStateException("Folder not open");
+ int total = getMessageCount();
+ Message[] msgs = new Message[total];
+ for (int i = 1; i <= total; i++)
+ msgs[i-1] = getMessage(i);
+ return msgs;
+ }
+
+ /**
+ * Append given Messages to this folder. This method can be
+ * invoked on a closed Folder. An appropriate MessageCountEvent
+ * is delivered to any MessageCountListener registered on this
+ * folder when the messages arrive in the folder.
+ *
+ * Folder implementations must not abort this operation if a
+ * Message in the given message array turns out to be an
+ * expunged Message.
+ *
+ * @param msgs array of Messages to be appended
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception MessagingException if the append failed.
+ */
+ public abstract void appendMessages(Message[] msgs)
+ throws MessagingException;
+
+ /**
+ * Prefetch the items specified in the FetchProfile for the
+ * given Messages.
+ *
+ * Clients use this method to indicate that the specified items are
+ * needed en-masse for the given message range. Implementations are
+ * expected to retrieve these items for the given message range in
+ * a efficient manner. Note that this method is just a hint to the
+ * implementation to prefetch the desired items.
+ *
+ * An example is a client filling its header-view window with
+ * the Subject, From and X-mailer headers for all messages in the
+ * folder.
+ *
+ *
+ * Message[] msgs = folder.getMessages();
+ *
+ * FetchProfile fp = new FetchProfile();
+ * fp.add(FetchProfile.Item.ENVELOPE);
+ * fp.add("X-mailer");
+ * folder.fetch(msgs, fp);
+ *
+ * for (int i = 0; i < folder.getMessageCount(); i++) {
+ * display(msg[i].getFrom());
+ * display(msg[i].getSubject());
+ * display(msg[i].getHeader("X-mailer"));
+ * }
+ *
+ *
+ *
+ * The implementation provided here just returns without
+ * doing anything useful. Providers wanting to provide a real
+ * implementation for this method should override this method.
+ *
+ * @param msgs fetch items for these messages
+ * @param fp the FetchProfile
+ * @exception IllegalStateException if this folder is not opened
+ * @exception MessagingException for other failures
+ */
+ public void fetch(Message[] msgs, FetchProfile fp)
+ throws MessagingException {
+ return;
+ }
+
+ /**
+ * Set the specified flags on the messages specified in the array.
+ * This will result in appropriate MessageChangedEvents being
+ * delivered to any MessageChangedListener registered on this
+ * Message's containing folder.
+ *
+ * Note that the specified Message objects must
+ * belong to this folder. Certain Folder implementations can
+ * optimize the operation of setting Flags for a group of messages,
+ * so clients might want to use this method, rather than invoking
+ * Message.setFlags
for each Message.
+ *
+ * This implementation degenerates to invoking setFlags()
+ * on each Message object. Specific Folder implementations that can
+ * optimize this case should do so.
+ * Also, an implementation must not abort the operation if a Message
+ * in the array turns out to be an expunged Message.
+ *
+ * @param msgs the array of message objects
+ * @param flag Flags object containing the flags to be set
+ * @param value set the flags to this boolean value
+ * @exception IllegalStateException if this folder is not opened
+ * or if it has been opened READ_ONLY.
+ * @exception MessagingException for other failures
+ * @see Message#setFlags
+ * @see javax.mail.event.MessageChangedEvent
+ */
+ public synchronized void setFlags(Message[] msgs,
+ Flags flag, boolean value) throws MessagingException {
+ for (int i = 0; i < msgs.length; i++) {
+ try {
+ msgs[i].setFlags(flag, value);
+ } catch (MessageRemovedException me) {
+ // This message is expunged, skip
+ }
+ }
+ }
+
+ /**
+ * Set the specified flags on the messages numbered from start
+ * through end, both start and end inclusive. Note that message
+ * numbers start at 1, not 0.
+ * This will result in appropriate MessageChangedEvents being
+ * delivered to any MessageChangedListener registered on this
+ * Message's containing folder.
+ *
+ * Certain Folder implementations can
+ * optimize the operation of setting Flags for a group of messages,
+ * so clients might want to use this method, rather than invoking
+ * Message.setFlags
for each Message.
+ *
+ * The default implementation uses getMessage(int)
to
+ * get each Message
object and then invokes
+ * setFlags
on that object to set the flags.
+ * Specific Folder implementations that can optimize this case should do so.
+ * Also, an implementation must not abort the operation if a message
+ * number refers to an expunged message.
+ *
+ * @param start the number of the first message
+ * @param end the number of the last message
+ * @param flag Flags object containing the flags to be set
+ * @param value set the flags to this boolean value
+ * @exception IllegalStateException if this folder is not opened
+ * or if it has been opened READ_ONLY.
+ * @exception IndexOutOfBoundsException if the start or end
+ * message numbers are out of range.
+ * @exception MessagingException for other failures
+ * @see Message#setFlags
+ * @see javax.mail.event.MessageChangedEvent
+ */
+ public synchronized void setFlags(int start, int end,
+ Flags flag, boolean value) throws MessagingException {
+ for (int i = start; i <= end; i++) {
+ try {
+ Message msg = getMessage(i);
+ msg.setFlags(flag, value);
+ } catch (MessageRemovedException me) {
+ // This message is expunged, skip
+ }
+ }
+ }
+
+ /**
+ * Set the specified flags on the messages whose message numbers
+ * are in the array.
+ * This will result in appropriate MessageChangedEvents being
+ * delivered to any MessageChangedListener registered on this
+ * Message's containing folder.
+ *
+ * Certain Folder implementations can
+ * optimize the operation of setting Flags for a group of messages,
+ * so clients might want to use this method, rather than invoking
+ * Message.setFlags
for each Message.
+ *
+ * The default implementation uses getMessage(int)
to
+ * get each Message
object and then invokes
+ * setFlags
on that object to set the flags.
+ * Specific Folder implementations that can optimize this case should do so.
+ * Also, an implementation must not abort the operation if a message
+ * number refers to an expunged message.
+ *
+ * @param msgnums the array of message numbers
+ * @param flag Flags object containing the flags to be set
+ * @param value set the flags to this boolean value
+ * @exception IllegalStateException if this folder is not opened
+ * or if it has been opened READ_ONLY.
+ * @exception IndexOutOfBoundsException if any message number
+ * in the given array is out of range.
+ * @exception MessagingException for other failures
+ * @see Message#setFlags
+ * @see javax.mail.event.MessageChangedEvent
+ */
+ public synchronized void setFlags(int[] msgnums,
+ Flags flag, boolean value) throws MessagingException {
+ for (int i = 0; i < msgnums.length; i++) {
+ try {
+ Message msg = getMessage(msgnums[i]);
+ msg.setFlags(flag, value);
+ } catch (MessageRemovedException me) {
+ // This message is expunged, skip
+ }
+ }
+ }
+
+ /**
+ * Copy the specified Messages from this Folder into another
+ * Folder. This operation appends these Messages to the
+ * destination Folder. The destination Folder does not have to
+ * be opened. An appropriate MessageCountEvent
+ * is delivered to any MessageCountListener registered on the
+ * destination folder when the messages arrive in the folder.
+ *
+ * Note that the specified Message objects must
+ * belong to this folder. Folder implementations might be able
+ * to optimize this method by doing server-side copies.
+ *
+ * This implementation just invokes appendMessages()
+ * on the destination folder to append the given Messages. Specific
+ * folder implementations that support server-side copies should
+ * do so, if the destination folder's Store is the same as this
+ * folder's Store.
+ * Also, an implementation must not abort the operation if a
+ * Message in the array turns out to be an expunged Message.
+ *
+ * @param msgs the array of message objects
+ * @param folder the folder to copy the messages to
+ * @exception FolderNotFoundException if the destination
+ * folder does not exist.
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception MessagingException for other failures
+ * @see #appendMessages
+ */
+ public void copyMessages(Message[] msgs, Folder folder)
+ throws MessagingException {
+ if (!folder.exists())
+ throw new FolderNotFoundException(
+ folder.getFullName() + " does not exist",
+ folder);
+
+ folder.appendMessages(msgs);
+ }
+
+ /**
+ * Expunge (permanently remove) messages marked DELETED. Returns an
+ * array containing the expunged message objects. The
+ * getMessageNumber
method
+ * on each of these message objects returns that Message's original
+ * (that is, prior to the expunge) sequence number. A MessageCountEvent
+ * containing the expunged messages is delivered to any
+ * MessageCountListeners registered on the folder.
+ *
+ * Expunge causes the renumbering of Message objects subsequent to
+ * the expunged messages. Clients that use message numbers as
+ * references to messages should be aware of this and should be
+ * prepared to deal with the situation (probably by flushing out
+ * existing message number caches and reloading them). Because of
+ * this complexity, it is better for clients to use Message objects
+ * as references to messages, rather than message numbers. Any
+ * expunged Messages objects still have to be pruned, but other
+ * Messages in that folder are not affected by the expunge.
+ *
+ * After a message is expunged, only the isExpunged
and
+ * getMessageNumber
methods are still valid on the
+ * corresponding Message object; other methods may throw
+ * MessageRemovedException
+ *
+ * @return array of expunged Message objects
+ * @exception FolderNotFoundException if this folder does not
+ * exist
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception MessagingException for other failures
+ * @see Message#isExpunged
+ * @see javax.mail.event.MessageCountEvent
+ */
+ public abstract Message[] expunge() throws MessagingException;
+
+ /**
+ * Search this Folder for messages matching the specified
+ * search criterion. Returns an array containing the matching
+ * messages . Returns an empty array if no matches were found.
+ *
+ * This implementation invokes
+ * search(term, getMessages())
, to apply the search
+ * over all the messages in this folder. Providers that can implement
+ * server-side searching might want to override this method to provide
+ * a more efficient implementation.
+ *
+ * @param term the search criterion
+ * @return array of matching messages
+ * @exception javax.mail.search.SearchException if the search
+ * term is too complex for the implementation to handle.
+ * @exception FolderNotFoundException if this folder does
+ * not exist.
+ * @exception IllegalStateException if this folder is not opened.
+ * @exception MessagingException for other failures
+ * @see javax.mail.search.SearchTerm
+ */
+ public Message[] search(SearchTerm term) throws MessagingException {
+ return search(term, getMessages());
+ }
+
+ /**
+ * Search the given array of messages for those that match the
+ * specified search criterion. Returns an array containing the
+ * matching messages. Returns an empty array if no matches were
+ * found.
+ *
+ * Note that the specified Message objects must
+ * belong to this folder.
+ *
+ * This implementation iterates through the given array of messages,
+ * and applies the search criterion on each message by calling
+ * its match()
method with the given term. The
+ * messages that succeed in the match are returned. Providers
+ * that can implement server-side searching might want to override
+ * this method to provide a more efficient implementation. If the
+ * search term is too complex or contains user-defined terms that
+ * cannot be executed on the server, providers may elect to either
+ * throw a SearchException or degenerate to client-side searching by
+ * calling super.search()
to invoke this implementation.
+ *
+ * @param term the search criterion
+ * @param msgs the messages to be searched
+ * @return array of matching messages
+ * @exception javax.mail.search.SearchException if the search
+ * term is too complex for the implementation to handle.
+ * @exception IllegalStateException if this folder is not opened
+ * @exception MessagingException for other failures
+ * @see javax.mail.search.SearchTerm
+ */
+ public Message[] search(SearchTerm term, Message[] msgs)
+ throws MessagingException {
+ List matchedMsgs = new ArrayList<>();
+
+ // Run thru the given messages
+ for (Message msg : msgs) {
+ try {
+ if (msg.match(term)) // matched
+ matchedMsgs.add(msg); // add it
+ } catch(MessageRemovedException mrex) { }
+ }
+
+ return matchedMsgs.toArray(new Message[matchedMsgs.size()]);
+ }
+
+ /*
+ * The set of listeners are stored in Vectors appropriate to their
+ * type. We mark all listener Vectors as "volatile" because, while
+ * we initialize them inside this folder's synchronization lock,
+ * they are accessed (checked for null) in the "notify" methods,
+ * which can't be synchronized due to lock ordering constraints.
+ * Since the listener fields (the handles on the Vector objects)
+ * are only ever set, and are never cleared, we believe this is
+ * safe. The code that dispatches the notifications will either
+ * see the null and assume there are no listeners or will see the
+ * Vector and will process the listeners. There's an inherent race
+ * between adding a listener and notifying the listeners; the lack
+ * of synchronization during notification does not make the race
+ * condition significantly worse. If one thread is setting a
+ * listener at the "same" time an event is being dispatched, the
+ * dispatch code might not see the listener right away. The
+ * dispatch code doesn't have to worry about the Vector handle
+ * being set to null, and thus using an out-of-date set of
+ * listeners, because we never set the field to null.
+ */
+
+ // Vector of connection listeners.
+ private volatile Vector connectionListeners = null;
+
+ /**
+ * Add a listener for Connection events on this Folder.
+ *
+ * The implementation provided here adds this listener
+ * to an internal list of ConnectionListeners.
+ *
+ * @param l the Listener for Connection events
+ * @see javax.mail.event.ConnectionEvent
+ */
+ public synchronized void
+ addConnectionListener(ConnectionListener l) {
+ if (connectionListeners == null)
+ connectionListeners = new Vector<>();
+ connectionListeners.addElement(l);
+ }
+
+ /**
+ * Remove a Connection event listener.
+ *
+ * The implementation provided here removes this listener
+ * from the internal list of ConnectionListeners.
+ *
+ * @param l the listener
+ * @see #addConnectionListener
+ */
+ public synchronized void
+ removeConnectionListener(ConnectionListener l) {
+ if (connectionListeners != null)
+ connectionListeners.removeElement(l);
+ }
+
+ /**
+ * Notify all ConnectionListeners. Folder implementations are
+ * expected to use this method to broadcast connection events.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the registered
+ * ConnectionListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param type the ConnectionEvent type
+ * @see javax.mail.event.ConnectionEvent
+ */
+ protected void notifyConnectionListeners(int type) {
+ if (connectionListeners != null) {
+ ConnectionEvent e = new ConnectionEvent(this, type);
+ queueEvent(e, connectionListeners);
+ }
+
+ /* Fix for broken JDK1.1.x Garbage collector :
+ * The 'conservative' GC in JDK1.1.x occasionally fails to
+ * garbage-collect Threads which are in the wait state.
+ * This would result in thread (and consequently memory) leaks.
+ *
+ * We attempt to fix this by sending a 'terminator' event
+ * to the queue, after we've sent the CLOSED event. The
+ * terminator event causes the event-dispatching thread to
+ * self destruct.
+ */
+ if (type == ConnectionEvent.CLOSED)
+ q.terminateQueue();
+ }
+
+ // Vector of folder listeners
+ private volatile Vector folderListeners = null;
+
+ /**
+ * Add a listener for Folder events on this Folder.
+ *
+ * The implementation provided here adds this listener
+ * to an internal list of FolderListeners.
+ *
+ * @param l the Listener for Folder events
+ * @see javax.mail.event.FolderEvent
+ */
+ public synchronized void addFolderListener(FolderListener l) {
+ if (folderListeners == null)
+ folderListeners = new Vector<>();
+ folderListeners.addElement(l);
+ }
+
+ /**
+ * Remove a Folder event listener.
+ *
+ * The implementation provided here removes this listener
+ * from the internal list of FolderListeners.
+ *
+ * @param l the listener
+ * @see #addFolderListener
+ */
+ public synchronized void removeFolderListener(FolderListener l) {
+ if (folderListeners != null)
+ folderListeners.removeElement(l);
+ }
+
+ /**
+ * Notify all FolderListeners registered on this Folder and
+ * this folder's Store. Folder implementations are expected
+ * to use this method to broadcast Folder events.
+ *
+ * The implementation provided here queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the
+ * FolderListeners registered on this folder. The implementation
+ * also invokes notifyFolderListeners
on this folder's
+ * Store to notify any FolderListeners registered on the store.
+ *
+ * @param type type of FolderEvent
+ * @see #notifyFolderRenamedListeners
+ */
+ protected void notifyFolderListeners(int type) {
+ if (folderListeners != null) {
+ FolderEvent e = new FolderEvent(this, this, type);
+ queueEvent(e, folderListeners);
+ }
+ store.notifyFolderListeners(type, this);
+ }
+
+ /**
+ * Notify all FolderListeners registered on this Folder and
+ * this folder's Store about the renaming of this folder.
+ * Folder implementations are expected to use this method to
+ * broadcast Folder events indicating the renaming of folders.
+ *
+ * The implementation provided here queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the
+ * FolderListeners registered on this folder. The implementation
+ * also invokes notifyFolderRenamedListeners
on this
+ * folder's Store to notify any FolderListeners registered on the store.
+ *
+ * @param folder Folder representing the new name.
+ * @see #notifyFolderListeners
+ * @since JavaMail 1.1
+ */
+ protected void notifyFolderRenamedListeners(Folder folder) {
+ if (folderListeners != null) {
+ FolderEvent e = new FolderEvent(this, this, folder,
+ FolderEvent.RENAMED);
+ queueEvent(e, folderListeners);
+ }
+ store.notifyFolderRenamedListeners(this, folder);
+ }
+
+ // Vector of MessageCount listeners
+ private volatile Vector messageCountListeners = null;
+
+ /**
+ * Add a listener for MessageCount events on this Folder.
+ *
+ * The implementation provided here adds this listener
+ * to an internal list of MessageCountListeners.
+ *
+ * @param l the Listener for MessageCount events
+ * @see javax.mail.event.MessageCountEvent
+ */
+ public synchronized void addMessageCountListener(MessageCountListener l) {
+ if (messageCountListeners == null)
+ messageCountListeners = new Vector<>();
+ messageCountListeners.addElement(l);
+ }
+
+ /**
+ * Remove a MessageCount listener.
+ *
+ * The implementation provided here removes this listener
+ * from the internal list of MessageCountListeners.
+ *
+ * @param l the listener
+ * @see #addMessageCountListener
+ */
+ public synchronized void
+ removeMessageCountListener(MessageCountListener l) {
+ if (messageCountListeners != null)
+ messageCountListeners.removeElement(l);
+ }
+
+ /**
+ * Notify all MessageCountListeners about the addition of messages
+ * into this folder. Folder implementations are expected to use this
+ * method to broadcast MessageCount events for indicating arrival of
+ * new messages.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the registered
+ * MessageCountListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param msgs the messages that were added
+ */
+ protected void notifyMessageAddedListeners(Message[] msgs) {
+ if (messageCountListeners == null)
+ return;
+
+ MessageCountEvent e = new MessageCountEvent(
+ this,
+ MessageCountEvent.ADDED,
+ false,
+ msgs);
+
+ queueEvent(e, messageCountListeners);
+ }
+
+ /**
+ * Notify all MessageCountListeners about the removal of messages
+ * from this Folder. Folder implementations are expected to use this
+ * method to broadcast MessageCount events indicating removal of
+ * messages.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the registered
+ * MessageCountListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param removed was the message removed by this client?
+ * @param msgs the messages that were removed
+ */
+ protected void notifyMessageRemovedListeners(boolean removed,
+ Message[] msgs) {
+ if (messageCountListeners == null)
+ return;
+
+ MessageCountEvent e = new MessageCountEvent(
+ this,
+ MessageCountEvent.REMOVED,
+ removed,
+ msgs);
+ queueEvent(e, messageCountListeners);
+ }
+
+ // Vector of MessageChanged listeners.
+ private volatile Vector