001/**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020package org.apache.james.mpt.helper; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.InetSocketAddress; 025import java.nio.ByteBuffer; 026import java.nio.CharBuffer; 027import java.nio.channels.ReadableByteChannel; 028import java.nio.channels.SocketChannel; 029import java.nio.channels.WritableByteChannel; 030import java.nio.charset.Charset; 031import java.util.Locale; 032 033import org.apache.commons.lang.StringUtils; 034 035public class ScriptBuilder { 036 037 public static ScriptBuilder open(String host, int port) throws Exception { 038 InetSocketAddress address = new InetSocketAddress(host, port); 039 SocketChannel socket = SocketChannel.open(address); 040 socket.configureBlocking(false); 041 Client client = new Client(socket, socket); 042 return new ScriptBuilder(client); 043 } 044 045 private int tagCount = 0; 046 047 private boolean uidSearch = false; 048 049 private boolean peek = false; 050 051 private int messageNumber = 1; 052 053 private String user = "imapuser"; 054 055 private String password = "password"; 056 057 private String mailbox = "testmailbox"; 058 059 private String file = "rfc822.mail"; 060 061 private String basedir = "/org/apache/james/imap/samples/"; 062 063 private boolean createdMailbox = false; 064 065 private final Client client; 066 067 private Fetch fetch = new Fetch(); 068 069 private Search search = new Search(); 070 071 private String partialFetch = ""; 072 073 public ScriptBuilder(Client client) { 074 super(); 075 this.client = client; 076 } 077 078 public final boolean isPeek() { 079 return peek; 080 } 081 082 public final void setPeek(boolean peek) { 083 this.peek = peek; 084 } 085 086 public final boolean isUidSearch() { 087 return uidSearch; 088 } 089 090 public final void setUidSearch(boolean uidSearch) { 091 this.uidSearch = uidSearch; 092 } 093 094 public final String getBasedir() { 095 return basedir; 096 } 097 098 public final void setBasedir(String basedir) { 099 this.basedir = basedir; 100 } 101 102 public final String getFile() { 103 return file; 104 } 105 106 public final void setFile(String file) { 107 this.file = file; 108 } 109 110 private InputStream openFile() throws Exception { 111 InputStream result = this.getClass() 112 .getResourceAsStream(basedir + file); 113 return new IgnoreHeaderInputStream(result); 114 } 115 116 public final Fetch getFetch() { 117 return fetch; 118 } 119 120 public final void setFetch(Fetch fetch) { 121 this.fetch = fetch; 122 } 123 124 public final Fetch resetFetch() { 125 this.fetch = new Fetch(); 126 return fetch; 127 } 128 129 public final int getMessageNumber() { 130 return messageNumber; 131 } 132 133 public final void setMessageNumber(int messageNumber) { 134 this.messageNumber = messageNumber; 135 } 136 137 public final String getMailbox() { 138 return mailbox; 139 } 140 141 public final ScriptBuilder setMailbox(String mailbox) { 142 this.mailbox = mailbox; 143 return this; 144 } 145 146 public final String getPassword() { 147 return password; 148 } 149 150 public final void setPassword(String password) { 151 this.password = password; 152 } 153 154 public final String getUser() { 155 return user; 156 } 157 158 public final void setUser(String user) { 159 this.user = user; 160 } 161 162 public void login() throws Exception { 163 command("LOGIN " + user + " " + password); 164 } 165 166 private void command(String command) throws Exception { 167 tag(); 168 write(command); 169 lineEnd(); 170 response(); 171 } 172 173 public ScriptBuilder rename(String to) throws Exception { 174 return rename(getMailbox(), to); 175 } 176 177 public ScriptBuilder rename(String from, String to) throws Exception { 178 command("RENAME " + from + " " + to); 179 return this; 180 } 181 182 public ScriptBuilder select() throws Exception { 183 command("SELECT " + mailbox); 184 return this; 185 } 186 187 public ScriptBuilder create() throws Exception { 188 command("CREATE " + mailbox); 189 createdMailbox = true; 190 return this; 191 } 192 193 public ScriptBuilder flagDeleted() throws Exception { 194 return flagDeleted(messageNumber); 195 } 196 197 public ScriptBuilder flagDeleted(int messageNumber) throws Exception { 198 store(new Flags().deleted().msn(messageNumber)); 199 return this; 200 } 201 202 public ScriptBuilder expunge() throws Exception { 203 command("EXPUNGE"); 204 return this; 205 } 206 207 public void delete() throws Exception { 208 if (createdMailbox) { 209 command("DELETE " + mailbox); 210 } 211 } 212 213 public void search() throws Exception { 214 search.setUidSearch(uidSearch); 215 command(search.command()); 216 search = new Search(); 217 } 218 219 public ScriptBuilder all() { 220 search.all(); 221 return this; 222 } 223 224 public ScriptBuilder answered() { 225 search.answered(); 226 return this; 227 } 228 229 public ScriptBuilder bcc(String address) { 230 search.bcc(address); 231 return this; 232 } 233 234 public ScriptBuilder before(int year, int month, int day) { 235 search.before(year, month, day); 236 return this; 237 } 238 239 public ScriptBuilder body(String text) { 240 search.body(text); 241 return this; 242 } 243 244 public ScriptBuilder cc(String address) { 245 search.cc(address); 246 return this; 247 } 248 249 public ScriptBuilder deleted() { 250 search.deleted(); 251 return this; 252 } 253 254 public ScriptBuilder draft() { 255 search.draft(); 256 return this; 257 } 258 259 public ScriptBuilder flagged() { 260 search.flagged(); 261 return this; 262 } 263 264 public ScriptBuilder from(String address) { 265 search.from(address); 266 return this; 267 } 268 269 public ScriptBuilder header(String field, String value) { 270 search.header(field, value); 271 return this; 272 } 273 274 public ScriptBuilder keyword(String flag) { 275 search.keyword(flag); 276 return this; 277 } 278 279 public ScriptBuilder larger(long size) { 280 search.larger(size); 281 return this; 282 } 283 284 public ScriptBuilder NEW() { 285 search.NEW(); 286 return this; 287 } 288 289 public ScriptBuilder not() { 290 search.not(); 291 return this; 292 } 293 294 public ScriptBuilder old() { 295 search.old(); 296 return this; 297 } 298 299 public ScriptBuilder on(int year, int month, int day) { 300 search.on(year, month, day); 301 return this; 302 } 303 304 public ScriptBuilder or() { 305 search.or(); 306 return this; 307 } 308 309 public ScriptBuilder recent() { 310 search.recent(); 311 return this; 312 } 313 314 public ScriptBuilder seen() { 315 search.seen(); 316 return this; 317 } 318 319 public ScriptBuilder sentbefore(int year, int month, int day) { 320 search.sentbefore(year, month, day); 321 return this; 322 } 323 324 public ScriptBuilder senton(int year, int month, int day) { 325 search.senton(year, month, day); 326 return this; 327 } 328 329 public ScriptBuilder sentsince(int year, int month, int day) { 330 search.sentsince(year, month, day); 331 return this; 332 } 333 334 public ScriptBuilder since(int year, int month, int day) { 335 search.since(year, month, day); 336 return this; 337 } 338 339 public ScriptBuilder smaller(int size) { 340 search.smaller(size); 341 return this; 342 } 343 344 public ScriptBuilder subject(String address) { 345 search.subject(address); 346 return this; 347 } 348 349 public ScriptBuilder text(String text) { 350 search.text(text); 351 return this; 352 } 353 354 public ScriptBuilder to(String address) { 355 search.to(address); 356 return this; 357 } 358 359 public ScriptBuilder uid() { 360 search.uid(); 361 return this; 362 } 363 364 public ScriptBuilder unanswered() { 365 search.unanswered(); 366 return this; 367 } 368 369 public ScriptBuilder undeleted() { 370 search.undeleted(); 371 return this; 372 } 373 374 public ScriptBuilder undraft() { 375 search.undraft(); 376 return this; 377 } 378 379 public ScriptBuilder unflagged() { 380 search.unflagged(); 381 return this; 382 } 383 384 public ScriptBuilder unkeyword(String flag) { 385 search.unkeyword(flag); 386 return this; 387 } 388 389 public ScriptBuilder unseen() { 390 search.unseen(); 391 return this; 392 } 393 394 public ScriptBuilder openParen() { 395 search.openParen(); 396 return this; 397 } 398 399 public ScriptBuilder closeParen() { 400 search.closeParen(); 401 return this; 402 } 403 404 public ScriptBuilder msn(int low, int high) { 405 search.msn(low, high); 406 return this; 407 } 408 409 public ScriptBuilder msnAndUp(int limit) { 410 search.msnAndUp(limit); 411 return this; 412 } 413 414 public ScriptBuilder msnAndDown(int limit) { 415 search.msnAndDown(limit); 416 return this; 417 } 418 419 public Flags flags() { 420 return new Flags(); 421 } 422 423 public void store(Flags flags) throws Exception { 424 String command = flags.command(); 425 command(command); 426 } 427 428 public Search getSearch() throws Exception { 429 return search; 430 } 431 432 public ScriptBuilder partial(long start, long octets) { 433 partialFetch = "<" + start + "." + octets + ">"; 434 return this; 435 } 436 437 public ScriptBuilder fetchSection(String section) throws Exception { 438 StringBuffer command = new StringBuffer("FETCH "); 439 command.append(messageNumber); 440 if (peek) { 441 command.append(" (BODY.PEEK["); 442 } else { 443 command.append(" (BODY["); 444 } 445 command.append(section).append("]").append(partialFetch).append(")"); 446 command(command.toString()); 447 return this; 448 } 449 450 public void fetchAllMessages() throws Exception { 451 final String command = fetch.command(); 452 command(command); 453 } 454 455 public ScriptBuilder list() throws Exception { 456 command("LIST \"\" \"*\""); 457 return this; 458 } 459 460 public void fetchBody() throws Exception { 461 462 } 463 464 public void fetch() throws Exception { 465 final String command = fetch.command(messageNumber); 466 command(command); 467 } 468 469 public void fetchFlags() throws Exception { 470 final String command = "FETCH " + messageNumber + " (FLAGS)"; 471 command(command); 472 } 473 474 public void append() throws Exception { 475 tag(); 476 write("APPEND " + mailbox); 477 write(openFile()); 478 lineEnd(); 479 response(); 480 } 481 482 private void write(InputStream in) throws Exception { 483 client.write(in); 484 } 485 486 private void response() throws Exception { 487 client.readResponse(); 488 } 489 490 private void tag() throws Exception { 491 client.lineStart(); 492 write("A" + ++tagCount + " "); 493 } 494 495 private void lineEnd() throws Exception { 496 client.lineEnd(); 497 } 498 499 private void write(String phrase) throws Exception { 500 client.write(phrase); 501 } 502 503 public void close() throws Exception { 504 client.close(); 505 } 506 507 public void logout() throws Exception { 508 command("LOGOUT"); 509 } 510 511 public void quit() throws Exception { 512 delete(); 513 logout(); 514 close(); 515 } 516 517 public static final class Flags { 518 private final StringBuffer flags; 519 520 private final StringBuffer msn; 521 522 private boolean first; 523 524 private boolean silent; 525 526 private boolean add; 527 528 private boolean subtract; 529 530 public Flags() { 531 add = false; 532 subtract = false; 533 silent = false; 534 first = true; 535 flags = new StringBuffer("("); 536 msn = new StringBuffer(); 537 } 538 539 public Flags msn(long number) { 540 msn.append(number); 541 msn.append(' '); 542 return this; 543 } 544 545 public Flags range(long low, long high) { 546 msn.append(low); 547 msn.append(':'); 548 msn.append(high); 549 msn.append(' '); 550 return this; 551 } 552 553 public Flags rangeTill(long number) { 554 msn.append("*:"); 555 msn.append(number); 556 msn.append(' '); 557 return this; 558 } 559 560 public Flags rangeFrom(long number) { 561 msn.append(number); 562 msn.append(":* "); 563 return this; 564 } 565 566 public Flags add() { 567 add = true; 568 subtract = false; 569 return this; 570 } 571 572 public Flags subtract() { 573 add = false; 574 subtract = true; 575 return this; 576 } 577 578 public Flags silent() { 579 silent = true; 580 return this; 581 } 582 583 public Flags deleted() { 584 return append("\\DELETED"); 585 } 586 587 public Flags flagged() { 588 return append("\\FLAGGED"); 589 } 590 591 public Flags answered() { 592 return append("\\ANSWERED"); 593 } 594 595 public Flags seen() { 596 return append("\\SEEN"); 597 } 598 599 public Flags draft() { 600 return append("\\DRAFT"); 601 } 602 603 public String command() { 604 String flags; 605 if (add) { 606 flags = " +FLAGS "; 607 } else if (subtract) { 608 flags = " -FLAGS "; 609 } else { 610 flags = " FLAGS "; 611 } 612 if (silent) { 613 flags = flags + ".SILENT"; 614 } 615 return "STORE " + msn + flags + this.flags + ")"; 616 } 617 618 private Flags append(String term) { 619 if (first) { 620 first = false; 621 } else { 622 flags.append(' '); 623 } 624 flags.append(term); 625 return this; 626 } 627 } 628 629 public static final class Search { 630 631 private StringBuffer buffer; 632 633 private boolean first; 634 635 private boolean uidSearch = false; 636 637 public Search() { 638 clear(); 639 } 640 641 public boolean isUidSearch() { 642 return uidSearch; 643 } 644 645 public void setUidSearch(boolean uidSearch) { 646 this.uidSearch = uidSearch; 647 } 648 649 public String command() { 650 if (uidSearch) { 651 return buffer.insert(0, "UID SEARCH ").toString(); 652 } else { 653 return buffer.insert(0, "SEARCH ").toString(); 654 } 655 } 656 657 public void clear() { 658 buffer = new StringBuffer(); 659 first = true; 660 } 661 662 private Search append(long term) { 663 return append(Long.valueOf(term).toString()); 664 } 665 666 private Search append(String term) { 667 if (first) { 668 first = false; 669 } else { 670 buffer.append(' '); 671 } 672 buffer.append(term); 673 return this; 674 } 675 676 private Search date(int year, int month, int day) { 677 append(day); 678 switch (month) { 679 case 1: 680 buffer.append("-Jan-"); 681 break; 682 case 2: 683 buffer.append("-Feb-"); 684 break; 685 case 3: 686 buffer.append("-Mar-"); 687 break; 688 case 4: 689 buffer.append("-Apr-"); 690 break; 691 case 5: 692 buffer.append("-May-"); 693 break; 694 case 6: 695 buffer.append("-Jun-"); 696 break; 697 case 7: 698 buffer.append("-Jul-"); 699 break; 700 case 8: 701 buffer.append("-Aug-"); 702 break; 703 case 9: 704 buffer.append("-Sep-"); 705 break; 706 case 10: 707 buffer.append("-Oct-"); 708 break; 709 case 11: 710 buffer.append("-Nov-"); 711 break; 712 case 12: 713 buffer.append("-Dec-"); 714 break; 715 } 716 buffer.append(year); 717 return this; 718 } 719 720 public Search all() { 721 return append("ALL"); 722 } 723 724 public Search answered() { 725 return append("ANSWERED"); 726 } 727 728 public Search bcc(String address) { 729 return append("BCC " + address); 730 } 731 732 public Search before(int year, int month, int day) { 733 return append("BEFORE").date(year, month, day); 734 } 735 736 public Search body(String text) { 737 return append("BODY").append(text); 738 } 739 740 public Search cc(String address) { 741 return append("CC").append(address); 742 } 743 744 public Search deleted() { 745 return append("DELETED"); 746 } 747 748 public Search draft() { 749 return append("DRAFT"); 750 } 751 752 public Search flagged() { 753 return append("FLAGGED"); 754 } 755 756 public Search from(String address) { 757 return append("FROM").append(address); 758 } 759 760 public Search header(String field, String value) { 761 return append("HEADER").append(field).append(value); 762 } 763 764 public Search keyword(String flag) { 765 return append("KEYWORD").append(flag); 766 } 767 768 public Search larger(long size) { 769 return append("LARGER").append(size); 770 } 771 772 public Search NEW() { 773 return append("NEW"); 774 } 775 776 public Search not() { 777 return append("NOT"); 778 } 779 780 public Search old() { 781 return append("OLD"); 782 } 783 784 public Search on(int year, int month, int day) { 785 return append("ON").date(year, month, day); 786 } 787 788 public Search or() { 789 return append("OR"); 790 } 791 792 public Search recent() { 793 return append("RECENT"); 794 } 795 796 public Search seen() { 797 return append("SEEN"); 798 } 799 800 public Search sentbefore(int year, int month, int day) { 801 return append("SENTBEFORE").date(year, month, day); 802 } 803 804 public Search senton(int year, int month, int day) { 805 return append("SENTON").date(year, month, day); 806 } 807 808 public Search sentsince(int year, int month, int day) { 809 return append("SENTSINCE").date(year, month, day); 810 } 811 812 public Search since(int year, int month, int day) { 813 return append("SINCE").date(year, month, day); 814 } 815 816 public Search smaller(int size) { 817 return append("SMALLER").append(size); 818 } 819 820 public Search subject(String address) { 821 return append("SUBJECT").append(address); 822 } 823 824 public Search text(String text) { 825 return append("TEXT").append(text); 826 } 827 828 public Search to(String address) { 829 return append("TO").append(address); 830 } 831 832 public Search uid() { 833 return append("UID"); 834 } 835 836 public Search unanswered() { 837 return append("UNANSWERED"); 838 } 839 840 public Search undeleted() { 841 return append("UNDELETED"); 842 } 843 844 public Search undraft() { 845 return append("UNDRAFT"); 846 } 847 848 public Search unflagged() { 849 return append("UNFLAGGED"); 850 } 851 852 public Search unkeyword(String flag) { 853 return append("UNKEYWORD").append(flag); 854 } 855 856 public Search unseen() { 857 return append("UNSEEN"); 858 } 859 860 public Search openParen() { 861 return append("("); 862 } 863 864 public Search closeParen() { 865 return append(")"); 866 } 867 868 public Search msn(int low, int high) { 869 return append(low + ":" + high); 870 } 871 872 public Search msnAndUp(int limit) { 873 return append(limit + ":*"); 874 } 875 876 public Search msnAndDown(int limit) { 877 return append("*:" + limit); 878 } 879 } 880 881 public static final class Fetch { 882 883 static final String[] COMPREHENSIVE_HEADERS = { "DATE", "FROM", 884 "TO", "CC", "SUBJECT", "REFERENCES", "IN-REPLY-TO", 885 "MESSAGE-ID", "MIME-VERSION", "CONTENT-TYPE", "X-MAILING-LIST", 886 "X-LOOP", "LIST-ID", "LIST-POST", "MAILING-LIST", "ORIGINATOR", 887 "X-LIST", "SENDER", "RETURN-PATH", "X-BEENTHERE" }; 888 889 static final String[] SELECT_HEADERS = { "DATE", "FROM", "TO", 890 "ORIGINATOR", "X-LIST" }; 891 892 private boolean flagsFetch = false; 893 894 private boolean rfc822Size = false; 895 896 private boolean rfc = false; 897 898 private boolean rfcText = false; 899 900 private boolean rfcHeaders = false; 901 902 private boolean internalDate = false; 903 904 private boolean uid = false; 905 906 private String body = null; 907 908 private boolean bodyFetch = false; 909 910 private boolean bodyStructureFetch = false; 911 912 public boolean isBodyFetch() { 913 return bodyFetch; 914 } 915 916 public Fetch setBodyFetch(boolean bodyFetch) { 917 this.bodyFetch = bodyFetch; 918 return this; 919 } 920 921 public boolean isBodyStructureFetch() { 922 return bodyStructureFetch; 923 } 924 925 public Fetch setBodyStructureFetch(boolean bodyStructureFetch) { 926 this.bodyStructureFetch = bodyStructureFetch; 927 return this; 928 } 929 930 public String command(int messageNumber) { 931 return "FETCH " + messageNumber + "(" + fetchData() + ")"; 932 } 933 934 public String command() { 935 return "FETCH 1:* (" + fetchData() + ")"; 936 } 937 938 public boolean isFlagsFetch() { 939 return flagsFetch; 940 } 941 942 public Fetch setFlagsFetch(boolean flagsFetch) { 943 this.flagsFetch = flagsFetch; 944 return this; 945 } 946 947 public boolean isUid() { 948 return uid; 949 } 950 951 public Fetch setUid(boolean uid) { 952 this.uid = uid; 953 return this; 954 } 955 956 public boolean isRfc822Size() { 957 return rfc822Size; 958 } 959 960 public Fetch setRfc822Size(boolean rfc822Size) { 961 this.rfc822Size = rfc822Size; 962 return this; 963 } 964 965 public boolean isRfc() { 966 return rfc; 967 } 968 969 public Fetch setRfc(boolean rfc) { 970 this.rfc = rfc; 971 return this; 972 } 973 974 public boolean isRfcHeaders() { 975 return rfcHeaders; 976 } 977 978 public Fetch setRfcHeaders(boolean rfcHeaders) { 979 this.rfcHeaders = rfcHeaders; 980 return this; 981 } 982 983 public boolean isRfcText() { 984 return rfcText; 985 } 986 987 public Fetch setRfcText(boolean rfcText) { 988 this.rfcText = rfcText; 989 return this; 990 } 991 992 public boolean isInternalDate() { 993 return internalDate; 994 } 995 996 public Fetch setInternalDate(boolean internalDate) { 997 this.internalDate = internalDate; 998 return this; 999 } 1000 1001 public String getBody() { 1002 return body; 1003 } 1004 1005 public void setBody(String bodyPeek) { 1006 this.body = bodyPeek; 1007 } 1008 1009 public void bodyPeekCompleteMessage() { 1010 setBody(buildBody(true, "")); 1011 } 1012 1013 public void bodyPeekNotHeaders(String[] fields) { 1014 setBody(buildBody(true, buildHeaderFields(fields, true))); 1015 } 1016 1017 public Fetch bodyPeekHeaders(String[] fields) { 1018 setBody(buildBody(true, buildHeaderFields(fields, false))); 1019 return this; 1020 } 1021 1022 public String buildBody(boolean peek, String section) { 1023 StringBuffer result; 1024 if (peek) { 1025 result = new StringBuffer("BODY.PEEK["); 1026 } else { 1027 result = new StringBuffer("BODY["); 1028 } 1029 result.append(section).append("]"); 1030 return result.toString(); 1031 } 1032 1033 public String buildHeaderFields(String[] fields, boolean not) { 1034 StringBuffer result; 1035 if (not) { 1036 result = new StringBuffer("HEADER.FIELDS.NOT ("); 1037 } else { 1038 result = new StringBuffer("HEADER.FIELDS ("); 1039 } 1040 for (int i = 0; i < fields.length; i++) { 1041 if (i > 0) { 1042 result.append(" "); 1043 } 1044 result.append(fields[i]); 1045 } 1046 result.append(")"); 1047 return result.toString(); 1048 } 1049 1050 public String fetchData() { 1051 final StringBuffer buffer = new StringBuffer(); 1052 boolean first = true; 1053 if (flagsFetch) { 1054 first = add(buffer, first, "FLAGS"); 1055 } 1056 if (rfc822Size) { 1057 first = add(buffer, first, "RFC822.SIZE"); 1058 } 1059 if (rfc) { 1060 first = add(buffer, first, "RFC822"); 1061 } 1062 if (rfcHeaders) { 1063 first = add(buffer, first, "RFC822.HEADER"); 1064 } 1065 if (rfcText) { 1066 first = add(buffer, first, "RFC822.TEXT"); 1067 } 1068 if (internalDate) { 1069 first = add(buffer, first, "INTERNALDATE"); 1070 } 1071 if (uid) { 1072 first = add(buffer, first, "UID"); 1073 } 1074 if (bodyFetch) { 1075 first = add(buffer, first, "BODY"); 1076 } 1077 if (bodyStructureFetch) { 1078 first = add(buffer, first, "BODYSTRUCTURE"); 1079 } 1080 add(buffer, first, body); 1081 return buffer.toString(); 1082 } 1083 1084 private boolean add(StringBuffer buffer, boolean first, 1085 String atom) { 1086 if (atom != null) { 1087 if (first) { 1088 first = false; 1089 } else { 1090 buffer.append(" "); 1091 } 1092 buffer.append(atom); 1093 } 1094 return first; 1095 } 1096 } 1097 1098 public static final class Client { 1099 1100 private static final Charset ASCII = Charset.forName("us-ascii"); 1101 1102 private final Out out; 1103 1104 private final ReadableByteChannel source; 1105 1106 private final WritableByteChannel sump; 1107 1108 private final ByteBuffer inBuffer = ByteBuffer.allocate(256); 1109 1110 private final ByteBuffer outBuffer = ByteBuffer.allocate(262144); 1111 1112 private final ByteBuffer crlf; 1113 1114 private boolean isLineTagged = false; 1115 1116 private int continuationBytes = 0; 1117 1118 public Client(ReadableByteChannel source, WritableByteChannel sump) 1119 throws Exception { 1120 super(); 1121 this.source = source; 1122 this.sump = sump; 1123 this.out = new Out(); 1124 byte[] crlf = { '\r', '\n' }; 1125 this.crlf = ByteBuffer.wrap(crlf); 1126 inBuffer.flip(); 1127 readLine(); 1128 } 1129 1130 public void write(InputStream in) throws Exception { 1131 outBuffer.clear(); 1132 int next = in.read(); 1133 while (next != -1) { 1134 if (next == '\n') { 1135 outBufferNext((byte) '\r'); 1136 outBufferNext((byte) '\n'); 1137 } else if (next == '\r') { 1138 outBufferNext((byte) '\r'); 1139 outBufferNext((byte) '\n'); 1140 next = in.read(); 1141 if (next == '\n') { 1142 next = in.read(); 1143 } else if (next != -1) { 1144 outBufferNext((byte) next); 1145 } 1146 } else { 1147 outBufferNext((byte) next); 1148 } 1149 next = in.read(); 1150 } 1151 1152 writeOutBuffer(); 1153 } 1154 1155 public void outBufferNext(byte next) throws Exception { 1156 outBuffer.put(next); 1157 } 1158 1159 private void writeOutBuffer() throws Exception { 1160 outBuffer.flip(); 1161 int count = outBuffer.limit(); 1162 String continuation = " {" + count + "+}"; 1163 write(continuation); 1164 lineEnd(); 1165 out.client(); 1166 while (outBuffer.hasRemaining()) { 1167 final byte next = outBuffer.get(); 1168 print(next); 1169 if (next == '\n') { 1170 out.client(); 1171 } 1172 } 1173 outBuffer.rewind(); 1174 while (outBuffer.hasRemaining()) { 1175 sump.write(outBuffer); 1176 } 1177 } 1178 1179 public void readResponse() throws Exception { 1180 isLineTagged = false; 1181 while (!isLineTagged) { 1182 readLine(); 1183 } 1184 } 1185 1186 private byte next() throws Exception { 1187 byte result; 1188 if (inBuffer.hasRemaining()) { 1189 result = inBuffer.get(); 1190 print(result); 1191 } else { 1192 inBuffer.compact(); 1193 int i = 0; 1194 while ((i = source.read(inBuffer)) == 0) 1195 ; 1196 if (i == -1) 1197 throw new RuntimeException("Unexpected EOF"); 1198 inBuffer.flip(); 1199 result = next(); 1200 } 1201 return result; 1202 } 1203 1204 private void print(char next) { 1205 out.print(next); 1206 } 1207 1208 private void print(byte next) { 1209 print((char) next); 1210 } 1211 1212 public void lineStart() throws Exception { 1213 out.client(); 1214 } 1215 1216 public void write(String phrase) throws Exception { 1217 out.print(phrase); 1218 final ByteBuffer buffer = ASCII.encode(phrase); 1219 writeRemaining(buffer); 1220 } 1221 1222 public void writeLine(String line) throws Exception { 1223 lineStart(); 1224 write(line); 1225 lineEnd(); 1226 } 1227 1228 private void writeRemaining(ByteBuffer buffer) throws IOException { 1229 while (buffer.hasRemaining()) { 1230 sump.write(buffer); 1231 } 1232 } 1233 1234 public void lineEnd() throws Exception { 1235 out.lineEnd(); 1236 crlf.rewind(); 1237 writeRemaining(crlf); 1238 } 1239 1240 private void readLine() throws Exception { 1241 out.server(); 1242 1243 final byte next = next(); 1244 isLineTagged = next != '*'; 1245 readRestOfLine(next); 1246 } 1247 1248 private void readRestOfLine(byte next) throws Exception { 1249 while (next != '\r') { 1250 if (next == '{') { 1251 startContinuation(); 1252 } 1253 next = next(); 1254 } 1255 next(); 1256 } 1257 1258 private void startContinuation() throws Exception { 1259 continuationBytes = 0; 1260 continuation(); 1261 } 1262 1263 private void continuation() throws Exception { 1264 byte next = next(); 1265 switch (next) { 1266 case '0': 1267 continuationDigit(0); 1268 break; 1269 case '1': 1270 continuationDigit(1); 1271 break; 1272 case '2': 1273 continuationDigit(2); 1274 break; 1275 case '3': 1276 continuationDigit(3); 1277 break; 1278 case '4': 1279 continuationDigit(4); 1280 break; 1281 case '5': 1282 continuationDigit(5); 1283 break; 1284 case '6': 1285 continuationDigit(6); 1286 break; 1287 case '7': 1288 continuationDigit(7); 1289 break; 1290 case '8': 1291 continuationDigit(8); 1292 break; 1293 case '9': 1294 continuationDigit(9); 1295 break; 1296 case '+': 1297 next(); 1298 next(); 1299 readContinuation(); 1300 break; 1301 default: 1302 next(); 1303 next(); 1304 readContinuation(); 1305 break; 1306 } 1307 } 1308 1309 private void readContinuation() throws Exception { 1310 out.server(); 1311 while (continuationBytes-- > 0) { 1312 int next = next(); 1313 if (next == '\n') { 1314 out.server(); 1315 } 1316 } 1317 } 1318 1319 private void continuationDigit(int digit) throws Exception { 1320 continuationBytes = 10 * continuationBytes + digit; 1321 continuation(); 1322 } 1323 1324 public void close() throws Exception { 1325 source.close(); 1326 sump.close(); 1327 } 1328 } 1329 1330 private static final class Out { 1331 private static final String OK_APPEND_COMPLETED = "OK APPEND completed."; 1332 1333 private static final String[] IGNORE_LINES_STARTING_WITH = { 1334 "S: \\* OK \\[PERMANENTFLAGS", "C: A22 LOGOUT", 1335 "S: \\* BYE Logging out", "S: \\* OK Dovecot ready\\." }; 1336 1337 private static final String[] IGNORE_LINES_CONTAINING = { 1338 "OK Logout completed.", "LOGIN imapuser password", 1339 "OK Logged in", "LOGOUT" }; 1340 1341 private final CharBuffer lineBuffer = CharBuffer.allocate(131072); 1342 1343 private boolean isClient = false; 1344 1345 public void client() { 1346 lineBuffer.put("C: "); 1347 isClient = true; 1348 } 1349 1350 public void print(char next) { 1351 if (!isClient) { 1352 escape(next); 1353 } 1354 lineBuffer.put(next); 1355 } 1356 1357 private void escape(char next) { 1358 if (next == '\\' || next == '*' || next == '.' || next == '[' 1359 || next == ']' || next == '+' || next == '(' || next == ')' 1360 || next == '{' || next == '}' || next == '?') { 1361 lineBuffer.put('\\'); 1362 } 1363 } 1364 1365 public void server() { 1366 lineBuffer.put("S: "); 1367 isClient = false; 1368 } 1369 1370 public void print(String phrase) { 1371 if (!isClient) { 1372 phrase = StringUtils.replace(phrase, "\\", "\\\\"); 1373 phrase = StringUtils.replace(phrase, "*", "\\*"); 1374 phrase = StringUtils.replace(phrase, ".", "\\."); 1375 phrase = StringUtils.replace(phrase, "[", "\\["); 1376 phrase = StringUtils.replace(phrase, "]", "\\]"); 1377 phrase = StringUtils.replace(phrase, "+", "\\+"); 1378 phrase = StringUtils.replace(phrase, "(", "\\("); 1379 phrase = StringUtils.replace(phrase, ")", "\\)"); 1380 phrase = StringUtils.replace(phrase, "}", "\\}"); 1381 phrase = StringUtils.replace(phrase, "{", "\\{"); 1382 phrase = StringUtils.replace(phrase, "?", "\\?"); 1383 } 1384 lineBuffer.put(phrase); 1385 } 1386 1387 public void lineEnd() { 1388 lineBuffer.flip(); 1389 final String text = lineBuffer.toString(); 1390 String[] lines = text.split("\r\n"); 1391 for (String line : lines) { 1392 String chompedLine = StringUtils.chomp(line); 1393 if (!ignoreLine(chompedLine)) { 1394 final String[] words = StringUtils.split(chompedLine); 1395 if (words.length > 3 && "S:".equalsIgnoreCase(words[0]) && "OK".equalsIgnoreCase(words[2])) { 1396 final int commandWordIndex; 1397 if (words[3] == null || !words[3].startsWith("\\[")) { 1398 commandWordIndex = 3; 1399 } else { 1400 int wordsCount = 3; 1401 while (wordsCount < words.length) { 1402 if (words[wordsCount++].endsWith("]")) { 1403 break; 1404 } 1405 } 1406 commandWordIndex = wordsCount; 1407 } 1408 final String command = words[commandWordIndex]; 1409 final String commandOkPhrase; 1410 if ("CREATE".equalsIgnoreCase(command)) { 1411 commandOkPhrase = "OK CREATE completed."; 1412 } else if ("FETCH".equalsIgnoreCase(command)) { 1413 commandOkPhrase = "OK FETCH completed."; 1414 } else if ("APPEND".equalsIgnoreCase(command)) { 1415 commandOkPhrase = OK_APPEND_COMPLETED; 1416 } else if ("DELETE".equalsIgnoreCase(command)) { 1417 commandOkPhrase = "OK DELETE completed."; 1418 } else if ("STORE".equalsIgnoreCase(command)) { 1419 commandOkPhrase = "OK STORE completed."; 1420 } else if ("RENAME".equalsIgnoreCase(command)) { 1421 commandOkPhrase = "OK RENAME completed."; 1422 } else if ("EXPUNGE".equalsIgnoreCase(command)) { 1423 commandOkPhrase = "OK EXPUNGE completed."; 1424 } else if ("LIST".equalsIgnoreCase(command)) { 1425 commandOkPhrase = "OK LIST completed."; 1426 } else if ("SELECT".equalsIgnoreCase(command)) { 1427 if (commandWordIndex == 3) { 1428 commandOkPhrase = "OK SELECT completed."; 1429 } else { 1430 commandOkPhrase = "OK " + words[3].toUpperCase(Locale.US) + " SELECT completed."; 1431 } 1432 } else { 1433 commandOkPhrase = null; 1434 } 1435 if (commandOkPhrase != null) { 1436 chompedLine = words[0] + " " + words[1] + " " + commandOkPhrase; 1437 } 1438 } 1439 chompedLine = StringUtils.replace(chompedLine, "\\\\Seen \\\\Draft", 1440 "\\\\Draft \\\\Seen"); 1441 chompedLine = StringUtils.replace(chompedLine, "\\\\Flagged \\\\Deleted", 1442 "\\\\Deleted \\\\Flagged"); 1443 chompedLine = StringUtils.replace(chompedLine, "\\\\Flagged \\\\Draft", 1444 "\\\\Draft \\\\Flagged"); 1445 chompedLine = StringUtils.replace(chompedLine, "\\\\Seen \\\\Recent", 1446 "\\\\Recent \\\\Seen"); 1447 chompedLine = StringUtils.replace(chompedLine, "\\] First unseen\\.", 1448 "\\](.)*"); 1449 if (chompedLine.startsWith("S: \\* OK \\[UIDVALIDITY ")) { 1450 chompedLine = "S: \\* OK \\[UIDVALIDITY \\d+\\]"; 1451 } else if (chompedLine.startsWith("S: \\* OK \\[UIDNEXT")) { 1452 chompedLine = "S: \\* OK \\[PERMANENTFLAGS \\(\\\\Answered \\\\Deleted \\\\Draft \\\\Flagged \\\\Seen\\)\\]"; 1453 } 1454 1455 System.out.println(chompedLine); 1456 } 1457 } 1458 lineBuffer.clear(); 1459 } 1460 1461 private boolean ignoreLine(String line) { 1462 boolean result = false; 1463 for (String entry : IGNORE_LINES_CONTAINING) { 1464 if (line.indexOf(entry) > 0) { 1465 result = true; 1466 break; 1467 } 1468 } 1469 for (int i = 0; i < IGNORE_LINES_STARTING_WITH.length && !result; i++) { 1470 if (line.startsWith(IGNORE_LINES_STARTING_WITH[i])) { 1471 result = true; 1472 break; 1473 } 1474 } 1475 return result; 1476 } 1477 } 1478 1479 private static final class IgnoreHeaderInputStream extends InputStream { 1480 1481 private boolean isFinishedHeaders = false; 1482 1483 private final InputStream delegate; 1484 1485 public IgnoreHeaderInputStream(InputStream delegate) { 1486 super(); 1487 this.delegate = delegate; 1488 } 1489 1490 public int read() throws IOException { 1491 final int result; 1492 final int next = delegate.read(); 1493 if (isFinishedHeaders) { 1494 result = next; 1495 } else { 1496 switch (next) { 1497 case -1: 1498 isFinishedHeaders = true; 1499 result = next; 1500 break; 1501 case '#': 1502 readLine(); 1503 result = read(); 1504 break; 1505 1506 case '\r': 1507 case '\n': 1508 case ' ': 1509 case '\t': 1510 result = read(); 1511 break; 1512 1513 default: 1514 isFinishedHeaders = true; 1515 result = next; 1516 break; 1517 } 1518 } 1519 return result; 1520 } 1521 1522 private void readLine() throws IOException { 1523 int next = delegate.read(); 1524 while (next != -1 && next != '\r' && next != '\n') { 1525 next = delegate.read(); 1526 } 1527 } 1528 } 1529}