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