View Javadoc

1   /*
2    * Copyright (c) 2004-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
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   * Fetch response
47   *
48   * @author Daniel Sendula
49   */
50  public class FetchResponse extends NumberResponse {
51  
52      /** Logger */
53      protected static final Logger logger = LoggerFactory.getLogger(FetchResponse.class);
54  
55      /** Empty buffer */
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       * Constructor
66       * @param session imap session
67       * @param num number
68       */
69      public FetchResponse(IMAPSession session, int num) {
70          super(session, "FETCH", num);
71          append(' ');
72      }
73  
74      /**
75       * Append message to response
76       * @param b body request
77       * @param msg message
78       * @return this
79       * @throws IOException
80       * @throws MessagingException
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                          // Moved response here since is.read can fail because
94                          // of bad decoding. In case everything is ok we can
95                          // react here. In case of an error we still have
96                          // enough time to react!
97                          size = is.size();
98                          // append(' ');
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                 } // while
108                 if (f) {
109                     // Moved response here since is.read can fail because
110                     // of bad decoding. In case everything is ok we can
111                     // react here. In case of an error we still have
112                     // enough time to react!
113                     size = is.size();
114                     // append(' ');
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                     // nothing got sent yet so we can send whatever we think.
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                     // just fill rest of it with zeroes...
142                 }
143             } finally {
144                 is.close();
145             }
146         }
147         return this;
148     }
149 
150     /**
151      * Creates envelope and appends it to response
152      * @param m message
153      * @throws MessagingException
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         //env.append("NIL");
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         //addString(env, m.getHeader());
200 
201         appendSpace();
202         appendNString(m.getMessageID());
203 
204         appendCloseP();
205     }
206 
207     /**
208      * Returns list of recipients or <code>null</code>
209      * @param m message
210      * @param type type of recipients
211      * @return addresses or <code>null</code> (in case of an error)
212      */
213     public Address[] safeRecipients(MimeMessage m, Message.RecipientType type) {
214         try {
215             return m.getRecipients(type);
216         } catch (Throwable e) {
217             // Don't want to make any fuss about wrong fields...
218             return null;
219         }
220     }
221 
222     /**
223      * Returns from addresses or <code>null</code> in case of an error)
224      * @param m message
225      * @return addresses or <code>null</code>
226      */
227     public Address[] safeFrom(MimeMessage m) {
228         try {
229             return m.getFrom();
230         } catch (Throwable e) {
231             // Don't want to make any fuss about wrong fields...
232             return null;
233         }
234     }
235 
236     /**
237      * Returns sender addresses or <code>null</code> in case of an error)
238      * @param m message
239      * @return addresses or <code>null</code>
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             // Don't want to make any fuss about wrong fields...
251             return null;
252         }
253     }
254 
255     /**
256      * Creates body structore and appends it to response
257      * @param p mime part
258      * @param extensible is extensible
259      * @throws IOException
260      * @throws MessagingException
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             // Multipart shouldn't fail with this exception.
270             // For else this shouldn't matter!
271             // o stays null
272         } catch (IOException e) {
273             // o stays null
274             // TODO this should be strengthened a bit
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                     //if (i > 0) {
283                     //    appendSpace();
284                     //}
285                     MimePart innerPart = (MimePart)mp.getBodyPart(i);
286                     createBodyStructure(innerPart, extensible);
287                 } // for
288             }
289             appendSpace();
290             type = mp.getContentType();
291             appendObject(getSubtype(type));
292 
293             if (extensible) {
294                 // Extension
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             //res.append("()"); // Body Parameters List
308             //res.append("NIL");
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                 //lines = 25;
338                 if (lines >= 0) {
339                     append(lines);
340                 } else {
341                     appendNil();
342                 }
343             } else if (type.startsWith("message/rfc822") && (o != null)) {
344                 // o is null only if there was IOException or UnsupportedEncodingException
345                 appendSpace();
346                 createEnvelope((MimeMessage)o);
347                 appendSpace();
348                 createBodyStructure((MimeMessage)o, extensible);
349                 appendSpace();
350                 int lines = countLines(p);
351                 //lines = 25;
352                 if (lines >= 0) {
353                     append(lines);
354                 } else {
355                     appendNil();
356                 }
357             }
358             /*
359             if (extensible) {
360                 // Extension ???
361                 appendSpace();
362                 appendNil();
363                 appendSpace();
364                 appendNil();
365                 appendSpace();
366                 appendNil();
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      * Appends NIL
382      */
383     public void appendNil() {
384         append("NIL");
385     }
386 
387     /**
388      * Appends space
389      */
390     public void appendSpace() {
391         append(' ');
392     }
393 
394     /**
395      * Appends (
396      */
397     public void appendOpenP() {
398         append('(');
399     }
400 
401     /**
402      * Appends )
403      */
404     public void appendCloseP() {
405         append(')');
406     }
407 
408     /**
409      * Appends quotation marks
410      */
411     public void appendQuote() {
412         append('"');
413     }
414 
415     /**
416      * Appends date.
417      * @param d date. Maybe null (and it will append NIL then)
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      * Appends string escaping all offending characters
431      * @param s string
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                     // There isn't much we can do right now so we will ignore it.
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                     } // for
467                     appendQuote();
468                     append(buf);
469                     appendQuote();
470                 }
471             }
472         }
473     }
474 
475     /**
476      * Appends NSTRING.
477      * @param s string. Maybe null (and it will append NIL then)
478      */
479     protected void appendNString(String s) {
480         if (s == null) {
481            appendNil();
482         } else {
483             appendString(s);
484         }
485     }
486 
487     /**
488      * Appends strings separated with spaces
489      * @param s strings. Maybe null (and it will append NIL then)
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      * Appends addresses
513      * @param ss addresses as strings
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                         // we don't care - we can't do anything - anyway
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      * Appends addresses
541      * @param a addresses. May be null and it will append NIL then.
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             } // for
587             appendCloseP();
588         }
589     }
590 
591     /**
592      * Appends object calling toString method
593      * @param o object. May be null and it will append NIL then
594      */
595     protected void appendObject(Object o) {
596         if (o == null) {
597             appendNil();
598         } else {
599             //appendString(o.toString().toUpperCase());
600             appendString(o.toString());
601         }
602     }
603 
604     /**
605      * Appends parameters
606      * @param type type of parameters
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      * Appends parameter
649      * @param param existing parameters
650      * @return new parameter
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      * Appends disposition or NIL
677      * @param part mime part
678      * @throws MessagingException
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      * Returns mime type
700      * @param type mime type
701      * @return type only without extra parameters
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      * Returns mime sub type
717      * @param type mime type
718      * @return sub type only
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      * Counts lines in given part
736      * @param part part
737      * @return number or lines
738      * @throws MessagingException
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                     } // while
754                 } finally {
755                     is.close();
756                 }
757             } catch (IOException e) {
758                 // throw new MessagingException("Cannot count lines", e);
759                 // ignore
760                 lines = 1; // Lets see if this do...
761             }
762             return lines;
763         } else {
764             return -1;
765         }
766 
767     }
768 }