From 638d34fd0628c36402e5805a713021d9c549ab18 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 23 Mar 2013 00:27:14 +0000 Subject: [PATCH] SMACK-413 fixed vCard parsing regarding the PHOTO element. Moved vCard test cases to unit-test where appropriate. Added testcases for vCard PHOTO parsing. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13584 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smackx/packet/VCard.java | 66 +++++++---- .../smackx/provider/VCardProvider.java | 37 +++++- .../jivesoftware/smackx/VCardUnitTest.java | 107 ++++++++++++++++++ .../smack/test/SmackTestCase.java | 2 - test/org/jivesoftware/smackx/VCardTest.java | 37 ++---- 5 files changed, 197 insertions(+), 52 deletions(-) create mode 100644 test-unit/org/jivesoftware/smackx/VCardUnitTest.java diff --git a/source/org/jivesoftware/smackx/packet/VCard.java b/source/org/jivesoftware/smackx/packet/VCard.java index db5ef76a9..9766db824 100644 --- a/source/org/jivesoftware/smackx/packet/VCard.java +++ b/source/org/jivesoftware/smackx/packet/VCard.java @@ -112,7 +112,8 @@ public class VCard extends IQ { private String organization; private String organizationUnit; - private String avatar; + private String photoMimeType; + private String photoBinval; /** * Such as DESC ROLE GEO etc.. see JEP-0054 @@ -343,12 +344,15 @@ public class VCard extends IQ { * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 */ public void removeAvatar() { - setAvatar(null, "image/jpeg"); + // Remove avatar (if any) + photoBinval = null; + photoMimeType = null; } /** - * Specify the bytes for the avatar to use. + * Specify the bytes of the JPEG for the avatar to use. * If bytes is null, then the avatar will be removed. + * 'image/jpeg' will be used as MIME type. * * @param bytes the bytes of the avatar, or null to remove the avatar data */ @@ -363,27 +367,27 @@ public class VCard extends IQ { * @param mimeType the mime type of the avatar. */ public void setAvatar(byte[] bytes, String mimeType) { + // If bytes is null, remove the avatar if (bytes == null) { - // Remove avatar (if any) from mappings - otherUnescapableFields.remove("PHOTO"); + removeAvatar(); return; } // Otherwise, add to mappings. String encodedImage = StringUtils.encodeBase64(bytes); - avatar = encodedImage; - setField("PHOTO", "" + mimeType + "" + encodedImage + "", true); + setAvatar(encodedImage, mimeType); } /** - * Set the encoded avatar string. This is used by the provider. + * Specify the Avatar used for this vCard. * - * @param encodedAvatar the encoded avatar string. + * @param encodedImage the Base64 encoded image as String + * @param mimeType the MIME type of the image */ - public void setEncodedImage(String encodedAvatar) { - //TODO Move VCard and VCardProvider into a vCard package. - this.avatar = encodedAvatar; + public void setAvatar(String encodedImage, String mimeType) { + photoBinval = encodedImage; + photoMimeType = mimeType; } /** @@ -410,10 +414,19 @@ public class VCard extends IQ { * @return byte representation of avatar. */ public byte[] getAvatar() { - if (avatar == null) { + if (photoBinval == null) { return null; } - return StringUtils.decodeBase64(avatar); + return StringUtils.decodeBase64(photoBinval); + } + + /** + * Returns the MIME Type of the avatar or null if none is set + * + * @return the MIME Type of the avatar or null + */ + public String getAvatarMimeType() { + return photoMimeType; } /** @@ -570,16 +583,14 @@ public class VCard extends IQ { return sb.toString(); } - private void copyFieldsFrom(VCard result) { - if (result == null) result = new VCard(); - + private void copyFieldsFrom(VCard from) { Field[] fields = VCard.class.getDeclaredFields(); for (Field field : fields) { if (field.getDeclaringClass() == VCard.class && !Modifier.isFinal(field.getModifiers())) { try { field.setAccessible(true); - field.set(this, field.get(result)); + field.set(this, field.get(from)); } catch (IllegalAccessException e) { throw new RuntimeException("This cannot happen:" + field, e); @@ -612,6 +623,7 @@ public class VCard extends IQ { || homePhones.size() > 0 || workAddr.size() > 0 || workPhones.size() > 0 + || photoBinval != null ; } @@ -666,8 +678,11 @@ public class VCard extends IQ { if (!workAddr.equals(vCard.workAddr)) { return false; } - return workPhones.equals(vCard.workPhones); + if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { + return false; + } + return workPhones.equals(vCard.workPhones); } public int hashCode() { @@ -684,6 +699,7 @@ public class VCard extends IQ { result = 29 * result + (organization != null ? organization.hashCode() : 0); result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); result = 29 * result + otherSimpleFields.hashCode(); + result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0); return result; } @@ -716,6 +732,7 @@ public class VCard extends IQ { appendOrganization(); appendGenericFields(); + appendPhoto(); appendEmail(emailWork, "WORK"); appendEmail(emailHome, "HOME"); @@ -727,6 +744,17 @@ public class VCard extends IQ { appendAddress(homeAddr, "HOME"); } + private void appendPhoto() { + if (photoBinval == null) + return; + + appendTag("PHOTO", true, new ContentBuilder() { + public void addTagContent() { + appendTag("BINVAL", photoBinval); // No need to escape photoBinval, as it's already Base64 encoded + appendTag("TYPE", StringUtils.escapeForXML(photoMimeType)); + } + }); + } private void appendEmail(final String email, final String type) { if (email != null) { appendTag("EMAIL", true, new ContentBuilder() { diff --git a/source/org/jivesoftware/smackx/provider/VCardProvider.java b/source/org/jivesoftware/smackx/provider/VCardProvider.java index 3f07af1fc..8fa04211d 100644 --- a/source/org/jivesoftware/smackx/provider/VCardProvider.java +++ b/source/org/jivesoftware/smackx/provider/VCardProvider.java @@ -114,7 +114,7 @@ public class VCardProvider implements IQProvider { vCard.setFirstName(getTagContents("GIVEN")); vCard.setLastName(getTagContents("FAMILY")); vCard.setMiddleName(getTagContents("MIDDLE")); - vCard.setEncodedImage(getTagContents("BINVAL")); + setupPhoto(); setupEmails(); @@ -127,6 +127,41 @@ public class VCardProvider implements IQProvider { setupAddresses(); } + private void setupPhoto() { + String binval = null; + String mimetype = null; + + NodeList photo = document.getElementsByTagName("PHOTO"); + if (photo.getLength() != 1) + return; + + Node photoNode = photo.item(0); + NodeList childNodes = photoNode.getChildNodes(); + + int childNodeCount = childNodes.getLength(); + List nodes = new ArrayList(childNodeCount); + for (int i = 0; i < childNodeCount; i++) + nodes.add(childNodes.item(i)); + + String name = null; + String value = null; + for (Node n : nodes) { + name = n.getNodeName(); + value = n.getTextContent(); + if (name.equals("BINVAL")) { + binval = value; + } + else if (name.equals("TYPE")) { + mimetype = value; + } + } + + if (binval == null || mimetype == null) + return; + + vCard.setAvatar(binval, mimetype); + } + private void setupEmails() { NodeList nodes = document.getElementsByTagName("USERID"); if (nodes == null) return; diff --git a/test-unit/org/jivesoftware/smackx/VCardUnitTest.java b/test-unit/org/jivesoftware/smackx/VCardUnitTest.java new file mode 100644 index 000000000..17adf75a7 --- /dev/null +++ b/test-unit/org/jivesoftware/smackx/VCardUnitTest.java @@ -0,0 +1,107 @@ +package org.jivesoftware.smackx; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.smackx.provider.VCardProvider; + +public class VCardUnitTest { + + @Test + public void testNoWorkHomeSpecifier_EMAIL() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("foo@fee.www.bar"); + assertEquals("foo@fee.www.bar", card.getEmailHome()); + } + + @Test + public void testNoWorkHomeSpecifier_TEL() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("3443233"); + assertEquals("3443233", card.getPhoneWork("FAX")); + } + + @Test + public void testNoWorkHomeSpecifier_ADDR() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("Some streetddss"); + assertEquals("Some street", card.getAddressFieldWork("STREET")); + assertEquals("ddss", card.getAddressFieldWork("FF")); + } + + @Test + public void testFN() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("kir max"); + assertEquals("kir max", card.getField("FN")); + // assertEquals("kir max", card.getFullName()); + } + + private final static String MIME_TYPE = "testtype"; + private final static String VCARD_XML = "" + getAvatarEncoded() + "" + MIME_TYPE + ""; + @Test + public void testPhoto() throws Throwable { + VCard vc = VCardProvider.createVCardFromXML(VCARD_XML); + byte[] avatar = vc.getAvatar(); + String mimeType = vc.getAvatarMimeType(); + assertEquals(mimeType, MIME_TYPE); + + byte[] expectedAvatar = getAvatarBinary(); + assertTrue(Arrays.equals(avatar, expectedAvatar)); + } + + public static byte[] getAvatarBinary() { + return StringUtils.decodeBase64(getAvatarEncoded()); + } + private static String getAvatarEncoded() { + return "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"; + } +} diff --git a/test/org/jivesoftware/smack/test/SmackTestCase.java b/test/org/jivesoftware/smack/test/SmackTestCase.java index fa6bb2d20..48e017743 100644 --- a/test/org/jivesoftware/smack/test/SmackTestCase.java +++ b/test/org/jivesoftware/smack/test/SmackTestCase.java @@ -272,8 +272,6 @@ public abstract class SmackTestCase extends TestCase { try { getConnection(i).login(currentUser, currentPassword, "Smack"); } catch (XMPPException e) { - e.printStackTrace(); - // Create the test accounts if (!getConnection(0).getAccountManager().supportsAccountCreation()) fail("Server does not support account creation"); diff --git a/test/org/jivesoftware/smackx/VCardTest.java b/test/org/jivesoftware/smackx/VCardTest.java index 08eb8b5d3..ac32a2494 100644 --- a/test/org/jivesoftware/smackx/VCardTest.java +++ b/test/org/jivesoftware/smackx/VCardTest.java @@ -23,7 +23,6 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.test.SmackTestCase; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.VCard; -import org.jivesoftware.smackx.provider.VCardProvider; /** * Created by IntelliJ IDEA. @@ -81,36 +80,12 @@ public class VCardTest extends SmackTestCase { //assertEquals("Should load another user's VCard successfully", origVCard.toString(), loaded.toString()); assertEquals("Should load another user's VCard successfully", origVCard, loaded); - - } - - public void testNoWorkHomeSpecifier_EMAIL() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("foo@fee.www.bar"); - assertEquals("foo@fee.www.bar", card.getEmailHome()); - } - - public void testNoWorkHomeSpecifier_TEL() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("3443233"); - assertEquals("3443233", card.getPhoneWork("FAX")); - } - - public void testNoWorkHomeSpecifier_ADDR() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("Some streetddss"); - assertEquals("Some street", card.getAddressFieldWork("STREET")); - assertEquals("ddss", card.getAddressFieldWork("FF")); - } - - public void testFN() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("kir max"); - assertEquals("kir max", card.getField("FN")); - // assertEquals("kir max", card.getFullName()); } public void testBinaryAvatar() throws Throwable { VCard card = new VCard(); card.setAvatar(getAvatarBinary()); card.save(getConnection(0)); - System.out.println(card.getChildElementXML()); VCard loaded = new VCard(); try { @@ -120,8 +95,10 @@ public class VCardTest extends SmackTestCase { e.printStackTrace(); fail(e.getMessage()); } - System.out.println(StringUtils.encodeBase64(loaded.getAvatar())); - assertEquals("Should load own Avatar successfully", card.getAvatar(), loaded.getAvatar()); + + byte[] initialAvatar = card.getAvatar(); + byte[] loadedAvatar = loaded.getAvatar(); + assertEquals("Should load own Avatar successfully", initialAvatar, loadedAvatar); loaded = new VCard(); try { @@ -135,11 +112,11 @@ public class VCardTest extends SmackTestCase { assertEquals("Should load avatar successfully", card.getAvatar(), loaded.getAvatar()); } - private byte[] getAvatarBinary() { - return StringUtils.decodeBase64(getAvatarEnconded()); + public static byte[] getAvatarBinary() { + return StringUtils.decodeBase64(getAvatarEncoded()); } - private String getAvatarEnconded() { + public static String getAvatarEncoded() { return "/9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD/2wBDAAUDBAQEAwUE\n" + "BAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/\n" + "2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e\n" +