Build JavaMail inline

pull/175/head
M66B 4 years ago
parent 4d3d13f152
commit 36ae2b16ee

@ -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/

@ -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;
}
}

@ -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();
}
}

@ -0,0 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
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
-->
<TITLE>com.sun.mail.auth package</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<P>
This package includes internal authentication support classes and
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
</P>
</BODY>
</HTML>

@ -0,0 +1,77 @@
/*
* 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.handlers;
import java.io.IOException;
import javax.activation.*;
/**
* Base class for other DataContentHandlers.
*/
public abstract class handler_base implements DataContentHandler {
/**
* Return an array of ActivationDataFlavors that we support.
* Usually there will be only one.
*
* @return array of ActivationDataFlavors that we support
*/
protected abstract ActivationDataFlavor[] getDataFlavors();
/**
* Given the flavor that matched, return the appropriate type of object.
* Usually there's only one flavor so just call getContent.
*
* @param aFlavor the ActivationDataFlavor
* @param ds DataSource containing the data
* @return the object
* @exception IOException for errors reading the data
*/
protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
throws IOException {
return getContent(ds);
}
/**
* Return the DataFlavors for this <code>DataContentHandler</code>.
*
* @return The DataFlavors
*/
public ActivationDataFlavor[] getTransferDataFlavors() {
return getDataFlavors().clone();
}
/**
* Return the Transfer Data of type DataFlavor from InputStream.
*
* @param df The DataFlavor
* @param ds The DataSource corresponding to the data
* @return the object
* @exception IOException for errors reading the data
*/
public Object getTransferData(ActivationDataFlavor df, DataSource ds)
throws IOException {
ActivationDataFlavor[] adf = getDataFlavors();
for (int i = 0; i < adf.length; i++) {
// use ActivationDataFlavor.equals, which properly
// ignores Content-Type parameters in comparison
if (adf[i].equals(df))
return getData(adf[i], ds);
}
return null;
}
}

@ -0,0 +1,93 @@
/*
* 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.handlers;
import java.io.*;
import java.util.Properties;
import javax.activation.*;
import javax.mail.*;
import javax.mail.internet.*;
/**
* @author Christopher Cotton
*/
public class message_rfc822 extends handler_base {
private static ActivationDataFlavor[] ourDataFlavor = {
new ActivationDataFlavor(Message.class, "message/rfc822", "Message")
};
@Override
protected ActivationDataFlavor[] getDataFlavors() {
return ourDataFlavor;
}
/**
* Return the content.
*/
@Override
public Object getContent(DataSource ds) throws IOException {
// create a new MimeMessage
try {
Session session;
if (ds instanceof MessageAware) {
MessageContext mc = ((MessageAware)ds).getMessageContext();
session = mc.getSession();
} else {
// Hopefully a rare case. Also hopefully the application
// has created a default Session that can just be returned
// here. If not, the one we create here is better than
// nothing, but overall not a really good answer.
session = Session.getDefaultInstance(new Properties(), null);
}
return new MimeMessage(session, ds.getInputStream());
} catch (MessagingException me) {
IOException ioex =
new IOException("Exception creating MimeMessage in " +
"message/rfc822 DataContentHandler");
ioex.initCause(me);
throw ioex;
}
}
/**
* Write the object as a byte stream.
*/
@Override
public void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException {
if (!(obj instanceof Message))
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
"\" DataContentHandler requires Message object, " +
"was given object of type " + obj.getClass().toString() +
"; obj.cl " + obj.getClass().getClassLoader() +
", Message.cl " + Message.class.getClassLoader());
// if the object is a message, we know how to write that out
Message m = (Message)obj;
try {
m.writeTo(os);
} catch (MessagingException me) {
IOException ioex = new IOException("Exception writing message");
ioex.initCause(me);
throw ioex;
}
}
}

@ -0,0 +1,74 @@
/*
* 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.handlers;
import java.io.*;
import javax.activation.*;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeMultipart;
public class multipart_mixed extends handler_base {
private static ActivationDataFlavor[] myDF = {
new ActivationDataFlavor(Multipart.class,
"multipart/mixed", "Multipart")
};
@Override
protected ActivationDataFlavor[] getDataFlavors() {
return myDF;
}
/**
* Return the content.
*/
@Override
public Object getContent(DataSource ds) throws IOException {
try {
return new MimeMultipart(ds);
} catch (MessagingException e) {
IOException ioex =
new IOException("Exception while constructing MimeMultipart");
ioex.initCause(e);
throw ioex;
}
}
/**
* Write the object to the output stream, using the specific MIME type.
*/
@Override
public void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException {
if (!(obj instanceof Multipart))
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
"\" DataContentHandler requires Multipart object, " +
"was given object of type " + obj.getClass().toString() +
"; obj.cl " + obj.getClass().getClassLoader() +
", Multipart.cl " + Multipart.class.getClassLoader());
try {
((Multipart)obj).writeTo(os);
} catch (MessagingException e) {
IOException ioex =
new IOException("Exception writing Multipart");
ioex.initCause(e);
throw ioex;
}
}
}

