Home | History | Annotate | Download | only in pdu
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.google.android.mms.pdu;
     19 
     20 import com.google.android.mms.ContentType;
     21 import com.google.android.mms.InvalidHeaderValueException;
     22 
     23 import android.util.Config;
     24 import android.util.Log;
     25 
     26 import java.io.ByteArrayInputStream;
     27 import java.io.ByteArrayOutputStream;
     28 import java.io.UnsupportedEncodingException;
     29 import java.util.Arrays;
     30 import java.util.HashMap;
     31 
     32 import android.content.res.Resources;
     33 
     34 public class PduParser {
     35     /**
     36      *  The next are WAP values defined in WSP specification.
     37      */
     38     private static final int QUOTE = 127;
     39     private static final int LENGTH_QUOTE = 31;
     40     private static final int TEXT_MIN = 32;
     41     private static final int TEXT_MAX = 127;
     42     private static final int SHORT_INTEGER_MAX = 127;
     43     private static final int SHORT_LENGTH_MAX = 30;
     44     private static final int LONG_INTEGER_LENGTH_MAX = 8;
     45     private static final int QUOTED_STRING_FLAG = 34;
     46     private static final int END_STRING_FLAG = 0x00;
     47     //The next two are used by the interface "parseWapString" to
     48     //distinguish Text-String and Quoted-String.
     49     private static final int TYPE_TEXT_STRING = 0;
     50     private static final int TYPE_QUOTED_STRING = 1;
     51     private static final int TYPE_TOKEN_STRING = 2;
     52 
     53     /**
     54      * Specify the part position.
     55      */
     56     private static final int THE_FIRST_PART = 0;
     57     private static final int THE_LAST_PART = 1;
     58 
     59     /**
     60      * The pdu data.
     61      */
     62     private ByteArrayInputStream mPduDataStream = null;
     63 
     64     /**
     65      * Store pdu headers
     66      */
     67     private PduHeaders mHeaders = null;
     68 
     69     /**
     70      * Store pdu parts.
     71      */
     72     private PduBody mBody = null;
     73 
     74     /**
     75      * Store the "type" parameter in "Content-Type" header field.
     76      */
     77     private static byte[] mTypeParam = null;
     78 
     79     /**
     80      * Store the "start" parameter in "Content-Type" header field.
     81      */
     82     private static byte[] mStartParam = null;
     83 
     84     /**
     85      * The log tag.
     86      */
     87     private static final String LOG_TAG = "PduParser";
     88     private static final boolean DEBUG = false;
     89     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
     90 
     91     /**
     92      * Constructor.
     93      *
     94      * @param pduDataStream pdu data to be parsed
     95      */
     96     public PduParser(byte[] pduDataStream) {
     97         mPduDataStream = new ByteArrayInputStream(pduDataStream);
     98     }
     99 
    100     /**
    101      * Parse the pdu.
    102      *
    103      * @return the pdu structure if parsing successfully.
    104      *         null if parsing error happened or mandatory fields are not set.
    105      */
    106     public GenericPdu parse(){
    107         if (mPduDataStream == null) {
    108             return null;
    109         }
    110 
    111         /* parse headers */
    112         mHeaders = parseHeaders(mPduDataStream);
    113         if (null == mHeaders) {
    114             // Parse headers failed.
    115             return null;
    116         }
    117 
    118         /* get the message type */
    119         int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
    120 
    121         /* check mandatory header fields */
    122         if (false == checkMandatoryHeader(mHeaders)) {
    123             log("check mandatory headers failed!");
    124             return null;
    125         }
    126 
    127         if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
    128                 (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
    129             /* need to parse the parts */
    130             mBody = parseParts(mPduDataStream);
    131             if (null == mBody) {
    132                 // Parse parts failed.
    133                 return null;
    134             }
    135         }
    136 
    137         switch (messageType) {
    138             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
    139                 SendReq sendReq = new SendReq(mHeaders, mBody);
    140                 return sendReq;
    141             case PduHeaders.MESSAGE_TYPE_SEND_CONF:
    142                 SendConf sendConf = new SendConf(mHeaders);
    143                 return sendConf;
    144             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
    145                 NotificationInd notificationInd =
    146                     new NotificationInd(mHeaders);
    147                 return notificationInd;
    148             case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
    149                 NotifyRespInd notifyRespInd =
    150                     new NotifyRespInd(mHeaders);
    151                 return notifyRespInd;
    152             case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
    153                 RetrieveConf retrieveConf =
    154                     new RetrieveConf(mHeaders, mBody);
    155 
    156                 byte[] contentType = retrieveConf.getContentType();
    157                 if (null == contentType) {
    158                     return null;
    159                 }
    160                 String ctTypeStr = new String(contentType);
    161                 if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
    162                         || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
    163                         || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
    164                     // The MMS content type must be "application/vnd.wap.multipart.mixed"
    165                     // or "application/vnd.wap.multipart.related"
    166                     // or "application/vnd.wap.multipart.alternative"
    167                     return retrieveConf;
    168                 }
    169                 return null;
    170             case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
    171                 DeliveryInd deliveryInd =
    172                     new DeliveryInd(mHeaders);
    173                 return deliveryInd;
    174             case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
    175                 AcknowledgeInd acknowledgeInd =
    176                     new AcknowledgeInd(mHeaders);
    177                 return acknowledgeInd;
    178             case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
    179                 ReadOrigInd readOrigInd =
    180                     new ReadOrigInd(mHeaders);
    181                 return readOrigInd;
    182             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
    183                 ReadRecInd readRecInd =
    184                     new ReadRecInd(mHeaders);
    185                 return readRecInd;
    186             default:
    187                 log("Parser doesn't support this message type in this version!");
    188             return null;
    189         }
    190     }
    191 
    192     /**
    193      * Parse pdu headers.
    194      *
    195      * @param pduDataStream pdu data input stream
    196      * @return headers in PduHeaders structure, null when parse fail
    197      */
    198     protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
    199         if (pduDataStream == null) {
    200             return null;
    201         }
    202 
    203         boolean keepParsing = true;
    204         PduHeaders headers = new PduHeaders();
    205 
    206         while (keepParsing && (pduDataStream.available() > 0)) {
    207             pduDataStream.mark(1);
    208             int headerField = extractByteValue(pduDataStream);
    209             /* parse custom text header */
    210             if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
    211                 pduDataStream.reset();
    212                 byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    213                 if (LOCAL_LOGV) {
    214                     Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
    215                 }
    216                 /* we should ignore it at the moment */
    217                 continue;
    218             }
    219             switch (headerField) {
    220                 case PduHeaders.MESSAGE_TYPE:
    221                 {
    222                     int messageType = extractByteValue(pduDataStream);
    223                     switch (messageType) {
    224                         // We don't support these kind of messages now.
    225                         case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
    226                         case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
    227                         case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
    228                         case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
    229                         case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
    230                         case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
    231                         case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
    232                         case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
    233                         case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
    234                         case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
    235                         case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
    236                         case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
    237                         case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
    238                         case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
    239                         case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
    240                             return null;
    241                     }
    242                     try {
    243                         headers.setOctet(messageType, headerField);
    244                     } catch(InvalidHeaderValueException e) {
    245                         log("Set invalid Octet value: " + messageType +
    246                                 " into the header filed: " + headerField);
    247                         return null;
    248                     } catch(RuntimeException e) {
    249                         log(headerField + "is not Octet header field!");
    250                         return null;
    251                     }
    252                     break;
    253                 }
    254                 /* Octect value */
    255                 case PduHeaders.REPORT_ALLOWED:
    256                 case PduHeaders.ADAPTATION_ALLOWED:
    257                 case PduHeaders.DELIVERY_REPORT:
    258                 case PduHeaders.DRM_CONTENT:
    259                 case PduHeaders.DISTRIBUTION_INDICATOR:
    260                 case PduHeaders.QUOTAS:
    261                 case PduHeaders.READ_REPORT:
    262                 case PduHeaders.STORE:
    263                 case PduHeaders.STORED:
    264                 case PduHeaders.TOTALS:
    265                 case PduHeaders.SENDER_VISIBILITY:
    266                 case PduHeaders.READ_STATUS:
    267                 case PduHeaders.CANCEL_STATUS:
    268                 case PduHeaders.PRIORITY:
    269                 case PduHeaders.STATUS:
    270                 case PduHeaders.REPLY_CHARGING:
    271                 case PduHeaders.MM_STATE:
    272                 case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
    273                 case PduHeaders.CONTENT_CLASS:
    274                 case PduHeaders.RETRIEVE_STATUS:
    275                 case PduHeaders.STORE_STATUS:
    276                     /**
    277                      * The following field has a different value when
    278                      * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
    279                      * For now we ignore this fact, since we do not support these PDUs
    280                      */
    281                 case PduHeaders.RESPONSE_STATUS:
    282                 {
    283                     int value = extractByteValue(pduDataStream);
    284 
    285                     try {
    286                         headers.setOctet(value, headerField);
    287                     } catch(InvalidHeaderValueException e) {
    288                         log("Set invalid Octet value: " + value +
    289                                 " into the header filed: " + headerField);
    290                         return null;
    291                     } catch(RuntimeException e) {
    292                         log(headerField + "is not Octet header field!");
    293                         return null;
    294                     }
    295                     break;
    296                 }
    297 
    298                 /* Long-Integer */
    299                 case PduHeaders.DATE:
    300                 case PduHeaders.REPLY_CHARGING_SIZE:
    301                 case PduHeaders.MESSAGE_SIZE:
    302                 {
    303                     try {
    304                         long value = parseLongInteger(pduDataStream);
    305                         headers.setLongInteger(value, headerField);
    306                     } catch(RuntimeException e) {
    307                         log(headerField + "is not Long-Integer header field!");
    308                         return null;
    309                     }
    310                     break;
    311                 }
    312 
    313                 /* Integer-Value */
    314                 case PduHeaders.MESSAGE_COUNT:
    315                 case PduHeaders.START:
    316                 case PduHeaders.LIMIT:
    317                 {
    318                     try {
    319                         long value = parseIntegerValue(pduDataStream);
    320                         headers.setLongInteger(value, headerField);
    321                     } catch(RuntimeException e) {
    322                         log(headerField + "is not Long-Integer header field!");
    323                         return null;
    324                     }
    325                     break;
    326                 }
    327 
    328                 /* Text-String */
    329                 case PduHeaders.TRANSACTION_ID:
    330                 case PduHeaders.REPLY_CHARGING_ID:
    331                 case PduHeaders.AUX_APPLIC_ID:
    332                 case PduHeaders.APPLIC_ID:
    333                 case PduHeaders.REPLY_APPLIC_ID:
    334                     /**
    335                      * The next three header fields are email addresses
    336                      * as defined in RFC2822,
    337                      * not including the characters "<" and ">"
    338                      */
    339                 case PduHeaders.MESSAGE_ID:
    340                 case PduHeaders.REPLACE_ID:
    341                 case PduHeaders.CANCEL_ID:
    342                     /**
    343                      * The following field has a different value when
    344                      * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
    345                      * For now we ignore this fact, since we do not support these PDUs
    346                      */
    347                 case PduHeaders.CONTENT_LOCATION:
    348                 {
    349                     byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    350                     if (null != value) {
    351                         try {
    352                             headers.setTextString(value, headerField);
    353                         } catch(NullPointerException e) {
    354                             log("null pointer error!");
    355                         } catch(RuntimeException e) {
    356                             log(headerField + "is not Text-String header field!");
    357                             return null;
    358                         }
    359                     }
    360                     break;
    361                 }
    362 
    363                 /* Encoded-string-value */
    364                 case PduHeaders.SUBJECT:
    365                 case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
    366                 case PduHeaders.RETRIEVE_TEXT:
    367                 case PduHeaders.STATUS_TEXT:
    368                 case PduHeaders.STORE_STATUS_TEXT:
    369                     /* the next one is not support
    370                      * M-Mbox-Delete.conf and M-Delete.conf now */
    371                 case PduHeaders.RESPONSE_TEXT:
    372                 {
    373                     EncodedStringValue value =
    374                         parseEncodedStringValue(pduDataStream);
    375                     if (null != value) {
    376                         try {
    377                             headers.setEncodedStringValue(value, headerField);
    378                         } catch(NullPointerException e) {
    379                             log("null pointer error!");
    380                         } catch (RuntimeException e) {
    381                             log(headerField + "is not Encoded-String-Value header field!");
    382                             return null;
    383                         }
    384                     }
    385                     break;
    386                 }
    387 
    388                 /* Addressing model */
    389                 case PduHeaders.BCC:
    390                 case PduHeaders.CC:
    391                 case PduHeaders.TO:
    392                 {
    393                     EncodedStringValue value =
    394                         parseEncodedStringValue(pduDataStream);
    395                     if (null != value) {
    396                         byte[] address = value.getTextString();
    397                         if (null != address) {
    398                             String str = new String(address);
    399                             int endIndex = str.indexOf("/");
    400                             if (endIndex > 0) {
    401                                 str = str.substring(0, endIndex);
    402                             }
    403                             try {
    404                                 value.setTextString(str.getBytes());
    405                             } catch(NullPointerException e) {
    406                                 log("null pointer error!");
    407                                 return null;
    408                             }
    409                         }
    410 
    411                         try {
    412                             headers.appendEncodedStringValue(value, headerField);
    413                         } catch(NullPointerException e) {
    414                             log("null pointer error!");
    415                         } catch(RuntimeException e) {
    416                             log(headerField + "is not Encoded-String-Value header field!");
    417                             return null;
    418                         }
    419                     }
    420                     break;
    421                 }
    422 
    423                 /* Value-length
    424                  * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
    425                 case PduHeaders.DELIVERY_TIME:
    426                 case PduHeaders.EXPIRY:
    427                 case PduHeaders.REPLY_CHARGING_DEADLINE:
    428                 {
    429                     /* parse Value-length */
    430                     parseValueLength(pduDataStream);
    431 
    432                     /* Absolute-token or Relative-token */
    433                     int token = extractByteValue(pduDataStream);
    434 
    435                     /* Date-value or Delta-seconds-value */
    436                     long timeValue;
    437                     try {
    438                         timeValue = parseLongInteger(pduDataStream);
    439                     } catch(RuntimeException e) {
    440                         log(headerField + "is not Long-Integer header field!");
    441                         return null;
    442                     }
    443                     if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
    444                         /* need to convert the Delta-seconds-value
    445                          * into Date-value */
    446                         timeValue = System.currentTimeMillis()/1000 + timeValue;
    447                     }
    448 
    449                     try {
    450                         headers.setLongInteger(timeValue, headerField);
    451                     } catch(RuntimeException e) {
    452                         log(headerField + "is not Long-Integer header field!");
    453                         return null;
    454                     }
    455                     break;
    456                 }
    457 
    458                 case PduHeaders.FROM: {
    459                     /* From-value =
    460                      * Value-length
    461                      * (Address-present-token Encoded-string-value | Insert-address-token)
    462                      */
    463                     EncodedStringValue from = null;
    464                     parseValueLength(pduDataStream); /* parse value-length */
    465 
    466                     /* Address-present-token or Insert-address-token */
    467                     int fromToken = extractByteValue(pduDataStream);
    468 
    469                     /* Address-present-token or Insert-address-token */
    470                     if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
    471                         /* Encoded-string-value */
    472                         from = parseEncodedStringValue(pduDataStream);
    473                         if (null != from) {
    474                             byte[] address = from.getTextString();
    475                             if (null != address) {
    476                                 String str = new String(address);
    477                                 int endIndex = str.indexOf("/");
    478                                 if (endIndex > 0) {
    479                                     str = str.substring(0, endIndex);
    480                                 }
    481                                 try {
    482                                     from.setTextString(str.getBytes());
    483                                 } catch(NullPointerException e) {
    484                                     log("null pointer error!");
    485                                     return null;
    486                                 }
    487                             }
    488                         }
    489                     } else {
    490                         try {
    491                             from = new EncodedStringValue(
    492                                     PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
    493                         } catch(NullPointerException e) {
    494                             log(headerField + "is not Encoded-String-Value header field!");
    495                             return null;
    496                         }
    497                     }
    498 
    499                     try {
    500                         headers.setEncodedStringValue(from, PduHeaders.FROM);
    501                     } catch(NullPointerException e) {
    502                         log("null pointer error!");
    503                     } catch(RuntimeException e) {
    504                         log(headerField + "is not Encoded-String-Value header field!");
    505                         return null;
    506                     }
    507                     break;
    508                 }
    509 
    510                 case PduHeaders.MESSAGE_CLASS: {
    511                     /* Message-class-value = Class-identifier | Token-text */
    512                     pduDataStream.mark(1);
    513                     int messageClass = extractByteValue(pduDataStream);
    514 
    515                     if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
    516                         /* Class-identifier */
    517                         try {
    518                             if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
    519                                 headers.setTextString(
    520                                         PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
    521                                         PduHeaders.MESSAGE_CLASS);
    522                             } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
    523                                 headers.setTextString(
    524                                         PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
    525                                         PduHeaders.MESSAGE_CLASS);
    526                             } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
    527                                 headers.setTextString(
    528                                         PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
    529                                         PduHeaders.MESSAGE_CLASS);
    530                             } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
    531                                 headers.setTextString(
    532                                         PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
    533                                         PduHeaders.MESSAGE_CLASS);
    534                             }
    535                         } catch(NullPointerException e) {
    536                             log("null pointer error!");
    537                         } catch(RuntimeException e) {
    538                             log(headerField + "is not Text-String header field!");
    539                             return null;
    540                         }
    541                     } else {
    542                         /* Token-text */
    543                         pduDataStream.reset();
    544                         byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    545                         if (null != messageClassString) {
    546                             try {
    547                                 headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
    548                             } catch(NullPointerException e) {
    549                                 log("null pointer error!");
    550                             } catch(RuntimeException e) {
    551                                 log(headerField + "is not Text-String header field!");
    552                                 return null;
    553                             }
    554                         }
    555                     }
    556                     break;
    557                 }
    558 
    559                 case PduHeaders.MMS_VERSION: {
    560                     int version = parseShortInteger(pduDataStream);
    561 
    562                     try {
    563                         headers.setOctet(version, PduHeaders.MMS_VERSION);
    564                     } catch(InvalidHeaderValueException e) {
    565                         log("Set invalid Octet value: " + version +
    566                                 " into the header filed: " + headerField);
    567                         return null;
    568                     } catch(RuntimeException e) {
    569                         log(headerField + "is not Octet header field!");
    570                         return null;
    571                     }
    572                     break;
    573                 }
    574 
    575                 case PduHeaders.PREVIOUSLY_SENT_BY: {
    576                     /* Previously-sent-by-value =
    577                      * Value-length Forwarded-count-value Encoded-string-value */
    578                     /* parse value-length */
    579                     parseValueLength(pduDataStream);
    580 
    581                     /* parse Forwarded-count-value */
    582                     try {
    583                         parseIntegerValue(pduDataStream);
    584                     } catch(RuntimeException e) {
    585                         log(headerField + " is not Integer-Value");
    586                         return null;
    587                     }
    588 
    589                     /* parse Encoded-string-value */
    590                     EncodedStringValue previouslySentBy =
    591                         parseEncodedStringValue(pduDataStream);
    592                     if (null != previouslySentBy) {
    593                         try {
    594                             headers.setEncodedStringValue(previouslySentBy,
    595                                     PduHeaders.PREVIOUSLY_SENT_BY);
    596                         } catch(NullPointerException e) {
    597                             log("null pointer error!");
    598                         } catch(RuntimeException e) {
    599                             log(headerField + "is not Encoded-String-Value header field!");
    600                             return null;
    601                         }
    602                     }
    603                     break;
    604                 }
    605 
    606                 case PduHeaders.PREVIOUSLY_SENT_DATE: {
    607                     /* Previously-sent-date-value =
    608                      * Value-length Forwarded-count-value Date-value */
    609                     /* parse value-length */
    610                     parseValueLength(pduDataStream);
    611 
    612                     /* parse Forwarded-count-value */
    613                     try {
    614                         parseIntegerValue(pduDataStream);
    615                     } catch(RuntimeException e) {
    616                         log(headerField + " is not Integer-Value");
    617                         return null;
    618                     }
    619 
    620                     /* Date-value */
    621                     try {
    622                         long perviouslySentDate = parseLongInteger(pduDataStream);
    623                         headers.setLongInteger(perviouslySentDate,
    624                                 PduHeaders.PREVIOUSLY_SENT_DATE);
    625                     } catch(RuntimeException e) {
    626                         log(headerField + "is not Long-Integer header field!");
    627                         return null;
    628                     }
    629                     break;
    630                 }
    631 
    632                 case PduHeaders.MM_FLAGS: {
    633                     /* MM-flags-value =
    634                      * Value-length
    635                      * ( Add-token | Remove-token | Filter-token )
    636                      * Encoded-string-value
    637                      */
    638 
    639                     /* parse Value-length */
    640                     parseValueLength(pduDataStream);
    641 
    642                     /* Add-token | Remove-token | Filter-token */
    643                     extractByteValue(pduDataStream);
    644 
    645                     /* Encoded-string-value */
    646                     parseEncodedStringValue(pduDataStream);
    647 
    648                     /* not store this header filed in "headers",
    649                      * because now PduHeaders doesn't support it */
    650                     break;
    651                 }
    652 
    653                 /* Value-length
    654                  * (Message-total-token | Size-total-token) Integer-Value */
    655                 case PduHeaders.MBOX_TOTALS:
    656                 case PduHeaders.MBOX_QUOTAS:
    657                 {
    658                     /* Value-length */
    659                     parseValueLength(pduDataStream);
    660 
    661                     /* Message-total-token | Size-total-token */
    662                     extractByteValue(pduDataStream);
    663 
    664                     /*Integer-Value*/
    665                     try {
    666                         parseIntegerValue(pduDataStream);
    667                     } catch(RuntimeException e) {
    668                         log(headerField + " is not Integer-Value");
    669                         return null;
    670                     }
    671 
    672                     /* not store these headers filed in "headers",
    673                     because now PduHeaders doesn't support them */
    674                     break;
    675                 }
    676 
    677                 case PduHeaders.ELEMENT_DESCRIPTOR: {
    678                     parseContentType(pduDataStream, null);
    679 
    680                     /* not store this header filed in "headers",
    681                     because now PduHeaders doesn't support it */
    682                     break;
    683                 }
    684 
    685                 case PduHeaders.CONTENT_TYPE: {
    686                     HashMap<Integer, Object> map =
    687                         new HashMap<Integer, Object>();
    688                     byte[] contentType =
    689                         parseContentType(pduDataStream, map);
    690 
    691                     if (null != contentType) {
    692                         try {
    693                             headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
    694                         } catch(NullPointerException e) {
    695                             log("null pointer error!");
    696                         } catch(RuntimeException e) {
    697                             log(headerField + "is not Text-String header field!");
    698                             return null;
    699                         }
    700                     }
    701 
    702                     /* get start parameter */
    703                     mStartParam = (byte[]) map.get(PduPart.P_START);
    704 
    705                     /* get charset parameter */
    706                     mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
    707 
    708                     keepParsing = false;
    709                     break;
    710                 }
    711 
    712                 case PduHeaders.CONTENT:
    713                 case PduHeaders.ADDITIONAL_HEADERS:
    714                 case PduHeaders.ATTRIBUTES:
    715                 default: {
    716                     log("Unknown header");
    717                 }
    718             }
    719         }
    720 
    721         return headers;
    722     }
    723 
    724     /**
    725      * Parse pdu parts.
    726      *
    727      * @param pduDataStream pdu data input stream
    728      * @return parts in PduBody structure
    729      */
    730     protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {
    731         if (pduDataStream == null) {
    732             return null;
    733         }
    734 
    735         int count = parseUnsignedInt(pduDataStream); // get the number of parts
    736         PduBody body = new PduBody();
    737 
    738         for (int i = 0 ; i < count ; i++) {
    739             int headerLength = parseUnsignedInt(pduDataStream);
    740             int dataLength = parseUnsignedInt(pduDataStream);
    741             PduPart part = new PduPart();
    742             int startPos = pduDataStream.available();
    743             if (startPos <= 0) {
    744                 // Invalid part.
    745                 return null;
    746             }
    747 
    748             /* parse part's content-type */
    749             HashMap<Integer, Object> map = new HashMap<Integer, Object>();
    750             byte[] contentType = parseContentType(pduDataStream, map);
    751             if (null != contentType) {
    752                 part.setContentType(contentType);
    753             } else {
    754                 part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
    755             }
    756 
    757             /* get name parameter */
    758             byte[] name = (byte[]) map.get(PduPart.P_NAME);
    759             if (null != name) {
    760                 part.setName(name);
    761             }
    762 
    763             /* get charset parameter */
    764             Integer charset = (Integer) map.get(PduPart.P_CHARSET);
    765             if (null != charset) {
    766                 part.setCharset(charset);
    767             }
    768 
    769             /* parse part's headers */
    770             int endPos = pduDataStream.available();
    771             int partHeaderLen = headerLength - (startPos - endPos);
    772             if (partHeaderLen > 0) {
    773                 if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
    774                     // Parse part header faild.
    775                     return null;
    776                 }
    777             } else if (partHeaderLen < 0) {
    778                 // Invalid length of content-type.
    779                 return null;
    780             }
    781 
    782             /* FIXME: check content-id, name, filename and content location,
    783              * if not set anyone of them, generate a default content-location
    784              */
    785             if ((null == part.getContentLocation())
    786                     && (null == part.getName())
    787                     && (null == part.getFilename())
    788                     && (null == part.getContentId())) {
    789                 part.setContentLocation(Long.toOctalString(
    790                         System.currentTimeMillis()).getBytes());
    791             }
    792 
    793             /* get part's data */
    794             if (dataLength > 0) {
    795                 byte[] partData = new byte[dataLength];
    796                 String partContentType = new String(part.getContentType());
    797                 pduDataStream.read(partData, 0, dataLength);
    798                 if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
    799                     // parse "multipart/vnd.wap.multipart.alternative".
    800                     PduBody childBody = parseParts(new ByteArrayInputStream(partData));
    801                     // take the first part of children.
    802                     part = childBody.getPart(0);
    803                 } else {
    804                     // Check Content-Transfer-Encoding.
    805                     byte[] partDataEncoding = part.getContentTransferEncoding();
    806                     if (null != partDataEncoding) {
    807                         String encoding = new String(partDataEncoding);
    808                         if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
    809                             // Decode "base64" into "binary".
    810                             partData = Base64.decodeBase64(partData);
    811                         } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
    812                             // Decode "quoted-printable" into "binary".
    813                             partData = QuotedPrintable.decodeQuotedPrintable(partData);
    814                         } else {
    815                             // "binary" is the default encoding.
    816                         }
    817                     }
    818                     if (null == partData) {
    819                         log("Decode part data error!");
    820                         return null;
    821                     }
    822                     part.setData(partData);
    823                 }
    824             }
    825 
    826             /* add this part to body */
    827             if (THE_FIRST_PART == checkPartPosition(part)) {
    828                 /* this is the first part */
    829                 body.addPart(0, part);
    830             } else {
    831                 /* add the part to the end */
    832                 body.addPart(part);
    833             }
    834         }
    835 
    836         return body;
    837     }
    838 
    839     /**
    840      * Log status.
    841      *
    842      * @param text log information
    843      */
    844     private static void log(String text) {
    845         if (LOCAL_LOGV) {
    846             Log.v(LOG_TAG, text);
    847         }
    848     }
    849 
    850     /**
    851      * Parse unsigned integer.
    852      *
    853      * @param pduDataStream pdu data input stream
    854      * @return the integer, -1 when failed
    855      */
    856     protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
    857         /**
    858          * From wap-230-wsp-20010705-a.pdf
    859          * The maximum size of a uintvar is 32 bits.
    860          * So it will be encoded in no more than 5 octets.
    861          */
    862         assert(null != pduDataStream);
    863         int result = 0;
    864         int temp = pduDataStream.read();
    865         if (temp == -1) {
    866             return temp;
    867         }
    868 
    869         while((temp & 0x80) != 0) {
    870             result = result << 7;
    871             result |= temp & 0x7F;
    872             temp = pduDataStream.read();
    873             if (temp == -1) {
    874                 return temp;
    875             }
    876         }
    877 
    878         result = result << 7;
    879         result |= temp & 0x7F;
    880 
    881         return result;
    882     }
    883 
    884     /**
    885      * Parse value length.
    886      *
    887      * @param pduDataStream pdu data input stream
    888      * @return the integer
    889      */
    890     protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
    891         /**
    892          * From wap-230-wsp-20010705-a.pdf
    893          * Value-length = Short-length | (Length-quote Length)
    894          * Short-length = <Any octet 0-30>
    895          * Length-quote = <Octet 31>
    896          * Length = Uintvar-integer
    897          * Uintvar-integer = 1*5 OCTET
    898          */
    899         assert(null != pduDataStream);
    900         int temp = pduDataStream.read();
    901         assert(-1 != temp);
    902         int first = temp & 0xFF;
    903 
    904         if (first <= SHORT_LENGTH_MAX) {
    905             return first;
    906         } else if (first == LENGTH_QUOTE) {
    907             return parseUnsignedInt(pduDataStream);
    908         }
    909 
    910         throw new RuntimeException ("Value length > LENGTH_QUOTE!");
    911     }
    912 
    913     /**
    914      * Parse encoded string value.
    915      *
    916      * @param pduDataStream pdu data input stream
    917      * @return the EncodedStringValue
    918      */
    919     protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
    920         /**
    921          * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
    922          * Encoded-string-value = Text-string | Value-length Char-set Text-string
    923          */
    924         assert(null != pduDataStream);
    925         pduDataStream.mark(1);
    926         EncodedStringValue returnValue = null;
    927         int charset = 0;
    928         int temp = pduDataStream.read();
    929         assert(-1 != temp);
    930         int first = temp & 0xFF;
    931 
    932         pduDataStream.reset();
    933         if (first < TEXT_MIN) {
    934             parseValueLength(pduDataStream);
    935 
    936             charset = parseShortInteger(pduDataStream); //get the "Charset"
    937         }
    938 
    939         byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    940 
    941         try {
    942             if (0 != charset) {
    943                 returnValue = new EncodedStringValue(charset, textString);
    944             } else {
    945                 returnValue = new EncodedStringValue(textString);
    946             }
    947         } catch(Exception e) {
    948             return null;
    949         }
    950 
    951         return returnValue;
    952     }
    953 
    954     /**
    955      * Parse Text-String or Quoted-String.
    956      *
    957      * @param pduDataStream pdu data input stream
    958      * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
    959      * @return the string without End-of-string in byte array
    960      */
    961     protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
    962             int stringType) {
    963         assert(null != pduDataStream);
    964         /**
    965          * From wap-230-wsp-20010705-a.pdf
    966          * Text-string = [Quote] *TEXT End-of-string
    967          * If the first character in the TEXT is in the range of 128-255,
    968          * a Quote character must precede it.
    969          * Otherwise the Quote character must be omitted.
    970          * The Quote is not part of the contents.
    971          * Quote = <Octet 127>
    972          * End-of-string = <Octet 0>
    973          *
    974          * Quoted-string = <Octet 34> *TEXT End-of-string
    975          *
    976          * Token-text = Token End-of-string
    977          */
    978 
    979         // Mark supposed beginning of Text-string
    980         // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
    981         pduDataStream.mark(1);
    982 
    983         // Check first char
    984         int temp = pduDataStream.read();
    985         assert(-1 != temp);
    986         if ((TYPE_QUOTED_STRING == stringType) &&
    987                 (QUOTED_STRING_FLAG == temp)) {
    988             // Mark again if QUOTED_STRING_FLAG and ignore it
    989             pduDataStream.mark(1);
    990         } else if ((TYPE_TEXT_STRING == stringType) &&
    991                 (QUOTE == temp)) {
    992             // Mark again if QUOTE and ignore it
    993             pduDataStream.mark(1);
    994         } else {
    995             // Otherwise go back to origin
    996             pduDataStream.reset();
    997         }
    998 
    999         // We are now definitely at the beginning of string
   1000         /**
   1001          * Return *TOKEN or *TEXT (Text-String without QUOTE,
   1002          * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
   1003          */
   1004         return getWapString(pduDataStream, stringType);
   1005     }
   1006 
   1007     /**
   1008      * Check TOKEN data defined in RFC2616.
   1009      * @param ch checking data
   1010      * @return true when ch is TOKEN, false when ch is not TOKEN
   1011      */
   1012     protected static boolean isTokenCharacter(int ch) {
   1013         /**
   1014          * Token      = 1*<any CHAR except CTLs or separators>
   1015          * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
   1016          *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
   1017          *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
   1018          *            | "{"(123) | "}"(125) | SP(32) | HT(9)
   1019          * CHAR       = <any US-ASCII character (octets 0 - 127)>
   1020          * CTL        = <any US-ASCII control character
   1021          *            (octets 0 - 31) and DEL (127)>
   1022          * SP         = <US-ASCII SP, space (32)>
   1023          * HT         = <US-ASCII HT, horizontal-tab (9)>
   1024          */
   1025         if((ch < 33) || (ch > 126)) {
   1026             return false;
   1027         }
   1028 
   1029         switch(ch) {
   1030             case '"': /* '"' */
   1031             case '(': /* '(' */
   1032             case ')': /* ')' */
   1033             case ',': /* ',' */
   1034             case '/': /* '/' */
   1035             case ':': /* ':' */
   1036             case ';': /* ';' */
   1037             case '<': /* '<' */
   1038             case '=': /* '=' */
   1039             case '>': /* '>' */
   1040             case '?': /* '?' */
   1041             case '@': /* '@' */
   1042             case '[': /* '[' */
   1043             case '\\': /* '\' */
   1044             case ']': /* ']' */
   1045             case '{': /* '{' */
   1046             case '}': /* '}' */
   1047                 return false;
   1048         }
   1049 
   1050         return true;
   1051     }
   1052 
   1053     /**
   1054      * Check TEXT data defined in RFC2616.
   1055      * @param ch checking data
   1056      * @return true when ch is TEXT, false when ch is not TEXT
   1057      */
   1058     protected static boolean isText(int ch) {
   1059         /**
   1060          * TEXT = <any OCTET except CTLs,
   1061          *      but including LWS>
   1062          * CTL  = <any US-ASCII control character
   1063          *      (octets 0 - 31) and DEL (127)>
   1064          * LWS  = [CRLF] 1*( SP | HT )
   1065          * CRLF = CR LF
   1066          * CR   = <US-ASCII CR, carriage return (13)>
   1067          * LF   = <US-ASCII LF, linefeed (10)>
   1068          */
   1069         if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
   1070             return true;
   1071         }
   1072 
   1073         switch(ch) {
   1074             case '\t': /* '\t' */
   1075             case '\n': /* '\n' */
   1076             case '\r': /* '\r' */
   1077                 return true;
   1078         }
   1079 
   1080         return false;
   1081     }
   1082 
   1083     protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
   1084             int stringType) {
   1085         assert(null != pduDataStream);
   1086         ByteArrayOutputStream out = new ByteArrayOutputStream();
   1087         int temp = pduDataStream.read();
   1088         assert(-1 != temp);
   1089         while((-1 != temp) && ('\0' != temp)) {
   1090             // check each of the character
   1091             if (stringType == TYPE_TOKEN_STRING) {
   1092                 if (isTokenCharacter(temp)) {
   1093                     out.write(temp);
   1094                 }
   1095             } else {
   1096                 if (isText(temp)) {
   1097                     out.write(temp);
   1098                 }
   1099             }
   1100 
   1101             temp = pduDataStream.read();
   1102             assert(-1 != temp);
   1103         }
   1104 
   1105         if (out.size() > 0) {
   1106             return out.toByteArray();
   1107         }
   1108 
   1109         return null;
   1110     }
   1111 
   1112     /**
   1113      * Extract a byte value from the input stream.
   1114      *
   1115      * @param pduDataStream pdu data input stream
   1116      * @return the byte
   1117      */
   1118     protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
   1119         assert(null != pduDataStream);
   1120         int temp = pduDataStream.read();
   1121         assert(-1 != temp);
   1122         return temp & 0xFF;
   1123     }
   1124 
   1125     /**
   1126      * Parse Short-Integer.
   1127      *
   1128      * @param pduDataStream pdu data input stream
   1129      * @return the byte
   1130      */
   1131     protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
   1132         /**
   1133          * From wap-230-wsp-20010705-a.pdf
   1134          * Short-integer = OCTET
   1135          * Integers in range 0-127 shall be encoded as a one
   1136          * octet value with the most significant bit set to one (1xxx xxxx)
   1137          * and with the value in the remaining least significant bits.
   1138          */
   1139         assert(null != pduDataStream);
   1140         int temp = pduDataStream.read();
   1141         assert(-1 != temp);
   1142         return temp & 0x7F;
   1143     }
   1144 
   1145     /**
   1146      * Parse Long-Integer.
   1147      *
   1148      * @param pduDataStream pdu data input stream
   1149      * @return long integer
   1150      */
   1151     protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
   1152         /**
   1153          * From wap-230-wsp-20010705-a.pdf
   1154          * Long-integer = Short-length Multi-octet-integer
   1155          * The Short-length indicates the length of the Multi-octet-integer
   1156          * Multi-octet-integer = 1*30 OCTET
   1157          * The content octets shall be an unsigned integer value
   1158          * with the most significant octet encoded first (big-endian representation).
   1159          * The minimum number of octets must be used to encode the value.
   1160          * Short-length = <Any octet 0-30>
   1161          */
   1162         assert(null != pduDataStream);
   1163         int temp = pduDataStream.read();
   1164         assert(-1 != temp);
   1165         int count = temp & 0xFF;
   1166 
   1167         if (count > LONG_INTEGER_LENGTH_MAX) {
   1168             throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
   1169         }
   1170 
   1171         long result = 0;
   1172 
   1173         for (int i = 0 ; i < count ; i++) {
   1174             temp = pduDataStream.read();
   1175             assert(-1 != temp);
   1176             result <<= 8;
   1177             result += (temp & 0xFF);
   1178         }
   1179 
   1180         return result;
   1181     }
   1182 
   1183     /**
   1184      * Parse Integer-Value.
   1185      *
   1186      * @param pduDataStream pdu data input stream
   1187      * @return long integer
   1188      */
   1189     protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
   1190         /**
   1191          * From wap-230-wsp-20010705-a.pdf
   1192          * Integer-Value = Short-integer | Long-integer
   1193          */
   1194         assert(null != pduDataStream);
   1195         pduDataStream.mark(1);
   1196         int temp = pduDataStream.read();
   1197         assert(-1 != temp);
   1198         pduDataStream.reset();
   1199         if (temp > SHORT_INTEGER_MAX) {
   1200             return parseShortInteger(pduDataStream);
   1201         } else {
   1202             return parseLongInteger(pduDataStream);
   1203         }
   1204     }
   1205 
   1206     /**
   1207      * To skip length of the wap value.
   1208      *
   1209      * @param pduDataStream pdu data input stream
   1210      * @param length area size
   1211      * @return the values in this area
   1212      */
   1213     protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
   1214         assert(null != pduDataStream);
   1215         byte[] area = new byte[length];
   1216         int readLen = pduDataStream.read(area, 0, length);
   1217         if (readLen < length) { //The actually read length is lower than the length
   1218             return -1;
   1219         } else {
   1220             return readLen;
   1221         }
   1222     }
   1223 
   1224     /**
   1225      * Parse content type parameters. For now we just support
   1226      * four parameters used in mms: "type", "start", "name", "charset".
   1227      *
   1228      * @param pduDataStream pdu data input stream
   1229      * @param map to store parameters of Content-Type field
   1230      * @param length length of all the parameters
   1231      */
   1232     protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
   1233             HashMap<Integer, Object> map, Integer length) {
   1234         /**
   1235          * From wap-230-wsp-20010705-a.pdf
   1236          * Parameter = Typed-parameter | Untyped-parameter
   1237          * Typed-parameter = Well-known-parameter-token Typed-value
   1238          * the actual expected type of the value is implied by the well-known parameter
   1239          * Well-known-parameter-token = Integer-value
   1240          * the code values used for parameters are specified in the Assigned Numbers appendix
   1241          * Typed-value = Compact-value | Text-value
   1242          * In addition to the expected type, there may be no value.
   1243          * If the value cannot be encoded using the expected type, it shall be encoded as text.
   1244          * Compact-value = Integer-value |
   1245          * Date-value | Delta-seconds-value | Q-value | Version-value |
   1246          * Uri-value
   1247          * Untyped-parameter = Token-text Untyped-value
   1248          * the type of the value is unknown, but it shall be encoded as an integer,
   1249          * if that is possible.
   1250          * Untyped-value = Integer-value | Text-value
   1251          */
   1252         assert(null != pduDataStream);
   1253         assert(length > 0);
   1254 
   1255         int startPos = pduDataStream.available();
   1256         int tempPos = 0;
   1257         int lastLen = length;
   1258         while(0 < lastLen) {
   1259             int param = pduDataStream.read();
   1260             assert(-1 != param);
   1261             lastLen--;
   1262 
   1263             switch (param) {
   1264                 /**
   1265                  * From rfc2387, chapter 3.1
   1266                  * The type parameter must be specified and its value is the MIME media
   1267                  * type of the "root" body part. It permits a MIME user agent to
   1268                  * determine the content-type without reference to the enclosed body
   1269                  * part. If the value of the type parameter and the root body part's
   1270                  * content-type differ then the User Agent's behavior is undefined.
   1271                  *
   1272                  * From wap-230-wsp-20010705-a.pdf
   1273                  * type = Constrained-encoding
   1274                  * Constrained-encoding = Extension-Media | Short-integer
   1275                  * Extension-media = *TEXT End-of-string
   1276                  */
   1277                 case PduPart.P_TYPE:
   1278                 case PduPart.P_CT_MR_TYPE:
   1279                     pduDataStream.mark(1);
   1280                     int first = extractByteValue(pduDataStream);
   1281                     pduDataStream.reset();
   1282                     if (first > TEXT_MAX) {
   1283                         // Short-integer (well-known type)
   1284                         int index = parseShortInteger(pduDataStream);
   1285 
   1286                         if (index < PduContentTypes.contentTypes.length) {
   1287                             byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
   1288                             map.put(PduPart.P_TYPE, type);
   1289                         } else {
   1290                             //not support this type, ignore it.
   1291                         }
   1292                     } else {
   1293                         // Text-String (extension-media)
   1294                         byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1295                         if ((null != type) && (null != map)) {
   1296                             map.put(PduPart.P_TYPE, type);
   1297                         }
   1298                     }
   1299 
   1300                     tempPos = pduDataStream.available();
   1301                     lastLen = length - (startPos - tempPos);
   1302                     break;
   1303 
   1304                     /**
   1305                      * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
   1306                      * Start Parameter Referring to Presentation
   1307                      *
   1308                      * From rfc2387, chapter 3.2
   1309                      * The start parameter, if given, is the content-ID of the compound
   1310                      * object's "root". If not present the "root" is the first body part in
   1311                      * the Multipart/Related entity. The "root" is the element the
   1312                      * applications processes first.
   1313                      *
   1314                      * From wap-230-wsp-20010705-a.pdf
   1315                      * start = Text-String
   1316                      */
   1317                 case PduPart.P_START:
   1318                 case PduPart.P_DEP_START:
   1319                     byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1320                     if ((null != start) && (null != map)) {
   1321                         map.put(PduPart.P_START, start);
   1322                     }
   1323 
   1324                     tempPos = pduDataStream.available();
   1325                     lastLen = length - (startPos - tempPos);
   1326                     break;
   1327 
   1328                     /**
   1329                      * From oma-ts-mms-conf-v1_3.pdf
   1330                      * In creation, the character set SHALL be either us-ascii
   1331                      * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
   1332                      * In retrieval, both us-ascii and utf-8 SHALL be supported.
   1333                      *
   1334                      * From wap-230-wsp-20010705-a.pdf
   1335                      * charset = Well-known-charset|Text-String
   1336                      * Well-known-charset = Any-charset | Integer-value
   1337                      * Both are encoded using values from Character Set
   1338                      * Assignments table in Assigned Numbers
   1339                      * Any-charset = <Octet 128>
   1340                      * Equivalent to the special RFC2616 charset value "*"
   1341                      */
   1342                 case PduPart.P_CHARSET:
   1343                     pduDataStream.mark(1);
   1344                     int firstValue = extractByteValue(pduDataStream);
   1345                     pduDataStream.reset();
   1346                     //Check first char
   1347                     if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
   1348                             (END_STRING_FLAG == firstValue)) {
   1349                         //Text-String (extension-charset)
   1350                         byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1351                         try {
   1352                             int charsetInt = CharacterSets.getMibEnumValue(
   1353                                     new String(charsetStr));
   1354                             map.put(PduPart.P_CHARSET, charsetInt);
   1355                         } catch (UnsupportedEncodingException e) {
   1356                             // Not a well-known charset, use "*".
   1357                             Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
   1358                             map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
   1359                         }
   1360                     } else {
   1361                         //Well-known-charset
   1362                         int charset = (int) parseIntegerValue(pduDataStream);
   1363                         if (map != null) {
   1364                             map.put(PduPart.P_CHARSET, charset);
   1365                         }
   1366                     }
   1367 
   1368                     tempPos = pduDataStream.available();
   1369                     lastLen = length - (startPos - tempPos);
   1370                     break;
   1371 
   1372                     /**
   1373                      * From oma-ts-mms-conf-v1_3.pdf
   1374                      * A name for multipart object SHALL be encoded using name-parameter
   1375                      * for Content-Type header in WSP multipart headers.
   1376                      *
   1377                      * From wap-230-wsp-20010705-a.pdf
   1378                      * name = Text-String
   1379                      */
   1380                 case PduPart.P_DEP_NAME:
   1381                 case PduPart.P_NAME:
   1382                     byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1383                     if ((null != name) && (null != map)) {
   1384                         map.put(PduPart.P_NAME, name);
   1385                     }
   1386 
   1387                     tempPos = pduDataStream.available();
   1388                     lastLen = length - (startPos - tempPos);
   1389                     break;
   1390                 default:
   1391                     if (LOCAL_LOGV) {
   1392                         Log.v(LOG_TAG, "Not supported Content-Type parameter");
   1393                     }
   1394                 if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1395                     Log.e(LOG_TAG, "Corrupt Content-Type");
   1396                 } else {
   1397                     lastLen = 0;
   1398                 }
   1399                 break;
   1400             }
   1401         }
   1402 
   1403         if (0 != lastLen) {
   1404             Log.e(LOG_TAG, "Corrupt Content-Type");
   1405         }
   1406     }
   1407 
   1408     /**
   1409      * Parse content type.
   1410      *
   1411      * @param pduDataStream pdu data input stream
   1412      * @param map to store parameters in Content-Type header field
   1413      * @return Content-Type value
   1414      */
   1415     protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
   1416             HashMap<Integer, Object> map) {
   1417         /**
   1418          * From wap-230-wsp-20010705-a.pdf
   1419          * Content-type-value = Constrained-media | Content-general-form
   1420          * Content-general-form = Value-length Media-type
   1421          * Media-type = (Well-known-media | Extension-Media) *(Parameter)
   1422          */
   1423         assert(null != pduDataStream);
   1424 
   1425         byte[] contentType = null;
   1426         pduDataStream.mark(1);
   1427         int temp = pduDataStream.read();
   1428         assert(-1 != temp);
   1429         pduDataStream.reset();
   1430 
   1431         int cur = (temp & 0xFF);
   1432 
   1433         if (cur < TEXT_MIN) {
   1434             int length = parseValueLength(pduDataStream);
   1435             int startPos = pduDataStream.available();
   1436             pduDataStream.mark(1);
   1437             temp = pduDataStream.read();
   1438             assert(-1 != temp);
   1439             pduDataStream.reset();
   1440             int first = (temp & 0xFF);
   1441 
   1442             if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
   1443                 contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1444             } else if (first > TEXT_MAX) {
   1445                 int index = parseShortInteger(pduDataStream);
   1446 
   1447                 if (index < PduContentTypes.contentTypes.length) { //well-known type
   1448                     contentType = (PduContentTypes.contentTypes[index]).getBytes();
   1449                 } else {
   1450                     pduDataStream.reset();
   1451                     contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1452                 }
   1453             } else {
   1454                 Log.e(LOG_TAG, "Corrupt content-type");
   1455                 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
   1456             }
   1457 
   1458             int endPos = pduDataStream.available();
   1459             int parameterLen = length - (startPos - endPos);
   1460             if (parameterLen > 0) {//have parameters
   1461                 parseContentTypeParams(pduDataStream, map, parameterLen);
   1462             }
   1463 
   1464             if (parameterLen < 0) {
   1465                 Log.e(LOG_TAG, "Corrupt MMS message");
   1466                 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
   1467             }
   1468         } else if (cur <= TEXT_MAX) {
   1469             contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1470         } else {
   1471             contentType =
   1472                 (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
   1473         }
   1474 
   1475         return contentType;
   1476     }
   1477 
   1478     /**
   1479      * Parse part's headers.
   1480      *
   1481      * @param pduDataStream pdu data input stream
   1482      * @param part to store the header informations of the part
   1483      * @param length length of the headers
   1484      * @return true if parse successfully, false otherwise
   1485      */
   1486     protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
   1487             PduPart part, int length) {
   1488         assert(null != pduDataStream);
   1489         assert(null != part);
   1490         assert(length > 0);
   1491 
   1492         /**
   1493          * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
   1494          * A name for multipart object SHALL be encoded using name-parameter
   1495          * for Content-Type header in WSP multipart headers.
   1496          * In decoding, name-parameter of Content-Type SHALL be used if available.
   1497          * If name-parameter of Content-Type is not available,
   1498          * filename parameter of Content-Disposition header SHALL be used if available.
   1499          * If neither name-parameter of Content-Type header nor filename parameter
   1500          * of Content-Disposition header is available,
   1501          * Content-Location header SHALL be used if available.
   1502          *
   1503          * Within SMIL part the reference to the media object parts SHALL use
   1504          * either Content-ID or Content-Location mechanism [RFC2557]
   1505          * and the corresponding WSP part headers in media object parts
   1506          * contain the corresponding definitions.
   1507          */
   1508         int startPos = pduDataStream.available();
   1509         int tempPos = 0;
   1510         int lastLen = length;
   1511         while(0 < lastLen) {
   1512             int header = pduDataStream.read();
   1513             assert(-1 != header);
   1514             lastLen--;
   1515 
   1516             if (header > TEXT_MAX) {
   1517                 // Number assigned headers.
   1518                 switch (header) {
   1519                     case PduPart.P_CONTENT_LOCATION:
   1520                         /**
   1521                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1522                          * Content-location-value = Uri-value
   1523                          */
   1524                         byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1525                         if (null != contentLocation) {
   1526                             part.setContentLocation(contentLocation);
   1527                         }
   1528 
   1529                         tempPos = pduDataStream.available();
   1530                         lastLen = length - (startPos - tempPos);
   1531                         break;
   1532                     case PduPart.P_CONTENT_ID:
   1533                         /**
   1534                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1535                          * Content-ID-value = Quoted-string
   1536                          */
   1537                         byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
   1538                         if (null != contentId) {
   1539                             part.setContentId(contentId);
   1540                         }
   1541 
   1542                         tempPos = pduDataStream.available();
   1543                         lastLen = length - (startPos - tempPos);
   1544                         break;
   1545                     case PduPart.P_DEP_CONTENT_DISPOSITION:
   1546                     case PduPart.P_CONTENT_DISPOSITION:
   1547                         /**
   1548                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1549                          * Content-disposition-value = Value-length Disposition *(Parameter)
   1550                          * Disposition = Form-data | Attachment | Inline | Token-text
   1551                          * Form-data = <Octet 128>
   1552                          * Attachment = <Octet 129>
   1553                          * Inline = <Octet 130>
   1554                          */
   1555 
   1556                         /*
   1557                          * some carrier mmsc servers do not support content_disposition
   1558                          * field correctly
   1559                          */
   1560                         boolean contentDisposition = Resources.getSystem().getBoolean(com
   1561                                 .android.internal.R.bool.config_mms_content_disposition_support);
   1562 
   1563                         if (contentDisposition) {
   1564                             int len = parseValueLength(pduDataStream);
   1565                             pduDataStream.mark(1);
   1566                             int thisStartPos = pduDataStream.available();
   1567                             int thisEndPos = 0;
   1568                             int value = pduDataStream.read();
   1569 
   1570                             if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
   1571                                 part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
   1572                             } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
   1573                                 part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
   1574                             } else if (value == PduPart.P_DISPOSITION_INLINE) {
   1575                                 part.setContentDisposition(PduPart.DISPOSITION_INLINE);
   1576                             } else {
   1577                                 pduDataStream.reset();
   1578                                 /* Token-text */
   1579                                 part.setContentDisposition(parseWapString(pduDataStream
   1580                                         , TYPE_TEXT_STRING));
   1581                             }
   1582 
   1583                             /* get filename parameter and skip other parameters */
   1584                             thisEndPos = pduDataStream.available();
   1585                             if (thisStartPos - thisEndPos < len) {
   1586                                 value = pduDataStream.read();
   1587                                 if (value == PduPart.P_FILENAME) { //filename is text-string
   1588                                     part.setFilename(parseWapString(pduDataStream
   1589                                             , TYPE_TEXT_STRING));
   1590                                 }
   1591 
   1592                                 /* skip other parameters */
   1593                                 thisEndPos = pduDataStream.available();
   1594                                 if (thisStartPos - thisEndPos < len) {
   1595                                     int last = len - (thisStartPos - thisEndPos);
   1596                                     byte[] temp = new byte[last];
   1597                                     pduDataStream.read(temp, 0, last);
   1598                                 }
   1599                             }
   1600 
   1601                             tempPos = pduDataStream.available();
   1602                             lastLen = length - (startPos - tempPos);
   1603                         }
   1604                         break;
   1605                     default:
   1606                         if (LOCAL_LOGV) {
   1607                             Log.v(LOG_TAG, "Not supported Part headers: " + header);
   1608                         }
   1609                     if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1610                         Log.e(LOG_TAG, "Corrupt Part headers");
   1611                         return false;
   1612                     }
   1613                     lastLen = 0;
   1614                     break;
   1615                 }
   1616             } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
   1617                 // Not assigned header.
   1618                 byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1619                 byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1620 
   1621                 // Check the header whether it is "Content-Transfer-Encoding".
   1622                 if (true ==
   1623                     PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
   1624                     part.setContentTransferEncoding(tempValue);
   1625                 }
   1626 
   1627                 tempPos = pduDataStream.available();
   1628                 lastLen = length - (startPos - tempPos);
   1629             } else {
   1630                 if (LOCAL_LOGV) {
   1631                     Log.v(LOG_TAG, "Not supported Part headers: " + header);
   1632                 }
   1633                 // Skip all headers of this part.
   1634                 if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1635                     Log.e(LOG_TAG, "Corrupt Part headers");
   1636                     return false;
   1637                 }
   1638                 lastLen = 0;
   1639             }
   1640         }
   1641 
   1642         if (0 != lastLen) {
   1643             Log.e(LOG_TAG, "Corrupt Part headers");
   1644             return false;
   1645         }
   1646 
   1647         return true;
   1648     }
   1649 
   1650     /**
   1651      * Check the position of a specified part.
   1652      *
   1653      * @param part the part to be checked
   1654      * @return part position, THE_FIRST_PART when it's the
   1655      * first one, THE_LAST_PART when it's the last one.
   1656      */
   1657     private static int checkPartPosition(PduPart part) {
   1658         assert(null != part);
   1659         if ((null == mTypeParam) &&
   1660                 (null == mStartParam)) {
   1661             return THE_LAST_PART;
   1662         }
   1663 
   1664         /* check part's content-id */
   1665         if (null != mStartParam) {
   1666             byte[] contentId = part.getContentId();
   1667             if (null != contentId) {
   1668                 if (true == Arrays.equals(mStartParam, contentId)) {
   1669                     return THE_FIRST_PART;
   1670                 }
   1671             }
   1672         }
   1673 
   1674         /* check part's content-type */
   1675         if (null != mTypeParam) {
   1676             byte[] contentType = part.getContentType();
   1677             if (null != contentType) {
   1678                 if (true == Arrays.equals(mTypeParam, contentType)) {
   1679                     return THE_FIRST_PART;
   1680                 }
   1681             }
   1682         }
   1683 
   1684         return THE_LAST_PART;
   1685     }
   1686 
   1687     /**
   1688      * Check mandatory headers of a pdu.
   1689      *
   1690      * @param headers pdu headers
   1691      * @return true if the pdu has all of the mandatory headers, false otherwise.
   1692      */
   1693     protected static boolean checkMandatoryHeader(PduHeaders headers) {
   1694         if (null == headers) {
   1695             return false;
   1696         }
   1697 
   1698         /* get message type */
   1699         int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
   1700 
   1701         /* check Mms-Version field */
   1702         int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
   1703         if (0 == mmsVersion) {
   1704             // Every message should have Mms-Version field.
   1705             return false;
   1706         }
   1707 
   1708         /* check mandatory header fields */
   1709         switch (messageType) {
   1710             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
   1711                 // Content-Type field.
   1712                 byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
   1713                 if (null == srContentType) {
   1714                     return false;
   1715                 }
   1716 
   1717                 // From field.
   1718                 EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   1719                 if (null == srFrom) {
   1720                     return false;
   1721                 }
   1722 
   1723                 // Transaction-Id field.
   1724                 byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1725                 if (null == srTransactionId) {
   1726                     return false;
   1727                 }
   1728 
   1729                 break;
   1730             case PduHeaders.MESSAGE_TYPE_SEND_CONF:
   1731                 // Response-Status field.
   1732                 int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
   1733                 if (0 == scResponseStatus) {
   1734                     return false;
   1735                 }
   1736 
   1737                 // Transaction-Id field.
   1738                 byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1739                 if (null == scTransactionId) {
   1740                     return false;
   1741                 }
   1742 
   1743                 break;
   1744             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
   1745                 // Content-Location field.
   1746                 byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
   1747                 if (null == niContentLocation) {
   1748                     return false;
   1749                 }
   1750 
   1751                 // Expiry field.
   1752                 long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
   1753                 if (-1 == niExpiry) {
   1754                     return false;
   1755                 }
   1756 
   1757                 // Message-Class field.
   1758                 byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
   1759                 if (null == niMessageClass) {
   1760                     return false;
   1761                 }
   1762 
   1763                 // Message-Size field.
   1764                 long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
   1765                 if (-1 == niMessageSize) {
   1766                     return false;
   1767                 }
   1768 
   1769                 // Transaction-Id field.
   1770                 byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1771                 if (null == niTransactionId) {
   1772                     return false;
   1773                 }
   1774 
   1775                 break;
   1776             case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
   1777                 // Status field.
   1778                 int nriStatus = headers.getOctet(PduHeaders.STATUS);
   1779                 if (0 == nriStatus) {
   1780                     return false;
   1781                 }
   1782 
   1783                 // Transaction-Id field.
   1784                 byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1785                 if (null == nriTransactionId) {
   1786                     return false;
   1787                 }
   1788 
   1789                 break;
   1790             case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
   1791                 // Content-Type field.
   1792                 byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
   1793                 if (null == rcContentType) {
   1794                     return false;
   1795                 }
   1796 
   1797                 // Date field.
   1798                 long rcDate = headers.getLongInteger(PduHeaders.DATE);
   1799                 if (-1 == rcDate) {
   1800                     return false;
   1801                 }
   1802 
   1803                 break;
   1804             case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
   1805                 // Date field.
   1806                 long diDate = headers.getLongInteger(PduHeaders.DATE);
   1807                 if (-1 == diDate) {
   1808                     return false;
   1809                 }
   1810 
   1811                 // Message-Id field.
   1812                 byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   1813                 if (null == diMessageId) {
   1814                     return false;
   1815                 }
   1816 
   1817                 // Status field.
   1818                 int diStatus = headers.getOctet(PduHeaders.STATUS);
   1819                 if (0 == diStatus) {
   1820                     return false;
   1821                 }
   1822 
   1823                 // To field.
   1824                 EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
   1825                 if (null == diTo) {
   1826                     return false;
   1827                 }
   1828 
   1829                 break;
   1830             case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
   1831                 // Transaction-Id field.
   1832                 byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1833                 if (null == aiTransactionId) {
   1834                     return false;
   1835                 }
   1836 
   1837                 break;
   1838             case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
   1839                 // Date field.
   1840                 long roDate = headers.getLongInteger(PduHeaders.DATE);
   1841                 if (-1 == roDate) {
   1842                     return false;
   1843                 }
   1844 
   1845                 // From field.
   1846                 EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   1847                 if (null == roFrom) {
   1848                     return false;
   1849                 }
   1850 
   1851                 // Message-Id field.
   1852                 byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   1853                 if (null == roMessageId) {
   1854                     return false;
   1855                 }
   1856 
   1857                 // Read-Status field.
   1858                 int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
   1859                 if (0 == roReadStatus) {
   1860                     return false;
   1861                 }
   1862 
   1863                 // To field.
   1864                 EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
   1865                 if (null == roTo) {
   1866                     return false;
   1867                 }
   1868 
   1869                 break;
   1870             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
   1871                 // From field.
   1872                 EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   1873                 if (null == rrFrom) {
   1874                     return false;
   1875                 }
   1876 
   1877                 // Message-Id field.
   1878                 byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   1879                 if (null == rrMessageId) {
   1880                     return false;
   1881                 }
   1882 
   1883                 // Read-Status field.
   1884                 int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
   1885                 if (0 == rrReadStatus) {
   1886                     return false;
   1887                 }
   1888 
   1889                 // To field.
   1890                 EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
   1891                 if (null == rrTo) {
   1892                     return false;
   1893                 }
   1894 
   1895                 break;
   1896             default:
   1897                 // Parser doesn't support this message type in this version.
   1898                 return false;
   1899         }
   1900 
   1901         return true;
   1902     }
   1903 }
   1904