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