@ -0,0 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
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
-->
<TITLE>com.sun.mail.handlers package</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<P>
This package includes internal data handler support classes and
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
</P>
</BODY>
</HTML>

@ -0,0 +1,34 @@
/*
* 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.handlers;
import javax.activation.ActivationDataFlavor;
/**
* DataContentHandler for text/html.
*
*/
public class text_html extends text_plain {
private static ActivationDataFlavor[] myDF = {
new ActivationDataFlavor(String.class, "text/html", "HTML String")
};
@Override
protected ActivationDataFlavor[] getDataFlavors() {
return myDF;
}
}

@ -0,0 +1,153 @@
/*
* 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.handlers;
import java.io.*;
import javax.activation.*;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeUtility;
/**
* DataContentHandler for text/plain.
*
*/
public class text_plain extends handler_base {
private static ActivationDataFlavor[] myDF = {
new ActivationDataFlavor(String.class, "text/plain", "Text String")
};
/**
* An OuputStream wrapper that doesn't close the underlying stream.
*/
private static class NoCloseOutputStream extends FilterOutputStream {
public NoCloseOutputStream(OutputStream os) {
super(os);
}
@Override
public void close() {
// do nothing
}
}
@Override
protected ActivationDataFlavor[] getDataFlavors() {
return myDF;
}
@Override
public Object getContent(DataSource ds) throws IOException {
String enc = null;
InputStreamReader is = null;
try {
enc = getCharset(ds.getContentType());
is = new InputStreamReader(ds.getInputStream(), enc);
} catch (IllegalArgumentException iex) {
/*
* An unknown charset of the form ISO-XXX-XXX will cause
* the JDK to throw an IllegalArgumentException. The
* JDK will attempt to create a classname using this string,
* but valid classnames must not contain the character '-',
* and this results in an IllegalArgumentException, rather than
* the expected UnsupportedEncodingException. Yikes.
*/
throw new UnsupportedEncodingException(enc);
}
try {
int pos = 0;
int count;
char buf[] = new char[1024];
while ((count = is.read(buf, pos, buf.length - pos)) != -1) {
pos += count;
if (pos >= buf.length) {
int size = buf.length;
if (size < 256*1024)
size += size;
else
size += 256*1024;
char tbuf[] = new char[size];
System.arraycopy(buf, 0, tbuf, 0, pos);
buf = tbuf;
}
}
return new String(buf, 0, pos);
} finally {
try {
is.close();
} catch (IOException ex) {
// ignore it
}
}
}
/**
* Write the object to the output stream, using the specified MIME type.
*/
@Override
public void writeTo(Object obj, String type, OutputStream os)
throws IOException {
if (!(obj instanceof String))
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
"\" DataContentHandler requires String object, " +
"was given object of type " + obj.getClass().toString());
String enc = null;
OutputStreamWriter osw = null;
try {
enc = getCharset(type);
osw = new OutputStreamWriter(new NoCloseOutputStream(os), enc);
} catch (IllegalArgumentException iex) {
/*
* An unknown charset of the form ISO-XXX-XXX will cause
* the JDK to throw an IllegalArgumentException. The
* JDK will attempt to create a classname using this string,
* but valid classnames must not contain the character '-',
* and this results in an IllegalArgumentException, rather than
* the expected UnsupportedEncodingException. Yikes.
*/
throw new UnsupportedEncodingException(enc);
}
String s = (String)obj;
osw.write(s, 0, s.length());
/*
* Have to call osw.close() instead of osw.flush() because
* some charset converts, such as the iso-2022-jp converter,
* don't output the "shift out" sequence unless they're closed.
* The NoCloseOutputStream wrapper prevents the underlying
* stream from being closed.
*/
osw.close();
}
private String getCharset(String type) {
try {
ContentType ct = new ContentType(type);
String charset = ct.getParameter("charset");
if (charset == null)
// If the charset parameter is absent, use US-ASCII.
charset = "us-ascii";
return MimeUtility.javaCharset(charset);
} catch (Exception ex) {
return null;
}
}
}

