1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.mercury.imap.util;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.util.ArrayList;
19 import java.util.Calendar;
20 import java.util.GregorianCalendar;
21 import java.util.List;
22 import java.util.TimeZone;
23 import javax.mail.Flags;
24 import org.abstracthorizon.mercury.imap.util.section.HeaderSection;
25 import org.abstracthorizon.mercury.imap.util.section.MimeSection;
26 import org.abstracthorizon.mercury.imap.util.section.MultipartSection;
27 import org.abstracthorizon.mercury.imap.util.section.PointerSection;
28 import org.abstracthorizon.mercury.imap.util.section.TextSection;
29
30
31
32
33
34
35
36 public class IMAPScanner {
37
38
39 protected InputStream in;
40
41
42 protected OutputStream out;
43
44
45 protected char[] buffer;
46
47
48 protected boolean literal;
49
50
51 protected int ptr = -1;
52
53
54 protected boolean eol = false;
55
56
57
58
59
60
61 public IMAPScanner(InputStream in, OutputStream out) {
62 this.in = in;
63 this.out = out;
64 in.mark(128);
65 }
66
67 protected boolean atom_char(char c) {
68 if ((c >= 31) &&
69 (c != '(') && (c != ')') && (c != '{') && (c != ' ') &&
70 (c != '%') && (c != '*') && (c != '"') && (c != ']') &&
71 (c != '\\')
72 )
73 {
74 return true;
75 }
76 return false;
77 }
78
79 protected boolean astring_char(char c) {
80 if (atom_char(c) || (c == ']')) {
81 return true;
82 }
83 return false;
84 }
85
86 protected boolean text_char(char c) {
87 if ((c != '\r') && (c != '\n')) {
88 return true;
89 }
90 return false;
91 }
92
93 protected boolean digit_nz(char c) {
94 return (c >= '1') && (c <= '9');
95 }
96
97 protected boolean digit(char c) {
98 return (c >= '0') && (c <= '9');
99 }
100
101 protected boolean list_char(char c) {
102 return atom_char(c) || (c == '%') || (c == '*') || (c == ']');
103 }
104
105
106 public void skip_line() throws IOException {
107 if (eol) {
108 eol = false;
109 return;
110 }
111 try {
112 int i = in.read();
113 char c = (char)i;
114 while (i >= 0) {
115 while ((i >= 0) && (c != '\r')) {
116 i = in.read();
117 c = (char)i;
118 }
119 i = in.read();
120 c = (char)i;
121 if (c == '\n') {
122 return;
123 }
124 }
125 } catch (IOException e) {
126 }
127 }
128
129 public void check_eol() throws IOException, ParserException {
130 try {
131 char c = (char)in.read();
132 if (c != '\r') {
133 throw new ParserException("<CR>");
134 }
135 c = (char)in.read();
136 if (c != '\n') {
137 throw new ParserException("<LF>");
138 }
139 } catch (IOException e) {
140 }
141 eol = true;
142 }
143
144 public boolean is_char(char r) throws IOException {
145 in.mark(1);
146 char c = (char)in.read();
147 if (c == r) {
148 return true;
149 }
150 in.reset();
151 return false;
152 }
153
154 public boolean peek_char(char r) throws IOException {
155 in.mark(1);
156 char c = (char)in.read();
157 in.reset();
158 if (c == r) {
159 return true;
160 }
161 return false;
162 }
163
164 public boolean number(Number num) throws IOException {
165 in.mark(10);
166 int len = 0;
167 num.number = 0;
168 char c = (char)in.read();
169 if ((c < '0') && (c > '9')) {
170 in.reset();
171 return false;
172 }
173 num.number = num.number*10 + (c-'0');
174 len = len + 1;
175 c = (char)in.read();
176 while ((c >= '0') && (c <= '9')) {
177 num.number = num.number*10 + (c-'0');
178 len = len + 1;
179 c = (char)in.read();
180 }
181 in.reset();
182 in.skip(len);
183 return true;
184 }
185
186 public boolean nz_number(Number num) throws IOException {
187 in.mark(10);
188 int len = 0;
189 num.number = 0;
190 char c = (char)in.read();
191 if ((c < '1') || (c > '9')) {
192 in.reset();
193 return false;
194 }
195 num.number = num.number*10 + (c-'0');
196 len = len + 1;
197 c = (char)in.read();
198 while ((c >= '0') && (c <= '9')) {
199 num.number = num.number*10 + (c-'0');
200 len = len + 1;
201 c = (char)in.read();
202 }
203 in.reset();
204 in.skip(len);
205 return true;
206 }
207
208 public boolean tag(StringBuffer res) throws IOException {
209 in.mark(128);
210 int i = in.read();
211 char c = (char)i;
212 if ((i != -1) && (c != '+') && astring_char(c)) {
213 res.append(c);
214 i = in.read();
215 c = (char)i;
216 while ((i != -1) && (c != '+') && astring_char(c)) {
217 res.append(c);
218 i = in.read();
219 c = (char)i;
220 }
221 in.reset();
222 in.skip(res.length());
223 return true;
224 }
225 in.reset();
226 res.delete(0, res.length());
227 return false;
228 }
229
230 public boolean keyword(String keyword) throws IOException {
231 in.mark(keyword.length()+1);
232 ptr = 0;
233 while (ptr < keyword.length()) {
234 char c = (char)in.read();
235 if (Character.toUpperCase(c) != Character.toUpperCase(keyword.charAt(ptr))) {
236 in.reset();
237
238 return false;
239 }
240 ptr = ptr + 1;
241 }
242 return true;
243 }
244
245 public boolean quoted(StringBuffer quoted) throws IOException, ParserException {
246 in.mark(512);
247 char c = (char)in.read();
248 if (c != '"') {
249 in.reset();
250 return false;
251 }
252 c = (char)in.read();
253 while (text_char(c) && (c != '"')) {
254 if (c == '\\') {
255 c = (char)in.read();
256 if ((c == '\\') || (c == '"')) {
257 quoted.append(c);
258 } else {
259 in.reset();
260 throw new ParserException("'"+c+"'");
261 }
262 } else {
263 quoted.append(c);
264 }
265 c = (char)in.read();
266 }
267 if (c != '"') {
268 in.reset();
269 throw new ParserException("'\"'");
270 }
271 return true;
272 }
273
274 public boolean literal(StringBuffer literal) throws IOException, ParserException {
275 in.mark(10);
276 char c = (char)in.read();
277 if (c != '{') {
278 in.reset();
279 return false;
280 }
281 c = (char)in.read();
282 if (!digit(c)) {
283 in.reset();
284 throw new ParserException("<digit>");
285 }
286 int len = (c - '0');
287 c = (char)in.read();
288 while (digit(c)) {
289 len = len*10 + (c - '0');
290 c = (char)in.read();
291 }
292
293 if (c != '}') {
294 in.reset();
295 throw new ParserException("'}'");
296 }
297 c = (char)in.read();
298 if (c != '\r') {
299 in.reset();
300 throw new ParserException("<CR>");
301 }
302 c = (char)in.read();
303 if (c != '\n') {
304 in.reset();
305 throw new ParserException("<LF>");
306 }
307 synchronized (out) {
308 out.write("+ Ready for literal data\r\n".getBytes());
309 out.flush();
310 }
311 byte[] buf = new byte[len];
312 int size = 0;
313 size = in.read(buf);
314 while (size < buf.length) {
315 int s = in.read(buf, size, buf.length-size);
316 if (s >= 0) {
317 size = size + s;
318 } else {
319 throw new ParserException(false, "Wrong literal - premature EOF reached!");
320 }
321 }
322 literal.append(new String(buf));
323 return true;
324 }
325
326 public long raw_literal() throws IOException, ParserException {
327 in.mark(10);
328 char c = (char)in.read();
329 if (c != '{') {
330 in.reset();
331 return -1;
332 }
333 c = (char)in.read();
334 if (!digit(c)) {
335 throw new ParserException("<digit>");
336 }
337 long len = (c - '0');
338 c = (char)in.read();
339 while (digit(c)) {
340 len = len*10 + (c - '0');
341 c = (char)in.read();
342 }
343
344 if (c != '}') {
345 throw new ParserException("'}'");
346 }
347 c = (char)in.read();
348 if (c != '\r') {
349 throw new ParserException("<CR>");
350 }
351 c = (char)in.read();
352 if (c != '\n') {
353 throw new ParserException("<LF>");
354 }
355 synchronized (out) {
356 out.write("+ Ready for literal data\r\n".getBytes());
357 out.flush();
358 }
359 return len;
360 }
361
362
363 public boolean string(StringBuffer buffer) throws IOException, ParserException {
364 if (quoted(buffer) || literal(buffer)) {
365 return true;
366 }
367 return false;
368 }
369
370 public boolean atom(StringBuffer buffer) throws IOException, ParserException {
371 in.mark(128);
372 char c = (char)in.read();
373 if (!atom_char(c)) {
374 in.reset();
375 return false;
376 }
377 buffer.append(c);
378 c = (char)in.read();
379 while (atom_char(c)) {
380 buffer.append(c);
381 c = (char)in.read();
382 }
383 in.reset();
384 in.skip(buffer.length());
385 return true;
386 }
387
388 public boolean astring(StringBuffer buffer) throws IOException, ParserException {
389 if (string(buffer)) {
390 return true;
391 }
392 in.mark(128);
393 char c = (char)in.read();
394 if (!astring_char(c)) {
395 in.reset();
396 return false;
397 }
398 buffer.append(c);
399 c = (char)in.read();
400 while (astring_char(c)) {
401 buffer.append(c);
402 c = (char)in.read();
403 }
404 in.reset();
405 in.skip(buffer.length());
406 return true;
407 }
408
409 public boolean readBase64Line(StringBuffer line) throws IOException, ParserException {
410 in.mark(128);
411 char c = (char)in.read();
412 while (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')) || (c == '+') || (c == '=') || (c == '/')) {
413 line.append(c);
414 c = (char)in.read();
415 }
416 if (c != '\r') {
417 throw new ParserException("<CR>");
418 }
419 c = (char)in.read();
420 if (c != '\n') {
421 throw new ParserException("<LF>");
422 }
423 return true;
424 }
425
426
427 public boolean list_mailbox(StringBuffer buffer) throws IOException, ParserException {
428 if (string(buffer)) {
429 return true;
430 }
431 in.mark(128);
432 char c = (char)in.read();
433 if (!list_char(c)) {
434 in.reset();
435 return false;
436 }
437 buffer.append(c);
438 c = (char)in.read();
439 while (list_char(c)) {
440 buffer.append(c);
441 c = (char)in.read();
442 }
443 in.reset();
444 in.skip(buffer.length());
445 return true;
446 }
447
448 public boolean sequence_set(ComposedSequence sequence) throws IOException, ParserException {
449 SimpleSequence seq = new SimpleSequence();
450 if (!seq_range(seq)) {
451 return false;
452 }
453 sequence.add(seq);
454 while (is_char(',')) {
455 seq = new SimpleSequence();
456 if (!seq_range(seq)) {
457 throw new ParserException("<seq_range>");
458 }
459 sequence.add(seq);
460 }
461 return true;
462 }
463
464 public boolean seq_range(SimpleSequence sequence) throws IOException, ParserException {
465 Number num = new Number();
466 if (is_char('*')) {
467 } else {
468 if (!number(num)) {
469 return false;
470 }
471 sequence.setMin(num.number);
472 }
473 if (is_char(':')) {
474 if (is_char('*')) {
475 } else {
476 if (!number(num)) {
477 throw new ParserException("<number>");
478 }
479 sequence.setMax(num.number);
480 }
481 } else {
482 sequence.setMax(num.number);
483 }
484 return true;
485 }
486
487 public boolean section(PointerSection section) throws IOException, ParserException {
488 in.mark(1);
489
490 char c = (char)in.read();
491
492 if (c != '[') {
493 in.reset();
494 return false;
495 }
496 if (section_msgtext(section)) {
497
498 } else {
499 int i = section_part(section);
500 if (i == 0) {
501
502
503 } else if (i == 2) {
504 while (section.child != null) {
505 section = (PointerSection)section.child;
506 }
507 if (!section_text(section)) {
508 throw new ParserException("<section_text>");
509 }
510 } else {
511 while (section.child != null) {
512 section = (PointerSection)section.child;
513 }
514
515
516
517
518
519 }
520 }
521 c = (char)in.read();
522 if (c != ']') {
523 throw new ParserException("']'");
524 }
525
526 return true;
527 }
528
529 public int section_part(PointerSection section) throws IOException, ParserException {
530 Number number = new Number();
531 if (!nz_number(number)) {
532 return 0;
533 }
534 MultipartSection sec = new MultipartSection();
535 section.child = sec;
536 sec.partNo = number.number;
537 section = sec;
538 in.mark(1);
539 char c = (char)in.read();
540 while (c == '.') {
541 if (!nz_number(number)) {
542 return 2;
543
544 }
545 sec = new MultipartSection();
546 section.child = sec;
547 sec.partNo = number.number;
548 section = sec;
549 in.mark(1);
550 c = (char)in.read();
551 }
552 in.reset();
553 return 1;
554 }
555
556 public boolean section_text(PointerSection section) throws IOException, ParserException {
557 if (section_msgtext(section)) {
558 return true;
559 } else if (keyword("MIME")) {
560 section.child = new MimeSection();
561 return true;
562 }
563 return false;
564 }
565
566 public boolean section_msgtext(PointerSection section) throws IOException, ParserException {
567 if (keyword("HEADER")) {
568 HeaderSection sec = new HeaderSection();
569 section.child = sec;
570 if (keyword(".FIELDS")) {
571 sec.all = false;
572 if (keyword(".NOT")) {
573 sec.not = true;
574 }
575 if (!is_char(' ')) {
576 throw new ParserException("<SP>");
577 }
578 sec.fields = new ArrayList<String>();
579 if (!header_list(sec.fields)) {
580 throw new ParserException("<header_list>");
581 }
582 }
583 return true;
584 } else if (keyword("TEXT")) {
585 section.child = new TextSection();
586 return true;
587 } else {
588 return false;
589 }
590
591
592 }
593
594 public boolean header_list(List<String> list) throws IOException, ParserException {
595 in.mark(1);
596 char c = (char)in.read();
597 if (c != '(') {
598 in.reset();
599 return false;
600 }
601 StringBuffer s = new StringBuffer();
602 if (!astring(s)) {
603 throw new ParserException("<string>");
604 }
605 list.add(s.toString());
606 s.delete(0, s.length());
607 while (is_char(' ')) {
608 if (!astring(s)) {
609 throw new ParserException("<string>");
610 }
611 list.add(s.toString());
612 s.delete(0, s.length());
613 }
614 if (!is_char(')')) {
615 throw new ParserException("')'");
616 }
617 return true;
618 }
619
620 public boolean mailbox(StringBuffer mailbox) throws IOException, ParserException {
621 if (keyword("INBOX")) {
622 mailbox.append("INBOX");
623 return true;
624 } else {
625 return astring(mailbox);
626 }
627 }
628
629 public boolean flag(Flags flags) throws IOException, ParserException {
630 if (keyword("\\Answered")) {
631 flags.add(Flags.Flag.ANSWERED);
632 return true;
633 } else if (keyword("\\Flagged")) {
634 flags.add(Flags.Flag.FLAGGED);
635 return true;
636 } else if (keyword("\\Deleted")) {
637 flags.add(Flags.Flag.DELETED);
638 return true;
639 } else if (keyword("\\Seen")) {
640 flags.add(Flags.Flag.SEEN);
641 return true;
642 } else if (keyword("\\Draft")) {
643 flags.add(Flags.Flag.DRAFT);
644 return true;
645 } else if (is_char('\\')) {
646 StringBuffer b = new StringBuffer();
647 if (!atom(b)) {
648 throw new ParserException("<atom>");
649 } else {
650 flags.add(b.toString());
651 }
652 return true;
653 } else {
654 StringBuffer b = new StringBuffer();
655 if (atom(b)) {
656 flags.add(b.toString());
657 return true;
658 }
659 }
660 return false;
661 }
662
663 public boolean flag_list(Flags fs) throws IOException, ParserException {
664
665 if (!is_char('(')) {
666 return false;
667 }
668 if (flag(fs)) {
669 while (is_char(' ')) {
670 if (!flag(fs)) {
671 throw new ParserException("<flag>");
672 }
673 }
674
675 }
676
677 if (!is_char(')')) {
678 throw new ParserException("')'");
679 }
680 return true;
681 }
682
683 public boolean date_day_fixed(Number num) throws IOException, ParserException {
684 if (is_char(' ')) {
685 in.mark(1);
686 char c = (char)in.read();
687 if (digit(c)) {
688 num.number = c-'0';
689 } else {
690 throw new ParserException("<digit>");
691 }
692 } else {
693 in.mark(1);
694 char c = (char)in.read();
695 if (digit(c)) {
696 num.number = c-'0';
697 c = (char)in.read();
698 if (digit(c)) {
699 num.number = num.number*10+c-'0';
700 } else {
701 throw new ParserException("<digit>");
702 }
703 } else {
704 return false;
705 }
706 }
707 return true;
708 }
709
710 public boolean date_day(Number num) throws IOException, ParserException {
711 in.mark(1);
712 char c = (char)in.read();
713 if (digit(c)) {
714 num.number = c-'0';
715 c = (char)in.read();
716 if (digit(c)) {
717 num.number = num.number*10+c-'0';
718 }
719 return true;
720 } else {
721 return false;
722 }
723 }
724
725
726 public boolean date_month(Number num) throws IOException, ParserException {
727
728
729 if (keyword("Jan")) {
730 num.number = 1;
731 } else if (keyword("Feb")) {
732 num.number = 2;
733 } else if (keyword("Mar")) {
734 num.number = 3;
735 } else if (keyword("Apr")) {
736 num.number = 4;
737 } else if (keyword("May")) {
738 num.number = 5;
739 } else if (keyword("Jun")) {
740 num.number = 6;
741 } else if (keyword("Jul")) {
742 num.number = 7;
743 } else if (keyword("Aug")) {
744 num.number = 8;
745 } else if (keyword("Sep")) {
746 num.number = 9;
747 } else if (keyword("Oct")) {
748 num.number = 10;
749 } else if (keyword("Nov")) {
750 num.number = 11;
751 } else if (keyword("Dec")) {
752 num.number = 12;
753 } else {
754 return false;
755 }
756 return true;
757 }
758
759 public boolean four_digit(Number num) throws IOException, ParserException {
760 in.mark(1);
761 char c = (char)in.read();
762 if (digit(c)) {
763 num.number = c-'0';
764
765 for (int i=0; i<3; i++) {
766 c = (char)in.read();
767 if (!digit(c)) {
768 throw new ParserException("<digit>");
769 }
770 num.number = num.number*10 + c-'0';
771 }
772
773 return true;
774 } else {
775 in.reset();
776 return false;
777 }
778
779 }
780
781 public boolean two_digit(Number num) throws IOException, ParserException {
782 in.mark(1);
783 char c = (char)in.read();
784 if (digit(c)) {
785 num.number = c-'0';
786 c = (char)in.read();
787 if (!digit(c)) {
788 throw new ParserException("<digit>");
789 }
790 num.number = num.number*10 + c-'0';
791 return true;
792 } else {
793 in.reset();
794 return false;
795 }
796 }
797
798 public boolean time(Number hour, Number min, Number sec) throws IOException, ParserException {
799
800 if (!two_digit(hour)) {
801 return false;
802 }
803 if (!is_char(':')) {
804 throw new ParserException("':'");
805 }
806 if (!two_digit(min)) {
807 throw new ParserException("<2DIGIT>");
808 }
809 if (!is_char(':')) {
810 throw new ParserException("':'");
811 }
812 if (!two_digit(sec)) {
813 throw new ParserException("<2DIGIT>");
814 }
815 return true;
816 }
817
818 public boolean zone(StringBuffer zone) throws IOException, ParserException {
819 in.mark(1);
820 char c = (char)in.read();
821 if (c == '+') {
822 zone.append("GMT");
823 zone.append('+');
824 } else if (c == '-') {
825 zone.append("GMT");
826 zone.append('-');
827 } else {
828 in.reset();
829 return false;
830 }
831 c = (char)in.read();
832 if (digit(c)) {
833 zone.append(c);
834
835 for (int i=0; i<3; i++) {
836 c = (char)in.read();
837 if (!digit(c)) {
838 throw new ParserException("<digit>");
839 }
840 if (i == 1) {
841 zone.append(':');
842 }
843 zone.append(c);
844 }
845
846 return true;
847 } else {
848 throw new ParserException("<digit>");
849 }
850 }
851
852 public boolean date_time(GregorianCalendar calendar) throws IOException, ParserException {
853
854 if (!is_char('"')) {
855 return false;
856 }
857 Number day = new Number();
858 if (!date_day_fixed(day)) {
859 throw new ParserException("<date_day_fixed>");
860 }
861 if (!is_char('-')) {
862 throw new ParserException("'-'");
863 }
864 Number month = new Number();
865 if (!date_month(month)) {
866 throw new ParserException("<date_month>");
867 }
868 if (!is_char('-')) {
869 throw new ParserException("'-'");
870 }
871 Number year = new Number();
872 if (!four_digit(year)) {
873 throw new ParserException("<date_year>");
874 }
875 if (!is_char(' ')) {
876 throw new ParserException("<SP>");
877 }
878 Number hour = new Number();
879 Number min = new Number();
880 Number sec = new Number();
881 if (!time(hour, min, sec)) {
882 throw new ParserException("<time>");
883 }
884 if (!is_char(' ')) {
885 throw new ParserException("<SP>");
886 }
887 StringBuffer zone = new StringBuffer();
888 if (!zone(zone)) {
889 throw new ParserException("<time>");
890 }
891 TimeZone timeZone = TimeZone.getTimeZone(zone.toString());
892
893
894
895
896 calendar.set(year.number, month.number, day.number,
897 hour.number, min.number);
898 calendar.set(Calendar.SECOND, sec.number);
899 calendar.setTimeZone(timeZone);
900 if (!is_char('"')) {
901 throw new ParserException("'\"'");
902 }
903
904 return true;
905 }
906
907 public boolean date_text(GregorianCalendar calendar) throws IOException, ParserException {
908 Number day = new Number();
909 if (!date_day(day)) {
910 throw new ParserException("<date_day_fixed>");
911 }
912 if (!is_char('-')) {
913 throw new ParserException("'-'");
914 }
915 Number month = new Number();
916 if (!date_month(month)) {
917 throw new ParserException("<date_month>");
918 }
919 if (!is_char('-')) {
920 throw new ParserException("'-'");
921 }
922 Number year = new Number();
923 if (!four_digit(year)) {
924 throw new ParserException("<date_year>");
925 }
926 calendar.set(year.number, month.number, day.number);
927 return true;
928 }
929
930 public boolean date(GregorianCalendar calendar) throws IOException, ParserException {
931 if (is_char('"')) {
932 if (!date_text(calendar)) {
933 throw new ParserException("<date_text>");
934 }
935 if (!is_char('"')) {
936 throw new ParserException("'\"'");
937 }
938 return true;
939 } else {
940 return date_text(calendar);
941 }
942 }
943
944
945 public static class Number {
946 public int number = 0;
947 }
948
949 }