Renga/jabber/JabberSpeak.cpp

844 lines
24 KiB
C++

//////////////////////////////////////////////////
// Blabber [JabberSpeak.cpp]
//////////////////////////////////////////////////
#include <gloox/jid.h>
#include <gloox/carbons.h>
#include <gloox/dataformitem.h>
#include <gloox/disco.h>
#include <gloox/forward.h>
#include <gloox/pubsubevent.h>
#include <gloox/registration.h>
#include <gloox/rostermanager.h>
#include <cstdio>
#include <Roster.h>
#include <unistd.h>
#include <sys/utsname.h>
#include "support/LogHandler.h"
#include "ui/ModalAlertFactory.h"
#include "BlabberApp.h"
#include "AgentList.h"
#include "GenericFunctions.h"
#include "JabberSpeak.h"
#include "JRoster.h"
#include "MessageRepeater.h"
#include "Messages.h"
#include "TalkManager.h"
#include "UserID.h"
#include "XMLEntity.h"
#include <stdlib.h>
#include <Path.h>
#include <FindDirectory.h>
#include <AppFileInfo.h>
#include <File.h>
JabberSpeak *JabberSpeak::_instance = NULL;
//////////////////////////////////////////////////
// CREATORS
//////////////////////////////////////////////////
// FIXME use the GlooxHandler instead for all XMPP things
JabberSpeak *JabberSpeak::Instance() {
if (_instance == NULL) {
_instance = new JabberSpeak();
}
return _instance;
}
JabberSpeak::JabberSpeak()
: XMLReader()
, fClient(NULL)
{
// grab a handle to the settings now for convenience later
_blabber_settings = BlabberSettings::Instance();
}
JabberSpeak::~JabberSpeak() {
_instance = NULL;
delete fVCardManager;
}
void JabberSpeak::Reset() {
if (!_reconnecting) {
BlabberMainWindow::Instance()->Lock();
BlabberMainWindow::Instance()->ShowLogin();
BlabberMainWindow::Instance()->Unlock();
}
if (!_reconnecting) {
BookmarkManager::Instance().Disconnect();
TalkManager::Instance()->Reset();
}
JRoster::Instance()->Lock();
JRoster::Instance()->RemoveAllUsers();
JRoster::Instance()->Unlock();
JRoster::Instance()->RefreshRoster();
// reset agent list
AgentList::Instance()->RemoveAllAgents();
}
void JabberSpeak::JabberSpeakReset() {
_curr_realname = "";
_curr_login = "";
_password = "";
_am_logged_in = false;
_reconnecting = false;
_got_some_agent_info = false;
_iq_map.clear();
}
//////////////////////////////////////////////////
// STANDARD METHODS
//////////////////////////////////////////////////
const string JabberSpeak::CurrentRealName() const {
return _curr_realname;
}
const string JabberSpeak::CurrentLogin() const {
return _curr_login;
}
//////////////////////////////////////////////////
// INCOMING COMMUNICATION
//////////////////////////////////////////////////
void JabberSpeak::OnTag(XMLEntity *entity) {
char buffer[4096]; // general buffer space
string iq_id; // used for IQ tags
static int seen_streams = 0;
// handle disconnection
if (entity->IsCompleted() && !strcasecmp(entity->Name(), "stream:stream")) {
++seen_streams;
if (seen_streams % 2 == 1) {
Reset();
MessageRepeater::Instance()->PostMessage(JAB_DISCONNECTED);
}
return;
}
// handle IQs (always closing tags)
if (entity->IsCompleted() && !strcasecmp(entity->Name(), "iq")) {
if (entity->Attribute("id")) {
iq_id = entity->Attribute("id");
}
if (!strcasecmp(entity->Attribute("type"), "error")) {
// get the intent of the IQ message
if (_iq_map.count(iq_id) > 0) {
// process based on the intent
iq_intent intent = _iq_map[iq_id];
// for errors on registration
if (intent == REGISTER) {
if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) {
const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str();
sprintf(buffer, "You were refused registration information from the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data());
} else {
sprintf(buffer, "You were refused registration information from an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data());
}
ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
}
// for errors on registration
if (intent == SEND_REGISTER) {
if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) {
const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str();
sprintf(buffer, "Your registration attempt was refused by the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data());
} else {
sprintf(buffer, "Your registration attempt was refused by an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data());
}
ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
}
// for errors on unregistration
if (intent == UNREGISTER) {
if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) {
const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str();
sprintf(buffer, "You were refused unregistration information from the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data());
} else {
sprintf(buffer, "You were refused unregistration information from an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data());
}
ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
}
}
}
// handle RESULT only (success)
if (!strcasecmp(entity->Attribute("type"), "result")) {
// get the intent of the IQ message
if (_iq_map.count(iq_id) > 0) {
// process based on the intent
iq_intent intent = _iq_map[iq_id];
// for the agents list
if (intent == AGENTS) {
_ParseAgentList(entity);
}
// for completed registration
if (intent == SEND_REGISTER) {
if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) {
const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str();
sprintf(buffer, "Your registration attempt with the %s has been accepted.", agent_name);
AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->SetRegisteredFlag(true);
} else {
sprintf(buffer, "Your registration attempt with an unidentifying Jabber service has been accepted.");
}
ModalAlertFactory::Alert(buffer, "Yeah!", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
}
}
}
}
}
void JabberSpeak::_ParseAgentList(XMLEntity *iq_agent_entity) {
XMLEntity *entity = iq_agent_entity;
// go one level deep to query
if (entity->Child("query")) {
entity = entity->Child("query");
} else {
return;
}
// iterate through child 'item' tags
for (int i=0; i<entity->CountChildren(); ++i) {
// handle the item child
if (!strcasecmp(entity->Child(i)->Name(), "agent")) {
// create a new agent
Agent *agent = new Agent();
// add data about the agent
if (entity->Child(i)->Attribute("jid")) {
agent->SetJID(entity->Child(i)->Attribute("jid"));
}
if (entity->Child(i)->Child("name")) {
agent->SetName(entity->Child(i)->Child("name")->Data());
}
if (entity->Child(i)->Child("description")) {
agent->SetDescription(entity->Child(i)->Child("description")->Data());
}
if (entity->Child(i)->Child("service")) {
agent->SetService(entity->Child(i)->Child("service")->Data());
}
agent->SetRegisterableFlag(entity->Child(i)->Child("register"));
if (entity->Child(i)->Child("transport")) {
agent->SetTransport(entity->Child(i)->Child("transport")->Data());
}
agent->SetSearchableFlag(entity->Child(i)->Child("search"));
// add to the AgentList BUGBUG need a return value
AgentList::Instance()->AddAgent(agent);
}
}
_got_some_agent_info = true;
_am_logged_in = true;
}
//////////////////////////////////////////////////
// OUTGOING COMMUNICATION
//////////////////////////////////////////////////
void JabberSpeak::SendConnect(string username, string password, string realname, bool suppress_auto_connect) {
// if there's another application instance running, suppress auto-login
BList *teams = new BList;
// query for this app signature
be_roster->GetAppList("application/jabber", teams);
if ((username.size() == 0 || password.size() == 0) && teams->CountItems() > 1) {
suppress_auto_connect = true;
}
// if we don't have all the data, query for it
if (username.size() == 0 || password.size() == 0 || gloox::JID(username).server().size() == 0) {
// check auto-login
if (suppress_auto_connect == false && _blabber_settings->Tag("auto-login")) {
// last login data should be used (make sure it's there though)
realname = (_blabber_settings->Data("last-realname")) ? _blabber_settings->Data("last-realname") : "";
username = (_blabber_settings->Data("last-login")) ? _blabber_settings->Data("last-login") : "";
password = (_blabber_settings->Data("last-password")) ? _blabber_settings->Data("last-password") : "";
}
// if we still don't have all the data, query for it
if (username.size() == 0 || password.size() == 0 || gloox::JID(username).server().size() == 0) {
BlabberMainWindow::Instance()->Lock();
BlabberMainWindow::Instance()->ShowLogin();
BlabberMainWindow::Instance()->Unlock();
return;
}
}
// save username/password
_curr_realname = realname;
_curr_login = username;
_password = password;
// PLACEHOLDER
// spawn listener thread (communication from remote machine)
resume_thread(_connection_thread_id = spawn_thread(JabberSpeak::_SpawnConnectionThread, "connection_listener", B_LOW_PRIORITY, this));
}
int32 JabberSpeak::_SpawnConnectionThread(void *obj) {
((JabberSpeak *)obj)->_ConnectionThread();
// Don't care about the return value
return 1;
}
string
JabberSpeak::GetRealServer()
{
return gloox::JID(_curr_login).server();
}
int
JabberSpeak::GetRealPort()
{
return 5222; //default jabber port.
}
void JabberSpeak::_ConnectionThread() {
gloox::JID jid(_curr_login);
fClient = new gloox::Client(jid, _password);
// Prepare for handling carbons
fClient->registerStanzaExtension(new gloox::Forward());
fClient->registerStanzaExtension(new gloox::Carbons());
// Register for roster events
fClient->rosterManager()->registerRosterListener(JRoster::Instance());
// Register for logging
fClient->logInstance().registerLogHandler(gloox::LogLevelDebug,
gloox::LogAreaXmlOutgoing, new LogHandler);
// Register for connection events
fClient->registerConnectionListener(this);
// Register for incoming chat sessions
fClient->registerMessageSessionHandler(TalkManager::Instance());
// Setup for handling VCard
fVCardManager = new gloox::VCardManager(fClient);
// Prepare our answer to version requests for disco
_ProcessVersionRequest();
// Set up for getting avatar notifications
fClient->disco()->addFeature("urn:xmpp:avatar:metadata+notify");
fPubSubManager = new gloox::PubSub::Manager(fClient);
fClient->registerStanzaExtension(new gloox::PubSub::Event());
// And let's go!
fClient->connect();
}
void JabberSpeak::SendDisconnect() {
_am_logged_in = false;
fClient->disconnect();
}
void JabberSpeak::SendSubscriptionRequest(string username) {
gloox::Subscription subscription(gloox::Subscription::Subscribe,
gloox::JID(username));
fClient->send(subscription);
}
void JabberSpeak::SendUnsubscriptionRequest(string username) {
gloox::Subscription subscription(gloox::Subscription::Unsubscribe,
gloox::JID(username));
fClient->send(subscription);
}
void JabberSpeak::SetFriendlyName(const gloox::JID& who, BString friendlyName)
{
gloox::RosterItem* item = fClient->rosterManager()->getRosterItem(who);
if (item) {
item->setName(friendlyName.String());
fClient->rosterManager()->synchronize();
} else {
const gloox::ConferenceListItem* bookmark
= BookmarkManager::Instance().GetBookmark(who.full().c_str());
if (bookmark) {
BookmarkManager::Instance().SetBookmark(who.full().c_str(),
bookmark->nick.c_str(), friendlyName, bookmark->autojoin);
} else {
debugger("User not found.");
}
}
}
void JabberSpeak::RemoveFromRoster(const UserID *removed_user) {
fClient->rosterManager()->remove(gloox::JID(removed_user->Handle()));
}
void JabberSpeak::SendMessage(const gloox::Message::MessageType type,
const gloox::JID& user, string body, string thread_id)
{
gloox::Message message(type, user, body, gloox::EmptyString, thread_id);
fClient->send(message);
}
void JabberSpeak::SendMessage(const gloox::Message::MessageType type,
string group_room, string body) {
gloox::Message message(type, gloox::JID(group_room), body);
fClient->send(message);
}
void JabberSpeak::SendPresence(gloox::Presence::PresenceType type, string status) {
gloox::Presence presence(type, gloox::JID(), status);
fClient->send(presence);
}
void JabberSpeak::SendLastPresence() {
bool last_used_custom = BlabberSettings::Instance()->Tag("last-used-custom-status");
gloox::Presence::PresenceType last_status = (gloox::Presence::PresenceType)atoi(
BlabberSettings::Instance()->Data("last-custom-exact-status"));
const char *last_custom_status = BlabberSettings::Instance()->Data("last-custom-more-exact-status");
if (last_used_custom) {
SendPresence(last_status, last_custom_status);
BlabberMainWindow::Instance()->SetCustomStatus(last_custom_status);
} else {
if (last_status == gloox::Presence::Chat) {
BlabberMainWindow::Instance()->PostMessage(BLAB_AVAILABLE_FOR_CHAT);
} else if (last_status == gloox::Presence::Away) {
BlabberMainWindow::Instance()->PostMessage(BLAB_AWAY_TEMPORARILY);
} else if (last_status == gloox::Presence::XA) {
BlabberMainWindow::Instance()->PostMessage(BLAB_AWAY_EXTENDED);
} else if (last_status == gloox::Presence::DND) {
BlabberMainWindow::Instance()->PostMessage(BLAB_DO_NOT_DISTURB);
} else {
BlabberMainWindow::Instance()->PostMessage(BLAB_AVAILABLE_FOR_CHAT);
}
}
}
void JabberSpeak::SendGroupUnvitation(string _group_room, string _group_username) {
// assemble group ID
string group_presence = _group_room + "/" + _group_username;
// Send presence Stanza
gloox::Presence presence(gloox::Presence::Unavailable, gloox::JID(group_presence));
fClient->send(presence);
// Disable autologin in bookmarks and store username
const gloox::ConferenceListItem* item
= BookmarkManager::Instance().GetBookmark(_group_room.c_str());
if (item) {
BookmarkManager::Instance().SetBookmark(_group_room.c_str(),
_group_username.c_str(), item->name.c_str(), false);
}
}
void JabberSpeak::RegisterWithAgent(string) {
#if 0
XMLEntity *entity;
// assemble attributes;
strcpy(atts[0], "type");
strcpy(atts[1], "get");
strcpy(atts[2], "id");
//strcpy(atts[3], GenericFunctions::GenerateUniqueID().c_str());
strcpy(atts[4], "to");
strcpy(atts[5], AgentList::Instance()->GetAgentByService(agent)->JID().c_str());
strcpy(atts_query[0], "xmlns");
strcpy(atts_query[1], "jabber:iq:register");
// send XML command
entity = new XMLEntity("iq", (const char **)atts);
entity->AddChild("query", (const char **)atts_query, NULL);
// log command
_iq_map[atts[3]] = REGISTER;
char *str = entity->ToString();
free(str);
delete entity;
#endif
}
void JabberSpeak::UnregisterWithAgent(string agent) {
// Find registration in roster
JRoster::Instance()->Lock();
UserID *user = NULL;
if (AgentList::Instance()->GetAgentByService(agent)) {
user = JRoster::Instance()->FindUser(JRoster::TRANSPORT_ID, AgentList::Instance()->GetAgentByService(agent)->JID());
}
// call RemoveFromRoster()
if (user) {
RemoveFromRoster(user);
}
JRoster::Instance()->Unlock();
}
void JabberSpeak::RequestVCard(const gloox::JID& jid)
{
fVCardManager->fetchVCard(jid, this);
}
void
JabberSpeak::RequestPubSubItem(const gloox::JID& jid, const std::string& node,
const std::string& subid, gloox::PubSub::ResultHandler* handler)
{
fPubSubManager->requestItems(jid, node, subid, 1, handler);
}
void JabberSpeak::_ProcessVersionRequest(void) {
string strVersion("Haiku");
BPath path;
if (find_directory(B_BEOS_LIB_DIRECTORY, &path) == B_OK) {
path.Append("libbe.so");
BAppFileInfo appFileInfo;
version_info versionInfo;
BFile file;
if (file.SetTo(path.Path(), B_READ_ONLY) == B_OK
&& appFileInfo.SetTo(&file) == B_OK
&& appFileInfo.GetVersionInfo(&versionInfo,
B_APP_VERSION_KIND) == B_OK
&& versionInfo.short_info[0] != '\0')
strVersion = versionInfo.short_info;
if (strVersion == "Walter")
strVersion = "Haiku";
BString appVersion;
appVersion << " " << versionInfo.major << "." << versionInfo.middle;
if (versionInfo.minor > 0)
appVersion << "." << versionInfo.minor;
// Add the version variety
const char* variety = NULL;
switch (versionInfo.variety) {
case B_DEVELOPMENT_VERSION:
variety = "development";
break;
case B_ALPHA_VERSION:
variety = "alpha";
break;
case B_BETA_VERSION:
variety = "beta";
break;
case B_GAMMA_VERSION:
variety = "gamma";
break;
case B_GOLDEN_MASTER_VERSION:
variety = "gold master";
break;
}
if (variety)
appVersion << "-" << variety;
strVersion += appVersion;
}
fClient->disco()->setVersion("Renga", APP_VERSION, strVersion);
}
void
JabberSpeak::onConnect()
{
fprintf(stderr, "Logged in!\n");
MessageRepeater::Instance()->PostMessage(JAB_LOGGED_IN);
//SendLastPresence();
BookmarkManager::Instance().Connect();
// Enable message carbons
gloox::IQ iq(gloox::IQ::Set, gloox::JID());
iq.addExtension(new gloox::Carbons(gloox::Carbons::Enable));
fClient->send(iq, this, 1);
_reconnecting = false;
}
void
JabberSpeak::onDisconnect(gloox::ConnectionError e)
{
BookmarkManager::Instance().Disconnect();
if (e == gloox::ConnAuthenticationFailed) {
gloox::AuthenticationError ae = fClient->authError();
if (ae == gloox::SaslNotAuthorized) {
// Incorrect login or password, highlight them in main window
// The code below gets us back to login screen
SendNotices(kAuthenticationFailed);
} else {
fprintf(stderr, "%s(%d) -> %d\n", __PRETTY_FUNCTION__, e, ae);
}
} else {
fprintf(stderr, "%s(%d)\n", __PRETTY_FUNCTION__, e);
}
// reset XMLReader
XMLReader::Reset();
if (_am_logged_in) {
// automatic reconnection
_reconnecting = true;
MessageRepeater::Instance()->PostMessage(JAB_RECONNECTING);
_got_some_agent_info = false;
_am_logged_in = false;
// reset networking
Reset();
SendConnect();
}
}
bool
JabberSpeak::onTLSConnect(const gloox::CertInfo& info)
{
// Certificate is verified as valid by gloox already
return info.status == gloox::CertOk;
}
bool
JabberSpeak::handleIq(const gloox::IQ&)
{
return true;
}
void
JabberSpeak::handleIqID(const gloox::IQ&, int)
{
}
//#pragma mark - VCardHandler
void
JabberSpeak::handleVCard(const gloox::JID& jid, const gloox::VCard* vcard)
{
BMessage message;
message.AddString("jid", jid.bare().c_str());
if (!vcard->formattedname().empty())
message.AddString("Name", vcard->formattedname().c_str());
if (!vcard->nickname().empty())
message.AddString("Nickname", vcard->nickname().c_str());
if (!vcard->url().empty())
message.AddString("url", vcard->url().c_str());
if (!vcard->bday().empty())
message.AddString("Birhtday", vcard->bday().c_str());
if (!vcard->title().empty())
message.AddString("Title", vcard->title().c_str());
if (!vcard->role().empty())
message.AddString("Role", vcard->role().c_str());
if (!vcard->note().empty())
message.AddString("Note", vcard->note().c_str());
if (!vcard->desc().empty())
message.AddString("Description", vcard->desc().c_str());
if (!vcard->mailer().empty())
message.AddString("Mailer", vcard->mailer().c_str());
if (!vcard->rev().empty())
message.AddString("Revision", vcard->rev().c_str());
if (!vcard->uid().empty())
message.AddString("User ID", vcard->uid().c_str());
if (!vcard->tz().empty())
message.AddString("Timezone", vcard->tz().c_str());
if (!vcard->sortstring().empty())
message.AddString("Sort String", vcard->sortstring().c_str());
if (!vcard->org().name.empty())
message.AddString("Organization", vcard->org().name.c_str());
for (std::string unit: vcard->org().units)
message.AddString("Unit", unit.c_str());
// Things not meant for direct display as strings
for (gloox::VCard::Email email: vcard->emailAddresses()) {
message.AddString("mail:address", email.userid.c_str());
int32 flags = 0;
if (email.pref)
flags |= 1;
if (email.home)
flags |= 2;
if (email.work)
flags |= 4;
if (email.internet)
flags |= 8;
if (email.x400)
flags |= 16;
message.AddInt32("mail:flags", flags);
}
for (gloox::VCard::Telephone tel: vcard->telephone()) {
message.AddString("phone:number", tel.number.c_str());
int32 flags = 0;
if (tel.pref)
flags |= 1;
if (tel.home)
flags |= 2;
if (tel.work)
flags |= 4;
if (tel.voice)
flags |= 8;
if (tel.fax)
flags |= 16;
if (tel.pager)
flags |= 32;
if (tel.msg)
flags |= 64;
if (tel.cell)
flags |= 128;
if (tel.video)
flags |= 256;
if (tel.bbs)
flags |= 512;
if (tel.modem)
flags |= 1024;
if (tel.isdn)
flags |= 2048;
if (tel.pcs)
flags |= 4096;
message.AddInt32("phone:flags", flags);
}
for (gloox::VCard::Label label: vcard->labels()) {
BString str;
for (auto line: label.lines)
str << line.c_str() << "\n";
message.AddString("label:lines", str);
int32 flags = 0;
if (label.pref)
flags |= 1;
if (label.home)
flags |= 2;
if (label.work)
flags |= 4;
if (label.postal)
flags |= 8;
if (label.parcel)
flags |= 16;
if (label.dom)
flags |= 32;
if (label.intl)
flags |= 64;
message.AddInt32("label:flags", flags);
}
for (gloox::VCard::Address address: vcard->addresses()) {
// Allow empty strings because we don't want to mix different addresses
// and all we get to differenciate them is the offset in the BMessage
message.AddString("address:pobox", address.pobox.c_str());
message.AddString("address:extadd", address.extadd.c_str());
message.AddString("address:street", address.street.c_str());
message.AddString("address:locality", address.locality.c_str());
message.AddString("address:region", address.region.c_str());
message.AddString("address:postcode", address.pcode.c_str());
message.AddString("address:country", address.ctry.c_str());
int32 flags = 0;
if (address.pref)
flags |= 1;
if (address.home)
flags |= 2;
if (address.work)
flags |= 4;
if (address.postal)
flags |= 8;
if (address.parcel)
flags |= 16;
if (address.dom)
flags |= 32;
if (address.intl)
flags |= 64;
message.AddInt32("address:flags", flags);
}
if (!vcard->geo().latitude.empty())
message.AddString("geo:lat", vcard->geo().latitude.c_str());
if (!vcard->geo().longitude.empty())
message.AddString("geo:lon", vcard->geo().longitude.c_str());
message.AddInt32("classification", vcard->classification());
if (!vcard->photo().binval.empty()) {
message.AddData("photo:binval", B_RAW_TYPE,
vcard->photo().binval.c_str(), vcard->photo().binval.length());
}
if (!vcard->photo().extval.empty())
message.AddString("photo:url", vcard->photo().extval.c_str());
if (!vcard->photo().type.empty()) {
message.AddData("photo:type", B_MIME_TYPE,
vcard->photo().type.c_str(), vcard->photo().type.length());
}
if (!vcard->logo().binval.empty()) {
message.AddData("logo:binval", B_RAW_TYPE,
vcard->logo().binval.c_str(), vcard->logo().binval.length());
}
if (!vcard->logo().extval.empty())
message.AddString("logo:url", vcard->logo().extval.c_str());
if (!vcard->logo().type.empty()) {
message.AddData("logo:type", B_MIME_TYPE,
vcard->logo().type.c_str(), vcard->logo().type.length());
}
SendNotices(kVCardReceived, &message);
}
void
JabberSpeak::handleVCardResult(VCardContext,
const gloox::JID&, gloox::StanzaError)
{
puts(__func__);
}