/* Jabber Protocol Plugin for Miranda IM Copyright (C) 2002-04 Santithorn Bunchua Copyright (C) 2005-12 George Hazan Copyright (C) 2007 Maxim Mluhov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "jabber.h" #include "jabber_list.h" #include "jabber_iq.h" #include "jabber_caps.h" #include "jabber_privacy.h" #include "m_genmenu.h" #include "m_clistint.h" void CJabberProto::OnIqResultServerDiscoInfo(HXML iqNode) { if ( !iqNode) return; const TCHAR *type = xmlGetAttrValue(iqNode, _T("type")); int i; if ( !_tcscmp(type, _T("result"))) { HXML query = xmlGetChildByTag(iqNode, "query", "xmlns", _T(JABBER_FEAT_DISCO_INFO)); if ( !query) return; HXML identity; for (i = 1; (identity = xmlGetNthChild(query, _T("identity"), i)) != NULL; i++) { const TCHAR *identityCategory = xmlGetAttrValue(identity, _T("category")); const TCHAR *identityType = xmlGetAttrValue(identity, _T("type")); const TCHAR *identityName = xmlGetAttrValue(identity, _T("name")); if (identityCategory && identityType && !_tcscmp(identityCategory, _T("pubsub")) && !_tcscmp(identityType, _T("pep"))) { m_bPepSupported = TRUE; EnableMenuItems(TRUE); RebuildInfoFrame(); } else if (identityCategory && identityType && identityName && !_tcscmp(identityCategory, _T("server")) && !_tcscmp(identityType, _T("im")) && !_tcscmp(identityName, _T("Google Talk"))) { m_ThreadInfo->jabberServerCaps |= JABBER_CAPS_PING; m_bGoogleTalk = true; // Google Shared Status m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultGoogleSharedStatus, JABBER_IQ_TYPE_GET)) << XQUERY(_T(JABBER_FEAT_GTALK_SHARED_STATUS)) << XATTR(_T("version"), _T("2"))); } } if (m_ThreadInfo) { HXML feature; for (i = 1; (feature = xmlGetNthChild(query, _T("feature"), i)) != NULL; i++) { const TCHAR *featureName = xmlGetAttrValue(feature, _T("var")); if (featureName) { for (int j = 0; g_JabberFeatCapPairs[j].szFeature; j++) { if ( !_tcscmp(g_JabberFeatCapPairs[j].szFeature, featureName)) { m_ThreadInfo->jabberServerCaps |= g_JabberFeatCapPairs[j].jcbCap; break; } } } } } OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_SERVERINFO); } } void CJabberProto::OnIqResultNestedRosterGroups(HXML iqNode, CJabberIqInfo* pInfo) { const TCHAR *szGroupDelimeter = NULL; BOOL bPrivateStorageSupport = FALSE; if (iqNode && pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) { bPrivateStorageSupport = TRUE; szGroupDelimeter = XPathFmt(iqNode, _T("query[@xmlns='%s']/roster[@xmlns='%s']"), _T(JABBER_FEAT_PRIVATE_STORAGE), _T(JABBER_FEAT_NESTED_ROSTER_GROUPS)); if (szGroupDelimeter && !szGroupDelimeter[0]) szGroupDelimeter = NULL; // "" as roster delimeter is not supported :) } // global fuckup if ( !m_ThreadInfo) return; // is our default delimiter? if ((!szGroupDelimeter && bPrivateStorageSupport) || (szGroupDelimeter && _tcscmp(szGroupDelimeter, _T("\\")))) m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext()) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILD(_T("roster"), _T("\\")) << XATTR(_T("xmlns"), _T(JABBER_FEAT_NESTED_ROSTER_GROUPS))); // roster request TCHAR *szUserData = mir_tstrdup(szGroupDelimeter ? szGroupDelimeter : _T("\\")); m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultGetRoster, JABBER_IQ_TYPE_GET, NULL, 0, -1, (void*)szUserData)) << XCHILDNS(_T("query"), _T(JABBER_FEAT_IQ_ROSTER))); } void CJabberProto::OnIqResultNotes(HXML iqNode, CJabberIqInfo* pInfo) { if (iqNode && pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) { HXML hXmlData = XPathFmt(iqNode, _T("query[@xmlns='%s']/storage[@xmlns='%s']"), _T(JABBER_FEAT_PRIVATE_STORAGE), _T(JABBER_FEAT_MIRANDA_NOTES)); if (hXmlData) m_notes.LoadXml(hXmlData); } } void CJabberProto::OnProcessLoginRq(ThreadData* info, DWORD rq) { if (info == NULL) return; info->dwLoginRqs |= rq; if ((info->dwLoginRqs & JABBER_LOGIN_ROSTER) && (info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS) && (info->dwLoginRqs & JABBER_LOGIN_SERVERINFO) && !(info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS_AJ)) { if (jabberChatDllPresent && m_options.AutoJoinBookmarks) { LIST ll(10); LISTFOREACH(i, this, LIST_BOOKMARK) { JABBER_LIST_ITEM* item = ListGetItemPtrFromIndex(i); if (item != NULL && !lstrcmp(item->type, _T("conference")) && item->bAutoJoin) ll.insert(item); } for (int j=0; j < ll.getCount(); j++) { JABBER_LIST_ITEM* item = ll[j]; TCHAR room[256], *server, *p; TCHAR text[128]; _tcsncpy(text, item->jid, SIZEOF(text)); _tcsncpy(room, text, SIZEOF(room)); p = _tcstok(room, _T("@")); server = _tcstok(NULL, _T("@")); if (item->nick && item->nick[0] != 0) GroupchatJoinRoom(server, p, item->nick, item->password, true); else { TCHAR* nick = JabberNickFromJID(m_szJabberJID); GroupchatJoinRoom(server, p, nick, item->password, true); mir_free(nick); } } ll.destroy(); } OnProcessLoginRq(info, JABBER_LOGIN_BOOKMARKS_AJ); } } void CJabberProto::OnLoggedIn() { m_bJabberOnline = TRUE; m_tmJabberLoggedInTime = time(0); m_ThreadInfo->dwLoginRqs = 0; // XEP-0083 support { CJabberIqInfo* pIqInfo = m_iqManager.AddHandler(&CJabberProto::OnIqResultNestedRosterGroups, JABBER_IQ_TYPE_GET); // ugly hack to prevent hangup during login process pIqInfo->SetTimeout(30000); m_ThreadInfo->send( XmlNodeIq(pIqInfo) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS(_T("roster"), _T(JABBER_FEAT_NESTED_ROSTER_GROUPS))); } // Server-side notes { m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultNotes, JABBER_IQ_TYPE_GET)) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS(_T("storage"), _T(JABBER_FEAT_MIRANDA_NOTES))); } int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_DISCOBOOKMARKS, &CJabberProto::OnIqResultDiscoBookmarks); m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS(_T("storage"), _T("storage:bookmarks"))); m_bPepSupported = FALSE; m_ThreadInfo->jabberServerCaps = JABBER_RESOURCE_CAPS_NONE; iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultServerDiscoInfo); m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, _A2T(m_ThreadInfo->server)) << XQUERY(_T(JABBER_FEAT_DISCO_INFO))); QueryPrivacyLists(m_ThreadInfo); char szServerName[ sizeof(m_ThreadInfo->server) ]; if (JGetStaticString("LastLoggedServer", NULL, szServerName, sizeof(szServerName))) SendGetVcard(m_szJabberJID); else if (strcmp(m_ThreadInfo->server, szServerName)) SendGetVcard(m_szJabberJID); JSetString(NULL, "LastLoggedServer", m_ThreadInfo->server); m_pepServices.ResetPublishAll(); } void CJabberProto::OnIqResultGetAuth(HXML iqNode) { // RECVED: result of the request for authentication method // ACTION: send account authentication information to log in Log(" iqIdGetAuth"); HXML queryNode; const TCHAR *type; if ((type=xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((queryNode=xmlGetChild(iqNode , "query")) == NULL) return; if ( !lstrcmp(type, _T("result"))) { int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultSetAuth); XmlNodeIq iq(_T("set"), iqId); HXML query = iq << XQUERY(_T("jabber:iq:auth")); query << XCHILD(_T("username"), m_ThreadInfo->username); if (xmlGetChild(queryNode, "digest") != NULL && m_szStreamId) { char* str = mir_utf8encodeT(m_ThreadInfo->password); char text[200]; mir_snprintf(text, SIZEOF(text), "%s%s", m_szStreamId, str); mir_free(str); if ((str=JabberSha1(text)) != NULL) { query << XCHILD(_T("digest"), _A2T(str)); mir_free(str); } } else if (xmlGetChild(queryNode, "password") != NULL) query << XCHILD(_T("password"), m_ThreadInfo->password); else { Log("No known authentication mechanism accepted by the server."); m_ThreadInfo->send(""); return; } if (xmlGetChild(queryNode , "resource") != NULL) query << XCHILD(_T("resource"), m_ThreadInfo->resource); m_ThreadInfo->send(iq); } else if ( !lstrcmp(type, _T("error"))) { m_ThreadInfo->send(""); TCHAR text[128]; mir_sntprintf(text, SIZEOF(text), _T("%s %s."), TranslateT("Authentication failed for"), m_ThreadInfo->username); MsgPopup(NULL, text, TranslateT("Jabber Authentication")); JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultSetAuth(HXML iqNode) { const TCHAR *type; // RECVED: authentication result // ACTION: if successfully logged in, continue by requesting roster list and set my initial status Log(" iqIdSetAuth"); if ((type=xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ( !lstrcmp(type, _T("result"))) { DBVARIANT dbv; if (JGetStringT(NULL, "Nick", &dbv)) JSetStringT(NULL, "Nick", m_ThreadInfo->username); else db_free(&dbv); OnLoggedIn(); } // What to do if password error? etc... else if ( !lstrcmp(type, _T("error"))) { TCHAR text[128]; m_ThreadInfo->send(""); mir_sntprintf(text, SIZEOF(text), _T("%s %s."), TranslateT("Authentication failed for"), m_ThreadInfo->username); MsgPopup(NULL, text, TranslateT("Jabber Authentication")); JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultBind(HXML iqNode, CJabberIqInfo* pInfo) { if ( !m_ThreadInfo || !iqNode) return; if (pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) { LPCTSTR szJid = XPathT(iqNode, "bind[@xmlns='urn:ietf:params:xml:ns:xmpp-bind']/jid"); if (szJid) { if ( !_tcsncmp(m_ThreadInfo->fullJID, szJid, SIZEOF(m_ThreadInfo->fullJID))) Log("Result Bind: " TCHAR_STR_PARAM " confirmed ", m_ThreadInfo->fullJID); else { Log("Result Bind: " TCHAR_STR_PARAM " changed to " TCHAR_STR_PARAM, m_ThreadInfo->fullJID, szJid); _tcsncpy(m_ThreadInfo->fullJID, szJid, SIZEOF(m_ThreadInfo->fullJID)); } } if (m_ThreadInfo->bIsSessionAvailable) m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultSession, JABBER_IQ_TYPE_SET)) << XCHILDNS(_T("session"), _T("urn:ietf:params:xml:ns:xmpp-session"))); else OnLoggedIn(); } else { //rfc3920 page 39 m_ThreadInfo->send(""); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultSession(HXML iqNode, CJabberIqInfo* pInfo) { if (pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) OnLoggedIn(); } void CJabberProto::GroupchatJoinByHContact(HANDLE hContact, bool autojoin) { TCHAR* roomjid = JGetStringT(hContact, "ChatRoomID"); if ( !roomjid) return; TCHAR* room = roomjid; TCHAR* server = _tcschr(roomjid, '@'); if ( !server) { mir_free(roomjid); return; } server[0] = 0; server++; TCHAR *nick = JGetStringT(hContact, "MyNick"); if ( !nick) { nick = JabberNickFromJID(m_szJabberJID); if ( !nick) { mir_free(roomjid); return; } } TCHAR *password = JGetStringCrypt(hContact, "LoginPassword"); GroupchatJoinRoom(server, room, nick, password, autojoin); mir_free(password); mir_free(nick); mir_free(roomjid); } ///////////////////////////////////////////////////////////////////////////////////////// // JabberIqResultGetRoster - populates LIST_ROSTER and creates contact for any new rosters void CJabberProto::OnIqResultGetRoster(HXML iqNode, CJabberIqInfo* pInfo) { Log(" iqIdGetRoster"); TCHAR *szGroupDelimeter = (TCHAR *)pInfo->GetUserData(); if (pInfo->GetIqType() != JABBER_IQ_TYPE_RESULT) { mir_free(szGroupDelimeter); return; } HXML queryNode = xmlGetChild(iqNode , "query"); if (queryNode == NULL) { mir_free(szGroupDelimeter); return; } if (lstrcmp(xmlGetAttrValue(queryNode, _T("xmlns")), _T(JABBER_FEAT_IQ_ROSTER))) { mir_free(szGroupDelimeter); return; } if ( !_tcscmp(szGroupDelimeter, _T("\\"))) { mir_free(szGroupDelimeter); szGroupDelimeter = NULL; } TCHAR* nick; int i; LIST chatRooms(10); OBJLIST *httpavatars = new OBJLIST(20, JABBER_HTTP_AVATARS::compare); for (i=0; ; i++) { BOOL bIsTransport=FALSE; HXML itemNode = xmlGetChild(queryNode ,i); if ( !itemNode) break; if (_tcscmp(xmlGetName(itemNode), _T("item"))) continue; const TCHAR *str = xmlGetAttrValue(itemNode, _T("subscription")), *name; JABBER_SUBSCRIPTION sub; if (str == NULL) sub = SUB_NONE; else if ( !_tcscmp(str, _T("both"))) sub = SUB_BOTH; else if ( !_tcscmp(str, _T("to"))) sub = SUB_TO; else if ( !_tcscmp(str, _T("from"))) sub = SUB_FROM; else sub = SUB_NONE; const TCHAR *jid = xmlGetAttrValue(itemNode, _T("jid")); if (jid == NULL) continue; if (_tcschr(jid, '@') == NULL) bIsTransport = TRUE; if ((name = xmlGetAttrValue(itemNode, _T("name"))) != NULL) nick = mir_tstrdup(name); else nick = JabberNickFromJID(jid); if (nick == NULL) continue; JABBER_LIST_ITEM* item = ListAdd(LIST_ROSTER, jid); item->subscription = sub; mir_free(item->nick); item->nick = nick; HXML groupNode = xmlGetChild(itemNode , "group"); replaceStrT(item->group, (groupNode) ? xmlGetText(groupNode) : NULL); // check group delimiters: if (item->group && szGroupDelimeter) { TCHAR *szPos = NULL; while (szPos = _tcsstr(item->group, szGroupDelimeter)) { *szPos = 0; szPos += _tcslen(szGroupDelimeter); TCHAR *szNewGroup = (TCHAR *)mir_alloc(sizeof(TCHAR) * (_tcslen(item->group) + _tcslen(szPos) + 2)); _tcscpy(szNewGroup, item->group); _tcscat(szNewGroup, _T("\\")); _tcscat(szNewGroup, szPos); mir_free(item->group); item->group = szNewGroup; } } HANDLE hContact = HContactFromJID(jid); if (hContact == NULL) { // Received roster has a new JID. // Add the jid (with empty resource) to Miranda contact list. hContact = DBCreateContact(jid, nick, FALSE, FALSE); } if (name != NULL) { DBVARIANT dbNick; if ( !JGetStringT(hContact, "Nick", &dbNick)) { if (lstrcmp(nick, dbNick.ptszVal) != 0) db_set_ts(hContact, "CList", "MyHandle", nick); else db_unset(hContact, "CList", "MyHandle"); db_free(&dbNick); } else db_set_ts(hContact, "CList", "MyHandle", nick); } else db_unset(hContact, "CList", "MyHandle"); if (JGetByte(hContact, "ChatRoom", 0)) { GCSESSION gcw = {0}; gcw.cbSize = sizeof(GCSESSION); gcw.iType = GCW_CHATROOM; gcw.pszModule = m_szModuleName; gcw.dwFlags = GC_TCHAR; gcw.ptszID = jid; gcw.ptszName = NEWTSTR_ALLOCA(jid); TCHAR* p = (TCHAR*)_tcschr(gcw.ptszName, '@'); if (p) *p = 0; CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcw); db_unset(hContact, "CList", "Hidden"); chatRooms.insert(hContact); } else { UpdateSubscriptionInfo(hContact, item); } if ( !m_options.IgnoreRosterGroups) { if (item->group != NULL) { JabberContactListCreateGroup(item->group); // Don't set group again if already correct, or Miranda may show wrong group count in some case DBVARIANT dbv; if ( !DBGetContactSettingTString(hContact, "CList", "Group", &dbv)) { if (lstrcmp(dbv.ptszVal, item->group)) db_set_ts(hContact, "CList", "Group", item->group); db_free(&dbv); } else db_set_ts(hContact, "CList", "Group", item->group); } else db_unset(hContact, "CList", "Group"); } if (hContact != NULL) { if (bIsTransport) JSetByte(hContact, "IsTransport", TRUE); else JSetByte(hContact, "IsTransport", FALSE); } const TCHAR *imagepath = xmlGetAttrValue(itemNode, _T("vz:img")); if (imagepath) httpavatars->insert(new JABBER_HTTP_AVATARS(imagepath, hContact)); } if (httpavatars->getCount()) JForkThread(&CJabberProto::LoadHttpAvatars, httpavatars); else delete httpavatars; // Delete orphaned contacts (if roster sync is enabled) if (m_options.RosterSync == TRUE) { int listSize = 0, listAllocSize = 0; HANDLE* list = NULL; HANDLE hContact = (HANDLE)db_find_first(); while (hContact != NULL) { char* str = GetContactProto(hContact); if (str != NULL && !strcmp(str, m_szModuleName)) { DBVARIANT dbv; if ( !JGetStringT(hContact, "jid", &dbv)) { if ( !ListExist(LIST_ROSTER, dbv.ptszVal)) { Log("Syncing roster: preparing to delete " TCHAR_STR_PARAM " (hContact=0x%x)", dbv.ptszVal, hContact); if (listSize >= listAllocSize) { listAllocSize = listSize + 100; if ((list=(HANDLE *) mir_realloc(list, listAllocSize * sizeof(HANDLE))) == NULL) { listSize = 0; break; } } list[listSize++] = hContact; } db_free(&dbv); } } hContact = db_find_next(hContact); } for (i=0; i < listSize; i++) { Log("Syncing roster: deleting 0x%x", list[i]); CallService(MS_DB_CONTACT_DELETE, (WPARAM)list[i], 0); } if (list != NULL) mir_free(list); } EnableMenuItems(TRUE); Log("Status changed via THREADSTART"); m_bModeMsgStatusChangePending = FALSE; SetServerStatus(m_iDesiredStatus); if (m_options.AutoJoinConferences) { for (i=0; i < chatRooms.getCount(); i++) GroupchatJoinByHContact((HANDLE)chatRooms[i], true); } chatRooms.destroy(); //UI_SAFE_NOTIFY(m_pDlgJabberJoinGroupchat, WM_JABBER_CHECK_ONLINE); //UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_CHECK_ONLINE); UI_SAFE_NOTIFY_HWND(m_hwndJabberAddBookmark, WM_JABBER_CHECK_ONLINE); WindowNotify(WM_JABBER_CHECK_ONLINE); UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH); if (szGroupDelimeter) mir_free(szGroupDelimeter); OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_ROSTER); RebuildInfoFrame(); } void CJabberProto::OnIqResultGetRegister(HXML iqNode) { // RECVED: result of the request for (agent) registration mechanism // ACTION: activate (agent) registration input dialog Log(" iqIdGetRegister"); HXML queryNode; const TCHAR *type; if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((queryNode = xmlGetChild(iqNode , "query")) == NULL) return; if ( !lstrcmp(type, _T("result"))) { if (m_hwndAgentRegInput) SendMessage(m_hwndAgentRegInput, WM_JABBER_REGINPUT_ACTIVATE, 1 /*success*/, (LPARAM)xi.copyNode(iqNode)); } else if ( !lstrcmp(type, _T("error"))) { if (m_hwndAgentRegInput) { HXML errorNode = xmlGetChild(iqNode , "error"); TCHAR* str = JabberErrorMsg(errorNode); SendMessage(m_hwndAgentRegInput, WM_JABBER_REGINPUT_ACTIVATE, 0 /*error*/, (LPARAM)str); mir_free(str); } } } void CJabberProto::OnIqResultSetRegister(HXML iqNode) { // RECVED: result of registration process // ACTION: notify of successful agent registration Log(" iqIdSetRegister"); const TCHAR *type, *from; if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((from = xmlGetAttrValue(iqNode, _T("from"))) == NULL) return; if ( !lstrcmp(type, _T("result"))) { HANDLE hContact = HContactFromJID(from); if (hContact != NULL) JSetByte(hContact, "IsTransport", TRUE); if (m_hwndRegProgress) SendMessage(m_hwndRegProgress, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Registration successful")); } else if ( !lstrcmp(type, _T("error"))) { if (m_hwndRegProgress) { HXML errorNode = xmlGetChild(iqNode , "error"); TCHAR* str = JabberErrorMsg(errorNode); SendMessage(m_hwndRegProgress, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)str); mir_free(str); } } } ///////////////////////////////////////////////////////////////////////////////////////// // JabberIqResultGetVcard - processes the server-side v-card void CJabberProto::OnIqResultGetVcardPhoto(const TCHAR *jid, HXML n, HANDLE hContact, BOOL& hasPhoto) { Log("JabberIqResultGetVcardPhoto: %d", hasPhoto); if (hasPhoto) return; HXML o = xmlGetChild(n , "BINVAL"); if (o == NULL || xmlGetText(o) == NULL) return; int bufferLen; char* buffer = JabberBase64DecodeT(xmlGetText(o), &bufferLen); if (buffer == NULL) return; const TCHAR *szPicType; HXML m = xmlGetChild(n , "TYPE"); if (m == NULL || xmlGetText(m) == NULL) { LBL_NoTypeSpecified: switch(JabberGetPictureType(buffer)) { case PA_FORMAT_GIF: szPicType = _T("image/gif"); break; case PA_FORMAT_BMP: szPicType = _T("image/bmp"); break; case PA_FORMAT_PNG: szPicType = _T("image/png"); break; case PA_FORMAT_JPEG: szPicType = _T("image/jpeg"); break; default: LBL_Ret: mir_free(buffer); return; } } else { const TCHAR *tszType = xmlGetText(m); if ( !_tcscmp(tszType, _T("image/jpeg")) || !_tcscmp(tszType, _T("image/png")) || !_tcscmp(tszType, _T("image/gif")) || !_tcscmp(tszType, _T("image/bmp"))) szPicType = tszType; else goto LBL_NoTypeSpecified; } TCHAR szAvatarFileName[MAX_PATH]; GetAvatarFileName(hContact, szAvatarFileName, SIZEOF(szAvatarFileName)); Log("Picture file name set to " TCHAR_STR_PARAM, szAvatarFileName); HANDLE hFile = CreateFile(szAvatarFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) goto LBL_Ret; Log("Writing %d bytes", bufferLen); DWORD nWritten; if ( !WriteFile(hFile, buffer, bufferLen, &nWritten, NULL)) goto LBL_Ret; CloseHandle(hFile); Log("%d bytes written", nWritten); if (hContact == NULL) { hasPhoto = TRUE; CallService(MS_AV_SETMYAVATART, (WPARAM)m_szModuleName, (LPARAM)szAvatarFileName); Log("My picture saved to " TCHAR_STR_PARAM, szAvatarFileName); } else { DBVARIANT dbv; if ( !JGetStringT(hContact, "jid", &dbv)) { JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_ROSTER, jid); if (item == NULL) { item = ListAdd(LIST_VCARD_TEMP, jid); // adding to the temp list to store information about photo item->bUseResource = TRUE; } if (item != NULL) { hasPhoto = TRUE; if (item->photoFileName) DeleteFile(item->photoFileName); replaceStrT(item->photoFileName, szAvatarFileName); Log("Contact's picture saved to " TCHAR_STR_PARAM, szAvatarFileName); if (JGetWord(hContact, "Status", ID_STATUS_OFFLINE) == ID_STATUS_OFFLINE) { char szHashValue[ MAX_PATH ]; if (JGetStaticString("AvatarHash", hContact, szHashValue, sizeof(szHashValue))) OnIqResultGotAvatar(hContact, o, xmlGetText(m)); } } db_free(&dbv); } } if ( !hasPhoto) DeleteFile(szAvatarFileName); goto LBL_Ret; } static TCHAR* sttGetText(HXML node, char* tag) { HXML n = xmlGetChild(node , tag); if (n == NULL) return NULL; return (TCHAR*)xmlGetText(n); } void CJabberProto::OnIqResultGetVcard(HXML iqNode) { HXML vCardNode, m, n, o; const TCHAR *type, *jid; HANDLE hContact; TCHAR text[128]; DBVARIANT dbv; Log(" iqIdGetVcard"); if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((jid = xmlGetAttrValue(iqNode, _T("from"))) == NULL) return; int id = JabberGetPacketID(iqNode); if (id == m_nJabberSearchID) { m_nJabberSearchID = -1; if ((vCardNode = xmlGetChild(iqNode , "vCard")) != NULL) { if ( !lstrcmp(type, _T("result"))) { JABBER_SEARCH_RESULT jsr = { 0 }; jsr.hdr.cbSize = sizeof(JABBER_SEARCH_RESULT); jsr.hdr.flags = PSR_TCHAR; jsr.hdr.nick = sttGetText(vCardNode, "NICKNAME"); jsr.hdr.firstName = sttGetText(vCardNode, "FN"); jsr.hdr.lastName = _T(""); jsr.hdr.email = sttGetText(vCardNode, "EMAIL"); _tcsncpy(jsr.jid, jid, SIZEOF(jsr.jid)); jsr.jid[ SIZEOF(jsr.jid)-1 ] = '\0'; JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&jsr); JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } else if ( !lstrcmp(type, _T("error"))) JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } else JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); return; } size_t len = _tcslen(m_szJabberJID); if ( !_tcsnicmp(jid, m_szJabberJID, len) && (jid[len]=='/' || jid[len]=='\0')) { hContact = NULL; Log("Vcard for myself"); } else { if ((hContact = HContactFromJID(jid)) == NULL) return; Log("Other user's vcard"); } if ( !lstrcmp(type, _T("result"))) { BOOL hasFn, hasNick, hasGiven, hasFamily, hasMiddle, hasBday, hasGender; BOOL hasPhone, hasFax, hasCell, hasUrl; BOOL hasHome, hasHomeStreet, hasHomeStreet2, hasHomeLocality, hasHomeRegion, hasHomePcode, hasHomeCtry; BOOL hasWork, hasWorkStreet, hasWorkStreet2, hasWorkLocality, hasWorkRegion, hasWorkPcode, hasWorkCtry; BOOL hasOrgname, hasOrgunit, hasRole, hasTitle; BOOL hasDesc, hasPhoto; int nEmail, nPhone, nYear, nMonth, nDay; hasFn = hasNick = hasGiven = hasFamily = hasMiddle = hasBday = hasGender = FALSE; hasPhone = hasFax = hasCell = hasUrl = FALSE; hasHome = hasHomeStreet = hasHomeStreet2 = hasHomeLocality = hasHomeRegion = hasHomePcode = hasHomeCtry = FALSE; hasWork = hasWorkStreet = hasWorkStreet2 = hasWorkLocality = hasWorkRegion = hasWorkPcode = hasWorkCtry = FALSE; hasOrgname = hasOrgunit = hasRole = hasTitle = FALSE; hasDesc = hasPhoto = FALSE; nEmail = nPhone = 0; if ((vCardNode = xmlGetChild(iqNode , "vCard")) != NULL) { for (int i=0; ; i++) { n = xmlGetChild(vCardNode ,i); if ( !n) break; if (xmlGetName(n) == NULL) continue; if ( !_tcscmp(xmlGetName(n), _T("FN"))) { if (xmlGetText(n) != NULL) { hasFn = TRUE; JSetStringT(hContact, "FullName", xmlGetText(n)); } } else if ( !lstrcmp(xmlGetName(n), _T("NICKNAME"))) { if (xmlGetText(n) != NULL) { hasNick = TRUE; JSetStringT(hContact, "Nick", xmlGetText(n)); } } else if ( !lstrcmp(xmlGetName(n), _T("N"))) { // First/Last name if ( !hasGiven && !hasFamily && !hasMiddle) { if ((m=xmlGetChild(n , "GIVEN")) != NULL && xmlGetText(m)!=NULL) { hasGiven = TRUE; JSetStringT(hContact, "FirstName", xmlGetText(m)); } if ((m=xmlGetChild(n , "FAMILY")) != NULL && xmlGetText(m)!=NULL) { hasFamily = TRUE; JSetStringT(hContact, "LastName", xmlGetText(m)); } if ((m=xmlGetChild(n , "MIDDLE")) != NULL && xmlGetText(m) != NULL) { hasMiddle = TRUE; JSetStringT(hContact, "MiddleName", xmlGetText(m)); } } } else if ( !lstrcmp(xmlGetName(n), _T("EMAIL"))) { // E-mail address(es) if ((m=xmlGetChild(n , "USERID")) == NULL) // Some bad client put e-mail directly in instead of m = n; if (xmlGetText(m) != NULL) { char text[100]; if (hContact != NULL) { if (nEmail == 0) strcpy(text, "e-mail"); else sprintf(text, "e-mail%d", nEmail-1); } else sprintf(text, "e-mail%d", nEmail); JSetStringT(hContact, text, xmlGetText(m)); if (hContact == NULL) { sprintf(text, "e-mailFlag%d", nEmail); int nFlag = 0; if (xmlGetChild(n , "HOME") != NULL) nFlag |= JABBER_VCEMAIL_HOME; if (xmlGetChild(n , "WORK") != NULL) nFlag |= JABBER_VCEMAIL_WORK; if (xmlGetChild(n , "INTERNET") != NULL) nFlag |= JABBER_VCEMAIL_INTERNET; if (xmlGetChild(n , "X400") != NULL) nFlag |= JABBER_VCEMAIL_X400; JSetWord(NULL, text, nFlag); } nEmail++; } } else if ( !lstrcmp(xmlGetName(n), _T("BDAY"))) { // Birthday if ( !hasBday && xmlGetText(n)!=NULL) { if (hContact != NULL) { if (_stscanf(xmlGetText(n), _T("%d-%d-%d"), &nYear, &nMonth, &nDay) == 3) { hasBday = TRUE; JSetWord(hContact, "BirthYear", (WORD)nYear); JSetByte(hContact, "BirthMonth", (BYTE) nMonth); JSetByte(hContact, "BirthDay", (BYTE) nDay); SYSTEMTIME sToday = {0}; GetLocalTime(&sToday); int nAge = sToday.wYear - nYear; if (sToday.wMonth < nMonth || (sToday.wMonth == nMonth && sToday.wDay < nDay)) nAge--; if (nAge) JSetWord(hContact, "Age", (WORD)nAge); } } else { hasBday = TRUE; JSetStringT(NULL, "BirthDate", xmlGetText(n)); } } } else if ( !lstrcmp(xmlGetName(n), _T("GENDER"))) { // Gender if ( !hasGender && xmlGetText(n)!=NULL) { if (hContact != NULL) { if (xmlGetText(n)[0] && strchr("mMfF", xmlGetText(n)[0])!=NULL) { hasGender = TRUE; JSetByte(hContact, "Gender", (BYTE) toupper(xmlGetText(n)[0])); } } else { hasGender = TRUE; JSetStringT(NULL, "GenderString", xmlGetText(n)); } } } else if ( !lstrcmp(xmlGetName(n), _T("ADR"))) { if ( !hasHome && xmlGetChild(n , "HOME")!=NULL) { // Home address hasHome = TRUE; if ((m=xmlGetChild(n , "STREET")) != NULL && xmlGetText(m) != NULL) { hasHomeStreet = TRUE; if (hContact != NULL) { if ((o=xmlGetChild(n , "EXTADR")) != NULL && xmlGetText(o) != NULL) mir_sntprintf(text, SIZEOF(text), _T("%s\r\n%s"), xmlGetText(m), xmlGetText(o)); else if ((o=xmlGetChild(n , "EXTADD"))!=NULL && xmlGetText(o)!=NULL) mir_sntprintf(text, SIZEOF(text), _T("%s\r\n%s"), xmlGetText(m), xmlGetText(o)); else _tcsncpy(text, xmlGetText(m), SIZEOF(text)); text[SIZEOF(text)-1] = '\0'; JSetStringT(hContact, "Street", text); } else { JSetStringT(hContact, "Street", xmlGetText(m)); if ((m=xmlGetChild(n , "EXTADR")) == NULL) m = xmlGetChild(n , "EXTADD"); if (m!=NULL && xmlGetText(m)!=NULL) { hasHomeStreet2 = TRUE; JSetStringT(hContact, "Street2", xmlGetText(m)); } } } if ((m=xmlGetChild(n , "LOCALITY"))!=NULL && xmlGetText(m)!=NULL) { hasHomeLocality = TRUE; JSetStringT(hContact, "City", xmlGetText(m)); } if ((m=xmlGetChild(n , "REGION"))!=NULL && xmlGetText(m)!=NULL) { hasHomeRegion = TRUE; JSetStringT(hContact, "State", xmlGetText(m)); } if ((m=xmlGetChild(n , "PCODE"))!=NULL && xmlGetText(m)!=NULL) { hasHomePcode = TRUE; JSetStringT(hContact, "ZIP", xmlGetText(m)); } if ((m=xmlGetChild(n , "CTRY"))==NULL || xmlGetText(m)==NULL) // Some bad client use instead of m = xmlGetChild(n , "COUNTRY"); if (m!=NULL && xmlGetText(m)!=NULL) { hasHomeCtry = TRUE; JSetStringT(hContact, "Country", xmlGetText(m)); } } if ( !hasWork && xmlGetChild(n , "WORK")!=NULL) { // Work address hasWork = TRUE; if ((m=xmlGetChild(n , "STREET"))!=NULL && xmlGetText(m)!=NULL) { hasWorkStreet = TRUE; if (hContact != NULL) { if ((o=xmlGetChild(n , "EXTADR"))!=NULL && xmlGetText(o)!=NULL) mir_sntprintf(text, SIZEOF(text), _T("%s\r\n%s"), xmlGetText(m), xmlGetText(o)); else if ((o=xmlGetChild(n , "EXTADD"))!=NULL && xmlGetText(o)!=NULL) mir_sntprintf(text, SIZEOF(text), _T("%s\r\n%s"), xmlGetText(m), xmlGetText(o)); else _tcsncpy(text, xmlGetText(m), SIZEOF(text)); text[SIZEOF(text)-1] = '\0'; JSetStringT(hContact, "CompanyStreet", text); } else { JSetStringT(hContact, "CompanyStreet", xmlGetText(m)); if ((m=xmlGetChild(n , "EXTADR")) == NULL) m = xmlGetChild(n , "EXTADD"); if (m!=NULL && xmlGetText(m)!=NULL) { hasWorkStreet2 = TRUE; JSetStringT(hContact, "CompanyStreet2", xmlGetText(m)); } } } if ((m=xmlGetChild(n , "LOCALITY"))!=NULL && xmlGetText(m)!=NULL) { hasWorkLocality = TRUE; JSetStringT(hContact, "CompanyCity", xmlGetText(m)); } if ((m=xmlGetChild(n , "REGION"))!=NULL && xmlGetText(m)!=NULL) { hasWorkRegion = TRUE; JSetStringT(hContact, "CompanyState", xmlGetText(m)); } if ((m=xmlGetChild(n , "PCODE"))!=NULL && xmlGetText(m)!=NULL) { hasWorkPcode = TRUE; JSetStringT(hContact, "CompanyZIP", xmlGetText(m)); } if ((m=xmlGetChild(n , "CTRY"))==NULL || xmlGetText(m)==NULL) // Some bad client use instead of m = xmlGetChild(n , "COUNTRY"); if (m!=NULL && xmlGetText(m)!=NULL) { hasWorkCtry = TRUE; JSetStringT(hContact, "CompanyCountry", xmlGetText(m)); } } } else if ( !lstrcmp(xmlGetName(n), _T("TEL"))) { // Telephone/Fax/Cellular if ((m=xmlGetChild(n , "NUMBER"))!=NULL && xmlGetText(m)!=NULL) { if (hContact != NULL) { if ( !hasFax && xmlGetChild(n , "FAX")!=NULL) { hasFax = TRUE; JSetStringT(hContact, "Fax", xmlGetText(m)); } else if ( !hasCell && xmlGetChild(n , "CELL")!=NULL) { hasCell = TRUE; JSetStringT(hContact, "Cellular", xmlGetText(m)); } else if ( !hasPhone && (xmlGetChild(n , "HOME")!=NULL || xmlGetChild(n , "WORK")!=NULL || xmlGetChild(n , "VOICE")!=NULL || (xmlGetChild(n , "FAX")==NULL && xmlGetChild(n , "PAGER")==NULL && xmlGetChild(n , "MSG")==NULL && xmlGetChild(n , "CELL")==NULL && xmlGetChild(n , "VIDEO")==NULL && xmlGetChild(n , "BBS")==NULL && xmlGetChild(n , "MODEM")==NULL && xmlGetChild(n , "ISDN")==NULL && xmlGetChild(n , "PCS")==NULL))) { hasPhone = TRUE; JSetStringT(hContact, "Phone", xmlGetText(m)); } } else { char text[ 100 ]; sprintf(text, "Phone%d", nPhone); JSetStringT(NULL, text, xmlGetText(m)); sprintf(text, "PhoneFlag%d", nPhone); int nFlag = 0; if (xmlGetChild(n ,"HOME") != NULL) nFlag |= JABBER_VCTEL_HOME; if (xmlGetChild(n ,"WORK") != NULL) nFlag |= JABBER_VCTEL_WORK; if (xmlGetChild(n ,"VOICE") != NULL) nFlag |= JABBER_VCTEL_VOICE; if (xmlGetChild(n ,"FAX") != NULL) nFlag |= JABBER_VCTEL_FAX; if (xmlGetChild(n ,"PAGER") != NULL) nFlag |= JABBER_VCTEL_PAGER; if (xmlGetChild(n ,"MSG") != NULL) nFlag |= JABBER_VCTEL_MSG; if (xmlGetChild(n ,"CELL") != NULL) nFlag |= JABBER_VCTEL_CELL; if (xmlGetChild(n ,"VIDEO") != NULL) nFlag |= JABBER_VCTEL_VIDEO; if (xmlGetChild(n ,"BBS") != NULL) nFlag |= JABBER_VCTEL_BBS; if (xmlGetChild(n ,"MODEM") != NULL) nFlag |= JABBER_VCTEL_MODEM; if (xmlGetChild(n ,"ISDN") != NULL) nFlag |= JABBER_VCTEL_ISDN; if (xmlGetChild(n ,"PCS") != NULL) nFlag |= JABBER_VCTEL_PCS; JSetWord(NULL, text, nFlag); nPhone++; } } } else if ( !lstrcmp(xmlGetName(n), _T("URL"))) { // Homepage if ( !hasUrl && xmlGetText(n)!=NULL) { hasUrl = TRUE; JSetStringT(hContact, "Homepage", xmlGetText(n)); } } else if ( !lstrcmp(xmlGetName(n), _T("ORG"))) { if ( !hasOrgname && !hasOrgunit) { if ((m=xmlGetChild(n ,"ORGNAME"))!=NULL && xmlGetText(m)!=NULL) { hasOrgname = TRUE; JSetStringT(hContact, "Company", xmlGetText(m)); } if ((m=xmlGetChild(n ,"ORGUNIT"))!=NULL && xmlGetText(m)!=NULL) { // The real vCard can have multiple but we will only display the first one hasOrgunit = TRUE; JSetStringT(hContact, "CompanyDepartment", xmlGetText(m)); } } } else if ( !lstrcmp(xmlGetName(n), _T("ROLE"))) { if ( !hasRole && xmlGetText(n)!=NULL) { hasRole = TRUE; JSetStringT(hContact, "Role", xmlGetText(n)); } } else if ( !lstrcmp(xmlGetName(n), _T("TITLE"))) { if ( !hasTitle && xmlGetText(n)!=NULL) { hasTitle = TRUE; JSetStringT(hContact, "CompanyPosition", xmlGetText(n)); } } else if ( !lstrcmp(xmlGetName(n), _T("DESC"))) { if ( !hasDesc && xmlGetText(n)!=NULL) { hasDesc = TRUE; TCHAR* szMemo = JabberUnixToDosT(xmlGetText(n)); JSetStringT(hContact, "About", szMemo); mir_free(szMemo); } } else if ( !lstrcmp(xmlGetName(n), _T("PHOTO"))) OnIqResultGetVcardPhoto(jid, n, hContact, hasPhoto); } } if (hasFn && !hasNick) { TCHAR *name = JGetStringT(hContact, "FullName"); TCHAR *nick = JGetStringT(hContact, "Nick"); TCHAR *jidNick = JabberNickFromJID(jid); if ( !nick || (jidNick && !_tcsicmp(nick, jidNick))) JSetStringT(hContact, "Nick", name); mir_free(jidNick); mir_free(nick); mir_free(name); } if ( !hasFn) JDeleteSetting(hContact, "FullName"); // We are not deleting "Nick" // if ( !hasNick) // JDeleteSetting(hContact, "Nick"); if ( !hasGiven) JDeleteSetting(hContact, "FirstName"); if ( !hasFamily) JDeleteSetting(hContact, "LastName"); if ( !hasMiddle) JDeleteSetting(hContact, "MiddleName"); if (hContact != NULL) { while (true) { if (nEmail <= 0) JDeleteSetting(hContact, "e-mail"); else { char text[ 100 ]; sprintf(text, "e-mail%d", nEmail-1); if ( DBGetContactSettingString(hContact, m_szModuleName, text, &dbv)) break; db_free(&dbv); JDeleteSetting(hContact, text); } nEmail++; } } else { while (true) { char text[ 100 ]; sprintf(text, "e-mail%d", nEmail); if ( DBGetContactSettingString(NULL, m_szModuleName, text, &dbv)) break; db_free(&dbv); JDeleteSetting(NULL, text); sprintf(text, "e-mailFlag%d", nEmail); JDeleteSetting(NULL, text); nEmail++; } } if ( !hasBday) { JDeleteSetting(hContact, "BirthYear"); JDeleteSetting(hContact, "BirthMonth"); JDeleteSetting(hContact, "BirthDay"); JDeleteSetting(hContact, "BirthDate"); JDeleteSetting(hContact, "Age"); } if ( !hasGender) { if (hContact != NULL) JDeleteSetting(hContact, "Gender"); else JDeleteSetting(NULL, "GenderString"); } if (hContact != NULL) { if ( !hasPhone) JDeleteSetting(hContact, "Phone"); if ( !hasFax) JDeleteSetting(hContact, "Fax"); if ( !hasCell) JDeleteSetting(hContact, "Cellular"); } else { while (true) { char text[ 100 ]; sprintf(text, "Phone%d", nPhone); if ( DBGetContactSettingString(NULL, m_szModuleName, text, &dbv)) break; db_free(&dbv); JDeleteSetting(NULL, text); sprintf(text, "PhoneFlag%d", nPhone); JDeleteSetting(NULL, text); nPhone++; } } if ( !hasHomeStreet) JDeleteSetting(hContact, "Street"); if ( !hasHomeStreet2 && hContact==NULL) JDeleteSetting(hContact, "Street2"); if ( !hasHomeLocality) JDeleteSetting(hContact, "City"); if ( !hasHomeRegion) JDeleteSetting(hContact, "State"); if ( !hasHomePcode) JDeleteSetting(hContact, "ZIP"); if ( !hasHomeCtry) JDeleteSetting(hContact, "Country"); if ( !hasWorkStreet) JDeleteSetting(hContact, "CompanyStreet"); if ( !hasWorkStreet2 && hContact==NULL) JDeleteSetting(hContact, "CompanyStreet2"); if ( !hasWorkLocality) JDeleteSetting(hContact, "CompanyCity"); if ( !hasWorkRegion) JDeleteSetting(hContact, "CompanyState"); if ( !hasWorkPcode) JDeleteSetting(hContact, "CompanyZIP"); if ( !hasWorkCtry) JDeleteSetting(hContact, "CompanyCountry"); if ( !hasUrl) JDeleteSetting(hContact, "Homepage"); if ( !hasOrgname) JDeleteSetting(hContact, "Company"); if ( !hasOrgunit) JDeleteSetting(hContact, "CompanyDepartment"); if ( !hasRole) JDeleteSetting(hContact, "Role"); if ( !hasTitle) JDeleteSetting(hContact, "CompanyPosition"); if ( !hasDesc) JDeleteSetting(hContact, "About"); if (id == m_ThreadInfo->resolveID) { const TCHAR *p = _tcschr(jid, '@'); ResolveTransportNicks((p != NULL) ? p+1 : jid); } else { if ((hContact = HContactFromJID(jid)) != NULL) JSendBroadcast(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1, 0); WindowNotify(WM_JABBER_REFRESH_VCARD); } } else if ( !lstrcmp(type, _T("error"))) { if ((hContact = HContactFromJID(jid)) != NULL) JSendBroadcast(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1, 0); } } void CJabberProto::OnIqResultSetVcard(HXML iqNode) { Log(" iqIdSetVcard"); if ( !xmlGetAttrValue(iqNode, _T("type"))) return; WindowNotify(WM_JABBER_REFRESH_VCARD); } void CJabberProto::OnIqResultSetSearch(HXML iqNode) { HXML queryNode, n; const TCHAR *type, *jid; int i, id; JABBER_SEARCH_RESULT jsr; Log(" iqIdGetSearch"); if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((id = JabberGetPacketID(iqNode)) == -1) return; if ( !lstrcmp(type, _T("result"))) { if ((queryNode=xmlGetChild(iqNode , "query")) == NULL) return; jsr.hdr.cbSize = sizeof(JABBER_SEARCH_RESULT); for (i=0; ; i++) { HXML itemNode = xmlGetChild(queryNode ,i); if ( !itemNode) break; if ( !lstrcmp(xmlGetName(itemNode), _T("item"))) { if ((jid=xmlGetAttrValue(itemNode, _T("jid"))) != NULL) { _tcsncpy(jsr.jid, jid, SIZEOF(jsr.jid)); jsr.jid[ SIZEOF(jsr.jid)-1] = '\0'; jsr.hdr.id = (TCHAR*)jid; Log("Result jid = " TCHAR_STR_PARAM, jid); if ((n=xmlGetChild(itemNode , "nick"))!=NULL && xmlGetText(n)!=NULL) jsr.hdr.nick = (TCHAR*)xmlGetText(n); else jsr.hdr.nick = _T(""); if ((n=xmlGetChild(itemNode , "first"))!=NULL && xmlGetText(n)!=NULL) jsr.hdr.firstName = (TCHAR*)xmlGetText(n); else jsr.hdr.firstName = _T(""); if ((n=xmlGetChild(itemNode , "last"))!=NULL && xmlGetText(n)!=NULL) jsr.hdr.lastName = (TCHAR*)xmlGetText(n); else jsr.hdr.lastName = _T(""); if ((n=xmlGetChild(itemNode , "email"))!=NULL && xmlGetText(n)!=NULL) jsr.hdr.email = (TCHAR*)xmlGetText(n); else jsr.hdr.email = _T(""); jsr.hdr.flags = PSR_TCHAR; JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&jsr); } } } JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } else if ( !lstrcmp(type, _T("error"))) JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } void CJabberProto::OnIqResultExtSearch(HXML iqNode) { HXML queryNode; const TCHAR *type; int id; Log(" iqIdGetExtSearch"); if ((type=xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ((id = JabberGetPacketID(iqNode)) == -1) return; if ( !lstrcmp(type, _T("result"))) { if ((queryNode=xmlGetChild(iqNode , "query")) == NULL) return; if ((queryNode=xmlGetChild(queryNode , "x")) == NULL) return; for (int i=0; ; i++) { HXML itemNode = xmlGetChild(queryNode ,i); if ( !itemNode) break; if (lstrcmp(xmlGetName(itemNode), _T("item"))) continue; JABBER_SEARCH_RESULT jsr = { 0 }; jsr.hdr.cbSize = sizeof(JABBER_SEARCH_RESULT); jsr.hdr.flags = PSR_TCHAR; // jsr.hdr.firstName = ""; for (int j=0; ; j++) { HXML fieldNode = xmlGetChild(itemNode ,j); if ( !fieldNode) break; if (lstrcmp(xmlGetName(fieldNode), _T("field"))) continue; const TCHAR *fieldName = xmlGetAttrValue(fieldNode, _T("var")); if (fieldName == NULL) continue; HXML n = xmlGetChild(fieldNode , "value"); if (n == NULL) continue; if ( !lstrcmp(fieldName, _T("jid"))) { _tcsncpy(jsr.jid, xmlGetText(n), SIZEOF(jsr.jid)); jsr.jid[SIZEOF(jsr.jid)-1] = '\0'; Log("Result jid = " TCHAR_STR_PARAM, jsr.jid); } else if ( !lstrcmp(fieldName, _T("nickname"))) jsr.hdr.nick = (xmlGetText(n) != NULL) ? (TCHAR*)xmlGetText(n) : _T(""); else if ( !lstrcmp(fieldName, _T("fn"))) jsr.hdr.firstName = (xmlGetText(n) != NULL) ? (TCHAR*)xmlGetText(n) : _T(""); else if ( !lstrcmp(fieldName, _T("given"))) jsr.hdr.firstName = (xmlGetText(n) != NULL) ? (TCHAR*)xmlGetText(n) : _T(""); else if ( !lstrcmp(fieldName, _T("family"))) jsr.hdr.lastName = (xmlGetText(n) != NULL) ? (TCHAR*)xmlGetText(n) : _T(""); else if ( !lstrcmp(fieldName, _T("email"))) jsr.hdr.email = (xmlGetText(n) != NULL) ? (TCHAR*)xmlGetText(n) : _T(""); } JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&jsr); } JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } else if ( !lstrcmp(type, _T("error"))) JSendBroadcast(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } void CJabberProto::OnIqResultSetPassword(HXML iqNode) { Log(" iqIdSetPassword"); const TCHAR *type = xmlGetAttrValue(iqNode, _T("type")); if (type == NULL) return; if ( !lstrcmp(type, _T("result"))) { _tcsncpy(m_ThreadInfo->password, m_ThreadInfo->newPassword, SIZEOF(m_ThreadInfo->password)); MessageBox(NULL, TranslateT("Password is successfully changed. Don't forget to update your password in the Jabber protocol option."), TranslateT("Change Password"), MB_OK|MB_ICONINFORMATION|MB_SETFOREGROUND); } else if ( !lstrcmp(type, _T("error"))) MessageBox(NULL, TranslateT("Password cannot be changed."), TranslateT("Change Password"), MB_OK|MB_ICONSTOP|MB_SETFOREGROUND); } /* void CJabberProto::OnIqResultDiscoAgentItems(HXML iqNode, void *userdata) { if ( !m_options.EnableAvatars) return; } */ void CJabberProto::OnIqResultGetVCardAvatar(HXML iqNode) { const TCHAR *type; Log(" OnIqResultGetVCardAvatar"); const TCHAR *from = xmlGetAttrValue(iqNode, _T("from")); if (from == NULL) return; HANDLE hContact = HContactFromJID(from); if (hContact == NULL) return; if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if (_tcscmp(type, _T("result"))) return; HXML vCard = xmlGetChild(iqNode , "vCard"); if (vCard == NULL) return; vCard = xmlGetChild(vCard , "PHOTO"); if (vCard == NULL) return; if (xmlGetChildCount(vCard) == 0) { JDeleteSetting(hContact, "AvatarHash"); DBVARIANT dbv = {0}; if ( !JGetStringT(hContact, "AvatarSaved", &dbv)) { db_free(&dbv); JDeleteSetting(hContact, "AvatarSaved"); JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, NULL, NULL); } return; } HXML typeNode = xmlGetChild(vCard , "TYPE"); const TCHAR *mimeType = NULL; if (typeNode != NULL) mimeType = xmlGetText(typeNode); HXML n = xmlGetChild(vCard , "BINVAL"); if (n == NULL) return; JSetByte(hContact, "AvatarXVcard", 1); OnIqResultGotAvatar(hContact, n, mimeType); } void CJabberProto::OnIqResultGetClientAvatar(HXML iqNode) { const TCHAR *type; Log(" iqIdResultGetClientAvatar"); const TCHAR *from = xmlGetAttrValue(iqNode, _T("from")); if (from == NULL) return; HANDLE hContact = HContactFromJID(from); if (hContact == NULL) return; HXML n = NULL; if ((type = xmlGetAttrValue(iqNode, _T("type"))) != NULL && !_tcscmp(type, _T("result"))) { HXML queryNode = xmlGetChild(iqNode , "query"); if (queryNode != NULL) { const TCHAR *xmlns = xmlGetAttrValue(queryNode, _T("xmlns")); if ( !lstrcmp(xmlns, _T(JABBER_FEAT_AVATAR))) { n = xmlGetChild(queryNode , "data"); } } } if (n == NULL) { TCHAR szJid[ JABBER_MAX_JID_LEN ]; lstrcpyn(szJid, from, SIZEOF(szJid)); TCHAR *res = _tcschr(szJid, _T('/')); if (res != NULL) *res = 0; // Try server stored avatar int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetServerAvatar); XmlNodeIq iq(_T("get"), iqId, szJid); iq << XQUERY(_T(JABBER_FEAT_SERVER_AVATAR)); m_ThreadInfo->send(iq); return; } const TCHAR *mimeType = mimeType = xmlGetAttrValue(n, _T("mimetype")); OnIqResultGotAvatar(hContact, n, mimeType); } void CJabberProto::OnIqResultGetServerAvatar(HXML iqNode) { const TCHAR *type; Log(" iqIdResultGetServerAvatar"); const TCHAR *from = xmlGetAttrValue(iqNode, _T("from")); if (from == NULL) return; HANDLE hContact = HContactFromJID(from); if (hContact == NULL) return; HXML n = NULL; if ((type = xmlGetAttrValue(iqNode, _T("type"))) != NULL && !_tcscmp(type, _T("result"))) { HXML queryNode = xmlGetChild(iqNode , "query"); if (queryNode != NULL) { const TCHAR *xmlns = xmlGetAttrValue(queryNode, _T("xmlns")); if ( !lstrcmp(xmlns, _T(JABBER_FEAT_SERVER_AVATAR))) { n = xmlGetChild(queryNode , "data"); } } } if (n == NULL) { TCHAR szJid[ JABBER_MAX_JID_LEN ]; lstrcpyn(szJid, from, SIZEOF(szJid)); TCHAR *res = _tcschr(szJid, _T('/')); if (res != NULL) *res = 0; // Try VCard photo int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetVCardAvatar); XmlNodeIq iq(_T("get"), iqId, szJid); iq << XCHILDNS(_T("vCard"), _T(JABBER_FEAT_VCARD_TEMP)); m_ThreadInfo->send(iq); return; } const TCHAR *mimeType = xmlGetAttrValue(n, _T("mimetype")); OnIqResultGotAvatar(hContact, n, mimeType); } void CJabberProto::OnIqResultGotAvatar(HANDLE hContact, HXML n, const TCHAR *mimeType) { int resultLen = 0; char* body = JabberBase64DecodeT(xmlGetText(n), &resultLen); int pictureType; if (mimeType != NULL) { if ( !lstrcmp(mimeType, _T("image/jpeg"))) pictureType = PA_FORMAT_JPEG; else if ( !lstrcmp(mimeType, _T("image/png"))) pictureType = PA_FORMAT_PNG; else if ( !lstrcmp(mimeType, _T("image/gif"))) pictureType = PA_FORMAT_GIF; else if ( !lstrcmp(mimeType, _T("image/bmp"))) pictureType = PA_FORMAT_BMP; else { LBL_ErrFormat: Log("Invalid mime type specified for picture: " TCHAR_STR_PARAM, mimeType); mir_free(body); return; } } else if ((pictureType = JabberGetPictureType(body)) == PA_FORMAT_UNKNOWN) goto LBL_ErrFormat; TCHAR tszFileName[ MAX_PATH ]; PROTO_AVATAR_INFORMATIONT AI; AI.cbSize = sizeof AI; AI.format = pictureType; AI.hContact = hContact; if (JGetByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN) != (unsigned char)pictureType) { GetAvatarFileName(hContact, tszFileName, SIZEOF(tszFileName)); DeleteFile(tszFileName); } JSetByte(hContact, "AvatarType", pictureType); char buffer[ 41 ]; mir_sha1_byte_t digest[20]; mir_sha1_ctx sha; mir_sha1_init(&sha); mir_sha1_append(&sha, (mir_sha1_byte_t*)body, resultLen); mir_sha1_finish(&sha, digest); for (int i=0; i<20; i++) sprintf(buffer+(i<<1), "%02x", digest[i]); GetAvatarFileName(hContact, tszFileName, SIZEOF(tszFileName)); _tcsncpy(AI.filename, tszFileName, SIZEOF(AI.filename)); FILE* out = _tfopen(tszFileName, _T("wb")); if (out != NULL) { fwrite(body, resultLen, 1, out); fclose(out); JSetString(hContact, "AvatarSaved", buffer); JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&AI), NULL); Log("Broadcast new avatar: %s",AI.filename); } else JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&AI), NULL); mir_free(body); } ///////////////////////////////////////////////////////////////////////////////////////// // Bookmarks void CJabberProto::OnIqResultDiscoBookmarks(HXML iqNode) { HXML storageNode;//, nickNode, passNode; const TCHAR *type, *jid, *name; // RECVED: list of bookmarks // ACTION: refresh bookmarks dialog Log(" iqIdGetBookmarks"); if ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL) return; if ( !lstrcmp(type, _T("result"))) { if (m_ThreadInfo && !(m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PRIVATE_STORAGE)) { m_ThreadInfo->jabberServerCaps |= JABBER_CAPS_PRIVATE_STORAGE; EnableMenuItems(TRUE); } if (storageNode = XPathT(iqNode, "query/storage[@xmlns='storage:bookmarks']")) { ListRemoveList(LIST_BOOKMARK); HXML itemNode; for (int i = 0; itemNode = xmlGetChild(storageNode, i); i++) { if (name = xmlGetName(itemNode)) { if ( !_tcscmp(name, _T("conference")) && (jid = xmlGetAttrValue(itemNode, _T("jid")))) { JABBER_LIST_ITEM* item = ListAdd(LIST_BOOKMARK, jid); item->name = mir_tstrdup(xmlGetAttrValue(itemNode, _T("name"))); item->type = mir_tstrdup(_T("conference")); item->bUseResource = TRUE; item->nick = mir_tstrdup(XPathT(itemNode, "nick")); item->password = mir_tstrdup(XPathT(itemNode, "password")); const TCHAR *autoJ = xmlGetAttrValue(itemNode, _T("autojoin")); if (autoJ != NULL) item->bAutoJoin = (!lstrcmp(autoJ, _T("true")) || !lstrcmp(autoJ, _T("1"))) ? true : false; } else if ( !_tcscmp(name, _T("url")) && (jid = xmlGetAttrValue(itemNode, _T("url") ))) { JABBER_LIST_ITEM* item = ListAdd(LIST_BOOKMARK, jid); item->bUseResource = TRUE; item->name = mir_tstrdup(xmlGetAttrValue(itemNode, _T("name"))); item->type = mir_tstrdup(_T("url")); } } } UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_REFRESH); m_ThreadInfo->bBookmarksLoaded = TRUE; OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_BOOKMARKS); } } else if ( !lstrcmp(type, _T("error"))) { if (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PRIVATE_STORAGE) { m_ThreadInfo->jabberServerCaps &= ~JABBER_CAPS_PRIVATE_STORAGE; EnableMenuItems(TRUE); UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_ACTIVATE); return; } } } void CJabberProto::SetBookmarkRequest (XmlNodeIq& iq) { HXML query = iq << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)); HXML storage = query << XCHILDNS(_T("storage"), _T("storage:bookmarks")); LISTFOREACH(i, this, LIST_BOOKMARK) { JABBER_LIST_ITEM* item = ListGetItemPtrFromIndex(i); if (item == NULL) continue; if (item->jid == NULL) continue; if ( !lstrcmp(item->type, _T("conference"))) { HXML itemNode = storage << XCHILD(_T("conference")) << XATTR(_T("jid"), item->jid); if (item->name) itemNode << XATTR(_T("name"), item->name); if (item->bAutoJoin) itemNode << XATTRI(_T("autojoin"), 1); if (item->nick) itemNode << XCHILD(_T("nick"), item->nick); if (item->password) itemNode << XCHILD(_T("password"), item->password); } if ( !lstrcmp(item->type, _T("url"))) { HXML itemNode = storage << XCHILD(_T("url")) << XATTR(_T("url"), item->jid); if (item->name) itemNode << XATTR(_T("name"), item->name); } } } void CJabberProto::OnIqResultSetBookmarks(HXML iqNode) { // RECVED: server's response // ACTION: refresh bookmarks list dialog Log(" iqIdSetBookmarks"); const TCHAR *type = xmlGetAttrValue(iqNode, _T("type")); if (type == NULL) return; if ( !lstrcmp(type, _T("result"))) { UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_REFRESH); } else if ( !lstrcmp(type, _T("error"))) { HXML errorNode = xmlGetChild(iqNode , "error"); TCHAR* str = JabberErrorMsg(errorNode); MessageBox(NULL, str, TranslateT("Jabber Bookmarks Error"), MB_OK|MB_SETFOREGROUND); mir_free(str); UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_ACTIVATE); } } // last activity (XEP-0012) support void CJabberProto::OnIqResultLastActivity(HXML iqNode, CJabberIqInfo* pInfo) { JABBER_RESOURCE_STATUS *r = ResourceInfoFromJID(pInfo->m_szFrom); if ( !r) return; time_t lastActivity = -1; if (pInfo->m_nIqType == JABBER_IQ_TYPE_RESULT) { LPCTSTR szSeconds = XPathT(iqNode, "query[@xmlns='jabber:iq:last']/@seconds"); if (szSeconds) { int nSeconds = _ttoi(szSeconds); if (nSeconds > 0) lastActivity = time(0) - nSeconds; } LPCTSTR szLastStatusMessage = XPathT(iqNode, "query[@xmlns='jabber:iq:last']"); if (szLastStatusMessage) // replace only if it exists replaceStrT(r->statusMessage, szLastStatusMessage); } r->idleStartTime = lastActivity; JabberUserInfoUpdate(pInfo->GetHContact()); } // entity time (XEP-0202) support void CJabberProto::OnIqResultEntityTime(HXML pIqNode, CJabberIqInfo* pInfo) { if ( !pInfo->m_hContact) return; if (pInfo->m_nIqType == JABBER_IQ_TYPE_RESULT) { LPCTSTR szTzo = XPathFmt(pIqNode, _T("time[@xmlns='%s']/tzo"), _T(JABBER_FEAT_ENTITY_TIME)); if (szTzo && szTzo[0]) { LPCTSTR szMin = _tcschr(szTzo, ':'); int nTz = _ttoi(szTzo) * -2; nTz += (nTz < 0 ? -1 : 1) * (szMin ? _ttoi(szMin + 1) / 30 : 0); TIME_ZONE_INFORMATION tzinfo; if (GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT) nTz -= tzinfo.DaylightBias / 30; JSetByte(pInfo->m_hContact, "Timezone", (signed char)nTz); LPCTSTR szTz = XPathFmt(pIqNode, _T("time[@xmlns='%s']/tz"), _T(JABBER_FEAT_ENTITY_TIME)); if (szTz) JSetStringT(pInfo->m_hContact, "TzName", szTz); else JDeleteSetting(pInfo->m_hContact, "TzName"); return; } } else if (pInfo->m_nIqType == JABBER_IQ_TYPE_ERROR) { if (JGetWord(pInfo->m_hContact, "Status", ID_STATUS_OFFLINE) == ID_STATUS_OFFLINE) return; } JDeleteSetting(pInfo->m_hContact, "Timezone"); JDeleteSetting(pInfo->m_hContact, "TzName"); }