Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2013 Samsung System LSI
      3 * Licensed under the Apache License, Version 2.0 (the "License");
      4 * you may not use this file except in compliance with the License.
      5 * You may obtain a copy of the License at
      6 *
      7 *      http://www.apache.org/licenses/LICENSE-2.0
      8 *
      9 * Unless required by applicable law or agreed to in writing, software
     10 * distributed under the License is distributed on an "AS IS" BASIS,
     11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 * See the License for the specific language governing permissions and
     13 * limitations under the License.
     14 */
     15 package com.android.bluetooth.map;
     16 
     17 import java.io.UnsupportedEncodingException;
     18 import java.nio.charset.Charset;
     19 import java.text.SimpleDateFormat;
     20 import java.util.ArrayList;
     21 import java.util.Arrays;
     22 import java.util.Date;
     23 import java.util.Locale;
     24 import java.util.UUID;
     25 
     26 import android.text.util.Rfc822Token;
     27 import android.text.util.Rfc822Tokenizer;
     28 import android.util.Base64;
     29 import android.util.Log;
     30 
     31 public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
     32 
     33     public static class MimePart {
     34         public long _id = INVALID_VALUE;   /* The _id from the content provider, can be used to sort the parts if needed */
     35         public String contentType = null;  /* The mime type, e.g. text/plain */
     36         public String contentId = null;
     37         public String contentLocation = null;
     38         public String contentDisposition = null;
     39         public String partName = null;     /* e.g. text_1.txt*/
     40         public String charsetName = null;  /* This seems to be a number e.g. 106 for UTF-8 CharacterSets
     41                                                 holds a method for the mapping. */
     42         public String fileName = null;     /* Do not seem to be used */
     43         public byte[] data = null;        /* The raw un-encoded data e.g. the raw jpeg data or the text.getBytes("utf-8") */
     44 
     45 
     46 
     47         public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException {
     48             sb.append("--").append(boundaryTag).append("\r\n");
     49             if(contentType != null)
     50                 sb.append("Content-Type: ").append(contentType);
     51             if(charsetName != null)
     52                 sb.append("; ").append("charset=\"").append(charsetName).append("\"");
     53             sb.append("\r\n");
     54             if(contentLocation != null)
     55                 sb.append("Content-Location: ").append(contentLocation).append("\r\n");
     56             if(contentId != null)
     57                 sb.append("Content-ID: ").append(contentId).append("\r\n");
     58             if(contentDisposition != null)
     59                 sb.append("Content-Disposition: ").append(contentDisposition).append("\r\n");
     60             if(data != null) {
     61                 /* TODO: If errata 4176 is adopted in the current form (it is not in either 1.1 or 1.2),
     62                 the below is not allowed, Base64 should be used for text. */
     63 
     64                 if(contentType != null &&
     65                         (contentType.toUpperCase().contains("TEXT") ||
     66                          contentType.toUpperCase().contains("SMIL") )) {
     67                     sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line
     68                     sb.append(new String(data,"UTF-8")).append("\r\n");
     69                 }
     70                 else {
     71                     sb.append("Content-Transfer-Encoding: Base64\r\n\r\n"); // Add the header split empty line
     72                     sb.append(Base64.encodeToString(data, Base64.DEFAULT)).append("\r\n");
     73                 }
     74             }
     75             if(last) {
     76                 sb.append("--").append(boundaryTag).append("--").append("\r\n");
     77             }
     78         }
     79 
     80         public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
     81             if(contentType != null && contentType.toUpperCase().contains("TEXT")) {
     82                 sb.append(new String(data,"UTF-8")).append("\r\n");
     83             } else if(contentType != null && contentType.toUpperCase().contains("/SMIL")) {
     84                 /* Skip the smil.xml, as no-one knows what it is. */
     85             } else {
     86                 /* Not a text part, just print the filename or part name if they exist. */
     87                 if(partName != null)
     88                     sb.append("<").append(partName).append(">\r\n");
     89                 else
     90                     sb.append("<").append("attachment").append(">\r\n");
     91             }
     92         }
     93     }
     94 
     95     private long date = INVALID_VALUE;
     96     private String subject = null;
     97     private ArrayList<Rfc822Token> from = null;   // Shall not be empty
     98     private ArrayList<Rfc822Token> sender = null;   // Shall not be empty
     99     private ArrayList<Rfc822Token> to = null;     // Shall not be empty
    100     private ArrayList<Rfc822Token> cc = null;     // Can be empty
    101     private ArrayList<Rfc822Token> bcc = null;    // Can be empty
    102     private ArrayList<Rfc822Token> replyTo = null;// Can be empty
    103     private String messageId = null;
    104     private ArrayList<MimePart> parts = null;
    105     private String contentType = null;
    106     private String boundary = null;
    107     private boolean textOnly = false;
    108     private boolean includeAttachments;
    109     private boolean hasHeaders = false;
    110     private String encoding = null;
    111 
    112     private String getBoundary() {
    113         if(boundary == null)
    114             boundary = "----" + UUID.randomUUID();
    115         return boundary;
    116     }
    117 
    118     /**
    119      * @return the parts
    120      */
    121     public ArrayList<MimePart> getMimeParts() {
    122         return parts;
    123     }
    124 
    125     public MimePart addMimePart() {
    126         if(parts == null)
    127             parts = new ArrayList<BluetoothMapbMessageMmsEmail.MimePart>();
    128         MimePart newPart = new MimePart();
    129         parts.add(newPart);
    130         return newPart;
    131     }
    132     public String getDateString() {
    133         SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
    134         Date dateObj = new Date(date);
    135         return format.format(dateObj); // Format according to RFC 2822 page 14
    136     }
    137     public long getDate() {
    138         return date;
    139     }
    140     public void setDate(long date) {
    141         this.date = date;
    142     }
    143     public String getSubject() {
    144         return subject;
    145     }
    146     public void setSubject(String subject) {
    147         this.subject = subject;
    148     }
    149     public ArrayList<Rfc822Token> getFrom() {
    150         return from;
    151     }
    152     public void setFrom(ArrayList<Rfc822Token> from) {
    153         this.from = from;
    154     }
    155     public void addFrom(String name, String address) {
    156         if(this.from == null)
    157             this.from = new ArrayList<Rfc822Token>(1);
    158         this.from.add(new Rfc822Token(name, address, null));
    159     }
    160     public ArrayList<Rfc822Token> getSender() {
    161         return sender;
    162     }
    163     public void setSender(ArrayList<Rfc822Token> sender) {
    164         this.sender = sender;
    165     }
    166     public void addSender(String name, String address) {
    167         if(this.sender == null)
    168             this.sender = new ArrayList<Rfc822Token>(1);
    169         this.sender.add(new Rfc822Token(name,address,null));
    170     }
    171     public ArrayList<Rfc822Token> getTo() {
    172         return to;
    173     }
    174     public void setTo(ArrayList<Rfc822Token> to) {
    175         this.to = to;
    176     }
    177     public void addTo(String name, String address) {
    178         if(this.to == null)
    179             this.to = new ArrayList<Rfc822Token>(1);
    180         this.to.add(new Rfc822Token(name, address, null));
    181     }
    182     public ArrayList<Rfc822Token> getCc() {
    183         return cc;
    184     }
    185     public void setCc(ArrayList<Rfc822Token> cc) {
    186         this.cc = cc;
    187     }
    188     public void addCc(String name, String address) {
    189         if(this.cc == null)
    190             this.cc = new ArrayList<Rfc822Token>(1);
    191         this.cc.add(new Rfc822Token(name, address, null));
    192     }
    193     public ArrayList<Rfc822Token> getBcc() {
    194         return bcc;
    195     }
    196     public void setBcc(ArrayList<Rfc822Token> bcc) {
    197         this.bcc = bcc;
    198     }
    199     public void addBcc(String name, String address) {
    200         if(this.bcc == null)
    201             this.bcc = new ArrayList<Rfc822Token>(1);
    202         this.bcc.add(new Rfc822Token(name, address, null));
    203     }
    204     public ArrayList<Rfc822Token> getReplyTo() {
    205         return replyTo;
    206     }
    207     public void setReplyTo(ArrayList<Rfc822Token> replyTo) {
    208         this.replyTo = replyTo;
    209     }
    210     public void addReplyTo(String name, String address) {
    211         if(this.replyTo == null)
    212             this.replyTo = new ArrayList<Rfc822Token>(1);
    213         this.replyTo.add(new Rfc822Token(name, address, null));
    214     }
    215     public void setMessageId(String messageId) {
    216         this.messageId = messageId;
    217     }
    218     public String getMessageId() {
    219         return messageId;
    220     }
    221     public void setContentType(String contentType) {
    222         this.contentType = contentType;
    223     }
    224     public String getContentType() {
    225         return contentType;
    226     }
    227     public void setTextOnly(boolean textOnly) {
    228         this.textOnly = textOnly;
    229     }
    230     public boolean getTextOnly() {
    231         return textOnly;
    232     }
    233     public void setIncludeAttachments(boolean includeAttachments) {
    234         this.includeAttachments = includeAttachments;
    235     }
    236     public boolean getIncludeAttachments() {
    237         return includeAttachments;
    238     }
    239     public void updateCharset() {
    240         charset = null;
    241         for(MimePart part : parts) {
    242             if(part.contentType != null &&
    243                part.contentType.toUpperCase().contains("TEXT")) {
    244                 charset = "UTF-8";
    245                 break;
    246             }
    247         }
    248     }
    249     public int getSize() {
    250         int message_size = 0;
    251         for(MimePart part : parts) {
    252             message_size += part.data.length;
    253         }
    254         return message_size;
    255     }
    256 
    257     /**
    258      * Encode an address header, and perform folding if needed.
    259      * @param sb The stringBuilder to write to
    260      * @param headerName The RFC 2822 header name
    261      * @param addresses the reformatted address substrings to encode.
    262      */
    263     public void encodeHeaderAddresses(StringBuilder sb, String headerName,
    264             ArrayList<Rfc822Token> addresses) {
    265         /* TODO: Do we need to encode the addresses if they contain illegal characters?
    266          * This depends of the outcome of errata 4176. The current spec. states to use UTF-8
    267          * where possible, but the RFCs states to use US-ASCII for the headers - hence encoding
    268          * would be needed to support non US-ASCII characters. But the MAP spec states not to
    269          * use any encoding... */
    270         int partLength, lineLength = 0;
    271         lineLength += headerName.getBytes().length;
    272         sb.append(headerName);
    273         for(Rfc822Token address : addresses) {
    274             partLength = address.toString().getBytes().length+1;
    275             // Add folding if needed
    276             if(lineLength + partLength >= 998) // max line length in RFC2822
    277             {
    278                 sb.append("\r\n "); // Append a FWS (folding whitespace)
    279                 lineLength = 0;
    280             }
    281             sb.append(address.toString()).append(";");
    282             lineLength += partLength;
    283         }
    284         sb.append("\r\n");
    285     }
    286 
    287     public void encodeHeaders(StringBuilder sb) throws UnsupportedEncodingException
    288     {
    289         /* TODO: From RFC-4356 - about the RFC-(2)822 headers:
    290          *    "Current Internet Message format requires that only 7-bit US-ASCII
    291          *     characters be present in headers.  Non-7-bit characters in an address
    292          *     domain must be encoded with [IDN].  If there are any non-7-bit
    293          *     characters in the local part of an address, the message MUST be
    294          *     rejected.  Non-7-bit characters elsewhere in a header MUST be encoded
    295          *     according to [Hdr-Enc]."
    296          *    We need to add the address encoding in encodeHeaderAddresses, but it is not
    297          *    straight forward, as it is unclear how to do this.  */
    298         if (date != INVALID_VALUE)
    299             sb.append("Date: ").append(getDateString()).append("\r\n");
    300         /* According to RFC-2822 headers must use US-ASCII, where the MAP specification states
    301          * UTF-8 should be used for the entire <bmessage-body-content>. We let the MAP specification
    302          * take precedence above the RFC-2822. The code to
    303          */
    304         /* If we are to use US-ASCII anyway, here are the code for it.
    305           if (subject != null){
    306             // Use base64 encoding for the subject, as it may contain non US-ASCII characters or other
    307             // illegal (RFC822 header), and android do not seem to have encoders/decoders for quoted-printables
    308             sb.append("Subject:").append("=?utf-8?B?");
    309             sb.append(Base64.encodeToString(subject.getBytes("utf-8"), Base64.DEFAULT));
    310             sb.append("?=\r\n");
    311         }*/
    312         if (subject != null)
    313             sb.append("Subject: ").append(subject).append("\r\n");
    314         if(from != null)
    315             encodeHeaderAddresses(sb, "From: ", from); // This includes folding if needed.
    316         if(sender != null)
    317             encodeHeaderAddresses(sb, "Sender: ", sender); // This includes folding if needed.
    318         /* For MMS one recipient(to, cc or bcc) must exists, if none: 'To:  undisclosed-
    319          * recipients:;' could be used.
    320          * TODO: Is this a valid solution for E-Mail?
    321          */
    322         if(to == null && cc == null && bcc == null)
    323             sb.append("To:  undisclosed-recipients:;\r\n");
    324         if(to != null)
    325             encodeHeaderAddresses(sb, "To: ", to); // This includes folding if needed.
    326         if(cc != null)
    327             encodeHeaderAddresses(sb, "Cc: ", cc); // This includes folding if needed.
    328         if(bcc != null)
    329             encodeHeaderAddresses(sb, "Bcc: ", bcc); // This includes folding if needed.
    330         if(replyTo != null)
    331             encodeHeaderAddresses(sb, "Reply-To: ", replyTo); // This includes folding if needed.
    332         if(includeAttachments == true)
    333         {
    334             if(messageId != null)
    335                 sb.append("Message-Id: ").append(messageId).append("\r\n");
    336             if(contentType != null)
    337                 sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()).append("\r\n");
    338         }
    339         sb.append("\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above.
    340     }
    341 
    342     /* Notes on MMS
    343      * ------------
    344      * According to rfc4356 all headers of a MMS converted to an E-mail must use
    345      * 7-bit encoding. According the the MAP specification only 8-bit encoding is
    346      * allowed - hence the bMessage-body should contain no SMTP headers. (Which makes
    347      * sense, since the info is already present in the bMessage properties.)
    348      * The result is that no information from RFC4356 is needed, since it does not
    349      * describe any mapping between MMS content and E-mail content.
    350      * Suggestion:
    351      * Clearly state in the MAP specification that
    352      * only the actual message content should be included in the <bmessage-body-content>.
    353      * Correct the Example to not include the E-mail headers, and in stead show how to
    354      * include a picture or another binary attachment.
    355      *
    356      * If the headers should be included, clearly state which, as the example clearly shows
    357      * that some of the headers should be excluded.
    358      * Additionally it is not clear how to handle attachments. There is a parameter in the
    359      * get message to include attachments, but since only 8-bit encoding is allowed,
    360      * (hence neither base64 nor binary) there is no mechanism to embed the attachment in
    361      * the <bmessage-body-content>.
    362      *
    363      * UPDATE: Errata 4176 allows the needed encoding typed inside the <bmessage-body-content>
    364      * including Base64 and Quoted Printables - hence it is possible to encode non-us-ascii
    365      * messages - e.g. pictures and utf-8 strings with non-us-ascii content.
    366      * It have not yet been adopted, but since the comments clearly suggest that it is allowed
    367      * to use encoding schemes for non-text parts, it is still not clear what to do about non
    368      * US-ASCII text in the headers.
    369      * */
    370 
    371     /**
    372      * Encode the bMessage as a MMS
    373      * @return
    374      * @throws UnsupportedEncodingException
    375      */
    376     public byte[] encodeMms() throws UnsupportedEncodingException
    377     {
    378         ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
    379         StringBuilder sb = new StringBuilder();
    380         int count = 0;
    381         String mmsBody;
    382 
    383         encoding = "8BIT"; // The encoding used
    384 
    385         encodeHeaders(sb);
    386         if(getIncludeAttachments() == false) {
    387             for(MimePart part : parts) {
    388                 part.encodePlainText(sb); /* We call encode on all parts, to include a tag, where an attachment is missing. */
    389             }
    390         } else {
    391             for(MimePart part : parts) {
    392                 count++;
    393                 part.encode(sb, getBoundary(), (count == parts.size()));
    394             }
    395         }
    396 
    397         mmsBody = sb.toString();
    398 
    399         if(mmsBody != null) {
    400             String tmpBody = mmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
    401             bodyFragments.add(tmpBody.getBytes("UTF-8"));
    402         } else {
    403             bodyFragments.add(new byte[0]);
    404         }
    405 
    406         return encodeGeneric(bodyFragments);
    407     }
    408 
    409 
    410     /**
    411      * Try to parse the hdrPart string as e-mail headers.
    412      * @param hdrPart The string to parse.
    413      * @return Null if the entire string were e-mail headers. The part of the string in which
    414      * no headers were found.
    415      */
    416     private String parseMmsHeaders(String hdrPart) {
    417         String[] headers = hdrPart.split("\r\n");
    418         String header;
    419         hasHeaders = false;
    420 
    421         for(int i = 0, c = headers.length; i < c; i++) {
    422             header = headers[i];
    423 
    424             /* We need to figure out if any headers are present, in cases where devices do not follow the e-mail RFCs.
    425              * Skip empty lines, and then parse headers until a non-header line is found, at which point we treat the
    426              * remaining as plain text.
    427              */
    428             if(header.trim() == "")
    429                 continue;
    430             String[] headerParts = header.split(":",2);
    431             if(headerParts.length != 2) {
    432                 // We treat the remaining content as plain text.
    433                 StringBuilder remaining = new StringBuilder();
    434                 for(; i < c; i++)
    435                     remaining.append(headers[i]);
    436 
    437                 return remaining.toString();
    438             }
    439 
    440             String headerType = headerParts[0].toUpperCase();
    441             String headerValue = headerParts[1].trim();
    442 
    443             // Address headers
    444             /* TODO: If this is empty, the MSE needs to fill it in before sending the message.
    445              * This happens when sending the MMS, not sure what happens for e-mail.
    446              */
    447             if(headerType.contains("FROM")) {
    448                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
    449                 from = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
    450             }
    451             else if(headerType.contains("TO")) {
    452                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
    453                 to = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
    454             }
    455             else if(headerType.contains("CC")) {
    456                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
    457                 cc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
    458             }
    459             else if(headerType.contains("BCC")) {
    460                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
    461                 bcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
    462             }
    463             else if(headerType.contains("REPLY-TO")) {
    464                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
    465                 replyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
    466             }// Other headers
    467             else if(headerType.contains("SUBJECT")) {
    468                 subject = headerValue;
    469             }
    470             else if(headerType.contains("MESSAGE-ID")) {
    471                 messageId = headerValue;
    472             }
    473             else if(headerType.contains("DATE")) {
    474                 /* TODO: Do we need the date? */
    475             }
    476             else if(headerType.contains("CONTENT-TYPE")) {
    477                 String[] contentTypeParts = headerValue.split(";");
    478                 contentType = contentTypeParts[0];
    479                 // Extract the boundary if it exists
    480                 for(int j=1, n=contentTypeParts.length; j<n; j++)
    481                 {
    482                     if(contentTypeParts[j].contains("boundary")) {
    483                         boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
    484                     }
    485                 }
    486             }
    487             else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
    488                 encoding = headerValue;
    489             }
    490             else {
    491                 if(D) Log.w(TAG,"Skipping unknown header: " + headerType + " (" + header + ")");
    492             }
    493         }
    494         return null;
    495     }
    496 
    497     private void parseMmsMimePart(String partStr) {
    498         String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
    499         String body;
    500         if(parts.length != 2) {
    501             body = partStr;
    502         } else {
    503             body = parts[1];
    504         }
    505         String[] headers = parts[0].split("\r\n");
    506         MimePart newPart = addMimePart();
    507         String partEncoding = encoding; /* Use the overall encoding as default */
    508 
    509         for(String header : headers) {
    510             if(header.length() == 0)
    511                 continue;
    512 
    513             if(header.trim() == "" || header.trim().equals("--")) // Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
    514                 continue;
    515             String[] headerParts = header.split(":",2);
    516             if(headerParts.length != 2)
    517                 throw new IllegalArgumentException("part-Header not formatted correctly: " + header);
    518             String headerType = headerParts[0].toUpperCase();
    519             String headerValue = headerParts[1].trim();
    520             if(headerType.contains("CONTENT-TYPE")) {
    521                 // TODO: extract charset? Only UTF-8 is allowed for TEXT typed parts
    522                 newPart.contentType = headerValue;
    523                 Log.d(TAG, "*** CONTENT-TYPE: " + newPart.contentType);
    524             }
    525             else if(headerType.contains("CONTENT-LOCATION")) {
    526                 // This is used if the smil refers to a file name in its src=
    527                 newPart.contentLocation = headerValue;
    528                 newPart.partName = headerValue;
    529             }
    530             else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
    531                 partEncoding = headerValue;
    532             }
    533             else if(headerType.contains("CONTENT-ID")) {
    534                 // This is used if the smil refers to a cid:<xxx> in it's src=
    535                 newPart.contentId = headerValue;
    536             }
    537             else if(headerType.contains("CONTENT-DISPOSITION")) {
    538                 // This is used if the smil refers to a cid:<xxx> in it's src=
    539                 newPart.contentDisposition = headerValue;
    540             }
    541             else {
    542                 if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")");
    543             }
    544         }
    545         // Now for the body
    546         newPart.data = decodeBody(body, partEncoding);
    547     }
    548 
    549     private void parseMmsMimeBody(String body) {
    550         MimePart newPart = addMimePart();
    551         newPart.data = decodeBody(body, encoding);
    552     }
    553 
    554     private byte[] decodeBody(String body, String encoding) {
    555         if(encoding != null && encoding.toUpperCase().contains("BASE64")) {
    556             return Base64.decode(body, Base64.DEFAULT);
    557         } else {
    558             // TODO: handle other encoding types? - here we simply store the string data as bytes
    559             try {
    560                 return body.getBytes("UTF-8");
    561             } catch (UnsupportedEncodingException e) {
    562                 // This will never happen, as UTF-8 is mandatory on Android platforms
    563             }
    564         }
    565         return null;
    566     }
    567 
    568     private void parseMms(String message) {
    569         /* Overall strategy for decoding:
    570          * 1) split on first empty line to extract the header
    571          * 2) unfold and parse headers
    572          * 3) split on boundary to split into parts (or use the remaining as a part,
    573          *    if part is not found)
    574          * 4) parse each part
    575          * */
    576         String[] messageParts;
    577         String[] mimeParts;
    578         String remaining = null;
    579         String messageBody = null;
    580         message = message.replaceAll("\\r\\n[ \\\t]+", ""); // Unfold
    581         messageParts = message.split("\r\n\r\n", 2); // Split the header from the body
    582         if(messageParts.length != 2) {
    583             // Handle entire message as plain text
    584             messageBody = message;
    585         }
    586         else
    587         {
    588             remaining = parseMmsHeaders(messageParts[0]);
    589             // If we have some text not being a header, add it to the message body.
    590             if(remaining != null) {
    591                 messageBody = remaining + messageParts[1];
    592             }
    593             else {
    594                 messageBody = messageParts[1];
    595             }
    596         }
    597 
    598         if(boundary == null)
    599         {
    600             // If the boundary is not set, handle as non-multi-part
    601             parseMmsMimeBody(messageBody);
    602             setTextOnly(true);
    603             if(contentType == null)
    604                 contentType = "text/plain";
    605             parts.get(0).contentType = contentType;
    606         }
    607         else
    608         {
    609             mimeParts = messageBody.split("--" + boundary);
    610             for(int i = 0; i < mimeParts.length - 1; i++) {
    611                 String part = mimeParts[i];
    612                 if (part != null && (part.length() > 0))
    613                     parseMmsMimePart(part);
    614         }
    615         }
    616     }
    617 
    618     /* Notes on SMIL decoding (from http://tools.ietf.org/html/rfc2557):
    619      * src="filename.jpg" refers to a part with Content-Location: filename.jpg
    620      * src="cid:1234 (at) hest.net" refers to a part with Content-ID:<1234 (at) hest.net>*/
    621     @Override
    622     public void parseMsgPart(String msgPart) {
    623         parseMms(msgPart);
    624 
    625     }
    626 
    627     @Override
    628     public void parseMsgInit() {
    629         // Not used for e-mail
    630 
    631     }
    632 
    633     @Override
    634     public byte[] encode() throws UnsupportedEncodingException {
    635         return encodeMms();
    636     }
    637 
    638 }
    639