KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > pop3 > Protocol


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)Protocol.java 1.26 05/08/29
24  *
25  * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.pop3;
29
30 import java.util.*;
31 import java.net.*;
32 import java.io.*;
33 import java.security.*;
34
35 import com.sun.mail.util.LineInputStream;
36 import com.sun.mail.util.SocketFetcher;
37 import javax.mail.util.SharedByteArrayInputStream;
38
39 class Response {
40     boolean ok = false; // true if "+OK"
41
String JavaDoc data = null; // rest of line after "+OK" or "-ERR"
42
InputStream bytes = null; // all the bytes from a multi-line response
43
}
44
45 /**
46  * This class provides a POP3 connection and implements
47  * the POP3 protocol requests.
48  *
49  * APOP support courtesy of "chamness".
50  *
51  * @author Bill Shannon
52  */

53 class Protocol {
54     private Socket socket; // POP3 socket
55
private DataInputStream input; // input buf
56
private PrintWriter output; // output buf
57
private static final int POP3_PORT = 110; // standard POP3 port
58
private static final String JavaDoc CRLF = "\r\n";
59     private boolean debug = false;
60     private PrintStream out;
61     private String JavaDoc apopChallenge = null;
62
63     /**
64      * Open a connection to the POP3 server.
65      */

66     Protocol(String JavaDoc host, int port, boolean debug, PrintStream out,
67             Properties props, String JavaDoc prefix, boolean isSSL)
68             throws IOException {
69     this.debug = debug;
70     this.out = out;
71     Response r;
72     String JavaDoc apop = props.getProperty(prefix + ".apop.enable");
73     boolean enableAPOP = apop != null && apop.equalsIgnoreCase("true");
74     try {
75         if (port == -1)
76         port = POP3_PORT;
77         if (debug)
78         out.println("DEBUG POP3: connecting to host \"" + host +
79                 "\", port " + port + ", isSSL " + isSSL);
80
81         socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
82
83         input = new DataInputStream(
84         new BufferedInputStream(socket.getInputStream()));
85         output = new PrintWriter(
86             new BufferedWriter(
87                 new OutputStreamWriter(socket.getOutputStream(),
88                 "iso-8859-1")));
89                 // should be US-ASCII, but not all JDK's support
90

91         r = simpleCommand(null);
92     } catch (IOException ioe) {
93         try {
94         socket.close();
95         } finally {
96         throw ioe;
97         }
98     }
99
100     if (!r.ok) {
101         try {
102         socket.close();
103         } finally {
104         throw new IOException("Connect failed");
105         }
106     }
107     if (enableAPOP) {
108         int challStart = r.data.indexOf('<'); // start of challenge
109
int challEnd = r.data.indexOf('>', challStart); // end of challenge
110
if (challStart != -1 && challEnd != -1)
111         apopChallenge = r.data.substring(challStart, challEnd + 1);
112         if (debug)
113         out.println("DEBUG POP3: APOP challenge: " + apopChallenge);
114     }
115     }
116
117     protected void finalize() throws Throwable JavaDoc {
118     super.finalize();
119     if (socket != null) { // Forgot to logout ?!
120
quit();
121     }
122     }
123
124     /**
125      * Login to the server, using the USER and PASS commands.
126      */

127     synchronized String JavaDoc login(String JavaDoc user, String JavaDoc password)
128                     throws IOException {
129     Response r;
130     String JavaDoc dpw = null;
131     if (apopChallenge != null)
132         dpw = getDigest(password);
133     if (apopChallenge != null && dpw != null) {
134         r = simpleCommand("APOP " + user + " " + dpw);
135     } else {
136         r = simpleCommand("USER " + user);
137         if (!r.ok)
138         return r.data != null ? r.data : "USER command failed";
139         r = simpleCommand("PASS " + password);
140     }
141     if (!r.ok)
142         return r.data != null ? r.data : "login failed";
143     return null;
144     }
145
146     /**
147      * Gets the APOP message digest.
148      * From RFC 1939:
149      *
150      * The 'digest' parameter is calculated by applying the MD5
151      * algorithm [RFC1321] to a string consisting of the timestamp
152      * (including angle-brackets) followed by a shared secret.
153      * The 'digest' parameter itself is a 16-octet value which is
154      * sent in hexadecimal format, using lower-case ASCII characters.
155      *
156      * @param password The APOP password
157      * @return The APOP digest or an empty string if an error occurs.
158      */

159     private String JavaDoc getDigest(String JavaDoc password) {
160     String JavaDoc key = apopChallenge + password;
161     byte[] digest;
162     try {
163         MessageDigest md = MessageDigest.getInstance("MD5");
164         digest = md.digest(key.getBytes("iso-8859-1")); // XXX
165
} catch (NoSuchAlgorithmException nsae) {
166         return null;
167     } catch (UnsupportedEncodingException uee) {
168         return null;
169     }
170     return toHex(digest);
171     }
172
173     private static char[] digits = {
174     '0', '1', '2', '3', '4', '5', '6', '7',
175     '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
176     };
177
178     /**
179      * Convert a byte array to a string of hex digits representing the bytes.
180      */

181     private static String JavaDoc toHex(byte[] bytes) {
182     char[] result = new char[bytes.length * 2];
183
184     for (int index = 0, i = 0; index < bytes.length; index++) {
185         int temp = bytes[index] & 0xFF;
186         result[i++] = digits[temp >> 4];
187         result[i++] = digits[temp & 0xF];
188     }
189     return new String JavaDoc(result);
190     }
191
192     /**
193      * Close down the connection, sending the QUIT command
194      * if expunge is true.
195      */

196     synchronized boolean quit() throws IOException {
197     boolean ok = false;
198     try {
199         Response r = simpleCommand("QUIT");
200         ok = r.ok;
201     } finally {
202         try {
203         socket.close();
204         } finally {
205         socket = null;
206         input = null;
207         output = null;
208         }
209     }
210     return ok;
211     }
212
213     /**
214      * Return the total number of messages and mailbox size,
215      * using the STAT command.
216      */

217     synchronized Status stat() throws IOException {
218     Response r = simpleCommand("STAT");
219     Status s = new Status();
220     if (r.ok && r.data != null) {
221         try {
222         StringTokenizer st = new StringTokenizer(r.data);
223         s.total = Integer.parseInt(st.nextToken());
224         s.size = Integer.parseInt(st.nextToken());
225         } catch (Exception JavaDoc e) {
226         }
227     }
228     return s;
229     }
230
231     /**
232      * Return the size of the message using the LIST command.
233      */

234     synchronized int list(int msg) throws IOException {
235     Response r = simpleCommand("LIST " + msg);
236     int size = -1;
237     if (r.ok && r.data != null) {
238         try {
239         StringTokenizer st = new StringTokenizer(r.data);
240         st.nextToken(); // skip message number
241
size = Integer.parseInt(st.nextToken());
242         } catch (Exception JavaDoc e) {
243         }
244     }
245     return size;
246     }
247
248     /**
249      * Return the size of all messages using the LIST command.
250      */

251     synchronized InputStream list() throws IOException {
252     Response r = multilineCommand("LIST", 128); // 128 == output size est
253
return r.bytes;
254     }
255
256     /**
257      * Retrieve the specified message.
258      * Given an estimate of the message's size we can be more efficient,
259      * preallocating the array and returning a SharedInputStream to allow
260      * us to share the array.
261      */

262     synchronized InputStream retr(int msg, int size) throws IOException {
263     Response r = multilineCommand("RETR " + msg, size);
264     return r.bytes;
265     }
266
267     /**
268      * Return the message header and the first n lines of the message.
269      */

270     synchronized InputStream top(int msg, int n) throws IOException {
271     Response r = multilineCommand("TOP " + msg + " " + n, 0);
272     return r.bytes;
273     }
274
275     /**
276      * Delete (permanently) the specified message.
277      */

278     synchronized boolean dele(int msg) throws IOException {
279     Response r = simpleCommand("DELE " + msg);
280     return r.ok;
281     }
282
283     /**
284      * Return the UIDL string for the message.
285      */

286     synchronized String JavaDoc uidl(int msg) throws IOException {
287     Response r = simpleCommand("UIDL " + msg);
288     if (!r.ok)
289         return null;
290     int i = r.data.indexOf(' ');
291     if (i > 0)
292         return r.data.substring(i + 1);
293     else
294         return null;
295     }
296
297     /**
298      * Return the UIDL strings for all messages.
299      * The UID for msg #N is returned in uids[N-1].
300      */

301     synchronized boolean uidl(String JavaDoc[] uids) throws IOException {
302     Response r = multilineCommand("UIDL", 15 * uids.length);
303     if (!r.ok)
304         return false;
305     LineInputStream lis = new LineInputStream(r.bytes);
306     String JavaDoc line = null;
307     while ((line = lis.readLine()) != null) {
308         int i = line.indexOf(' ');
309         if (i < 1 || i >= line.length())
310         continue;
311         int n = Integer.parseInt(line.substring(0, i));
312         if (n > 0 && n <= uids.length)
313         uids[n - 1] = line.substring(i + 1);
314     }
315     return true;
316     }
317
318     /**
319      * Do a NOOP.
320      */

321     synchronized boolean noop() throws IOException {
322     Response r = simpleCommand("NOOP");
323     return r.ok;
324     }
325
326     /**
327      * Do an RSET.
328      */

329     synchronized boolean rset() throws IOException {
330     Response r = simpleCommand("RSET");
331     return r.ok;
332     }
333
334     /**
335      * Issue a simple POP3 command and return the response.
336      */

337     private Response simpleCommand(String JavaDoc cmd) throws IOException {
338     if (socket == null)
339         throw new IOException("Folder is closed"); // XXX
340
if (cmd != null) {
341         if (debug)
342         out.println("C: " + cmd);
343         cmd += CRLF;
344         output.print(cmd); // do it in one write
345
output.flush();
346     }
347     String JavaDoc line = input.readLine(); // XXX - readLine is deprecated
348
if (line == null) {
349         if (debug)
350         out.println("S: EOF");
351         throw new EOFException("EOF on socket");
352     }
353     if (debug)
354         out.println("S: " + line);
355     Response r = new Response();
356     if (line.startsWith("+OK"))
357         r.ok = true;
358     else if (line.startsWith("-ERR"))
359         r.ok = false;
360     else
361         throw new IOException("Unexpected response: " + line);
362     int i;
363     if ((i = line.indexOf(' ')) >= 0)
364         r.data = line.substring(i + 1);
365     return r;
366     }
367
368     /**
369      * Issue a POP3 command that expects a multi-line response.
370      * <code>size</code> is an estimate of the response size.
371      */

372     private Response multilineCommand(String JavaDoc cmd, int size) throws IOException {
373     Response r = simpleCommand(cmd);
374     if (!r.ok)
375         return (r);
376
377     SharedByteArrayOutputStream buf = new SharedByteArrayOutputStream(size);
378     int b, lastb = '\n';
379     while ((b = input.read()) >= 0) {
380         if (lastb == '\n' && b == '.') {
381         if (debug)
382             out.write(b);
383         b = input.read();
384         if (b == '\r') {
385             if (debug)
386             out.write(b);
387             // end of response, consume LF as well
388
b = input.read();
389             if (debug)
390             out.write(b);
391             break;
392         }
393         }
394         buf.write(b);
395         if (debug)
396         out.write(b);
397         lastb = b;
398     }
399     if (b < 0)
400         throw new EOFException("EOF on socket");
401     r.bytes = buf.toStream();
402     return r;
403     }
404 }
405
406 /**
407  * A ByteArrayOutputStream that allows us to share the byte array
408  * rather than copy it. Eventually could replace this with something
409  * that doesn't require a single contiguous byte array.
410  */

411 class SharedByteArrayOutputStream extends ByteArrayOutputStream {
412     public SharedByteArrayOutputStream(int size) {
413     super(size);
414     }
415
416     public InputStream toStream() {
417     return new SharedByteArrayInputStream(buf, 0, count);
418     }
419 }
420
Popular Tags