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