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