KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > mail > ImapSession


1 /*
2  * ImapSession.java
3  *
4  * Copyright (C) 2000-2002 Peter Graves
5  * $Id: ImapSession.java,v 1.2 2002/09/25 13:58:11 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j.mail;
23
24 import java.io.IOException JavaDoc;
25 import java.io.InterruptedIOException JavaDoc;
26 import java.io.OutputStreamWriter JavaDoc;
27 import java.net.Socket JavaDoc;
28 import java.net.SocketException JavaDoc;
29 import org.armedbear.j.Debug;
30 import org.armedbear.j.Editor;
31 import org.armedbear.j.FastStringBuffer;
32 import org.armedbear.j.Log;
33 import org.armedbear.j.Netrc;
34 import org.armedbear.j.SocketConnection;
35 import org.armedbear.j.Utilities;
36
37 public final class ImapSession
38 {
39     private static final int DEFAULT_PORT = 143;
40
41     // States.
42
private static final int DISCONNECTED = 0;
43     private static final int NONAUTHENTICATED = 1;
44     private static final int AUTHENTICATED = 2;
45     private static final int SELECTED = 3;
46
47     // Responses.
48
public static final int OK = 0;
49     public static final int NO = 1;
50     public static final int BAD = 2;
51     public static final int PREAUTH = 3;
52     public static final int BYE = 4;
53
54     public static final int UNKNOWN = -1;
55
56     private final String JavaDoc host;
57     private final String JavaDoc user;
58     private final String JavaDoc password;
59     private final int port;
60
61     private String JavaDoc tunnelHost;
62     private int tunnelPort = -1;
63     private ImapMailbox mailbox;
64     private int state;
65     private boolean echo;
66     private Socket JavaDoc socket;
67     private MailReader reader;
68     private OutputStreamWriter JavaDoc writer;
69     private String JavaDoc folderName;
70     private boolean readOnly;
71     private int messageCount;
72     private int recent;
73     private int uidValidity;
74     private int uidNext;
75     private String JavaDoc errorText;
76     private long lastErrorMillis;
77
78     private ImapSession(ImapURL url, String JavaDoc user, String JavaDoc password)
79     {
80         this.host = url.getHost();
81         this.folderName = url.getFolderName();
82         this.port = url.getPort();
83         this.user = user;
84         this.password = password;
85     }
86
87     public final void setMailbox(ImapMailbox mb)
88     {
89         if (mailbox != null)
90             Debug.bug();
91         mailbox = mb;
92     }
93
94     public final boolean isReadOnly()
95     {
96         return readOnly;
97     }
98
99     public final String JavaDoc getHost()
100     {
101         return host;
102     }
103
104     public final int getPort()
105     {
106         return port;
107     }
108
109     public final String JavaDoc getUser()
110     {
111         return user;
112     }
113
114     public final String JavaDoc getFolderName()
115     {
116         return folderName;
117     }
118
119     public final int getMessageCount()
120     {
121         return messageCount;
122     }
123
124     public final int getRecent()
125     {
126         return recent;
127     }
128
129     public final int getUidNext()
130     {
131         return uidNext;
132     }
133
134     public final int getUidValidity()
135     {
136         return uidValidity;
137     }
138
139     public final String JavaDoc getErrorText()
140     {
141         return errorText;
142     }
143
144     public void setTunnel(String JavaDoc tunnel)
145     {
146         if (tunnel != null) {
147             tunnel = tunnel.trim();
148             int colon = tunnel.indexOf(':');
149             if (colon > 0) {
150                 tunnelHost = tunnel.substring(0, colon);
151                 try {
152                     tunnelPort =
153                         Integer.parseInt(tunnel.substring(colon+1).trim());
154                 }
155                 catch (NumberFormatException JavaDoc e) {
156                     Log.error(e);
157                     tunnelHost = null;
158                     tunnelPort = -1;
159                 }
160             }
161         }
162         Log.debug("setTunnel host = |" + tunnelHost + "| port = " + tunnelPort);
163     }
164
165     public synchronized final long getLastErrorMillis()
166     {
167         return lastErrorMillis;
168     }
169
170     private synchronized final void setLastErrorMillis(long millis)
171     {
172         Log.debug("setLastErrorMillis");
173         lastErrorMillis = millis;
174     }
175
176     public static ImapSession getSession(ImapURL url)
177     {
178         if (url.getHost() == null || url.getFolderName() == null)
179             return null;
180         String JavaDoc user = url.getUser();
181         if (user == null)
182             user = System.getProperty("user.name");
183         return getSession(url, user);
184     }
185
186     public static ImapSession getSession(ImapURL url, String JavaDoc user)
187     {
188         String JavaDoc password = Netrc.getPassword(url.getHost(), user);
189         if (password == null)
190             return null;
191         return new ImapSession(url, user, password);
192     }
193
194     public static ImapSession getSession(ImapURL url, String JavaDoc user,
195         String JavaDoc password)
196     {
197         return new ImapSession(url, user, password);
198     }
199
200     public boolean verifyConnected()
201     {
202         if (state != DISCONNECTED) {
203             // We send a NOOP command here both to verify that we really are
204
// connected and to give the server a chance to report changes to
205
// the mailbox.
206
if (writeTagged("noop")) {
207                 if (getResponse() == OK)
208                     return true;
209             }
210         }
211         return connect();
212     }
213
214     public boolean verifySelected(String JavaDoc folderName)
215     {
216         if (state == SELECTED && this.folderName.equals(folderName))
217             return true;
218         return reselect(folderName);
219     }
220
221     private boolean connect()
222     {
223         socket = null;
224         errorText = null;
225         final String JavaDoc h; // Host.
226
final int p; // Port.
227
if (tunnelHost != null && tunnelPort > 0) {
228             h = tunnelHost;
229             p = tunnelPort;
230             Log.debug("connect using tunnel h = " + h + " p = " + p);
231         } else {
232             h = host;
233             p = port;
234         }
235         SocketConnection sc = new SocketConnection(h, p, 30000, 200, null);
236         Log.debug("connecting to " + h + " on port " + p);
237         socket = sc.connect();
238         if (socket == null) {
239             errorText = sc.getErrorText();
240             Log.error(errorText);
241             return false;
242         }
243         Log.debug("connected to " + host);
244         boolean succeeded = false;
245         boolean oldEcho = echo;
246         if (Editor.isDebugEnabled())
247             echo = true;
248         try {
249             reader = new MailReader(socket.getInputStream());
250             writer = new OutputStreamWriter JavaDoc(socket.getOutputStream(),
251                 "iso-8859-1");
252             if (readLine() != null) {
253                 writeTagged("login " + user + " " + password);
254                 if (getResponse() == OK) {
255                     state = AUTHENTICATED;
256                     succeeded = true;
257                 }
258             }
259         }
260         catch (IOException JavaDoc e) {
261             Log.error(e);
262         }
263         finally {
264             echo = oldEcho;
265         }
266         return succeeded;
267     }
268
269     private static final String JavaDoc UIDVALIDITY = "* OK [UIDVALIDITY ";
270     private static final String JavaDoc UIDNEXT = "* OK [UIDNEXT ";
271
272     public boolean reselect(String JavaDoc folderName)
273     {
274         long start = System.currentTimeMillis();
275         boolean oldEcho = echo;
276         if (Editor.isDebugEnabled())
277             echo = true;
278         try {
279             if (state < AUTHENTICATED ||
280                 !writeTagged("select \"" + folderName + "\"")) {
281                 connect();
282                 if (state < AUTHENTICATED)
283                     return false;
284                 if (!writeTagged("select \"" + folderName + "\""))
285                     return false;
286             }
287             while (true) {
288                 String JavaDoc s = readLine();
289                 if (s == null) {
290                     Log.error("ImapSession.reselect readLine returned null");
291                     this.folderName = null;
292                     messageCount = 0;
293                     recent = 0;
294                     return false;
295                 }
296                 final String JavaDoc upper = s.toUpperCase();
297                 if (upper.startsWith("* NO ")) {
298                     mailbox.setStatusText(s.substring(5).trim());
299                     continue;
300                 }
301                 if (upper.startsWith("* ")) {
302                     if (upper.endsWith(" EXISTS")) {
303                         processUntaggedResponse(s);
304                         continue;
305                     }
306                     if (upper.endsWith(" RECENT")) {
307                         processUntaggedResponse(s);
308                         continue;
309                     }
310                 }
311                 if (upper.startsWith(UIDVALIDITY)) {
312                     uidValidity =
313                         Utilities.parseInt(s.substring(UIDVALIDITY.length()));
314                     continue;
315                 }
316                 if (upper.startsWith(UIDNEXT)) {
317                     uidNext = Utilities.parseInt(s.substring(UIDNEXT.length()));
318                     continue;
319                 }
320                 if (upper.startsWith(lastTag + " ")) {
321                     // Tagged response.
322
if (upper.startsWith(lastTag + " OK ")) {
323                         // Success!
324
state = SELECTED;
325                         this.folderName = folderName;
326                         readOnly = upper.indexOf("[READ-ONLY]") >= 0;
327                         if (readOnly) {
328                             Log.warn("reselect mailbox " + folderName +
329                                 " is read-only!");
330                             setLastErrorMillis(System.currentTimeMillis());
331                         } else {
332                             Log.debug("reselect mailbox " + folderName +
333                                 " is read-write");
334                         }
335                         return true;
336                     } else {
337                         // Error!
338
Log.error("SELECT " + folderName + " failed");
339                         // Don't assume old folder is still selected.
340
state = AUTHENTICATED;
341                         this.folderName = null;
342                         messageCount = 0;
343                         recent = 0;
344                         return false;
345                     }
346                 }
347             }
348         }
349         catch (Exception JavaDoc e) {
350             Log.error(e);
351             disconnect();
352             this.folderName = null;
353             messageCount = 0;
354             recent = 0;
355             return false;
356         }
357         finally {
358             echo = oldEcho;
359             long elapsed = System.currentTimeMillis() - start;
360             Log.debug("ImapSession.reselect " + folderName + " " + elapsed + " ms");
361         }
362     }
363
364     public boolean close()
365     {
366         if (state != SELECTED) {
367             Log.debug("already closed");
368             return true;
369         }
370         // State may be set to DISCONNECTED if writeTagged() or getResponse()
371
// fails.
372
if (writeTagged("close") && getResponse() == OK)
373             state = AUTHENTICATED;
374         folderName = null;
375         messageCount = 0;
376         recent = 0;
377         return true;
378     }
379
380     public void logout()
381     {
382         Log.debug("ImapSession.logout " + host);
383         if (state > DISCONNECTED) {
384             if (writeTagged("logout"))
385                 getResponse();
386             if (state > DISCONNECTED)
387                 disconnect();
388         }
389     }
390
391     public synchronized void disconnect()
392     {
393         if (socket != null) {
394             try {
395                 socket.close();
396             }
397             catch (IOException JavaDoc e) {
398                 Log.error(e);
399             }
400             socket = null;
401             reader = null;
402             writer = null;
403         }
404         state = DISCONNECTED;
405     }
406
407     public String JavaDoc readLine()
408     {
409         if (reader == null)
410             return null;
411         try {
412             String JavaDoc s = reader.readLine();
413             if (s != null) {
414                 if (echo)
415                     Log.debug("<== " + s);
416                 if (s.startsWith(lastTag + " "))
417                     errorText = getTaggedResponseText(s);
418             }
419             return s;
420         }
421         catch (InterruptedIOException JavaDoc e) {
422             // Timed out.
423
Log.error(e);
424             setLastErrorMillis(System.currentTimeMillis());
425             disconnect();
426             return null;
427         }
428         catch (SocketException JavaDoc e) {
429             // This is what happens when the user cancels and we close the
430
// socket.
431
disconnect();
432             return null;
433         }
434         catch (IOException JavaDoc e) {
435             Log.error(e);
436             setLastErrorMillis(System.currentTimeMillis());
437             disconnect();
438             return null;
439         }
440     }
441
442     public void uidStore(int uid, String JavaDoc arg)
443     {
444         FastStringBuffer sb = new FastStringBuffer("uid store ");
445         sb.append(uid);
446         sb.append(' ');
447         sb.append(arg);
448         writeTagged(sb.toString());
449     }
450
451     public void uidStore(String JavaDoc messageSet, String JavaDoc arg)
452     {
453         FastStringBuffer sb = new FastStringBuffer("uid store ");
454         sb.append(messageSet);
455         sb.append(' ');
456         sb.append(arg);
457         writeTagged(sb.toString());
458     }
459
460     public boolean writeTagged(String JavaDoc s)
461     {
462         if (writer == null)
463             return false;
464         // Store command.
465
int index = s.indexOf(' ');
466         final String JavaDoc lastCommand = index >= 0 ? s.substring(0, index) : s;
467         // Prepend tag.
468
s = nextTag() + " " + s;
469         if (echo) {
470             if (lastCommand.equalsIgnoreCase("login")) {
471                 index = s.lastIndexOf(' ');
472                 if (index >= 0)
473                     Log.debug("==> " + s.substring(0, index));
474                 else
475                     Log.debug("==> " + s);
476             } else
477                 Log.debug("==> " + s);
478         }
479         try {
480             writer.write(s.concat("\r\n"));
481             writer.flush();
482             return true;
483         }
484         catch (IOException JavaDoc e) {
485             Log.error(e);
486             disconnect();
487             return false;
488         }
489     }
490
491     public int getResponse()
492     {
493         while (true) {
494             String JavaDoc s = readLine();
495             if (s == null)
496                 return BYE;
497             String JavaDoc upper = s.toUpperCase();
498             int index = upper.indexOf("[ALERT]");
499             if (index >= 0)
500                 mailbox.setAlertText(s.substring(index+7).trim());
501             if (upper.startsWith("* BYE ")) {
502                 Log.debug("getResponse |" + s + "|");
503                 disconnect();
504                 return BYE;
505             }
506             if (upper.startsWith(lastTag + " ")) {
507                 upper = upper.substring(lastTag.length() + 1);
508                 if (upper.startsWith("OK "))
509                     return OK;
510                 if (upper.startsWith("NO ")) {
511                     mailbox.setStatusText(s.substring(3).trim());
512                     return NO;
513                 }
514                 if (upper.startsWith("BAD "))
515                     return BAD;
516                 // According to Section 7.1 of RFC 2060, PREAUTH and BYE are
517
// always untagged, so we should never encounter the following
518
// cases.
519
if (upper.startsWith("PREAUTH"))
520                     return PREAUTH;
521                 if (upper.startsWith("BYE")) {
522                     disconnect();
523                     return BYE;
524                 }
525                 return UNKNOWN;
526             }
527             processUntaggedResponse(s);
528         }
529     }
530
531     private void processUntaggedResponse(String JavaDoc s)
532     {
533         Log.debug("processUntaggedResponse |" + s + "|");
534         if (s.startsWith("* ")) {
535             final String JavaDoc upper = s.toUpperCase();
536             if (upper.endsWith(" EXISTS")) {
537                 try {
538                     messageCount = Integer.parseInt(upper.substring(2, upper.length()-7));
539                     Log.debug("messageCount = " + messageCount);
540                 }
541                 catch (NumberFormatException JavaDoc e) {
542                     Log.error(e);
543                 }
544             } else if (upper.endsWith(" RECENT")) {
545                 try {
546                     recent = Integer.parseInt(upper.substring(2, upper.length()-7));
547                     Log.debug("recent = " + recent);
548                 }
549                 catch (NumberFormatException JavaDoc e) {
550                     Log.error(e);
551                 }
552             } else if (upper.endsWith(" EXPUNGE")) {
553                 try {
554                     int messageNumber =
555                         Integer.parseInt(upper.substring(2, upper.length()-8));
556                     if (messageCount > 0) {
557                         --messageCount;
558                         Log.debug("EXPUNGE messageCount = " + messageCount);
559                     } else
560                         Log.error("received untagged EXPUNGE response with messageCount = " +
561                             messageCount);
562                     mailbox.messageExpunged(messageNumber);
563                 }
564                 catch (NumberFormatException JavaDoc e) {
565                     Log.error(e);
566                 }
567             }
568         }
569     }
570
571     private static String JavaDoc getTaggedResponseText(String JavaDoc taggedResponse)
572     {
573         // Skip tag.
574
int index = taggedResponse.indexOf(' ');
575         if (index < 0)
576             return null;
577         // Skip status string ("OK", "NO", etc.).
578
index = taggedResponse.indexOf(' ', index + 1);
579         if (index < 0)
580             return null;
581         // Return rest of line.
582
return taggedResponse.substring(index + 1);
583     }
584
585     private int tagNumber;
586     private String JavaDoc lastTag;
587
588     public final String JavaDoc lastTag()
589     {
590         return lastTag;
591     }
592
593     // Upper case.
594
private final String JavaDoc nextTag()
595     {
596         return lastTag = "A".concat(String.valueOf(++tagNumber));
597     }
598
599     public final void setEcho(boolean b)
600     {
601         echo = b;
602     }
603
604     protected void finalize() throws Throwable JavaDoc
605     {
606         Log.debug("ImapSession.finalize " + host);
607         super.finalize();
608     }
609 }
610
Popular Tags