1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.mercury.imap.response;
14
15 import org.abstracthorizon.mercury.imap.IMAPSession;
16 import org.abstracthorizon.mercury.imap.util.MessageUtilities;
17 import org.abstracthorizon.mercury.imap.util.section.Body;
18 import org.abstracthorizon.mercury.imap.util.section.MeasuredInputStream;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.OutputStream;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.io.UnsupportedEncodingException;
28 import java.util.ArrayList;
29 import java.util.Date;
30
31 import javax.mail.Address;
32 import javax.mail.Message;
33 import javax.mail.MessagingException;
34 import javax.mail.Multipart;
35 import javax.mail.Part;
36 import javax.mail.internet.AddressException;
37 import javax.mail.internet.InternetAddress;
38 import javax.mail.internet.MimeMessage;
39 import javax.mail.internet.MimePart;
40 import javax.mail.internet.NewsAddress;
41
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50 public class FetchResponse extends NumberResponse {
51
52
53 protected static final Logger logger = LoggerFactory.getLogger(FetchResponse.class);
54
55
56 public static final byte[] empty = new byte[10240];
57
58 static {
59 for (int i=0; i<empty.length; i++) {
60 empty[i] = 32;
61 }
62 }
63
64
65
66
67
68
69 public FetchResponse(IMAPSession session, int num) {
70 super(session, "FETCH", num);
71 append(' ');
72 }
73
74
75
76
77
78
79
80
81
82 public FetchResponse append(Body b, MimeMessage msg) throws IOException, MessagingException {
83 MeasuredInputStream is = b.getInputStream(msg);
84 OutputStream out = (OutputStream)session.adapt(OutputStream.class);
85 synchronized (out) {
86 long size = 0;
87 boolean f = true;
88 try {
89 byte[] buf = new byte[10240];
90 int r = is.read(buf);
91 while (r >= 0) {
92 if (f) {
93
94
95
96
97 size = is.size();
98
99 append(b.toString());
100 append(" {").append(size).append("}");
101 commit();
102 f = false;
103 }
104 append(buf, 0, r);
105 size = size - r;
106 r = is.read(buf);
107 }
108 if (f) {
109
110
111
112
113 size = is.size();
114
115 append(b.toString());
116 append(" {").append(size).append("}");
117 commit();
118 f = false;
119 }
120 } catch (Exception e) {
121 session.getDebugStream().write(e.getMessage().getBytes());
122 session.setKeepLog(true);
123 logger.error("Error reading body for message with id="+msg.getMessageID()+" subject="+msg.getSubject(), e);
124 if (size == 0) {
125
126 StringWriter exc = new StringWriter();
127 PrintWriter eout = new PrintWriter(exc);
128 e.printStackTrace(eout);
129 append(b.toString()).append(" \"").append(exc.toString()).append('"');
130 } else {
131
132 while (size > 0) {
133 if (size > 10240) {
134 append(empty, 0, 10240);
135 size = size - 10240;
136 } else {
137 append(empty, 0, (int)size);
138 size = 0;
139 }
140 }
141
142 }
143 } finally {
144 is.close();
145 }
146 }
147 return this;
148 }
149
150
151
152
153
154
155 public void createEnvelope(MimeMessage m) throws MessagingException {
156 appendOpenP();
157
158 Date sentDate = m.getSentDate();
159 if (sentDate == null) {
160 sentDate = m.getReceivedDate();
161 }
162 appendDate(sentDate);
163
164 appendSpace();
165
166 appendNString(m.getSubject());
167
168 appendSpace();
169 appendAddress(safeFrom(m));
170
171 appendSpace();
172 appendAddress(safeSender(m));
173
174 appendSpace();
175 Address[] tos = null;
176 try {
177 tos = m.getReplyTo();
178 } catch (Exception e) {
179 }
180 appendAddress(tos);
181
182 appendSpace();
183 appendAddress(safeRecipients(m, Message.RecipientType.TO));
184
185 appendSpace();
186 appendAddress(safeRecipients(m, Message.RecipientType.CC));
187
188 appendSpace();
189 appendAddress(safeRecipients(m, Message.RecipientType.BCC));
190
191 appendSpace();
192
193 String[] s = m.getHeader("in-reply-to");
194 if ((s == null) || (s.length == 0)) {
195 appendNString((String)null);
196 } else {
197 appendNString(s[0]);
198 }
199
200
201 appendSpace();
202 appendNString(m.getMessageID());
203
204 appendCloseP();
205 }
206
207
208
209
210
211
212
213 public Address[] safeRecipients(MimeMessage m, Message.RecipientType type) {
214 try {
215 return m.getRecipients(type);
216 } catch (Throwable e) {
217
218 return null;
219 }
220 }
221
222
223
224
225
226
227 public Address[] safeFrom(MimeMessage m) {
228 try {
229 return m.getFrom();
230 } catch (Throwable e) {
231
232 return null;
233 }
234 }
235
236
237
238
239
240
241 public Address[] safeSender(MimeMessage m) {
242 try {
243 Address a = m.getSender();
244 if (a == null) {
245 return null;
246 } else {
247 return new Address[]{a};
248 }
249 } catch (Throwable e) {
250
251 return null;
252 }
253 }
254
255
256
257
258
259
260
261
262 public void createBodyStructure(MimePart p, boolean extensible) throws IOException, MessagingException {
263 appendOpenP();
264 Object o = null;
265 try {
266 o = p.getContent();
267 } catch (UnsupportedEncodingException e) {
268
269
270
271
272 } catch (IOException e) {
273
274
275 }
276 String type = p.getContentType();
277 if (o instanceof Multipart) {
278 Multipart mp = (Multipart)o;
279 int count = mp.getCount();
280 if (count > 0) {
281 for (int i=0; i<count; i++) {
282
283
284
285 MimePart innerPart = (MimePart)mp.getBodyPart(i);
286 createBodyStructure(innerPart, extensible);
287 }
288 }
289 appendSpace();
290 type = mp.getContentType();
291 appendObject(getSubtype(type));
292
293 if (extensible) {
294
295 appendSpace();
296 appendParameters(type);
297 appendSpace();
298 appendDisposition(p);
299 appendSpace();
300 appendObject(p.getContentLanguage());
301 }
302 } else {
303 appendObject(getType(type));
304 appendSpace();
305 appendObject(getSubtype(type));
306 appendSpace();
307
308
309 appendParameters(type);
310 appendSpace();
311
312 appendObject(p.getContentID());
313 appendSpace();
314
315 appendObject(p.getDescription());
316 appendSpace();
317
318 String encoding = p.getEncoding();
319 if (encoding == null) {
320 encoding = "7BIT";
321 }
322 appendObject(encoding);
323 appendSpace();
324
325 int size = p.getSize();
326 if (size < 0) {
327 size = 0;
328 }
329 if (size >= 0) {
330 append(size);
331 } else {
332 appendNil();
333 }
334 if (type.startsWith("text")) {
335 appendSpace();
336 int lines = countLines(p);
337
338 if (lines >= 0) {
339 append(lines);
340 } else {
341 appendNil();
342 }
343 } else if (type.startsWith("message/rfc822") && (o != null)) {
344
345 appendSpace();
346 createEnvelope((MimeMessage)o);
347 appendSpace();
348 createBodyStructure((MimeMessage)o, extensible);
349 appendSpace();
350 int lines = countLines(p);
351
352 if (lines >= 0) {
353 append(lines);
354 } else {
355 appendNil();
356 }
357 }
358
359
360
361
362
363
364
365
366
367
368
369 appendSpace();
370 appendObject(p.getContentMD5());
371 appendSpace();
372 appendDisposition(p);
373 appendSpace();
374 appendObject(p.getContentLanguage());
375 }
376 appendCloseP();
377
378 }
379
380
381
382
383 public void appendNil() {
384 append("NIL");
385 }
386
387
388
389
390 public void appendSpace() {
391 append(' ');
392 }
393
394
395
396
397 public void appendOpenP() {
398 append('(');
399 }
400
401
402
403
404 public void appendCloseP() {
405 append(')');
406 }
407
408
409
410
411 public void appendQuote() {
412 append('"');
413 }
414
415
416
417
418
419 protected void appendDate(Date d) {
420 if (d != null) {
421 appendQuote();
422 append(MessageUtilities.dateFormat.format(d));
423 appendQuote();
424 } else {
425 appendNil();
426 }
427 }
428
429
430
431
432
433 protected void appendString(String s) {
434 if (s != null) {
435 boolean literal = false;
436 if ((s.indexOf('\n') >= 0) || (s.indexOf('\r') >= 0)) {
437 literal = true;
438 }
439
440 if (literal) {
441 try {
442 append('{').append(s.length()).append('}');
443 commit();
444 append(s.getBytes());
445 } catch (IOException ignore) {
446
447 }
448 } else {
449 int i = s.indexOf('"');
450 int j = s.indexOf('\\');
451 if ((i < 0) && (j < 0)) {
452 append('"').append(s).append('"');
453 } else {
454
455 StringBuffer buf = new StringBuffer();
456 char c;
457 for (i=0; i<s.length(); i++) {
458 c = s.charAt(i);
459 if (c == '"') {
460 buf.append('\\').append(c);
461 } else if (c == '\\') {
462 buf.append(c).append(c);
463 } else {
464 buf.append(c);
465 }
466 }
467 appendQuote();
468 append(buf);
469 appendQuote();
470 }
471 }
472 }
473 }
474
475
476
477
478
479 protected void appendNString(String s) {
480 if (s == null) {
481 appendNil();
482 } else {
483 appendString(s);
484 }
485 }
486
487
488
489
490
491 protected void appendString(String[] s) {
492 if (s == null) {
493 appendNil();
494 } else {
495 appendQuote();
496 boolean first = true;
497 for (int i=0; i<s.length; i++) {
498 if (s[i] != null) {
499 if (first) {
500 first = false;
501 } else {
502 append(' ');
503 }
504 appendString(s[i]);
505 }
506 }
507 appendQuote();
508 }
509 }
510
511
512
513
514
515 protected void appendAddress(String[] ss) {
516 if (ss != null) {
517 ArrayList<InternetAddress> list = new ArrayList<InternetAddress>();
518 if (ss.length > 0) {
519 for (int i=0; i<ss.length; i++) {
520 try {
521 list.add(new InternetAddress(ss[i]));
522 } catch (AddressException e) {
523
524 }
525 }
526 }
527 if (list.size() > 0) {
528 Address[] as = new Address[list.size()];
529 as = list.toArray(as);
530 appendAddress(as);
531 } else {
532 appendNil();
533 }
534 } else {
535 appendNil();
536 }
537 }
538
539
540
541
542
543 protected void appendAddress(Address[] a) {
544 if ((a == null) || (a.length == 0)) {
545 appendNil();
546 } else {
547 appendOpenP();
548 for (int i=0; i<a.length; i++) {
549 appendOpenP();
550 if (a[i] instanceof InternetAddress) {
551 InternetAddress ia = (InternetAddress)a[i];
552 appendNString(ia.getPersonal());
553 appendSpace();
554 appendNil();
555 appendSpace();
556 String adr = ia.getAddress();
557 if (adr == null) {
558 appendSpace();
559 appendNil();
560 appendSpace();
561 appendNil();
562 } else {
563 int j = adr.indexOf('@');
564 if (j >= 0) {
565 String n = adr.substring(0, j);
566 String ad = adr.substring(j+1);
567 appendNString(n);
568 append(' ');
569 appendNString(ad);
570 } else {
571 appendNil();
572 appendSpace();
573 appendNString(adr);
574 }
575 }
576 } else {
577 appendNil();
578 appendSpace();
579 appendNil();
580 appendSpace();
581 appendNil();
582 appendSpace();
583 append(((NewsAddress)a[i]).getHost());
584 }
585 appendCloseP();
586 }
587 appendCloseP();
588 }
589 }
590
591
592
593
594
595 protected void appendObject(Object o) {
596 if (o == null) {
597 appendNil();
598 } else {
599
600 appendString(o.toString());
601 }
602 }
603
604
605
606
607
608 protected void appendParameters(String type) {
609 int i = type.indexOf(';');
610 if (i < 0) {
611 appendNil();
612 return;
613 }
614
615 boolean first = true;
616 int j = i+1;
617 while (i > 0) {
618 String p = null;
619 i = type.indexOf(';',j);
620 if (i < 0) {
621 p = type.substring(j).trim();
622 } else {
623 p = type.substring(j, i).trim();
624 j = i + 1;
625 }
626 if (p.length() > 0) {
627 p = appendParameter(p);
628 if (p != null) {
629 if (first) {
630 appendOpenP();
631 first = false;
632 } else {
633 appendSpace();
634 }
635 append(p);
636 }
637 }
638 }
639
640 if (first) {
641 appendNil();
642 } else {
643 appendCloseP();
644 }
645 }
646
647
648
649
650
651
652 protected String appendParameter(String param) {
653 param = param.trim();
654 int i = param.indexOf('=');
655 if (i + 1 >= param.length()) {
656 return null;
657 }
658 if (i < 0) {
659 return null;
660 }
661
662 String name = param.substring(0, i).trim().toUpperCase();
663 String value = param.substring(i + 1).trim();
664 if (value.charAt(0) == '"') {
665 if (value.charAt(value.length()-1) == '"') {
666 return "\""+name+"\" "+value;
667 } else {
668 return null;
669 }
670 } else {
671 return "\""+name+"\" \""+value.toUpperCase()+"\"";
672 }
673 }
674
675
676
677
678
679
680 protected void appendDisposition(MimePart part) throws MessagingException {
681 String disposition = part.getDisposition();
682 if (disposition == null) {
683 appendNil();
684 return;
685 }
686 String[] ss = part.getHeader("Content-Disposition");
687 if ((ss == null) || (ss.length == 0)) {
688 appendNil();
689 return;
690 }
691 appendOpenP();
692 appendObject(disposition);
693 appendSpace();
694 appendParameters(ss[0]);
695 appendCloseP();
696 }
697
698
699
700
701
702
703 protected String getType(String type) {
704 int i = type.indexOf('/');
705 if (i >= 0) {
706 return type.substring(0, i).toUpperCase();
707 }
708 i = type.indexOf(';');
709 if (i >= 0) {
710 return type.substring(0, i).toUpperCase();
711 }
712 return type.toUpperCase();
713 }
714
715
716
717
718
719
720 protected String getSubtype(String type) {
721 int i = type.indexOf('/');
722 if (i >= 0) {
723 int j = type.indexOf(';');
724 if (j >= 0) {
725 return type.substring(i+1, j).toUpperCase();
726 } else {
727 return type.substring(i+1).toUpperCase();
728 }
729 } else {
730 return null;
731 }
732 }
733
734
735
736
737
738
739
740 protected int countLines(Part part) throws MessagingException {
741 String type = part.getContentType();
742 if (type.startsWith("text") || type.startsWith("message/rfc822")) {
743 int lines = 0;
744 try {
745 InputStream is = part.getInputStream();
746 try {
747 InputStreamReader reader = new InputStreamReader(part.getInputStream(), "ISO-8859-1");
748 BufferedReader in = new BufferedReader(reader);
749 String line = in.readLine();
750 while (line != null) {
751 lines = lines + 1;
752 line = in.readLine();
753 }
754 } finally {
755 is.close();
756 }
757 } catch (IOException e) {
758
759
760 lines = 1;
761 }
762 return lines;
763 } else {
764 return -1;
765 }
766
767 }
768 }