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