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 }