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