View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.mime4j.parser;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  
25  import org.apache.james.mime4j.MimeException;
26  import org.apache.james.mime4j.codec.Base64InputStream;
27  import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
28  import org.apache.james.mime4j.descriptor.BodyDescriptor;
29  import org.apache.james.mime4j.io.BufferedLineReaderInputStream;
30  import org.apache.james.mime4j.io.LimitedInputStream;
31  import org.apache.james.mime4j.io.LineNumberSource;
32  import org.apache.james.mime4j.io.LineReaderInputStream;
33  import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor;
34  import org.apache.james.mime4j.io.MimeBoundaryInputStream;
35  import org.apache.james.mime4j.util.ByteSequence;
36  import org.apache.james.mime4j.util.ContentUtil;
37  import org.apache.james.mime4j.util.MimeUtil;
38  
39  public class MimeEntity extends AbstractEntity {
40  
41      /**
42       * Internal state, not exposed.
43       */
44      private static final int T_IN_BODYPART = -2;
45      /**
46       * Internal state, not exposed.
47       */
48      private static final int T_IN_MESSAGE = -3;
49  
50      private final LineNumberSource lineSource;
51      private final BufferedLineReaderInputStream inbuffer;
52      
53      private int recursionMode;
54      private MimeBoundaryInputStream mimeStream;
55      private LineReaderInputStreamAdaptor dataStream;
56      private boolean skipHeader;
57      
58      private byte[] tmpbuf;
59      
60      public MimeEntity(
61              LineNumberSource lineSource,
62              BufferedLineReaderInputStream inbuffer,
63              BodyDescriptor parent, 
64              int startState, 
65              int endState,
66              MimeEntityConfig config) {
67          super(parent, startState, endState, config);
68          this.lineSource = lineSource;
69          this.inbuffer = inbuffer;
70          this.dataStream = new LineReaderInputStreamAdaptor(
71                  inbuffer,
72                  config.getMaxLineLen());
73          this.skipHeader = false;
74      }
75  
76      public MimeEntity(
77              LineNumberSource lineSource,
78              BufferedLineReaderInputStream inbuffer,
79              BodyDescriptor parent, 
80              int startState, 
81              int endState) {
82          this(lineSource, inbuffer, parent, startState, endState, 
83                  new MimeEntityConfig());
84      }
85  
86      public int getRecursionMode() {
87          return recursionMode;
88      }
89  
90      public void setRecursionMode(int recursionMode) {
91          this.recursionMode = recursionMode;
92      }
93  
94      public void skipHeader(String contentType) {
95          if (state != EntityStates.T_START_MESSAGE) {
96              throw new IllegalStateException("Invalid state: " + stateToString(state));
97          }
98          skipHeader = true;
99          ByteSequence raw = ContentUtil.encode("Content-Type: " + contentType);
100         body.addField(new RawField(raw, 12));
101     }
102 
103     @Override
104     protected int getLineNumber() {
105         if (lineSource == null)
106             return -1;
107         else
108             return lineSource.getLineNumber();
109     }
110     
111     @Override
112     protected LineReaderInputStream getDataStream() {
113         return dataStream;
114     }
115     
116     public EntityStateMachine advance() throws IOException, MimeException {
117         switch (state) {
118         case EntityStates.T_START_MESSAGE:
119             if (skipHeader) {
120                 state = EntityStates.T_END_HEADER;
121             } else {
122                 state = EntityStates.T_START_HEADER;
123             }
124             break;
125         case EntityStates.T_START_BODYPART:
126             state = EntityStates.T_START_HEADER;
127             break;
128         case EntityStates.T_START_HEADER:
129         case EntityStates.T_FIELD:
130             state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
131             break;
132         case EntityStates.T_END_HEADER:
133             String mimeType = body.getMimeType();
134             if (recursionMode == RecursionMode.M_FLAT) {
135                 state = EntityStates.T_BODY;
136             } else if (MimeUtil.isMultipart(mimeType)) {
137                 state = EntityStates.T_START_MULTIPART;
138                 clearMimeStream();
139             } else if (recursionMode != RecursionMode.M_NO_RECURSE 
140                     && MimeUtil.isMessage(mimeType)) {
141                 state = T_IN_MESSAGE;
142                 return nextMessage();
143             } else {
144                 state = EntityStates.T_BODY;
145             }
146             break;
147         case EntityStates.T_START_MULTIPART:
148             if (dataStream.isUsed()) {
149                 advanceToBoundary();            
150                 state = EntityStates.T_END_MULTIPART;
151             } else {
152                 createMimeStream();
153                 state = EntityStates.T_PREAMBLE;
154             }
155             break;
156         case EntityStates.T_PREAMBLE:
157             advanceToBoundary();            
158             if (mimeStream.isLastPart()) {
159                 clearMimeStream();
160                 state = EntityStates.T_END_MULTIPART;
161             } else {
162                 clearMimeStream();
163                 createMimeStream();
164                 state = T_IN_BODYPART;
165                 return nextMimeEntity();
166             }
167             break;
168         case T_IN_BODYPART:
169             advanceToBoundary();
170             if (mimeStream.eof() && !mimeStream.isLastPart()) {
171                 monitor(Event.MIME_BODY_PREMATURE_END);
172             } else {
173                 if (!mimeStream.isLastPart()) {
174                     clearMimeStream();
175                     createMimeStream();
176                     state = T_IN_BODYPART;
177                     return nextMimeEntity();
178                 }
179             }
180             clearMimeStream();
181             state = EntityStates.T_EPILOGUE;
182             break;
183         case EntityStates.T_EPILOGUE:
184             state = EntityStates.T_END_MULTIPART;
185             break;
186         case EntityStates.T_BODY:
187         case EntityStates.T_END_MULTIPART:
188         case T_IN_MESSAGE:
189             state = endState;
190             break;
191         default:
192             if (state == endState) {
193                 state = EntityStates.T_END_OF_STREAM;
194                 break;
195             }
196             throw new IllegalStateException("Invalid state: " + stateToString(state));
197         }
198         return null;
199     }
200 
201     private void createMimeStream() throws MimeException, IOException {
202         String boundary = body.getBoundary();
203         int bufferSize = 2 * boundary.length();
204         if (bufferSize < 4096) {
205             bufferSize = 4096;
206         }
207         try {
208             if (mimeStream != null) {
209                 mimeStream = new MimeBoundaryInputStream(
210                         new BufferedLineReaderInputStream(
211                                 mimeStream, 
212                                 bufferSize, 
213                                 config.getMaxLineLen()), 
214                         boundary);
215             } else {
216                 inbuffer.ensureCapacity(bufferSize);
217                 mimeStream = new MimeBoundaryInputStream(inbuffer, boundary);
218             }
219         } catch (IllegalArgumentException e) {
220             // thrown when boundary is too long
221             throw new MimeException(e.getMessage(), e);
222         }
223         dataStream = new LineReaderInputStreamAdaptor(
224                 mimeStream,
225                 config.getMaxLineLen()); 
226     }
227     
228     private void clearMimeStream() {
229         mimeStream = null;
230         dataStream = new LineReaderInputStreamAdaptor(
231                 inbuffer,
232                 config.getMaxLineLen()); 
233     }
234     
235     private void advanceToBoundary() throws IOException {
236         if (!dataStream.eof()) {
237             if (tmpbuf == null) {
238                 tmpbuf = new byte[2048];
239             }
240             InputStream instream = getLimitedContentStream();
241             while (instream.read(tmpbuf)!= -1) {
242             }
243         }
244     }
245     
246     private EntityStateMachine nextMessage() {
247         String transferEncoding = body.getTransferEncoding();
248         InputStream instream;
249         if (MimeUtil.isBase64Encoding(transferEncoding)) {
250             log.debug("base64 encoded message/rfc822 detected");
251             instream = new Base64InputStream(dataStream);                    
252         } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
253             log.debug("quoted-printable encoded message/rfc822 detected");
254             instream = new QuotedPrintableInputStream(dataStream);                    
255         } else {
256             instream = dataStream;
257         }
258         
259         if (recursionMode == RecursionMode.M_RAW) {
260             RawEntity message = new RawEntity(instream);
261             return message;
262         } else {
263             MimeEntity message = new MimeEntity(
264                     lineSource, 
265                     new BufferedLineReaderInputStream(
266                             instream, 
267                             4 * 1024,
268                             config.getMaxLineLen()),
269                     body, 
270                     EntityStates.T_START_MESSAGE, 
271                     EntityStates.T_END_MESSAGE,
272                     config);
273             message.setRecursionMode(recursionMode);
274             return message;
275         }
276     }
277     
278     private EntityStateMachine nextMimeEntity() {
279         if (recursionMode == RecursionMode.M_RAW) {
280             RawEntity message = new RawEntity(mimeStream);
281             return message;
282         } else {
283             BufferedLineReaderInputStream stream = new BufferedLineReaderInputStream(
284                     mimeStream, 
285                     4 * 1024,
286                     config.getMaxLineLen());
287             MimeEntity mimeentity = new MimeEntity(
288                     lineSource, 
289                     stream,
290                     body, 
291                     EntityStates.T_START_BODYPART, 
292                     EntityStates.T_END_BODYPART,
293                     config);
294             mimeentity.setRecursionMode(recursionMode);
295             return mimeentity;
296         }
297     }
298     
299     private InputStream getLimitedContentStream() {
300         long maxContentLimit = config.getMaxContentLen();
301         if (maxContentLimit >= 0) {
302             return new LimitedInputStream(dataStream, maxContentLimit);
303         } else {
304             return dataStream;
305         }
306     }
307     
308     public InputStream getContentStream() {
309         switch (state) {
310         case EntityStates.T_START_MULTIPART:
311         case EntityStates.T_PREAMBLE:
312         case EntityStates.T_EPILOGUE:
313         case EntityStates.T_BODY:
314             return getLimitedContentStream();
315         default:
316             throw new IllegalStateException("Invalid state: " + stateToString(state));
317         }
318     }
319 
320 }