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