@ -0,0 +1,121 @@
/*
* 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.handlers;
import java.io.IOException;
import java.io.OutputStream;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataSource;
import javax.mail.internet.ContentType;
import javax.mail.internet.ParseException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* DataContentHandler for text/xml.
*
* @author Anil Vijendran
* @author Bill Shannon
*/
public class text_xml extends text_plain {
private static final ActivationDataFlavor[] flavors = {
new ActivationDataFlavor(String.class, "text/xml", "XML String"),
new ActivationDataFlavor(String.class, "application/xml", "XML String"),
new ActivationDataFlavor(StreamSource.class, "text/xml", "XML"),
new ActivationDataFlavor(StreamSource.class, "application/xml", "XML")
};
@Override
protected ActivationDataFlavor[] getDataFlavors() {
return flavors;
}
@Override
protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
throws IOException {
if (aFlavor.getRepresentationClass() == String.class)
return super.getContent(ds);
else if (aFlavor.getRepresentationClass() == StreamSource.class)
return new StreamSource(ds.getInputStream());
else
return null; // XXX - should never happen
}
/**
*/
@Override
public void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException {
if (!isXmlType(mimeType))
throw new IOException(
"Invalid content type \"" + mimeType + "\" for text/xml DCH");
if (obj instanceof String) {
super.writeTo(obj, mimeType, os);
return;
}
if (!(obj instanceof DataSource || obj instanceof Source)) {
throw new IOException("Invalid Object type = "+obj.getClass()+
". XmlDCH can only convert DataSource or Source to XML.");
}
try {
Transformer transformer =
TransformerFactory.newInstance().newTransformer();
StreamResult result = new StreamResult(os);
if (obj instanceof DataSource) {
// Streaming transform applies only to
// javax.xml.transform.StreamSource
transformer.transform(
new StreamSource(((DataSource)obj).getInputStream()),
result);
} else {
transformer.transform((Source)obj, result);
}
} catch (TransformerException ex) {
IOException ioex = new IOException(
"Unable to run the JAXP transformer on a stream "
+ ex.getMessage());
ioex.initCause(ex);
throw ioex;
} catch (RuntimeException ex) {
IOException ioex = new IOException(
"Unable to run the JAXP transformer on a stream "
+ ex.getMessage());
ioex.initCause(ex);
throw ioex;
}
}
private boolean isXmlType(String type) {
try {
ContentType ct = new ContentType(type);
return ct.getSubType().equals("xml") &&
(ct.getPrimaryType().equals("text") ||
ct.getPrimaryType().equals("application"));
} catch (ParseException ex) {
return false;
} catch (RuntimeException ex) {
return false;
}
}
}

@ -0,0 +1,431 @@
/*
* 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.util.List;
import java.util.ArrayList;
import java.io.*;
import java.nio.charset.Charset;
import com.sun.mail.util.ASCIIUtility;
/**
* @author John Mani
* @author Bill Shannon
*/
public class Argument {
protected List<Object> 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. <p>
*
* 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. <p>
*
* @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;
}
}

@ -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);
}
}

@ -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;
}
}

@ -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);
}
}

@ -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;
}
}

@ -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;
}

@ -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;
}
}

@ -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);
}
}

@ -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. <p>
*
* 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<ResponseHandler> 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. <p>
*
* 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<Response> 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.
* <code>cmd</code> 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.
* <code>cmd</code> 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 &lt;prefix&gt;.localhost overrides
* &lt;prefix&gt;.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() { }
}

@ -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;
}
}

@ -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<String> 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);
}
}

@ -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);
}

@ -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 : {<digits>}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();
}
}

@ -0,0 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
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
-->
<TITLE>com.sun.mail.iap package</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<P>
This package includes internal IMAP support classes and
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
</P>
</BODY>
</HTML>

@ -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.
* <p>
*
* @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;
}
}

@ -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 -
* <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
*
* @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;
}
}

@ -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 -
* <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
*
* @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;
}
}

@ -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");
}
}

@ -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<String> 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<Header> getAllHeaders() throws MessagingException {
loadHeaders();
return super.getAllHeaders();
}
@Override
public Enumeration<Header> getMatchingHeaders(String[] names)
throws MessagingException {
loadHeaders();
return super.getMatchingHeaders(names);
}
@Override
public Enumeration<Header> 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<String> getAllHeaderLines() throws MessagingException {
loadHeaders();
return super.getAllHeaderLines();
}
@Override
public Enumeration<String> getMatchingHeaderLines(String[] names)
throws MessagingException {
loadHeaders();
return super.getMatchingHeaderLines(names);
}
@Override
public Enumeration<String> 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;
}
}

File diff suppressed because it is too large Load Diff

@ -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 <code>-1</code> 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 <code>len</code> bytes of data from this
* input stream into the given buffer. <p>
*
* Returns the total number of bytes read into the buffer,
* or <code>-1</code> if there is no more data. <p>
*
* 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 <code>b.length</code> bytes of data from this input
* stream into an array of bytes. <p>
*
* Returns the total number of bytes read into the buffer, or
* <code>-1</code> is there is no more data. <p>
*
* 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
}
}
}

File diff suppressed because it is too large Load Diff

@ -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<IMAPBodyPart> 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);
}
}

@ -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. <p>
*
* 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");
}
}

@ -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);
}
}

