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