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