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.descriptor; 021 022 import java.io.StringReader; 023 import java.util.Collections; 024 import java.util.List; 025 import java.util.Map; 026 027 import org.apache.james.mime4j.MimeException; 028 import org.apache.james.mime4j.field.datetime.DateTime; 029 import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; 030 import org.apache.james.mime4j.field.datetime.parser.ParseException; 031 import org.apache.james.mime4j.field.language.parser.ContentLanguageParser; 032 import org.apache.james.mime4j.field.mimeversion.parser.MimeVersionParser; 033 import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser; 034 import org.apache.james.mime4j.parser.Field; 035 import org.apache.james.mime4j.util.MimeUtil; 036 037 /** 038 * Parses and stores values for standard MIME header values. 039 * 040 */ 041 public class MaximalBodyDescriptor extends DefaultBodyDescriptor { 042 043 private static final int DEFAULT_MINOR_VERSION = 0; 044 private static final int DEFAULT_MAJOR_VERSION = 1; 045 private boolean isMimeVersionSet; 046 private int mimeMinorVersion; 047 private int mimeMajorVersion; 048 private MimeException mimeVersionException; 049 private String contentId; 050 private boolean isContentIdSet; 051 private String contentDescription; 052 private boolean isContentDescriptionSet; 053 private String contentDispositionType; 054 private Map<String, String> contentDispositionParameters; 055 private DateTime contentDispositionModificationDate; 056 private MimeException contentDispositionModificationDateParseException; 057 private DateTime contentDispositionCreationDate; 058 private MimeException contentDispositionCreationDateParseException; 059 private DateTime contentDispositionReadDate; 060 private MimeException contentDispositionReadDateParseException; 061 private long contentDispositionSize; 062 private MimeException contentDispositionSizeParseException; 063 private boolean isContentDispositionSet; 064 private List<String> contentLanguage; 065 private MimeException contentLanguageParseException; 066 private boolean isContentLanguageSet; 067 private MimeException contentLocationParseException; 068 private String contentLocation; 069 private boolean isContentLocationSet; 070 private String contentMD5Raw; 071 private boolean isContentMD5Set; 072 073 protected MaximalBodyDescriptor() { 074 this(null); 075 } 076 077 public MaximalBodyDescriptor(BodyDescriptor parent) { 078 super(parent); 079 isMimeVersionSet = false; 080 mimeMajorVersion = DEFAULT_MAJOR_VERSION; 081 mimeMinorVersion = DEFAULT_MINOR_VERSION; 082 this.contentId = null; 083 this.isContentIdSet = false; 084 this.contentDescription = null; 085 this.isContentDescriptionSet = false; 086 this.contentDispositionType = null; 087 this.contentDispositionParameters = Collections.emptyMap(); 088 this.contentDispositionModificationDate = null; 089 this.contentDispositionModificationDateParseException = null; 090 this.contentDispositionCreationDate = null; 091 this.contentDispositionCreationDateParseException = null; 092 this.contentDispositionReadDate = null; 093 this.contentDispositionReadDateParseException = null; 094 this.contentDispositionSize = -1; 095 this.contentDispositionSizeParseException = null; 096 this.isContentDispositionSet = false; 097 this.contentLanguage = null; 098 this.contentLanguageParseException = null; 099 this.isContentIdSet = false; 100 this.contentLocation = null; 101 this.contentLocationParseException = null; 102 this.isContentLocationSet = false; 103 this.contentMD5Raw = null; 104 this.isContentMD5Set = false; 105 } 106 107 @Override 108 public void addField(Field field) { 109 String name = field.getName(); 110 String value = field.getBody(); 111 name = name.trim().toLowerCase(); 112 if (MimeUtil.MIME_HEADER_MIME_VERSION.equals(name) && !isMimeVersionSet) { 113 parseMimeVersion(value); 114 } else if (MimeUtil.MIME_HEADER_CONTENT_ID.equals(name) && !isContentIdSet) { 115 parseContentId(value); 116 } else if (MimeUtil.MIME_HEADER_CONTENT_DESCRIPTION.equals(name) && !isContentDescriptionSet) { 117 parseContentDescription(value); 118 } else if (MimeUtil.MIME_HEADER_CONTENT_DISPOSITION.equals(name) && !isContentDispositionSet) { 119 parseContentDisposition(value); 120 } else if (MimeUtil.MIME_HEADER_LANGAUGE.equals(name) && !isContentLanguageSet) { 121 parseLanguage(value); 122 } else if (MimeUtil.MIME_HEADER_LOCATION.equals(name) && !isContentLocationSet) { 123 parseLocation(value); 124 } else if (MimeUtil.MIME_HEADER_MD5.equals(name) && !isContentMD5Set) { 125 parseMD5(value); 126 } else { 127 super.addField(field); 128 } 129 } 130 131 private void parseMD5(String value) { 132 isContentMD5Set = true; 133 if (value != null) { 134 contentMD5Raw = value.trim(); 135 } 136 } 137 138 private void parseLocation(final String value) { 139 isContentLocationSet = true; 140 if (value != null) { 141 final StringReader stringReader = new StringReader(value); 142 final StructuredFieldParser parser = new StructuredFieldParser(stringReader); 143 parser.setFoldingPreserved(false); 144 try { 145 contentLocation = parser.parse(); 146 } catch (MimeException e) { 147 contentLocationParseException = e; 148 } 149 } 150 } 151 152 private void parseLanguage(final String value) { 153 isContentLanguageSet = true; 154 if (value != null) { 155 try { 156 final ContentLanguageParser parser = new ContentLanguageParser(new StringReader(value)); 157 contentLanguage = parser.parse(); 158 } catch (MimeException e) { 159 contentLanguageParseException = e; 160 } 161 } 162 } 163 164 private void parseContentDisposition(final String value) { 165 isContentDispositionSet = true; 166 contentDispositionParameters = MimeUtil.getHeaderParams(value); 167 contentDispositionType = contentDispositionParameters.get(""); 168 169 final String contentDispositionModificationDate 170 = contentDispositionParameters.get(MimeUtil.PARAM_MODIFICATION_DATE); 171 if (contentDispositionModificationDate != null) { 172 try { 173 this.contentDispositionModificationDate = parseDate(contentDispositionModificationDate); 174 } catch (ParseException e) { 175 this.contentDispositionModificationDateParseException = e; 176 } 177 } 178 179 final String contentDispositionCreationDate 180 = contentDispositionParameters.get(MimeUtil.PARAM_CREATION_DATE); 181 if (contentDispositionCreationDate != null) { 182 try { 183 this.contentDispositionCreationDate = parseDate(contentDispositionCreationDate); 184 } catch (ParseException e) { 185 this.contentDispositionCreationDateParseException = e; 186 } 187 } 188 189 final String contentDispositionReadDate 190 = contentDispositionParameters.get(MimeUtil.PARAM_READ_DATE); 191 if (contentDispositionReadDate != null) { 192 try { 193 this.contentDispositionReadDate = parseDate(contentDispositionReadDate); 194 } catch (ParseException e) { 195 this.contentDispositionReadDateParseException = e; 196 } 197 } 198 199 final String size = contentDispositionParameters.get(MimeUtil.PARAM_SIZE); 200 if (size != null) { 201 try { 202 contentDispositionSize = Long.parseLong(size); 203 } catch (NumberFormatException e) { 204 this.contentDispositionSizeParseException = (MimeException) new MimeException(e.getMessage(), e).fillInStackTrace(); 205 } 206 } 207 contentDispositionParameters.remove(""); 208 } 209 210 private DateTime parseDate(final String date) throws ParseException { 211 final StringReader stringReader = new StringReader(date); 212 final DateTimeParser parser = new DateTimeParser(stringReader); 213 DateTime result = parser.date_time(); 214 return result; 215 } 216 217 private void parseContentDescription(String value) { 218 if (value == null) { 219 contentDescription = ""; 220 } else { 221 contentDescription = value.trim(); 222 } 223 isContentDescriptionSet = true; 224 } 225 226 private void parseContentId(final String value) { 227 if (value == null) { 228 contentId = ""; 229 } else { 230 contentId = value.trim(); 231 } 232 isContentIdSet = true; 233 } 234 235 private void parseMimeVersion(String value) { 236 final StringReader reader = new StringReader(value); 237 final MimeVersionParser parser = new MimeVersionParser(reader); 238 try { 239 parser.parse(); 240 final int major = parser.getMajorVersion(); 241 if (major != MimeVersionParser.INITIAL_VERSION_VALUE) { 242 mimeMajorVersion = major; 243 } 244 final int minor = parser.getMinorVersion(); 245 if (minor != MimeVersionParser.INITIAL_VERSION_VALUE) { 246 mimeMinorVersion = minor; 247 } 248 } catch (MimeException e) { 249 this.mimeVersionException = e; 250 } 251 isMimeVersionSet = true; 252 } 253 254 /** 255 * Gets the MIME major version 256 * as specified by the <code>MIME-Version</code> 257 * header. 258 * Defaults to one. 259 * @return positive integer 260 */ 261 public int getMimeMajorVersion() { 262 return mimeMajorVersion; 263 } 264 265 /** 266 * Gets the MIME minor version 267 * as specified by the <code>MIME-Version</code> 268 * header. 269 * Defaults to zero. 270 * @return positive integer 271 */ 272 public int getMimeMinorVersion() { 273 return mimeMinorVersion; 274 } 275 276 277 /** 278 * When the MIME version header exists but cannot be parsed 279 * this field will be contain the exception. 280 * @return <code>MimeException</code> if the mime header cannot 281 * be parsed, null otherwise 282 */ 283 public MimeException getMimeVersionParseException() { 284 return mimeVersionException; 285 } 286 287 /** 288 * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> 289 * <code>Content-Description</code> header. 290 * @return value of the <code>Content-Description</code> when present, 291 * null otherwise 292 */ 293 public String getContentDescription() { 294 return contentDescription; 295 } 296 297 /** 298 * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> 299 * <code>Content-ID</code> header. 300 * @return value of the <code>Content-ID</code> when present, 301 * null otherwise 302 */ 303 public String getContentId() { 304 return contentId; 305 } 306 307 /** 308 * Gets the disposition type of the <code>content-disposition</code> field. 309 * The value is case insensitive and will be converted to lower case. 310 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 311 * @return content disposition type, 312 * or null when this has not been set 313 */ 314 public String getContentDispositionType() { 315 return contentDispositionType; 316 } 317 318 /** 319 * Gets the parameters of the <code>content-disposition</code> field. 320 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 321 * @return parameter value strings indexed by parameter name strings, 322 * not null 323 */ 324 public Map<String, String> getContentDispositionParameters() { 325 return contentDispositionParameters; 326 } 327 328 /** 329 * Gets the <code>filename</code> parameter value of the <code>content-disposition</code> field. 330 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 331 * @return filename parameter value, 332 * or null when it is not present 333 */ 334 public String getContentDispositionFilename() { 335 return contentDispositionParameters.get(MimeUtil.PARAM_FILENAME); 336 } 337 338 /** 339 * Gets the <code>modification-date</code> parameter value of the <code>content-disposition</code> field. 340 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 341 * @return modification-date parameter value, 342 * or null when this is not present 343 */ 344 public DateTime getContentDispositionModificationDate() { 345 return contentDispositionModificationDate; 346 } 347 348 /** 349 * Gets any exception thrown during the parsing of {@link #getContentDispositionModificationDate()} 350 * @return <code>ParseException</code> when the modification-date parse fails, 351 * null otherwise 352 */ 353 public MimeException getContentDispositionModificationDateParseException() { 354 return contentDispositionModificationDateParseException; 355 } 356 357 /** 358 * Gets the <code>creation-date</code> parameter value of the <code>content-disposition</code> field. 359 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 360 * @return creation-date parameter value, 361 * or null when this is not present 362 */ 363 public DateTime getContentDispositionCreationDate() { 364 return contentDispositionCreationDate; 365 } 366 367 /** 368 * Gets any exception thrown during the parsing of {@link #getContentDispositionCreationDate()} 369 * @return <code>ParseException</code> when the creation-date parse fails, 370 * null otherwise 371 */ 372 public MimeException getContentDispositionCreationDateParseException() { 373 return contentDispositionCreationDateParseException; 374 } 375 376 /** 377 * Gets the <code>read-date</code> parameter value of the <code>content-disposition</code> field. 378 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 379 * @return read-date parameter value, 380 * or null when this is not present 381 */ 382 public DateTime getContentDispositionReadDate() { 383 return contentDispositionReadDate; 384 } 385 386 /** 387 * Gets any exception thrown during the parsing of {@link #getContentDispositionReadDate()} 388 * @return <code>ParseException</code> when the read-date parse fails, 389 * null otherwise 390 */ 391 public MimeException getContentDispositionReadDateParseException() { 392 return contentDispositionReadDateParseException; 393 } 394 395 /** 396 * Gets the <code>size</code> parameter value of the <code>content-disposition</code> field. 397 * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. 398 * @return size parameter value, 399 * or -1 if this size has not been set 400 */ 401 public long getContentDispositionSize() { 402 return contentDispositionSize; 403 } 404 405 /** 406 * Gets any exception thrown during the parsing of {@link #getContentDispositionSize()} 407 * @return <code>ParseException</code> when the read-date parse fails, 408 * null otherwise 409 */ 410 public MimeException getContentDispositionSizeParseException() { 411 return contentDispositionSizeParseException; 412 } 413 414 /** 415 * Get the <code>content-language</code> header values. 416 * Each applicable language tag will be returned in order. 417 * See <a href='http://tools.ietf.org/html/rfc4646'>RFC4646</a> 418 * <cite>http://tools.ietf.org/html/rfc4646</cite>. 419 * @return list of language tag Strings, 420 * or null if no header exists 421 */ 422 public List<String> getContentLanguage() { 423 return contentLanguage; 424 } 425 426 /** 427 * Gets any exception thrown during the parsing of {@link #getContentLanguage()} 428 * @return <code>ParseException</code> when the content-language parse fails, 429 * null otherwise 430 */ 431 public MimeException getContentLanguageParseException() { 432 return contentLanguageParseException; 433 } 434 435 436 /** 437 * Get the <code>content-location</code> header value. 438 * See <a href='http://tools.ietf.org/html/rfc2557'>RFC2557</a> 439 * @return the URL content-location 440 * or null if no header exists 441 */ 442 public String getContentLocation() { 443 return contentLocation; 444 } 445 446 /** 447 * Gets any exception thrown during the parsing of {@link #getContentLocation()} 448 * @return <code>ParseException</code> when the content-language parse fails, 449 * null otherwise 450 */ 451 public MimeException getContentLocationParseException() { 452 return contentLocationParseException; 453 } 454 455 /** 456 * Gets the raw, Base64 encoded value of the 457 * <code>Content-MD5</code> field. 458 * See <a href='http://tools.ietf.org/html/rfc1864'>RFC1864</a>. 459 * @return raw encoded content-md5 460 * or null if no header exists 461 */ 462 public String getContentMD5Raw() { 463 return contentMD5Raw; 464 } 465 466 467 }