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