blob: 867c43d86d28154f0aea5f4e5ddaea4d35b4be1c [file] [log] [blame]
/****************************************************************
* 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;
}
}