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