blob: 5340245ae869b03546100993874990bcf6f6d0ce [file] [log] [blame]
Amit Mahajan82f245f2019-09-10 13:19:05 -07001/*
2 * Copyright (C) 2007-2008 Esmertec AG.
3 * Copyright (C) 2007-2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.google.android.mms.pdu;
19
Artur Satayev9a5c3102020-01-23 16:49:36 +000020import android.compat.annotation.UnsupportedAppUsage;
Amit Mahajan82f245f2019-09-10 13:19:05 -070021import android.util.Log;
22
Amit Mahajan82f245f2019-09-10 13:19:05 -070023import com.google.android.mms.ContentType;
24import com.google.android.mms.InvalidHeaderValueException;
25
26import java.io.ByteArrayInputStream;
27import java.io.ByteArrayOutputStream;
28import java.io.UnsupportedEncodingException;
29import java.util.Arrays;
30import java.util.HashMap;
31
32public 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
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
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 */
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 */
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
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
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
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 */
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
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 */
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 */
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
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 */
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
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 */
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 */
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 */
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 */
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
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
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
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 */
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}