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