VCardParserImpl_V21.java revision 58610106ce61adad9b1caa1fe9f7925c3e938bab
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 if (mInterpreter != null) { 356 long start = System.currentTimeMillis(); 357 mInterpreter.startProperty(); 358 mTimeStartProperty += System.currentTimeMillis() - start; 359 } 360 try { 361 ended = parseItem(); 362 } catch (VCardInvalidCommentLineException e) { 363 Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored."); 364 ended = false; 365 } 366 if (mInterpreter != null && !ended) { 367 long start = System.currentTimeMillis(); 368 mInterpreter.endProperty(); 369 mTimeEndProperty += System.currentTimeMillis() - start; 370 } 371 } 372 } 373 374 /* 375 * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR" 376 * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts 377 * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."] 378 * "AGENT" [params] ":" vcard CRLF 379 */ 380 protected boolean parseItem() throws IOException, VCardException { 381 mCurrentEncoding = DEFAULT_ENCODING; 382 383 final String line = getNonEmptyLine(); 384 long start = System.currentTimeMillis(); 385 386 String[] propertyNameAndValue = separateLineAndHandleGroup(line); 387 if (propertyNameAndValue == null) { 388 return true; 389 } 390 if (propertyNameAndValue.length != 2) { 391 throw new VCardInvalidLineException("Invalid line \"" + line + "\""); 392 } 393 String propertyName = propertyNameAndValue[0].toUpperCase(); 394 String propertyValue = propertyNameAndValue[1]; 395 396 mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start; 397 398 if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) { 399 start = System.currentTimeMillis(); 400 handleMultiplePropertyValue(propertyName, propertyValue); 401 mTimeParseAdrOrgN += System.currentTimeMillis() - start; 402 return false; 403 } else if (propertyName.equals("AGENT")) { 404 handleAgent(propertyValue); 405 return false; 406 } else if (isValidPropertyName(propertyName)) { 407 if (propertyName.equals("BEGIN")) { 408 if (propertyValue.equals("VCARD")) { 409 throw new VCardNestedException("This vCard has nested vCard data in it."); 410 } else { 411 throw new VCardException("Unknown BEGIN type: " + propertyValue); 412 } 413 } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) { 414 throw new VCardVersionException("Incompatible version: " + propertyValue + " != " 415 + getVersionString()); 416 } 417 start = System.currentTimeMillis(); 418 handlePropertyValue(propertyName, propertyValue); 419 mTimeParsePropertyValues += System.currentTimeMillis() - start; 420 return false; 421 } 422 423 throw new VCardException("Unknown property name: \"" + propertyName + "\""); 424 } 425 426 // For performance reason, the states for group and property name are merged into one. 427 static private final int STATE_GROUP_OR_PROPERTY_NAME = 0; 428 static private final int STATE_PARAMS = 1; 429 // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not. 430 static private final int STATE_PARAMS_IN_DQUOTE = 2; 431 432 protected String[] separateLineAndHandleGroup(String line) throws VCardException { 433 final String[] propertyNameAndValue = new String[2]; 434 final int length = line.length(); 435 if (length > 0 && line.charAt(0) == '#') { 436 throw new VCardInvalidCommentLineException(); 437 } 438 439 int state = STATE_GROUP_OR_PROPERTY_NAME; 440 int nameIndex = 0; 441 442 // This loop is developed so that we don't have to take care of bottle neck here. 443 // Refactor carefully when you need to do so. 444 for (int i = 0; i < length; i++) { 445 final char ch = line.charAt(i); 446 switch (state) { 447 case STATE_GROUP_OR_PROPERTY_NAME: { 448 if (ch == ':') { // End of a property name. 449 final String propertyName = line.substring(nameIndex, i); 450 if (propertyName.equalsIgnoreCase("END")) { 451 mPreviousLine = line; 452 return null; 453 } 454 if (mInterpreter != null) { 455 mInterpreter.propertyName(propertyName); 456 } 457 propertyNameAndValue[0] = propertyName; 458 if (i < length - 1) { 459 propertyNameAndValue[1] = line.substring(i + 1); 460 } else { 461 propertyNameAndValue[1] = ""; 462 } 463 return propertyNameAndValue; 464 } else if (ch == '.') { // Each group is followed by the dot. 465 final String groupName = line.substring(nameIndex, i); 466 if (groupName.length() == 0) { 467 Log.w(LOG_TAG, "Empty group found. Ignoring."); 468 } else if (mInterpreter != null) { 469 mInterpreter.propertyGroup(groupName); 470 } 471 nameIndex = i + 1; // Next should be another group or a property name. 472 } else if (ch == ';') { // End of property name and beginneng of parameters. 473 final String propertyName = line.substring(nameIndex, i); 474 if (propertyName.equalsIgnoreCase("END")) { 475 mPreviousLine = line; 476 return null; 477 } 478 if (mInterpreter != null) { 479 mInterpreter.propertyName(propertyName); 480 } 481 propertyNameAndValue[0] = propertyName; 482 nameIndex = i + 1; 483 state = STATE_PARAMS; // Start parameter parsing. 484 } 485 break; 486 } 487 case STATE_PARAMS: { 488 if (ch == '"') { 489 if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { 490 Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + 491 "Silently allow it"); 492 } 493 state = STATE_PARAMS_IN_DQUOTE; 494 } else if (ch == ';') { // Starts another param. 495 handleParams(line.substring(nameIndex, i)); 496 nameIndex = i + 1; 497 } else if (ch == ':') { // End of param and beginenning of values. 498 handleParams(line.substring(nameIndex, i)); 499 if (i < length - 1) { 500 propertyNameAndValue[1] = line.substring(i + 1); 501 } else { 502 propertyNameAndValue[1] = ""; 503 } 504 return propertyNameAndValue; 505 } 506 break; 507 } 508 case STATE_PARAMS_IN_DQUOTE: { 509 if (ch == '"') { 510 if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { 511 Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + 512 "Silently allow it"); 513 } 514 state = STATE_PARAMS; 515 } 516 break; 517 } 518 } 519 } 520 521 throw new VCardInvalidLineException("Invalid line: \"" + line + "\""); 522 } 523 524 /* 525 * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param / 526 * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws] 527 * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "=" 528 * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "=" 529 * [ws] word / knowntype 530 */ 531 protected void handleParams(String params) throws VCardException { 532 final String[] strArray = params.split("=", 2); 533 if (strArray.length == 2) { 534 final String paramName = strArray[0].trim().toUpperCase(); 535 String paramValue = strArray[1].trim(); 536 if (paramName.equals("TYPE")) { 537 handleType(paramValue); 538 } else if (paramName.equals("VALUE")) { 539 handleValue(paramValue); 540 } else if (paramName.equals("ENCODING")) { 541 handleEncoding(paramValue); 542 } else if (paramName.equals("CHARSET")) { 543 handleCharset(paramValue); 544 } else if (paramName.equals("LANGUAGE")) { 545 handleLanguage(paramValue); 546 } else if (paramName.startsWith("X-")) { 547 handleAnyParam(paramName, paramValue); 548 } else { 549 throw new VCardException("Unknown type \"" + paramName + "\""); 550 } 551 } else { 552 handleParamWithoutName(strArray[0]); 553 } 554 } 555 556 /** 557 * vCard 3.0 parser implementation may throw VCardException. 558 */ 559 @SuppressWarnings("unused") 560 protected void handleParamWithoutName(final String paramValue) throws VCardException { 561 handleType(paramValue); 562 } 563 564 /* 565 * ptypeval = knowntype / "X-" word 566 */ 567 protected void handleType(final String ptypeval) { 568 if (!(getKnownTypeSet().contains(ptypeval.toUpperCase()) 569 || ptypeval.startsWith("X-")) 570 && !mUnknownTypeSet.contains(ptypeval)) { 571 mUnknownTypeSet.add(ptypeval); 572 Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval)); 573 } 574 if (mInterpreter != null) { 575 mInterpreter.propertyParamType("TYPE"); 576 mInterpreter.propertyParamValue(ptypeval); 577 } 578 } 579 580 /* 581 * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word 582 */ 583 protected void handleValue(final String pvalueval) { 584 if (!(getKnownValueSet().contains(pvalueval.toUpperCase()) 585 || pvalueval.startsWith("X-") 586 || mUnknownValueSet.contains(pvalueval))) { 587 mUnknownValueSet.add(pvalueval); 588 Log.w(LOG_TAG, String.format( 589 "The value unsupported by TYPE of %s: ", getVersion(), pvalueval)); 590 } 591 if (mInterpreter != null) { 592 mInterpreter.propertyParamType("VALUE"); 593 mInterpreter.propertyParamValue(pvalueval); 594 } 595 } 596 597 /* 598 * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word 599 */ 600 protected void handleEncoding(String pencodingval) throws VCardException { 601 if (getAvailableEncodingSet().contains(pencodingval) || 602 pencodingval.startsWith("X-")) { 603 if (mInterpreter != null) { 604 mInterpreter.propertyParamType("ENCODING"); 605 mInterpreter.propertyParamValue(pencodingval); 606 } 607 mCurrentEncoding = pencodingval; 608 } else { 609 throw new VCardException("Unknown encoding \"" + pencodingval + "\""); 610 } 611 } 612 613 /** 614 * <p> 615 * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521), 616 * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc. 617 * We allow any charset. 618 * </p> 619 */ 620 protected void handleCharset(String charsetval) { 621 if (mInterpreter != null) { 622 mInterpreter.propertyParamType("CHARSET"); 623 mInterpreter.propertyParamValue(charsetval); 624 } 625 } 626 627 /** 628 * See also Section 7.1 of RFC 1521 629 */ 630 protected void handleLanguage(String langval) throws VCardException { 631 String[] strArray = langval.split("-"); 632 if (strArray.length != 2) { 633 throw new VCardException("Invalid Language: \"" + langval + "\""); 634 } 635 String tmp = strArray[0]; 636 int length = tmp.length(); 637 for (int i = 0; i < length; i++) { 638 if (!isAsciiLetter(tmp.charAt(i))) { 639 throw new VCardException("Invalid Language: \"" + langval + "\""); 640 } 641 } 642 tmp = strArray[1]; 643 length = tmp.length(); 644 for (int i = 0; i < length; i++) { 645 if (!isAsciiLetter(tmp.charAt(i))) { 646 throw new VCardException("Invalid Language: \"" + langval + "\""); 647 } 648 } 649 if (mInterpreter != null) { 650 mInterpreter.propertyParamType("LANGUAGE"); 651 mInterpreter.propertyParamValue(langval); 652 } 653 } 654 655 private boolean isAsciiLetter(char ch) { 656 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 657 return true; 658 } 659 return false; 660 } 661 662 /** 663 * Mainly for "X-" type. This accepts any kind of type without check. 664 */ 665 protected void handleAnyParam(String paramName, String paramValue) { 666 if (mInterpreter != null) { 667 mInterpreter.propertyParamType(paramName); 668 mInterpreter.propertyParamValue(paramValue); 669 } 670 } 671 672 protected void handlePropertyValue(String propertyName, String propertyValue) 673 throws IOException, VCardException { 674 final String upperEncoding = mCurrentEncoding.toUpperCase(); 675 if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) { 676 final long start = System.currentTimeMillis(); 677 final String result = getQuotedPrintable(propertyValue); 678 if (mInterpreter != null) { 679 ArrayList<String> v = new ArrayList<String>(); 680 v.add(result); 681 mInterpreter.propertyValues(v); 682 } 683 mTimeHandleQuotedPrintable += System.currentTimeMillis() - start; 684 } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64) 685 || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) { 686 final long start = System.currentTimeMillis(); 687 // It is very rare, but some BASE64 data may be so big that 688 // OutOfMemoryError occurs. To ignore such cases, use try-catch. 689 try { 690 final String result = getBase64(propertyValue); 691 if (mInterpreter != null) { 692 ArrayList<String> arrayList = new ArrayList<String>(); 693 arrayList.add(result); 694 mInterpreter.propertyValues(arrayList); 695 } 696 } catch (OutOfMemoryError error) { 697 Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!"); 698 if (mInterpreter != null) { 699 mInterpreter.propertyValues(null); 700 } 701 } 702 mTimeHandleBase64 += System.currentTimeMillis() - start; 703 } else { 704 if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") || 705 upperEncoding.startsWith("X-"))) { 706 Log.w(LOG_TAG, 707 String.format("The encoding \"%s\" is unsupported by vCard %s", 708 mCurrentEncoding, getVersionString())); 709 } 710 711 final long start = System.currentTimeMillis(); 712 if (mInterpreter != null) { 713 ArrayList<String> v = new ArrayList<String>(); 714 v.add(maybeUnescapeText(propertyValue)); 715 mInterpreter.propertyValues(v); 716 } 717 mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start; 718 } 719 } 720 721 /** 722 * <p> 723 * Parses and returns Quoted-Printable. 724 * </p> 725 * 726 * @param firstString The string following a parameter name and attributes. 727 * Example: "string" in 728 * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r". 729 * @return whole Quoted-Printable string, including a given argument and 730 * following lines. Excludes the last empty line following to Quoted 731 * Printable lines. 732 * @throws IOException 733 * @throws VCardException 734 */ 735 private String getQuotedPrintable(String firstString) throws IOException, VCardException { 736 // Specifically, there may be some padding between = and CRLF. 737 // See the following: 738 // 739 // qp-line := *(qp-segment transport-padding CRLF) 740 // qp-part transport-padding 741 // qp-segment := qp-section *(SPACE / TAB) "=" 742 // ; Maximum length of 76 characters 743 // 744 // e.g. (from RFC 2045) 745 // Now's the time = 746 // for all folk to come= 747 // to the aid of their country. 748 if (firstString.trim().endsWith("=")) { 749 // remove "transport-padding" 750 int pos = firstString.length() - 1; 751 while (firstString.charAt(pos) != '=') { 752 } 753 StringBuilder builder = new StringBuilder(); 754 builder.append(firstString.substring(0, pos + 1)); 755 builder.append("\r\n"); 756 String line; 757 while (true) { 758 line = getLine(); 759 if (line == null) { 760 throw new VCardException("File ended during parsing a Quoted-Printable String"); 761 } 762 if (line.trim().endsWith("=")) { 763 // remove "transport-padding" 764 pos = line.length() - 1; 765 while (line.charAt(pos) != '=') { 766 } 767 builder.append(line.substring(0, pos + 1)); 768 builder.append("\r\n"); 769 } else { 770 builder.append(line); 771 break; 772 } 773 } 774 return builder.toString(); 775 } else { 776 return firstString; 777 } 778 } 779 780 protected String getBase64(String firstString) throws IOException, VCardException { 781 StringBuilder builder = new StringBuilder(); 782 builder.append(firstString); 783 784 while (true) { 785 String line = getLine(); 786 if (line == null) { 787 throw new VCardException("File ended during parsing BASE64 binary"); 788 } 789 if (line.length() == 0) { 790 break; 791 } 792 builder.append(line); 793 } 794 795 return builder.toString(); 796 } 797 798 /** 799 * <p> 800 * Mainly for "ADR", "ORG", and "N" 801 * </p> 802 */ 803 /* 804 * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr, 805 * Street, Locality, Region, Postal Code, Country Name orgparts = 806 * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are 807 * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family, 808 * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III, 809 * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a 810 * semicolon in this string, it must be escaped ; with a "\" character. We 811 * do not care the number of "strnosemi" here. We are not sure whether we 812 * should add "\" CRLF to each value. We exclude them for now. 813 */ 814 protected void handleMultiplePropertyValue(String propertyName, String propertyValue) 815 throws IOException, VCardException { 816 // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some 817 // softwares/devices 818 // emit such data. 819 if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { 820 propertyValue = getQuotedPrintable(propertyValue); 821 } 822 823 if (mInterpreter != null) { 824 mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue, 825 (getVersion() == VCardConfig.FLAG_V30))); 826 } 827 } 828 829 /* 830 * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an 831 * error toward the AGENT property. 832 * // TODO: Support AGENT property. 833 * item = 834 * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws] 835 * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD" 836 */ 837 protected void handleAgent(final String propertyValue) throws VCardException { 838 if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) { 839 // Apparently invalid line seen in Windows Mobile 6.5. Ignore them. 840 return; 841 } else { 842 throw new VCardAgentNotSupportedException("AGENT Property is not supported now."); 843 } 844 } 845 846 /** 847 * For vCard 3.0. 848 */ 849 protected String maybeUnescapeText(final String text) { 850 return text; 851 } 852 853 /** 854 * Returns unescaped String if the character should be unescaped. Return 855 * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";" 856 * while "\x" should not be. 857 */ 858 protected String maybeUnescapeCharacter(final char ch) { 859 return unescapeCharacter(ch); 860 } 861 862 /* package */ static String unescapeCharacter(final char ch) { 863 // Original vCard 2.1 specification does not allow transformation 864 // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous 865 // implementation of 866 // this class allowed them, so keep it as is. 867 if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') { 868 return String.valueOf(ch); 869 } else { 870 return null; 871 } 872 } 873 874 private void showPerformanceInfo() { 875 Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms"); 876 if (mReader instanceof CustomBufferedReader) { 877 Log.d(LOG_TAG, "Total readLine time: " 878 + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms"); 879 } 880 Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord 881 + " ms"); 882 Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms"); 883 Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup 884 + " ms"); 885 Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms"); 886 Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms"); 887 Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue 888 + " ms"); 889 Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms"); 890 Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms"); 891 } 892 893 /** 894 * @return {@link VCardConfig#FLAG_V21} 895 */ 896 protected int getVersion() { 897 return VCardConfig.FLAG_V21; 898 } 899 900 /** 901 * @return {@link VCardConfig#FLAG_V30} 902 */ 903 protected String getVersionString() { 904 return VCardConstants.VERSION_V21; 905 } 906 907 protected Set<String> getKnownPropertyNameSet() { 908 return VCardParser_V21.sKnownPropertyNameSet; 909 } 910 911 protected Set<String> getKnownTypeSet() { 912 return VCardParser_V21.sKnownTypeSet; 913 } 914 915 protected Set<String> getKnownValueSet() { 916 return VCardParser_V21.sKnownValueSet; 917 } 918 919 protected Set<String> getAvailableEncodingSet() { 920 return VCardParser_V21.sAvailableEncoding; 921 } 922 923 protected String getDefaultEncoding() { 924 return DEFAULT_ENCODING; 925 } 926 927 928 public void parse(InputStream is, VCardInterpreter interpreter) 929 throws IOException, VCardException { 930 if (is == null) { 931 throw new NullPointerException("InputStream must not be null."); 932 } 933 934 final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset); 935 if (VCardConfig.showPerformanceLog()) { 936 mReader = new CustomBufferedReader(tmpReader); 937 } else { 938 mReader = new BufferedReader(tmpReader); 939 } 940 941 mInterpreter = interpreter; 942 943 final long start = System.currentTimeMillis(); 944 if (mInterpreter != null) { 945 mInterpreter.start(); 946 } 947 parseVCardFile(); 948 if (mInterpreter != null) { 949 mInterpreter.end(); 950 } 951 mTimeTotal += System.currentTimeMillis() - start; 952 953 if (VCardConfig.showPerformanceLog()) { 954 showPerformanceInfo(); 955 } 956 } 957 958 public final void cancel() { 959 mCanceled = true; 960 } 961} 962