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.field;
21
22 import java.io.StringReader;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser;
31 import org.apache.james.mime4j.field.contenttype.parser.ParseException;
32 import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
33 import org.apache.james.mime4j.util.ByteSequence;
34
35 /**
36 * Represents a <code>Content-Type</code> field.
37 */
38 public class ContentTypeField extends AbstractField {
39 private static Log log = LogFactory.getLog(ContentTypeField.class);
40
41 /** The prefix of all <code>multipart</code> MIME types. */
42 public static final String TYPE_MULTIPART_PREFIX = "multipart/";
43
44 /** The <code>multipart/digest</code> MIME type. */
45 public static final String TYPE_MULTIPART_DIGEST = "multipart/digest";
46
47 /** The <code>text/plain</code> MIME type. */
48 public static final String TYPE_TEXT_PLAIN = "text/plain";
49
50 /** The <code>message/rfc822</code> MIME type. */
51 public static final String TYPE_MESSAGE_RFC822 = "message/rfc822";
52
53 /** The name of the <code>boundary</code> parameter. */
54 public static final String PARAM_BOUNDARY = "boundary";
55
56 /** The name of the <code>charset</code> parameter. */
57 public static final String PARAM_CHARSET = "charset";
58
59 private boolean parsed = false;
60
61 private String mimeType = "";
62 private Map<String, String> parameters = new HashMap<String, String>();
63 private ParseException parseException;
64
65 ContentTypeField(String name, String body, ByteSequence raw) {
66 super(name, body, raw);
67 }
68
69 /**
70 * Gets the exception that was raised during parsing of the field value, if
71 * any; otherwise, null.
72 */
73 @Override
74 public ParseException getParseException() {
75 if (!parsed)
76 parse();
77
78 return parseException;
79 }
80
81 /**
82 * Gets the MIME type defined in this Content-Type field.
83 *
84 * @return the MIME type or an empty string if not set.
85 */
86 public String getMimeType() {
87 if (!parsed)
88 parse();
89
90 return mimeType;
91 }
92
93 /**
94 * Gets the value of a parameter. Parameter names are case-insensitive.
95 *
96 * @param name
97 * the name of the parameter to get.
98 * @return the parameter value or <code>null</code> if not set.
99 */
100 public String getParameter(String name) {
101 if (!parsed)
102 parse();
103
104 return parameters.get(name.toLowerCase());
105 }
106
107 /**
108 * Gets all parameters.
109 *
110 * @return the parameters.
111 */
112 public Map<String, String> getParameters() {
113 if (!parsed)
114 parse();
115
116 return Collections.unmodifiableMap(parameters);
117 }
118
119 /**
120 * Determines if the MIME type of this field matches the given one.
121 *
122 * @param mimeType
123 * the MIME type to match against.
124 * @return <code>true</code> if the MIME type of this field matches,
125 * <code>false</code> otherwise.
126 */
127 public boolean isMimeType(String mimeType) {
128 if (!parsed)
129 parse();
130
131 return this.mimeType.equalsIgnoreCase(mimeType);
132 }
133
134 /**
135 * Determines if the MIME type of this field is <code>multipart/*</code>.
136 *
137 * @return <code>true</code> if this field is has a
138 * <code>multipart/*</code> MIME type, <code>false</code>
139 * otherwise.
140 */
141 public boolean isMultipart() {
142 if (!parsed)
143 parse();
144
145 return mimeType.startsWith(TYPE_MULTIPART_PREFIX);
146 }
147
148 /**
149 * Gets the value of the <code>boundary</code> parameter if set.
150 *
151 * @return the <code>boundary</code> parameter value or <code>null</code>
152 * if not set.
153 */
154 public String getBoundary() {
155 return getParameter(PARAM_BOUNDARY);
156 }
157
158 /**
159 * Gets the value of the <code>charset</code> parameter if set.
160 *
161 * @return the <code>charset</code> parameter value or <code>null</code>
162 * if not set.
163 */
164 public String getCharset() {
165 return getParameter(PARAM_CHARSET);
166 }
167
168 /**
169 * Gets the MIME type defined in the child's Content-Type field or derives a
170 * MIME type from the parent if child is <code>null</code> or hasn't got a
171 * MIME type value set. If child's MIME type is multipart but no boundary
172 * has been set the MIME type of child will be derived from the parent.
173 *
174 * @param child
175 * the child.
176 * @param parent
177 * the parent.
178 * @return the MIME type.
179 */
180 public static String getMimeType(ContentTypeField child,
181 ContentTypeField parent) {
182 if (child == null || child.getMimeType().length() == 0
183 || child.isMultipart() && child.getBoundary() == null) {
184
185 if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) {
186 return TYPE_MESSAGE_RFC822;
187 } else {
188 return TYPE_TEXT_PLAIN;
189 }
190 }
191
192 return child.getMimeType();
193 }
194
195 /**
196 * Gets the value of the <code>charset</code> parameter if set for the
197 * given field. Returns the default <code>us-ascii</code> if not set or if
198 * <code>f</code> is <code>null</code>.
199 *
200 * @return the <code>charset</code> parameter value.
201 */
202 public static String getCharset(ContentTypeField f) {
203 if (f != null) {
204 String charset = f.getCharset();
205 if (charset != null && charset.length() > 0) {
206 return charset;
207 }
208 }
209 return "us-ascii";
210 }
211
212 private void parse() {
213 String body = getBody();
214
215 ContentTypeParser parser = new ContentTypeParser(new StringReader(body));
216 try {
217 parser.parseAll();
218 } catch (ParseException e) {
219 if (log.isDebugEnabled()) {
220 log.debug("Parsing value '" + body + "': " + e.getMessage());
221 }
222 parseException = e;
223 } catch (TokenMgrError e) {
224 if (log.isDebugEnabled()) {
225 log.debug("Parsing value '" + body + "': " + e.getMessage());
226 }
227 parseException = new ParseException(e.getMessage());
228 }
229
230 final String type = parser.getType();
231 final String subType = parser.getSubType();
232
233 if (type != null && subType != null) {
234 mimeType = (type + "/" + subType).toLowerCase();
235
236 List<String> paramNames = parser.getParamNames();
237 List<String> paramValues = parser.getParamValues();
238
239 if (paramNames != null && paramValues != null) {
240 final int len = Math.min(paramNames.size(), paramValues.size());
241 for (int i = 0; i < len; i++) {
242 String paramName = paramNames.get(i).toLowerCase();
243 String paramValue = paramValues.get(i);
244 parameters.put(paramName, paramValue);
245 }
246 }
247 }
248
249 parsed = true;
250 }
251
252 static final FieldParser PARSER = new FieldParser() {
253 public ParsedField parse(final String name, final String body,
254 final ByteSequence raw) {
255 return new ContentTypeField(name, body, raw);
256 }
257 };
258 }