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