GERONIMO-3623 javamail POP3 implementation has numerous incompatibilities with Sun impl.
git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk/geronimo-javamail_1.4@597135 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
index fa33a43..c844e9b 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
@@ -22,9 +22,7 @@
import javax.mail.URLName;
/**
- * IMAP implementation of javax.mail.Store
- * POP protocol spec is implemented in
- * org.apache.geronimo.javamail.store.pop3.IMAPConnection
+ * IMAP implementation of javax.mail.Store for SSL connections.
*
* @version $Rev$ $Date$
*/
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
index 5a0f179..ecbac91 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
@@ -101,18 +101,10 @@
/**
* Normal constructor for an IMAPConnection() object.
- *
- * @param store The store we're associated with (source of parameter values).
- * @param host The target host name of the IMAP server.
- * @param port The target listening port of the server. Defaults to 119 if
- * the port is specified as -1.
- * @param username The login user name (can be null unless authentication is
- * required).
- * @param password Password associated with the userid account. Can be null if
- * authentication is not required.
- * @param sslConnection
- * True if this is targetted as an SSLConnection.
- * @param debug The session debug flag.
+ *
+ * @param props The protocol properties abstraction containing our
+ * property modifiers.
+ * @param pool
*/
public IMAPConnection(ProtocolProperties props, IMAPConnectionPool pool) {
super(props);
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
index 05002ec..f89619c 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
@@ -90,26 +90,19 @@
protected Map capabilities;
/**
- * Create a connection pool associated with a give IMAPStore instance. The
- * connection pool manages handing out connections for both the Store and
- * Folder and Message usage.
+ * Create a connection pool associated with a give IMAPStore instance. The
+ * connection pool manages handing out connections for both the Store and
+ * Folder and Message usage.
*
- * Depending on the session properties, the Store may be given a dedicated
- * connection, or will share connections with the Folders. Connections may
- * be requested from either the Store or Folders. Messages must request
- * their connections from their hosting Folder, and only one connection is
- * allowed per folder.
+ * Depending on the session properties, the Store may be given a dedicated
+ * connection, or will share connections with the Folders. Connections may
+ * be requested from either the Store or Folders. Messages must request
+ * their connections from their hosting Folder, and only one connection is
+ * allowed per folder.
*
- * @param store The Store we're creating the pool for.
- * @param session The Session this Store is created under. This contains the properties
- * we used to tailor behavior.
- * @param sslConnection
- * Indicates whether we need to start connections using an SSL connection.
- * @param defaultPort
- * The default port. Used if we receive a -1 port value on the initial
- * connection request.
- * @param debug The debug flag. Tells us whether to wrapper connections with debug
- * capture streams.
+ * @param store The Store we're creating the pool for.
+ * @param props The property bundle that defines protocol properties
+ * that alter the connection behavior.
*/
public IMAPConnectionPool(IMAPStore store, ProtocolProperties props) {
this.store = store;
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.java
deleted file mode 100644
index b161994..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3;
-
-/**
- * An abstraction for POP3Commands
- *
- * @see org.apache.geronimo.javamail.store.pop3.POP3CommandFactory
- *
- * @version $Rev$ $Date$
- */
-public interface POP3Command {
-
- /**
- * This method will get the POP3 command in string format according o
- * rfc1939
- */
- public String getCommand();
-
- /**
- * Indicates wether this command expects a multiline response or not
- *
- */
- public boolean isMultiLineResponse();
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java
deleted file mode 100644
index a66f97f..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3;
-
-/**
- * Provides concrete implementations of
- * org.apache.geronimo.javamail.store.pop3.POP3Command objects representing the
- * POP3 commands defined in rfc 1939
- *
- * @link http://www.faqs.org/rfcs/rfc1939.html
- * @version $Rev$ $Date$
- */
-public final class POP3CommandFactory implements POP3Constants {
-
- public static POP3Command getCOMMAND_USER(final String user) {
- return new POP3Command() {
- public String getCommand() {
- return "USER" + SPACE + user + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_PASS(final String passwd) {
- return new POP3Command() {
- public String getCommand() {
- return "PASS" + SPACE + passwd + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_QUIT() {
- return new POP3Command() {
- public String getCommand() {
- return "QUIT" + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_NOOP() {
- return new POP3Command() {
- public String getCommand() {
- return "NOOP" + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_STAT() {
- return new POP3Command() {
- public String getCommand() {
- return "STAT" + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_LIST() {
- return getCOMMAND_LIST(-1);
- }
-
- public static POP3Command getCOMMAND_LIST(final int msgNo) {
- return new POP3Command() {
- public String getCommand() {
- if (msgNo > 0) {
- return "LIST" + SPACE + msgNo + CRLF;
- } else {
- return "LIST" + CRLF;
- }
- }
-
- /**
- * If a msg num is specified then the the message details will be on
- * the first line for ex. +OK 3 4520
- *
- * if no msgnum is specified then all the msg details are return in
- * a multiline format for ex. +OK 2 messages 1 456 2 46456 ..... n
- * 366
- */
- public boolean isMultiLineResponse() {
- return (msgNo < 0);
- }
- };
- }
-
- public static POP3Command getCOMMAND_RETR(final int msgNo) {
- return new POP3Command() {
- public String getCommand() {
- return "RETR" + SPACE + msgNo + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return true;
- }
- };
- }
-
- public static POP3Command getCOMMAND_DELE(final int msgNo) {
- return new POP3Command() {
- public String getCommand() {
- return "DELE" + SPACE + msgNo + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_REST(final int msgNo) {
- return new POP3Command() {
- public String getCommand() {
- return "REST" + SPACE + msgNo + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_TOP(final int msgNo, final int numLines) {
- return new POP3Command() {
- public String getCommand() {
- return "TOP" + SPACE + msgNo + SPACE + numLines + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return true;
- }
- };
- }
-
- public static POP3Command getCOMMAND_UIDL() {
- return getCOMMAND_UIDL(-1);
- }
-
- public static POP3Command getCOMMAND_UIDL(final int msgNo) {
- return new POP3Command() {
- public String getCommand() {
- if (msgNo > 0) {
- return "UIDL" + SPACE + msgNo + CRLF;
- } else {
- return "UIDL" + CRLF;
- }
- }
-
- public boolean isMultiLineResponse() {
- return true;
- }
- };
- }
-
- public static POP3Command getCOMMAND_CAPA() {
- return new POP3Command() {
- public String getCommand() {
- return "CAPA" + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return true;
- }
- };
- }
-
- public static POP3Command getCOMMAND_AUTH(final String protocol) {
- return new POP3Command() {
- public String getCommand() {
- return "AUTH " + protocol + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_AUTH(final String protocol, final String initialResponse) {
- return new POP3Command() {
- public String getCommand() {
- return "AUTH " + protocol + " " + initialResponse + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-
- public static POP3Command getCOMMAND_ChallengeReply(final String command) {
- return new POP3Command() {
- public String getCommand() {
- return command + CRLF;
- }
-
- public boolean isMultiLineResponse() {
- return false;
- }
- };
- }
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java
deleted file mode 100644
index e5ccd4f..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.Socket;
-
-import javax.mail.MessagingException;
-import javax.mail.Session;
-
-/**
- * Represents a connection with the POP3 mail server. The connection is owned by
- * a pop3 store and is only associated with one user who owns the respective
- * POP3Store instance
- *
- * @version $Rev$ $Date$
- */
-
-
-public class POP3Connection {
-
- protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
-
- protected static final String MAIL_POP3_FACTORY_CLASS = "socketFactory.class";
-
- protected static final String MAIL_POP3_FACTORY_FALLBACK = "socketFactory.fallback";
-
- protected static final String MAIL_POP3_FACTORY_PORT = "socketFactory.port";
-
- protected static final String MAIL_POP3_LOCALADDRESS = "localAddress";
-
- protected static final String MAIL_POP3_LOCALPORT = "localPort";
-
- protected static final String MAIL_POP3_TIMEOUT = "timeout";
-
- private Socket socket;
-
- private Session session;
-
- private String host;
-
- private int port;
-
- private PrintWriter writer;
-
- private BufferedReader reader;
-
- private String protocol;
-
- private boolean sslConnection;
-
- POP3Connection(Session session, String host, int port, boolean sslConnection, String protocol) {
-
- this.session = session;
- this.host = host;
- this.port = port;
- this.sslConnection = sslConnection;
- this.protocol = protocol;
- }
-
- public void open() throws Exception {
- try {
-
- if (!sslConnection) {
- getConnectedSocket();
- } else {
- getConnectedSSLSocket();
- }
-
- if (session.getDebug()) {
- session.getDebugOut().println("Connection successful " + this.toString());
- }
-
- buildInputReader();
- buildOutputWriter();
-
- // consume the greeting
- if (session.getDebug()) {
- session.getDebugOut().println("Greeting from server " + reader.readLine());
- } else {
- reader.readLine();
- }
-
- } catch (IOException e) {
- Exception ex = new Exception("Error opening connection " + this.toString(), e);
- throw ex;
- }
- }
-
- void close() throws Exception {
- try {
- socket.close();
- if (session.getDebug()) {
- session.getDebugOut().println("Connection successfuly closed " + this.toString());
- }
-
- } catch (IOException e) {
- Exception ex = new Exception("Error closing connection " + this.toString(), e);
- throw ex;
- }
-
- }
-
- public synchronized POP3Response sendCommand(POP3Command cmd) throws MessagingException {
- if (socket.isConnected()) {
-
- // if the underlying output stream is down
- // attempt to rebuild the writer
- if (socket.isOutputShutdown()) {
- buildOutputWriter();
- }
-
- // if the underlying inout stream is down
- // attempt to rebuild the reader
- if (socket.isInputShutdown()) {
- buildInputReader();
- }
-
- if (session.getDebug()) {
- session.getDebugOut().println("\nCommand sent " + cmd.getCommand());
- }
-
- POP3Response res = null;
-
- // this method supresses IOException
- // but choose bcos of ease of use
- {
- writer.write(cmd.getCommand());
- writer.flush();
- res = POP3ResponseBuilder.buildResponse(session, reader, cmd.isMultiLineResponse());
- }
-
- return res;
- }
-
- throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
- }
-
- private void buildInputReader() throws MessagingException {
- try {
- reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- } catch (IOException e) {
- throw new MessagingException("Error obtaining input stream " + this.toString(), e);
- }
- }
-
- private void buildOutputWriter() throws MessagingException {
- try {
- writer = new PrintWriter(new BufferedOutputStream(socket.getOutputStream()));
- } catch (IOException e) {
- throw new MessagingException("Error obtaining output stream " + this.toString(), e);
- }
- }
-
- public String toString() {
- return "POP3Connection host: " + host + " port: " + port;
- }
-
-
- /**
- * Creates a connected socket
- *
- * @exception MessagingException
- */
- protected void getConnectedSocket() throws IOException {
-
- // the socket factory can be specified via a session property. By
- // default, we just directly
- // instantiate a socket without using a factor.
- String socketFactory = getProtocolProperty(MAIL_POP3_FACTORY_CLASS);
-
- // there are several protocol properties that can be set to tune the
- // created socket. We need to
- // retrieve those bits before creating the socket.
- int timeout = getIntProtocolProperty(MAIL_POP3_TIMEOUT, -1);
- InetAddress localAddress = null;
- // see if we have a local address override.
- String localAddrProp = getProtocolProperty(MAIL_POP3_LOCALADDRESS);
- if (localAddrProp != null) {
- localAddress = InetAddress.getByName(localAddrProp);
- }
-
- // check for a local port...default is to allow socket to choose.
- int localPort = getIntProtocolProperty(MAIL_POP3_LOCALPORT, 0);
-
- socket = null;
-
- // if there is no socket factory defined (normal), we just create a
- // socket directly.
- if (socketFactory == null) {
- socket = new Socket(host, port, localAddress, localPort);
- }
-
- else {
- try {
- int socketFactoryPort = getIntProtocolProperty(MAIL_POP3_FACTORY_PORT, -1);
-
- // we choose the port used by the socket based on overrides.
- Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
- // use the current context loader to resolve this.
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class factoryClass = loader.loadClass(socketFactory);
-
- // done indirectly, we need to invoke the method using
- // reflection.
- // This retrieves a factory instance.
- Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
- Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
- // now that we have the factory, there are two different
- // createSocket() calls we use,
- // depending on whether we have a localAddress override.
-
- if (localAddress != null) {
- // retrieve the createSocket(String, int, InetAddress, int)
- // method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- } else {
- // retrieve the createSocket(String, int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- }
- } catch (Throwable e) {
- // if a socket factory is specified, then we may need to fall
- // back to a default. This behavior
- // is controlled by (surprise) more session properties.
- if (isProtocolPropertyTrue(MAIL_POP3_FACTORY_FALLBACK)) {
- socket = new Socket(host, port, localAddress, localPort);
- }
- // we have an exception. We're going to throw an IOException,
- // which may require unwrapping
- // or rewrapping the exception.
- else {
- // we have an exception from the reflection, so unwrap the
- // base exception
- if (e instanceof InvocationTargetException) {
- e = ((InvocationTargetException) e).getTargetException();
- }
-
-
- // throw this as an IOException, with the original exception
- // attached.
- IOException ioe = new IOException("Error connecting to " + host + ", " + port);
- ioe.initCause(e);
- throw ioe;
- }
- }
- }
-
- if (timeout >= 0) {
- socket.setSoTimeout(timeout);
- }
- }
-
- /**
- * Creates a connected SSL socket for an initial SSL connection.
- *
- * @exception MessagingException
- */
- protected void getConnectedSSLSocket() throws IOException {
-
- if (session.getDebug()) {
- session.getDebugOut().println("Attempting SSL socket connection to server " + host + ":" + port);
- }
- // the socket factory can be specified via a protocol property, a
- // session property, and if all else
- // fails (which it usually does), we fall back to the standard factory
- // class.
- String socketFactory = getProtocolProperty(MAIL_POP3_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
- "javax.net.ssl.SSLSocketFactory"));
-
- // there are several protocol properties that can be set to tune the
- // created socket. We need to
- // retrieve those bits before creating the socket.
- int timeout = getIntProtocolProperty(MAIL_POP3_TIMEOUT, -1);
- InetAddress localAddress = null;
- // see if we have a local address override.
- String localAddrProp = getProtocolProperty(MAIL_POP3_LOCALADDRESS);
- if (localAddrProp != null) {
- localAddress = InetAddress.getByName(localAddrProp);
- }
-
- // check for a local port...default is to allow socket to choose.
- int localPort = getIntProtocolProperty(MAIL_POP3_LOCALPORT, 0);
-
- socket = null;
-
- // if there is no socket factory defined (normal), we just create a
- // socket directly.
- if (socketFactory == null) {
- System.out.println("SocketFactory was null so creating the connection using a default");
- socket = new Socket(host, port, localAddress, localPort);
- }
-
- else {
- // we'll try this with potentially two different factories if we're
- // allowed to fall back.
- boolean fallback = isProtocolPropertyTrue(MAIL_POP3_FACTORY_FALLBACK);
- while(true) {
- try {
-
-
- if (socket != null) {
- if (socket.isConnected())
- break;
- }
-
- if (session.getDebug()) {
- session.getDebugOut().println("Creating SSL socket using factory " + socketFactory);
- }
-
- int socketFactoryPort = getIntProtocolProperty(MAIL_POP3_FACTORY_PORT, -1);
-
- // we choose the port used by the socket based on overrides.
- Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
- // use the current context loader to resolve this.
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class factoryClass = loader.loadClass(socketFactory);
-
- // done indirectly, we need to invoke the method using
- // reflection.
- // This retrieves a factory instance.
- Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
- Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
- // now that we have the factory, there are two different
- // createSocket() calls we use,
- // depending on whether we have a localAddress override.
-
- if (localAddress != null) {
- // retrieve the createSocket(String, int, InetAddress,
- // int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class,
- Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- } else {
- // retrieve the createSocket(String, int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- }
- } catch (Throwable e) {
- // if we're allowed to fallback, then use the default
- // factory and try this again. We only
- // allow this to happen once.
- if (session.getDebug()) {
- session.getDebugOut().println("First attempt at creating SSL socket failed, falling back to default factory");
- }
- if (fallback) {
- socketFactory = "javax.net.ssl.SSLSocketFactory";
- fallback = false;
- continue;
- }
- // we have an exception. We're going to throw an
- // IOException, which may require unwrapping
- // or rewrapping the exception.
- else {
- // we have an exception from the reflection, so unwrap
- // the base exception
- if (e instanceof InvocationTargetException) {
- e = ((InvocationTargetException) e).getTargetException();
- }
-
- if (session.getDebug()) {
- session.getDebugOut().println("Failure creating SSL socket: " + e);
- }
- // throw this as an IOException, with the original
- // exception attached.
- IOException ioe = new IOException("Error connecting to " + host + ", " + port);
- ioe.initCause(e);
- throw ioe;
- }
- }
- }
- }
-
- if (timeout >= 0) {
- socket.setSoTimeout(timeout);
- }
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "true". Returns false for any other
- * value (including null).
- */
- protected boolean isProtocolPropertyTrue(String name) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol ("pop3").
- String fullName = "mail." + protocol + "." + name;
- return isSessionPropertyTrue(fullName);
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "true". Returns false for any other
- * value (including null).
- */
- protected boolean isSessionPropertyTrue(String name) {
- String property = session.getProperty(name);
- if (property != null) {
- return property.equals("true");
- }
- return false;
- }
-
- /**
- * Get a property associated with this mail session as an integer value.
- * Returns the default value if the property doesn't exist or it doesn't
- * have a valid int value.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value converted to an int.
- */
- protected int getIntProtocolProperty(String name, int defaultValue) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (pop3).
- String fullName = "mail." + protocol + "." + name;
- return getIntSessionProperty(fullName, defaultValue);
- }
-
- /**
- * Get a property associated with this mail session as an integer value.
- * Returns the default value if the property doesn't exist or it doesn't
- * have a valid int value.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value converted to an int.
- */
- protected int getIntSessionProperty(String name, int defaultValue) {
- String result = getSessionProperty(name);
- if (result != null) {
- try {
- // convert into an int value.
- return Integer.parseInt(result);
- } catch (NumberFormatException e) {
- }
- }
- // return default value if it doesn't exist is isn't convertable.
- return defaultValue;
- }
-
- /**
- * Get a property associated with this mail session. Returns the provided
- * default if it doesn't exist.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value (returns defaultValue if the property has not
- * been set).
- */
- protected String getSessionProperty(String name, String defaultValue) {
- String result = session.getProperty(name);
- if (result == null) {
- return defaultValue;
- }
- return result;
- }
-
- /**
- * Get a property associated with this mail session. Returns the provided
- * default if it doesn't exist.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value (returns defaultValue if the property has not
- * been set).
- */
- protected String getProtocolProperty(String name, String defaultValue) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol ("pop3").
- String fullName = "mail." + protocol + "." + name;
- return getSessionProperty(fullName, defaultValue);
- }
-
- /**
- * Get a property associated with this mail protocol.
- *
- * @param name
- * The name of the property.
- *
- * @return The property value (returns null if the property has not been
- * set).
- */
- protected String getProtocolProperty(String name) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol ("pop3").
- String fullName = "mail." + protocol + "." + name;
- return getSessionProperty(fullName);
- }
-
- /**
- * Get a property associated with this mail session.
- *
- * @param name
- * The name of the property.
- *
- * @return The property value (returns null if the property has not been
- * set).
- */
- protected String getSessionProperty(String name) {
- return session.getProperty(name);
- }
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java
index 57aed7e..6b3f5ef 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java
@@ -30,10 +30,6 @@
public final static String CRLF = "\r\n";
- public final static int LF = '\n';
-
- public final static int CR = '\r';
-
public final static int DOT = '.';
public final static int OK = 0;
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
index 042c170..6e59064 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
@@ -19,11 +19,12 @@
package org.apache.geronimo.javamail.store.pop3;
-import java.util.Vector;
+import java.util.List;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
+import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
@@ -32,10 +33,8 @@
import javax.mail.URLName;
import javax.mail.event.ConnectionEvent;
-import org.apache.geronimo.javamail.store.pop3.message.POP3Message;
-import org.apache.geronimo.javamail.store.pop3.message.POP3MessageFactory;
-import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
-import org.apache.geronimo.javamail.store.pop3.response.POP3StatusResponse;
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
+import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse;
/**
* The POP3 implementation of the javax.mail.Folder Note that only INBOX is
@@ -50,53 +49,90 @@
*/
public class POP3Folder extends Folder {
- private boolean isFolderOpen = false;
+ protected boolean isFolderOpen = false;
- private int mode;
+ protected int mode;
- private POP3Connection pop3Con;
+ protected POP3Connection currentConnection;
- private int msgCount;
+ protected int msgCount;
- private Session session;
+ private POP3Message[] messageCache;
+ // The fully qualified name of the folder. For a POP3 folder, this is either "" for the root or
+ // "INPUT" for the in-basket. It is possible to create other folders, but they will report that
+ // they don't exist.
+ protected String fullName;
+ // indicates whether this folder exists or not
+ protected boolean exists = false;
+ // indicates the type of folder this is.
+ protected int folderType;
+
+ /**
+ * Create a new folder associate with a POP3 store instance.
+ *
+ * @param store The owning Store.
+ * @param name The name of the folder. Note that POP3 stores only
+ * have 2 real folders, the root ("") and the in-basket
+ * ("INBOX"). It is possible to create other instances
+ * of Folder associated with the Store, but they will
+ * be non-functional.
+ */
+ public POP3Folder(POP3Store store, String name) {
+ super(store);
+ this.fullName = name;
+ // if this is the input folder, this exists
+ if (name.equalsIgnoreCase("INPUT")) {
+ exists = true;
+ }
+ // by default, we're holding messages.
+ folderType = Folder.HOLDS_MESSAGES;
+ }
+
+
+ /**
+ * Retrieve the folder name. This is the simple folder
+ * name at the its hiearchy level. This can be invoked when the folder is closed.
+ *
+ * @return The folder's name.
+ */
+ public String getName() {
+ // the name and the full name are always the same
+ return fullName;
+ }
/**
- * Vector is synchronized so choose over the other Collection impls This is
- * initialized on open A chache will save the expensive operation of
- * retrieving the message again from the server.
+ * Retrieve the folder's full name (including hierarchy information).
+ * This can be invoked when the folder is closed.
+ *
+ * @return The full name value.
*/
- private Vector msgCache;
+ public String getFullName() {
+ return fullName;
+ }
- protected POP3Folder(Store store, URLName url) {
- super(store);
- }
-
- protected POP3Folder(Store store, Session session, POP3Connection pop3Con) {
- super(store);
- this.pop3Con = pop3Con;
- this.session = session;
- }
-
- public String getName() {
- return "INBOX";
- }
-
- public String getFullName() {
- return "INBOX";
- }
-
+
/**
* Never return "this" as the parent folder. Somebody not familliar with
* POP3 may do something like while(getParent() != null) or something
* simmilar which will result in an infinte loop
*/
public Folder getParent() throws MessagingException {
- throw new MethodNotSupportedException("INBOX is the root folder");
+ // the default folder returns null. We return the default
+ // folder
+ return store.getDefaultFolder();
}
+ /**
+ * Indicate whether a folder exists. Only the root
+ * folder and "INBOX" will ever return true.
+ *
+ * @return true for real POP3 folders, false for any other
+ * instances that have been created.
+ * @exception MessagingException
+ */
public boolean exists() throws MessagingException {
- // INBOX always exists at the backend
- return true;
+ // only one folder truely exists...this might be it.
+ return exists;
}
public Folder[] list(String pattern) throws MessagingException {
@@ -104,22 +140,44 @@
}
/**
- * No sub folders, hence there is no notion of a seperator
+ * No sub folders, hence there is no notion of a seperator. This is always a null character.
*/
public char getSeparator() throws MessagingException {
- throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+ return '\0';
}
+ /**
+ * There's no hierarchy in POP3, so the only type
+ * is HOLDS_MESSAGES (and only one of those exists).
+ *
+ * @return Always returns HOLDS_MESSAGES.
+ * @exception MessagingException
+ */
public int getType() throws MessagingException {
- return HOLDS_MESSAGES;
+ return folderType;
}
+ /**
+ * Always returns false as any creation operation must
+ * fail.
+ *
+ * @param type The type of folder to create. This is ignored.
+ *
+ * @return Always returns false.
+ * @exception MessagingException
+ */
public boolean create(int type) throws MessagingException {
- throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+ return false;
}
+ /**
+ * No way to detect new messages, so always return false.
+ *
+ * @return Always returns false.
+ * @exception MessagingException
+ */
public boolean hasNewMessages() throws MessagingException {
- throw new MethodNotSupportedException("POP3 doesn't support this operation");
+ return false;
}
public Folder getFolder(String name) throws MessagingException {
@@ -142,13 +200,14 @@
checkClosed();
try {
+
+ // ask the store to kindly hook us up with a connection.
+ // We're going to hang on to this until we're closed, so store it in
+ // the Folder field. We need to make sure our mailbox is selected while
+ // we're working things.
+ currentConnection = ((POP3Store)store).getFolderConnection(this);
- POP3StatusResponse res = (POP3StatusResponse) POP3ResponseFactory.getStatusResponse(pop3Con
- .sendCommand(POP3CommandFactory.getCOMMAND_STAT()));
-
- // I am not checking for the res == null condition as the
- // try catch block will handle it.
-
+ POP3StatusResponse res = currentConnection.retrieveMailboxStatus();
this.mode = mode;
this.isFolderOpen = true;
this.msgCount = res.getNumMessages();
@@ -156,11 +215,9 @@
// size (no of bytes) of the mail drop;
// NB: We use the actual message number to access the messages from
- // the cache, which is origin 1. Vectors are origin 0, so we add one additional
- // element and burn the
- msgCache = new Vector(msgCount + 1);
- msgCache.setSize(msgCount + 1);
-
+ // the cache, which is origin 1. Vectors are origin 0, so we have to subtract each time
+ // we access a messagge.
+ messageCache = new POP3Message[msgCount];
} catch (Exception e) {
throw new MessagingException("Unable to execute STAT command", e);
}
@@ -168,57 +225,137 @@
notifyConnectionListeners(ConnectionEvent.OPENED);
}
+ /**
+ * Close a POP3 folder.
+ *
+ * @param expunge The expunge flag (ignored for POP3).
+ *
+ * @exception MessagingException
+ */
public void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
-
try {
- if (mode == READ_WRITE) {
- // find all messages marked deleted and issue DELE commands
- POP3Message m;
- // NB: the first element in the cache is not used.
- for (int i = 1; i < msgCache.size(); i++) {
- if ((m = (POP3Message) msgCache.elementAt(i)) != null) {
- if (m.isSet(Flags.Flag.DELETED)) {
- try {
- pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_DELE(i + 1));
- } catch (Exception e) {
- throw new MessagingException("Exception deleting message no [" + (i + 1)
- + "] during close", e);
- }
+ // we might need to reset the connection before we
+ // process deleted messages and send the QUIT. The
+ // connection knows if we need to do this.
+ currentConnection.reset();
+ // clean up any messages marked for deletion
+ expungeDeletedMessages();
+ } finally {
+ // cleanup the the state even if exceptions occur when deleting the
+ // messages.
+ cleanupFolder(false);
+ }
+ }
+
+ /**
+ * Mark any messages we've flagged as deleted from the
+ * IMAP server before closing.
+ *
+ * @exception MessagingException
+ */
+ protected void expungeDeletedMessages() throws MessagingException {
+ if (mode == READ_WRITE) {
+ for (int i = 0; i < messageCache.length; i++) {
+ POP3Message msg = messageCache[i];
+ if (msg != null) {
+ // if the deleted flag is set, go delete this
+ // message. NB: We adjust the index back to an
+ // origin 1 value
+ if (msg.isSet(Flags.Flag.DELETED)) {
+ try {
+ currentConnection.deleteMessage(i + 1);
+ } catch (MessagingException e) {
+ throw new MessagingException("Exception deleting message number " + (i + 1), e);
}
}
}
}
-
- try {
- pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
- } catch (Exception e) {
- // doesn't really care about the response
- }
- // dosn't need a catch block here, but added incase something goes
- // wrong
- // so that the finnaly is garunteed to execute in such a case.
- } finally {
- try {
- pop3Con.close();
- } catch (Exception e) {
- // doesn't really care about the response
- // all we can do is to set the reference explicitly to null
- pop3Con = null;
- }
-
- /*
- * The message numbers depend on the mail drop if the connection is
- * closed, then purge the cache
- */
- msgCache = null;
- isFolderOpen = false;
- notifyConnectionListeners(ConnectionEvent.CLOSED);
}
}
+
+
+ /**
+ * Do folder cleanup. This is used both for normal
+ * close operations, and adnormal closes where the
+ * server has sent us a BYE message.
+ *
+ * @param expunge Indicates whether open messages should be expunged.
+ * @param disconnected
+ * The disconnected flag. If true, the server has cut
+ * us off, which means our connection can not be returned
+ * to the connection pool.
+ *
+ * @exception MessagingException
+ */
+ protected void cleanupFolder(boolean disconnected) throws MessagingException {
+ messageCache = null;
+ isFolderOpen = false;
+ // if we have a connection active at the moment
+ if (currentConnection != null) {
+ // was this a forced disconnect by the server?
+ if (disconnected) {
+ currentConnection.setClosed();
+ }
+ else {
+ // have this close the selected mailbox
+ currentConnection.logout();
+ }
+ // we need to release the connection to the Store once we're closed
+ ((POP3Store)store).releaseFolderConnection(this, currentConnection);
+ currentConnection = null;
+ }
+ notifyConnectionListeners(ConnectionEvent.CLOSED);
+ }
+
+
+ /**
+ * Obtain a connection object for a Message attached to this Folder. This
+ * will be the Folder's connection, which is only available if the Folder
+ * is currently open.
+ *
+ * @return The connection object for the Message instance to use.
+ * @exception MessagingException
+ */
+ synchronized POP3Connection getMessageConnection() throws MessagingException {
+ // if we're not open, the messages can't communicate either
+ if (currentConnection == null) {
+ throw new FolderClosedException(this, "No Folder connections available");
+ }
+ // return the current Folder connection. At this point, we'll be sharing the
+ // connection between the Folder and the Message (and potentially, other messages). The
+ // command operations on the connection are synchronized so only a single command can be
+ // issued at one time.
+ return currentConnection;
+ }
+
+
+ /**
+ * Release the connection object back to the Folder instance.
+ *
+ * @param connection The connection being released.
+ *
+ * @exception MessagingException
+ */
+ void releaseMessageConnection(POP3Connection connection) throws MessagingException {
+ // This is a NOP for this folder type.
+ }
public boolean isOpen() {
+ // if we're not open, we're not open
+ if (!isFolderOpen) {
+ return false;
+ }
+
+ try {
+ // we might be open, but the Store has been closed. In which case, we're not any more
+ // closing also changes the isFolderOpen flag.
+ if (!((POP3Store)store).isConnected()) {
+ close(false);
+ }
+ } catch (MessagingException e) {
+ }
return isFolderOpen;
}
@@ -233,7 +370,14 @@
return new Flags();
}
+ /**
+ * Get the folder message count.
+ *
+ * @return The number of messages in the folder.
+ * @exception MessagingException
+ */
public int getMessageCount() throws MessagingException {
+ // NB: returns -1 if the folder isn't open.
return msgCount;
}
@@ -250,15 +394,10 @@
throw new MessagingException("Invalid Message number");
}
- Message msg = null;
- try {
- msg = (Message) msgCache.elementAt(msgNum);
- } catch (RuntimeException e) {
- session.getDebugOut().println("Message not in cache");
- }
+ Message msg = messageCache[msgNum - 1];
if (msg == null) {
- msg = POP3MessageFactory.createMessage(this, session, pop3Con, msgNum);
- msgCache.setElementAt(msg, msgNum);
+ msg = new POP3Message(this, msgNum);
+ messageCache[msgNum - 1] = (POP3Message)msg;
}
return msg;
@@ -286,29 +425,50 @@
* The JavaMail API recommends that this method be overrident to provide a
* meaningfull implementation.
*/
- public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
+ public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
for (int i = 0; i < msgs.length; i++) {
Message msg = msgs[i];
- if (msg == null) {
- msg = POP3MessageFactory.createMessage(this, session, pop3Con, i);
- }
+
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
- msg = POP3MessageFactory.createMessageWithEvelope((POP3Message) msg);
+ // fetching the size and the subject will force all of the
+ // envelope information to load
+ msg.getHeader("Subject");
+ msg.getSize();
}
-
if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
- msg = POP3MessageFactory.createMessageWithContentInfo((POP3Message) msg);
+ // force the content to load...this also fetches the header information.
+ // C'est la vie.
+ ((POP3Message)msg).loadContent();
+ msg.getSize();
}
-
+ // force flag loading for this message
if (fp.contains(FetchProfile.Item.FLAGS)) {
- msg = POP3MessageFactory.createMessageWithFlags((POP3Message) msg);
+ msg.getFlags();
}
-
- msgs[i] = msg;
+
+ if (fp.getHeaderNames().length > 0) {
+ // loading any header loads all headers, so just grab the header set.
+ msg.getHeader("Subject");
+ }
}
}
+
+ /**
+ * Retrieve the UID for a given message.
+ *
+ * @param msg The message of interest.
+ *
+ * @return The String UID value for this message.
+ * @exception MessagingException
+ */
+ public synchronized String getUID(Message msg) throws MessagingException {
+ checkOpen();
+ // the Message knows how to do this
+ return ((POP3Message)msg).getUID();
+ }
+
/**
* Below is a list of covinience methods that avoid repeated checking for a
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java
new file mode 100644
index 0000000..966da66
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.javamail.store.pop3;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.IllegalWriteException;
+import javax.mail.MessagingException;
+import javax.mail.event.MessageChangedEvent;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
+
+/**
+ * POP3 implementation of javax.mail.internet.MimeMessage
+ *
+ * Only the most basic information is given and Message objects created here is
+ * a light-weight reference to the actual Message As per the JavaMail spec items
+ * from the actual message will get filled up on demand
+ *
+ * If some other items are obtained from the server as a result of one call,
+ * then the other details are also processed and filled in. For ex if RETR is
+ * called then header information will also be processed in addition to the
+ * content
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3Message extends MimeMessage {
+ // the size of the message, in bytes
+ protected int msgSize = -1;
+ // the size of the headers. We keep this around, as it's needed to
+ // properly calculate the size of the message
+ protected int headerSize = -1;
+ // the UID value retrieved from the server
+ protected String uid;
+ // the raw message data from loading the message
+ protected byte[] messageData;
+
+ /**
+ * Create a new POP3 message associated with a folder.
+ *
+ * @param folder The owning folder.
+ * @param msgnum The message sequence number in the folder.
+ */
+ protected POP3Message(Folder folder, int msgnum) {
+ super(folder, msgnum);
+ this.session = session;
+ // force the headers to empty so we'll load them the first time they're referenced.
+ this.headers = null;
+ }
+
+ /**
+ * Get an InputStream for reading the message content.
+ *
+ * @return An InputStream instance initialized to read the message
+ * content.
+ * @exception MessagingException
+ */
+ protected InputStream getContentStream() throws MessagingException {
+ // make sure the content is loaded first
+ loadContent();
+ // allow the super class to handle creating it from the loaded content.
+ return super.getContentStream();
+ }
+
+
+ /**
+ * Write out the byte data to the provided output stream.
+ *
+ * @param out The target stream.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ // make sure we have everything loaded
+ loadContent();
+ // just write out the raw message data
+ out.write(messageData);
+ }
+
+
+ /**
+ * Set a flag value for this Message. The flags are
+ * only set locally, not the server. When the folder
+ * is closed, any messages with the Deleted flag set
+ * will be removed from the server.
+ *
+ * @param newFlags The new flag values.
+ * @param set Indicates whether this is a set or an unset operation.
+ *
+ * @exception MessagingException
+ */
+ public void setFlags(Flags newFlags, boolean set) throws MessagingException {
+ Flags oldFlags = (Flags) flags.clone();
+ super.setFlags(newFlags, set);
+
+ if (!flags.equals(oldFlags)) {
+ ((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this);
+ }
+ }
+
+ /**
+ * Unconditionally load the headers from an inputstream.
+ * When retrieving content, we get back the entire message,
+ * including the headers. This allows us to skip over
+ * them to reach the content, even if we already have
+ * headers loaded.
+ *
+ * @param in The InputStream with the header data.
+ *
+ * @exception MessagingException
+ */
+ protected void loadHeaders(InputStream in) throws MessagingException {
+ try {
+ headerSize = in.available();
+ // just load and replace the haders
+ headers = new InternetHeaders(in);
+ headerSize -= in.available();
+ } catch (IOException e) {
+ // reading from a ByteArrayInputStream...this should never happen.
+ }
+ }
+
+ /**
+ * Lazy loading of the message content.
+ *
+ * @exception MessagingException
+ */
+ protected void loadContent() throws MessagingException {
+ if (content == null) {
+ POP3Connection connection = getConnection();
+ try {
+ // retrieve (and save the raw message data
+ messageData = connection.retrieveMessageData(msgnum);
+ } finally {
+ // done with the connection
+ releaseConnection(connection);
+ }
+ // now create a input stream for splitting this into headers and
+ // content
+ ByteArrayInputStream in = new ByteArrayInputStream(messageData);
+
+ // the Sun implementation has an option that forces headers loaded using TOP
+ // should be forgotten when retrieving the message content. This is because
+ // some POP3 servers return different results for TOP and RETR. Since we need to
+ // retrieve the headers anyway, and this set should be the most complete, we'll
+ // just replace the headers unconditionally.
+ loadHeaders(in);
+ // load headers stops loading at the header terminator. Everything
+ // after that is content.
+ loadContent(in);
+ }
+ }
+
+ /**
+ * Load the message content from the server.
+ *
+ * @param stream A ByteArrayInputStream containing the message content.
+ * We explicitly use ByteArrayInputStream because
+ * there are some optimizations that can take advantage
+ * of the fact it is such a stream.
+ *
+ * @exception MessagingException
+ */
+ protected void loadContent(ByteArrayInputStream stream) throws MessagingException {
+ // since this is a byte array input stream, available() returns reliable value.
+ content = new byte[stream.available()];
+ try {
+ // just read everything in to the array
+ stream.read(content);
+ } catch (IOException e) {
+ // should never happen
+ throw new MessagingException("Error loading content info", e);
+ }
+ }
+
+ /**
+ * Get the size of the message.
+ *
+ * @return The calculated message size, in bytes.
+ * @exception MessagingException
+ */
+ public int getSize() throws MessagingException {
+ if (msgSize < 0) {
+ // we need to get the headers loaded, since we need that information to calculate the total
+ // content size without retrieving the content.
+ loadHeaders();
+
+ POP3Connection connection = getConnection();
+ try {
+
+ // get the total message size, and adjust by size of the headers to get the content size.
+ msgSize = connection.retrieveMessageSize(msgnum) - headerSize;
+ } finally {
+ // done with the connection
+ releaseConnection(connection);
+ }
+ }
+ return msgSize;
+ }
+
+ /**
+ * notice that we pass zero as the no of lines from the message,as it
+ * doesn't serv any purpose to get only a certain number of lines.
+ *
+ * However this maybe important if a mail client only shows 3 or 4 lines of
+ * the message in the list and then when the user clicks they would load the
+ * message on demand.
+ *
+ */
+ protected void loadHeaders() throws MessagingException {
+ if (headers == null) {
+ POP3Connection connection = getConnection();
+ try {
+ loadHeaders(connection.retrieveMessageHeaders(msgnum));
+ } finally {
+ // done with the connection
+ releaseConnection(connection);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the message UID from the server.
+ *
+ * @return The string UID value.
+ * @exception MessagingException
+ */
+ protected String getUID() throws MessagingException {
+ if (uid == null) {
+ POP3Connection connection = getConnection();
+ try {
+ uid = connection.retrieveMessageUid(msgnum);
+ } finally {
+ // done with the connection
+ releaseConnection(connection);
+ }
+ }
+ return uid;
+ }
+
+ // The following are methods that deal with all header accesses. Most of the
+ // methods that retrieve information from the headers funnel through these, so we
+ // can lazy-retrieve the header information.
+
+ public String[] getHeader(String name) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getHeader(name);
+ }
+
+ public String getHeader(String name, String delimiter) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getHeader(name, delimiter);
+ }
+
+ public Enumeration getAllHeaders() throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getAllHeaders();
+ }
+
+ public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getMatchingHeaders(names);
+ }
+
+ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getNonMatchingHeaders(names);
+ }
+
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getAllHeaderLines();
+ }
+
+ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getMatchingHeaderLines(names);
+ }
+
+ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+ // make sure the headers are loaded
+ loadHeaders();
+ // allow the super class to handle everything from here
+ return super.getNonMatchingHeaderLines(names);
+ }
+
+ // the following are overrides for header modification methods. These
+ // messages are read only,
+ // so the headers cannot be modified.
+ public void addHeader(String name, String value) throws MessagingException {
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ public void setHeader(String name, String value) throws MessagingException {
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ public void removeHeader(String name) throws MessagingException {
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ public void addHeaderLine(String line) throws MessagingException {
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+ /**
+ * We cannot modify these messages
+ */
+ public void saveChanges() throws MessagingException {
+ throw new IllegalWriteException("POP3 messages are read-only");
+ }
+
+
+ /**
+ * get the current connection pool attached to the folder. We need
+ * to do this dynamically, to A) ensure we're only accessing an
+ * currently open folder, and B) to make sure we're using the
+ * correct connection attached to the folder.
+ *
+ * @return A connection attached to the hosting folder.
+ */
+ protected POP3Connection getConnection() throws MessagingException {
+ // the folder owns everything.
+ return ((POP3Folder)folder).getMessageConnection();
+ }
+
+ /**
+ * Release the connection back to the Folder after performing an operation
+ * that requires a connection.
+ *
+ * @param connection The previously acquired connection.
+ */
+ protected void releaseConnection(POP3Connection connection) throws MessagingException {
+ // the folder owns everything.
+ ((POP3Folder)folder).releaseMessageConnection(connection);
+ }
+}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java
deleted file mode 100644
index 197ca20..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3;
-
-import java.io.InputStream;
-
-/**
- * An abstraction for POP3 Response
- *
- * @see org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory
- * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
- * @see org.apache.geronimo.javamail.store.pop3.response.POP3StatusResponse
- *
- * @version $Rev$ $Date$
- */
-public interface POP3Response {
-
- /**
- * Returns the response OK, CHALLENGE or ERR
- * <ul>
- * <li>OK --> +OK in pop3 spec
- * <li>CHALLENGE --> + in pop3 spec
- * <li>ERR --> -ERR in pop3 spec
- * </ul>
- */
- public int getStatus();
-
- /**
- * this corresponds to the line with the status however the status will be
- * removed and the remainder is returned. Ex. "+OK 132 3023673" is the first
- * line of response for a STAT command this method will return "132 3023673"
- *
- * So any subsequent process can parse the params 132 as no of msgs and
- * 3023674 as the size.
- *
- * @see org.apache.geronimo.javamail.store.pop3.response.POP3StatusResponse
- */
- public String getFirstLine();
-
- /**
- * This way we are not restricting anybody as InputStream.class is the most
- * basic type to represent an inputstream and ppl can decorate it anyway
- * they want, for ex BufferedInputStream or as an InputStreamReader allowing
- * maximum flexibility in using it.
- */
- public InputStream getData();
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java
deleted file mode 100644
index 7b0ab4a..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.mail.MessagingException;
-import javax.mail.Session;
-
-import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
-
-;
-
-/**
- * Builds a basic response out of the input stream received by the connection.
- * Performs only two basic functions
- * <ul>
- * <li>Extrats the status code</li>
- * <li>If multi-line response then extract the data as an input stream</li>
- * </ul>
- *
- * @version $Rev$ $Date$
- */
-
-public final class POP3ResponseBuilder implements POP3Constants {
-
- public static POP3Response buildResponse(Session session, BufferedReader reader, boolean isMultiLineResponse)
- throws MessagingException {
-
- int status = ERR;
- InputStream data = null;
-
- String line;
- try {
- line = reader.readLine();
- } catch (IOException e) {
- throw new MessagingException("Error in receving response");
- }
- if (line == null || line.trim().equals("")) {
- if (session.getDebug()) {
- session.getDebugOut().println("Empty Response");
- }
- throw new MessagingException("Empty Response");
- }
- if (session.getDebug()) {
- session.getDebugOut().println("Response From Server " + line);
- }
-
- if (line.startsWith("+OK")) {
- status = OK;
- line = removeStatusField(line);
- if (isMultiLineResponse) {
- data = getMultiLineResponse(session, reader);
- }
- } else if (line.startsWith("-ERR")) {
- status = ERR;
- line = removeStatusField(line);
- }else if (line.startsWith("+")) {
- status = CHALLENGE;
- line = removeStatusField(line);
- if (isMultiLineResponse) {
- data = getMultiLineResponse(session, reader);
- }
- } else {
- throw new MessagingException("Unexpected response: " + line);
- }
-
- return POP3ResponseFactory.getDefaultResponse(status, line, data);
- }
-
- private static String removeStatusField(String line) {
- return line.substring(line.indexOf(SPACE) + 1);
- }
-
- /**
- * This could be a multiline response
- */
- private static InputStream getMultiLineResponse(Session session, BufferedReader reader) throws MessagingException {
-
- int byteRead = -1;
- int lastByteRead = LF;
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- while ((byteRead = reader.read()) >= 0) {
- // We are checking for the end of a multiline response
- // the format is .CRLF
-
- // checking for the DOT and CR
- if (lastByteRead == DOT && byteRead == CR) {
- byteRead = reader.read();
- // now checking for the LF of the second CRLF
- if (byteRead == LF) {
- // end of response
- break;
- }
- }
-
- out.write(byteRead);
- lastByteRead = byteRead;
- }
-
- if (session.getDebug()) {
- session.getDebugOut().println("\n============================ Response Content==================\n");
- session.getDebugOut().write(out.toByteArray());
- session.getDebugOut().println("\n==============================================================\n");
- }
-
- } catch (IOException e) {
- throw new MessagingException("Error processing a multi-line response", e);
- }
-
- return new ByteArrayInputStream(out.toByteArray());
- }
-
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java
new file mode 100644
index 0000000..99cde3b
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java
@@ -0,0 +1,142 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.geronimo.javamail.store.pop3;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import javax.mail.Store;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
+
+/**
+ * An POP3 folder instance for the root of POP3 folder tree. This has
+ * some of the folder operations disabled.
+ */
+public class POP3RootFolder extends POP3Folder {
+ // the inbox folder is the only one that exists
+ protected Folder inbox;
+
+ /**
+ * Create a default POP3RootFolder attached to a specific Store instance.
+ *
+ * @param store The Store instance this is the root for.
+ */
+ public POP3RootFolder(POP3Store store) {
+ // create a folder with a null string name and the default separator.
+ super(store, "");
+ // this only holds folders
+ folderType = HOLDS_FOLDERS;
+ // this folder does exist
+ exists = true;
+ // no messages in this folder
+ msgCount = 0;
+ }
+
+
+ /**
+ * Get the parent. This is the root folder, which
+ * never has a parent.
+ *
+ * @return Always returns null.
+ */
+ public Folder getParent() {
+ // we never have a parent folder
+ return null;
+ }
+
+ /**
+ * We have a separator because the root folder is "special".
+ */
+ public char getSeparator() throws MessagingException {
+ return '/';
+ }
+
+ /**
+ * Retrieve a list of folders that match a pattern.
+ *
+ * @param pattern The match pattern.
+ *
+ * @return An array of matching folders.
+ * @exception MessagingException
+ */
+ public Folder[] list(String pattern) throws MessagingException {
+ // I'm not sure this is correct, but the Sun implementation appears to
+ // return a array containing the inbox regardless of what pattern was specified.
+ return new Folder[] { getInbox() };
+ }
+
+ /**
+ * Get a folder of a given name from the root folder.
+ * The Sun implementation seems somewhat inconsistent
+ * here. The docs for Store claim that only INBOX is
+ * supported, but it will return a Folder instance for any
+ * name. On the other hand, the root folder raises
+ * an exception for anything but the INBOX.
+ *
+ * @param name The folder name (which must be "INBOX".
+ *
+ * @return The inbox folder instance.
+ * @exception MessagingException
+ */
+ public Folder getFolder(String name) throws MessagingException {
+ if (!name.equalsIgnoreCase("INBOX")) {
+ throw new MessagingException("Only the INBOX folder is supported");
+ }
+ // return the inbox folder
+ return getInbox();
+ }
+
+ /**
+ * Override for the isOpen method. The root folder can
+ * never be opened.
+ *
+ * @return always returns false.
+ */
+ public boolean isOpen() {
+ return false;
+ }
+
+ public void open(int mode) throws MessagingException {
+ throw new MessagingException("POP3 root folder cannot be opened");
+ }
+
+ public void open(boolean expunge) throws MessagingException {
+ throw new MessagingException("POP3 root folder cannot be close");
+ }
+
+
+ /**
+ * Retrieve the INBOX folder from the root.
+ *
+ * @return The Folder instance for the inbox.
+ * @exception MessagingException
+ */
+ protected Folder getInbox() throws MessagingException {
+ // we're the only place that creates folders, and
+ // we only create the single instance.
+ if (inbox == null) {
+ inbox = new POP3Folder((POP3Store)store, "INBOX");
+ }
+ return inbox;
+ }
+}
+
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java
new file mode 100644
index 0000000..67c254a
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.geronimo.javamail.store.pop3;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+/**
+ * POP3 implementation of javax.mail.Store over an SSL connection.
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3SSLStore extends POP3Store {
+ /**
+ * Construct an POP3SSLStore item.
+ *
+ * @param session The owning javamail Session.
+ * @param urlName The Store urlName, which can contain server target information.
+ */
+ public POP3SSLStore(Session session, URLName urlName) {
+ // we're the imaps protocol, our default connection port is 993, and we must use
+ // an SSL connection for the initial hookup
+ super(session, urlName, "pop3s", DEFAULT_POP3_SSL_PORT, true);
+ }
+}
+
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
index 5caed89..913f356 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
@@ -18,6 +18,10 @@
*/
package org.apache.geronimo.javamail.store.pop3;
+
+import java.io.PrintStream;
+import java.util.LinkedList;
+import java.util.List;
import javax.mail.AuthenticationFailedException;
import javax.mail.Folder;
@@ -26,6 +30,10 @@
import javax.mail.Store;
import javax.mail.URLName;
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
+import org.apache.geronimo.javamail.store.pop3.connection.POP3ConnectionPool;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+
/**
* POP3 implementation of javax.mail.Store POP protocol spec is implemented in
* org.apache.geronimo.javamail.store.pop3.POP3Connection
@@ -34,16 +42,28 @@
*/
public class POP3Store extends Store {
-
- private POP3Connection pop3Con;
-
- protected static final int DEFAULT_MAIL_POP3_PORT = 110;
- private boolean sslConnection;
- private int defaultPort;
+ protected static final int DEFAULT_POP3_PORT = 110;
+ protected static final int DEFAULT_POP3_SSL_PORT = 995;
- private String protocol;
+
+ // our accessor for protocol properties and the holder of
+ // protocol-specific information
+ protected ProtocolProperties props;
+ // our connection object
+ protected POP3ConnectionPool connectionPool;
+ // our session provided debug output stream.
+ protected PrintStream debugStream;
+ // the debug flag
+ protected boolean debug;
+ // the root folder
+ protected POP3RootFolder root;
+ // until we're connected, we're closed
+ boolean closedForBusiness = true;
+ protected LinkedList openFolders = new LinkedList();
+
+
public POP3Store(Session session, URLName name) {
- this(session, name, "pop3", DEFAULT_MAIL_POP3_PORT, false);
+ this(session, name, "pop3", DEFAULT_POP3_PORT, false);
}
/**
@@ -68,173 +88,227 @@
*/
protected POP3Store(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
- this.protocol = protocol;
+
+ // create the protocol property holder. This gives an abstraction over the different
+ // flavors of the protocol.
+ props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
- // these are defaults based on what the superclass specifies.
- this.sslConnection = sslConnection;
- this.defaultPort = defaultPort;
-
- }
- /**
- * @see javax.mail.Store#getDefaultFolder()
- *
- * There is only INBOX supported in POP3 so the default folder is inbox
- */
- public Folder getDefaultFolder() throws MessagingException {
- return getFolder("INBOX");
+ // get our debug settings
+ debugStream = session.getDebugOut();
+ debug = session.getDebug();
+ // the connection pool manages connections for the stores, folder, and message usage.
+ connectionPool = new POP3ConnectionPool(this, props);
}
+
/**
- * @see javax.mail.Store#getFolder(java.lang.String)
+ * Return a Folder object that represents the root of the namespace for the current user.
+ *
+ * Note that in some store configurations (such as IMAP4) the root folder might
+ * not be the INBOX folder.
+ *
+ * @return the root Folder
+ * @throws MessagingException if there was a problem accessing the store
*/
- public Folder getFolder(String name) throws MessagingException {
-
- checkConnectionStatus();
-
- if (!"INBOX".equalsIgnoreCase(name)) {
- throw new MessagingException("Only INBOX is supported in POP3");
+ public Folder getDefaultFolder() throws MessagingException {
+ checkConnectionStatus();
+ // if no root yet, create a root folder instance.
+ if (root == null) {
+ return new POP3RootFolder(this);
}
- return new POP3Folder(this, session, pop3Con);
- }
+ return root;
+ }
/**
- * @see javax.mail.Store#getFolder(javax.mail.URLName)
+ * Return the Folder corresponding to the given name.
+ * The folder might not physically exist; the {@link Folder#exists()} method can be used
+ * to determine if it is real.
+ *
+ * @param name the name of the Folder to return
+ *
+ * @return the corresponding folder
+ * @throws MessagingException
+ * if there was a problem accessing the store
*/
- public Folder getFolder(URLName url) throws MessagingException {
- return getFolder(url.getFile());
- }
+ public Folder getFolder(String name) throws MessagingException {
+ return getDefaultFolder().getFolder(name);
+ }
+
+ /**
+ * Return the folder identified by the URLName; the URLName must refer to this Store.
+ * Implementations may use the {@link URLName#getFile()} method to determined the folder name.
+ *
+ * @param url
+ *
+ * @return the corresponding folder
+ * @throws MessagingException
+ * if there was a problem accessing the store
+ */
+ public Folder getFolder(URLName url) throws MessagingException {
+ return getDefaultFolder().getFolder(url.getFile());
+ }
+
+
/**
* @see javax.mail.Service#protocolConnect(java.lang.String, int,
* java.lang.String, java.lang.String)
*/
- protected synchronized boolean protocolConnect(String host, int portNum, String user, String passwd)
- throws MessagingException {
-
- // Never store the user, passwd for security reasons
-
- // if these values are null, no connection attempt should be made
- if (host == null || passwd == null || user == null) {
- return false;
+ protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+
+ if (debug) {
+ debugOut("Connecting to server " + host + ":" + port + " for user " + username);
}
- // validate port num
- if (portNum < 1) {
- String portstring = session.getProperty("mail.pop3.port");
- if (portstring != null) {
+ // the connection pool handles all of the details here.
+ if (connectionPool.protocolConnect(host, port, username, password))
+ {
+ // the store is now open
+ closedForBusiness = false;
+ return true;
+ }
+ return false;
+ }
+
+
+ protected POP3Connection getConnection() throws MessagingException {
+ return connectionPool.getConnection();
+ }
+
+ protected void releaseConnection(POP3Connection connection) throws MessagingException {
+ connectionPool.releaseConnection(connection);
+ }
+
+ synchronized POP3Connection getFolderConnection(POP3Folder folder) throws MessagingException {
+ POP3Connection connection = connectionPool.getConnection();
+ openFolders.add(folder);
+ return connection;
+ }
+
+ synchronized void releaseFolderConnection(POP3Folder folder, POP3Connection connection) throws MessagingException {
+ openFolders.remove(folder);
+ // a connection returned from a folder is no longer usable. Just close it and
+ // let it drift off.
+ connection.close();
+ }
+
+ /**
+ * Close all open folders. We have a small problem here with a race condition. There's no safe, single
+ * synchronization point for us to block creation of new folders while we're closing. So we make a copy of
+ * the folders list, close all of those folders, and keep repeating until we're done.
+ */
+ protected void closeOpenFolders() {
+ // we're no longer accepting additional opens. Any folders that open after this point will get an
+ // exception trying to get a connection.
+ closedForBusiness = true;
+
+ while (true) {
+ List folders = null;
+
+ // grab our lock, copy the open folders reference, and null this out. Once we see a null
+ // open folders ref, we're done closing.
+ synchronized(connectionPool) {
+ folders = openFolders;
+ openFolders = new LinkedList();
+ }
+
+ // null folder, we're done
+ if (folders.isEmpty()) {
+ return;
+ }
+ // now close each of the open folders.
+ for (int i = 0; i < folders.size(); i++) {
+ POP3Folder folder = (POP3Folder)folders.get(i);
try {
- portNum = Integer.parseInt(portstring);
- } catch (NumberFormatException e) {
- portNum = defaultPort;
+ folder.close(false);
+ } catch (MessagingException e) {
}
}
}
-
- /*
- * Obtaining a connection to the server.
- *
- */
- pop3Con = new POP3Connection(this.session, host, portNum, sslConnection, protocol);
- try {
- pop3Con.open();
- } catch (Exception e) {
- throw new MessagingException("Connection failed", e);
- }
-
- /*
- * Sending the USER command with username
- *
- */
- POP3Response resUser = null;
- try {
- resUser = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_USER(user));
- } catch (Exception e) {
- throw new MessagingException("Connection failed", e);
- }
-
- if (POP3Constants.ERR == resUser.getStatus()) {
-
- /*
- * Authentication failed so sending QUIT
- *
- */
- try {
- pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
- } catch (Exception e) {
- // We don't care about the response or if any error happens
- // just trying to comply with the spec.
- // Most likely the server would have terminated the connection
- // by now.
- }
-
- throw new AuthenticationFailedException(resUser.getFirstLine());
- }
-
- /*
- * Sending the PASS command with password
- *
- */
- POP3Response resPwd = null;
- try {
- resPwd = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_PASS(passwd));
- } catch (Exception e) {
- throw new MessagingException("Connection failed", e);
- }
-
- if (POP3Constants.ERR == resPwd.getStatus()) {
-
- /*
- * Authentication failed so sending QUIT
- *
- */
- try {
- pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
- } catch (Exception e) {
- // We don't care about the response or if any error happens
- // just trying to comply with the spec.
- // Most likely the server would have terminated the connection
- // by now.
- }
-
- throw new AuthenticationFailedException(resPwd.getFirstLine());
- }
-
- return true;
}
+
/**
* @see javax.mail.Service#isConnected()
*/
public boolean isConnected() {
- POP3Response res = null;
try {
- res = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_NOOP());
- } catch (Exception e) {
- return false;
+ POP3Connection connection = getConnection();
+ try {
+ connection.pingServer();
+ return true;
+ }
+ finally {
+ releaseConnection(connection);
+ }
+ } catch (MessagingException e) {
}
-
- return (POP3Constants.OK == res.getStatus());
+ return false;
}
/**
- * @see javax.mail.Service#close()
+ * Close the store, and any open folders associated with the
+ * store.
+ *
+ * @exception MessagingException
*/
- public void close() throws MessagingException {
- // This is done to ensure proper event notification.
- super.close();
- try {
- pop3Con.close();
- } catch (Exception e) {
- // A message is already set at the connection level
- // unfortuantely there is no constructor that takes only
- // the root exception
- new MessagingException("", e);
+ public synchronized void close() throws MessagingException{
+ // if already closed, nothing to do.
+ if (closedForBusiness) {
+ return;
}
- }
+
+ // close the folders first, then shut down the Store.
+ closeOpenFolders();
+
+ connectionPool.close();
+ connectionPool = null;
+ // make sure we do the superclass close operation first so
+ // notification events get broadcast properly.
+ super.close();
+ }
+
+ /**
+ * Check the status of our connection.
+ *
+ * @exception MessagingException
+ */
private void checkConnectionStatus() throws MessagingException {
if (!this.isConnected()) {
throw new MessagingException("Not connected ");
}
}
+
+ /**
+ * Internal debug output routine.
+ *
+ * @param value The string value to output.
+ */
+ void debugOut(String message) {
+ debugStream.println("POP3Store DEBUG: " + message);
+ }
+
+ /**
+ * Internal debugging routine for reporting exceptions.
+ *
+ * @param message A message associated with the exception context.
+ * @param e The received exception.
+ */
+ void debugOut(String message, Throwable e) {
+ debugOut("Received exception -> " + message);
+ debugOut("Exception message -> " + e.getMessage());
+ e.printStackTrace(debugStream);
+ }
+
+ /**
+ * Finalizer to perform IMAPStore() cleanup when
+ * no longer in use.
+ *
+ * @exception Throwable
+ */
+ protected void finalize() throws Throwable {
+ super.finalize();
+ close();
+ }
}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java
new file mode 100644
index 0000000..0b12a09
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java
@@ -0,0 +1,655 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.geronimo.javamail.store.pop3.connection;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+import org.apache.geronimo.javamail.util.CommandFailedException;
+import org.apache.geronimo.javamail.util.InvalidCommandException;
+import org.apache.geronimo.javamail.util.MIMEInputReader;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Hex;
+
+/**
+ * Simple implementation of POP3 transport.
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3Connection extends MailConnection implements POP3Constants {
+
+ static final protected String MAIL_APOP_ENABLED = "apop.enable";
+ static final protected String MAIL_AUTH_ENABLED = "auth.enable";
+ static final protected String MAIL_RESET_QUIT = "rsetbeforequit";
+ static final protected String MAIL_DISABLE_TOP = "disabletop";
+ static final protected String MAIL_FORGET_TOP = "forgettopheaders";
+
+ // the initial greeting string, which might be required for APOP authentication.
+ protected String greeting;
+ // is use of the AUTH command enabled
+ protected boolean authEnabled;
+ // is use of APOP command enabled
+ protected boolean apopEnabled;
+ // input reader wrapped around the socket input stream
+ protected BufferedReader reader;
+ // output writer wrapped around the socket output stream.
+ protected PrintWriter writer;
+ // this connection was closed unexpectedly
+ protected boolean closed;
+ // indicates whether this conneciton is currently logged in. Once
+ // we send a QUIT, we're finished.
+ protected boolean loggedIn;
+ // indicates whether we need to avoid using the TOP command
+ // when retrieving headers
+ protected boolean topDisabled = false;
+
+ /**
+ * Normal constructor for an POP3Connection() object.
+ *
+ * @param store The store we're associated with (source of parameter values).
+ * @param host The target host name of the IMAP server.
+ * @param port The target listening port of the server. Defaults to 119 if
+ * the port is specified as -1.
+ * @param username The login user name (can be null unless authentication is
+ * required).
+ * @param password Password associated with the userid account. Can be null if
+ * authentication is not required.
+ * @param sslConnection
+ * True if this is targetted as an SSLConnection.
+ * @param debug The session debug flag.
+ */
+ public POP3Connection(ProtocolProperties props) {
+ super(props);
+
+ // get our login properties flags
+ authEnabled = props.getBooleanProperty(MAIL_AUTH_ENABLED, false);
+ apopEnabled = props.getBooleanProperty(MAIL_APOP_ENABLED, false);
+ topDisabled = props.getBooleanProperty(MAIL_DISABLE_TOP, false);
+ }
+
+
+ /**
+ * Connect to the server and do the initial handshaking.
+ *
+ * @exception MessagingException
+ */
+ public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
+ this.serverHost = host;
+ this.serverPort = port;
+ this.realm = realm;
+ this.authid = authid;
+ this.username = username;
+ this.password = password;
+
+ try {
+ // create socket and connect to server.
+ getConnection();
+ // consume the welcome line
+ getWelcome();
+
+ // go login with the server
+ if (login())
+ {
+ loggedIn = true;
+ return true;
+ }
+ return false;
+ } catch (IOException e) {
+ if (debug) {
+ debugOut("I/O exception establishing connection", e);
+ }
+ throw new MessagingException("Connection error", e);
+ }
+ }
+
+
+ /**
+ * Create a transport connection object and connect it to the
+ * target server.
+ *
+ * @exception MessagingException
+ */
+ protected void getConnection() throws MessagingException
+ {
+ try {
+ // do all of the non-protocol specific set up. This will get our socket established
+ // and ready use.
+ super.getConnection();
+ } catch (IOException e) {
+ throw new MessagingException("Unable to obtain a connection to the POP3 server", e);
+ }
+
+ // The POp3 protocol is inherently a string-based protocol, so we get
+ // string readers/writers for the connection streams
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+ writer = new PrintWriter(new BufferedOutputStream(outputStream));
+ }
+
+ protected void getWelcome() throws IOException {
+ // just read the line and consume it. If debug is
+ // enabled, there I/O stream will be traced
+ greeting = reader.readLine();
+ }
+
+ public String toString() {
+ return "POP3Connection host: " + serverHost + " port: " + serverPort;
+ }
+
+
+ /**
+ * Close the connection. On completion, we'll be disconnected from
+ * the server and unable to send more data.
+ *
+ * @exception MessagingException
+ */
+ public void close() throws MessagingException {
+ // if we're already closed, get outta here.
+ if (socket == null) {
+ return;
+ }
+ try {
+ // say goodbye
+ logout();
+ } finally {
+ // and close up the connection. We do this in a finally block to make sure the connection
+ // is shut down even if quit gets an error.
+ closeServerConnection();
+ // get rid of our response processor too.
+ reader = null;
+ writer = null;
+ }
+ }
+
+
+ /**
+ * Tag this connection as having been closed by the
+ * server. This will not be returned to the
+ * connection pool.
+ */
+ public void setClosed() {
+ closed = true;
+ }
+
+ /**
+ * Test if the connnection has been forcibly closed.
+ *
+ * @return True if the server disconnected the connection.
+ */
+ public boolean isClosed() {
+ return closed;
+ }
+
+ protected POP3Response sendCommand(String cmd) throws MessagingException {
+ return sendCommand(cmd, false);
+ }
+
+ protected POP3Response sendMultiLineCommand(String cmd) throws MessagingException {
+ return sendCommand(cmd, true);
+ }
+
+ protected synchronized POP3Response sendCommand(String cmd, boolean multiLine) throws MessagingException {
+ if (socket.isConnected()) {
+ {
+ // NOTE: We don't use println() because it uses the platform concept of a newline rather
+ // than using CRLF, which is required by the POP3 protocol.
+ writer.write(cmd);
+ writer.write("\r\n");
+ writer.flush();
+
+ POP3Response response = buildResponse(multiLine);
+ if (response.isError()) {
+ throw new CommandFailedException("Error issuing POP3 command: " + cmd);
+ }
+ return response;
+ }
+ }
+ throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
+ }
+
+ /**
+ * Build a POP3Response item from the response stream.
+ *
+ * @param isMultiLineResponse
+ * If true, this command is expecting multiple lines back from the server.
+ *
+ * @return A POP3Response item with all of the command response data.
+ * @exception MessagingException
+ */
+ protected POP3Response buildResponse(boolean isMultiLineResponse) throws MessagingException {
+ int status = ERR;
+ byte[] data = null;
+
+ String line;
+ MIMEInputReader source = new MIMEInputReader(reader);
+
+ try {
+ line = reader.readLine();
+ } catch (IOException e) {
+ throw new MessagingException("Error in receving response");
+ }
+
+ if (line == null || line.trim().equals("")) {
+ throw new MessagingException("Empty Response");
+ }
+
+ if (line.startsWith("+OK")) {
+ status = OK;
+ line = removeStatusField(line);
+ if (isMultiLineResponse) {
+ data = getMultiLineResponse();
+ }
+ } else if (line.startsWith("-ERR")) {
+ status = ERR;
+ line = removeStatusField(line);
+ }else if (line.startsWith("+")) {
+ status = CHALLENGE;
+ line = removeStatusField(line);
+ if (isMultiLineResponse) {
+ data = getMultiLineResponse();
+ }
+ } else {
+ throw new MessagingException("Unexpected response: " + line);
+ }
+ return new POP3Response(status, line, data);
+ }
+
+ private static String removeStatusField(String line) {
+ return line.substring(line.indexOf(SPACE) + 1);
+ }
+
+ /**
+ * This could be a multiline response
+ */
+ private byte[] getMultiLineResponse() throws MessagingException {
+
+ MIMEInputReader source = new MIMEInputReader(reader);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // it's more efficient to do this a buffer at a time.
+ // the MIMEInputReader takes care of the byte-stuffing and
+ // ".\r\n" input terminator for us.
+ OutputStreamWriter outWriter = new OutputStreamWriter(out);
+ char buffer[] = new char[500];
+ try {
+ int charsRead = -1;
+ while ((charsRead = source.read(buffer)) >= 0) {
+ outWriter.write(buffer, 0, charsRead);
+ }
+ outWriter.flush();
+ } catch (IOException e) {
+ throw new MessagingException("Error processing a multi-line response", e);
+ }
+
+ return out.toByteArray();
+ }
+
+
+ /**
+ * Retrieve the raw message content from the POP3
+ * server. This is all of the message data, including
+ * the header.
+ *
+ * @param sequenceNumber
+ * The message sequence number.
+ *
+ * @return A byte array containing all of the message data.
+ * @exception MessagingException
+ */
+ public byte[] retrieveMessageData(int sequenceNumber) throws MessagingException {
+ POP3Response msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+ // we want the data directly in this case.
+ return msgResponse.getData();
+ }
+
+ /**
+ * Retrieve the message header information for a given
+ * message, returned as an input stream suitable
+ * for loading the message data.
+ *
+ * @param sequenceNumber
+ * The server sequence number for the message.
+ *
+ * @return An inputstream that can be used to read the message
+ * data.
+ * @exception MessagingException
+ */
+ public ByteArrayInputStream retrieveMessageHeaders(int sequenceNumber) throws MessagingException {
+ POP3Response msgResponse;
+
+ // some POP3 servers don't correctly implement TOP, so this can be disabled. If
+ // we can't use TOP, then use RETR and retrieve everything. We can just hand back
+ // the stream, as the header loading routine will stop at the first
+ // null line.
+ if (topDisabled) {
+ msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+ }
+ else {
+ msgResponse = sendMultiLineCommand("TOP " + sequenceNumber + " 0");
+ }
+
+ // just load the returned message data as a set of headers
+ return msgResponse.getContentStream();
+ }
+
+ /**
+ * Retrieve the total message size from the mail
+ * server. This is the size of the headers plus
+ * the size of the message content.
+ *
+ * @param sequenceNumber
+ * The message sequence number.
+ *
+ * @return The full size of the message.
+ * @exception MessagingException
+ */
+ public int retrieveMessageSize(int sequenceNumber) throws MessagingException {
+ POP3Response msgResponse = sendCommand("LIST " + sequenceNumber);
+ // Convert this into the parsed response type we need.
+ POP3ListResponse list = new POP3ListResponse(msgResponse);
+ // this returns the total message size
+ return list.getSize();
+ }
+
+ /**
+ * Retrieve the mail drop status information.
+ *
+ * @return An object representing the returned mail drop status.
+ * @exception MessagingException
+ */
+ public POP3StatusResponse retrieveMailboxStatus() throws MessagingException {
+ // issue the STAT command and return this into a status response
+ return new POP3StatusResponse(sendCommand("STAT"));
+ }
+
+
+ /**
+ * Retrieve the UID for an individual message.
+ *
+ * @param sequenceNumber
+ * The target message sequence number.
+ *
+ * @return The string UID maintained by the server.
+ * @exception MessagingException
+ */
+ public String retrieveMessageUid(int sequenceNumber) throws MessagingException {
+ POP3Response msgResponse = sendCommand("UIDL " + sequenceNumber);
+
+ String message = msgResponse.getFirstLine();
+ // the UID is everything after the blank separating the message number and the UID.
+ // there's not supposed to be anything else on the message, but trim it of whitespace
+ // just to be on the safe side.
+ return message.substring(message.indexOf(' ') + 1).trim();
+ }
+
+
+ /**
+ * Delete a single message from the mail server.
+ *
+ * @param sequenceNumber
+ * The sequence number of the message to delete.
+ *
+ * @exception MessagingException
+ */
+ public void deleteMessage(int sequenceNumber) throws MessagingException {
+ // just issue the command...we ignore the command response
+ sendCommand("DELE " + sequenceNumber);
+ }
+
+ /**
+ * Logout from the mail server. This sends a QUIT
+ * command, which will likely sever the mail connection.
+ *
+ * @exception MessagingException
+ */
+ public void logout() throws MessagingException {
+ // we may have already sent the QUIT command
+ if (!loggedIn) {
+ return;
+ }
+ // just issue the command...we ignore the command response
+ sendCommand("QUIT");
+ loggedIn = false;
+ }
+
+ /**
+ * Perform a reset on the mail server.
+ *
+ * @exception MessagingException
+ */
+ public void reset() throws MessagingException {
+ // some mail servers mark retrieved messages for deletion
+ // automatically. This will reset the read flags before
+ // we go through normal cleanup.
+ if (props.getBooleanProperty(MAIL_RESET_QUIT, false)) {
+ // just send an RSET command first
+ sendCommand("RSET");
+ }
+ }
+
+ /**
+ * Ping the mail server to see if we still have an active connection.
+ *
+ * @exception MessagingException thrown if we do not have an active connection.
+ */
+ public void pingServer() throws MessagingException {
+ // just issue the command...we ignore the command response
+ sendCommand("NOOP");
+ }
+
+ /**
+ * Login to the mail server, using whichever method is
+ * configured. This will try multiple methods, if allowed,
+ * in decreasing levels of security.
+ *
+ * @return true if the login was successful.
+ * @exception MessagingException
+ */
+ public synchronized boolean login() throws MessagingException {
+ // permitted to use the AUTH command?
+ if (authEnabled) {
+ try {
+ // go do the SASL thing
+ return processSaslAuthentication();
+ } catch (MessagingException e) {
+ // Any error here means fall back to the next mechanism
+ }
+ }
+
+ if (apopEnabled) {
+ try {
+ // go do the SASL thing
+ return processAPOPAuthentication();
+ } catch (MessagingException e) {
+ // Any error here means fall back to the next mechanism
+ }
+ }
+
+ try {
+ // do the tried and true login processing.
+ return processLogin();
+ } catch (MessagingException e) {
+ }
+ // everything failed...can't get in
+ return false;
+ }
+
+
+ /**
+ * Process a basic LOGIN operation, using the
+ * plain test USER/PASS command combo.
+ *
+ * @return true if we logged successfully.
+ * @exception MessagingException
+ */
+ public boolean processLogin() throws MessagingException {
+ // start by sending the USER command, followed by
+ // the PASS command
+ sendCommand("USER " + username);
+ sendCommand("PASS " + password);
+ return true; // we're in
+ }
+
+ /**
+ * Process logging in using the APOP command. Only
+ * works on servers that give a timestamp value
+ * in the welcome response.
+ *
+ * @return true if the login was accepted.
+ * @exception MessagingException
+ */
+ public boolean processAPOPAuthentication() throws MessagingException {
+ int timeStart = greeting.indexOf('<');
+ // if we didn't get an APOP challenge on the greeting, throw an exception
+ // the main login processor will swallow that and fall back to the next
+ // mechanism
+ if (timeStart == -1) {
+ throw new MessagingException("POP3 Server does not support APOP");
+ }
+ int timeEnd = greeting.indexOf('>');
+ String timeStamp = greeting.substring(timeStart, timeEnd + 1);
+
+ // we create the digest password using the timestamp value sent to use
+ // concatenated with the password.
+ String digestPassword = timeStamp + password;
+
+ byte[] digest;
+
+ try {
+ // create a digest value from the password.
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ digest = md.digest(digestPassword.getBytes("iso-8859-1"));
+ } catch (NoSuchAlgorithmException e) {
+ // this shouldn't happen, but if it does, we'll just try a plain
+ // login.
+ throw new MessagingException("Unable to create MD5 digest", e);
+ } catch (UnsupportedEncodingException e) {
+ // this shouldn't happen, but if it does, we'll just try a plain
+ // login.
+ throw new MessagingException("Unable to create MD5 digest", e);
+ }
+ // this will throw an exception if it gives an error failure
+ sendCommand("APOP " + username + " " + Hex.encode(digest));
+ // no exception, we must have passed
+ return true;
+ }
+
+
+ /**
+ * Process SASL-type authentication.
+ *
+ * @return Returns true if the server support a SASL authentication mechanism and
+ * accepted reponse challenges.
+ * @exception MessagingException
+ */
+ protected boolean processSaslAuthentication() throws MessagingException {
+ // if unable to get an appropriate authenticator, just fail it.
+ ClientAuthenticator authenticator = getSaslAuthenticator();
+ if (authenticator == null) {
+ throw new MessagingException("Unable to obtain SASL authenticator");
+ }
+
+ // go process the login.
+ return processLogin(authenticator);
+ }
+
+ /**
+ * Attempt to retrieve a SASL authenticator for this
+ * protocol.
+ *
+ * @return A SASL authenticator, or null if a suitable one
+ * was not located.
+ */
+ protected ClientAuthenticator getSaslAuthenticator() {
+ return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
+ }
+
+
+ /**
+ * Process a login using the provided authenticator object.
+ *
+ * NB: This method is synchronized because we have a multi-step process going on
+ * here. No other commands should be sent to the server until we complete.
+ *
+ * @return Returns true if the server support a SASL authentication mechanism and
+ * accepted reponse challenges.
+ * @exception MessagingException
+ */
+ protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
+ if (debug) {
+ debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+ }
+
+ POP3Response response = sendCommand("AUTH " + authenticator.getMechanismName());
+
+ // now process the challenge sequence. We get a continuation response back for each stage of the
+ // authentication, and finally an OK when everything passes muster.
+ while (true) {
+ // this should be a continuation reply, if things are still good.
+ if (response.isChallenge()) {
+ // we're passed back a challenge value, Base64 encoded.
+ byte[] challenge = response.decodeChallengeResponse();
+
+ String responseString = new String(Base64.encode(authenticator.evaluateChallenge(challenge)));
+
+ // have the authenticator evaluate and send back the encoded response.
+ response = sendCommand(responseString);
+ }
+ else {
+ // there are only two choices here, OK or a continuation. OK means
+ // we've passed muster and are in.
+ return true;
+ }
+ }
+ }
+
+
+ /**
+ * Merge the configured SASL mechanisms with the capabilities that the
+ * server has indicated it supports, returning a merged list that can
+ * be used for selecting a mechanism.
+ *
+ * @return A List representing the intersection of the configured list and the
+ * capabilities list.
+ */
+ protected List selectSaslMechanisms() {
+ // just return the set that have been explicity permitted
+ return getSaslMechanisms();
+ }
+}
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java
new file mode 100644
index 0000000..8aa9c4b
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java
@@ -0,0 +1,224 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.geronimo.javamail.store.pop3.connection;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+
+import javax.mail.StoreClosedException;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Store;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+
+public class POP3ConnectionPool {
+
+ protected static final String MAIL_PORT = "port";
+
+ protected static final String MAIL_SASL_REALM = "sasl.realm";
+ protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
+
+ protected static final String DEFAULT_MAIL_HOST = "localhost";
+
+ // Our hosting Store instance
+ protected POP3Store store;
+ // our Protocol abstraction
+ protected ProtocolProperties props;
+ // POP3 is not nearly as multi-threaded as IMAP. We really just have a single folder,
+ // plus the Store, but the Store doesn't really talk to the server very much. We only
+ // hold one connection available, and on the off chance there is a situation where
+ // we need to create a new one, we'll authenticate on demand. The one case where
+ // I know this might be an issue is a folder checking back with the Store to see it if
+ // it is still connected.
+ protected POP3Connection availableConnection;
+
+ // our debug flag
+ protected boolean debug;
+
+ // the target host
+ protected String host;
+ // the target server port.
+ protected int port;
+ // the username we connect with
+ protected String username;
+ // the authentication password.
+ protected String password;
+ // the SASL realm name
+ protected String realm;
+ // the authorization id.
+ protected String authid;
+ // Turned on when the store is closed for business.
+ protected boolean closed = false;
+
+ /**
+ * Create a connection pool associated with a give POP3Store instance. The
+ * connection pool manages handing out connections for both the Store and
+ * Folder and Message usage.
+ *
+ * @param store The Store we're creating the pool for.
+ * @param props The protocol properties abstraction we use.
+ */
+ public POP3ConnectionPool(POP3Store store, ProtocolProperties props) {
+ this.store = store;
+ this.props = props;
+ }
+
+
+ /**
+ * Manage the initial connection to the POP3 server. This is the first
+ * point where we obtain the information needed to make an actual server
+ * connection. Like the Store protocolConnect method, we return false
+ * if there's any sort of authentication difficulties.
+ *
+ * @param host The host of the mail server.
+ * @param port The mail server connection port.
+ * @param user The connection user name.
+ * @param password The connection password.
+ *
+ * @return True if we were able to connect and authenticate correctly.
+ * @exception MessagingException
+ */
+ public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+ // NOTE: We don't check for the username/password being null at this point. It's possible that
+ // the server will send back a PREAUTH response, which means we don't need to go through login
+ // processing. We'll need to check the capabilities response after we make the connection to decide
+ // if logging in is necesssary.
+
+ // save this for subsequent connections. All pool connections will use this info.
+ // if the port is defaulted, then see if we have something configured in the session.
+ // if not configured, we just use the default default.
+ if (port == -1) {
+ // check for a property and fall back on the default if it's not set.
+ port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
+ // it's possible that -1 might have been explicitly set, so one last check.
+ if (port == -1) {
+ port = props.getDefaultPort();
+ }
+ }
+
+ // Before we do anything, let's make sure that we succesfully received a host
+ if ( host == null ) {
+ host = DEFAULT_MAIL_HOST;
+ }
+
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+
+ // make sure we have the realm information
+ realm = props.getProperty(MAIL_SASL_REALM);
+ // get an authzid value, if we have one. The default is to use the username.
+ authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
+
+ // go create a connection and just add it to the pool. If there is an authenticaton error,
+ // return the connect failure, and we may end up trying again.
+ availableConnection = createPoolConnection();
+ if (availableConnection == null) {
+ return false;
+ }
+ // we're connected, authenticated, and ready to go.
+ return true;
+ }
+
+ /**
+ * Creates an authenticated pool connection and adds it to
+ * the connection pool. If there is an existing connection
+ * already in the pool, this returns without creating a new
+ * connection.
+ *
+ * @exception MessagingException
+ */
+ protected POP3Connection createPoolConnection() throws MessagingException {
+ POP3Connection connection = new POP3Connection(props);
+ if (!connection.protocolConnect(host, port, authid, realm, username, password)) {
+ // we only add live connections to the pool. Sever the connections and
+ // allow it to go free.
+ connection.closeServerConnection();
+ return null;
+ }
+ // just return this connection
+ return connection;
+ }
+
+
+ /**
+ * Get a connection from the pool. We try to retrieve a live
+ * connection, but we test the connection's liveness before
+ * returning one. If we don't have a viable connection in
+ * the pool, we'll create a new one. The returned connection
+ * will be in the authenticated state already.
+ *
+ * @return A POP3Connection object that is connected to the server.
+ */
+ public synchronized POP3Connection getConnection() throws MessagingException {
+ // if we have an available one (common when opening the INBOX), just return it
+ POP3Connection connection = availableConnection;
+
+ if (connection != null) {
+ availableConnection = null;
+ return connection;
+ }
+ // we need an additional connection...rare, but it can happen if we've closed the INBOX folder.
+ return createPoolConnection();
+ }
+
+
+ /**
+ * Return a connection to the connection pool.
+ *
+ * @param connection The connection getting returned.
+ *
+ * @exception MessagingException
+ */
+ public synchronized void releaseConnection(POP3Connection connection) throws MessagingException
+ {
+ // we're generally only called if the store needed to talk to the server and
+ // then returned the connection to the pool. So it's pretty likely that we'll just cache this
+ if (availableConnection == null) {
+ availableConnection = connection;
+ }
+ else {
+ // got too many connections created...not sure how, but get rid of this one.
+ connection.close();
+ }
+ }
+
+
+ /**
+ * Close the entire connection pool.
+ *
+ * @exception MessagingException
+ */
+ public synchronized void close() throws MessagingException {
+ // we'll on have the single connection in reserver
+ if (availableConnection != null) {
+ availableConnection.close();
+ availableConnection = null;
+ }
+ // turn out the lights, hang the closed sign on the wall.
+ closed = true;
+ }
+}
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ListResponse.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
similarity index 85%
rename from geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ListResponse.java
rename to geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
index 448322a..d1b2066 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ListResponse.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
@@ -17,14 +17,15 @@
* under the License.
*/
-package org.apache.geronimo.javamail.store.pop3.response;
+package org.apache.geronimo.javamail.store.pop3.connection;
-import java.util.Vector;
+import java.io.ByteArrayInputStream;
+
+import java.util.ArrayList;
+import java.util.List;
import javax.mail.MessagingException;
-import org.apache.geronimo.javamail.store.pop3.POP3Response;
-
/**
* This class adds functionality to the basic response by parsing the reply for
* LIST command and obtaining specific information about the msgnum and the
@@ -37,13 +38,13 @@
* @version $Rev$ $Date$
*/
-public class POP3ListResponse extends DefaultPOP3Response {
+public class POP3ListResponse extends POP3Response {
private int msgnum = 0;
private int size = 0;
- private Vector multipleMsgs = null;
+ private List multipleMsgs = null;
POP3ListResponse(POP3Response baseRes) throws MessagingException {
super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
@@ -57,12 +58,12 @@
try {
msgnum = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
- throw new MessagingException("Invalid response for STAT command", e);
+ throw new MessagingException("Invalid response for LIST command", e);
}
try {
size = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
- throw new MessagingException("Invalid response for STAT command", e);
+ throw new MessagingException("Invalid response for LIST command", e);
}
} else {
int totalMsgs = 0;
@@ -70,10 +71,9 @@
try {
totalMsgs = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
- throw new MessagingException("Invalid response for STAT command", e);
+ throw new MessagingException("Invalid response for LIST command", e);
}
- multipleMsgs = new Vector(totalMsgs);
- multipleMsgs.setSize(totalMsgs);
+ multipleMsgs = new ArrayList(totalMsgs);
// Todo : multi-line response parsing
}
@@ -92,7 +92,7 @@
* Messages can be accessed by multipleMsgs.getElementAt(msgnum)
*
*/
- public Vector getMultipleMessageDetails() {
+ public List getMultipleMessageDetails() {
return multipleMsgs;
}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java
new file mode 100644
index 0000000..2351187
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.javamail.store.pop3.connection;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+
+import org.apache.geronimo.mail.util.Base64;
+
+/**
+ * This class provides the basic implementation for the POP3Response.
+ *
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @version $Rev$ $Date$
+ */
+
+public class POP3Response implements POP3Constants {
+
+ private int status = ERR;
+
+ private String firstLine;
+
+ private byte[] data;
+
+ POP3Response(int status, String firstLine, byte []data) {
+ this.status = status;
+ this.firstLine = firstLine;
+ this.data = data;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public ByteArrayInputStream getContentStream() {
+ return new ByteArrayInputStream(data);
+ }
+
+ public String getFirstLine() {
+ return firstLine;
+ }
+
+ public boolean isError() {
+ return status == ERR;
+ }
+
+ public boolean isChallenge() {
+ return status == CHALLENGE;
+ }
+
+ /**
+ * Decode the message portion of a continuation challenge response.
+ *
+ * @return The byte array containing the decoded data.
+ */
+ public byte[] decodeChallengeResponse()
+ {
+ // the challenge response is a base64 encoded string...
+ return Base64.decode(firstLine.trim());
+ }
+
+}
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java
similarity index 91%
rename from geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.java
rename to geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java
index 4fceb84..dbd3e53 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java
@@ -17,12 +17,10 @@
* under the License.
*/
-package org.apache.geronimo.javamail.store.pop3.response;
+package org.apache.geronimo.javamail.store.pop3.connection;
import javax.mail.MessagingException;
-import org.apache.geronimo.javamail.store.pop3.POP3Response;
-
/**
* This class adds functionality to the basic response by parsing the status
* line and obtaining specific information about num of msgs and the size
@@ -33,7 +31,7 @@
* @version $Rev$ $Date$
*/
-public class POP3StatusResponse extends DefaultPOP3Response {
+public class POP3StatusResponse extends POP3Response {
private int numMessages = 0;
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java
deleted file mode 100644
index c8183ec..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.message;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-
-import javax.mail.Flags;
-import javax.mail.Folder;
-import javax.mail.IllegalWriteException;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.event.MessageChangedEvent;
-import javax.mail.internet.InternetHeaders;
-import javax.mail.internet.MimeMessage;
-
-import org.apache.geronimo.javamail.store.pop3.POP3CommandFactory;
-import org.apache.geronimo.javamail.store.pop3.POP3Connection;
-import org.apache.geronimo.javamail.store.pop3.POP3Folder;
-import org.apache.geronimo.javamail.store.pop3.POP3Response;
-import org.apache.geronimo.javamail.store.pop3.response.POP3ListResponse;
-import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
-
-/**
- * POP3 implementation of javax.mail.internet.MimeMessage
- *
- * Only the most basic information is given and Message objects created here is
- * a light-weight reference to the actual Message As per the JavaMail spec items
- * from the actual message will get filled up on demand
- *
- * If some other items are obtained from the server as a result of one call,
- * then the other details are also processed and filled in. For ex if RETR is
- * called then header information will also be processed in addition to the
- * content
- *
- * @version $Rev$ $Date$
- */
-public class POP3Message extends MimeMessage {
-
- private POP3Connection pop3Con;
-
- private int msgSize = -1;
-
- private int headerSize = -1;
-
- // We can't use header bcos it's already initialize to
- // to an empty InternetHeader
- private InputStream rawHeaders;
-
- // used to force loading of headers again
- private boolean loadHeaders = true;
-
- // to get accessed to the debug setting and log
- private Session session;
-
- protected POP3Message(Folder folder, int msgnum, Session session, POP3Connection pop3Con) {
- super(folder, msgnum);
- this.pop3Con = pop3Con;
- this.session = session;
- }
-
- /**
- * @see javax.mail.internet.MimeMessage#getContentStream()
- */
- protected InputStream getContentStream() throws MessagingException {
- POP3Response msgResponse = null;
- try {
- msgResponse = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_RETR(msgnum));
- } catch (Exception e) {
- e.printStackTrace();
- }
- loadHeaders = true;
- loadHeaders(msgResponse.getData());
- loadContent(msgResponse.getData());
-
- return contentStream;
- }
-
- public void setFlags(Flags newFlags, boolean set) throws MessagingException {
- Flags oldFlags = (Flags) flags.clone();
- super.setFlags(newFlags, set);
-
- if (!flags.equals(oldFlags)) {
- ((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this);
- }
- }
-
- protected void loadHeaders(InputStream in) throws MessagingException {
- if (loadHeaders || rawHeaders == null) {
- rawHeaders = in;
- headers = new InternetHeaders(rawHeaders);
- loadHeaders = false;
- }
- }
-
- protected void loadContent(InputStream stream) throws MessagingException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- int byteRead = stream.read();
- int lastByte = -1;
- for (; byteRead > 0;) {
- if (byteRead == ' ' && lastByte == '\n') {
- break;
- }
- lastByte = byteRead;
- byteRead = stream.read();
- }
-
- for (; stream.available() > 0;) {
- out.write(stream.read());
- }
-
- contentStream = new ByteArrayInputStream(out.toByteArray());
- msgSize = contentStream.available();
-
- } catch (IOException e) {
-
- throw new MessagingException("Error loading content info", e);
- }
- }
-
- public int getSize() throws MessagingException {
- if (msgSize >= 0) {
- return msgSize;
- }
- try {
-
- if (msgSize < 0) {
- if (rawHeaders == null) {
- loadHeaders();
- }
- POP3ListResponse res = (POP3ListResponse) POP3ResponseFactory.getListResponse(pop3Con
- .sendCommand(POP3CommandFactory.getCOMMAND_LIST(msgnum)));
- msgSize = res.getSize() - headerSize;
- }
- return msgSize;
- } catch (MessagingException ex) {
- throw new MessagingException("error getting size", ex);
- }
- }
-
- /**
- * notice that we pass zero as the no of lines from the message,as it
- * doesn't serv any purpose to get only a certain number of lines.
- *
- * However this maybe important if a mail client only shows 3 or 4 lines of
- * the message in the list and then when the user clicks they would load the
- * message on demand.
- *
- */
- protected void loadHeaders() throws MessagingException {
- POP3Response msgResponse = null;
- try {
-
- msgResponse = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_TOP(msgnum, 0));
- } catch (Exception e) {
- e.printStackTrace();
- }
- loadHeaders(msgResponse.getData());
- }
-
- /***************************************************************************
- * Following is a set of methods that deal with headers I have tried to use
- * the bare minimum
- *
- * Used sun's POP3 impl & JavaMail API as a guide in decided which methods
- * are important.
- **************************************************************************/
-
- public String[] getHeader(String name) throws MessagingException {
- if (rawHeaders == null)
- loadHeaders();
- return headers.getHeader(name);
- }
-
- public String getHeader(String name, String delimiter) throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getHeader(name, delimiter);
- }
-
- public Enumeration getAllHeaders() throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getAllHeaders();
- }
-
- public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getMatchingHeaders(names);
- }
-
- public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getNonMatchingHeaders(names);
- }
-
- public Enumeration getAllHeaderLines() throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getAllHeaderLines();
- }
-
- public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getMatchingHeaderLines(names);
- }
-
- public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
- if (headers == null)
- loadHeaders();
- return headers.getNonMatchingHeaderLines(names);
- }
-
- // the following are overrides for header modification methods. These
- // messages are read only,
- // so the headers cannot be modified.
- public void addHeader(String name, String value) throws MessagingException {
- throw new IllegalWriteException("POP3 messages are read-only");
- }
-
- public void setHeader(String name, String value) throws MessagingException {
- throw new IllegalWriteException("POP3 messages are read-only");
- }
-
- public void removeHeader(String name) throws MessagingException {
- throw new IllegalWriteException("POP3 messages are read-only");
- }
-
- public void addHeaderLine(String line) throws MessagingException {
- throw new IllegalWriteException("POP3 messages are read-only");
- }
-
- /**
- * We cannot modify these messages
- */
- public void saveChanges() throws MessagingException {
- throw new IllegalWriteException("POP3 messages are read-only");
- }
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.java
deleted file mode 100644
index 7c8d493..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.message;
-
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.internet.MimeMessage.RecipientType;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Connection;
-import org.apache.geronimo.javamail.store.pop3.POP3Folder;
-
-/**
- * Fctory class to create POP3Messages based on the fetch profile
- *
- * @version $Rev$ $Date$
- */
-public final class POP3MessageFactory {
-
- /**
- * Creates a basic method with no items, the items will be loaded on demand
- *
- * @param folder
- * @param session
- * @param pop3Con
- * @param msgNum
- * @return
- */
- public static Message createMessage(POP3Folder folder, Session session, POP3Connection pop3Con, int msgNum) {
- return new POP3Message(folder, msgNum, session, pop3Con);
- }
-
- /**
- * Created in response to <cpde>FetchProfile.ENVELOPE</code>
- */
- public static Message createMessageWithEvelope(POP3Message msg) throws MessagingException {
- msg.getAllHeaders();
- msg.getSender();
- msg.getSentDate();
- msg.getSubject();
- msg.getReplyTo();
- msg.getReceivedDate();
- msg.getRecipients(RecipientType.TO);
-
- return msg;
- }
-
- /**
- * Created in response to <code>FetchProfile.CONTENT_INFO</code>
- */
- public static Message createMessageWithContentInfo(POP3Message msg) throws MessagingException {
- msg.getContentType();
- msg.getDisposition();
- msg.getDescription();
- msg.getSize();
- msg.getLineCount();
-
- return msg;
- }
-
- /**
- * Created in response to <code>FetchProfile.FLAGS</code>
- */
- public static Message createMessageWithFlags(POP3Message msg) throws MessagingException {
- msg.getFlags();
- return msg;
- }
-
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.java
deleted file mode 100644
index 17ca20d..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.message;
-
-import javax.mail.Folder;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Connection;
-
-/**
- * light-weight Message object will be created in response to
- * FetchProfile.CONTENT_INFO other details will be filled on demand *
- *
- * @version $Rev$ $Date$
- *
- */
-
-public class POP3MessageWithContentInfo extends POP3Message {
-
- public POP3MessageWithContentInfo(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
- throws MessagingException {
- super(folder, msgnum, null, pop3Con);
- this.getContentType();
- this.getDisposition();
- this.getDescription();
- this.getSize();
- this.getLineCount();
- }
-
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.java
deleted file mode 100644
index bc05d39..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.message;
-
-import javax.mail.Folder;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Connection;
-
-/**
- * light-weight Message object will be created in response to
- * FetchProfile.ENVELOPE other details will be filled on demand *
- *
- * @version $Rev$ $Date$
- */
-
-public class POP3MessageWithEnvelope extends POP3Message {
-
- protected POP3MessageWithEnvelope(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
- throws MessagingException {
- super(folder, msgnum, session, pop3Con);
- this.getAllHeaders();
- this.getSender();
- this.getSentDate();
- this.getSubject();
- this.getReplyTo();
- this.getReceivedDate();
- this.getRecipients(RecipientType.TO);
- }
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.java
deleted file mode 100644
index 2c6ac64..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.message;
-
-import javax.mail.Folder;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Connection;
-
-/**
- * light-weight Message object will be created in response to FetchProfile.FLAGS
- * other details will be filled on demand *
- *
- * @version $Rev$ $Date$
- */
-
-public class POP3MessageWithFlags extends POP3Message {
-
- protected POP3MessageWithFlags(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
- throws MessagingException {
- super(folder, msgnum, session, pop3Con);
- this.getFlags();
- }
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.java
deleted file mode 100644
index 43e33ed..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.response;
-
-import java.io.InputStream;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Constants;
-import org.apache.geronimo.javamail.store.pop3.POP3Response;
-
-/**
- * This class provides the basic implementation for the POP3Response.
- *
- * @see org.apache.geronimo.javamail.store.pop3.POP3Response
- * @version $Rev$ $Date$
- */
-
-public class DefaultPOP3Response implements POP3Response, POP3Constants {
-
- private int status = ERR;
-
- private String firstLine;
-
- private InputStream data;
-
- DefaultPOP3Response(int status, String firstLine, InputStream data) {
- this.status = status;
- this.firstLine = firstLine;
- this.data = data;
- }
-
- public int getStatus() {
- return status;
- }
-
- public InputStream getData() {
- return data;
- }
-
- public String getFirstLine() {
- return firstLine;
- }
-
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.java
deleted file mode 100644
index 5412746..0000000
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.geronimo.javamail.store.pop3.response;
-
-import java.io.InputStream;
-
-import javax.mail.MessagingException;
-
-import org.apache.geronimo.javamail.store.pop3.POP3Constants;
-import org.apache.geronimo.javamail.store.pop3.POP3Response;
-
-/**
- * This factory provides a uniform way of handling the creation of response
- * objects.
- *
- * @version $Rev$ $Date$
- */
-
-public final class POP3ResponseFactory implements POP3Constants {
-
- public static POP3Response getDefaultResponse(int status, String line, InputStream data) {
- return new DefaultPOP3Response(status, line, data);
- }
-
- public static POP3Response getStatusResponse(POP3Response baseRes) throws MessagingException {
- return new POP3StatusResponse(baseRes);
- }
-
- public static POP3Response getListResponse(POP3Response baseRes) throws MessagingException {
- return new POP3StatusResponse(baseRes);
- }
-
-}
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java
new file mode 100644
index 0000000..5241e78
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.javamail.util;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * An implementation of an OutputStream that performs MIME linebreak
+ * canonicalization and "byte-stuff" so that data content does not get mistaken
+ * for a message data-end marker (CRLF.CRLF)l
+ *
+ * @version $Rev$ $Date$
+ */
+public class MIMEInputReader extends Reader {
+
+ // the wrappered output stream.
+ protected Reader source;
+
+ // a flag to indicate we've just processed a line break. This is used for
+ // byte stuffing purposes. This
+ // is initially true, because if the first character of the content is a
+ // period, we need to byte-stuff
+ // immediately.
+ protected boolean atLineBreak = true;
+ // we've hit the terminating marker on the data
+ protected boolean endOfData = false;
+
+
+ /**
+ * Create an input reader that reads from the source input reader
+ *
+ * @param out
+ * The wrapped Reader
+ */
+ public MIMEInputReader(Reader source) {
+ this.source = source;
+ }
+
+ /**
+ * Concrete implementation of the Reader read()
+ * abstract method. This appears to be the only
+ * abstract method, so all of the other reads must
+ * funnel through this method.
+ *
+ * @param buffer The buffer to fill.
+ * @param off The offset to start adding characters.
+ * @param len The number of requested characters.
+ *
+ * @return The actual count of characters read. Returns -1
+ * if we hit an EOF without reading any characters.
+ * @exception IOException
+ */
+ public int read(char buffer[], int off, int len) throws IOException {
+ // we've been asked for nothing, we'll return nothing.
+ if (len == 0) {
+ return 0;
+ }
+
+ // have we hit the end of data? Return a -1 indicator
+ if (endOfData) {
+ return -1;
+ }
+
+ // number of bytes read
+ int bytesRead = 0;
+
+ int lastRead;
+
+ while (bytesRead < len && (lastRead = source.read()) >= 0) {
+ // We are checking for the end of a multiline response
+ // the format is .CRLF
+
+ // we also have to check for byte-stuffing situation
+ // where we remove a leading period.
+ if (atLineBreak && lastRead == '.') {
+ // step to the next character
+ lastRead = source.read();
+ // we have ".CR"...this is our end of stream
+ // marker. Consume the LF from the reader and return
+ if (lastRead == '\r') {
+ source.read();
+ // no more reads from this point.
+ endOfData = true;
+ break;
+ }
+ // the next character SHOULD be a ".". We swallow the first
+ // dot and just write the next character to the buffer
+ atLineBreak = false;
+ }
+ else if (lastRead == '\n') {
+ // hit an end-of-line marker?
+ // remember we just had a line break
+ atLineBreak = true;
+ }
+ else
+ {
+ // something other than a line break character
+ atLineBreak = false;
+ }
+ // add the character to the buffer
+ buffer[off++] = (char)lastRead;
+ bytesRead++;
+ }
+
+ // we must have had an EOF condition of some sort
+ if (bytesRead == 0) {
+ return -1;
+ }
+ // return the actual length read in
+ return bytesRead;
+ }
+
+ /**
+ * Close the stream. This is a NOP for this stream.
+ *
+ * @exception IOException
+ */
+ public void close() throws IOException {
+ // does nothing
+ }
+}
+
diff --git a/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers b/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
index 15287a7..80f342e 100644
--- a/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
+++ b/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
@@ -35,6 +35,7 @@
protocol=nntp-post; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPTransport; vendor=Apache Software Foundation; version=1.0
protocol=nntp; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPStore; vendor=Apache Software Foundation; version=1.0
protocol=pop3; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3Store; vendor=Apache Software Foundation; version=1.0
+protocol=pop3s; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3SSLStore; vendor=Apache Software Foundation; version=1.0
protocol=imap; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPStore; vendor=Apache Software Foundation; version=1.0
protocol=imaps; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPSSLStore; vendor=Apache Software Foundation; version=1.0