Fix import of iOS vcards
Fix handling of multiline data blocks in v3.0 vcards with \r\r\n line
terminators.
Bug:16433675
Change-Id: I77c7c94fa1b13e18e53459e94c3c73ad53b7d8e2
diff --git a/java/com/android/vcard/VCardParserImpl_V21.java b/java/com/android/vcard/VCardParserImpl_V21.java
index aaf442f..386d626 100644
--- a/java/com/android/vcard/VCardParserImpl_V21.java
+++ b/java/com/android/vcard/VCardParserImpl_V21.java
@@ -450,7 +450,7 @@
} else if (paramName.equals("VALUE")) {
handleValue(propertyData, paramValue);
} else if (paramName.equals("ENCODING")) {
- handleEncoding(propertyData, paramValue);
+ handleEncoding(propertyData, paramValue.toUpperCase());
} else if (paramName.equals("CHARSET")) {
handleCharset(propertyData, paramValue);
} else if (paramName.equals("LANGUAGE")) {
@@ -862,7 +862,10 @@
if (line.length() == 0) {
break;
}
- builder.append(line);
+ // Trim off any extraneous whitespace to handle 2.1 implementations
+ // that use 3.0 style line continuations. This is safe because space
+ // isn't a Base64 encoding value.
+ builder.append(line.trim());
}
return builder.toString();
diff --git a/java/com/android/vcard/VCardParserImpl_V30.java b/java/com/android/vcard/VCardParserImpl_V30.java
index eb63010..357383b 100644
--- a/java/com/android/vcard/VCardParserImpl_V30.java
+++ b/java/com/android/vcard/VCardParserImpl_V30.java
@@ -78,64 +78,41 @@
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
StringBuilder builder = null;
- while (true) {
- line = mReader.readLine();
- if (line == null) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- throw new VCardException("Reached end of buffer.");
- } else if (line.length() == 0) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
+ while ((line = mReader.readLine()) != null) {
+ // Skip empty lines in order to accomodate implementations that
+ // send line termination variations such as \r\r\n.
+ if (line.length() == 0) {
+ continue;
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
- if (builder != null) {
- // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
- // Following is the excerpts from it.
- //
- // DESCRIPTION:This is a long description that exists on a long line.
- //
- // Can be represented as:
- //
- // DESCRIPTION:This is a long description
- // that exists on a long line.
- //
- // It could also be represented as:
- //
- // DESCRIPTION:This is a long descrip
- // tion that exists o
- // n a long line.
- builder.append(line.substring(1));
- } else if (mPreviousLine != null) {
+ // RFC 2425 describes line continuation as \r\n followed by
+ // a single ' ' or '\t' whitespace character.
+ if (builder == null) {
builder = new StringBuilder();
+ }
+ if (mPreviousLine != null) {
builder.append(mPreviousLine);
mPreviousLine = null;
- builder.append(line.substring(1));
- } else {
- throw new VCardException("Space exists at the beginning of the line");
}
+ builder.append(line.substring(1));
} else {
- if (mPreviousLine == null) {
- mPreviousLine = line;
- if (builder != null) {
- return builder.toString();
- }
- } else {
- String ret = mPreviousLine;
- mPreviousLine = line;
- return ret;
+ if (builder != null || mPreviousLine != null) {
+ break;
}
+ mPreviousLine = line;
}
}
+
+ String ret = null;
+ if (builder != null) {
+ ret = builder.toString();
+ } else if (mPreviousLine != null) {
+ ret = mPreviousLine;
+ }
+ mPreviousLine = line;
+ if (ret == null) {
+ throw new VCardException("Reached end of buffer.");
+ }
+ return ret;
}
/*
@@ -313,30 +290,16 @@
}
/**
- * vCard 3.0 does not require two CRLF at the last of BASE64 data.
- * It only requires that data should be MIME-encoded.
+ * This is only called from handlePropertyValue(), which has already
+ * read the first line of this property. With v3.0, the getNonEmptyLine()
+ * routine has already concatenated all following continuation lines.
+ * The routine is implemented in the V21 parser to concatenate v2.1 style
+ * data blocks, but is unnecessary here.
*/
@Override
protected String getBase64(final String firstString)
throws IOException, VCardException {
- final StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- final String line = getLine();
- if (line == null) {
- throw new VCardException("File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
- mPreviousLine = line;
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
+ return firstString;
}
/**
diff --git a/tests/res/raw/v30_ios_613_multiline.vcf b/tests/res/raw/v30_ios_613_multiline.vcf
new file mode 100644
index 0000000..4e737bd
--- /dev/null
+++ b/tests/res/raw/v30_ios_613_multiline.vcf
Binary files differ
diff --git a/tests/src/com/android/vcard/tests/VCardParserTests.java b/tests/src/com/android/vcard/tests/VCardParserTests.java
index 9b65593..2b63103 100644
--- a/tests/src/com/android/vcard/tests/VCardParserTests.java
+++ b/tests/src/com/android/vcard/tests/VCardParserTests.java
@@ -18,6 +18,7 @@
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
import com.android.vcard.VCardProperty;
import com.android.vcard.exception.VCardException;
@@ -136,6 +137,38 @@
}
/**
+ * Test vCard containing v3.0 line continuations and with non-standard
+ * line terminators \r\r\n coming from iOS 6.1.3. Tests to make sure the
+ * parser correctly skips the extra line terminators and still
+ * successfully concatenates the multi-line block.
+ */
+ public void testIosMultiline() throws IOException, VCardException {
+ InputStream inputStream = getContext().getResources().openRawResource(R.raw.v30_ios_613_multiline);
+ try {
+ VCardParser parser = new VCardParser_V30();
+ MockVCardInterpreter interpreter = new MockVCardInterpreter();
+ interpreter.addExpectedOrder(Order.START)
+ .addExpectedOrder(Order.START_ENTRY)
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For N
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For FN
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For ORG
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For item1
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For item2
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For item3
+ .addExpectedOrder(Order.PROPERTY_CREATED) // For PHOTO
+ .addExpectedOrder(Order.END_ENTRY)
+ .addExpectedOrder(Order.END);
+ parser.addInterpreter(interpreter);
+ parser.parse(inputStream);
+ interpreter.verify();
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ /**
* Tests if {@link VCardParser#parse(InputStream)} parses the whole vCard file and
* {@link VCardParser#parseOne(InputStream)} parses just first entry of a vCard file
* with multiple vCard entries.
@@ -206,4 +239,4 @@
}
}
-}
\ No newline at end of file
+}