Resolved MIME4J-48: Messages containing qp or base64 encoded embedded messages should...
[mime4j.git] / src / main / java / org / apache / james / mime4j / MimeEntity.java
bloba7cd16f17f489d97621ca034348bd6c20139ec07
1 package org.apache.james.mime4j;
3 import java.io.IOException;
4 import java.io.InputStream;
6 import org.apache.james.mime4j.decoder.Base64InputStream;
7 import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
8 import org.apache.james.mime4j.util.MimeUtil;
10 public class MimeEntity extends AbstractEntity {
12 /**
13 * Internal state, not exposed.
15 private static final int T_IN_BODYPART = -2;
16 /**
17 * Internal state, not exposed.
19 private static final int T_IN_MESSAGE = -3;
21 private final RootInputStream rootStream;
22 private final InputStream rawStream;
23 private final InputBuffer inbuffer;
25 private int recursionMode;
26 private MimeBoundaryInputStream mimeStream;
27 private BufferingInputStreamAdaptor dataStream;
28 private boolean skipHeader;
30 private byte[] tmpbuf;
32 public MimeEntity(
33 RootInputStream rootStream,
34 InputStream rawStream,
35 InputBuffer inbuffer,
36 BodyDescriptor parent,
37 int startState,
38 int endState,
39 boolean maximalBodyDescriptor,
40 boolean strictParsing) {
41 super(parent, startState, endState, maximalBodyDescriptor, strictParsing);
42 this.rootStream = rootStream;
43 this.inbuffer = inbuffer;
44 this.rawStream = rawStream;
45 this.dataStream = new BufferingInputStreamAdaptor(rawStream);
46 this.skipHeader = false;
49 public MimeEntity(
50 RootInputStream rootStream,
51 InputStream rawStream,
52 InputBuffer inbuffer,
53 BodyDescriptor parent,
54 int startState,
55 int endState) {
56 this(rootStream, rawStream, inbuffer, parent, startState, endState, false, false);
59 public int getRecursionMode() {
60 return recursionMode;
63 public void setRecursionMode(int recursionMode) {
64 this.recursionMode = recursionMode;
67 public void skipHeader(String contentType) {
68 if (state != EntityStates.T_START_MESSAGE) {
69 throw new IllegalStateException("Invalid state: " + stateToString(state));
71 skipHeader = true;
72 body.addField("Content-Type", contentType);
75 protected int getLineNumber() {
76 return rootStream.getLineNumber();
79 protected BufferingInputStream getDataStream() {
80 return dataStream;
83 public EntityStateMachine advance() throws IOException, MimeException {
84 switch (state) {
85 case EntityStates.T_START_MESSAGE:
86 if (skipHeader) {
87 state = EntityStates.T_END_HEADER;
88 } else {
89 state = EntityStates.T_START_HEADER;
91 break;
92 case EntityStates.T_START_BODYPART:
93 state = EntityStates.T_START_HEADER;
94 break;
95 case EntityStates.T_START_HEADER:
96 case EntityStates.T_FIELD:
97 state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
98 break;
99 case EntityStates.T_END_HEADER:
100 String mimeType = body.getMimeType();
101 if (recursionMode == RecursionMode.M_FLAT) {
102 state = EntityStates.T_BODY;
103 } else if (MimeUtil.isMultipart(mimeType)) {
104 state = EntityStates.T_START_MULTIPART;
105 clearMimeStream();
106 } else if (recursionMode != RecursionMode.M_NO_RECURSE
107 && MimeUtil.isMessage(mimeType)) {
108 state = T_IN_MESSAGE;
109 return nextMessage();
110 } else {
111 state = EntityStates.T_BODY;
113 break;
114 case EntityStates.T_START_MULTIPART:
115 if (dataStream.isUsed()) {
116 advanceToBoundary();
117 state = EntityStates.T_END_MULTIPART;
118 } else {
119 createMimeStream();
120 state = EntityStates.T_PREAMBLE;
122 break;
123 case EntityStates.T_PREAMBLE:
124 advanceToBoundary();
125 if (mimeStream.isLastPart()) {
126 clearMimeStream();
127 state = EntityStates.T_END_MULTIPART;
128 } else {
129 createMimeStream();
130 state = T_IN_BODYPART;
131 return nextMimeEntity();
133 break;
134 case T_IN_BODYPART:
135 advanceToBoundary();
136 if (mimeStream.eof() && !mimeStream.isLastPart()) {
137 monitor(Event.MIME_BODY_PREMATURE_END);
138 } else {
139 if (!mimeStream.isLastPart()) {
140 createMimeStream();
141 state = T_IN_BODYPART;
142 return nextMimeEntity();
145 clearMimeStream();
146 state = EntityStates.T_EPILOGUE;
147 break;
148 case EntityStates.T_EPILOGUE:
149 state = EntityStates.T_END_MULTIPART;
150 break;
151 case EntityStates.T_BODY:
152 case EntityStates.T_END_MULTIPART:
153 case T_IN_MESSAGE:
154 state = endState;
155 break;
156 default:
157 if (state == endState) {
158 state = EntityStates.T_END_OF_STREAM;
159 break;
161 throw new IllegalStateException("Invalid state: " + stateToString(state));
163 return null;
166 private void createMimeStream() throws IOException {
167 mimeStream = new MimeBoundaryInputStream(inbuffer, body.getBoundary());
168 dataStream = new BufferingInputStreamAdaptor(mimeStream);
169 // If multipart message is embedded into another multipart message
170 // make sure to reset parent's mime stream
171 if (rawStream instanceof BufferingInputStream) {
172 ((BufferingInputStream) rawStream).reset();
176 private void clearMimeStream() {
177 mimeStream = null;
178 dataStream = new BufferingInputStreamAdaptor(rawStream);
181 private void advanceToBoundary() throws IOException {
182 if (!dataStream.eof()) {
183 if (tmpbuf == null) {
184 tmpbuf = new byte[2048];
186 while (dataStream.read(tmpbuf)!= -1) {
191 private EntityStateMachine nextMessage() {
192 String transferEncoding = body.getTransferEncoding();
193 InputStream instream;
194 InputBuffer buffer;
195 if (MimeUtil.isBase64Encoding(transferEncoding)) {
196 log.debug("base64 encoded message/rfc822 detected");
197 instream = new Base64InputStream(dataStream);
198 buffer = new InputBuffer(instream, 4 * 1024);
199 } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
200 log.debug("quoted-printable encoded message/rfc822 detected");
201 instream = new QuotedPrintableInputStream(dataStream);
202 buffer = new InputBuffer(instream, 4 * 1024);
203 } else {
204 instream = dataStream;
205 buffer = inbuffer;
208 if (recursionMode == RecursionMode.M_RAW) {
209 RawEntity message = new RawEntity(instream);
210 return message;
211 } else {
212 MimeEntity message = new MimeEntity(
213 rootStream,
214 instream,
215 buffer,
216 body,
217 EntityStates.T_START_MESSAGE,
218 EntityStates.T_END_MESSAGE,
219 maximalBodyDescriptor,
220 strictParsing);
221 message.setRecursionMode(recursionMode);
222 return message;
226 private EntityStateMachine nextMimeEntity() {
227 if (recursionMode == RecursionMode.M_RAW) {
228 RawEntity message = new RawEntity(mimeStream);
229 return message;
230 } else {
231 MimeEntity mimeentity = new MimeEntity(
232 rootStream,
233 mimeStream,
234 inbuffer,
235 body,
236 EntityStates.T_START_BODYPART,
237 EntityStates.T_END_BODYPART,
238 maximalBodyDescriptor,
239 strictParsing);
240 mimeentity.setRecursionMode(recursionMode);
241 return mimeentity;
245 public InputStream getContentStream() {
246 switch (state) {
247 case EntityStates.T_START_MULTIPART:
248 case EntityStates.T_PREAMBLE:
249 case EntityStates.T_EPILOGUE:
250 case EntityStates.T_BODY:
251 return this.dataStream;
252 default:
253 throw new IllegalStateException("Invalid state: " + stateToString(state));