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