1 package org.columba.mail.imap; 19 20 import java.io.IOException ; 21 import java.io.InputStream ; 22 import java.nio.charset.Charset ; 23 import java.text.DateFormat ; 24 import java.text.MessageFormat ; 25 import java.util.ArrayList ; 26 import java.util.Arrays ; 27 import java.util.Collections ; 28 import java.util.Iterator ; 29 import java.util.LinkedList ; 30 import java.util.List ; 31 import java.util.Observable ; 32 import java.util.Observer ; 33 import java.util.logging.Logger ; 34 35 import javax.net.ssl.SSLException; 36 import javax.swing.JOptionPane ; 37 38 import org.columba.api.command.IStatusObservable; 39 import org.columba.core.base.Blowfish; 40 import org.columba.core.base.ListTools; 41 import org.columba.core.command.CommandCancelledException; 42 import org.columba.core.filter.FilterCriteria; 43 import org.columba.core.filter.FilterRule; 44 import org.columba.core.filter.IFilterCriteria; 45 import org.columba.core.filter.IFilterRule; 46 import org.columba.core.gui.base.MultiLineLabel; 47 import org.columba.core.gui.frame.FrameManager; 48 import org.columba.mail.config.AccountItem; 49 import org.columba.mail.config.ImapItem; 50 import org.columba.mail.config.IncomingItem; 51 import org.columba.mail.filter.MailFilterCriteria; 52 import org.columba.mail.folder.IMailbox; 53 import org.columba.mail.folder.command.MarkMessageCommand; 54 import org.columba.mail.folder.headercache.CachedHeaderfields; 55 import org.columba.mail.folder.imap.IMAPFolder; 56 import org.columba.mail.folder.imap.IMAPRootFolder; 57 import org.columba.mail.gui.util.PasswordDialog; 58 import org.columba.mail.message.ColumbaHeader; 59 import org.columba.mail.message.IHeaderList; 60 import org.columba.mail.util.AuthenticationManager; 61 import org.columba.mail.util.AuthenticationSecurityComparator; 62 import org.columba.mail.util.MailResourceLoader; 63 import org.columba.ristretto.auth.AuthenticationException; 64 import org.columba.ristretto.auth.AuthenticationFactory; 65 import org.columba.ristretto.imap.IMAPDate; 66 import org.columba.ristretto.imap.IMAPDisconnectedException; 67 import org.columba.ristretto.imap.IMAPException; 68 import org.columba.ristretto.imap.IMAPFlags; 69 import org.columba.ristretto.imap.IMAPHeader; 70 import org.columba.ristretto.imap.IMAPListener; 71 import org.columba.ristretto.imap.IMAPProtocol; 72 import org.columba.ristretto.imap.IMAPResponse; 73 import org.columba.ristretto.imap.ListInfo; 74 import org.columba.ristretto.imap.MailboxStatus; 75 import org.columba.ristretto.imap.NamespaceCollection; 76 import org.columba.ristretto.imap.SearchKey; 77 import org.columba.ristretto.imap.SequenceSet; 78 import org.columba.ristretto.io.SequenceInputStream; 79 import org.columba.ristretto.message.Header; 80 import org.columba.ristretto.message.MailboxInfo; 81 import org.columba.ristretto.message.MimeTree; 82 83 106 public class IMAPServer implements IMAPListener, Observer , IImapServer { 107 108 private static final int STEP_SIZE = 50; 109 110 private static final int UID_FETCH_STEPS = 500; 111 112 private static final Logger LOG = Logger.getLogger("org.columba.mail.imap"); 113 114 private static final Charset UTF8 = Charset.forName("UTF-8"); 115 116 private static final Charset DEFAULT = Charset.forName(System 117 .getProperty("file.encoding")); 118 119 122 private IMailbox selectedFolder; 123 124 127 private MailboxStatus selectedStatus; 128 129 134 private String delimiter; 135 136 139 private IMAPProtocol protocol; 140 141 144 private ImapItem item; 145 146 private MimeTree aktMimeTree; 147 148 private Object aktMessageUid; 149 150 private MailboxInfo messageFolderInfo; 151 152 private boolean firstLogin; 153 154 boolean usingSSL; 155 156 String [] capabilities; 157 158 private long lastCommunication; 159 160 private IStatusObservable observable; 161 162 private int MIN_IDLE = 30 * 1000; 165 private boolean updatesEnabled = true; 168 169 private IFirstLoginAction firstLoginAction; 170 171 private IUpdateFlagAction updateFlagAction; 172 173 private IExistsChangedAction existsChangedAction; 174 175 private boolean statusDirty; 176 177 public IMAPServer(ImapItem item) { 178 this.item = item; 179 180 item.getRoot().addObserver(this); 181 182 protocol = new IMAPProtocol(item.get("host"), item.getInteger("port")); 184 protocol.addIMAPListener(this); 186 187 firstLogin = true; 188 usingSSL = false; 189 190 lastCommunication = System.currentTimeMillis(); 191 } 192 193 196 protected IStatusObservable getObservable() { 197 return observable; 198 } 199 200 203 protected void printStatusMessage(String message) { 204 if (getObservable() != null) { 205 getObservable().setMessage(item.get("host") + ": " + message); 206 } 207 } 208 209 218 public String getDelimiter() throws IOException , IMAPException, 219 CommandCancelledException { 220 if (delimiter == null) { 221 delimiter = fetchDelimiter(); 223 } 224 225 return delimiter; 226 } 227 228 231 public void logout() throws Exception { 232 if (protocol.getState() != IMAPProtocol.NOT_CONNECTED) { 233 try { 234 protocol.logout(); 235 } catch (Exception e) { 236 } 238 } 239 } 240 241 private void openConnection() throws IOException , IMAPException, 242 CommandCancelledException { 243 printStatusMessage(MailResourceLoader.getString("statusbar", "message", 244 "connecting")); 245 246 int sslType = item.getIntegerWithDefault("ssl_type", IncomingItem.TLS); 247 boolean sslEnabled = item.getBoolean("enable_ssl"); 248 249 if (sslEnabled && sslType == IncomingItem.IMAPS_POP3S) { 251 try { 252 protocol.openSSLPort(); 253 usingSSL = true; 254 } catch (SSLException e) { 255 int result = showErrorDialog(MailResourceLoader.getString( 256 "dialog", "error", "ssl_handshake_error") 257 + ": " 258 + e.getLocalizedMessage() 259 + "\n" 260 + MailResourceLoader.getString("dialog", "error", 261 "ssl_turn_off")); 262 263 if (result == 1) { 264 throw new CommandCancelledException(); 265 } 266 267 item.setBoolean("enable_ssl", false); 269 item.setInteger("port", IMAPProtocol.DEFAULT_PORT); 270 271 protocol.openPort(); 273 } 274 } else { 275 protocol.openPort(); 276 } 277 278 if (!usingSSL && sslEnabled && sslType == IncomingItem.TLS) { 280 if (isSupported("STLS") || isSupported("STARTTLS") || (capabilities.length == 0)) { 282 try { 283 protocol.startTLS(); 284 285 usingSSL = true; 286 LOG.info("Switched to SSL"); 287 } catch (IOException e) { 288 int result = showErrorDialog(MailResourceLoader.getString( 289 "dialog", "error", "ssl_handshake_error") 290 + ": " 291 + e.getLocalizedMessage() 292 + "\n" 293 + MailResourceLoader.getString("dialog", "error", 294 "ssl_turn_off")); 295 296 if (result == 1) { 297 throw new CommandCancelledException(); 298 } 299 300 item.setBoolean("enable_ssl", false); 302 303 protocol.openPort(); 305 } catch (IMAPException e) { 306 int result = showErrorDialog(MailResourceLoader.getString( 307 "dialog", "error", "ssl_not_supported") 308 + "\n" 309 + MailResourceLoader.getString("dialog", "error", 310 "ssl_turn_off")); 311 312 if (result == 1) { 313 throw new CommandCancelledException(); 314 } 315 316 item.setBoolean("enable_ssl", false); 318 } 319 } else { 320 int result = showErrorDialog(MailResourceLoader.getString( 322 "dialog", "error", "ssl_not_supported") 323 + "\n" 324 + MailResourceLoader.getString("dialog", "error", 325 "ssl_turn_off")); 326 327 if (result == 1) { 328 throw new CommandCancelledException(); 329 } 330 331 item.setBoolean("enable_ssl", false); 333 } 334 } 335 336 } 337 338 341 public List checkSupportedAuthenticationMethods() throws IOException { 342 343 ArrayList supportedMechanisms = new ArrayList (); 344 supportedMechanisms.add(new Integer (AuthenticationManager.LOGIN)); 346 347 try { 348 String serverSaslMechansims[] = getCapas("AUTH"); 349 StringBuffer oneLine = new StringBuffer ("AUTH"); 351 for (int i = 0; i < serverSaslMechansims.length; i++) { 352 oneLine.append(' '); 353 oneLine.append(serverSaslMechansims[i].substring(5)); } 357 358 if (serverSaslMechansims != null) { 360 List authMechanisms = AuthenticationFactory.getInstance() 361 .getSupportedMechanisms(oneLine.toString()); 362 Iterator it = authMechanisms.iterator(); 363 while (it.hasNext()) { 364 supportedMechanisms.add(new Integer (AuthenticationManager 365 .getSaslCode((String ) it.next()))); 366 } 367 } 368 } catch (IOException e) { 369 } 370 371 return supportedMechanisms; 372 } 373 374 378 private String [] getCapas(String command) throws IOException { 379 fetchCapas(); 380 ArrayList list = new ArrayList (); 381 382 for (int i = 0; i < capabilities.length; i++) { 383 if (capabilities[i].startsWith(command)) { 384 list.add(capabilities[i]); 385 } 386 } 387 388 return (String []) list.toArray(new String [0]); 389 } 390 391 394 public boolean isSupported(String command) throws IOException { 395 fetchCapas(); 396 397 for (int i = 0; i < capabilities.length; i++) { 398 if (capabilities[i].startsWith(command)) { 399 return true; 400 } 401 } 402 403 return false; 404 } 405 406 409 private void fetchCapas() throws IOException { 410 if (capabilities == null) { 411 try { 412 ensureConnectedState(); 413 414 capabilities = protocol.capability(); 415 } catch (IMAPException e) { 416 capabilities = new String [0]; 418 } catch (CommandCancelledException e) { 419 420 } 421 } 422 } 423 424 429 private int getLoginMethod() throws CommandCancelledException, IOException { 430 String loginMethod = item.get("login_method"); 431 int result = 0; 432 433 try { 434 result = Integer.parseInt(loginMethod); 435 } catch (NumberFormatException e) { 436 } 438 439 if (result == 0) { 440 List supported = checkSupportedAuthenticationMethods(); 441 442 if (usingSSL) { 443 result = ((Integer ) supported.get(0)).intValue(); 446 } else { 447 Collections.sort(supported, 448 new AuthenticationSecurityComparator()); 449 result = ((Integer ) supported.get(supported.size() - 1)) 450 .intValue(); 451 } 452 453 } 454 455 return result; 456 } 457 458 467 private void login() throws IOException , IMAPException, 468 CommandCancelledException { 469 PasswordDialog dialog = new PasswordDialog(); 470 ensureConnectedState(); 471 472 boolean authenticated = false; 473 boolean first = true; 474 475 char[] password = new char[0]; 476 477 printStatusMessage(MailResourceLoader.getString("statusbar", "message", 478 "authenticating")); 479 480 int loginMethod = getLoginMethod(); 481 482 if (item.get("password").length() != 0) { 484 password = Blowfish.decrypt(item.get("password")); 485 } 486 while (!authenticated) { 488 if (!first || password.length == 0) { 491 dialog.showDialog(MessageFormat.format(MailResourceLoader 493 .getString("dialog", "password", "enter_password"), 494 new Object [] { item.get("user"), item.get("host") }), 495 new String (password), item.getBoolean("save_password")); 496 if (dialog.success()) { 497 password = dialog.getPassword(); 499 500 item.setBoolean("save_password", dialog.getSave()); 502 if (dialog.getSave()) { 503 item.setString("password", Blowfish.encrypt(password)); 504 } else { 505 item.setString("password", ""); 506 } 507 } else { 508 510 throw new CommandCancelledException(); 511 } 512 } 513 514 517 try { 518 if (loginMethod == AuthenticationManager.LOGIN) { 519 protocol.login(item.get("user"), password); 520 521 authenticated = true; 524 } else { 525 try { 526 protocol.authenticate(AuthenticationManager 528 .getSaslName(loginMethod), item.get("user"), 529 password); 530 531 authenticated = true; 534 } catch (AuthenticationException e) { 535 if (e.getCause() instanceof IMAPException) 539 throw (IMAPException) e.getCause(); 540 541 int result = JOptionPane 544 .showConfirmDialog( 545 FrameManager.getInstance() 546 .getActiveFrame(), 547 new MultiLineLabel( 548 e.getMessage() 549 + "\n" 550 + MailResourceLoader 551 .getString( 552 "dialog", 553 "error", 554 "authentication_fallback_to_default")), 555 MailResourceLoader.getString("dialog", 556 "error", 557 "authentication_process_error"), 558 JOptionPane.OK_CANCEL_OPTION); 559 560 if (result == JOptionPane.OK_OPTION) { 561 loginMethod = AuthenticationManager.LOGIN; 562 item.setString("login_method", Integer 563 .toString(loginMethod)); 564 } else { 565 throw new CommandCancelledException(); 566 } 567 } 568 } 569 570 } catch (IMAPException ex) { 571 IMAPResponse response = ex.getResponse(); 573 if (response == null || !response.isNO()) { 574 throw ex; 577 } 578 } 579 first = false; 580 } 581 582 if (firstLogin) { 585 if( firstLoginAction != null) { 586 firstLoginAction.actionPerformed(); 587 } 588 } 589 590 firstLogin = false; 591 } 592 593 596 public void setFirstLoginAction( IFirstLoginAction action) { 597 this.firstLoginAction = action; 598 } 599 600 603 public void ensureSelectedState(IMAPFolder folder) throws IOException , 604 IMAPException, CommandCancelledException { 605 ensureLoginState(); 607 String path = folder.getImapPath(); 608 609 if (protocol.getState() != IMAPProtocol.SELECTED 611 || !protocol.getSelectedMailbox().equals(path)) { 612 613 printStatusMessage(MessageFormat.format(MailResourceLoader 614 .getString("statusbar", "message", "select"), 615 new Object [] { folder.getName() })); 616 617 messageFolderInfo = protocol.select(path); 619 620 folder.setReadOnly(!messageFolderInfo.isWriteAccess()); 622 623 selectedStatus = new MailboxStatus(messageFolderInfo); 625 statusDirty = false; 626 627 selectedFolder = folder; 628 629 aktMimeTree = null; 631 aktMessageUid = null; 632 } 633 } 634 635 public int getLargestRemoteUid(IMAPFolder folder) throws IOException , IMAPException, CommandCancelledException { 636 MailboxStatus status = getStatus(folder); 637 if(status.getUidNext() < 0 && status.getMessages() > 0 ) { 638 return fetchUid(new SequenceSet(status.getMessages()), folder); 639 } else { 640 return (int)(status.getUidNext() -1); 641 } 642 643 } 644 645 648 public MailboxStatus getStatus(IMAPFolder folder) throws IOException , 649 IMAPException, CommandCancelledException { 650 ensureLoginState(); 651 652 if (selectedFolder != null && selectedFolder.equals(folder) && !statusDirty) { 653 658 return selectedStatus; 659 } 660 661 if( selectedFolder == null || protocol.getState() < IMAPProtocol.SELECTED) { 662 ensureSelectedState(folder); 664 return selectedStatus; 665 } 666 667 printStatusMessage(MessageFormat.format(MailResourceLoader.getString( 668 "statusbar", "message", "status"), new Object [] { folder 669 .getName() })); 670 671 MailboxStatus result = protocol.status(folder.getImapPath(), new String [] { "MESSAGES", 672 "UIDNEXT", "RECENT", "UNSEEN", "UIDVALIDITY" }); 673 674 if( result.getUnseen() == -1) result.setUnseen(0); 676 if( result.getRecent() == -1) result.setRecent(0); 677 statusDirty = false; 678 679 return result; 680 } 681 682 686 protected String fetchDelimiter() throws IOException , IMAPException, 687 CommandCancelledException { 688 ensureLoginState(); 690 691 try { 692 ListInfo[] listInfo = protocol.list("", ""); 693 return listInfo[0].getDelimiter(); 694 } catch (IMAPDisconnectedException e1) { 695 ListInfo[] listInfo = protocol.list("", ""); 696 return listInfo[0].getDelimiter(); 697 } 698 } 699 700 703 public ListInfo[] list(String reference, String pattern) throws Exception { 704 ensureLoginState(); 705 706 try { 707 return protocol.list(reference, pattern); 708 } catch (IMAPDisconnectedException e) { 709 return protocol.list(reference, pattern); 710 } 711 } 712 713 716 public Integer append(InputStream messageSource, IMAPFlags flags, 717 IMAPFolder folder) throws Exception { 718 ensureLoginState(); 720 721 if (protocol.getState() == IMAPProtocol.SELECTED 723 && protocol.getSelectedMailbox().equals(folder)) { 724 protocol.close(); 725 } 726 727 MailboxStatus status = protocol.status(folder.getImapPath(), 728 new String [] { "UIDNEXT" }); 729 730 if (flags != null) { 731 protocol.append(folder.getImapPath(), messageSource, 732 new Object [] { flags }); 733 } else { 734 protocol.append(folder.getImapPath(), messageSource); 735 736 } 737 738 return new Integer ((int) status.getUidNext()); 739 } 740 741 744 public Integer append(InputStream messageSource, IMAPFolder folder) 745 throws Exception { 746 return append(messageSource, null, folder); 747 } 748 749 752 public void createMailbox(String mailboxName, IMAPFolder folder) 753 throws IOException , IMAPException, CommandCancelledException { 754 ensureLoginState(); 756 757 String fullName; 759 String path = (folder == null ? "" : folder.getImapPath()); 760 761 if (path.length() > 0) 762 fullName = path + getDelimiter() + mailboxName; 763 else 764 fullName = mailboxName; 765 766 if (protocol.list("", fullName).length == 0) { 768 protocol.create(fullName); 770 } 771 772 protocol.subscribe(fullName); 774 } 775 776 779 public void deleteFolder(String path) throws Exception { 780 ensureLoginState(); 782 783 if (protocol.getState() == IMAPProtocol.SELECTED 784 && protocol.getSelectedMailbox().equals(path)) { 785 protocol.close(); 786 } 787 788 protocol.unsubscribe(path); 789 790 protocol.delete(path); 791 } 792 793 796 public void renameFolder(String oldMailboxName, String newMailboxName) 797 throws IOException , IMAPException, CommandCancelledException { 798 ensureLoginState(); 800 protocol.rename(oldMailboxName, newMailboxName); 801 protocol.unsubscribe(oldMailboxName); 802 protocol.subscribe(newMailboxName); 803 } 804 805 808 public void subscribeFolder(String mailboxName) throws IOException , 809 IMAPException, CommandCancelledException { 810 ensureLoginState(); 812 813 protocol.subscribe(mailboxName); 814 } 815 816 819 public void unsubscribeFolder(String mailboxName) throws IOException , 820 IMAPException, CommandCancelledException { 821 ensureLoginState(); 823 824 protocol.unsubscribe(mailboxName); 825 } 826 827 830 public void expunge(IMAPFolder folder) throws IOException , IMAPException, 831 CommandCancelledException { 832 ensureSelectedState(folder); 833 834 updatesEnabled = false; 835 protocol.expunge(); 836 updatesEnabled = true; 837 statusDirty = true; 838 } 839 840 843 public Integer [] copy(IMAPFolder destFolder, Object [] uids, 844 IMAPFolder folder) throws Exception { 845 846 ensureSelectedState(folder); 847 848 List sortedUids = Arrays.asList(uids); 852 Collections.sort(sortedUids); 853 854 MailboxStatus statusBefore = protocol.status(destFolder.getImapPath(), 855 new String [] { "UIDNEXT" }); 856 857 protocol.uidCopy(new SequenceSet(Arrays.asList(uids)), destFolder 858 .getImapPath()); 859 860 MailboxStatus statusAfter = protocol.status(destFolder.getImapPath(), 861 new String [] { "UIDNEXT" }); 862 863 int copied = (int) (statusAfter.getUidNext() - statusBefore 865 .getUidNext()); 866 Integer [] destUids = new Integer [copied]; 867 for (int i = 0; i < copied; i++) { 868 destUids[i] = new Integer ((int) (statusBefore.getUidNext() + i)); 869 } 870 871 return destUids; 872 } 873 874 877 public int fetchUid( SequenceSet set, IMAPFolder folder ) throws IOException , IMAPException, CommandCancelledException { 878 ensureSelectedState(folder); 879 Integer [] result = protocol.fetchUid(set); 880 if( result.length == 1) 881 return result[0].intValue(); 882 else 883 return -1; 884 885 } 886 887 890 public Integer [] fetchUids(SequenceSet set, IMAPFolder folder) 891 throws IOException , IMAPException, CommandCancelledException { 892 IStatusObservable observable = getObservable(); 893 printStatusMessage(MailResourceLoader.getString("statusbar", "message", 894 "fetch_uid_list")); 895 896 ensureSelectedState(folder); 897 if (messageFolderInfo.getExists() > 0) { 898 SequenceSet[] packs = divide(set); 899 Integer [] result = new Integer [set.getLength(messageFolderInfo 900 .getExists())]; 901 902 if (observable != null) { 904 observable.setCurrent(0); 905 observable.setMax(result.length); 906 } 907 908 int pos = 0; 909 910 for (int i = 0; i < packs.length; i++) { 911 int packLength = packs[i].getLength(messageFolderInfo 912 .getExists()); 913 System.arraycopy(protocol.fetchUid(packs[i]), 0, result, pos, 914 packLength); 915 pos += packLength; 916 917 if (observable != null) { 919 observable.setCurrent(pos); 920 } 921 } 922 923 return result; 924 } else { 925 return new Integer [0]; 926 } 927 } 928 929 private SequenceSet[] divide(SequenceSet in) { 930 int length = in.getLength(messageFolderInfo.getExists()); 931 932 if (length > UID_FETCH_STEPS) { 933 int[] decomposed = in.toArray(messageFolderInfo.getExists()); 934 935 List result = new ArrayList (); 936 int pos = 0; 937 while (decomposed.length - pos > UID_FETCH_STEPS) { 939 result.add(new SequenceSet(decomposed, pos, UID_FETCH_STEPS)); 940 pos += UID_FETCH_STEPS; 941 } 942 if (decomposed.length - pos > 0) { 944 result.add(new SequenceSet(decomposed, pos, decomposed.length 945 - pos)); 946 } 947 948 return (SequenceSet[]) result.toArray(new SequenceSet[0]); 949 } else { 950 return new SequenceSet[] { in }; 951 } 952 953 } 954 955 958 public IMAPFlags[] fetchFlagsListStartFrom(int startIdx, IMAPFolder folder) 959 throws IOException , IMAPException, CommandCancelledException { 960 IStatusObservable observable = getObservable(); 961 962 ensureSelectedState(folder); 963 if (selectedStatus.getMessages() - startIdx >= 0) { 964 SequenceSet set = new SequenceSet(); 965 set.addOpenRange(startIdx); 966 967 SequenceSet[] packs = divide(set); 968 969 if (observable != null) { 971 observable.setCurrent(0); 972 observable.setMax(set.getLength(selectedStatus.getMessages())); 973 } 974 975 List allResults = new ArrayList (packs.length); 976 977 int pos = 0; 978 979 for (int i = 0; i < packs.length; i++) { 981 try { 982 IMAPFlags[] r = protocol.fetchFlags(packs[i]); 983 pos += r.length; 984 985 allResults.add(r); 986 } catch (IMAPException e) { 987 } 990 991 if (observable != null) { 993 observable.setCurrent(pos); 994 } 995 } 996 997 IMAPFlags[] result = new IMAPFlags[pos]; 999 Iterator it = allResults.iterator(); 1000 1001 pos = 0; 1002 while (it.hasNext()) { 1003 IMAPFlags[] r = (IMAPFlags[]) it.next(); 1004 System.arraycopy(r, 0, result, pos, r.length); 1005 1006 pos += r.length; 1007 } 1008 1009 return result; 1010 } else { 1011 return new IMAPFlags[0]; 1012 } 1013 } 1014 1015 1016 1019 public IMAPFlags[] fetchFlagsListStartFrom2(int startIdx, IMAPFolder folder) 1020 throws IOException , IMAPException, CommandCancelledException { 1021 ensureSelectedState(folder); 1022 if (selectedStatus.getMessages() - startIdx >= 0) { 1023 SequenceSet set = new SequenceSet(); 1024 set.add(startIdx, Math.min(startIdx + 9, selectedStatus.getMessages())); 1025 1026 IMAPFlags[] result = protocol.fetchFlags(set); 1027 1028 return result; 1029 } else { 1030 return new IMAPFlags[0]; 1031 } 1032 } 1033 1034 1037 public NamespaceCollection fetchNamespaces() throws IOException , 1038 IMAPException, CommandCancelledException { 1039 ensureLoginState(); 1040 return protocol.namespace(); 1041 } 1042 1043 1046 public void fetchHeaderList(IHeaderList headerList, List list, 1047 IMAPFolder folder) throws Exception { 1048 ensureSelectedState(folder); 1050 1051 printStatusMessage(MailResourceLoader.getString("statusbar", "message", 1052 "fetch_header_list")); 1053 1054 int count = list.size() / IMAPServer.STEP_SIZE; 1055 int rest = list.size() % IMAPServer.STEP_SIZE; 1056 getObservable().setCurrent(0); 1057 getObservable().setMax(count + 1); 1058 for (int i = 0; i < count; i++) { 1059 doFetchHeaderList(headerList, list.subList( 1060 i * IMAPServer.STEP_SIZE, (i + 1) * IMAPServer.STEP_SIZE)); 1061 getObservable().setCurrent(i); 1062 } 1063 1064 if (rest > 0) { 1065 doFetchHeaderList(headerList, list 1066 .subList(count * IMAPServer.STEP_SIZE, count 1067 * IMAPServer.STEP_SIZE + rest)); 1068 } 1069 1070 getObservable().setCurrent(count + 1); 1071 } 1072 1073 1079 private void doFetchHeaderList(IHeaderList headerList, List list) 1080 throws IOException , IMAPException { 1081 String [] headerFields = CachedHeaderfields.getDefaultHeaderfields(); 1083 1084 IMAPHeader[] headers = protocol.uidFetchHeaderFields(new SequenceSet( 1085 list), headerFields); 1086 1087 for (int i = 0; i < headers.length; i++) { 1088 ColumbaHeader header = new ColumbaHeader(headers[i].getHeader()); 1090 Object uid = headers[i].getUid(); 1091 1092 header.getAttributes().put("columba.uid", uid); 1093 header.getAttributes().put("columba.size", headers[i].getSize()); 1094 header.getAttributes().put("columba.accountuid", getAccountUid()); 1095 1096 header.getAttributes().put("columba.attachment", 1098 header.hasAttachments()); 1099 1100 String messageID = (String ) header.get("Message-Id"); 1102 if (messageID != null) 1103 header.set("Message-ID", header.get("Message-Id")); 1104 1105 headerList.add(header, uid); 1106 } 1107 } 1108 1109 protected Integer getAccountUid() { 1110 AccountItem accountItem = new AccountItem(item.getRoot().getParent()); 1111 return new Integer (accountItem.getInteger("uid")); 1112 } 1113 1114 protected synchronized void ensureConnectedState() throws CommandCancelledException, 1115 IOException , IMAPException { 1116 if (Math.abs(System.currentTimeMillis() - lastCommunication) > MIN_IDLE) { 1117 try { 1118 protocol.noop(); 1119 } catch (IOException e) { 1120 } catch (IMAPDisconnectedException e) { 1122 1123 } 1124 } 1125 1126 if (protocol.getState() < IMAPProtocol.NON_AUTHENTICATED) { 1127 printStatusMessage(MailResourceLoader 1128 .getString("statusbar", "message", "connecting")); 1129 openConnection(); 1130 } 1131 1132 lastCommunication = System.currentTimeMillis(); 1136 } 1137 1138 1143 protected void ensureLoginState() throws IOException , IMAPException, 1144 CommandCancelledException { 1145 ensureConnectedState(); 1146 1147 if (protocol.getState() < IMAPProtocol.AUTHENTICATED) { 1148 printStatusMessage(MailResourceLoader 1149 .getString("statusbar", "message", "authenticating")); 1150 login(); 1151 } 1152 } 1153 1154 1157 public MimeTree getMimeTree(Object uid, IMAPFolder folder) 1158 throws IOException , IMAPException, CommandCancelledException { 1159 try { 1160 ensureSelectedState(folder); 1161 1162 if (aktMimeTree == null || !aktMessageUid.equals(uid)) { 1164 aktMimeTree = protocol.uidFetchBodystructure(((Integer ) uid) 1165 .intValue()); 1166 aktMessageUid = uid; 1167 } 1168 1169 return aktMimeTree; 1170 } catch (IMAPDisconnectedException e) { 1171 return getMimeTree(uid, folder); 1172 } 1173 } 1174 1175 1178 public InputStream getMimePartBodyStream(Object uid, Integer [] address, 1179 IMAPFolder folder) throws IOException , IMAPException, 1180 CommandCancelledException { 1181 try { 1182 ensureSelectedState(folder); 1183 1184 return protocol.uidFetchBody(((Integer ) uid).intValue(), address); 1185 } catch (IMAPDisconnectedException e) { 1186 return getMimePartBodyStream(uid, address, folder); 1187 } 1188 } 1189 1190 1193 public Header getHeaders(Object uid, String [] keys, IMAPFolder folder) 1194 throws IOException , IMAPException, CommandCancelledException { 1195 try { 1196 ensureSelectedState(folder); 1197 1198 IMAPHeader[] headers = protocol.uidFetchHeaderFields( 1199 new SequenceSet(((Integer ) uid).intValue()), keys); 1200 1201 return headers[0].getHeader(); 1202 } catch (IMAPDisconnectedException e) { 1203 return getHeaders(uid, keys, folder); 1204 } 1205 } 1206 1207 1210 public Header getAllHeaders(Object uid, IMAPFolder folder) 1211 throws IOException , IMAPException, CommandCancelledException { 1212 try { 1213 ensureSelectedState(folder); 1214 1215 IMAPHeader[] headers = protocol.uidFetchHeader(new SequenceSet( 1216 ((Integer ) uid).intValue())); 1217 1218 return headers[0].getHeader(); 1219 } catch (IMAPDisconnectedException e) { 1220 return getAllHeaders(uid, folder); 1221 } 1222 } 1223 1224 1227 public InputStream getMimePartSourceStream(Object uid, Integer [] address, 1228 IMAPFolder folder) throws IOException , IMAPException, 1229 CommandCancelledException { 1230 try { 1231 ensureSelectedState(folder); 1232 1233 InputStream headerSource = protocol.uidFetchMimeHeaderSource( 1234 ((Integer ) uid).intValue(), address); 1235 InputStream bodySource = protocol.uidFetchBody(((Integer ) uid) 1236 .intValue(), address); 1237 1238 return new SequenceInputStream(headerSource, bodySource); 1239 } catch (IMAPDisconnectedException e) { 1240 return getMimePartSourceStream(uid, address, folder); 1241 } 1242 } 1243 1244 1247 public InputStream getMessageSourceStream(Object uid, IMAPFolder folder) 1248 throws IOException , IMAPException, CommandCancelledException { 1249 try { 1250 ensureSelectedState(folder); 1251 1252 return protocol.uidFetchMessage(((Integer ) uid).intValue()); 1253 } catch (IMAPDisconnectedException e) { 1254 return getMessageSourceStream(uid, folder); 1255 } 1256 } 1257 1258 1261 public void markMessage(Object [] uids, int variant, IMAPFolder folder) 1262 throws IOException , IMAPException, CommandCancelledException { 1263 try { 1264 ensureSelectedState(folder); 1265 1266 SequenceSet uidSet = new SequenceSet(Arrays.asList(uids)); 1267 1268 protocol.uidStore(uidSet, variant > 0, convertToFlags(variant)); 1269 1270 statusDirty = true; 1271 } catch (IMAPDisconnectedException e) { 1272 markMessage(uids, variant, folder); 1273 } 1274 } 1275 1276 1279 public void setFlags(Object [] uids, IMAPFlags flags, IMAPFolder folder) 1280 throws IOException , IMAPException, CommandCancelledException { 1281 try { 1282 ensureSelectedState(folder); 1283 SequenceSet uidSet = new SequenceSet(Arrays.asList(uids)); 1284 1285 protocol.uidStore(uidSet, true, flags); 1286 } catch (IMAPDisconnectedException e) { 1287 setFlags(uids, flags, folder); 1288 } 1289 } 1290 1291 1294 public List search(Object [] uids, IFilterRule filterRule, IMAPFolder folder) 1295 throws Exception { 1296 LinkedList result = new LinkedList (search(filterRule, folder)); 1297 1298 ListTools.intersect(result, Arrays.asList(uids)); 1299 1300 return result; 1301 } 1302 1303 1306 public int getIndex(Integer uid, IMAPFolder folder) throws IOException , 1307 IMAPException, CommandCancelledException { 1308 1309 try { 1310 ensureSelectedState(folder); 1311 1312 SearchKey key = new SearchKey(SearchKey.UID, uid); 1313 1314 Integer [] index = protocol.search(new SearchKey[] { key }); 1315 if (index.length > 0) { 1316 return index[0].intValue(); 1317 } else { 1318 return -1; 1319 } 1320 } catch (IMAPDisconnectedException e) { 1321 return getIndex(uid, folder); 1322 } 1323 } 1324 1325 1328 public Integer [] search(SearchKey key, IMAPFolder folder) 1329 throws IOException , IMAPException, CommandCancelledException { 1330 try { 1331 ensureSelectedState(folder); 1332 1333 return protocol.uidSearch(new SearchKey[] { key }); 1334 } catch (IMAPDisconnectedException e) { 1335 return search(key, folder); 1336 } 1337 } 1338 1339 1342 public List search(IFilterRule filterRule, IMAPFolder folder) 1343 throws IOException , IMAPException, CommandCancelledException { 1344 1345 try { 1346 ensureSelectedState(folder); 1347 1348 SearchKey[] searchRequest; 1349 1350 searchRequest = createSearchKey(filterRule); 1351 1352 Integer [] result = null; 1353 Charset charset = UTF8; 1354 1355 while (result == null) { 1356 try { 1357 result = protocol.uidSearch(charset, searchRequest); 1358 } catch (IMAPException e) { 1359 if (e.getResponse().isNO() && charset != null) { 1360 if ( charset.equals(UTF8)) { 1363 charset = DEFAULT; 1364 } else if (charset == DEFAULT) { 1365 charset = null; 1368 } else { 1369 throw e; 1371 } 1372 } else 1373 throw e; 1374 } 1375 } 1376 1377 return Arrays.asList(result); 1378 } catch (IMAPDisconnectedException e) { 1379 return search(filterRule, folder); 1380 } 1381 } 1382 1383 1386 private SearchKey[] createSearchKey(IFilterRule filterRule) { 1387 SearchKey[] searchRequest; 1388 int argumentSize = filterRule.getChildCount(); 1389 if (argumentSize == 1) { 1391 searchRequest = new SearchKey[] { getSearchKey(filterRule.get(0)) }; 1393 } else { 1394 if (filterRule.getConditionInt() == FilterRule.MATCH_ALL) { 1396 searchRequest = new SearchKey[argumentSize]; 1398 1399 for (int i = 0; i < argumentSize; i++) { 1400 searchRequest[i] = getSearchKey(filterRule.get(i)); 1401 } 1402 1403 } else { 1404 SearchKey orKey; 1406 1407 orKey = new SearchKey(SearchKey.OR, getSearchKey(filterRule 1408 .get(argumentSize - 1)), getSearchKey(filterRule 1409 .get(argumentSize - 2))); 1410 1411 for (int i = argumentSize - 3; i >= 0; i--) { 1412 orKey = new SearchKey(SearchKey.OR, getSearchKey(filterRule 1413 .get(i)), orKey); 1414 } 1415 1416 searchRequest = new SearchKey[] { orKey }; 1417 } 1418 } 1419 1420 return searchRequest; 1421 } 1422 1423 1427 private SearchKey getSearchKey(IFilterCriteria criteria) { 1428 int operator = criteria.getCriteria(); 1429 int type = new MailFilterCriteria(criteria).getType(); 1430 1431 switch (type) { 1432 case MailFilterCriteria.FROM: { 1433 if (operator == FilterCriteria.CONTAINS) { 1434 return new SearchKey(SearchKey.FROM, criteria 1435 .getPatternString()); 1436 } else { 1437 return new SearchKey(SearchKey.NOT, new SearchKey( 1439 SearchKey.FROM, criteria.getPatternString())); 1440 } 1441 } 1442 1443 case MailFilterCriteria.CC: { 1444 if (operator == FilterCriteria.CONTAINS) { 1445 return new SearchKey(SearchKey.CC, criteria.getPatternString()); 1446 } else { 1447 return new SearchKey(SearchKey.NOT, new SearchKey(SearchKey.CC, 1449 criteria.getPatternString())); 1450 } 1451 } 1452 1453 case MailFilterCriteria.BCC: { 1454 if (operator == FilterCriteria.CONTAINS) { 1455 return new SearchKey(SearchKey.BCC, criteria.getPatternString()); 1456 } else { 1457 return new SearchKey(SearchKey.NOT, new SearchKey( 1459 SearchKey.BCC, criteria.getPatternString())); 1460 } 1461 } 1462 1463 case MailFilterCriteria.TO: { 1464 if (operator == FilterCriteria.CONTAINS) { 1465 return new SearchKey(SearchKey.TO, criteria.getPatternString()); 1466 } else { 1467 return new SearchKey(SearchKey.NOT, new SearchKey(SearchKey.TO, 1469 criteria.getPatternString())); 1470 } 1471 } 1472 1473 case MailFilterCriteria.SUBJECT: { 1474 if (operator == FilterCriteria.CONTAINS) { 1475 return new SearchKey(SearchKey.SUBJECT, criteria 1476 .getPatternString()); 1477 } else { 1478 return new SearchKey(SearchKey.NOT, new SearchKey( 1480 SearchKey.SUBJECT, criteria.getPatternString())); 1481 } 1482 } 1483 1484 case MailFilterCriteria.BODY: { 1485 if (operator == FilterCriteria.CONTAINS) { 1486 return new SearchKey(SearchKey.BODY, criteria 1487 .getPatternString()); 1488 } else { 1489 return new SearchKey(SearchKey.NOT, new SearchKey( 1491 SearchKey.BODY, criteria.getPatternString())); 1492 } 1493 } 1494 1495 case MailFilterCriteria.CUSTOM_HEADERFIELD: { 1496 if (operator == FilterCriteria.CONTAINS) { 1497 return new SearchKey(SearchKey.HEADER, new MailFilterCriteria( 1498 criteria).getHeaderfieldString(), criteria 1499 .getPatternString()); 1500 } else { 1501 return new SearchKey(SearchKey.NOT, new SearchKey( 1503 SearchKey.HEADER, new MailFilterCriteria(criteria) 1504 .getHeaderfieldString(), criteria 1505 .getPatternString())); 1506 } 1507 } 1508 1509 case MailFilterCriteria.DATE: { 1510 DateFormat df = DateFormat.getDateInstance(); 1511 1512 IMAPDate searchPattern = null; 1513 1514 try { 1515 searchPattern = new IMAPDate(df.parse(criteria 1516 .getPatternString())); 1517 } catch (java.text.ParseException ex) { 1518 ex.printStackTrace(); 1520 } 1521 1522 if (operator == FilterCriteria.DATE_BEFORE) { 1523 return new SearchKey(SearchKey.BEFORE, searchPattern); 1524 } else { 1525 return new SearchKey(SearchKey.NOT, new SearchKey( 1527 SearchKey.BEFORE, searchPattern)); 1528 } 1529 } 1530 1531 case MailFilterCriteria.SIZE: { 1532 if (operator == FilterCriteria.SIZE_SMALLER) { 1533 return new SearchKey(SearchKey.SMALLER, criteria 1534 .getPatternString()); 1535 } else { 1536 return new SearchKey(SearchKey.NOT, new SearchKey( 1538 SearchKey.SMALLER, criteria.getPatternString())); 1539 } 1540 } 1541 } 1542 1543 return null; 1544 } 1545 1546 1552 protected static boolean isAscii(String s) { 1553 int l = s.length(); 1554 1555 for (int i = 0; i < l; i++) { 1556 if ((int) s.charAt(i) > 0177) { 1558 return false; 1559 } 1560 } 1561 1562 return true; 1563 } 1564 1565 1571 private IMAPFlags convertToFlags(int variant) { 1572 IMAPFlags result = new IMAPFlags(); 1573 1574 switch (variant) { 1575 case MarkMessageCommand.MARK_AS_READ: 1576 case MarkMessageCommand.MARK_AS_UNREAD: { 1577 result.setSeen(true); 1578 1579 break; 1580 } 1581 1582 case MarkMessageCommand.MARK_AS_FLAGGED: 1583 case MarkMessageCommand.MARK_AS_UNFLAGGED: { 1584 result.setFlagged(true); 1585 1586 break; 1587 } 1588 1589 case MarkMessageCommand.MARK_AS_EXPUNGED: 1590 case MarkMessageCommand.MARK_AS_UNEXPUNGED: { 1591 result.setDeleted(true); 1592 1593 break; 1594 } 1595 1596 case MarkMessageCommand.MARK_AS_ANSWERED: { 1597 result.setAnswered(true); 1598 1599 break; 1600 } 1601 1602 case MarkMessageCommand.MARK_AS_SPAM: 1603 case MarkMessageCommand.MARK_AS_NOTSPAM: { 1604 result.setJunk(true); 1605 1606 break; 1607 } 1608 case MarkMessageCommand.MARK_AS_DRAFT: { 1609 result.setDraft(true); 1610 1611 break; 1612 } 1613 } 1614 1615 return result; 1616 } 1617 1618 1621 public MailboxInfo getMessageFolderInfo(IMAPFolder folder) 1622 throws IOException , IMAPException, CommandCancelledException { 1623 ensureSelectedState(folder); 1624 1625 return messageFolderInfo; 1626 } 1627 1628 1631 public ListInfo[] fetchSubscribedFolders() throws IOException , 1632 IMAPException, CommandCancelledException { 1633 try { 1634 ensureLoginState(); 1635 ListInfo[] lsub = protocol.lsub("", "*"); 1636 1637 if (lsub.length > 0) { 1639 delimiter = lsub[0].getDelimiter(); 1640 } 1641 1642 return lsub; 1643 } catch (IMAPDisconnectedException e) { 1644 return fetchSubscribedFolders(); 1645 } 1646 } 1647 1648 1651 public boolean isSelected(IMAPFolder folder) throws IOException , 1652 IMAPException, CommandCancelledException { 1653 ensureLoginState(); 1654 1655 return (protocol.getState() == IMAPProtocol.SELECTED && protocol 1656 .getSelectedMailbox().equals(folder.getImapPath())); 1657 } 1658 1659 1663 private int showErrorDialog(String message) { 1664 Object [] options = new String [] { 1665 MailResourceLoader.getString("", "global", "ok").replaceAll( 1666 "&", ""), 1667 MailResourceLoader.getString("", "global", "cancel") 1668 .replaceAll("&", "") }; 1669 1670 int result = JOptionPane.showOptionDialog(FrameManager.getInstance() 1671 .getActiveFrame(), message, "Warning", 1672 JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, 1673 options, options[0]); 1674 return result; 1675 } 1676 1677 1680 public void alertMessage(String arg0) { 1681 LOG.warning(arg0); 1683 } 1684 1685 1688 public void connectionClosed(String arg0, String arg1) { 1689 LOG.info(arg0); 1690 selectedFolder = null; 1691 } 1692 1693 1696 public void existsChanged(String arg0, int arg1) { 1697 if( selectedStatus == null) return; 1698 1699 selectedStatus.setMessages(arg1); 1700 statusDirty = true; 1701 1702 if (updatesEnabled) { 1703 if( existsChangedAction != null) { 1704 existsChangedAction.actionPerformed(selectedFolder); 1705 } 1706 1707 LOG.fine("Exists changed -> triggering update"); 1708 1709 } 1710 } 1711 1712 1715 public void flagsChanged(String arg0, IMAPFlags arg1) { 1716 LOG.fine("Flag changed -> triggering update"); 1717 1718 if( updateFlagAction != null) { 1719 updateFlagAction.actionPerformed(selectedFolder, arg1); 1720 } 1721 1722 } 1723 1724 1727 public void parseError(String arg0) { 1728 LOG.warning(arg0); 1729 } 1730 1731 1734 public void recentChanged(String arg0, int arg1) { 1735 if( selectedStatus == null) return; 1736 1737 selectedStatus.setRecent(arg1); 1738 statusDirty = true; 1739 1740 } 1743 1744 1747 public void warningMessage(String arg0) { 1748 LOG.warning(arg0); 1749 } 1750 1751 1758 public ImapItem getItem() { 1759 return item; 1760 } 1761 1762 1765 public void update(Observable o, Object arg) { 1766 protocol = new IMAPProtocol(item.get("host"), item.getInteger("port")); 1767 } 1768 1769 1772 public void setExistsChangedAction(IExistsChangedAction existsChangedAction) { 1773 this.existsChangedAction = existsChangedAction; 1774 } 1775 1776 1779 public void setUpdateFlagAction(IUpdateFlagAction updateFlagAction) { 1780 this.updateFlagAction = updateFlagAction; 1781 } 1782 1783 1786 public void setObservable(IStatusObservable observable) { 1787 this.observable = observable; 1788 } 1789} 1790 | Popular Tags |