| /**************************************************************** |
| * 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; |
| } |
| } |