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