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