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 *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
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 ****************************************************************/
20 package org
.apache
.james
.mime4j
;
22 import java
.io
.IOException
;
23 import java
.util
.BitSet
;
25 import org
.apache
.commons
.logging
.Log
;
26 import org
.apache
.commons
.logging
.LogFactory
;
27 import org
.apache
.james
.mime4j
.util
.MessageUtils
;
30 * Abstract MIME entity.
32 public abstract class AbstractEntity
implements EntityStateMachine
{
34 protected final Log log
;
36 protected final BodyDescriptor parent
;
37 protected final int startState
;
38 protected final int endState
;
39 protected final boolean maximalBodyDescriptor
;
40 protected final boolean strictParsing
;
41 protected final MutableBodyDescriptor body
;
45 private final ByteArrayBuffer linebuf
;
46 private final CharArrayBuffer fieldbuf
;
48 private int lineCount
;
49 private String field
, fieldName
, fieldValue
;
50 private boolean endOfHeader
;
52 private static final BitSet fieldChars
= new BitSet();
55 for (int i
= 0x21; i
<= 0x39; i
++) {
58 for (int i
= 0x3b; i
<= 0x7e; i
++) {
64 * Internal state, not exposed.
66 private static final int T_IN_BODYPART
= -2;
68 * Internal state, not exposed.
70 private static final int T_IN_MESSAGE
= -3;
73 BodyDescriptor parent
,
76 boolean maximalBodyDescriptor
,
77 boolean strictParsing
) {
78 this.log
= LogFactory
.getLog(getClass());
80 this.state
= startState
;
81 this.startState
= startState
;
82 this.endState
= endState
;
83 this.maximalBodyDescriptor
= maximalBodyDescriptor
;
84 this.strictParsing
= strictParsing
;
85 this.body
= newBodyDescriptor(parent
);
86 this.linebuf
= new ByteArrayBuffer(64);
87 this.fieldbuf
= new CharArrayBuffer(64);
89 this.endOfHeader
= false;
92 public int getState() {
97 * Creates a new instance of {@link BodyDescriptor}. Subclasses may override
98 * this in order to create body descriptors, that provide more specific
101 protected MutableBodyDescriptor
newBodyDescriptor(BodyDescriptor pParent
) {
102 final MutableBodyDescriptor result
;
103 if (maximalBodyDescriptor
) {
104 result
= new MaximalBodyDescriptor(pParent
);
106 result
= new DefaultBodyDescriptor(pParent
);
111 protected abstract int getLineNumber();
113 protected abstract BufferingInputStream
getDataStream();
115 private void fillFieldBuffer() throws IOException
, MimeException
{
119 BufferingInputStream instream
= getDataStream();
122 // If there's still data stuck in the line buffer
123 // copy it to the field buffer
124 int len
= linebuf
.length();
126 fieldbuf
.append(linebuf
, 0, len
);
129 if (instream
.readLine(linebuf
) == -1) {
130 monitor(Event
.HEADERS_PREMATURE_END
);
134 len
= linebuf
.length();
135 if (len
> 0 && linebuf
.byteAt(len
- 1) == '\n') {
138 if (len
> 0 && linebuf
.byteAt(len
- 1) == '\r') {
142 // empty line detected
148 int ch
= linebuf
.byteAt(0);
149 if (ch
!= MessageUtils
.SP
&& ch
!= MessageUtils
.HT
) {
150 // new header detected
157 protected boolean parseField() throws IOException
{
164 // Strip away line delimiter
165 int len
= fieldbuf
.length();
166 if (len
> 0 && fieldbuf
.charAt(len
- 1) == '\n') {
169 if (len
> 0 && fieldbuf
.charAt(len
- 1) == '\r') {
172 fieldbuf
.setLength(len
);
174 boolean valid
= true;
175 field
= fieldbuf
.toString();
176 int pos
= fieldbuf
.indexOf(':');
178 monitor(Event
.INALID_HEADER
);
181 fieldName
= fieldbuf
.substring(0, pos
);
182 for (int i
= 0; i
< fieldName
.length(); i
++) {
183 if (!fieldChars
.get(fieldName
.charAt(i
))) {
184 monitor(Event
.INALID_HEADER
);
189 fieldValue
= fieldbuf
.substring(pos
+ 1, fieldbuf
.length());
192 body
.addField(fieldName
, fieldValue
);
199 * <p>Gets a descriptor for the current entity.
200 * This method is valid if {@link #getState()} returns:</p>
202 * <li>{@link #T_BODY}</li>
203 * <li>{@link #T_START_MULTIPART}</li>
204 * <li>{@link #T_EPILOGUE}</li>
205 * <li>{@link #T_PREAMBLE}</li>
207 * @return <code>BodyDescriptor</code>, not nulls
209 public BodyDescriptor
getBodyDescriptor() {
210 switch (getState()) {
211 case EntityStates
.T_BODY
:
212 case EntityStates
.T_START_MULTIPART
:
213 case EntityStates
.T_PREAMBLE
:
214 case EntityStates
.T_EPILOGUE
:
215 case EntityStates
.T_END_OF_STREAM
:
218 throw new IllegalStateException("Invalid state :" + stateToString(state
));
223 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
224 * @return String with the fields raw contents.
225 * @throws IllegalStateException {@link #getState()} returns another
226 * value than {@link #T_FIELD}.
228 public String
getField() {
229 switch (getState()) {
230 case EntityStates
.T_FIELD
:
233 throw new IllegalStateException("Invalid state :" + stateToString(state
));
238 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
239 * @return String with the fields name.
240 * @throws IllegalStateException {@link #getState()} returns another
241 * value than {@link #T_FIELD}.
243 public String
getFieldName() {
244 switch (getState()) {
245 case EntityStates
.T_FIELD
:
248 throw new IllegalStateException("Invalid state :" + stateToString(state
));
253 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
254 * @return String with the fields value.
255 * @throws IllegalStateException {@link #getState()} returns another
256 * value than {@link #T_FIELD}.
258 public String
getFieldValue() {
259 switch (getState()) {
260 case EntityStates
.T_FIELD
:
263 throw new IllegalStateException("Invalid state :" + stateToString(state
));
268 * Monitors the given event.
269 * Subclasses may override to perform actions upon events.
270 * Base implementation logs at warn.
271 * @param event <code>Event</code>, not null
272 * @throws MimeException subclasses may elect to throw this exception upon
274 * @throws IOException subclasses may elect to throw this exception
276 protected void monitor(Event event
) throws MimeException
, IOException
{
278 throw new MimeParseEventException(event
);
285 * Creates an indicative message suitable for display
286 * based on the given event and the current state of the system.
287 * @param event <code>Event</code>, not null
288 * @return message suitable for use as a message in an exception
291 protected String
message(Event event
) {
292 String preamble
= "Line " + getLineNumber() + ": ";
293 final String message
;
295 message
= "Event is unexpectedly null.";
297 message
= event
.toString();
299 final String result
= preamble
+ message
;
304 * Logs (at warn) an indicative message based on the given event
305 * and the current state of the system.
306 * @param event <code>Event</code>, not null
308 protected void warn(Event event
) {
309 if (log
.isWarnEnabled()) {
310 log
.warn(message(event
));
315 * Logs (at debug) an indicative message based on the given event
316 * and the current state of the system.
317 * @param event <code>Event</code>, not null
319 protected void debug(Event event
) {
320 if (log
.isDebugEnabled()) {
321 log
.debug(message(event
));
325 public String
toString() {
326 return getClass().getName() + " [" + stateToString(state
)
327 + "][" + body
.getMimeType() + "][" + body
.getBoundary() + "]";
331 * Renders a state as a string suitable for logging.
333 * @return rendered as string, not null
335 public static final String
stateToString(int state
) {
338 case EntityStates
.T_END_OF_STREAM
:
339 result
= "End of stream";
341 case EntityStates
.T_START_MESSAGE
:
342 result
= "Start message";
344 case EntityStates
.T_END_MESSAGE
:
345 result
= "End message";
347 case EntityStates
.T_RAW_ENTITY
:
348 result
= "Raw entity";
350 case EntityStates
.T_START_HEADER
:
351 result
= "Start header";
353 case EntityStates
.T_FIELD
:
356 case EntityStates
.T_END_HEADER
:
357 result
= "End header";
359 case EntityStates
.T_START_MULTIPART
:
360 result
= "Start multipart";
362 case EntityStates
.T_END_MULTIPART
:
363 result
= "End multipart";
365 case EntityStates
.T_PREAMBLE
:
368 case EntityStates
.T_EPILOGUE
:
371 case EntityStates
.T_START_BODYPART
:
372 result
= "Start bodypart";
374 case EntityStates
.T_END_BODYPART
:
375 result
= "End bodypart";
377 case EntityStates
.T_BODY
:
384 result
= "In message";