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.util.section;
14  
15  import java.io.ByteArrayInputStream;
16  import java.io.ByteArrayOutputStream;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.PrintStream;
20  import java.io.SequenceInputStream;
21  import javax.mail.BodyPart;
22  import javax.mail.Flags;
23  import javax.mail.Folder;
24  import javax.mail.Message;
25  import javax.mail.MessagingException;
26  import javax.mail.Multipart;
27  import javax.mail.Part;
28  import javax.mail.internet.MimeBodyPart;
29  import javax.mail.internet.MimeMessage;
30  import javax.mail.internet.MimePart;
31  import javax.mail.internet.SharedInputStream;
32  
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import org.abstracthorizon.mercury.common.io.RangedInputStream;
37  import org.abstracthorizon.mercury.imap.util.MessageUtilities;
38  
39  /**
40   * BODY section
41   *
42   * @author Daniel Sendula
43   */
44  public class Body extends PointerSection {
45  
46      /** Logger */
47      protected static final Logger logger = LoggerFactory.getLogger(Body.class);
48  
49      /** Is this PEEK */
50      public boolean peek = false;
51  
52      /** From */
53      public int from = -1;
54  
55      /** To */
56      public int to = -1;
57  
58      /** RFC822 keyword */
59      public boolean rfc822 = false;
60  
61      /**
62       * Constructor
63       */
64      public Body() {
65      }
66  
67      /**
68       * Returns measured input stream of a message
69       * @param msg message
70       * @return measured input stream
71       * @throws IOException
72       * @throws MessagingException
73       */
74      public MeasuredInputStream getInputStream(MimeMessage msg) throws IOException, MessagingException {
75          long size = -1;
76          InputStream is = null;
77          String headers = null;
78  
79  
80          Part part = msg;
81          PointerSection section = this;
82  
83          while ((section.child != null) && (section.child instanceof MultipartSection)) {
84              section = (MultipartSection)section.child;
85              Object o = part.getContent();
86              int partNo = ((MultipartSection)section).partNo-1;
87              if (o instanceof Multipart) {
88                  part = ((Multipart)o).getBodyPart(partNo);
89                  if (part.getContentType().toLowerCase().startsWith("message/rfc822")) {
90                      part = (MimePart)part.getContent();
91                  }
92              } else if (o instanceof MimeMessage) {
93                  part = (MimeMessage)o;
94              } else if (partNo == 0) {
95                  //part = part;
96              } else {
97                  throw new MessagingException("Multipart part expected");
98              }
99  
100         }
101         if ((section.child != null) && (section.child instanceof HeaderSection)) {
102 
103             HeaderSection ss = (HeaderSection)section.child;
104             if (ss.all) {
105                 headers = MessageUtilities.createHeaders((MimePart)part);
106             } else {
107                 headers = MessageUtilities.createHeaders((MimePart)part, ss.fields, ss.not);
108             }
109             byte[] headerBytes = headers.getBytes();
110             is = new ByteArrayInputStream(headerBytes);
111             size = headerBytes.length;
112 
113         } else if ((section.child != null) && (section.child instanceof TextSection)) {
114             is = getPartsInputStream(part);
115             size = calcSize(part);
116         } else if ((section.child != null) && (section.child instanceof MimeSection)) {
117             headers = MessageUtilities.createHeaders((MimePart)part);
118             byte[] headerBytes = headers.getBytes();
119             is = new ByteArrayInputStream(headerBytes);
120             size = headerBytes.length;
121         } else if (part instanceof MimeMessage) {
122             headers = MessageUtilities.createHeaders((MimePart)part);
123             byte[] headerBytes = headers.getBytes();
124             ByteArrayInputStream bais = new ByteArrayInputStream(headerBytes);
125             is = getPartsInputStream(part);
126             is = new SequenceInputStream(bais, is);
127             long partSize = calcSize(part);
128             long headersSize = headerBytes.length;
129             size = partSize+headersSize;
130         } else {
131             is = getPartsInputStream(part);
132             size = calcSize(part);
133         }
134 
135         if ((from >= 0) && (to >= 0)) {
136             if (to > size) {
137                 to = (int)size;
138             }
139             size = to-from;
140             if (is instanceof SharedInputStream) {
141                 is = ((SharedInputStream)is).newStream(from, to);
142             } else {
143                 is = new RangedInputStream(is, from, to);
144             }
145         }
146 
147         return new MeasuredInputStream(is, size);
148     }
149 
150     /**
151      * Returns input stream of a part
152      * @param part part
153      * @return input stream
154      * @throws IOException
155      * @throws MessagingException
156      */
157     public InputStream getPartsInputStream(Part part) throws IOException, MessagingException {
158         try {
159             if (part instanceof MimeBodyPart) {
160                 return ((MimeBodyPart)part).getRawInputStream();
161                 // Since this is raw stream we cannot skip headers as was thought before...
162                 // return skipHeaders(((MimeBodyPart)part).getRawInputStream());
163             } else if (part instanceof MimeMessage) {
164                 return ((MimeMessage)part).getRawInputStream();
165             } else {
166                 // This won't happen since part can be only BodyPart or Message (MimeBodyPart or MimeMessage)
167                 // This is only if previous statement is wrong.
168                 return part.getInputStream();
169             }
170         } catch (IOException e) {
171             try {
172                 // Correcting file on the fly
173                 String oldEncoding = "";
174                 String[] encodings = part.getHeader("Content-Transfer-Encoding");
175                 if (encodings.length > 0) {
176                     oldEncoding = encodings[0];
177                 }
178                 part.addHeader("X-Error", "Wrong: Content-Transfer-Encoding: "+oldEncoding);
179                 part.setHeader("Content-Transfer-Encoding", "7bit");
180                 saveChanges(part);
181                 return part.getInputStream();
182             } catch (MessagingException me) {
183                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
184                 PrintStream ps = new PrintStream(baos);
185                 me.printStackTrace(ps);
186                 return new ByteArrayInputStream(baos.toByteArray());
187             }
188         }
189     }
190 
191     /**
192      * Saves changes of the part
193      * @param part part
194      * @throws MessagingException
195      */
196     protected void saveChanges(Part part) throws MessagingException {
197         while ((part != null) && !(part instanceof MimeMessage)) {
198             if (part instanceof BodyPart) {
199                 Multipart mp = ((BodyPart)part).getParent();
200                 part = mp.getParent();
201             } else {
202                 // This shouldn't happened since Part can be or Message or BodyPart.
203                 // We are here not dealing with any other messages then MimeMessages and
204                 // then we are fine to be able to get to the top of hierarchy where
205                 // part is really MimeMessage so we can save it.
206 
207                 // This is just in case previous statement is not entirely true.
208                 // Then we won't do anything.
209                 part = null;
210             }
211         }
212         if (part instanceof MimeMessage) {
213             MimeMessage mm = (MimeMessage)part;
214             Folder f = mm.getFolder();
215             f.appendMessages(new Message[]{mm});
216             mm.setFlag(Flags.Flag.DELETED, true);
217         }
218     }
219 
220     /**
221      * Returns <code>true</code> if child exists and child is instance of {@link HeaderSection}
222      * @return <code>true</code> if child exists and child is instance of {@link HeaderSection}
223      */
224     public boolean hasStream() {
225         return !((child != null) && (child instanceof HeaderSection));
226     }
227 
228     /**
229      * String representation of this class
230      * @return string representation of this class
231      */
232     public String toString() {
233         StringBuffer b = new StringBuffer();
234         if (rfc822) {
235             if ((child != null) && (child instanceof HeaderSection)) {
236                 b.append("RFC822.HEADER");
237             } else if ((child != null) && (child instanceof TextSection)) {
238                 b.append("RFC822.TEXT");
239             } else {
240                 b.append("RFC822");
241             }
242         } else {
243             b.append("BODY");
244             b.append("[");
245             if (child != null) {
246                 b.append(child.toString());
247             }
248             b.append("]");
249             if ((from >= 0) && (to >= 0)) {
250                 b.append("<").append(from).append('.').append(to).append(">");
251             }
252         }
253         return b.toString();
254     }
255 
256     /**
257      * This method skips over header in the input stream
258      * @param is input stream
259      * @return same input stream
260      * @throws IOException
261      */
262     protected InputStream skipHeaders(InputStream is) throws IOException {
263         int state = 0;
264         while (true) {
265             int c = is.read();
266             if (c < 0) {
267                 return is;
268             }
269             switch (state) {
270                 case 0: {
271                     if (c == 13) {
272                         state = 1;
273                     }
274                     break;
275                 }
276                 case 1: {
277                     if (c == 10) {
278                         state = 2;
279                     } else {
280                         state = 0;
281                     }
282                     break;
283                 }
284                 case 2: {
285                     if (c == 13) {
286                         state = 3;
287                     } else {
288                         state = 0;
289                     }
290                     break;
291                 }
292                 case 3: {
293                     if (c == 10) {
294                         return is;
295                     }
296                     break;
297                 }
298             } // switch
299         } // while
300     }
301 
302     /**
303      * Calculates the size of given part
304      * @param part part
305      * @return size of given part
306      * @throws IOException
307      * @throws MessagingException
308      */
309     protected long calcSize(Part part) throws IOException, MessagingException {
310         InputStream is = getPartsInputStream(part);
311         if (is instanceof ByteArrayInputStream) {
312             return is.available();
313         }
314         if (is instanceof SharedInputStream) {
315             return is.available();
316         }
317         try {
318             long size = 0;
319             byte[] buf = new byte[10240];
320             int r = is.read(buf);
321             while (r >= 0) {
322                 size = size+r;
323                 r = is.read(buf);
324             } // while
325             return size;
326         } catch (Exception e) {
327             // This is situation when decoding is incorrect or because of
328             // any reason we can't really read the message. In that case
329             // even simplest, undecoded, value is correct
330             return part.getSize();
331         }
332     }
333 }