@ -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);
}
}

@ -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
}
}

File diff suppressed because it is too large Load Diff

@ -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
* (<A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>)
* 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:
* <blockquote><pre>
* ExecutorService es = Executors.newCachedThreadPool();
* final IdleManager idleManager = new IdleManager(session, es);
* </pre></blockquote>
* For a Java EE 7 application:
* <blockquote><pre>
* {@literal @}Resource
* ManagedExecutorService es;
* final IdleManager idleManager = new IdleManager(session, es);
* </pre></blockquote>
* To watch for new messages in a folder, open the folder, register a listener,
* and ask the IdleManager to watch the folder:
* <blockquote><pre>
* 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);
* </pre></blockquote>
* This delivers the events for each folder in a separate thread, <b>NOT</b>
* 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.
* <blockquote><pre>
* // the following should be done once...
* Properties props = session.getProperties();
* props.put("mail.event.scope", "session"); // or "application"
* props.put("mail.event.executor", es);
* </pre></blockquote>
* 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.
* <p>
* 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.
* <p>
* 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
* <code>mail.imap.usesocketchannels</code> property in the Session used to
* access the IMAP Folder. (Or <code>mail.imaps.usesocketchannels</code> 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.
* <p>
* NOTE: The IdleManager, and all APIs and properties related to it, should
* be considered <strong>EXPERIMENTAL</strong>. 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<IMAPFolder> toWatch = new ConcurrentLinkedQueue<>();
private Queue<IMAPFolder> 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<SelectionKey> 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<SelectionKey> 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<SelectionKey> 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();
}
}
}

@ -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<IMAPMessage> 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<IMAPMessage> 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;
}
}

@ -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;
}
}

@ -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
* (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
*
* @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;
}
}

@ -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
* (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
*
* @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;
}
}

@ -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 <a href="http://www.ietf.org/rfc/rfc2221.txt">RFC 2221</a> for details.
*
* @since JavaMail 1.5.5
*/
public class ReferralException extends AuthenticationFailedException {
private String url;
private String text;
private static final long serialVersionUID = -3414063558596287683L;
/**
* Constructs an ReferralException with the specified URL and text.
*
* @param text the detail message
* @param url the URL
*/
public ReferralException(String url, String text) {
super("[REFERRAL " + url + "] " + text);
this.url = url;
this.text = text;
}
/**
* Return the IMAP URL in the referral.
*
* @return the IMAP URL
*/
public String getUrl() {
return url;
}
/**
* Return the text sent by the server along with the referral.
*
* @return the text
*/
public String getText() {
return text;
}
}

@ -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
* (<A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>).
* An instance of <CODE>ResyncData</CODE> is supplied to the
* {@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
* IMAPFolder open} method.
* The CONDSTORE <CODE>ResyncData</CODE> instance is used to enable the
* CONDSTORE extension
* (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
* A <CODE>ResyncData</CODE> 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;
}
}

@ -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). <p>
*
* A right is represented by the <code>Rights.Right</code>
* inner class. <p>
*
* 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. <p>
*
* The following code sample illustrates how to examine your
* rights for a folder.
* <pre>
*
* 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);
* </pre>
* <p>
*
* @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<Right> 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");
}
****/
}

@ -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
* <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
* 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;
}
}

@ -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<MessageSet> 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<Message>() {
@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<UIDSet> 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
* <code>toMessageSet()</code>.
*/
public static interface Condition {
public boolean test(IMAPMessage message);
}
}

@ -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
* (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
*
* @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;
}
}

File diff suppressed because it is too large Load Diff

@ -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;
}
}

@ -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:
*
* <blockquote><pre>
*
* 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 "&amp;"
* represent themselves; that is, characters with octet values 0x20-0x25
* and 0x27-0x7e. The character "&amp;" (0x26) is represented by the two-
* octet sequence "&amp;-".
*
* 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.
*
* "&amp;" 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/&amp;ZeVnLIqe-/&amp;U,BTFw-
*
* </pre></blockquote>
*
* 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
};
}

@ -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;
}
}

@ -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<BODYSTRUCTURE> 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();
}
}

@ -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<InternetAddress> 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<InternetAddress> 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();
}
}

@ -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);
}
}
}
}

@ -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. <p>
*
* @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;
}

