/**************************************************************** | |
* Licensed to the Apache Software Foundation (ASF) under one * | |
* or more contributor license agreements. See the NOTICE file * | |
* distributed with this work for additional information * | |
* regarding copyright ownership. The ASF licenses this file * | |
* to you under the Apache License, Version 2.0 (the * | |
* "License"); you may not use this file except in compliance * | |
* with the License. You may obtain a copy of the License at * | |
* * | |
* http://www.apache.org/licenses/LICENSE-2.0 * | |
* * | |
* Unless required by applicable law or agreed to in writing, * | |
* software distributed under the License is distributed on an * | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * | |
* KIND, either express or implied. See the License for the * | |
* specific language governing permissions and limitations * | |
* under the License. * | |
****************************************************************/ | |
package org.apache.james.mime4j; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* Encapsulates the values of the MIME-specific header fields | |
* (which starts with <code>Content-</code>). | |
* | |
* | |
* @version $Id: BodyDescriptor.java,v 1.4 2005/02/11 10:08:37 ntherning Exp $ | |
*/ | |
public class BodyDescriptor { | |
private static Log log = LogFactory.getLog(BodyDescriptor.class); | |
private String mimeType = "text/plain"; | |
private String boundary = null; | |
private String charset = "us-ascii"; | |
private String transferEncoding = "7bit"; | |
private Map<String, String> parameters = new HashMap<String, String>(); | |
private boolean contentTypeSet = false; | |
private boolean contentTransferEncSet = false; | |
/** | |
* Creates a new root <code>BodyDescriptor</code> instance. | |
*/ | |
public BodyDescriptor() { | |
this(null); | |
} | |
/** | |
* Creates a new <code>BodyDescriptor</code> instance. | |
* | |
* @param parent the descriptor of the parent or <code>null</code> if this | |
* is the root descriptor. | |
*/ | |
public BodyDescriptor(BodyDescriptor parent) { | |
if (parent != null && parent.isMimeType("multipart/digest")) { | |
mimeType = "message/rfc822"; | |
} else { | |
mimeType = "text/plain"; | |
} | |
} | |
/** | |
* Should be called for each <code>Content-</code> header field of | |
* a MIME message or part. | |
* | |
* @param name the field name. | |
* @param value the field value. | |
*/ | |
public void addField(String name, String value) { | |
name = name.trim().toLowerCase(); | |
if (name.equals("content-transfer-encoding") && !contentTransferEncSet) { | |
contentTransferEncSet = true; | |
value = value.trim().toLowerCase(); | |
if (value.length() > 0) { | |
transferEncoding = value; | |
} | |
} else if (name.equals("content-type") && !contentTypeSet) { | |
contentTypeSet = true; | |
value = value.trim(); | |
/* | |
* Unfold Content-Type value | |
*/ | |
StringBuffer sb = new StringBuffer(); | |
for (int i = 0; i < value.length(); i++) { | |
char c = value.charAt(i); | |
if (c == '\r' || c == '\n') { | |
continue; | |
} | |
sb.append(c); | |
} | |
Map<String, String> params = getHeaderParams(sb.toString()); | |
String main = params.get(""); | |
if (main != null) { | |
main = main.toLowerCase().trim(); | |
int index = main.indexOf('/'); | |
boolean valid = false; | |
if (index != -1) { | |
String type = main.substring(0, index).trim(); | |
String subtype = main.substring(index + 1).trim(); | |
if (type.length() > 0 && subtype.length() > 0) { | |
main = type + "/" + subtype; | |
valid = true; | |
} | |
} | |
if (!valid) { | |
main = null; | |
} | |
} | |
String b = params.get("boundary"); | |
if (main != null | |
&& ((main.startsWith("multipart/") && b != null) | |
|| !main.startsWith("multipart/"))) { | |
mimeType = main; | |
} | |
if (isMultipart()) { | |
boundary = b; | |
} | |
String c = params.get("charset"); | |
if (c != null) { | |
c = c.trim(); | |
if (c.length() > 0) { | |
charset = c.toLowerCase(); | |
} | |
} | |
/* | |
* Add all other parameters to parameters. | |
*/ | |
parameters.putAll(params); | |
parameters.remove(""); | |
parameters.remove("boundary"); | |
parameters.remove("charset"); | |
} | |
} | |
private Map<String, String> getHeaderParams(String headerValue) { | |
Map<String, String> result = new HashMap<String, String>(); | |
// split main value and parameters | |
String main; | |
String rest; | |
if (headerValue.indexOf(";") == -1) { | |
main = headerValue; | |
rest = null; | |
} else { | |
main = headerValue.substring(0, headerValue.indexOf(";")); | |
rest = headerValue.substring(main.length() + 1); | |
} | |
result.put("", main); | |
if (rest != null) { | |
char[] chars = rest.toCharArray(); | |
StringBuffer paramName = new StringBuffer(); | |
StringBuffer paramValue = new StringBuffer(); | |
final byte READY_FOR_NAME = 0; | |
final byte IN_NAME = 1; | |
final byte READY_FOR_VALUE = 2; | |
final byte IN_VALUE = 3; | |
final byte IN_QUOTED_VALUE = 4; | |
final byte VALUE_DONE = 5; | |
final byte ERROR = 99; | |
byte state = READY_FOR_NAME; | |
boolean escaped = false; | |
for (int i = 0; i < chars.length; i++) { | |
char c = chars[i]; | |
switch (state) { | |
case ERROR: | |
if (c == ';') | |
state = READY_FOR_NAME; | |
break; | |
case READY_FOR_NAME: | |
if (c == '=') { | |
log.error("Expected header param name, got '='"); | |
state = ERROR; | |
break; | |
} | |
paramName = new StringBuffer(); | |
paramValue = new StringBuffer(); | |
state = IN_NAME; | |
// $FALL-THROUGH$ | |
case IN_NAME: | |
if (c == '=') { | |
if (paramName.length() == 0) | |
state = ERROR; | |
else | |
state = READY_FOR_VALUE; | |
break; | |
} | |
// not '='... just add to name | |
paramName.append(c); | |
break; | |
case READY_FOR_VALUE: | |
boolean fallThrough = false; | |
switch (c) { | |
case ' ': | |
case '\t': | |
break; // ignore spaces, especially before '"' | |
case '"': | |
state = IN_QUOTED_VALUE; | |
break; | |
default: | |
state = IN_VALUE; | |
fallThrough = true; | |
break; | |
} | |
if (!fallThrough) | |
break; | |
// $FALL-THROUGH$ | |
case IN_VALUE: | |
fallThrough = false; | |
switch (c) { | |
case ';': | |
case ' ': | |
case '\t': | |
result.put( | |
paramName.toString().trim().toLowerCase(), | |
paramValue.toString().trim()); | |
state = VALUE_DONE; | |
fallThrough = true; | |
break; | |
default: | |
paramValue.append(c); | |
break; | |
} | |
if (!fallThrough) | |
break; | |
// $FALL-THROUGH$ | |
case VALUE_DONE: | |
switch (c) { | |
case ';': | |
state = READY_FOR_NAME; | |
break; | |
case ' ': | |
case '\t': | |
break; | |
default: | |
state = ERROR; | |
break; | |
} | |
break; | |
case IN_QUOTED_VALUE: | |
switch (c) { | |
case '"': | |
if (!escaped) { | |
// don't trim quoted strings; the spaces could be intentional. | |
result.put( | |
paramName.toString().trim().toLowerCase(), | |
paramValue.toString()); | |
state = VALUE_DONE; | |
} else { | |
escaped = false; | |
paramValue.append(c); | |
} | |
break; | |
case '\\': | |
if (escaped) { | |
paramValue.append('\\'); | |
} | |
escaped = !escaped; | |
break; | |
default: | |
if (escaped) { | |
paramValue.append('\\'); | |
} | |
escaped = false; | |
paramValue.append(c); | |
break; | |
} | |
break; | |
} | |
} | |
// done looping. check if anything is left over. | |
if (state == IN_VALUE) { | |
result.put( | |
paramName.toString().trim().toLowerCase(), | |
paramValue.toString().trim()); | |
} | |
} | |
return result; | |
} | |
public boolean isMimeType(String mimeType) { | |
return this.mimeType.equals(mimeType.toLowerCase()); | |
} | |
/** | |
* Return true if the BodyDescriptor belongs to a message | |
*/ | |
public boolean isMessage() { | |
return mimeType.equals("message/rfc822"); | |
} | |
/** | |
* Return true if the BodyDescripotro belongs to a multipart | |
*/ | |
public boolean isMultipart() { | |
return mimeType.startsWith("multipart/"); | |
} | |
/** | |
* Return the MimeType | |
*/ | |
public String getMimeType() { | |
return mimeType; | |
} | |
/** | |
* Return the boundary | |
*/ | |
public String getBoundary() { | |
return boundary; | |
} | |
/** | |
* Return the charset | |
*/ | |
public String getCharset() { | |
return charset; | |
} | |
/** | |
* Return all parameters for the BodyDescriptor | |
*/ | |
public Map<String, String> getParameters() { | |
return parameters; | |
} | |
/** | |
* Return the TransferEncoding | |
*/ | |
public String getTransferEncoding() { | |
return transferEncoding; | |
} | |
/** | |
* Return true if it's base64 encoded | |
*/ | |
public boolean isBase64Encoded() { | |
return "base64".equals(transferEncoding); | |
} | |
/** | |
* Return true if it's quoted-printable | |
*/ | |
public boolean isQuotedPrintableEncoded() { | |
return "quoted-printable".equals(transferEncoding); | |
} | |
@Override | |
public String toString() { | |
return mimeType; | |
} | |
} |