Support vCard format emitted by Windows Mobile 6.5, which contains invalid "VALUE" params and

"AGENT" line.

Internal Issue number: 2247192
This commit is contained in:
Daisuke Miyakawa
2009-11-09 15:10:04 +09:00
parent df0ce74126
commit 0e983864fc
8 changed files with 177 additions and 36 deletions

View File

@@ -21,9 +21,23 @@ import java.io.IOException;
import java.io.InputStream;
public abstract class VCardParser {
public static final int PARSER_MODE_DEFAULT = 0;
/**
* The parser should ignore "AGENT" properties and nested vCard structure.
*/
public static final int PARSER_MODE_SCAN = 1;
protected final int mParserMode;
protected boolean mCanceled;
public VCardParser() {
mParserMode = PARSER_MODE_DEFAULT;
}
public VCardParser(int parserMode) {
mParserMode = parserMode;
}
/**
* Parses the given stream and send the VCard data into VCardBuilderBase object.
*

View File

@@ -15,11 +15,11 @@
*/
package android.pim.vcard;
import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException;
import android.pim.vcard.exception.VCardInvalidCommentLineException;
import android.pim.vcard.exception.VCardInvalidLineException;
import android.pim.vcard.exception.VCardNestedException;
import android.pim.vcard.exception.VCardNotSupportedException;
import android.pim.vcard.exception.VCardVersionException;
import android.util.Log;
@@ -91,8 +91,15 @@ public class VCardParser_V21 extends VCardParser {
// In order to reduce warning message as much as possible, we hold the value which made Logger
// emit a warning message.
protected HashSet<String> mWarningValueMap = new HashSet<String>();
protected HashSet<String> mUnknownTypeMap = new HashSet<String>();
protected HashSet<String> mUnknownValueMap = new HashSet<String>();
// It seems Windows Mobile 6.5 uses "AGENT" property with completely wrong usage.
// We should just ignore just one line.
// e.g.
// "AGENT;CHARSET=SHIFT_JIS:some text"
private boolean mIgnoreAgentLine = false;
// Just for debugging
private long mTimeTotal;
private long mTimeReadStartRecord;
@@ -106,21 +113,41 @@ public class VCardParser_V21 extends VCardParser {
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
/**
* Create a new VCard parser.
*/
public VCardParser_V21() {
super();
this(null, PARSER_MODE_DEFAULT);
}
public VCardParser_V21(int parserMode) {
this(null, parserMode);
}
public VCardParser_V21(VCardSourceDetector detector) {
super();
if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) {
mNestCount = 1;
this(detector, PARSER_MODE_DEFAULT);
}
/**
* TODO: Merge detector and parser mode.
*/
public VCardParser_V21(VCardSourceDetector detector, int parserMode) {
super(parserMode);
if (detector != null) {
final int type = detector.getType();
if (type == VCardSourceDetector.TYPE_FOMA) {
mNestCount = 1;
} else if (type == VCardSourceDetector.TYPE_JAPANESE_MOBILE_PHONE) {
mIgnoreAgentLine = true;
}
}
if (parserMode == PARSER_MODE_SCAN) {
mIgnoreAgentLine = true;
}
}
/**
* Parse the file at the given position
* vcard_file = [wsls] vcard [wsls]
@@ -160,8 +187,8 @@ public class VCardParser_V21 extends VCardParser {
protected boolean isValidPropertyName(String propertyName) {
if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
propertyName.startsWith("X-")) &&
!mWarningValueMap.contains(propertyName)) {
mWarningValueMap.add(propertyName);
!mUnknownTypeMap.contains(propertyName)) {
mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
}
return true;
@@ -554,9 +581,9 @@ public class VCardParser_V21 extends VCardParser {
protected void handleType(final String ptypeval) {
String upperTypeValue = ptypeval;
if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
!mWarningValueMap.contains(ptypeval)) {
mWarningValueMap.add(ptypeval);
Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval);
!mUnknownTypeMap.contains(ptypeval)) {
mUnknownTypeMap.add(ptypeval);
Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("TYPE");
@@ -567,15 +594,16 @@ public class VCardParser_V21 extends VCardParser {
/**
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
protected void handleValue(final String pvalueval) throws VCardException {
if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
pvalueval.startsWith("X-")) {
if (mBuilder != null) {
mBuilder.propertyParamType("VALUE");
mBuilder.propertyParamValue(pvalueval);
}
} else {
throw new VCardException("Unknown value \"" + pvalueval + "\"");
protected void handleValue(final String pvalueval) {
if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
pvalueval.startsWith("X-") &&
!mUnknownValueMap.contains(pvalueval)) {
mUnknownValueMap.add(pvalueval);
Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("VALUE");
mBuilder.propertyParamValue(pvalueval);
}
}
@@ -800,9 +828,14 @@ public class VCardParser_V21 extends VCardParser {
* items *CRLF "END" [ws] ":" [ws] "VCARD"
*
*/
protected void handleAgent(String propertyValue) throws VCardException {
throw new VCardNotSupportedException("AGENT Property is not supported now.");
/* This is insufficient support. Also, AGENT Property is very rare.
protected void handleAgent(final String propertyValue) throws VCardException {
if (mIgnoreAgentLine) {
return;
} else {
throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
}
/* This is insufficient support. Also, AGENT Property is very rare and really hard to
understand the content.
Ignore it for now.
String[] strArray = propertyValue.split(":", 2);
@@ -819,7 +852,7 @@ public class VCardParser_V21 extends VCardParser {
/**
* For vCard 3.0.
*/
protected String maybeUnescapeText(String text) {
protected String maybeUnescapeText(final String text) {
return text;
}
@@ -827,11 +860,11 @@ public class VCardParser_V21 extends VCardParser {
* Returns unescaped String if the character should be unescaped. Return null otherwise.
* e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
*/
protected String maybeUnescapeCharacter(char ch) {
protected String maybeUnescapeCharacter(final char ch) {
return unescapeCharacter(ch);
}
public static String unescapeCharacter(char ch) {
public static String unescapeCharacter(final char ch) {
// Original vCard 2.1 specification does not allow transformation
// "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
// this class allowed them, so keep it as is.
@@ -843,7 +876,7 @@ public class VCardParser_V21 extends VCardParser {
}
@Override
public boolean parse(InputStream is, VCardBuilder builder)
public boolean parse(final InputStream is, final VCardBuilder builder)
throws IOException, VCardException {
return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
}

View File

@@ -72,6 +72,11 @@ public class VCardParser_V30 extends VCardParser_V21 {
mStrictParsing = strictParsing;
}
public VCardParser_V30(int parseMode) {
super(parseMode);
mStrictParsing = false;
}
@Override
protected int getVersion() {
return VCardConfig.FLAG_V30;
@@ -87,18 +92,18 @@ public class VCardParser_V30 extends VCardParser_V21 {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
acceptablePropsWithoutParam.contains(propertyName) ||
propertyName.startsWith("X-")) &&
!mWarningValueMap.contains(propertyName)) {
mWarningValueMap.add(propertyName);
!mUnknownTypeMap.contains(propertyName)) {
mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
}
return true;
}
@Override
protected boolean isValidEncoding(String encoding) {
return sAcceptableEncodingV30.contains(encoding.toUpperCase());
}
@Override
protected String getLine() throws IOException {
if (mPreviousLine != null) {

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard.exception;
public class VCardAgentNotSupportedException extends VCardNotSupportedException {
public VCardAgentNotSupportedException() {
super();
}
public VCardAgentNotSupportedException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
N:Example;;;;
FN:Example
ANNIVERSARY;VALUE=DATE:20091010
AGENT:Invalid line which must be handled correctly.
X-CLASS:PUBLIC
X-REDUCTION:
X-NO:
END:VCARD

View File

@@ -23,7 +23,6 @@ import android.pim.vcard.VCardParser_V21;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import android.util.Log;
import junit.framework.TestCase;
@@ -56,6 +55,12 @@ public class PropertyNodesVerifier extends VNodeBuilder {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
@@ -63,6 +68,11 @@ public class PropertyNodesVerifier extends VNodeBuilder {
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
try {
vCardParser.parse(is, this);
} finally {

View File

@@ -16,7 +16,10 @@
package com.android.unit_tests.vcard;
import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21;
import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -999,6 +1002,35 @@ public class VCardImporterTests extends VCardTestsBase {
verifier.verify(R.raw.v21_multiple_entry, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
}
public void testIgnoreAgentV21_Parse() throws IOException, VCardException {
PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
ContentValues contentValuesForValue = new ContentValues();
contentValuesForValue.put("VALUE", "DATE");
verifier.addPropertyNodesVerifierElem()
.addNodeWithOrder("VERSION", "2.1")
.addNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
.addNodeWithOrder("FN", "Example")
.addNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
.addNodeWithOrder("AGENT", "")
.addNodeWithOrder("X-CLASS", "PUBLIC")
.addNodeWithOrder("X-REDUCTION", "")
.addNodeWithOrder("X-NO", "");
// Only scan mode lets vCard parser accepts invalid AGENT lines like above.
verifier.verify(R.raw.v21_winmo_65, V21,
new VCardParser_V21(VCardParser.PARSER_MODE_SCAN));
}
public void testIgnoreAgentV21() throws IOException, VCardException {
ImportVerifier verifier = new ImportVerifier();
ImportVerifierElem elem = verifier.addImportVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Example")
.put(StructuredName.DISPLAY_NAME, "Example");
verifier.verify(R.raw.v21_winmo_65, V21,
new VCardParser_V21(VCardParser.PARSER_MODE_SCAN));
}
public void testPagerV30_Parse() throws IOException, VCardException {
PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
verifier.addPropertyNodesVerifierElem()

View File

@@ -455,6 +455,11 @@ class CustomMockContext extends MockContext {
verify(getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(getContext().getResources().openRawResource(resId), vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
@@ -462,6 +467,11 @@ class CustomMockContext extends MockContext {
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
VCardDataBuilder builder =
new VCardDataBuilder(null, null, false, vCardType, null);
builder.addEntryHandler(this);