1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
43
44 private static final int T_IN_BODYPART = -2;
45
46
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
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 }