View Javadoc

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 }