001    /*
002     * Copyright 2001-2005 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.net.pop3;
017    
018    import java.io.BufferedReader;
019    import java.io.BufferedWriter;
020    import java.io.EOFException;
021    import java.io.IOException;
022    import java.io.InputStreamReader;
023    import java.io.OutputStreamWriter;
024    import java.lang.StringBuffer;
025    import java.util.Enumeration;
026    import java.util.Vector;
027    import org.apache.commons.net.MalformedServerReplyException;
028    import org.apache.commons.net.ProtocolCommandListener;
029    import org.apache.commons.net.ProtocolCommandSupport;
030    import org.apache.commons.net.SocketClient;
031    
032    /***
033     * The POP3 class is not meant to be used by itself and is provided
034     * only so that you may easily implement your own POP3 client if
035     * you so desire.  If you have no need to perform your own implementation,
036     * you should use {@link org.apache.commons.net.pop3.POP3Client}.
037     * <p>
038     * Rather than list it separately for each method, we mention here that
039     * every method communicating with the server and throwing an IOException
040     * can also throw a
041     * {@link org.apache.commons.net.MalformedServerReplyException}
042     * , which is a subclass
043     * of IOException.  A MalformedServerReplyException will be thrown when
044     * the reply received from the server deviates enough from the protocol
045     * specification that it cannot be interpreted in a useful manner despite
046     * attempts to be as lenient as possible.
047     * <p>
048     * <p>
049     * @author Daniel F. Savarese
050     * @see POP3Client
051     * @see org.apache.commons.net.MalformedServerReplyException
052     ***/
053    
054    public class POP3 extends SocketClient
055    {
056        /*** The default POP3 port.  Set to 110 according to RFC 1288. ***/
057        public static final int DEFAULT_PORT = 110;
058        /***
059         * A constant representing the state where the client is not yet connected
060         * to a POP3 server.
061         ***/
062        public static final int DISCONNECTED_STATE = -1;
063        /***  A constant representing the POP3 authorization state. ***/
064        public static final int AUTHORIZATION_STATE = 0;
065        /***  A constant representing the POP3 transaction state. ***/
066        public static final int TRANSACTION_STATE = 1;
067        /***  A constant representing the POP3 update state. ***/
068        public static final int UPDATE_STATE = 2;
069    
070        static final String _OK = "+OK";
071        static final String _ERROR = "-ERR";
072    
073        // We have to ensure that the protocol communication is in ASCII
074        // but we use ISO-8859-1 just in case 8-bit characters cross
075        // the wire.
076        private static final String __DEFAULT_ENCODING = "ISO-8859-1";
077    
078        private int __popState;
079        private BufferedWriter __writer;
080        private StringBuffer __commandBuffer;
081    
082        BufferedReader _reader;
083        int _replyCode;
084        String _lastReplyLine;
085        Vector _replyLines;
086    
087        /***
088         * A ProtocolCommandSupport object used to manage the registering of
089         * ProtocolCommandListeners and te firing of ProtocolCommandEvents.
090         ***/
091        protected ProtocolCommandSupport _commandSupport_;
092    
093        /***
094         * The default POP3Client constructor.  Initializes the state
095         * to <code>DISCONNECTED_STATE</code>.
096         ***/
097        public POP3()
098        {
099            setDefaultPort(DEFAULT_PORT);
100            __commandBuffer = new StringBuffer();
101            __popState = DISCONNECTED_STATE;
102            _reader = null;
103            __writer = null;
104            _replyLines = new Vector();
105            _commandSupport_ = new ProtocolCommandSupport(this);
106        }
107    
108        private void __getReply() throws IOException
109        {
110            String line;
111    
112            _replyLines.setSize(0);
113            line = _reader.readLine();
114    
115            if (line == null)
116                throw new EOFException("Connection closed without indication.");
117    
118            if (line.startsWith(_OK))
119                _replyCode = POP3Reply.OK;
120            else if (line.startsWith(_ERROR))
121                _replyCode = POP3Reply.ERROR;
122            else
123                throw new
124                MalformedServerReplyException(
125                    "Received invalid POP3 protocol response from server.");
126    
127            _replyLines.addElement(line);
128            _lastReplyLine = line;
129    
130            if (_commandSupport_.getListenerCount() > 0)
131                _commandSupport_.fireReplyReceived(_replyCode, getReplyString());
132        }
133    
134    
135        /***
136         * Performs connection initialization and sets state to
137         * <code> AUTHORIZATION_STATE </code>.
138         ***/
139        protected void _connectAction_() throws IOException
140        {
141            super._connectAction_();
142            _reader =
143              new BufferedReader(new InputStreamReader(_input_,
144                                                       __DEFAULT_ENCODING));
145            __writer =
146              new BufferedWriter(new OutputStreamWriter(_output_,
147                                                        __DEFAULT_ENCODING));
148            __getReply();
149            setState(AUTHORIZATION_STATE);
150        }
151    
152    
153        /***
154         * Adds a ProtocolCommandListener.  Delegates this task to
155         * {@link #_commandSupport_  _commandSupport_ }.
156         * <p>
157         * @param listener  The ProtocolCommandListener to add.
158         ***/
159        public void addProtocolCommandListener(ProtocolCommandListener listener)
160        {
161            _commandSupport_.addProtocolCommandListener(listener);
162        }
163    
164        /***
165         * Removes a ProtocolCommandListener.  Delegates this task to
166         * {@link #_commandSupport_  _commandSupport_ }.
167         * <p>
168         * @param listener  The ProtocolCommandListener to remove.
169         ***/
170        public void removeProtocolCommandistener(ProtocolCommandListener listener)
171        {
172            _commandSupport_.removeProtocolCommandListener(listener);
173        }
174    
175    
176        /***
177         * Sets POP3 client state.  This must be one of the
178         * <code>_STATE</code> constants.
179         * <p>
180         * @param state  The new state.
181         ***/
182        public void setState(int state)
183        {
184            __popState = state;
185        }
186    
187    
188        /***
189         * Returns the current POP3 client state.
190         * <p>
191         * @return The current POP3 client state.
192         ***/
193        public int getState()
194        {
195            return __popState;
196        }
197    
198    
199        /***
200         * Retrieves the additional lines of a multi-line server reply.
201         ***/
202        public void getAdditionalReply() throws IOException
203        {
204            String line;
205    
206            line = _reader.readLine();
207            while (line != null)
208            {
209                _replyLines.addElement(line);
210                if (line.equals("."))
211                    break;
212                line = _reader.readLine();
213            }
214        }
215    
216    
217        /***
218         * Disconnects the client from the server, and sets the state to
219         * <code> DISCONNECTED_STATE </code>.  The reply text information
220         * from the last issued command is voided to allow garbage collection
221         * of the memory used to store that information.
222         * <p>
223         * @exception IOException  If there is an error in disconnecting.
224         ***/
225        public void disconnect() throws IOException
226        {
227            super.disconnect();
228            _reader = null;
229            __writer = null;
230            _lastReplyLine = null;
231            _replyLines.setSize(0);
232            setState(DISCONNECTED_STATE);
233        }
234    
235    
236        /***
237         * Sends a command an arguments to the server and returns the reply code.
238         * <p>
239         * @param command  The POP3 command to send.
240         * @param args     The command arguments.
241         * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
242         ***/
243        public int sendCommand(String command, String args) throws IOException
244        {
245            String message;
246    
247            __commandBuffer.setLength(0);
248            __commandBuffer.append(command);
249    
250            if (args != null)
251            {
252                __commandBuffer.append(' ');
253                __commandBuffer.append(args);
254            }
255            __commandBuffer.append(SocketClient.NETASCII_EOL);
256    
257            __writer.write(message = __commandBuffer.toString());
258            __writer.flush();
259    
260            if (_commandSupport_.getListenerCount() > 0)
261                _commandSupport_.fireCommandSent(command, message);
262    
263            __getReply();
264            return _replyCode;
265        }
266    
267        /***
268         * Sends a command with no arguments to the server and returns the
269         * reply code.
270         * <p>
271         * @param command  The POP3 command to send.
272         * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
273         ***/
274        public int sendCommand(String command) throws IOException
275        {
276            return sendCommand(command, null);
277        }
278    
279        /***
280         * Sends a command an arguments to the server and returns the reply code.
281         * <p>
282         * @param command  The POP3 command to send
283         *                  (one of the POP3Command constants).
284         * @param args     The command arguments.
285         * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
286         ***/
287        public int sendCommand(int command, String args) throws IOException
288        {
289            return sendCommand(POP3Command._commands[command], args);
290        }
291    
292        /***
293         * Sends a command with no arguments to the server and returns the
294         * reply code.
295         * <p>
296         * @param command  The POP3 command to send
297         *                  (one of the POP3Command constants).
298         * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
299         ***/
300        public int sendCommand(int command) throws IOException
301        {
302            return sendCommand(POP3Command._commands[command], null);
303        }
304    
305    
306        /***
307         * Returns an array of lines received as a reply to the last command
308         * sent to the server.  The lines have end of lines truncated.  If
309         * the reply is a single line, but its format ndicates it should be
310         * a multiline reply, then you must call
311         * {@link #getAdditionalReply  getAdditionalReply() } to
312         * fetch the rest of the reply, and then call <code>getReplyStrings</code>
313         * again.  You only have to worry about this if you are implementing
314         * your own client using the {@link #sendCommand  sendCommand } methods.
315         * <p>
316         * @return The last server response.
317         ***/
318        public String[] getReplyStrings()
319        {
320            String[] lines;
321            lines = new String[_replyLines.size()];
322            _replyLines.copyInto(lines);
323            return lines;
324        }
325    
326        /***
327         * Returns the reply to the last command sent to the server.
328         * The value is a single string containing all the reply lines including
329         * newlines.  If the reply is a single line, but its format ndicates it
330         * should be a multiline reply, then you must call
331         * {@link #getAdditionalReply  getAdditionalReply() } to
332         * fetch the rest of the reply, and then call <code>getReplyString</code>
333         * again.  You only have to worry about this if you are implementing
334         * your own client using the {@link #sendCommand  sendCommand } methods.
335         * <p>
336         * @return The last server response.
337         ***/
338        public String getReplyString()
339        {
340            Enumeration en;
341            StringBuffer buffer = new StringBuffer(256);
342    
343            en = _replyLines.elements();
344            while (en.hasMoreElements())
345            {
346                buffer.append((String)en.nextElement());
347                buffer.append(SocketClient.NETASCII_EOL);
348            }
349    
350            return buffer.toString();
351        }
352    
353    }
354