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