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