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