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
+}