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