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