Home | History | Annotate | Download | only in mime4j
      1 /****************************************************************
      2  * Licensed to the Apache Software Foundation (ASF) under one   *
      3  * or more contributor license agreements.  See the NOTICE file *
      4  * distributed with this work for additional information        *
      5  * regarding copyright ownership.  The ASF licenses this file   *
      6  * to you under the Apache License, Version 2.0 (the            *
      7  * "License"); you may not use this file except in compliance   *
      8  * with the License.  You may obtain a copy of the License at   *
      9  *                                                              *
     10  *   http://www.apache.org/licenses/LICENSE-2.0                 *
     11  *                                                              *
     12  * Unless required by applicable law or agreed to in writing,   *
     13  * software distributed under the License is distributed on an  *
     14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
     15  * KIND, either express or implied.  See the License for the    *
     16  * specific language governing permissions and limitations      *
     17  * under the License.                                           *
     18  ****************************************************************/
     19 
     20 package org.apache.james.mime4j;
     21 
     22 import java.util.HashMap;
     23 import java.util.Map;
     24 
     25 /**
     26  * Encapsulates the values of the MIME-specific header fields
     27  * (which starts with <code>Content-</code>).
     28  *
     29  *
     30  * @version $Id: BodyDescriptor.java,v 1.4 2005/02/11 10:08:37 ntherning Exp $
     31  */
     32 public class BodyDescriptor {
     33     private static Log log = LogFactory.getLog(BodyDescriptor.class);
     34 
     35     private String mimeType = "text/plain";
     36     private String boundary = null;
     37     private String charset = "us-ascii";
     38     private String transferEncoding = "7bit";
     39     private Map<String, String> parameters = new HashMap<String, String>();
     40     private boolean contentTypeSet = false;
     41     private boolean contentTransferEncSet = false;
     42 
     43     /**
     44      * Creates a new root <code>BodyDescriptor</code> instance.
     45      */
     46     public BodyDescriptor() {
     47         this(null);
     48     }
     49 
     50     /**
     51      * Creates a new <code>BodyDescriptor</code> instance.
     52      *
     53      * @param parent the descriptor of the parent or <code>null</code> if this
     54      *        is the root descriptor.
     55      */
     56     public BodyDescriptor(BodyDescriptor parent) {
     57         if (parent != null && parent.isMimeType("multipart/digest")) {
     58             mimeType = "message/rfc822";
     59         } else {
     60             mimeType = "text/plain";
     61         }
     62     }
     63 
     64     /**
     65      * Should be called for each <code>Content-</code> header field of
     66      * a MIME message or part.
     67      *
     68      * @param name the field name.
     69      * @param value the field value.
     70      */
     71     public void addField(String name, String value) {
     72 
     73         name = name.trim().toLowerCase();
     74 
     75         if (name.equals("content-transfer-encoding") && !contentTransferEncSet) {
     76             contentTransferEncSet = true;
     77 
     78             value = value.trim().toLowerCase();
     79             if (value.length() > 0) {
     80                 transferEncoding = value;
     81             }
     82 
     83         } else if (name.equals("content-type") && !contentTypeSet) {
     84             contentTypeSet = true;
     85 
     86             value = value.trim();
     87 
     88             /*
     89              * Unfold Content-Type value
     90              */
     91             StringBuffer sb = new StringBuffer();
     92             for (int i = 0; i < value.length(); i++) {
     93                 char c = value.charAt(i);
     94                 if (c == '\r' || c == '\n') {
     95                     continue;
     96                 }
     97                 sb.append(c);
     98             }
     99 
    100             Map<String, String> params = getHeaderParams(sb.toString());
    101 
    102             String main = params.get("");
    103             if (main != null) {
    104                 main = main.toLowerCase().trim();
    105                 int index = main.indexOf('/');
    106                 boolean valid = false;
    107                 if (index != -1) {
    108                     String type = main.substring(0, index).trim();
    109                     String subtype = main.substring(index + 1).trim();
    110                     if (type.length() > 0 && subtype.length() > 0) {
    111                         main = type + "/" + subtype;
    112                         valid = true;
    113                     }
    114                 }
    115 
    116                 if (!valid) {
    117                     main = null;
    118                 }
    119             }
    120             String b = params.get("boundary");
    121 
    122             if (main != null
    123                     && ((main.startsWith("multipart/") && b != null)
    124                             || !main.startsWith("multipart/"))) {
    125 
    126                 mimeType = main;
    127             }
    128 
    129             if (isMultipart()) {
    130                 boundary = b;
    131             }
    132 
    133             String c = params.get("charset");
    134             if (c != null) {
    135                 c = c.trim();
    136                 if (c.length() > 0) {
    137                     charset = c.toLowerCase();
    138                 }
    139             }
    140 
    141             /*
    142              * Add all other parameters to parameters.
    143              */
    144             parameters.putAll(params);
    145             parameters.remove("");
    146             parameters.remove("boundary");
    147             parameters.remove("charset");
    148         }
    149     }
    150 
    151     private Map<String, String> getHeaderParams(String headerValue) {
    152         Map<String, String> result = new HashMap<String, String>();
    153 
    154         // split main value and parameters
    155         String main;
    156         String rest;
    157         if (headerValue.indexOf(";") == -1) {
    158             main = headerValue;
    159             rest = null;
    160         } else {
    161             main = headerValue.substring(0, headerValue.indexOf(";"));
    162             rest = headerValue.substring(main.length() + 1);
    163         }
    164 
    165         result.put("", main);
    166         if (rest != null) {
    167             char[] chars = rest.toCharArray();
    168             StringBuffer paramName = new StringBuffer();
    169             StringBuffer paramValue = new StringBuffer();
    170 
    171             final byte READY_FOR_NAME = 0;
    172             final byte IN_NAME = 1;
    173             final byte READY_FOR_VALUE = 2;
    174             final byte IN_VALUE = 3;
    175             final byte IN_QUOTED_VALUE = 4;
    176             final byte VALUE_DONE = 5;
    177             final byte ERROR = 99;
    178 
    179             byte state = READY_FOR_NAME;
    180             boolean escaped = false;
    181             for (int i = 0; i < chars.length; i++) {
    182                 char c = chars[i];
    183 
    184                 switch (state) {
    185                     case ERROR:
    186                         if (c == ';')
    187                             state = READY_FOR_NAME;
    188                         break;
    189 
    190                     case READY_FOR_NAME:
    191                         if (c == '=') {
    192                             log.error("Expected header param name, got '='");
    193                             state = ERROR;
    194                             break;
    195                         }
    196 
    197                         paramName = new StringBuffer();
    198                         paramValue = new StringBuffer();
    199 
    200                         state = IN_NAME;
    201                         // $FALL-THROUGH$
    202 
    203                     case IN_NAME:
    204                         if (c == '=') {
    205                             if (paramName.length() == 0)
    206                                 state = ERROR;
    207                             else
    208                                 state = READY_FOR_VALUE;
    209                             break;
    210                         }
    211 
    212                         // not '='... just add to name
    213                         paramName.append(c);
    214                         break;
    215 
    216                     case READY_FOR_VALUE:
    217                         boolean fallThrough = false;
    218                         switch (c) {
    219                             case ' ':
    220                             case '\t':
    221                                 break;  // ignore spaces, especially before '"'
    222 
    223                             case '"':
    224                                 state = IN_QUOTED_VALUE;
    225                                 break;
    226 
    227                             default:
    228                                 state = IN_VALUE;
    229                                 fallThrough = true;
    230                                 break;
    231                         }
    232                         if (!fallThrough)
    233                             break;
    234 
    235                         // $FALL-THROUGH$
    236 
    237                     case IN_VALUE:
    238                         fallThrough = false;
    239                         switch (c) {
    240                             case ';':
    241                             case ' ':
    242                             case '\t':
    243                                 result.put(
    244                                    paramName.toString().trim().toLowerCase(),
    245                                    paramValue.toString().trim());
    246                                 state = VALUE_DONE;
    247                                 fallThrough = true;
    248                                 break;
    249                             default:
    250                                 paramValue.append(c);
    251                                 break;
    252                         }
    253                         if (!fallThrough)
    254                             break;
    255 
    256                         // $FALL-THROUGH$
    257 
    258                     case VALUE_DONE:
    259                         switch (c) {
    260                             case ';':
    261                                 state = READY_FOR_NAME;
    262                                 break;
    263 
    264                             case ' ':
    265                             case '\t':
    266                                 break;
    267 
    268                             default:
    269                                 state = ERROR;
    270                                 break;
    271                         }
    272                         break;
    273 
    274                     case IN_QUOTED_VALUE:
    275                         switch (c) {
    276                             case '"':
    277                                 if (!escaped) {
    278                                     // don't trim quoted strings; the spaces could be intentional.
    279                                     result.put(
    280                                             paramName.toString().trim().toLowerCase(),
    281                                             paramValue.toString());
    282                                     state = VALUE_DONE;
    283                                 } else {
    284                                     escaped = false;
    285                                     paramValue.append(c);
    286                                 }
    287                                 break;
    288 
    289                             case '\\':
    290                                 if (escaped) {
    291                                     paramValue.append('\\');
    292                                 }
    293                                 escaped = !escaped;
    294                                 break;
    295 
    296                             default:
    297                                 if (escaped) {
    298                                     paramValue.append('\\');
    299                                 }
    300                                 escaped = false;
    301                                 paramValue.append(c);
    302                                 break;
    303                         }
    304                         break;
    305 
    306                 }
    307             }
    308 
    309             // done looping.  check if anything is left over.
    310             if (state == IN_VALUE) {
    311                 result.put(
    312                         paramName.toString().trim().toLowerCase(),
    313                         paramValue.toString().trim());
    314             }
    315         }
    316 
    317         return result;
    318     }
    319 
    320 
    321     public boolean isMimeType(String mimeType) {
    322         return this.mimeType.equals(mimeType.toLowerCase());
    323     }
    324 
    325     /**
    326      * Return true if the BodyDescriptor belongs to a message
    327      */
    328     public boolean isMessage() {
    329         return mimeType.equals("message/rfc822");
    330     }
    331 
    332     /**
    333      * Return true if the BodyDescripotro belongs to a multipart
    334      */
    335     public boolean isMultipart() {
    336         return mimeType.startsWith("multipart/");
    337     }
    338 
    339     /**
    340      * Return the MimeType
    341      */
    342     public String getMimeType() {
    343         return mimeType;
    344     }
    345 
    346     /**
    347      * Return the boundary
    348      */
    349     public String getBoundary() {
    350         return boundary;
    351     }
    352 
    353     /**
    354      * Return the charset
    355      */
    356     public String getCharset() {
    357         return charset;
    358     }
    359 
    360     /**
    361      * Return all parameters for the BodyDescriptor
    362      */
    363     public Map<String, String> getParameters() {
    364         return parameters;
    365     }
    366 
    367     /**
    368      * Return the TransferEncoding
    369      */
    370     public String getTransferEncoding() {
    371         return transferEncoding;
    372     }
    373 
    374     /**
    375      * Return true if it's base64 encoded
    376      */
    377     public boolean isBase64Encoded() {
    378         return "base64".equals(transferEncoding);
    379     }
    380 
    381     /**
    382      * Return true if it's quoted-printable
    383      */
    384     public boolean isQuotedPrintableEncoded() {
    385         return "quoted-printable".equals(transferEncoding);
    386     }
    387 
    388     @Override
    389     public String toString() {
    390         return mimeType;
    391     }
    392 }
    393