@ -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<String, Object> 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 extends Item> T getItem(Class<T> 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 <T> the type of fetch item
* @return the fetch item
*/
public static <T extends Item> T getItem(Response[] r, int msgno,
Class<T> 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 <T> the type of fetch items
* @return the list of fetch items
* @since JavaMail 1.5.2
*/
public static <T extends Item> List<T> getItems(Response[] r, int msgno,
Class<T> c) {
List<T> 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<String, Object> 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<Item> 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;
}
}

@ -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. <p>
*
* See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
*
* @since JavaMail 1.5.1
* @author Bill Shannon
*/
public class ID {
private Map<String, String> 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<String, String> getServerParams() {
return serverParams;
}
/**
* Convert the client parameters into an argument list for the ID command.
*/
static Argument getArgumentList(Map<String,String> clientParams) {
Argument arg = new Argument();
if (clientParams == null) {
arg.writeAtom("NIL");
return arg;
}
Argument list = new Argument();
// add params to list
for (Map.Entry<String, String> e : clientParams.entrySet()) {
list.writeNString(e.getKey()); // assume these are ASCII only
list.writeNString(e.getValue());
}
arg.writeArgument(list);
return arg;
}
}

@ -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;
}
}

@ -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 "* <number> <command>"
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<String> 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;
}
}

@ -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();
}
}

@ -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. <p>
*
* See the BODY, BODYSTRUCTURE, ENVELOPE, FLAGS, INTERNALDATE, RFC822DATA,
* RFC822SIZE, and UID classes.
*
* @author John Mani
*/
public interface Item {
}

@ -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<String> 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);
}
}

@ -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");
}
}

@ -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<IMAPResponse> 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();
}
}
}

@ -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<MessageSet> 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;
}
}

@ -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. <p>
*
* See <A HREF="http://www.ietf.org/rfc/rfc2342.txt">RFC 2342</A>.
*
* @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<Namespace> 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;
}
}
}

@ -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;
}
}

@ -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();
}
}

@ -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;
}

@ -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;
}
}

@ -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<String,Long> 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);
}
}

@ -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();
}
}

@ -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<UIDSet> 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<UIDSet> 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;
}
}

@ -0,0 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
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
-->
<TITLE>com.sun.mail.imap.protocol package</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<P>
This package includes internal IMAP support classes and
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
</P>
</BODY>
</HTML>

@ -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);
}
}

@ -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");
}
}

@ -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 <a href="package-summary.html">com.sun.mail.pop3</a> package
* documentation for further information on the POP3 protocol provider. <p>
*
* @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 <code>MessagingException</code> 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 <code>false</code>; the POP3 protocol doesn't
* support creating folders.
*
* @return false
*/
@Override
public boolean create(int type) throws MessagingException {
return false;
}
/**
* Always returns <code>false</code>; 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 <code>MessagingException</code> 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 <code>MethodNotSupportedException</code>
* 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 <code>MethodNotSupportedException</code>
* 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 <code>FolderNotFoundException</code> 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 <code>Flags</code> 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 <code>MethodNotSupportedException</code>
* 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 <code>MethodNotSupportedException</code>
* because the POP3 protocol doesn't support expunging messages
* without closing the folder; call the {@link #close close} method
* with the <code>expunge</code> argument set to <code>true</code>
* 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 <code>UIDFolder.FetchProfileItem.UID</code>,
* POP3 UIDs for all messages in the folder are fetched using the POP3
* UIDL command.
* If the FetchProfile contains <code>FetchProfile.Item.ENVELOPE</code>,
* 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>i</i> corresponds to message number <i>i+1</i>.
*
* @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;
}
}

@ -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<InputStream> 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. <p>
*
* 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. <p>
*
* @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 <code>invalidateHeaders</code> 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 <code>n</code> 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. <p>
*
* @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 <code>null</code>, 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. <p>
*
* Note that certain headers may be encoded as per RFC 2047
* if they contain non US-ASCII characters and these should
* be decoded. <p>
*
* @return array of header objects
* @exception MessagingException for failures
* @see javax.mail.internet.MimeUtility
*/
@Override
public Enumeration<Header> 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<Header> 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<Header> 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<String> 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<String> 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<String> 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);
}
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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 <a href="package-summary.html">com.sun.mail.pop3</a> package
* documentation for further information on the POP3 protocol provider. <p>
*
* @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<String, String> 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. <p>
*
* For example, to check if the server supports the STLS capability, use:
* <code>if (store.capabilities().containsKey("STLS")) ...</code>
*
* @return Map of capabilities
* @exception MessagingException for failures
* @since JavaMail 1.4.3
*/
public Map<String, String> capabilities() throws MessagingException {
Map<String, String> c;
synchronized (this) {
c = capabilities;
}
if (c != null)
return Collections.unmodifiableMap(c);
else
return Collections.<String, String>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");
}
}

@ -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<String, String> 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 <code>true</code> 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<String, String> 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("<EOF>");
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.
* <code>size</code> 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() { }
}

@ -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
};

@ -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();
}
}
}

@ -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;
}
}

