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