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.Date;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.james.mime4j.field.contentdisposition.parser.ContentDispositionParser;
33  import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError;
34  import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
35  import org.apache.james.mime4j.util.ByteSequence;
36  
37  /**
38   * Represents a <code>Content-Disposition</code> field.
39   */
40  public class ContentDispositionField extends AbstractField {
41      private static Log log = LogFactory.getLog(ContentDispositionField.class);
42  
43      /** The <code>inline</code> disposition type. */
44      public static final String DISPOSITION_TYPE_INLINE = "inline";
45  
46      /** The <code>attachment</code> disposition type. */
47      public static final String DISPOSITION_TYPE_ATTACHMENT = "attachment";
48  
49      /** The name of the <code>filename</code> parameter. */
50      public static final String PARAM_FILENAME = "filename";
51  
52      /** The name of the <code>creation-date</code> parameter. */
53      public static final String PARAM_CREATION_DATE = "creation-date";
54  
55      /** The name of the <code>modification-date</code> parameter. */
56      public static final String PARAM_MODIFICATION_DATE = "modification-date";
57  
58      /** The name of the <code>read-date</code> parameter. */
59      public static final String PARAM_READ_DATE = "read-date";
60  
61      /** The name of the <code>size</code> parameter. */
62      public static final String PARAM_SIZE = "size";
63  
64      private boolean parsed = false;
65  
66      private String dispositionType = "";
67      private Map<String, String> parameters = new HashMap<String, String>();
68      private ParseException parseException;
69  
70      private boolean creationDateParsed;
71      private Date creationDate;
72  
73      private boolean modificationDateParsed;
74      private Date modificationDate;
75  
76      private boolean readDateParsed;
77      private Date readDate;
78  
79      ContentDispositionField(String name, String body, ByteSequence raw) {
80          super(name, body, raw);
81      }
82  
83      /**
84       * Gets the exception that was raised during parsing of the field value, if
85       * any; otherwise, null.
86       */
87      @Override
88      public ParseException getParseException() {
89          if (!parsed)
90              parse();
91  
92          return parseException;
93      }
94  
95      /**
96       * Gets the disposition type defined in this Content-Disposition field.
97       * 
98       * @return the disposition type or an empty string if not set.
99       */
100     public String getDispositionType() {
101         if (!parsed)
102             parse();
103 
104         return dispositionType;
105     }
106 
107     /**
108      * Gets the value of a parameter. Parameter names are case-insensitive.
109      * 
110      * @param name
111      *            the name of the parameter to get.
112      * @return the parameter value or <code>null</code> if not set.
113      */
114     public String getParameter(String name) {
115         if (!parsed)
116             parse();
117 
118         return parameters.get(name.toLowerCase());
119     }
120 
121     /**
122      * Gets all parameters.
123      * 
124      * @return the parameters.
125      */
126     public Map<String, String> getParameters() {
127         if (!parsed)
128             parse();
129 
130         return Collections.unmodifiableMap(parameters);
131     }
132 
133     /**
134      * Determines if the disposition type of this field matches the given one.
135      * 
136      * @param dispositionType
137      *            the disposition type to match against.
138      * @return <code>true</code> if the disposition type of this field
139      *         matches, <code>false</code> otherwise.
140      */
141     public boolean isDispositionType(String dispositionType) {
142         if (!parsed)
143             parse();
144 
145         return this.dispositionType.equalsIgnoreCase(dispositionType);
146     }
147 
148     /**
149      * Return <code>true</code> if the disposition type of this field is
150      * <i>inline</i>, <code>false</code> otherwise.
151      * 
152      * @return <code>true</code> if the disposition type of this field is
153      *         <i>inline</i>, <code>false</code> otherwise.
154      */
155     public boolean isInline() {
156         if (!parsed)
157             parse();
158 
159         return dispositionType.equals(DISPOSITION_TYPE_INLINE);
160     }
161 
162     /**
163      * Return <code>true</code> if the disposition type of this field is
164      * <i>attachment</i>, <code>false</code> otherwise.
165      * 
166      * @return <code>true</code> if the disposition type of this field is
167      *         <i>attachment</i>, <code>false</code> otherwise.
168      */
169     public boolean isAttachment() {
170         if (!parsed)
171             parse();
172 
173         return dispositionType.equals(DISPOSITION_TYPE_ATTACHMENT);
174     }
175 
176     /**
177      * Gets the value of the <code>filename</code> parameter if set.
178      * 
179      * @return the <code>filename</code> parameter value or <code>null</code>
180      *         if not set.
181      */
182     public String getFilename() {
183         return getParameter(PARAM_FILENAME);
184     }
185 
186     /**
187      * Gets the value of the <code>creation-date</code> parameter if set and
188      * valid.
189      * 
190      * @return the <code>creation-date</code> parameter value or
191      *         <code>null</code> if not set or invalid.
192      */
193     public Date getCreationDate() {
194         if (!creationDateParsed) {
195             creationDate = parseDate(PARAM_CREATION_DATE);
196             creationDateParsed = true;
197         }
198 
199         return creationDate;
200     }
201 
202     /**
203      * Gets the value of the <code>modification-date</code> parameter if set
204      * and valid.
205      * 
206      * @return the <code>modification-date</code> parameter value or
207      *         <code>null</code> if not set or invalid.
208      */
209     public Date getModificationDate() {
210         if (!modificationDateParsed) {
211             modificationDate = parseDate(PARAM_MODIFICATION_DATE);
212             modificationDateParsed = true;
213         }
214 
215         return modificationDate;
216     }
217 
218     /**
219      * Gets the value of the <code>read-date</code> parameter if set and
220      * valid.
221      * 
222      * @return the <code>read-date</code> parameter value or <code>null</code>
223      *         if not set or invalid.
224      */
225     public Date getReadDate() {
226         if (!readDateParsed) {
227             readDate = parseDate(PARAM_READ_DATE);
228             readDateParsed = true;
229         }
230 
231         return readDate;
232     }
233 
234     /**
235      * Gets the value of the <code>size</code> parameter if set and valid.
236      * 
237      * @return the <code>size</code> parameter value or <code>-1</code> if
238      *         not set or invalid.
239      */
240     public long getSize() {
241         String value = getParameter(PARAM_SIZE);
242         if (value == null)
243             return -1;
244 
245         try {
246             long size = Long.parseLong(value);
247             return size < 0 ? -1 : size;
248         } catch (NumberFormatException e) {
249             return -1;
250         }
251     }
252 
253     private Date parseDate(String paramName) {
254         String value = getParameter(paramName);
255         if (value == null) {
256             if (log.isDebugEnabled()) {
257                 log.debug("Parsing " + paramName + " null");
258             }
259             return null;
260         }
261 
262         try {
263             return new DateTimeParser(new StringReader(value)).parseAll()
264                     .getDate();
265         } catch (ParseException e) {
266             if (log.isDebugEnabled()) {
267                 log.debug("Parsing " + paramName + " '" + value + "': "
268                         + e.getMessage());
269             }
270             return null;
271         } catch (org.apache.james.mime4j.field.datetime.parser.TokenMgrError e) {
272             if (log.isDebugEnabled()) {
273                 log.debug("Parsing " + paramName + " '" + value + "': "
274                         + e.getMessage());
275             }
276             return null;
277         }
278     }
279 
280     private void parse() {
281         String body = getBody();
282 
283         ContentDispositionParser parser = new ContentDispositionParser(
284                 new StringReader(body));
285         try {
286             parser.parseAll();
287         } catch (ParseException e) {
288             if (log.isDebugEnabled()) {
289                 log.debug("Parsing value '" + body + "': " + e.getMessage());
290             }
291             parseException = e;
292         } catch (TokenMgrError e) {
293             if (log.isDebugEnabled()) {
294                 log.debug("Parsing value '" + body + "': " + e.getMessage());
295             }
296             parseException = new ParseException(e.getMessage());
297         }
298 
299         final String dispositionType = parser.getDispositionType();
300 
301         if (dispositionType != null) {
302             this.dispositionType = dispositionType.toLowerCase(Locale.US);
303 
304             List<String> paramNames = parser.getParamNames();
305             List<String> paramValues = parser.getParamValues();
306 
307             if (paramNames != null && paramValues != null) {
308                 final int len = Math.min(paramNames.size(), paramValues.size());
309                 for (int i = 0; i < len; i++) {
310                     String paramName = paramNames.get(i).toLowerCase(Locale.US);
311                     String paramValue = paramValues.get(i);
312                     parameters.put(paramName, paramValue);
313                 }
314             }
315         }
316 
317         parsed = true;
318     }
319 
320     static final FieldParser PARSER = new FieldParser() {
321         public ParsedField parse(final String name, final String body,
322                 final ByteSequence raw) {
323             return new ContentDispositionField(name, body, raw);
324         }
325     };
326 }