@ -0,0 +1,674 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--
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
-->
<TITLE>com.sun.mail.pop3 package</TITLE>
</HEAD>
<BODY BGCOLOR="white">
A POP3 protocol provider for the Jakarta Mail API
that provides access to a POP3 message store.
Refer to <A HREF="http://www.ietf.org/rfc/rfc1939.txt" TARGET="_top">
RFC 1939</A>
for more information.
<P>
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.
</P>
<P>
Note that Jakarta Mail does <strong>not</strong> include a local store into
which messages can be downloaded and stored. See our
<A HREF="https://eclipse-ee4j.github.io/jakartamail/ThirdPartyProducts" TARGET="_top">
Third Party Products</A>
web page for availability of "mbox" and "MH" local store providers.
</P>
<P>
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".
</P>
<P>
POP3 supports only a single folder named "INBOX".
</P>
<P>
POP3 supports <strong>no</strong> permanent flags (see
{@link javax.mail.Folder#getPermanentFlags Folder.getPermanentFlags()}).
In particular, the <code>Flags.Flag.RECENT</code> 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:
</P>
<UL>
<LI>
A simple approach would be to keep track of the newest
message seen by the application.
</LI>
<LI>
An alternative would be to keep track of the UIDs (see below)
of all messages that have been seen.
</LI>
<LI>
Another approach is to download <strong>all</strong> messages into a local
mailbox, so that all messages in the POP3 mailbox are, by
definition, new.
</LI>
</UL>
<P>
All approaches will require some permanent storage associated with the client.
</P>
<P>
POP3 does not support the <code>Folder.expunge()</code> method. To delete and
expunge messages, set the <code>Flags.Flag.DELETED</code> flag on the messages
and close the folder using the <code>Folder.close(true)</code> method. You
cannot expunge without closing the folder.
</P>
<P>
POP3 does not provide a "received date", so the <code>getReceivedDate</code>
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.
</P>
<P>
The POP3 provider supports the POP3 UIDL command, see
{@link com.sun.mail.pop3.POP3Folder#getUID POP3Folder.getUID()}.
You can use it as follows:
</P>
<BLOCKQUOTE><PRE>
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
}
</PRE></BLOCKQUOTE>
<P>
You can also pre-fetch all the UIDs for all messages like this:
</P>
<BLOCKQUOTE><PRE>
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
folder.fetch(folder.getMessages(), fp);
</PRE></BLOCKQUOTE>
<P>
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.
</P>
<P>
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 <CODE>mail.pop3.disabletop</CODE>
property, in which case the entire message content is fetched with the
RETR command.
</P>
<P>
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
<CODE>mail.pop3.filecache.enable</CODE> 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.
</P>
<P>
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.
</P>
<P>
The POP3 CAPA command (defined by
<A HREF="http://www.ietf.org/rfc/rfc2449.txt" TARGET="_top">RFC 2449</A>)
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.
</P>
<P>
If the server advertises the PIPELINING capability (defined by
<A HREF="http://www.ietf.org/rfc/rfc2449.txt" TARGET="_top">RFC 2449</A>),
or the <CODE>mail.pop3.pipelining</CODE> 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.
</P>
<P>
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.)
</P>
<P>
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.
</P>
<A ID="properties"><STRONG>Properties</STRONG></A>
<P>
The POP3 protocol provider supports the following properties,
which may be set in the Jakarta Mail <code>Session</code> object.
The properties are always set as strings; the Type column describes
how the string is interpreted. For example, use
</P>
<PRE>
props.put("mail.pop3.port", "888");
</PRE>
<P>
to set the <CODE>mail.pop3.port</CODE> property, which is of type int.
</P>
<P>
Note that if you're using the "pop3s" protocol to access POP3 over SSL,
all the properties would be named "mail.pop3s.*".
</P>
<TABLE BORDER="1">
<CAPTION>POP3 properties</CAPTION>
<TR>
<TH>Name</TH>
<TH>Type</TH>
<TH>Description</TH>
</TR>
<TR>
<TD><A ID="mail.pop3.user">mail.pop3.user</A></TD>
<TD>String</TD>
<TD>Default user name for POP3.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.host">mail.pop3.host</A></TD>
<TD>String</TD>
<TD>The POP3 server to connect to.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.port">mail.pop3.port</A></TD>
<TD>int</TD>
<TD>The POP3 server port to connect to, if the connect() method doesn't
explicitly specify one. Defaults to 110.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.connectiontimeout">mail.pop3.connectiontimeout</A></TD>
<TD>int</TD>
<TD>Socket connection timeout value in milliseconds.
This timeout is implemented by java.net.Socket.
Default is infinite timeout.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.timeout">mail.pop3.timeout</A></TD>
<TD>int</TD>
<TD>Socket read timeout value in milliseconds.
This timeout is implemented by java.net.Socket.
Default is infinite timeout.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.writetimeout">mail.pop3.writetimeout</A></TD>
<TD>int</TD>
<TD>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.</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.rsetbeforequit">mail.pop3.rsetbeforequit</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.message.class">mail.pop3.message.class</A></TD>
<TD>String</TD>
<TD>
Class name of a subclass of <code>com.sun.mail.pop3.POP3Message</code>.
The subclass can be used to handle (for example) non-standard
Content-Type headers. The subclass must have a public constructor
of the form <code>MyPOP3Message(Folder f, int msgno)
throws MessagingException</code>.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.localaddress">mail.pop3.localaddress</A></TD>
<TD>String</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.localport">mail.pop3.localport</A></TD>
<TD>int</TD>
<TD>
Local port number to bind to when creating the POP3 socket.
Defaults to the port number picked by the Socket class.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.apop.enable">mail.pop3.apop.enable</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socketFactory">mail.pop3.socketFactory</A></TD>
<TD>SocketFactory</TD>
<TD>
If set to a class that implements the
<code>javax.net.SocketFactory</code> 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
<code>put</code> method, not the <code>setProperty</code> method.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socketFactory.class">mail.pop3.socketFactory.class</A></TD>
<TD>String</TD>
<TD>
If set, specifies the name of a class that implements the
<code>javax.net.SocketFactory</code> interface. This class
will be used to create POP3 sockets.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socketFactory.fallback">mail.pop3.socketFactory.fallback</A></TD>
<TD>boolean</TD>
<TD>
If set to true, failure to create a socket using the specified
socket factory class will cause the socket to be created using
the <code>java.net.Socket</code> class.
Defaults to true.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socketFactory.port">mail.pop3.socketFactory.port</A></TD>
<TD>int</TD>
<TD>
Specifies the port to connect to when using the specified socket
factory.
If not set, the default port will be used.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.enable">mail.pop3.ssl.enable</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.checkserveridentity">mail.pop3.ssl.checkserveridentity</A></TD>
<TD>boolean</TD>
<TD>
If set to true, check the server identity as specified by
<A HREF="http://www.ietf.org/rfc/rfc2595.txt" TARGET="_top">RFC 2595</A>.
These additional checks based on the content of the server's certificate
are intended to prevent man-in-the-middle attacks.
Defaults to false.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.trust">mail.pop3.ssl.trust</A></TD>
<TD>String</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.socketFactory">mail.pop3.ssl.socketFactory</A></TD>
<TD>SSLSocketFactory</TD>
<TD>
If set to a class that extends the
<code>javax.net.ssl.SSLSocketFactory</code> 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
<code>put</code> method, not the <code>setProperty</code> method.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.socketFactory.class">mail.pop3.ssl.socketFactory.class</A></TD>
<TD>String</TD>
<TD>
If set, specifies the name of a class that extends the
<code>javax.net.ssl.SSLSocketFactory</code> class. This class
will be used to create POP3 SSL sockets.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.socketFactory.port">mail.pop3.ssl.socketFactory.port</A></TD>
<TD>int</TD>
<TD>
Specifies the port to connect to when using the specified socket
factory.
If not set, the default port will be used.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.protocols">mail.pop3.ssl.protocols</A></TD>
<TD>string</TD>
<TD>
Specifies the SSL protocols that will be enabled for SSL connections.
The property value is a whitespace separated list of tokens acceptable
to the <code>javax.net.ssl.SSLSocket.setEnabledProtocols</code> method.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.ssl.ciphersuites">mail.pop3.ssl.ciphersuites</A></TD>
<TD>string</TD>
<TD>
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 <code>javax.net.ssl.SSLSocket.setEnabledCipherSuites</code> method.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.starttls.enable">mail.pop3.starttls.enable</A></TD>
<TD>boolean</TD>
<TD>
If true, enables the use of the <code>STLS</code> 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
<A HREF="#mail.pop3.starttls.required"><code>mail.pop3.starttls.required</code></A>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.starttls.required">mail.pop3.starttls.required</A></TD>
<TD>boolean</TD>
<TD>
If true, requires the use of the <code>STLS</code> command.
If the server doesn't support the STLS command, or the command
fails, the connect method will fail.
Defaults to false.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.proxy.host">mail.pop3.proxy.host</A></TD>
<TD>string</TD>
<TD>
Specifies the host name of an HTTP web proxy server that will be used for
connections to the mail server.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.proxy.port">mail.pop3.proxy.port</A></TD>
<TD>string</TD>
<TD>
Specifies the port number for the HTTP web proxy server.
Defaults to port 80.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.proxy.user">mail.pop3.proxy.user</A></TD>
<TD>string</TD>
<TD>
Specifies the user name to use to authenticate with the HTTP web proxy server.
By default, no authentication is done.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.proxy.password">mail.pop3.proxy.password</A></TD>
<TD>string</TD>
<TD>
Specifies the password to use to authenticate with the HTTP web proxy server.
By default, no authentication is done.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socks.host">mail.pop3.socks.host</A></TD>
<TD>string</TD>
<TD>
Specifies the host name of a SOCKS5 proxy server that will be used for
connections to the mail server.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.socks.port">mail.pop3.socks.port</A></TD>
<TD>string</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.disabletop">mail.pop3.disabletop</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.disablecapa">mail.pop3.disablecapa</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.forgettopheaders">mail.pop3.forgettopheaders</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.filecache.enable">mail.pop3.filecache.enable</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.filecache.dir">mail.pop3.filecache.dir</A></TD>
<TD>String</TD>
<TD>
If the file cache is enabled, this property can be used to override the
default directory used by the JDK for temporary files.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.cachewriteto">mail.pop3.cachewriteto</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.keepmessagecontent">mail.pop3.keepmessagecontent</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
<TR>
<TD><A ID="mail.pop3.finalizecleanclose">mail.pop3.finalizecleanclose</A></TD>
<TD>boolean</TD>
<TD>
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.
</TD>
</TR>
</TABLE>
<P>
In general, applications should not need to use the classes in this
package directly. Instead, they should use the APIs defined by
<code>javax.mail</code> package (and subpackages). Applications should
never construct instances of <code>POP3Store</code> or
<code>POP3Folder</code> directly. Instead, they should use the
<code>Session</code> method <code>getStore</code> to acquire an
appropriate <code>Store</code> object, and from that acquire
<code>Folder</code> objects.
</P>
<P>
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:
</P>
<TABLE BORDER="1">
<CAPTION>POP3 Loggers</CAPTION>
<TR>
<TH>Logger Name</TH>
<TH>Logging Level</TH>
<TH>Purpose</TH>
</TR>
<TR>
<TD>com.sun.mail.pop3</TD>
<TD>CONFIG</TD>
<TD>Configuration of the POP3Store</TD>
</TR>
<TR>
<TD>com.sun.mail.pop3</TD>
<TD>FINE</TD>
<TD>General debugging output</TD>
</TR>
<TR>
<TD>com.sun.mail.pop3.protocol</TD>
<TD>FINEST</TD>
<TD>Complete protocol trace</TD>
</TR>
</TABLE>
<P>
<strong>WARNING:</strong> The APIs unique to this package should be
considered <strong>EXPERIMENTAL</strong>. They may be changed in the
future in ways that are incompatible with applications using the
current APIs.
</P>
</BODY>
</HTML>

@ -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<String, String> 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<String, String> 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<String, String> tokenize(String serverResponse)
throws IOException {
Map<String, String> 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);
}
}

