1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2010-2013 Licq developers <licq-dev@googlegroups.com>
4  *
5  * Please refer to the COPYRIGHT file distributed with this source
6  * distribution for the names of the individual contributors.
7  *
8  * Licq is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Licq is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Licq; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "vcard.h"
24 
25 #include "user.h"
26 
27 #include <gloox/vcard.h>
28 
29 #include <licq/crypto.h>
30 #include <licq/logging/log.h>
31 
32 #include <cstdlib>
33 #include <cstring>
34 #include <iomanip>
35 #include <sstream>
36 
37 using namespace LicqJabber;
38 using Licq::gLog;
39 
40 namespace
41 {
guessPictureMimeType(const std::string & bindata)42 static std::string guessPictureMimeType(const std::string& bindata)
43 {
44   if (bindata.size() > 4 && bindata.substr(1, 3) == "PNG")
45     return "image/png";
46 
47   if (bindata.size() > 11
48       && (bindata.substr(0, 11).find("JFIF") != std::string::npos
49           || bindata.substr(0, 11).find("Exif") != std::string::npos))
50     return "image/jpeg";
51 
52   if (bindata.size() > 3 && bindata.substr(0, 3) == "GIF")
53     return "image/gif";
54 
55   return "";
56 }
57 
58 } // namespace
59 
pictureSha1() const60 boost::optional<std::string> UserToVCard::pictureSha1() const
61 {
62   if (Licq::Sha1::supported())
63     return myUser->pictureSha1();
64   else
65     return boost::none;
66 }
67 
createVCard() const68 gloox::VCard* UserToVCard::createVCard() const
69 {
70   gloox::VCard* card = new gloox::VCard;
71 
72   card->setJabberid(myUser->accountId());
73   card->setNickname(myUser->getAlias());
74   card->setFormattedname(myUser->getFullName());
75   card->setName(myUser->getLastName(), myUser->getFirstName());
76   if (!myUser->getEmail().empty())
77     card->addEmail(myUser->getEmail(), gloox::VCard::AddrTypePref);
78 
79   std::ostringstream tz;
80   int offset = myUser->timezone();
81   if (offset == User::TimezoneUnknown)
82     tz << "-00:00";
83   else
84   {
85     tz << (offset >= 0 ? '+' : '-')
86        << std::setw(2) << std::setfill('0')
87        << std::abs(offset) / 3600
88        << ':'
89        << std::setw(2) << std::setfill('0')
90        << std::abs(offset / 60) % 60;
91   }
92   card->setTz(tz.str());
93 
94   if (myUser->GetPicturePresent())
95   {
96     std::string pictureData;
97     if (myUser->readPictureData(pictureData))
98     {
99       // Only 8 KiB allowed according to XEP-0153
100       const size_t maxSize = 8 * 1024;
101 
102       if (pictureData.size() < maxSize)
103         card->setPhoto(guessPictureMimeType(pictureData), pictureData);
104       else
105         gLog.error("Picture is too large (%zu bytes); must be less than %zu",
106                    pictureData.size(), maxSize);
107     }
108   }
109 
110   return card;
111 }
112 
113 
VCardToUser(const gloox::VCard * vcard)114 VCardToUser::VCardToUser(const gloox::VCard* vcard)
115   : myVCard(vcard)
116 {
117   if (Licq::Sha1::supported())
118   {
119     const gloox::VCard::Photo& photo = myVCard->photo();
120     if (!photo.binval.empty())
121       myPictureSha1 = Licq::Sha1::hashToHexString(photo.binval);
122   }
123 }
124 
pictureSha1() const125 boost::optional<std::string> VCardToUser::pictureSha1() const
126 {
127   if (Licq::Sha1::supported())
128     return myPictureSha1;
129   else
130     return boost::none;
131 }
132 
updateUser(User * user) const133 int VCardToUser::updateUser(User* user) const
134 {
135   int saveGroup = User::SaveUserInfo;
136   user->SetEnableSave(false);
137 
138   if (!user->KeepAliasOnUpdate())
139   {
140     if (!myVCard->nickname().empty())
141       user->setAlias(myVCard->nickname());
142     else if (!myVCard->formattedname().empty())
143       user->setAlias(myVCard->formattedname());
144   }
145 
146   const gloox::VCard::Name& name = myVCard->name();
147   user->setUserInfoString("FirstName", name.given);
148   user->setUserInfoString("LastName", name.family);
149 
150   // Bug in gloox: emailAddresses should be const
151   const gloox::VCard::EmailList& emails =
152       const_cast<gloox::VCard*>(myVCard)->emailAddresses();
153   if (emails.begin() != emails.end())
154     user->setUserInfoString("Email1", emails.begin()->userid);
155 
156   const gloox::VCard::Photo& photo = myVCard->photo();
157   if (!photo.binval.empty())
158   {
159     saveGroup |= User::SavePictureInfo;
160 
161     // Store hash of too large picture as well to avoid downloading them again
162     if (Licq::Sha1::supported())
163       user->setPictureSha1(myPictureSha1);
164 
165     if (photo.binval.size() <= 100 * 1024)
166       user->SetPicturePresent(user->writePictureData(photo.binval));
167     else
168     {
169       gLog.error("Picture for %s is too big (%zu bytes)",
170                  user->id().accountId().c_str(), photo.binval.size());
171 
172       user->SetPicturePresent(false);
173       user->deletePictureData();
174     }
175   }
176   else if (user->GetPicturePresent())
177   {
178     saveGroup |= User::SavePictureInfo;
179 
180     user->setPictureSha1("");
181     user->SetPicturePresent(false);
182     user->deletePictureData();
183   }
184 
185   user->SetEnableSave(true);
186   user->save(saveGroup);
187 
188   return saveGroup;
189 }
190