diff --git a/src/at/bitfire/davdroid/resource/Contact.java b/src/at/bitfire/davdroid/resource/Contact.java index 39b186f5..3841a6c7 100644 --- a/src/at/bitfire/davdroid/resource/Contact.java +++ b/src/at/bitfire/davdroid/resource/Contact.java @@ -26,6 +26,7 @@ import ezvcard.Ezvcard; import ezvcard.VCard; import ezvcard.VCardException; import ezvcard.VCardVersion; +import ezvcard.ValidationWarnings; import ezvcard.parameter.EmailType; import ezvcard.parameter.ImageType; import ezvcard.parameter.TelephoneType; @@ -375,6 +376,11 @@ public class Contact extends Resource { vcard.setProdId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")"); vcard.setRevision(Revision.now()); + // validate and print warnings + ValidationWarnings warnings = vcard.validate(VCardVersion.V3_0); + if (!warnings.isEmpty()) + Log.w(TAG, "Created potentially invalid VCard! " + warnings); + ByteArrayOutputStream os = new ByteArrayOutputStream(); Ezvcard .write(vcard) diff --git a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java b/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java index 2f77dae2..54612b85 100644 --- a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java +++ b/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java @@ -60,7 +60,7 @@ public class TlsSniSocketFactory implements LayeredConnectionSocketFactory { @Override public Socket createSocket(HttpContext context) throws IOException { - return new Socket(); + return sslSocketFactory.createSocket(); } @Override @@ -87,6 +87,7 @@ public class TlsSniSocketFactory implements LayeredConnectionSocketFactory { SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(plain, host, port, true); // already connected, but verify host name again and print some connection info + Log.w(TAG, "Setting SNI/TLSv1.2 will silently fail because the handshake is already done"); connectWithSNI(ssl, host); return ssl; @@ -95,26 +96,23 @@ public class TlsSniSocketFactory implements LayeredConnectionSocketFactory { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void connectWithSNI(SSLSocket ssl, String host) throws SSLPeerUnverifiedException { - if (!ssl.isConnected()) { - // set reasonable SSL/TLS settings before the handshake: - // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <4.4.3, if available) - ssl.setEnabledProtocols(ssl.getSupportedProtocols()); - - // - set SNI host name - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Log.d(TAG, "Using documented SNI with host name " + host); - sslSocketFactory.setHostname(ssl, host); - } else { - Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection"); - try { - java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class); - setHostnameMethod.invoke(ssl, host); - } catch (Exception e) { - Log.w(TAG, "SNI not useable", e); - } + // set reasonable SSL/TLS settings before the handshake: + // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <4.4.3, if available) + ssl.setEnabledProtocols(ssl.getSupportedProtocols()); + + // - set SNI host name + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Log.d(TAG, "Using documented SNI with host name " + host); + sslSocketFactory.setHostname(ssl, host); + } else { + Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection"); + try { + java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class); + setHostnameMethod.invoke(ssl, host); + } catch (Exception e) { + Log.w(TAG, "SNI not useable", e); } - } else - Log.d(TAG, "Socket is already connected, SNI/TLv1.2 not available unless activated by Android defaults"); + } // verify hostname and certificate SSLSession session = ssl.getSession(); diff --git a/test/assets/invalid-unknown-properties.vcf b/test/assets/invalid-unknown-properties.vcf new file mode 100644 index 00000000..3fd77fce --- /dev/null +++ b/test/assets/invalid-unknown-properties.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD +VERSION:3.0 +FN:VCard with invalid unknown properties +X-UNKNOWN@PROPERTY:MUST-NOT_CONTAIN?OTHER*LETTERS; +END:VCARD \ No newline at end of file diff --git a/test/assets/reference.vcf b/test/assets/reference.vcf new file mode 100644 index 00000000..ba6a6b4d --- /dev/null +++ b/test/assets/reference.vcf @@ -0,0 +1,16 @@ +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;Mr. +FN:Forrest Gump +ORG:Bubba Gump Shrimp Co. +TITLE:Shrimp Man +PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif +TEL;TYPE=WORK,VOICE:(111) 555-1212 +TEL;TYPE=HOME,VOICE:(404) 555-1212 +ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America +LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America +ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America +LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America +EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com +REV:2008-04-24T19:52:43Z +END:VCARD \ No newline at end of file diff --git a/test/src/at/bitfire/davdroid/resource/test/ContactTest.java b/test/src/at/bitfire/davdroid/resource/test/ContactTest.java new file mode 100644 index 00000000..90442809 --- /dev/null +++ b/test/src/at/bitfire/davdroid/resource/test/ContactTest.java @@ -0,0 +1,58 @@ +package at.bitfire.davdroid.resource.test; + +import java.io.IOException; +import java.io.InputStream; + +import ezvcard.property.Email; +import ezvcard.property.Telephone; +import lombok.Cleanup; +import android.content.res.AssetManager; +import android.test.InstrumentationTestCase; +import at.bitfire.davdroid.resource.Contact; +import at.bitfire.davdroid.resource.InvalidResourceException; + +public class ContactTest extends InstrumentationTestCase { + AssetManager assetMgr; + + public void setUp() throws IOException, InvalidResourceException { + assetMgr = getInstrumentation().getContext().getResources().getAssets(); + } + + public void testReferenceVCard() throws IOException, InvalidResourceException { + Contact c = parseVCF("reference.vcf"); + assertEquals("Gump", c.getFamilyName()); + assertEquals("Forrest", c.getGivenName()); + assertEquals("Forrest Gump", c.getDisplayName()); + assertEquals("Bubba Gump Shrimp Co.", c.getOrganization().getValues().get(0)); + assertEquals("Shrimp Man", c.getJobTitle()); + + Telephone phone1 = c.getPhoneNumbers().get(0); + assertEquals("(111) 555-1212", phone1.getText()); + assertEquals("WORK", phone1.getParameters("TYPE").get(0)); + assertEquals("VOICE", phone1.getParameters("TYPE").get(1)); + + Telephone phone2 = c.getPhoneNumbers().get(1); + assertEquals("(404) 555-1212", phone2.getText()); + assertEquals("HOME", phone2.getParameters("TYPE").get(0)); + assertEquals("VOICE", phone2.getParameters("TYPE").get(1)); + + Email email = c.getEmails().get(0); + assertEquals("forrestgump@example.com", email.getValue()); + assertEquals("PREF", email.getParameters("TYPE").get(0)); + assertEquals("INTERNET", email.getParameters("TYPE").get(1)); + } + + public void testParseInvalidUnknownProperties() throws IOException, InvalidResourceException { + Contact c = parseVCF("invalid-unknown-properties.vcf"); + assertEquals("VCard with invalid unknown properties", c.getDisplayName()); + assertNull(c.getUnknownProperties()); + } + + + protected Contact parseVCF(String fname) throws IOException, InvalidResourceException { + @Cleanup InputStream in = assetMgr.open(fname, AssetManager.ACCESS_STREAMING); + Contact c = new Contact(fname, null); + c.parseEntity(in); + return c; + } +} diff --git a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java index 6d2d31a2..0f033ba7 100644 --- a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java +++ b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java @@ -41,7 +41,7 @@ public class WebDavResourceTest extends InstrumentationTestCase { @Override protected void setUp() throws Exception { - httpClient = DavHttpClient.create(); + httpClient = DavHttpClient.create(true, true); assetMgr = getInstrumentation().getContext().getResources().getAssets();