@ -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. <p>
*
* 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
* <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
* for interpretation of the return code.
*
* @return the return code
*/
public int getReturnCode() {
return rc;
}
}

@ -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
* <code>mail.smtp.reportsuccess</code> 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
* <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
* for interpretation of the return code.
*
* @return the return code
*/
public int getReturnCode() {
return rc;
}
}

@ -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. <p>
*
* See the <a href="package-summary.html">com.sun.mail.smtp</a> package
* documentation for further information on the SMTP protocol provider. <p>
*
* @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 <code>headers</code> field is set to an empty InternetHeaders
* object. The <code>flags</code> field is set to an empty Flags
* object. The <code>modified</code> 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
* <code>source</code> MimeMessage. The new message is independent
* of the original. <p>
*
* 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 <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A> for
* details. <p>
*
* If set, overrides the <code>mail.smtp.from</code> 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
* (<A HREF="http://www.ietf.org/rfc/rfc1891.txt">RFC 1891</A>).
* Either <code>NOTIFY_NEVER</code> or some combination of
* <code>NOTIFY_SUCCESS</code>, <code>NOTIFY_FAILURE</code>, and
* <code>NOTIFY_DELAY</code>. <p>
*
* If set, overrides the <code>mail.smtp.dsn.notify</code> 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
* (<A HREF="http://www.ietf.org/rfc/rfc1891.txt">RFC 1891</A>).
* Either <code>RETURN_FULL</code> or <code>RETURN_HDRS</code>. <p>
*
* If set, overrides the <code>mail.smtp.dsn.ret</code> 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. <p>
*
* If true, overrides the <code>mail.smtp.allow8bitmime</code> 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. <p>
*
* If true, overrides the <code>mail.smtp.sendpartial</code> 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
* <A HREF="http://www.ietf.org/rfc/rfc2554.txt">RFC 2554</A>
* 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 <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
* and other RFCs that define specific extensions. <p>
*
* For example:
*
* <blockquote><pre>
* if (smtpTransport.supportsExtension("DELIVERBY"))
* smtpMsg.setMailExtension("BY=60;R");
* </pre></blockquote>
*
* @param extension the extension string
* @since JavaMail 1.3.2
*/
public void setMailExtension(String extension) {
this.extension = extension;
}
}

@ -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();
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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. <p>
*
* 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
* <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
* for interpretation of the return code.
*
* @return the return code
*/
public int getReturnCode() {
return rc;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save