blob: f7f71ed5f921112480bf6d2b4b9bf6608ae3a734 [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;
937
938 pduDataStream.reset();
939 if (first < TEXT_MIN) {
940 parseValueLength(pduDataStream);
941
942 charset = parseShortInteger(pduDataStream); //get the "Charset"
943 }
944
945 byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
946
947 try {
948 if (0 != charset) {
949 returnValue = new EncodedStringValue(charset, textString);
950 } else {
951 returnValue = new EncodedStringValue(textString);
952 }
953 } catch(Exception e) {
954 return null;
955 }
956
957 return returnValue;
958 }
959
960 /**
961 * Parse Text-String or Quoted-String.
962 *
963 * @param pduDataStream pdu data input stream
964 * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
965 * @return the string without End-of-string in byte array
966 */
967 protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
968 int stringType) {
969 assert(null != pduDataStream);
970 /**
971 * From wap-230-wsp-20010705-a.pdf
972 * Text-string = [Quote] *TEXT End-of-string
973 * If the first character in the TEXT is in the range of 128-255,
974 * a Quote character must precede it.
975 * Otherwise the Quote character must be omitted.
976 * The Quote is not part of the contents.
977 * Quote = <Octet 127>
978 * End-of-string = <Octet 0>
979 *
980 * Quoted-string = <Octet 34> *TEXT End-of-string
981 *
982 * Token-text = Token End-of-string
983 */
984
985 // Mark supposed beginning of Text-string
986 // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
987 pduDataStream.mark(1);
988
989 // Check first char
990 int temp = pduDataStream.read();
991 assert(-1 != temp);
992 if ((TYPE_QUOTED_STRING == stringType) &&
993 (QUOTED_STRING_FLAG == temp)) {
994 // Mark again if QUOTED_STRING_FLAG and ignore it
995 pduDataStream.mark(1);
996 } else if ((TYPE_TEXT_STRING == stringType) &&
997 (QUOTE == temp)) {
998 // Mark again if QUOTE and ignore it
999 pduDataStream.mark(1);
1000 } else {
1001 // Otherwise go back to origin
1002 pduDataStream.reset();
1003 }
1004
1005 // We are now definitely at the beginning of string
1006 /**
1007 * Return *TOKEN or *TEXT (Text-String without QUOTE,
1008 * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
1009 */
1010 return getWapString(pduDataStream, stringType);
1011 }
1012
1013 /**
1014 * Check TOKEN data defined in RFC2616.
1015 * @param ch checking data
1016 * @return true when ch is TOKEN, false when ch is not TOKEN
1017 */
1018 protected static boolean isTokenCharacter(int ch) {
1019 /**
1020 * Token = 1*<any CHAR except CTLs or separators>
1021 * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
1022 * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
1023 * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
1024 * | "{"(123) | "}"(125) | SP(32) | HT(9)
1025 * CHAR = <any US-ASCII character (octets 0 - 127)>
1026 * CTL = <any US-ASCII control character
1027 * (octets 0 - 31) and DEL (127)>
1028 * SP = <US-ASCII SP, space (32)>
1029 * HT = <US-ASCII HT, horizontal-tab (9)>
1030 */
1031 if((ch < 33) || (ch > 126)) {
1032 return false;
1033 }
1034
1035 switch(ch) {
1036 case '"': /* '"' */
1037 case '(': /* '(' */
1038 case ')': /* ')' */
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 return false;
1054 }
1055
1056 return true;
1057 }
1058
1059 /**
1060 * Check TEXT data defined in RFC2616.
1061 * @param ch checking data
1062 * @return true when ch is TEXT, false when ch is not TEXT
1063 */
1064 protected static boolean isText(int ch) {
1065 /**
1066 * TEXT = <any OCTET except CTLs,
1067 * but including LWS>
1068 * CTL = <any US-ASCII control character
1069 * (octets 0 - 31) and DEL (127)>
1070 * LWS = [CRLF] 1*( SP | HT )
1071 * CRLF = CR LF
1072 * CR = <US-ASCII CR, carriage return (13)>
1073 * LF = <US-ASCII LF, linefeed (10)>
1074 */
1075 if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
1076 return true;
1077 }
1078
1079 switch(ch) {
1080 case '\t': /* '\t' */
1081 case '\n': /* '\n' */
1082 case '\r': /* '\r' */
1083 return true;
1084 }
1085
1086 return false;
1087 }
1088
1089 protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
1090 int stringType) {
1091 assert(null != pduDataStream);
1092 ByteArrayOutputStream out = new ByteArrayOutputStream();
1093 int temp = pduDataStream.read();
1094 assert(-1 != temp);
1095 while((-1 != temp) && ('\0' != temp)) {
1096 // check each of the character
1097 if (stringType == TYPE_TOKEN_STRING) {
1098 if (isTokenCharacter(temp)) {
1099 out.write(temp);
1100 }
1101 } else {
1102 if (isText(temp)) {
1103 out.write(temp);
1104 }
1105 }
1106
1107 temp = pduDataStream.read();
1108 assert(-1 != temp);
1109 }
1110
1111 if (out.size() > 0) {
1112 return out.toByteArray();
1113 }
1114
1115 return null;
1116 }
1117
1118 /**
1119 * Extract a byte value from the input stream.
1120 *
1121 * @param pduDataStream pdu data input stream
1122 * @return the byte
1123 */
1124 protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
1125 assert(null != pduDataStream);
1126 int temp = pduDataStream.read();
1127 assert(-1 != temp);
1128 return temp & 0xFF;
1129 }
1130
1131 /**
1132 * Parse Short-Integer.
1133 *
1134 * @param pduDataStream pdu data input stream
1135 * @return the byte
1136 */
1137 protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
1138 /**
1139 * From wap-230-wsp-20010705-a.pdf
1140 * Short-integer = OCTET
1141 * Integers in range 0-127 shall be encoded as a one
1142 * octet value with the most significant bit set to one (1xxx xxxx)
1143 * and with the value in the remaining least significant bits.
1144 */
1145 assert(null != pduDataStream);
1146 int temp = pduDataStream.read();
1147 assert(-1 != temp);
1148 return temp & 0x7F;
1149 }
1150
1151 /**
1152 * Parse Long-Integer.
1153 *
1154 * @param pduDataStream pdu data input stream
1155 * @return long integer
1156 */
1157 protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
1158 /**
1159 * From wap-230-wsp-20010705-a.pdf
1160 * Long-integer = Short-length Multi-octet-integer
1161 * The Short-length indicates the length of the Multi-octet-integer
1162 * Multi-octet-integer = 1*30 OCTET
1163 * The content octets shall be an unsigned integer value
1164 * with the most significant octet encoded first (big-endian representation).
1165 * The minimum number of octets must be used to encode the value.
1166 * Short-length = <Any octet 0-30>
1167 */
1168 assert(null != pduDataStream);
1169 int temp = pduDataStream.read();
1170 assert(-1 != temp);
1171 int count = temp & 0xFF;
1172
1173 if (count > LONG_INTEGER_LENGTH_MAX) {
1174 throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
1175 }
1176
1177 long result = 0;
1178
1179 for (int i = 0 ; i < count ; i++) {
1180 temp = pduDataStream.read();
1181 assert(-1 != temp);
1182 result <<= 8;
1183 result += (temp & 0xFF);
1184 }
1185
1186 return result;
1187 }
1188
1189 /**
1190 * Parse Integer-Value.
1191 *
1192 * @param pduDataStream pdu data input stream
1193 * @return long integer
1194 */
1195 protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
1196 /**
1197 * From wap-230-wsp-20010705-a.pdf
1198 * Integer-Value = Short-integer | Long-integer
1199 */
1200 assert(null != pduDataStream);
1201 pduDataStream.mark(1);
1202 int temp = pduDataStream.read();
1203 assert(-1 != temp);
1204 pduDataStream.reset();
1205 if (temp > SHORT_INTEGER_MAX) {
1206 return parseShortInteger(pduDataStream);
1207 } else {
1208 return parseLongInteger(pduDataStream);
1209 }
1210 }
1211
1212 /**
1213 * To skip length of the wap value.
1214 *
1215 * @param pduDataStream pdu data input stream
1216 * @param length area size
1217 * @return the values in this area
1218 */
1219 protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
1220 assert(null != pduDataStream);
1221 byte[] area = new byte[length];
1222 int readLen = pduDataStream.read(area, 0, length);
1223 if (readLen < length) { //The actually read length is lower than the length
1224 return -1;
1225 } else {
1226 return readLen;
1227 }
1228 }
1229
1230 /**
1231 * Parse content type parameters. For now we just support
1232 * four parameters used in mms: "type", "start", "name", "charset".
1233 *
1234 * @param pduDataStream pdu data input stream
1235 * @param map to store parameters of Content-Type field
1236 * @param length length of all the parameters
1237 */
1238 protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
1239 HashMap<Integer, Object> map, Integer length) {
1240 /**
1241 * From wap-230-wsp-20010705-a.pdf
1242 * Parameter = Typed-parameter | Untyped-parameter
1243 * Typed-parameter = Well-known-parameter-token Typed-value
1244 * the actual expected type of the value is implied by the well-known parameter
1245 * Well-known-parameter-token = Integer-value
1246 * the code values used for parameters are specified in the Assigned Numbers appendix
1247 * Typed-value = Compact-value | Text-value
1248 * In addition to the expected type, there may be no value.
1249 * If the value cannot be encoded using the expected type, it shall be encoded as text.
1250 * Compact-value = Integer-value |
1251 * Date-value | Delta-seconds-value | Q-value | Version-value |
1252 * Uri-value
1253 * Untyped-parameter = Token-text Untyped-value
1254 * the type of the value is unknown, but it shall be encoded as an integer,
1255 * if that is possible.
1256 * Untyped-value = Integer-value | Text-value
1257 */
1258 assert(null != pduDataStream);
1259 assert(length > 0);
1260
1261 int startPos = pduDataStream.available();
1262 int tempPos = 0;
1263 int lastLen = length;
1264 while(0 < lastLen) {
1265 int param = pduDataStream.read();
1266 assert(-1 != param);
1267 lastLen--;
1268
1269 switch (param) {
1270 /**
1271 * From rfc2387, chapter 3.1
1272 * The type parameter must be specified and its value is the MIME media
1273 * type of the "root" body part. It permits a MIME user agent to
1274 * determine the content-type without reference to the enclosed body
1275 * part. If the value of the type parameter and the root body part's
1276 * content-type differ then the User Agent's behavior is undefined.
1277 *
1278 * From wap-230-wsp-20010705-a.pdf
1279 * type = Constrained-encoding
1280 * Constrained-encoding = Extension-Media | Short-integer
1281 * Extension-media = *TEXT End-of-string
1282 */
1283 case PduPart.P_TYPE:
1284 case PduPart.P_CT_MR_TYPE:
1285 pduDataStream.mark(1);
1286 int first = extractByteValue(pduDataStream);
1287 pduDataStream.reset();
1288 if (first > TEXT_MAX) {
1289 // Short-integer (well-known type)
1290 int index = parseShortInteger(pduDataStream);
1291
1292 if (index < PduContentTypes.contentTypes.length) {
1293 byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
1294 map.put(PduPart.P_TYPE, type);
1295 } else {
1296 //not support this type, ignore it.
1297 }
1298 } else {
1299 // Text-String (extension-media)
1300 byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1301 if ((null != type) && (null != map)) {
1302 map.put(PduPart.P_TYPE, type);
1303 }
1304 }
1305
1306 tempPos = pduDataStream.available();
1307 lastLen = length - (startPos - tempPos);
1308 break;
1309
1310 /**
1311 * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
1312 * Start Parameter Referring to Presentation
1313 *
1314 * From rfc2387, chapter 3.2
1315 * The start parameter, if given, is the content-ID of the compound
1316 * object's "root". If not present the "root" is the first body part in
1317 * the Multipart/Related entity. The "root" is the element the
1318 * applications processes first.
1319 *
1320 * From wap-230-wsp-20010705-a.pdf
1321 * start = Text-String
1322 */
1323 case PduPart.P_START:
1324 case PduPart.P_DEP_START:
1325 byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1326 if ((null != start) && (null != map)) {
1327 map.put(PduPart.P_START, start);
1328 }
1329
1330 tempPos = pduDataStream.available();
1331 lastLen = length - (startPos - tempPos);
1332 break;
1333
1334 /**
1335 * From oma-ts-mms-conf-v1_3.pdf
1336 * In creation, the character set SHALL be either us-ascii
1337 * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
1338 * In retrieval, both us-ascii and utf-8 SHALL be supported.
1339 *
1340 * From wap-230-wsp-20010705-a.pdf
1341 * charset = Well-known-charset|Text-String
1342 * Well-known-charset = Any-charset | Integer-value
1343 * Both are encoded using values from Character Set
1344 * Assignments table in Assigned Numbers
1345 * Any-charset = <Octet 128>
1346 * Equivalent to the special RFC2616 charset value "*"
1347 */
1348 case PduPart.P_CHARSET:
1349 pduDataStream.mark(1);
1350 int firstValue = extractByteValue(pduDataStream);
1351 pduDataStream.reset();
1352 //Check first char
1353 if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
1354 (END_STRING_FLAG == firstValue)) {
1355 //Text-String (extension-charset)
1356 byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1357 try {
1358 int charsetInt = CharacterSets.getMibEnumValue(
1359 new String(charsetStr));
1360 map.put(PduPart.P_CHARSET, charsetInt);
1361 } catch (UnsupportedEncodingException e) {
1362 // Not a well-known charset, use "*".
1363 Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
1364 map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
1365 }
1366 } else {
1367 //Well-known-charset
1368 int charset = (int) parseIntegerValue(pduDataStream);
1369 if (map != null) {
1370 map.put(PduPart.P_CHARSET, charset);
1371 }
1372 }
1373
1374 tempPos = pduDataStream.available();
1375 lastLen = length - (startPos - tempPos);
1376 break;
1377
1378 /**
1379 * From oma-ts-mms-conf-v1_3.pdf
1380 * A name for multipart object SHALL be encoded using name-parameter
1381 * for Content-Type header in WSP multipart headers.
1382 *
1383 * From wap-230-wsp-20010705-a.pdf
1384 * name = Text-String
1385 */
1386 case PduPart.P_DEP_NAME:
1387 case PduPart.P_NAME:
1388 byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1389 if ((null != name) && (null != map)) {
1390 map.put(PduPart.P_NAME, name);
1391 }
1392
1393 tempPos = pduDataStream.available();
1394 lastLen = length - (startPos - tempPos);
1395 break;
1396 default:
1397 if (LOCAL_LOGV) {
1398 Log.v(LOG_TAG, "Not supported Content-Type parameter");
1399 }
1400 if (-1 == skipWapValue(pduDataStream, lastLen)) {
1401 Log.e(LOG_TAG, "Corrupt Content-Type");
1402 } else {
1403 lastLen = 0;
1404 }
1405 break;
1406 }
1407 }
1408
1409 if (0 != lastLen) {
1410 Log.e(LOG_TAG, "Corrupt Content-Type");
1411 }
1412 }
1413
1414 /**
1415 * Parse content type.
1416 *
1417 * @param pduDataStream pdu data input stream
1418 * @param map to store parameters in Content-Type header field
1419 * @return Content-Type value
1420 */
1421 protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
1422 HashMap<Integer, Object> map) {
1423 /**
1424 * From wap-230-wsp-20010705-a.pdf
1425 * Content-type-value = Constrained-media | Content-general-form
1426 * Content-general-form = Value-length Media-type
1427 * Media-type = (Well-known-media | Extension-Media) *(Parameter)
1428 */
1429 assert(null != pduDataStream);
1430
1431 byte[] contentType = null;
1432 pduDataStream.mark(1);
1433 int temp = pduDataStream.read();
1434 assert(-1 != temp);
1435 pduDataStream.reset();
1436
1437 int cur = (temp & 0xFF);
1438
1439 if (cur < TEXT_MIN) {
1440 int length = parseValueLength(pduDataStream);
1441 int startPos = pduDataStream.available();
1442 pduDataStream.mark(1);
1443 temp = pduDataStream.read();
1444 assert(-1 != temp);
1445 pduDataStream.reset();
1446 int first = (temp & 0xFF);
1447
1448 if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
1449 contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1450 } else if (first > TEXT_MAX) {
1451 int index = parseShortInteger(pduDataStream);
1452
1453 if (index < PduContentTypes.contentTypes.length) { //well-known type
1454 contentType = (PduContentTypes.contentTypes[index]).getBytes();
1455 } else {
1456 pduDataStream.reset();
1457 contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1458 }
1459 } else {
1460 Log.e(LOG_TAG, "Corrupt content-type");
1461 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
1462 }
1463
1464 int endPos = pduDataStream.available();
1465 int parameterLen = length - (startPos - endPos);
1466 if (parameterLen > 0) {//have parameters
1467 parseContentTypeParams(pduDataStream, map, parameterLen);
1468 }
1469
1470 if (parameterLen < 0) {
1471 Log.e(LOG_TAG, "Corrupt MMS message");
1472 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
1473 }
1474 } else if (cur <= TEXT_MAX) {
1475 contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1476 } else {
1477 contentType =
1478 (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
1479 }
1480
1481 return contentType;
1482 }
1483
1484 /**
1485 * Parse part's headers.
1486 *
1487 * @param pduDataStream pdu data input stream
1488 * @param part to store the header informations of the part
1489 * @param length length of the headers
1490 * @return true if parse successfully, false otherwise
1491 */
1492 protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
1493 PduPart part, int length) {
1494 assert(null != pduDataStream);
1495 assert(null != part);
1496 assert(length > 0);
1497
1498 /**
1499 * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
1500 * A name for multipart object SHALL be encoded using name-parameter
1501 * for Content-Type header in WSP multipart headers.
1502 * In decoding, name-parameter of Content-Type SHALL be used if available.
1503 * If name-parameter of Content-Type is not available,
1504 * filename parameter of Content-Disposition header SHALL be used if available.
1505 * If neither name-parameter of Content-Type header nor filename parameter
1506 * of Content-Disposition header is available,
1507 * Content-Location header SHALL be used if available.
1508 *
1509 * Within SMIL part the reference to the media object parts SHALL use
1510 * either Content-ID or Content-Location mechanism [RFC2557]
1511 * and the corresponding WSP part headers in media object parts
1512 * contain the corresponding definitions.
1513 */
1514 int startPos = pduDataStream.available();
1515 int tempPos = 0;
1516 int lastLen = length;
1517 while(0 < lastLen) {
1518 int header = pduDataStream.read();
1519 assert(-1 != header);
1520 lastLen--;
1521
1522 if (header > TEXT_MAX) {
1523 // Number assigned headers.
1524 switch (header) {
1525 case PduPart.P_CONTENT_LOCATION:
1526 /**
1527 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
1528 * Content-location-value = Uri-value
1529 */
1530 byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1531 if (null != contentLocation) {
1532 part.setContentLocation(contentLocation);
1533 }
1534
1535 tempPos = pduDataStream.available();
1536 lastLen = length - (startPos - tempPos);
1537 break;
1538 case PduPart.P_CONTENT_ID:
1539 /**
1540 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
1541 * Content-ID-value = Quoted-string
1542 */
1543 byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
1544 if (null != contentId) {
1545 part.setContentId(contentId);
1546 }
1547
1548 tempPos = pduDataStream.available();
1549 lastLen = length - (startPos - tempPos);
1550 break;
1551 case PduPart.P_DEP_CONTENT_DISPOSITION:
1552 case PduPart.P_CONTENT_DISPOSITION:
1553 /**
1554 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
1555 * Content-disposition-value = Value-length Disposition *(Parameter)
1556 * Disposition = Form-data | Attachment | Inline | Token-text
1557 * Form-data = <Octet 128>
1558 * Attachment = <Octet 129>
1559 * Inline = <Octet 130>
1560 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001561
Soojung Shinff5a0992011-03-02 09:39:35 +09001562 /*
1563 * some carrier mmsc servers do not support content_disposition
1564 * field correctly
1565 */
1566 boolean contentDisposition = Resources.getSystem().getBoolean(com
1567 .android.internal.R.bool.config_mms_content_disposition_support);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568
Soojung Shinff5a0992011-03-02 09:39:35 +09001569 if (contentDisposition) {
1570 int len = parseValueLength(pduDataStream);
1571 pduDataStream.mark(1);
1572 int thisStartPos = pduDataStream.available();
1573 int thisEndPos = 0;
1574 int value = pduDataStream.read();
1575
1576 if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
1577 part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
1578 } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
1579 part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
1580 } else if (value == PduPart.P_DISPOSITION_INLINE) {
1581 part.setContentDisposition(PduPart.DISPOSITION_INLINE);
1582 } else {
1583 pduDataStream.reset();
1584 /* Token-text */
1585 part.setContentDisposition(parseWapString(pduDataStream
1586 , TYPE_TEXT_STRING));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001587 }
1588
Soojung Shinff5a0992011-03-02 09:39:35 +09001589 /* get filename parameter and skip other parameters */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001590 thisEndPos = pduDataStream.available();
1591 if (thisStartPos - thisEndPos < len) {
Soojung Shinff5a0992011-03-02 09:39:35 +09001592 value = pduDataStream.read();
1593 if (value == PduPart.P_FILENAME) { //filename is text-string
1594 part.setFilename(parseWapString(pduDataStream
1595 , TYPE_TEXT_STRING));
1596 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001597
Soojung Shinff5a0992011-03-02 09:39:35 +09001598 /* skip other parameters */
1599 thisEndPos = pduDataStream.available();
1600 if (thisStartPos - thisEndPos < len) {
1601 int last = len - (thisStartPos - thisEndPos);
1602 byte[] temp = new byte[last];
1603 pduDataStream.read(temp, 0, last);
1604 }
1605 }
1606
1607 tempPos = pduDataStream.available();
1608 lastLen = length - (startPos - tempPos);
1609 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001610 break;
1611 default:
1612 if (LOCAL_LOGV) {
1613 Log.v(LOG_TAG, "Not supported Part headers: " + header);
1614 }
1615 if (-1 == skipWapValue(pduDataStream, lastLen)) {
1616 Log.e(LOG_TAG, "Corrupt Part headers");
1617 return false;
1618 }
1619 lastLen = 0;
1620 break;
1621 }
1622 } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
1623 // Not assigned header.
1624 byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1625 byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
1626
1627 // Check the header whether it is "Content-Transfer-Encoding".
1628 if (true ==
1629 PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
1630 part.setContentTransferEncoding(tempValue);
1631 }
1632
1633 tempPos = pduDataStream.available();
1634 lastLen = length - (startPos - tempPos);
1635 } else {
1636 if (LOCAL_LOGV) {
1637 Log.v(LOG_TAG, "Not supported Part headers: " + header);
1638 }
1639 // Skip all headers of this part.
1640 if (-1 == skipWapValue(pduDataStream, lastLen)) {
1641 Log.e(LOG_TAG, "Corrupt Part headers");
1642 return false;
1643 }
1644 lastLen = 0;
1645 }
1646 }
1647
1648 if (0 != lastLen) {
1649 Log.e(LOG_TAG, "Corrupt Part headers");
1650 return false;
1651 }
1652
1653 return true;
1654 }
1655
1656 /**
1657 * Check the position of a specified part.
1658 *
1659 * @param part the part to be checked
1660 * @return part position, THE_FIRST_PART when it's the
1661 * first one, THE_LAST_PART when it's the last one.
1662 */
1663 private static int checkPartPosition(PduPart part) {
1664 assert(null != part);
1665 if ((null == mTypeParam) &&
1666 (null == mStartParam)) {
1667 return THE_LAST_PART;
1668 }
1669
1670 /* check part's content-id */
1671 if (null != mStartParam) {
1672 byte[] contentId = part.getContentId();
1673 if (null != contentId) {
1674 if (true == Arrays.equals(mStartParam, contentId)) {
1675 return THE_FIRST_PART;
1676 }
1677 }
1678 }
1679
1680 /* check part's content-type */
1681 if (null != mTypeParam) {
1682 byte[] contentType = part.getContentType();
1683 if (null != contentType) {
1684 if (true == Arrays.equals(mTypeParam, contentType)) {
1685 return THE_FIRST_PART;
1686 }
1687 }
1688 }
1689
1690 return THE_LAST_PART;
1691 }
1692
1693 /**
1694 * Check mandatory headers of a pdu.
1695 *
1696 * @param headers pdu headers
1697 * @return true if the pdu has all of the mandatory headers, false otherwise.
1698 */
1699 protected static boolean checkMandatoryHeader(PduHeaders headers) {
1700 if (null == headers) {
1701 return false;
1702 }
1703
1704 /* get message type */
1705 int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
1706
1707 /* check Mms-Version field */
1708 int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
1709 if (0 == mmsVersion) {
1710 // Every message should have Mms-Version field.
1711 return false;
1712 }
1713
1714 /* check mandatory header fields */
1715 switch (messageType) {
1716 case PduHeaders.MESSAGE_TYPE_SEND_REQ:
1717 // Content-Type field.
1718 byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
1719 if (null == srContentType) {
1720 return false;
1721 }
1722
1723 // From field.
1724 EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
1725 if (null == srFrom) {
1726 return false;
1727 }
1728
1729 // Transaction-Id field.
1730 byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
1731 if (null == srTransactionId) {
1732 return false;
1733 }
1734
1735 break;
1736 case PduHeaders.MESSAGE_TYPE_SEND_CONF:
1737 // Response-Status field.
1738 int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
1739 if (0 == scResponseStatus) {
1740 return false;
1741 }
1742
1743 // Transaction-Id field.
1744 byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
1745 if (null == scTransactionId) {
1746 return false;
1747 }
1748
1749 break;
1750 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
1751 // Content-Location field.
1752 byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
1753 if (null == niContentLocation) {
1754 return false;
1755 }
1756
1757 // Expiry field.
1758 long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
1759 if (-1 == niExpiry) {
1760 return false;
1761 }
1762
1763 // Message-Class field.
1764 byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
1765 if (null == niMessageClass) {
1766 return false;
1767 }
1768
1769 // Message-Size field.
1770 long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
1771 if (-1 == niMessageSize) {
1772 return false;
1773 }
1774
1775 // Transaction-Id field.
1776 byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
1777 if (null == niTransactionId) {
1778 return false;
1779 }
1780
1781 break;
1782 case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
1783 // Status field.
1784 int nriStatus = headers.getOctet(PduHeaders.STATUS);
1785 if (0 == nriStatus) {
1786 return false;
1787 }
1788
1789 // Transaction-Id field.
1790 byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
1791 if (null == nriTransactionId) {
1792 return false;
1793 }
1794
1795 break;
1796 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
1797 // Content-Type field.
1798 byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
1799 if (null == rcContentType) {
1800 return false;
1801 }
1802
1803 // Date field.
1804 long rcDate = headers.getLongInteger(PduHeaders.DATE);
1805 if (-1 == rcDate) {
1806 return false;
1807 }
1808
1809 break;
1810 case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
1811 // Date field.
1812 long diDate = headers.getLongInteger(PduHeaders.DATE);
1813 if (-1 == diDate) {
1814 return false;
1815 }
1816
1817 // Message-Id field.
1818 byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
1819 if (null == diMessageId) {
1820 return false;
1821 }
1822
1823 // Status field.
1824 int diStatus = headers.getOctet(PduHeaders.STATUS);
1825 if (0 == diStatus) {
1826 return false;
1827 }
1828
1829 // To field.
1830 EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
1831 if (null == diTo) {
1832 return false;
1833 }
1834
1835 break;
1836 case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
1837 // Transaction-Id field.
1838 byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
1839 if (null == aiTransactionId) {
1840 return false;
1841 }
1842
1843 break;
1844 case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
1845 // Date field.
1846 long roDate = headers.getLongInteger(PduHeaders.DATE);
1847 if (-1 == roDate) {
1848 return false;
1849 }
1850
1851 // From field.
1852 EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
1853 if (null == roFrom) {
1854 return false;
1855 }
1856
1857 // Message-Id field.
1858 byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
1859 if (null == roMessageId) {
1860 return false;
1861 }
1862
1863 // Read-Status field.
1864 int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
1865 if (0 == roReadStatus) {
1866 return false;
1867 }
1868
1869 // To field.
1870 EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
1871 if (null == roTo) {
1872 return false;
1873 }
1874
1875 break;
1876 case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
1877 // From field.
1878 EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
1879 if (null == rrFrom) {
1880 return false;
1881 }
1882
1883 // Message-Id field.
1884 byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
1885 if (null == rrMessageId) {
1886 return false;
1887 }
1888
1889 // Read-Status field.
1890 int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
1891 if (0 == rrReadStatus) {
1892 return false;
1893 }
1894
1895 // To field.
1896 EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
1897 if (null == rrTo) {
1898 return false;
1899 }
1900
1901 break;
1902 default:
1903 // Parser doesn't support this message type in this version.
1904 return false;
1905 }
1906
1907 return true;
1908 }
1909}