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