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