1 21 22 package org.armedbear.j.mail; 23 24 import java.io.IOException ; 25 import java.io.InterruptedIOException ; 26 import java.io.OutputStreamWriter ; 27 import java.net.Socket ; 28 import java.net.SocketException ; 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 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 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 host; 57 private final String user; 58 private final String password; 59 private final int port; 60 61 private String tunnelHost; 62 private int tunnelPort = -1; 63 private ImapMailbox mailbox; 64 private int state; 65 private boolean echo; 66 private Socket socket; 67 private MailReader reader; 68 private OutputStreamWriter writer; 69 private String folderName; 70 private boolean readOnly; 71 private int messageCount; 72 private int recent; 73 private int uidValidity; 74 private int uidNext; 75 private String errorText; 76 private long lastErrorMillis; 77 78 private ImapSession(ImapURL url, String user, String 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 getHost() 100 { 101 return host; 102 } 103 104 public final int getPort() 105 { 106 return port; 107 } 108 109 public final String getUser() 110 { 111 return user; 112 } 113 114 public final String 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 getErrorText() 140 { 141 return errorText; 142 } 143 144 public void setTunnel(String 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 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 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 user) 187 { 188 String 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 user, 195 String password) 196 { 197 return new ImapSession(url, user, password); 198 } 199 200 public boolean verifyConnected() 201 { 202 if (state != DISCONNECTED) { 203 if (writeTagged("noop")) { 207 if (getResponse() == OK) 208 return true; 209 } 210 } 211 return connect(); 212 } 213 214 public boolean verifySelected(String 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 h; final int p; 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 (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 e) { 261 Log.error(e); 262 } 263 finally { 264 echo = oldEcho; 265 } 266 return succeeded; 267 } 268 269 private static final String UIDVALIDITY = "* OK [UIDVALIDITY "; 270 private static final String UIDNEXT = "* OK [UIDNEXT "; 271 272 public boolean reselect(String 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 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 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 if (upper.startsWith(lastTag + " OK ")) { 323 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 Log.error("SELECT " + folderName + " failed"); 339 state = AUTHENTICATED; 341 this.folderName = null; 342 messageCount = 0; 343 recent = 0; 344 return false; 345 } 346 } 347 } 348 } 349 catch (Exception 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 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 e) { 398 Log.error(e); 399 } 400 socket = null; 401 reader = null; 402 writer = null; 403 } 404 state = DISCONNECTED; 405 } 406 407 public String readLine() 408 { 409 if (reader == null) 410 return null; 411 try { 412 String 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 e) { 422 Log.error(e); 424 setLastErrorMillis(System.currentTimeMillis()); 425 disconnect(); 426 return null; 427 } 428 catch (SocketException e) { 429 disconnect(); 432 return null; 433 } 434 catch (IOException e) { 435 Log.error(e); 436 setLastErrorMillis(System.currentTimeMillis()); 437 disconnect(); 438 return null; 439 } 440 } 441 442 public void uidStore(int uid, String 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 messageSet, String 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 s) 461 { 462 if (writer == null) 463 return false; 464 int index = s.indexOf(' '); 466 final String lastCommand = index >= 0 ? s.substring(0, index) : s; 467 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 e) { 485 Log.error(e); 486 disconnect(); 487 return false; 488 } 489 } 490 491 public int getResponse() 492 { 493 while (true) { 494 String s = readLine(); 495 if (s == null) 496 return BYE; 497 String 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 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 s) 532 { 533 Log.debug("processUntaggedResponse |" + s + "|"); 534 if (s.startsWith("* ")) { 535 final String 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 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 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 e) { 565 Log.error(e); 566 } 567 } 568 } 569 } 570 571 private static String getTaggedResponseText(String taggedResponse) 572 { 573 int index = taggedResponse.indexOf(' '); 575 if (index < 0) 576 return null; 577 index = taggedResponse.indexOf(' ', index + 1); 579 if (index < 0) 580 return null; 581 return taggedResponse.substring(index + 1); 583 } 584 585 private int tagNumber; 586 private String lastTag; 587 588 public final String lastTag() 589 { 590 return lastTag; 591 } 592 593 private final String 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 605 { 606 Log.debug("ImapSession.finalize " + host); 607 super.finalize(); 608 } 609 } 610 | Popular Tags |