001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.field;
021    
022    import java.io.StringReader;
023    import java.util.Collections;
024    import java.util.Date;
025    import java.util.HashMap;
026    import java.util.List;
027    import java.util.Locale;
028    import java.util.Map;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.apache.james.mime4j.field.contentdisposition.parser.ContentDispositionParser;
033    import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError;
034    import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
035    import org.apache.james.mime4j.util.ByteSequence;
036    
037    /**
038     * Represents a <code>Content-Disposition</code> field.
039     */
040    public class ContentDispositionField extends AbstractField {
041        private static Log log = LogFactory.getLog(ContentDispositionField.class);
042    
043        /** The <code>inline</code> disposition type. */
044        public static final String DISPOSITION_TYPE_INLINE = "inline";
045    
046        /** The <code>attachment</code> disposition type. */
047        public static final String DISPOSITION_TYPE_ATTACHMENT = "attachment";
048    
049        /** The name of the <code>filename</code> parameter. */
050        public static final String PARAM_FILENAME = "filename";
051    
052        /** The name of the <code>creation-date</code> parameter. */
053        public static final String PARAM_CREATION_DATE = "creation-date";
054    
055        /** The name of the <code>modification-date</code> parameter. */
056        public static final String PARAM_MODIFICATION_DATE = "modification-date";
057    
058        /** The name of the <code>read-date</code> parameter. */
059        public static final String PARAM_READ_DATE = "read-date";
060    
061        /** The name of the <code>size</code> parameter. */
062        public static final String PARAM_SIZE = "size";
063    
064        private boolean parsed = false;
065    
066        private String dispositionType = "";
067        private Map<String, String> parameters = new HashMap<String, String>();
068        private ParseException parseException;
069    
070        private boolean creationDateParsed;
071        private Date creationDate;
072    
073        private boolean modificationDateParsed;
074        private Date modificationDate;
075    
076        private boolean readDateParsed;
077        private Date readDate;
078    
079        ContentDispositionField(String name, String body, ByteSequence raw) {
080            super(name, body, raw);
081        }
082    
083        /**
084         * Gets the exception that was raised during parsing of the field value, if
085         * any; otherwise, null.
086         */
087        @Override
088        public ParseException getParseException() {
089            if (!parsed)
090                parse();
091    
092            return parseException;
093        }
094    
095        /**
096         * Gets the disposition type defined in this Content-Disposition field.
097         * 
098         * @return the disposition type or an empty string if not set.
099         */
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    }