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