diff options
6 files changed, 125 insertions, 15 deletions
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 1da6d7a252fd..d634672b74ce 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -1463,6 +1463,9 @@ public class VCardBuilder { parameterList.add(VCardConstants.PARAM_TYPE_VOICE); } else if (VCardUtils.isMobilePhoneLabel(label)) { parameterList.add(VCardConstants.PARAM_TYPE_CELL); + } else if (mIsV30) { + // This label is appropriately encoded in appendTypeParameters. + parameterList.add(label); } else { final String upperLabel = label.toUpperCase(); if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) { @@ -1741,21 +1744,30 @@ public class VCardBuilder { // which would be recommended way in vcard 3.0 though not valid in vCard 2.1. boolean first = true; for (final String typeValue : types) { - // Note: vCard 3.0 specifies the different type of acceptable type Strings, but - // we don't emit that kind of vCard 3.0 specific type since there should be - // high probabilyty in which external importers cannot understand them. - // - // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they - // are quoted.) - if (!VCardUtils.isV21Word(typeValue)) { - continue; - } - if (first) { - first = false; - } else { - mBuilder.append(VCARD_PARAM_SEPARATOR); + if (VCardConfig.isV30(mVCardType)) { + // Note: vCard 3.0 specifies the different type of acceptable type Strings, but + // we don't emit that kind of vCard 3.0 specific type since there should be + // high probabilyty in which external importers cannot understand them. + // + // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they + // are quoted.) + if (first) { + first = false; + } else { + mBuilder.append(VCARD_PARAM_SEPARATOR); + } + appendTypeParameter(VCardUtils.toStringAvailableAsV30ParameValue(typeValue)); + } else { // vCard 2.1 + if (!VCardUtils.isV21Word(typeValue)) { + continue; + } + if (first) { + first = false; + } else { + mBuilder.append(VCARD_PARAM_SEPARATOR); + } + appendTypeParameter(typeValue); } - appendTypeParameter(typeValue); } } diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java index 290ca2bfec14..ae4ec29ead27 100644 --- a/core/java/android/pim/vcard/VCardEntryConstructor.java +++ b/core/java/android/pim/vcard/VCardEntryConstructor.java @@ -157,11 +157,15 @@ public class VCardEntryConstructor implements VCardInterpreter { mParamType = type; } + @Override public void propertyParamValue(String value) { if (mParamType == null) { // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. mParamType = "TYPE"; } + if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { + value = encodeString(value, mCharsetForDecodedBytes); + } mCurrentProperty.addParameter(mParamType, value); mParamType = null; } diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java index 11b112b76dbb..f9727992a022 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -16,10 +16,10 @@ package android.pim.vcard; import android.content.ContentProviderOperation; -import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.provider.ContactsContract.Data; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; @@ -477,6 +477,43 @@ public class VCardUtils { return true; } + /** + * <P> + * Returns String available as parameter value in vCard 3.0. + * </P> + * <P> + * RFC 2426 requires vCard composer to quote parameter values when it contains + * semi-colon, for example (See RFC 2426 for more information). + * This method checks whether the given String can be used without quotes. + * </P> + * <P> + * Note: We remove DQUOTE silently for now. + * </P> + */ + public static String toStringAvailableAsV30ParameValue(String value) { + if (TextUtils.isEmpty(value)) { + value = ""; + } + final int asciiFirst = 0x20; + final int asciiLast = 0x7E; // included + final StringBuilder builder = new StringBuilder(); + final int length = value.length(); + boolean needQuote = false; + for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { + final int codePoint = value.codePointAt(i); + if (codePoint < asciiFirst || codePoint == '"') { + // CTL characters and DQUOTE are never accepted. Remove them. + continue; + } + builder.appendCodePoint(codePoint); + if (codePoint == ':' || codePoint == ',' || codePoint == ' ') { + needQuote = true; + } + } + final String result = builder.toString(); + return ((needQuote || result.isEmpty()) ? ('"' + result + '"') : result); + } + public static String toHalfWidthString(final String orgString) { if (TextUtils.isEmpty(orgString)) { return null; diff --git a/core/tests/coretests/res/raw/v30_multibyte_param.vcf b/core/tests/coretests/res/raw/v30_multibyte_param.vcf new file mode 100644 index 000000000000..cd200e563d11 --- /dev/null +++ b/core/tests/coretests/res/raw/v30_multibyte_param.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE="่ดน":1
+END:VCARD
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java index 21f22540c697..e0e1f879bb2f 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java @@ -1008,4 +1008,26 @@ public class VCardImporterTests extends VCardTestsBase { .put(Phone.TYPE, Phone.TYPE_PAGER) .put(Phone.NUMBER, "6101231234@pagersample.com"); } + + public void testMultiBytePropV30_Parse() { + mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("VERSION", "3.0") + .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) + .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39")); + } + + public void testMultiBytePropV30() { + mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); + final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "F") + .put(StructuredName.MIDDLE_NAME, "M") + .put(StructuredName.GIVEN_NAME, "G") + .put(StructuredName.DISPLAY_NAME, "G M F"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "\u8D39") + .put(Phone.NUMBER, "1"); + } } diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java index 59299f9dff35..e805bee8cb82 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java @@ -82,4 +82,34 @@ public class VCardUtilsTests extends TestCase { assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); } } + + public void testToStringAvailableAsV30ParamValue() { + // Smoke tests. + assertEquals("HOME", VCardUtils.toStringAvailableAsV30ParameValue("HOME")); + assertEquals("TEL", VCardUtils.toStringAvailableAsV30ParameValue("TEL")); + assertEquals("PAGER", VCardUtils.toStringAvailableAsV30ParameValue("PAGER")); + + assertEquals("\"\"", VCardUtils.toStringAvailableAsV30ParameValue("")); + + // non-Ascii must be allowed + assertEquals("\u4E8B\u52D9\u6240", + VCardUtils.toStringAvailableAsV30ParameValue("\u4E8B\u52D9\u6240")); + // Reported as bug report. + assertEquals("\u8D39", VCardUtils.toStringAvailableAsV30ParameValue("\u8D39")); + assertEquals("\"comma,separated\"", + VCardUtils.toStringAvailableAsV30ParameValue("comma,separated")); + assertEquals("\"colon:aware\"", + VCardUtils.toStringAvailableAsV30ParameValue("colon:aware")); + // CTL characters. + assertEquals("CTLExample", + VCardUtils.toStringAvailableAsV30ParameValue("CTL\u0001Example")); + // DQUOTE must be removed. + assertEquals("quoted", + VCardUtils.toStringAvailableAsV30ParameValue("\"quoted\"")); + // DQUOTE must be removed basically, but we should detect a space, which + // require us to use DQUOTE again. + // Right-side has one more illegal dquote to test quote-handle code thoroughly. + assertEquals("\"Already quoted\"", + VCardUtils.toStringAvailableAsV30ParameValue("\"Already quoted\"\"")); + } } |