PduParser.java revision 92010ff5cf014c0ff73ee4ad4bb169dd916a15a9
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                }
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