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 org.apache.james.mime4j.MimeException;
23 import org.apache.james.mime4j.descriptor.BodyDescriptor;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27
28 /**
29 * <p>
30 * Parses MIME (or RFC822) message streams of bytes or characters and reports
31 * parsing events to a <code>ContentHandler</code> instance.
32 * </p>
33 * <p>
34 * Typical usage:<br/>
35 * <pre>
36 * ContentHandler handler = new MyHandler();
37 * MimeStreamParser parser = new MimeStreamParser();
38 * parser.setContentHandler(handler);
39 * parser.parse(new FileInputStream("mime.msg"));
40 * </pre>
41 */
42 public class MimeStreamParser {
43
44 private ContentHandler handler = null;
45 private boolean contentDecoding;
46
47 private final MimeTokenStream mimeTokenStream;
48
49 public MimeStreamParser(final MimeEntityConfig config) {
50 super();
51 MimeEntityConfig localConfig;
52 if (config != null) {
53 localConfig = config.clone();
54 } else {
55 localConfig = new MimeEntityConfig();
56 }
57 this.mimeTokenStream = new MimeTokenStream(localConfig);
58 this.contentDecoding = false;
59 }
60
61 public MimeStreamParser() {
62 this(null);
63 }
64
65 /**
66 * Determines whether this parser automatically decodes body content
67 * based on the on the MIME fields with the standard defaults.
68 */
69 public boolean isContentDecoding() {
70 return contentDecoding;
71 }
72
73 /**
74 * Defines whether parser should automatically decode body content
75 * based on the on the MIME fields with the standard defaults.
76 */
77 public void setContentDecoding(boolean b) {
78 this.contentDecoding = b;
79 }
80
81 /**
82 * Parses a stream of bytes containing a MIME message.
83 *
84 * @param is the stream to parse.
85 * @throws MimeException if the message can not be processed
86 * @throws IOException on I/O errors.
87 */
88 public void parse(InputStream is) throws MimeException, IOException {
89 mimeTokenStream.parse(is);
90 OUTER: for (;;) {
91 int state = mimeTokenStream.getState();
92 switch (state) {
93 case MimeTokenStream.T_BODY:
94 BodyDescriptor desc = mimeTokenStream.getBodyDescriptor();
95 InputStream bodyContent;
96 if (contentDecoding) {
97 bodyContent = mimeTokenStream.getDecodedInputStream();
98 } else {
99 bodyContent = mimeTokenStream.getInputStream();
100 }
101 handler.body(desc, bodyContent);
102 break;
103 case MimeTokenStream.T_END_BODYPART:
104 handler.endBodyPart();
105 break;
106 case MimeTokenStream.T_END_HEADER:
107 handler.endHeader();
108 break;
109 case MimeTokenStream.T_END_MESSAGE:
110 handler.endMessage();
111 break;
112 case MimeTokenStream.T_END_MULTIPART:
113 handler.endMultipart();
114 break;
115 case MimeTokenStream.T_END_OF_STREAM:
116 break OUTER;
117 case MimeTokenStream.T_EPILOGUE:
118 handler.epilogue(mimeTokenStream.getInputStream());
119 break;
120 case MimeTokenStream.T_FIELD:
121 handler.field(mimeTokenStream.getField());
122 break;
123 case MimeTokenStream.T_PREAMBLE:
124 handler.preamble(mimeTokenStream.getInputStream());
125 break;
126 case MimeTokenStream.T_RAW_ENTITY:
127 handler.raw(mimeTokenStream.getInputStream());
128 break;
129 case MimeTokenStream.T_START_BODYPART:
130 handler.startBodyPart();
131 break;
132 case MimeTokenStream.T_START_HEADER:
133 handler.startHeader();
134 break;
135 case MimeTokenStream.T_START_MESSAGE:
136 handler.startMessage();
137 break;
138 case MimeTokenStream.T_START_MULTIPART:
139 handler.startMultipart(mimeTokenStream.getBodyDescriptor());
140 break;
141 default:
142 throw new IllegalStateException("Invalid state: " + state);
143 }
144 state = mimeTokenStream.next();
145 }
146 }
147
148 /**
149 * Determines if this parser is currently in raw mode.
150 *
151 * @return <code>true</code> if in raw mode, <code>false</code>
152 * otherwise.
153 * @see #setRaw(boolean)
154 */
155 public boolean isRaw() {
156 return mimeTokenStream.isRaw();
157 }
158
159 /**
160 * Enables or disables raw mode. In raw mode all future entities
161 * (messages or body parts) in the stream will be reported to the
162 * {@link ContentHandler#raw(InputStream)} handler method only.
163 * The stream will contain the entire unparsed entity contents
164 * including header fields and whatever is in the body.
165 *
166 * @param raw <code>true</code> enables raw mode, <code>false</code>
167 * disables it.
168 */
169 public void setRaw(boolean raw) {
170 mimeTokenStream.setRecursionMode(MimeTokenStream.M_RAW);
171 }
172
173 /**
174 * Finishes the parsing and stops reading lines.
175 * NOTE: No more lines will be parsed but the parser
176 * will still call
177 * {@link ContentHandler#endMultipart()},
178 * {@link ContentHandler#endBodyPart()},
179 * {@link ContentHandler#endMessage()}, etc to match previous calls
180 * to
181 * {@link ContentHandler#startMultipart(BodyDescriptor)},
182 * {@link ContentHandler#startBodyPart()},
183 * {@link ContentHandler#startMessage()}, etc.
184 */
185 public void stop() {
186 mimeTokenStream.stop();
187 }
188
189 /**
190 * Sets the <code>ContentHandler</code> to use when reporting
191 * parsing events.
192 *
193 * @param h the <code>ContentHandler</code>.
194 */
195 public void setContentHandler(ContentHandler h) {
196 this.handler = h;
197 }
198
199 }