/** * * Copyright 2003-2007 Jive Software. * * All rights reserved. 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 org.jivesoftware.smackx.packet; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.PacketCollector; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.util.StringUtils; /** * A VCard class for use with the * SMACK jabber library.
*
* You should refer to the * JEP-54 documentation.*
* Please note that this class is incomplete but it does provide the most commonly found * information in vCards. Also remember that VCard transfer is not a standard, and the protocol * may change or be replaced.*
* Usage: ** * // To save VCard: * * VCard vCard = new VCard(); * vCard.setFirstName("kir"); * vCard.setLastName("max"); * vCard.setEmailHome("foo@fee.bar"); * vCard.setJabberId("jabber@id.org"); * vCard.setOrganization("Jetbrains, s.r.o"); * vCard.setNickName("KIR"); * * vCard.setField("TITLE", "Mr"); * vCard.setAddressFieldHome("STREET", "Some street"); * vCard.setAddressFieldWork("CTRY", "US"); * vCard.setPhoneWork("FAX", "3443233"); * * vCard.save(connection); * * // To load VCard: * * VCard vCard = new VCard(); * vCard.load(conn); // load own VCard * vCard.load(conn, "joe@foo.bar"); // load someone's VCard ** * @author Kirill Maximov (kir@maxkir.com) */ public class VCard extends IQ { private static Logger log = Logger.getLogger(VCard.class.getName()); private static final String DEFAULT_MIME_TYPE = "image/jpeg"; /** * Phone types: * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? */ private Map
* // Load Avatar from VCard * byte[] avatarBytes = vCard.getAvatar(); * * // To create an ImageIcon for Swing applications * ImageIcon icon = new ImageIcon(avatar); * * // To create just an image object from the bytes * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); * try { * Image image = ImageIO.read(bais); * } * catch (IOException e) { * e.printStackTrace(); * } ** * @return byte representation of avatar. */ public byte[] getAvatar() { if (photoBinval == null) { return null; } 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; } /** * Common code for getting the bytes of a url. * * @param url the url to read. */ public static byte[] getBytes(URL url) throws IOException { final String path = url.getPath(); final File file = new File(path); if (file.exists()) { return getFileBytes(file); } return null; } private static byte[] getFileBytes(File file) throws IOException { BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(file)); int bytes = (int) file.length(); byte[] buffer = new byte[bytes]; int readBytes = bis.read(buffer); if (readBytes != buffer.length) { throw new IOException("Entire file not read"); } return buffer; } finally { if (bis != null) { bis.close(); } } } /** * Returns the SHA-1 Hash of the Avatar image. * * @return the SHA-1 Hash of the Avatar image. */ public String getAvatarHash() { byte[] bytes = getAvatar(); if (bytes == null) { return null; } MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { log.log(Level.SEVERE, "Failed to get message digest", e); return null; } digest.update(bytes); return StringUtils.encodeHex(digest.digest()); } private void updateFN() { StringBuilder sb = new StringBuilder(); if (firstName != null) { sb.append(StringUtils.escapeForXML(firstName)).append(' '); } if (middleName != null) { sb.append(StringUtils.escapeForXML(middleName)).append(' '); } if (lastName != null) { sb.append(StringUtils.escapeForXML(lastName)); } setField("FN", sb.toString()); } /** * Save this vCard for the user connected by 'connection'. Connection should be authenticated * and not anonymous.
*
* NOTE: the method is asynchronous and does not wait for the returned value. * * @param connection the Connection to use. * @throws XMPPException thrown if there was an issue setting the VCard in the server. */ public void save(Connection connection) throws XMPPException { checkAuthenticated(connection, true); setType(IQ.Type.SET); setFrom(connection.getUser()); PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID())); connection.sendPacket(this); Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); // Cancel the collector. collector.cancel(); if (response == null) { throw new XMPPException("No response from server on status set."); } if (response.getError() != null) { throw new XMPPException(response.getError()); } } /** * Load VCard information for a connected user. Connection should be authenticated * and not anonymous. */ public void load(Connection connection) throws XMPPException { checkAuthenticated(connection, true); setFrom(connection.getUser()); doLoad(connection, connection.getUser()); } /** * Load VCard information for a given user. Connection should be authenticated and not anonymous. */ public void load(Connection connection, String user) throws XMPPException { checkAuthenticated(connection, false); setTo(user); doLoad(connection, user); } private void doLoad(Connection connection, String user) throws XMPPException { setType(Type.GET); PacketCollector collector = connection.createPacketCollector( new PacketIDFilter(getPacketID())); connection.sendPacket(this); VCard result = null; Packet packet = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); if (packet == null) { String errorMessage = "Timeout getting VCard information"; throw new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.request_timeout, errorMessage)); } if (packet.getError() != null) { throw new XMPPException(packet.getError()); } try { result = (VCard) packet; } catch (ClassCastException e) { log.log(Level.SEVERE, "No VCard for " + user, e); return; } copyFieldsFrom(result); } public String getChildElementXML() { StringBuilder sb = new StringBuilder(); new VCardWriter(sb).write(); return sb.toString(); } 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(from)); } catch (IllegalAccessException e) { throw new RuntimeException("This cannot happen:" + field, e); } } } } private void checkAuthenticated(Connection connection, boolean checkForAnonymous) { if (connection == null) { throw new IllegalArgumentException("No connection was provided"); } if (!connection.isAuthenticated()) { throw new IllegalArgumentException("Connection is not authenticated"); } if (checkForAnonymous && connection.isAnonymous()) { throw new IllegalArgumentException("Connection cannot be anonymous"); } } private boolean hasContent() { //noinspection OverlyComplexBooleanExpression return hasNameField() || hasOrganizationFields() || emailHome != null || emailWork != null || otherSimpleFields.size() > 0 || otherUnescapableFields.size() > 0 || homeAddr.size() > 0 || homePhones.size() > 0 || workAddr.size() > 0 || workPhones.size() > 0 || photoBinval != null ; } private boolean hasNameField() { return firstName != null || lastName != null || middleName != null; } private boolean hasOrganizationFields() { return organization != null || organizationUnit != null; } // Used in tests: public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final VCard vCard = (VCard) o; if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { return false; } if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { return false; } if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { return false; } if (!homeAddr.equals(vCard.homeAddr)) { return false; } if (!homePhones.equals(vCard.homePhones)) { return false; } if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { return false; } if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { return false; } if (organization != null ? !organization.equals(vCard.organization) : vCard.organization != null) { return false; } if (organizationUnit != null ? !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { return false; } if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { return false; } if (!workAddr.equals(vCard.workAddr)) { return false; } if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { return false; } return workPhones.equals(vCard.workPhones); } public int hashCode() { int result; result = homePhones.hashCode(); result = 29 * result + workPhones.hashCode(); result = 29 * result + homeAddr.hashCode(); result = 29 * result + workAddr.hashCode(); result = 29 * result + (firstName != null ? firstName.hashCode() : 0); result = 29 * result + (lastName != null ? lastName.hashCode() : 0); result = 29 * result + (middleName != null ? middleName.hashCode() : 0); result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); 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; } public String toString() { return getChildElementXML(); } //============================================================== private class VCardWriter { private final StringBuilder sb; VCardWriter(StringBuilder sb) { this.sb = sb; } public void write() { appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() { public void addTagContent() { buildActualContent(); } }); } private void buildActualContent() { if (hasNameField()) { appendN(); } appendOrganization(); appendGenericFields(); appendPhoto(); appendEmail(emailWork, "WORK"); appendEmail(emailHome, "HOME"); appendPhones(workPhones, "WORK"); appendPhones(homePhones, "HOME"); appendAddress(workAddr, "WORK"); 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() { public void addTagContent() { appendEmptyTag(type); appendEmptyTag("INTERNET"); appendEmptyTag("PREF"); appendTag("USERID", StringUtils.escapeForXML(email)); } }); } } private void appendPhones(Map