VCardParserImpl_V21.java revision 4199c54c527330ac01699b176e7bca186a3aa3a4
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.vcard; 17 18import android.util.Log; 19 20import com.android.vcard.exception.VCardAgentNotSupportedException; 21import com.android.vcard.exception.VCardException; 22import com.android.vcard.exception.VCardInvalidCommentLineException; 23import com.android.vcard.exception.VCardInvalidLineException; 24import com.android.vcard.exception.VCardNestedException; 25import com.android.vcard.exception.VCardVersionException; 26 27import java.io.BufferedReader; 28import java.io.IOException; 29import java.io.InputStream; 30import java.io.InputStreamReader; 31import java.io.Reader; 32import java.util.ArrayList; 33import java.util.HashSet; 34import java.util.Set; 35 36/** 37 * <p> 38 * Basic implementation achieving vCard parsing. Based on vCard 2.1, 39 * </p> 40 * @hide 41 */ 42/* package */ class VCardParserImpl_V21 { 43 private static final String LOG_TAG = "VCardParserImpl_V21"; 44 45 private static final class CustomBufferedReader extends BufferedReader { 46 private long mTime; 47 48 public CustomBufferedReader(Reader in) { 49 super(in); 50 } 51 52 @Override 53 public String readLine() throws IOException { 54 long start = System.currentTimeMillis(); 55 String ret = super.readLine(); 56 long end = System.currentTimeMillis(); 57 mTime += end - start; 58 return ret; 59 } 60 61 public long getTotalmillisecond() { 62 return mTime; 63 } 64 } 65 66 private static final String DEFAULT_ENCODING = "8BIT"; 67 68 protected boolean mCanceled; 69 protected VCardInterpreter mInterpreter; 70 71 protected final String mIntermediateCharset; 72 73 /** 74 * <p> 75 * The encoding type for deconding byte streams. This member variable is 76 * reset to a default encoding every time when a new item comes. 77 * </p> 78 * <p> 79 * "Encoding" in vCard is different from "Charset". It is mainly used for 80 * addresses, notes, images. "7BIT", "8BIT", "BASE64", and 81 * "QUOTED-PRINTABLE" are known examples. 82 * </p> 83 */ 84 protected String mCurrentEncoding; 85 86 /** 87 * <p> 88 * The reader object to be used internally. 89 * </p> 90 * <p> 91 * Developers should not directly read a line from this object. Use 92 * getLine() unless there some reason. 93 * </p> 94 */ 95 protected BufferedReader mReader; 96 97 /** 98 * <p> 99 * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard 100 * specification, but happens to be seen in real world vCard. 101 * </p> 102 */ 103 protected final Set<String> mUnknownTypeSet = new HashSet<String>(); 104 105 /** 106 * <p> 107 * Set for storing unkonwn VALUE attributes, which is not acceptable in 108 * vCard specification, but happens to be seen in real world vCard. 109 * </p> 110 */ 111 protected final Set<String> mUnknownValueSet = new HashSet<String>(); 112 113 114 // In some cases, vCard is nested. Currently, we only consider the most 115 // interior vCard data. 116 // See v21_foma_1.vcf in test directory for more information. 117 // TODO: Don't ignore by using count, but read all of information outside vCard. 118 private int mNestCount; 119 120 // Used only for parsing END:VCARD. 121 private String mPreviousLine; 122 123 // For measuring performance. 124 private long mTimeTotal; 125 private long mTimeReadStartRecord; 126 private long mTimeReadEndRecord; 127 private long mTimeStartProperty; 128 private long mTimeEndProperty; 129 private long mTimeParseItems; 130 private long mTimeParseLineAndHandleGroup; 131 private long mTimeParsePropertyValues; 132 private long mTimeParseAdrOrgN; 133 private long mTimeHandleMiscPropertyValue; 134 private long mTimeHandleQuotedPrintable; 135 private long mTimeHandleBase64; 136 137 public VCardParserImpl_V21() { 138 this(VCardConfig.VCARD_TYPE_DEFAULT); 139 } 140 141 public VCardParserImpl_V21(int vcardType) { 142 if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) { 143 mNestCount = 1; 144 } 145 146 mIntermediateCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET; 147 } 148 149 /** 150 * <p> 151 * Parses the file at the given position. 152 * </p> 153 */ 154 // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre> 155 protected void parseVCardFile() throws IOException, VCardException { 156 boolean readingFirstFile = true; 157 while (true) { 158 if (mCanceled) { 159 break; 160 } 161 if (!parseOneVCard(readingFirstFile)) { 162 break; 163 } 164 readingFirstFile = false; 165 } 166 167 if (mNestCount > 0) { 168 boolean useCache = true; 169 for (int i = 0; i < mNestCount; i++) { 170 readEndVCard(useCache, true); 171 useCache = false; 172 } 173 } 174 } 175 176 /** 177 * @return true when a given property name is a valid property name. 178 */ 179 protected boolean isValidPropertyName(final String propertyName) { 180 if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) || 181 propertyName.startsWith("X-")) 182 && !mUnknownTypeSet.contains(propertyName)) { 183 mUnknownTypeSet.add(propertyName); 184 Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName); 185 } 186 return true; 187 } 188 189 /** 190 * @return String. It may be null, or its length may be 0 191 * @throws IOException 192 */ 193 protected String getLine() throws IOException { 194 return mReader.readLine(); 195 } 196 197 /** 198 * @return String with it's length > 0 199 * @throws IOException 200 * @throws VCardException when the stream reached end of line 201 */ 202 protected String getNonEmptyLine() throws IOException, VCardException { 203 String line; 204 while (true) { 205 line = getLine(); 206 if (line == null) { 207 throw new VCardException("Reached end of buffer."); 208 } else if (line.trim().length() > 0) { 209 return line; 210 } 211 } 212 } 213 214 /* 215 * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF 216 * items *CRLF 217 * "END" [ws] ":" [ws] "VCARD" 218 */ 219 private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException { 220 boolean allowGarbage = false; 221 if (firstRead) { 222 if (mNestCount > 0) { 223 for (int i = 0; i < mNestCount; i++) { 224 if (!readBeginVCard(allowGarbage)) { 225 return false; 226 } 227 allowGarbage = true; 228 } 229 } 230 } 231 232 if (!readBeginVCard(allowGarbage)) { 233 return false; 234 } 235 long start; 236 if (mInterpreter != null) { 237 start = System.currentTimeMillis(); 238 mInterpreter.startEntry(); 239 mTimeReadStartRecord += System.currentTimeMillis() - start; 240 } 241 start = System.currentTimeMillis(); 242 parseItems(); 243 mTimeParseItems += System.currentTimeMillis() - start; 244 readEndVCard(true, false); 245 if (mInterpreter != null) { 246 start = System.currentTimeMillis(); 247 mInterpreter.endEntry(); 248 mTimeReadEndRecord += System.currentTimeMillis() - start; 249 } 250 return true; 251 } 252 253 /** 254 * @return True when successful. False when reaching the end of line 255 * @throws IOException 256 * @throws VCardException 257 */ 258 protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { 259 String line; 260 do { 261 while (true) { 262 line = getLine(); 263 if (line == null) { 264 return false; 265 } else if (line.trim().length() > 0) { 266 break; 267 } 268 } 269 String[] strArray = line.split(":", 2); 270 int length = strArray.length; 271 272 // Though vCard 2.1/3.0 specification does not allow lower cases, 273 // vCard file emitted by some external vCard expoter have such 274 // invalid Strings. 275 // So we allow it. 276 // e.g. BEGIN:vCard 277 if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN") 278 && strArray[1].trim().equalsIgnoreCase("VCARD")) { 279 return true; 280 } else if (!allowGarbage) { 281 if (mNestCount > 0) { 282 mPreviousLine = line; 283 return false; 284 } else { 285 throw new VCardException("Expected String \"BEGIN:VCARD\" did not come " 286 + "(Instead, \"" + line + "\" came)"); 287 } 288 } 289 } while (allowGarbage); 290 291 throw new VCardException("Reached where must not be reached."); 292 } 293 294 /** 295 * <p> 296 * The arguments useCache and allowGarbase are usually true and false 297 * accordingly when this function is called outside this function itself. 298 * </p> 299 * 300 * @param useCache When true, line is obtained from mPreviousline. 301 * Otherwise, getLine() is used. 302 * @param allowGarbage When true, ignore non "END:VCARD" line. 303 * @throws IOException 304 * @throws VCardException 305 */ 306 protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException, 307 VCardException { 308 String line; 309 do { 310 if (useCache) { 311 // Though vCard specification does not allow lower cases, 312 // some data may have them, so we allow it. 313 line = mPreviousLine; 314 } else { 315 while (true) { 316 line = getLine(); 317 if (line == null) { 318 throw new VCardException("Expected END:VCARD was not found."); 319 } else if (line.trim().length() > 0) { 320 break; 321 } 322 } 323 } 324 325 String[] strArray = line.split(":", 2); 326 if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END") 327 && strArray[1].trim().equalsIgnoreCase("VCARD")) { 328 return; 329 } else if (!allowGarbage) { 330 throw new VCardException("END:VCARD != \"" + mPreviousLine + "\""); 331 } 332 useCache = false; 333 } while (allowGarbage); 334 } 335 336 /* 337 * items = *CRLF item / item 338 */ 339 protected void parseItems() throws IOException, VCardException { 340 boolean ended = false; 341 342 if (mInterpreter != null) { 343 long start = System.currentTimeMillis(); 344 mInterpreter.startProperty(); 345 mTimeStartProperty += System.currentTimeMillis() - start; 346 } 347 ended = parseItem(); 348 if (mInterpreter != null && !ended) { 349 long start = System.currentTimeMillis(); 350 mInterpreter.endProperty(); 351 mTimeEndProperty += System.currentTimeMillis() - start; 352 } 353 354 while (!ended) { 355 // follow VCARD ,it wont reach endProperty 356 if (mInterpreter != null) { 357 long start = System.currentTimeMillis(); 358 mInterpreter.startProperty(); 359 mTimeStartProperty += System.currentTimeMillis() - start; 360 } 361 try { 362 ended = parseItem(); 363 } catch (VCardInvalidCommentLineException e) { 364 Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored."); 365 ended = false; 366 } 367 if (mInterpreter != null && !ended) { 368 long start = System.currentTimeMillis(); 369 mInterpreter.endProperty(); 370 mTimeEndProperty += System.currentTimeMillis() - start; 371 } 372 } 373 } 374 375 /* 376 * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR" 377 * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts 378 * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."] 379 * "AGENT" [params] ":" vcard CRLF 380 */ 381 protected boolean parseItem() throws IOException, VCardException { 382 mCurrentEncoding = DEFAULT_ENCODING; 383 384 final String line = getNonEmptyLine(); 385 long start = System.currentTimeMillis(); 386 387 String[] propertyNameAndValue = separateLineAndHandleGroup(line); 388 if (propertyNameAndValue == null) { 389 return true; 390 } 391 if (propertyNameAndValue.length != 2) { 392 throw new VCardInvalidLineException("Invalid line \"" + line + "\""); 393 } 394 String propertyName = propertyNameAndValue[0].toUpperCase(); 395 String propertyValue = propertyNameAndValue[1]; 396 397 mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start; 398 399 if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) { 400 start = System.currentTimeMillis(); 401 handleMultiplePropertyValue(propertyName, propertyValue); 402 mTimeParseAdrOrgN += System.currentTimeMillis() - start; 403 return false; 404 } else if (propertyName.equals("AGENT")) { 405 handleAgent(propertyValue); 406 return false; 407 } else if (isValidPropertyName(propertyName)) { 408 if (propertyName.equals("BEGIN")) { 409 if (propertyValue.equals("VCARD")) { 410 throw new VCardNestedException("This vCard has nested vCard data in it."); 411 } else { 412 throw new VCardException("Unknown BEGIN type: " + propertyValue); 413 } 414 } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) { 415 throw new VCardVersionException("Incompatible version: " + propertyValue + " != " 416 + getVersionString()); 417 } 418 start = System.currentTimeMillis(); 419 handlePropertyValue(propertyName, propertyValue); 420 mTimeParsePropertyValues += System.currentTimeMillis() - start; 421 return false; 422 } 423 424 throw new VCardException("Unknown property name: \"" + propertyName + "\""); 425 } 426 427 // For performance reason, the states for group and property name are merged into one. 428 static private final int STATE_GROUP_OR_PROPERTY_NAME = 0; 429 static private final int STATE_PARAMS = 1; 430 // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not. 431 static private final int STATE_PARAMS_IN_DQUOTE = 2; 432 433 protected String[] separateLineAndHandleGroup(String line) throws VCardException { 434 final String[] propertyNameAndValue = new String[2]; 435 final int length = line.length(); 436 if (length > 0 && line.charAt(0) == '#') { 437 throw new VCardInvalidCommentLineException(); 438 } 439 440 int state = STATE_GROUP_OR_PROPERTY_NAME; 441 int nameIndex = 0; 442 443 // This loop is developed so that we don't have to take care of bottle neck here. 444 // Refactor carefully when you need to do so. 445 for (int i = 0; i < length; i++) { 446 final char ch = line.charAt(i); 447 switch (state) { 448 case STATE_GROUP_OR_PROPERTY_NAME: { 449 if (ch == ':') { // End of a property name. 450 final String propertyName = line.substring(nameIndex, i); 451 if (propertyName.equalsIgnoreCase("END")) { 452 mPreviousLine = line; 453 return null; 454 } 455 if (mInterpreter != null) { 456 mInterpreter.propertyName(propertyName); 457 } 458 propertyNameAndValue[0] = propertyName; 459 if (i < length - 1) { 460 propertyNameAndValue[1] = line.substring(i + 1); 461 } else { 462 propertyNameAndValue[1] = ""; 463 } 464 return propertyNameAndValue; 465 } else if (ch == '.') { // Each group is followed by the dot. 466 final String groupName = line.substring(nameIndex, i); 467 if (groupName.length() == 0) { 468 Log.w(LOG_TAG, "Empty group found. Ignoring."); 469 } else if (mInterpreter != null) { 470 mInterpreter.propertyGroup(groupName); 471 } 472 nameIndex = i + 1; // Next should be another group or a property name. 473 } else if (ch == ';') { // End of property name and beginneng of parameters. 474 final String propertyName = line.substring(nameIndex, i); 475 if (propertyName.equalsIgnoreCase("END")) { 476 mPreviousLine = line; 477 return null; 478 } 479 if (mInterpreter != null) { 480 mInterpreter.propertyName(propertyName); 481 } 482 propertyNameAndValue[0] = propertyName; 483 nameIndex = i + 1; 484 state = STATE_PARAMS; // Start parameter parsing. 485 } 486 break; 487 } 488 case STATE_PARAMS: { 489 if (ch == '"') { 490 if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { 491 Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + 492 "Silently allow it"); 493 } 494 state = STATE_PARAMS_IN_DQUOTE; 495 } else if (ch == ';') { // Starts another param. 496 handleParams(line.substring(nameIndex, i)); 497 nameIndex = i + 1; 498 } else if (ch == ':') { // End of param and beginenning of values. 499 handleParams(line.substring(nameIndex, i)); 500 if (i < length - 1) { 501 propertyNameAndValue[1] = line.substring(i + 1); 502 } else { 503 propertyNameAndValue[1] = ""; 504 } 505 return propertyNameAndValue; 506 } 507 break; 508 } 509 case STATE_PARAMS_IN_DQUOTE: { 510 if (ch == '"') { 511 if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { 512 Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + 513 "Silently allow it"); 514 } 515 state = STATE_PARAMS; 516 } 517 break; 518 } 519 } 520 } 521 522 throw new VCardInvalidLineException("Invalid line: \"" + line + "\""); 523 } 524 525 /* 526 * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param / 527 * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws] 528 * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "=" 529 * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "=" 530 * [ws] word / knowntype 531 */ 532 protected void handleParams(String params) throws VCardException { 533 final String[] strArray = params.split("=", 2); 534 if (strArray.length == 2) { 535 final String paramName = strArray[0].trim().toUpperCase(); 536 String paramValue = strArray[1].trim(); 537 if (paramName.equals("TYPE")) { 538 handleType(paramValue); 539 } else if (paramName.equals("VALUE")) { 540 handleValue(paramValue); 541 } else if (paramName.equals("ENCODING")) { 542 handleEncoding(paramValue); 543 } else if (paramName.equals("CHARSET")) { 544 handleCharset(paramValue); 545 } else if (paramName.equals("LANGUAGE")) { 546 handleLanguage(paramValue); 547 } else if (paramName.startsWith("X-")) { 548 handleAnyParam(paramName, paramValue); 549 } else { 550 throw new VCardException("Unknown type \"" + paramName + "\""); 551 } 552 } else { 553 handleParamWithoutName(strArray[0]); 554 } 555 } 556 557 /** 558 * vCard 3.0 parser implementation may throw VCardException. 559 */ 560 @SuppressWarnings("unused") 561 protected void handleParamWithoutName(final String paramValue) throws VCardException { 562 handleType(paramValue); 563 } 564 565 /* 566 * ptypeval = knowntype / "X-" word 567 */ 568 protected void handleType(final String ptypeval) { 569 if (!(getKnownTypeSet().contains(ptypeval.toUpperCase()) 570 || ptypeval.startsWith("X-")) 571 && !mUnknownTypeSet.contains(ptypeval)) { 572 mUnknownTypeSet.add(ptypeval); 573 Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval)); 574 } 575 if (mInterpreter != null) { 576 mInterpreter.propertyParamType("TYPE"); 577 mInterpreter.propertyParamValue(ptypeval); 578 } 579 } 580 581 /* 582 * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word 583 */ 584 protected void handleValue(final String pvalueval) { 585 if (!(getKnownValueSet().contains(pvalueval.toUpperCase()) 586 || pvalueval.startsWith("X-") 587 || mUnknownValueSet.contains(pvalueval))) { 588 mUnknownValueSet.add(pvalueval); 589 Log.w(LOG_TAG, String.format( 590 "The value unsupported by TYPE of %s: ", getVersion(), pvalueval)); 591 } 592 if (mInterpreter != null) { 593 mInterpreter.propertyParamType("VALUE"); 594 mInterpreter.propertyParamValue(pvalueval); 595 } 596 } 597 598 /* 599 * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word 600 */ 601 protected void handleEncoding(String pencodingval) throws VCardException { 602 if (getAvailableEncodingSet().contains(pencodingval) || 603 pencodingval.startsWith("X-")) { 604 if (mInterpreter != null) { 605 mInterpreter.propertyParamType("ENCODING"); 606 mInterpreter.propertyParamValue(pencodingval); 607 } 608 mCurrentEncoding = pencodingval; 609 } else { 610 throw new VCardException("Unknown encoding \"" + pencodingval + "\""); 611 } 612 } 613 614 /** 615 * <p> 616 * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521), 617 * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc. 618 * We allow any charset. 619 * </p> 620 */ 621 protected void handleCharset(String charsetval) { 622 if (mInterpreter != null) { 623 mInterpreter.propertyParamType("CHARSET"); 624 mInterpreter.propertyParamValue(charsetval); 625 } 626 } 627 628 /** 629 * See also Section 7.1 of RFC 1521 630 */ 631 protected void handleLanguage(String langval) throws VCardException { 632 String[] strArray = langval.split("-"); 633 if (strArray.length != 2) { 634 throw new VCardException("Invalid Language: \"" + langval + "\""); 635 } 636 String tmp = strArray[0]; 637 int length = tmp.length(); 638 for (int i = 0; i < length; i++) { 639 if (!isAsciiLetter(tmp.charAt(i))) { 640 throw new VCardException("Invalid Language: \"" + langval + "\""); 641 } 642 } 643 tmp = strArray[1]; 644 length = tmp.length(); 645 for (int i = 0; i < length; i++) { 646 if (!isAsciiLetter(tmp.charAt(i))) { 647 throw new VCardException("Invalid Language: \"" + langval + "\""); 648 } 649 } 650 if (mInterpreter != null) { 651 mInterpreter.propertyParamType("LANGUAGE"); 652 mInterpreter.propertyParamValue(langval); 653 } 654 } 655 656 private boolean isAsciiLetter(char ch) { 657 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 658 return true; 659 } 660 return false; 661 } 662 663 /** 664 * Mainly for "X-" type. This accepts any kind of type without check. 665 */ 666 protected void handleAnyParam(String paramName, String paramValue) { 667 if (mInterpreter != null) { 668 mInterpreter.propertyParamType(paramName); 669 mInterpreter.propertyParamValue(paramValue); 670 } 671 } 672 673 protected void handlePropertyValue(String propertyName, String propertyValue) 674 throws IOException, VCardException { 675 final String upperEncoding = mCurrentEncoding.toUpperCase(); 676 if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) { 677 final long start = System.currentTimeMillis(); 678 final String result = getQuotedPrintable(propertyValue); 679 if (mInterpreter != null) { 680 ArrayList<String> v = new ArrayList<String>(); 681 v.add(result); 682 mInterpreter.propertyValues(v); 683 } 684 mTimeHandleQuotedPrintable += System.currentTimeMillis() - start; 685 } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64) 686 || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) { 687 final long start = System.currentTimeMillis(); 688 // It is very rare, but some BASE64 data may be so big that 689 // OutOfMemoryError occurs. To ignore such cases, use try-catch. 690 try { 691 final String result = getBase64(propertyValue); 692 if (mInterpreter != null) { 693 ArrayList<String> arrayList = new ArrayList<String>(); 694 arrayList.add(result); 695 mInterpreter.propertyValues(arrayList); 696 } 697 } catch (OutOfMemoryError error) { 698 Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!"); 699 if (mInterpreter != null) { 700 mInterpreter.propertyValues(null); 701 } 702 } 703 mTimeHandleBase64 += System.currentTimeMillis() - start; 704 } else { 705 if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") || 706 upperEncoding.startsWith("X-"))) { 707 Log.w(LOG_TAG, 708 String.format("The encoding \"%s\" is unsupported by vCard %s", 709 mCurrentEncoding, getVersionString())); 710 } 711 712 final long start = System.currentTimeMillis(); 713 if (mInterpreter != null) { 714 ArrayList<String> v = new ArrayList<String>(); 715 v.add(maybeUnescapeText(propertyValue)); 716 mInterpreter.propertyValues(v); 717 } 718 mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start; 719 } 720 } 721 722 /** 723 * <p> 724 * Parses and returns Quoted-Printable. 725 * </p> 726 * 727 * @param firstString The string following a parameter name and attributes. 728 * Example: "string" in 729 * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r". 730 * @return whole Quoted-Printable string, including a given argument and 731 * following lines. Excludes the last empty line following to Quoted 732 * Printable lines. 733 * @throws IOException 734 * @throws VCardException 735 */ 736 private String getQuotedPrintable(String firstString) throws IOException, VCardException { 737 // Specifically, there may be some padding between = and CRLF. 738 // See the following: 739 // 740 // qp-line := *(qp-segment transport-padding CRLF) 741 // qp-part transport-padding 742 // qp-segment := qp-section *(SPACE / TAB) "=" 743 // ; Maximum length of 76 characters 744 // 745 // e.g. (from RFC 2045) 746 // Now's the time = 747 // for all folk to come= 748 // to the aid of their country. 749 if (firstString.trim().endsWith("=")) { 750 // remove "transport-padding" 751 int pos = firstString.length() - 1; 752 while (firstString.charAt(pos) != '=') { 753 } 754 StringBuilder builder = new StringBuilder(); 755 builder.append(firstString.substring(0, pos + 1)); 756 builder.append("\r\n"); 757 String line; 758 while (true) { 759 line = getLine(); 760 if (line == null) { 761 throw new VCardException("File ended during parsing a Quoted-Printable String"); 762 } 763 if (line.trim().endsWith("=")) { 764 // remove "transport-padding" 765 pos = line.length() - 1; 766 while (line.charAt(pos) != '=') { 767 } 768 builder.append(line.substring(0, pos + 1)); 769 builder.append("\r\n"); 770 } else { 771 builder.append(line); 772 break; 773 } 774 } 775 return builder.toString(); 776 } else { 777 return firstString; 778 } 779 } 780 781 protected String getBase64(String firstString) throws IOException, VCardException { 782 StringBuilder builder = new StringBuilder(); 783 builder.append(firstString); 784 785 while (true) { 786 String line = getLine(); 787 if (line == null) { 788 throw new VCardException("File ended during parsing BASE64 binary"); 789 } 790 if (line.length() == 0) { 791 break; 792 } 793 builder.append(line); 794 } 795 796 return builder.toString(); 797 } 798 799 /** 800 * <p> 801 * Mainly for "ADR", "ORG", and "N" 802 * </p> 803 */ 804 /* 805 * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr, 806 * Street, Locality, Region, Postal Code, Country Name orgparts = 807 * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are 808 * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family, 809 * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III, 810 * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a 811 * semicolon in this string, it must be escaped ; with a "\" character. We 812 * do not care the number of "strnosemi" here. We are not sure whether we 813 * should add "\" CRLF to each value. We exclude them for now. 814 */ 815 protected void handleMultiplePropertyValue(String propertyName, String propertyValue) 816 throws IOException, VCardException { 817 // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some 818 // softwares/devices 819 // emit such data. 820 if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { 821 propertyValue = getQuotedPrintable(propertyValue); 822 } 823 824 if (mInterpreter != null) { 825 mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue, 826 (getVersion() == VCardConfig.FLAG_V30))); 827 } 828 } 829 830 /* 831 * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an 832 * error toward the AGENT property. 833 * // TODO: Support AGENT property. 834 * item = 835 * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws] 836 * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD" 837 */ 838 protected void handleAgent(final String propertyValue) throws VCardException { 839 if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) { 840 // Apparently invalid line seen in Windows Mobile 6.5. Ignore them. 841 return; 842 } else { 843 throw new VCardAgentNotSupportedException("AGENT Property is not supported now."); 844 } 845 } 846 847 /** 848 * For vCard 3.0. 849 */ 850 protected String maybeUnescapeText(final String text) { 851 return text; 852 } 853 854 /** 855 * Returns unescaped String if the character should be unescaped. Return 856 * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";" 857 * while "\x" should not be. 858 */ 859 protected String maybeUnescapeCharacter(final char ch) { 860 return unescapeCharacter(ch); 861 } 862 863 /* package */ static String unescapeCharacter(final char ch) { 864 // Original vCard 2.1 specification does not allow transformation 865 // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous 866 // implementation of 867 // this class allowed them, so keep it as is. 868 if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') { 869 return String.valueOf(ch); 870 } else { 871 return null; 872 } 873 } 874 875 private void showPerformanceInfo() { 876 Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms"); 877 if (mReader instanceof CustomBufferedReader) { 878 Log.d(LOG_TAG, "Total readLine time: " 879 + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms"); 880 } 881 Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord 882 + " ms"); 883 Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms"); 884 Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup 885 + " ms"); 886 Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms"); 887 Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms"); 888 Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue 889 + " ms"); 890 Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms"); 891 Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms"); 892 } 893 894 /** 895 * @return {@link VCardConfig#FLAG_V21} 896 */ 897 protected int getVersion() { 898 return VCardConfig.FLAG_V21; 899 } 900 901 /** 902 * @return {@link VCardConfig#FLAG_V30} 903 */ 904 protected String getVersionString() { 905 return VCardConstants.VERSION_V21; 906 } 907 908 protected Set<String> getKnownPropertyNameSet() { 909 return VCardParser_V21.sKnownPropertyNameSet; 910 } 911 912 protected Set<String> getKnownTypeSet() { 913 return VCardParser_V21.sKnownTypeSet; 914 } 915 916 protected Set<String> getKnownValueSet() { 917 return VCardParser_V21.sKnownValueSet; 918 } 919 920 protected Set<String> getAvailableEncodingSet() { 921 return VCardParser_V21.sAvailableEncoding; 922 } 923 924 protected String getDefaultEncoding() { 925 return DEFAULT_ENCODING; 926 } 927 928 929 public void parse(InputStream is, VCardInterpreter interpreter) 930 throws IOException, VCardException { 931 if (is == null) { 932 throw new NullPointerException("InputStream must not be null."); 933 } 934 935 final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset); 936 if (VCardConfig.showPerformanceLog()) { 937 mReader = new CustomBufferedReader(tmpReader); 938 } else { 939 mReader = new BufferedReader(tmpReader); 940 } 941 942 mInterpreter = interpreter; 943 944 final long start = System.currentTimeMillis(); 945 if (mInterpreter != null) { 946 mInterpreter.start(); 947 } 948 parseVCardFile(); 949 if (mInterpreter != null) { 950 mInterpreter.end(); 951 } 952 mTimeTotal += System.currentTimeMillis() - start; 953 954 if (VCardConfig.showPerformanceLog()) { 955 showPerformanceInfo(); 956 } 957 } 958 959 public final void cancel() { 960 mCanceled = true; 961 } 962} 963