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.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.commons.logging.Log;
029    import org.apache.commons.logging.LogFactory;
030    import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser;
031    import org.apache.james.mime4j.field.contenttype.parser.ParseException;
032    import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
033    import org.apache.james.mime4j.util.ByteSequence;
034    
035    /**
036     * Represents a <code>Content-Type</code> field.
037     */
038    public class ContentTypeField extends AbstractField {
039        private static Log log = LogFactory.getLog(ContentTypeField.class);
040    
041        /** The prefix of all <code>multipart</code> MIME types. */
042        public static final String TYPE_MULTIPART_PREFIX = "multipart/";
043    
044        /** The <code>multipart/digest</code> MIME type. */
045        public static final String TYPE_MULTIPART_DIGEST = "multipart/digest";
046    
047        /** The <code>text/plain</code> MIME type. */
048        public static final String TYPE_TEXT_PLAIN = "text/plain";
049    
050        /** The <code>message/rfc822</code> MIME type. */
051        public static final String TYPE_MESSAGE_RFC822 = "message/rfc822";
052    
053        /** The name of the <code>boundary</code> parameter. */
054        public static final String PARAM_BOUNDARY = "boundary";
055    
056        /** The name of the <code>charset</code> parameter. */
057        public static final String PARAM_CHARSET = "charset";
058    
059        private boolean parsed = false;
060    
061        private String mimeType = "";
062        private Map<String, String> parameters = new HashMap<String, String>();
063        private ParseException parseException;
064    
065        ContentTypeField(String name, String body, ByteSequence raw) {
066            super(name, body, raw);
067        }
068    
069        /**
070         * Gets the exception that was raised during parsing of the field value, if
071         * any; otherwise, null.
072         */
073        @Override
074        public ParseException getParseException() {
075            if (!parsed)
076                parse();
077    
078            return parseException;
079        }
080    
081        /**
082         * Gets the MIME type defined in this Content-Type field.
083         * 
084         * @return the MIME type or an empty string if not set.
085         */
086        public String getMimeType() {
087            if (!parsed)
088                parse();
089    
090            return mimeType;
091        }
092    
093        /**
094         * Gets the value of a parameter. Parameter names are case-insensitive.
095         * 
096         * @param name
097         *            the name of the parameter to get.
098         * @return the parameter value or <code>null</code> if not set.
099         */
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    }