From ed4483115a924f177b786e11bc436243512eb8da Mon Sep 17 00:00:00 2001 From: Rozhuk Ivan Date: Tue, 25 Nov 2014 23:34:49 +0000 Subject: jabber code clean up req code review git-svn-id: http://svn.miranda-ng.org/main/trunk@11074 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/IcqOscarJ/src/fam_13servclist.cpp | 3510 +++++++++++++-------------- protocols/IcqOscarJ/src/icq_avatar.cpp | 8 +- protocols/IcqOscarJ/src/icqosc_svcs.cpp | 4 +- protocols/JabberG/src/jabber_search.cpp | 1532 ++++++------ protocols/JabberG/src/jabber_userinfo.cpp | 1697 ++++++------- 5 files changed, 3380 insertions(+), 3371 deletions(-) (limited to 'protocols') diff --git a/protocols/IcqOscarJ/src/fam_13servclist.cpp b/protocols/IcqOscarJ/src/fam_13servclist.cpp index 050aea947a..13d3958ec8 100644 --- a/protocols/IcqOscarJ/src/fam_13servclist.cpp +++ b/protocols/IcqOscarJ/src/fam_13servclist.cpp @@ -1,1755 +1,1755 @@ -// ---------------------------------------------------------------------------80 -// ICQ plugin for Miranda Instant Messenger -// ________________________________________ -// -// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede -// Copyright © 2001-2002 Jon Keating, Richard Hughes -// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater -// Copyright © 2004-2010 Joe Kucera -// Copyright © 2012-2014 Miranda NG Team -// -// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// ----------------------------------------------------------------------------- - -#include "icqoscar.h" - -static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength); - - -void CIcqProto::handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info) -{ - switch (pSnacHeader->wSubtype) { - case ICQ_LISTS_ACK: // UPDATE_ACK - if (wBufferLength >= 2) { - WORD wError; - cookie_servlist_action* sc; - - unpackWord(&pBuffer, &wError); - - if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // look for action cookie - debugLogA("Received expected server list ack, action: %d, result: %d", sc->dwAction, wError); - FreeCookie(pSnacHeader->dwRef); // release cookie - - if (sc->dwAction == SSA_ACTION_GROUP) { // group cookie, handle sub-items - int i; - - debugLogA("Server-List: Grouped action contains %d actions.", sc->dwGroupCount); - - pBuffer -= 2; // revoke unpack - if (wBufferLength != 2 * sc->dwGroupCount) - debugLogA("Error: Server list ack does not contain expected amount of result codes (%u != %u)", wBufferLength / 2, sc->dwGroupCount); - - for (i = 0; i < sc->dwGroupCount; i++) { - if (wBufferLength >= 2) { // get proper result code - unpackWord(&pBuffer, &wError); - wBufferLength -= 2; - } - else // missing result code, give some special - wError = -1; - - debugLogA("Action: %d, ack result: %d", sc->pGroupItems[i]->dwAction, wError); - - // call normal ack handler - handleServerCListAck(sc->pGroupItems[i], wError); - } - // Release cookie - SAFE_FREE((void**)&sc->pGroupItems); - SAFE_FREE((void**)&sc); - } - else // single ack - handleServerCListAck(sc, wError); - } - else debugLogA("Received unexpected server list ack %u", wError); - } - break; - - case ICQ_LISTS_SRV_REPLYLISTS: - /* received server-list rights */ - handleServerCListRightsReply(pBuffer, wBufferLength); - debugLogA("Server sent SNAC(x13,x03) - SRV_REPLYLISTS"); - break; - - case ICQ_LISTS_LIST: // SRV_REPLYROSTER - { - cookie_servlist_action* sc; - BOOL blWork = bIsSyncingCL; - bIsSyncingCL = TRUE; // this is not used if cookie takes place - - if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // we do it by reliable cookie - if (!sc->lParam) { // is this first packet ? - ResetSettingsOnListReload(); - sc->lParam = 1; - } - handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); - if (!(pSnacHeader->wFlags & 0x0001)) // was that last packet ? - ReleaseCookie(pSnacHeader->dwRef); // yes, release cookie - } - else { // use old fake - if (!blWork) // this can fail on some crazy situations - ResetSettingsOnListReload(); - - handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); - } - } - break; - - case ICQ_LISTS_UPTODATE: // SRV_REPLYROSTEROK - bIsSyncingCL = FALSE; - { - cookie_servlist_action* sc; - if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // we requested servlist check - debugLogA("Server stated roster is ok."); - ReleaseCookie(pSnacHeader->dwRef); - LoadServerIDs(); - } - else debugLogA("Server sent unexpected SNAC(x13,x0F) - SRV_REPLYROSTEROK"); - - // This will activate the server side list - sendRosterAck(); // this must be here, cause of failures during cookie alloc - handleServUINSettings(wListenPort, info); - } - break; - - case ICQ_LISTS_CLI_MODIFYSTART: - debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYSTART, "Server is modifying contact list"); - break; - - case ICQ_LISTS_CLI_MODIFYEND: - debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYEND, "End of server modification"); - break; - - case ICQ_LISTS_ADDTOLIST: - case ICQ_LISTS_UPDATEGROUP: - case ICQ_LISTS_REMOVEFROMLIST: - { - int nItems = 0; - - while (wBufferLength >= 10) { - WORD wGroupId, wItemId, wItemType, wTlvLen; - uid_str szRecordName; - - if (unpackServerListItem(&pBuffer, &wBufferLength, szRecordName, &wGroupId, &wItemId, &wItemType, &wTlvLen)) { - BYTE *buf = pBuffer; - oscar_tlv_chain *pChain = NULL; - - nItems++; - - // parse possible item's data - if (wBufferLength >= wTlvLen && wTlvLen > 0) { - pChain = readIntoTLVChain(&buf, wTlvLen, 0); - pBuffer += wTlvLen; - wBufferLength -= wTlvLen; - } - else if (wTlvLen > 0) - wBufferLength = 0; - - // process item change - if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) - handleServerCListItemAdd(szRecordName, wGroupId, wItemId, wItemType, pChain); - else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) - handleServerCListItemUpdate(szRecordName, wGroupId, wItemId, wItemType, pChain); - else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) - handleServerCListItemDelete(szRecordName, wGroupId, wItemId, wItemType, pChain); - - // release memory - disposeChain(&pChain); - } - } - { - // log packet basics - char *szChange; - - if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) - szChange = "Server added %u item(s) to list"; - else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) - szChange = "Server updated %u item(s) on list"; - else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) - szChange = "Server removed %u item(s) from list"; - - char szLogText[MAX_PATH]; - mir_snprintf(szLogText, MAX_PATH, szChange, nItems); - debugLogA("Server sent SNAC(x13,x%02x) - %s", pSnacHeader->wSubtype, szLogText); - } - } - break; - - case ICQ_LISTS_AUTHREQUEST: - handleRecvAuthRequest(pBuffer, wBufferLength); - break; - - case ICQ_LISTS_SRV_AUTHRESPONSE: - handleRecvAuthResponse(pBuffer, wBufferLength); - break; - - case ICQ_LISTS_AUTHGRANTED: - debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_AUTHGRANTED, "User granted us future authorization"); - break; - - case ICQ_LISTS_YOUWEREADDED: - handleRecvAdded(pBuffer, wBufferLength); - break; - - case ICQ_LISTS_ERROR: - if (wBufferLength >= 2) { - WORD wError; - cookie_servlist_action* sc; - - unpackWord(&pBuffer, &wError); - - if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // look for action cookie - debugLogA("Received server list error, action: %d, result: %d", sc->dwAction, wError); - FreeCookie(pSnacHeader->dwRef); // release cookie - - if (sc->dwAction == SSA_CHECK_ROSTER) { // the serv-list is unavailable turn it off - icq_LogMessage(LOG_ERROR, LPGEN("Server contact list is unavailable, Miranda will use local contact list.")); - m_bSsiEnabled = 0; - handleServUINSettings(wListenPort, info); - } - /// FIXME: properly release pending operations & cookie memory - SAFE_FREE((void**)&sc); - } - else LogFamilyError(ICQ_LISTS_FAMILY, wError); - } - break; - - default: - debugLogA("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LISTS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); - break; - } -} - - -static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength) -{ - WORD wRecordNameLen; - - // The name of the entry. If this is a group header, then this - // is the name of the group. If it is a plain contact list entry, - // then it's the UIN of the contact. - unpackWord(pbuf, &wRecordNameLen); - if (*pwLen < 10 + wRecordNameLen || wRecordNameLen >= MAX_PATH) - return 0; // Failure - - unpackString(pbuf, pszRecordName, wRecordNameLen); - if (pszRecordName) - pszRecordName[wRecordNameLen] = '\0'; - - // The group identifier this entry belongs to. If 0, this is meta information or - // a contact without a group - unpackWord(pbuf, pwGroupId); - - // The ID of this entry. Group headers have ID 0. Otherwise, this - // is a random number generated when the user is added to the - // contact list, or when the user is ignored. See CLI_ADDBUDDY. - unpackWord(pbuf, pwItemId); - - // This field indicates what type of entry this is - unpackWord(pbuf, pwItemType); - - // The length in bytes of the following TLV chain - unpackWord(pbuf, pwTlvLength); - - *pwLen -= wRecordNameLen + 10; - - return 1; // Success -} - - -void CIcqProto::handleServerCListRightsReply(BYTE *buf, WORD wLen) -{ - /* received list rights, store the item limits for future use */ - oscar_tlv_chain* chain; - - memset(m_wServerListLimits, -1, sizeof(m_wServerListLimits)); - m_wServerListGroupMaxContacts = 0; - m_wServerListRecordNameMaxLength = 0xFFFF; - - if (chain = readIntoTLVChain(&buf, wLen, 0)) { - // determine max number of contacts in a group - m_wServerListGroupMaxContacts = chain->getWord(0x0C, 1); - // determine length limit for server-list item's name - m_wServerListRecordNameMaxLength = chain->getWord(0x06, 1); - - if (oscar_tlv *pTLV = chain->getTLV(0x04, 1)) { // limits for item types - WORD *pLimits = (WORD*)pTLV->pData; - for (int i = 0; i < pTLV->wLen / 2; i++) { - m_wServerListLimits[i] = (pLimits[i] & 0xFF) << 8 | (pLimits[i] >> 8); - - if (i + 1 >= SIZEOF(m_wServerListLimits)) - break; - } - - debugLogA("SSI: Max %d contacts (%d per group), %d groups, %d permit, %d deny, %d ignore items.", m_wServerListLimits[SSI_ITEM_BUDDY], m_wServerListGroupMaxContacts, m_wServerListLimits[SSI_ITEM_GROUP], m_wServerListLimits[SSI_ITEM_PERMIT], m_wServerListLimits[SSI_ITEM_DENY], m_wServerListLimits[SSI_ITEM_IGNORE]); - } - - disposeChain(&chain); - } -} - -DWORD CIcqProto::updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags) -{ - cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (!ack) { - debugLogA("Updating of group on server list failed (malloc error)"); - return 0; - } - ack->dwAction = SSA_GROUP_UPDATE; - ack->szGroupName = getServListGroupName(wGroupId); - ack->wGroupId = wGroupId; - - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); - return icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, ack->wGroupId, ack->szGroupName, groupData, groupSize, dwOperationFlags); -} - -void CIcqProto::handleServerCListAck(cookie_servlist_action* sc, WORD wError) -{ - switch (sc->dwAction) { - case SSA_VISIBILITY: - if (wError) - debugLogA("Server visibility update failed, error %d", wError); - break; - - case SSA_CONTACT_UPDATE: - servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, wError ? PENDING_RESULT_FAILED : PENDING_RESULT_SUCCESS); - if (wError) { - debugLogA("Updating of server contact failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Updating of server contact failed.")); - } - break; - - case SSA_PRIVACY_ADD: - if (wError) { - debugLogA("Adding of privacy item to server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Adding of privacy item to server list failed.")); - } - break; - - case SSA_PRIVACY_REMOVE: - if (wError) { - debugLogA("Removing of privacy item from server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Removing of privacy item from server list failed.")); - } - FreeServerID(sc->wContactId, SSIT_ITEM); // release server id - break; - - case SSA_CONTACT_ADD: - if (wError) { - if (wError == 0xE) { // server refused to add contact w/o auth, add with - debugLogA("Contact could not be added without authorization, add with await auth flag."); - - setByte(sc->hContact, "Auth", 1); // we need auth - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); - icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wGroupId, sc->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL); - - sc = NULL; // we do not want it to be freed now - break; - } - FreeServerID(sc->wContactId, SSIT_ITEM); - - debugLogA("Adding of contact to server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Adding of contact to server list failed.")); - - servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_FAILED); - - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here - } - else { - void* groupData; - int groupSize; - - setWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wContactId); - setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wGroupId); - - servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_SUCCESS); - - if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) { // the group is not empty, just update it - updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); - SAFE_FREE((void**)&groupData); - } - else { // this should never happen - debugLogA("Group update failed."); - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here - } - } - break; - - case SSA_GROUP_ADD: - if (wError) { - FreeServerID(sc->wGroupId, SSIT_GROUP); - debugLogA("Adding of group to server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Adding of group to server list failed.")); - - servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); - } - else { // group added, we need to update master group - void* groupData; - int groupSize; - - setServListGroupName(sc->wGroupId, sc->szGroupName); // add group to namelist - { // add group to known - char *szCListGroup = getServListGroupCListPath(sc->wGroupId); - - // create link to the original CList group - setServListGroupLinkID(sc->szGroup, sc->wGroupId); - - servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); - SAFE_FREE((void**)&szCListGroup); - } - - groupData = collectGroups(&groupSize); - groupData = SAFE_REALLOC(groupData, groupSize + 2); - *(((WORD*)groupData) + (groupSize >> 1)) = sc->wGroupId; // add this new group id - groupSize += 2; - - cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (ack) { - ack->dwAction = SSA_GROUP_UPDATE; - - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); - icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, SSOF_END_OPERATION); - } - else // end server modifications here - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); - - SAFE_FREE((void**)&groupData); - } - if (sc->szGroup != sc->szGroupName) - SAFE_FREE((void**)&sc->szGroup); - - SAFE_FREE((void**)&sc->szGroupName); - break; - - case SSA_CONTACT_REMOVE: - if (!wError) { - void* groupData; - int groupSize; - - setWord(sc->hContact, DBSETTING_SERVLIST_ID, 0); // clear the values - setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, 0); - - FreeServerID(sc->wContactId, SSIT_ITEM); - - servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_SUCCESS); - - if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) { // the group is still not empty, just update it - updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); - } - else // the group is empty, delete it - { - char *szGroup = getServListGroupCListPath(sc->wGroupId); - - servlistRemoveGroup(szGroup, sc->wGroupId); - SAFE_FREE((void**)&szGroup); - } - SAFE_FREE((void**)&groupData); // free the memory - } - else { - debugLogA("Removing of contact from server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Removing of contact from server list failed.")); - - servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_FAILED); - - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here - } - break; - - case SSA_GROUP_UPDATE: - if (wError) { - debugLogA("Updating of group on server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Updating of group on server list failed.")); - } - SAFE_FREE((void**)&sc->szGroupName); - break; - - case SSA_GROUP_REMOVE: - SAFE_FREE((void**)&sc->szGroupName); - if (wError) { - debugLogA("Removing of group from server list failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Removing of group from server list failed.")); - - servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); - - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here - SAFE_FREE((void**)&sc->szGroup); - } - else { // group removed, we need to update master group - void* groupData; - int groupSize; - - setServListGroupName(sc->wGroupId, NULL); // clear group from namelist - FreeServerID(sc->wGroupId, SSIT_GROUP); - removeGroupPathLinks(sc->wGroupId); - - servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_SUCCESS); - SAFE_FREE((void**)&sc->szGroup); - - groupData = collectGroups(&groupSize); - sc->wGroupId = 0; - sc->dwAction = SSA_GROUP_UPDATE; - sc->szGroupName = NULL; - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, sc); - - icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, sc->szGroupName, groupData, groupSize, SSOF_END_OPERATION); - // end server modifications here - - sc = NULL; // we do not want to be freed here - - SAFE_FREE((void**)&groupData); - } - break; - - case SSA_CONTACT_SET_GROUP: - // we moved contact to another group - if (sc->lParam == -1) // the first was an error - break; - - if (wError) { - if (wError == 0x0E && sc->lParam == 1) { // second ack - adding failed with error 0x0E, try to add with AVAIT_AUTH flag - if (!getByte(sc->hContact, "Auth", 0)) { // we tried without AWAIT_AUTH, try again with it - debugLogA("Contact could not be added without authorization, add with await auth flag."); - setByte(sc->hContact, "Auth", 1); // we need auth - } - else { // we tried with AWAIT_AUTH, try again without - debugLogA("Contact count not be added awaiting authorization, try authorized."); - setByte(sc->hContact, "Auth", 0); - } - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); - icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wNewGroupId, sc->wNewContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL); - - sc->lParam = 2; // do not cycle - sc = NULL; // we do not want to be freed here - break; - } - FreeServerID(sc->wNewContactId, SSIT_ITEM); - debugLogA("Moving of user to another group on server list failed, error %d", wError); - icq_LogMessage(LOG_ERROR, LPGEN("Moving of user to another group on server list failed.")); - - servlistPendingRemoveContact(sc->hContact, 0, (WORD)(sc->lParam ? sc->wGroupId : sc->wNewGroupId), PENDING_RESULT_FAILED); - - servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here - - if (!sc->lParam) { // is this first ack ? - sc->lParam = -1; - sc = NULL; // this can't be freed here - } - break; - } - - if (sc->lParam) { // is this the second ack ? - void* groupData; - int groupSize; - int bEnd = 1; // shall we end the sever modifications - - setWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wNewContactId); - setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wNewGroupId); - - servlistPendingRemoveContact(sc->hContact, sc->wNewContactId, sc->wNewGroupId, PENDING_RESULT_SUCCESS); - - if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) // update the group we moved from - { // the group is still not empty, just update it - updateServerGroupData(sc->wGroupId, groupData, groupSize, 0); - SAFE_FREE((void**)&groupData); // free the memory - } - else { // the group is empty, delete it - char* szGroup = getServListGroupCListPath(sc->wGroupId); - - servlistRemoveGroup(szGroup, sc->wGroupId); - SAFE_FREE((void**)&szGroup); - bEnd = 0; // here the modifications go on - } - - groupData = collectBuddyGroup(sc->wNewGroupId, &groupSize); // update the group we moved to - updateServerGroupData(sc->wNewGroupId, groupData, groupSize, bEnd ? SSOF_END_OPERATION : 0); - // end server modifications here - SAFE_FREE((void**)&groupData); - - } - else // contact was deleted from server-list - { - delSetting(sc->hContact, DBSETTING_SERVLIST_ID); - delSetting(sc->hContact, DBSETTING_SERVLIST_GROUP); - FreeServerID(sc->wContactId, SSIT_ITEM); // release old contact id - sc->lParam = 1; - sc = NULL; // wait for second ack - } - break; - - case SSA_CONTACT_FIX_AUTH: - if (wError) { // FIXME: something failed, we should handle it properly - } - break; - - case SSA_GROUP_RENAME: - if (wError) { - debugLogA("Renaming of server group failed, error %d", wError); - icq_LogMessage(LOG_WARNING, LPGEN("Renaming of server group failed.")); - - servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_FAILED); - } - else { - setServListGroupName(sc->wGroupId, sc->szGroupName); - removeGroupPathLinks(sc->wGroupId); - { // add group to known - char *szCListGroup = getServListGroupCListPath(sc->wGroupId); - - /// FIXME: need to create link to the new group name before unique item name correction as well - setServListGroupLinkID(szCListGroup, sc->wGroupId); - SAFE_FREE((void**)&szCListGroup); - } - servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); - } - SAFE_FREE((void**)&sc->szGroupName); - SAFE_FREE((void**)&sc->szGroup); - break; - - case SSA_SETAVATAR: - if (wError) { - debugLogA("Uploading of avatar hash failed."); - if (sc->wGroupId) { // is avatar added or updated? - FreeServerID(sc->wContactId, SSIT_ITEM); - delSetting(DBSETTING_SERVLIST_AVATAR); // to fix old versions - } - } - else setWord(DBSETTING_SERVLIST_AVATAR, sc->wContactId); - break; - - case SSA_REMOVEAVATAR: - if (wError) - debugLogA("Removing of avatar hash failed."); - else { - FreeServerID(sc->wContactId, SSIT_ITEM); - delSetting(DBSETTING_SERVLIST_AVATAR); - } - break; - - case SSA_SERVLIST_ACK: - ProtoBroadcastAck(sc->hContact, ICQACKTYPE_SERVERCLIST, wError ? ACKRESULT_FAILED : ACKRESULT_SUCCESS, (HANDLE)sc->lParam, wError); - break; - - case SSA_IMPORT: - if (wError) - debugLogA("Re-starting import sequence failed, error %d", wError); - else { - setWord("SrvImportID", 0); - delSetting("ImportTS"); - } - break; - - default: - debugLogA("Server ack cookie type (%d) not recognized.", sc->dwAction); - } - - SAFE_FREE((void**)&sc); // free the memory - return; -} - -MCONTACT CIcqProto::HContactFromRecordName(const char* szRecordName, int *bAdded) -{ - MCONTACT hContact = INVALID_CONTACT_ID; - - if (!IsStringUIN(szRecordName)) // probably AIM contact - hContact = HContactFromUID(0, szRecordName, bAdded); - else { // this should be ICQ number - DWORD dwUin = atoi(szRecordName); - hContact = HContactFromUIN(dwUin, bAdded); - } - return hContact; -} - -int CIcqProto::getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf) /// FIXME: need to keep original order -{ - // get server-list item's TLV data - oscar_tlv_chain* list = pChain; - int datalen = 0; - icq_packet pBuf; - - // Initialize our handy data buffer - pBuf.wPlace = 0; - pBuf.pData = buf; - - while (list) { // collect non-standard TLVs and save them to DB - if (list->tlv.wType != SSI_TLV_AWAITING_AUTH && - list->tlv.wType != SSI_TLV_NAME && - list->tlv.wType != SSI_TLV_COMMENT && - list->tlv.wType != SSI_TLV_METAINFO_TOKEN && - list->tlv.wType != SSI_TLV_METAINFO_TIME) { // only TLVs which we do not handle on our own - packTLV(&pBuf, list->tlv.wType, list->tlv.wLen, list->tlv.pData); - - datalen += list->tlv.wLen + 4; - } - list = list->next; - } - return datalen; -} - -void CIcqProto::handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info) -{ - BYTE bySSIVersion; - WORD wRecordCount; - WORD wRecord; - WORD wGroupId; - WORD wItemId; - WORD wTlvType; - WORD wTlvLength; - BOOL bIsLastPacket; - uid_str szRecordName; - oscar_tlv_chain* pChain = NULL; - oscar_tlv* pTLV = NULL; - char *szActiveSrvGroup = NULL; - WORD wActiveSrvGroupId = -1; - - - // If flag bit 1 is set, this is not the last - // packet. If it is 0, this is the last packet - // and there will be a timestamp at the end. - if (wFlags & 0x0001) - bIsLastPacket = FALSE; - else - bIsLastPacket = TRUE; - - if (wLen < 3) - return; - - // Version number of SSI protocol? - unpackByte(&buf, &bySSIVersion); - wLen -= 1; - - // Total count of following entries. This is the size of the server - // side contact list and should be saved and sent with CLI_CHECKROSTER. - // NOTE: When the entries are split up in several packets, each packet - // has it's own count and they must be added to get the total size of - // server list. - unpackWord(&buf, &wRecordCount); - wLen -= 2; - debugLogA("SSI: number of entries is %u, version is %u", wRecordCount, bySSIVersion); - - // Loop over all items in the packet - for (wRecord = 0; wRecord < wRecordCount; wRecord++) { - debugLogA("SSI: parsing record %u", wRecord + 1); - - if (wLen < 10) { // minimum: name length (zero), group ID, item ID, empty TLV - debugLogA("Warning: SSI parsing error (%d)", 0); - break; - } - - if (!unpackServerListItem(&buf, &wLen, szRecordName, &wGroupId, &wItemId, &wTlvType, &wTlvLength)) { // unpack basic structure - debugLogA("Warning: SSI parsing error (%d)", 1); - break; - } - - debugLogA("Name: '%s', GroupID: %u, EntryID: %u, EntryType: %u, TLVlength: %u", - szRecordName, wGroupId, wItemId, wTlvType, wTlvLength); - - if (wLen < wTlvLength) { - debugLogA("Warning: SSI parsing error (%d)", 2); - break; - } - - // Initialize the tlv chain - if (wTlvLength > 0) { - pChain = readIntoTLVChain(&buf, wTlvLength, 0); - wLen -= wTlvLength; - } - else pChain = NULL; - - switch (wTlvType) { - case SSI_ITEM_BUDDY: - { - /* this is a contact */ - int bAdded; - MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); - - if (hContact != INVALID_CONTACT_ID) { - int bRegroup = 0; - int bNicked = 0; - - if (bAdded) { // Not already on list: added - debugLogA("SSI added new %s contact '%s'", "ICQ", szRecordName); - - AddJustAddedContact(hContact); - } - else { // we should add new contacts and this contact was just added, show it - if (IsContactJustAdded(hContact)) { - setContactHidden(hContact, 0); - bAdded = 1; // we want details for new contacts - } - else debugLogA("SSI ignoring existing contact '%s'", szRecordName); - - // Contact on server is always on list - db_set_b(hContact, "CList", "NotOnList", 0); - } - - // Save group and item ID - setWord(hContact, DBSETTING_SERVLIST_ID, wItemId); - setWord(hContact, DBSETTING_SERVLIST_GROUP, wGroupId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - if (!bAdded && getByte("LoadServerDetails", DEFAULT_SS_LOAD)) { // check if the contact has been moved on the server - if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) { - SAFE_FREE(&szActiveSrvGroup); - szActiveSrvGroup = getServListGroupCListPath(wGroupId); - wActiveSrvGroupId = wGroupId; - } - char *szLocalGroup = getContactCListGroup(hContact); - - if (!strlennull(szLocalGroup)) { // no CListGroup - SAFE_FREE(&szLocalGroup); - - szLocalGroup = null_strdup(DEFAULT_SS_GROUP); - } - - if (strcmpnull(szActiveSrvGroup, szLocalGroup) && - (strlennull(szActiveSrvGroup) >= strlennull(szLocalGroup) || (szActiveSrvGroup && _strnicmp(szActiveSrvGroup, szLocalGroup, strlennull(szLocalGroup))))) { // contact moved to new group or sub-group or not to master group - bRegroup = 1; - } - if (bRegroup && !stricmpnull(DEFAULT_SS_GROUP, szActiveSrvGroup)) /// TODO: invent something more clever for "root" group - { // is it the default "General" group ? - bRegroup = 0; // if yes, do not move to it - cause it would hide the contact - } - SAFE_FREE(&szLocalGroup); - } - - if (bRegroup || bAdded) { // if we should load server details or contact was just added, update its group - if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) { - SAFE_FREE(&szActiveSrvGroup); - szActiveSrvGroup = getServListGroupCListPath(wGroupId); - wActiveSrvGroupId = wGroupId; - } - - if (szActiveSrvGroup) { // try to get Miranda Group path from groupid, if succeeded save to db - moveContactToCListGroup(hContact, szActiveSrvGroup); - } - } - - if (pChain) { // Look for nickname TLV and copy it to the db if necessary - if (pTLV = pChain->getTLV(SSI_TLV_NAME, 1)) { - if (pTLV->pData && (pTLV->wLen > 0)) { - char *pszNick; - WORD wNickLength; - - wNickLength = pTLV->wLen; - - pszNick = (char*)SAFE_MALLOC(wNickLength + 1); - // Copy buffer to utf-8 buffer - memcpy(pszNick, pTLV->pData, wNickLength); - pszNick[wNickLength] = 0; // Terminate string - - debugLogA("Nickname is '%s'", pszNick); - - bNicked = 1; - - // Write nickname to database - if (getByte("LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) { // if just added contact, save details always - does no harm - char *szOldNick; - - if (szOldNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL)) { - if ((strcmpnull(szOldNick, pszNick)) && (strlennull(pszNick) > 0)) { // check if the truncated nick changed, i.e. do not overwrite locally stored longer nick - if (strlennull(szOldNick) <= strlennull(pszNick) || strncmp(szOldNick, pszNick, null_strcut(szOldNick, MAX_SSI_TLV_NAME_SIZE))) { - // Yes, we really do need to delete it first. Otherwise the CLUI nick - // cache isn't updated (I'll look into it) - db_unset(hContact, "CList", "MyHandle"); - db_set_utf(hContact, "CList", "MyHandle", pszNick); - } - } - SAFE_FREE(&szOldNick); - } - else if (strlennull(pszNick) > 0) { - db_unset(hContact, "CList", "MyHandle"); - db_set_utf(hContact, "CList", "MyHandle", pszNick); - } - } - SAFE_FREE(&pszNick); - } - else debugLogA("Invalid nickname"); - } - if (bAdded && !bNicked) - icq_QueueUser(hContact); // queue user without nick for fast auto info update - - // Look for comment TLV and copy it to the db if necessary - if (pTLV = pChain->getTLV(SSI_TLV_COMMENT, 1)) { - if (pTLV->pData && (pTLV->wLen > 0)) { - char *pszComment; - WORD wCommentLength; - - - wCommentLength = pTLV->wLen; - - pszComment = (char*)SAFE_MALLOC(wCommentLength + 1); - // Copy buffer to utf-8 buffer - memcpy(pszComment, pTLV->pData, wCommentLength); - pszComment[wCommentLength] = 0; // Terminate string - - debugLogA("Comment is '%s'", pszComment); - - // Write comment to database - if (getByte("LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) { // if just added contact, save details always - does no harm - char *szOldComment; - - if (szOldComment = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL)) { - if ((strcmpnull(szOldComment, pszComment)) && (strlennull(pszComment) > 0)) // check if the truncated comment changed, i.e. do not overwrite locally stored longer comment - if (strlennull(szOldComment) <= strlennull(pszComment) || strncmp((char*)szOldComment, (char*)pszComment, null_strcut(szOldComment, MAX_SSI_TLV_COMMENT_SIZE))) - db_set_utf(hContact, "UserInfo", "MyNotes", pszComment); - - SAFE_FREE((void**)&szOldComment); - } - else if (strlennull(pszComment) > 0) - db_set_utf(hContact, "UserInfo", "MyNotes", pszComment); - } - SAFE_FREE((void**)&pszComment); - } - else debugLogA("Invalid comment"); - } - - // Look for need-authorization TLV - if (pChain->getTLV(SSI_TLV_AWAITING_AUTH, 1)) { - setByte(hContact, "Auth", 1); - debugLogA("SSI contact need authorization"); - } - else setByte(hContact, "Auth", 0); - - if (pTLV = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1)) { - setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pTLV->pData, pTLV->wLen); - if (pChain->getTLV(SSI_TLV_METAINFO_TIME, 1)) - setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); - debugLogA("SSI contact has meta info token"); - } - else { - delSetting(hContact, DBSETTING_METAINFO_TOKEN); - delSetting(hContact, DBSETTING_METAINFO_TIME); - } - - { // store server-list item's TLV data - BYTE* data = (BYTE*)SAFE_MALLOC(wTlvLength); - int datalen = getServerDataFromItemTLV(pChain, data); - - if (datalen > 0) - setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); - else - delSetting(hContact, DBSETTING_SERVLIST_DATA); - - SAFE_FREE((void**)&data); - } - } - } - else // failed to add or other error - debugLogA("SSI failed to handle %s Item '%s'", "Buddy", szRecordName); - } - break; - - case SSI_ITEM_GROUP: - if ((wGroupId == 0) && (wItemId == 0)) { - /* list of groups. wTlvType=1, data is TLV(C8) containing list of WORDs which */ - /* is the group ids - /* we don't need to use this. Our processing is on-the-fly */ - /* this record is always sent first in the first packet only, */ - } - else if (wGroupId != 0) { - /* wGroupId != 0: a group record */ - if (wItemId == 0) { /* no item ID: this is a group */ - /* pszRecordName is the name of the group */ - ReserveServerID(wGroupId, SSIT_GROUP, 0); - - setServListGroupName(wGroupId, szRecordName); - - debugLogA("Group %s added to known groups.", szRecordName); - - /* demangle full grouppath, set it to known */ - SAFE_FREE(&szActiveSrvGroup); - szActiveSrvGroup = getServListGroupCListPath(wGroupId); - wActiveSrvGroupId = wGroupId; - - /* TLV contains a TLV(C8) with a list of WORDs of contained contact IDs */ - /* our processing is good enough that we don't need this duplication */ - } - else debugLogA("Unhandled type 0x01, wItemID != 0"); - } - else debugLogA("Unhandled type 0x01"); - break; - - case SSI_ITEM_PERMIT: - { - /* item on visible list */ - /* wItemId not related to contact ID */ - /* pszRecordName is the UIN */ - int bAdded; - MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); - - if (hContact != INVALID_CONTACT_ID) { - if (bAdded) { - debugLogA("SSI added new %s contact '%s'", "Permit", szRecordName); - // It wasn't previously in the list, we hide it so it only appears in the visible list - setContactHidden(hContact, 1); - // Add it to the list, so it can be added properly if proper contact - AddJustAddedContact(hContact); - } - else debugLogA("SSI %s contact already exists '%s'", "Permit", szRecordName); - - // Save permit ID - setWord(hContact, DBSETTING_SERVLIST_PERMIT, wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - // Set apparent mode - setWord(hContact, "ApparentMode", ID_STATUS_ONLINE); - debugLogA("Visible-contact (%s)", szRecordName); - } - else { // failed to add or other error - debugLogA("SSI failed to handle %s Item '%s'", "Permit", szRecordName); - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - } - } - break; - - case SSI_ITEM_DENY: - { - /* Item on invisible list */ - /* wItemId not related to contact ID */ - /* pszRecordName is the UIN */ - int bAdded; - MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); - - if (hContact != INVALID_CONTACT_ID) { - if (bAdded) { - /* not already on list: added */ - debugLogA("SSI added new %s contact '%s'", "Deny", szRecordName); - // It wasn't previously in the list, we hide it so it only appears in the visible list - setContactHidden(hContact, 1); - // Add it to the list, so it can be added properly if proper contact - AddJustAddedContact(hContact); - } - else debugLogA("SSI %s contact already exists '%s'", "Deny", szRecordName); - - // Save Deny ID - setWord(hContact, DBSETTING_SERVLIST_DENY, wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - // Set apparent mode - setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); - debugLogA("Invisible-contact (%s)", szRecordName); - } - else { // failed to add or other error - debugLogA("SSI failed to handle %s Item '%s'", "Deny", szRecordName); - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - } - } - break; - - case SSI_ITEM_VISIBILITY: /* My visibility settings */ - // Look for visibility TLV - if (BYTE bVisibility = pChain->getByte(SSI_TLV_VISIBILITY, 1)) { // found it, store the id, we do not need current visibility - we do not rely on it - setWord(DBSETTING_SERVLIST_PRIVACY, wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - debugLogA("Visibility is %u", bVisibility); - } - else ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - break; - - case SSI_ITEM_IGNORE: - { - /* item on ignore list */ - /* wItemId not related to contact ID */ - /* pszRecordName is the UIN */ - int bAdded; - MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); - - if (hContact != INVALID_CONTACT_ID) { - if (bAdded) { - /* not already on list: add */ - debugLogA("SSI added new %s contact '%s'", "Ignore", szRecordName); - // It wasn't previously in the list, we hide it - setContactHidden(hContact, 1); - // Add it to the list, so it can be added properly if proper contact - AddJustAddedContact(hContact); - } - else debugLogA("SSI %s contact already exists '%s'", "Ignore", szRecordName); - - // Save Ignore ID - setWord(hContact, DBSETTING_SERVLIST_IGNORE, wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - // Set apparent mode & ignore - setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); - // set ignore all events - CallService(MS_IGNORE_IGNORE, hContact, IGNOREEVENT_ALL); - debugLogA("Ignore-contact (%s)", szRecordName); - } - else { // failed to add or other error - debugLogA("SSI failed to handle %s Item '%s'", "Ignore", szRecordName); - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - } - } - break; - - case SSI_ITEM_UNKNOWN2: - debugLogA("SSI unknown type 0x11"); - - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - break; - - case SSI_ITEM_IMPORTTIME: - if (wGroupId == 0) { - /* time our list was first imported */ - /* pszRecordName is "Import Time" */ - /* data is TLV(13) {TLV(D4) {time_t importTime}} */ - setDword("ImportTS", pChain->getDWord(SSI_TLV_TIMESTAMP, 1)); - setWord("SrvImportID", wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - debugLogA("SSI %s item recognized", "first import"); - } - break; - - case SSI_ITEM_BUDDYICON: - if (wGroupId == 0) { - /* our avatar MD5-hash */ - /* pszRecordName is "1" */ - /* data is TLV(D5) hash */ - /* we ignore this, just save the id */ - /* cause we get the hash again after login */ - if (!strcmpnull(szRecordName, "12")) { // need to handle Photo Item separately - setWord(DBSETTING_SERVLIST_PHOTO, wItemId); - debugLogA("SSI %s item recognized", "Photo"); - } - else { - setWord(DBSETTING_SERVLIST_AVATAR, wItemId); - debugLogA("SSI %s item recognized", "Avatar"); - } - ReserveServerID(wItemId, SSIT_ITEM, 0); - } - break; - - case SSI_ITEM_METAINFO: - if (wGroupId == 0) { - /* our meta info token & last update time */ - /* pszRecordName is "ICQ-MDIR" */ - /* data is TLV(15C) and TLV(15D) */ - oscar_tlv* pToken = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1); - oscar_tlv* pTime = pChain->getTLV(SSI_TLV_METAINFO_TIME, 1); - if (pToken) - setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); - if (pTime) - setSettingDouble(NULL, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); - - setWord(DBSETTING_SERVLIST_METAINFO, wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - debugLogA("SSI %s item recognized", "Meta info"); - } - break; - - case SSI_ITEM_CLIENTDATA: - if (wGroupId == 0) { - /* ICQ2k ShortcutBar Items */ - /* data is TLV(CD) text */ - if (wItemId) - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - } - - case SSI_ITEM_SAVED: - case SSI_ITEM_PREAUTH: - break; - - default: - debugLogA("SSI unhandled item %2x", wTlvType); - - if (wItemId) - ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); - break; - } - - disposeChain(&pChain); - } // end for - - // Release Memory - SAFE_FREE(&szActiveSrvGroup); - - debugLogA("Bytes left: %u", wLen); - - setWord("SrvRecordCount", (WORD)(wRecord + getWord("SrvRecordCount", 0))); - - if (bIsLastPacket) { - // No contacts left to sync - bIsSyncingCL = FALSE; - - StoreServerIDs(); - - icq_RescanInfoUpdate(); - - if (wLen >= 4) { - DWORD dwLastUpdateTime; - - /* finally we get a time_t of the last update time */ - unpackDWord(&buf, &dwLastUpdateTime); - setDword("SrvLastUpdate", dwLastUpdateTime); - debugLogA("Last update of server list was (%u) %s", dwLastUpdateTime, time2text(dwLastUpdateTime)); - - sendRosterAck(); - handleServUINSettings(wListenPort, info); - - servlistProcessLogin(); - } - else debugLogA("Last packet missed update time..."); - - if (getWord("SrvRecordCount", 0) == 0) { // we got empty serv-list, create master group - cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (ack) { - DWORD dwCookie; - - ack->dwAction = SSA_GROUP_UPDATE; - ack->szGroupName = null_strdup(""); - dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); - icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, 0, ack->szGroupName, NULL, 0, 0); - } - } - // serv-list sync finished, clear just added contacts - FlushJustAddedContacts(); - } - else debugLogA("Waiting for more packets"); -} - -void CIcqProto::handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) -{ - if (wItemType == SSI_ITEM_IMPORTTIME) { - if (pItemData) { - setDword("ImportTS", pItemData->getDWord(SSI_TLV_TIMESTAMP, 1)); - setWord("SrvImportID", wItemId); - ReserveServerID(wItemId, SSIT_ITEM, 0); - - debugLogA("Server added Import timestamp to list"); - return; - } - } - // Reserve server-list ID - ReserveServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM, SSIF_UNHANDLED); -} - -void CIcqProto::handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) -{ - MCONTACT hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; - - if (hContact != INVALID_CONTACT_ID && wItemType == SSI_ITEM_BUDDY) { // a contact was updated on server - if (pItemData) { - oscar_tlv *pAuth = pItemData->getTLV(SSI_TLV_AWAITING_AUTH, 1); - BYTE bAuth = getByte(hContact, "Auth", 0); - - if (bAuth && !pAuth) { // server authorized our contact - char str[MAX_PATH]; - char msg[MAX_PATH]; - char *nick = NickFromHandleUtf(hContact); - - setByte(hContact, "Auth", 0); - mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" was authorized in the server list."), msg, MAX_PATH), nick); - icq_LogMessage(LOG_WARNING, str); - SAFE_FREE(&nick); - } - else if (!bAuth && pAuth) { // server took away authorization of our contact - char str[MAX_PATH]; - char msg[MAX_PATH]; - char *nick = NickFromHandleUtf(hContact); - - setByte(hContact, "Auth", 1); - mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" lost its authorization in the server list."), msg, MAX_PATH), nick); - icq_LogMessage(LOG_WARNING, str); - SAFE_FREE(&nick); - } - { - // update metainfo data - DBVARIANT dbv = { 0 }; - oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); - oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); - - if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) { - if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) { - if (!pToken) - debugLogA("Contact %s, meta info token removed", szRecordName); - else - debugLogA("Contact %s, meta info token changed", szRecordName); - - // user info was changed, refresh - if (IsMetaInfoChanged(hContact)) - icq_QueueUser(hContact); - } - - db_free(&dbv); - } - else if (pToken) { - debugLogA("Contact %s, meta info token added", szRecordName); - - // user info was changed, refresh - if (IsMetaInfoChanged(hContact)) - icq_QueueUser(hContact); - } - - if (pToken) - setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); - if (pTime) - setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); - } - { - // update server's data - otherwise consequent operations can fail with 0x0E - BYTE *data = (BYTE*)_alloca(pItemData->getChainLength()); - int datalen = getServerDataFromItemTLV(pItemData, data); - - if (datalen > 0) - setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); - else - delSetting(hContact, DBSETTING_SERVLIST_DATA); - } - } - } - else if (wItemType == SSI_ITEM_METAINFO) { // owner MetaInfo data updated - if (pItemData) { - DBVARIANT dbv = { 0 }; - oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); - oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); - - if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) { - if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) { - if (!pToken) - debugLogA("Owner meta info token removed"); - else - debugLogA("Owner meta info token changed"); - } - - db_free(&dbv); - } - - if (pToken) - setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); - if (pTime) - setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); - } - } - else if (wItemType == SSI_ITEM_GROUP) // group updated - debugLogA("Server updated our group \"%s\" on list", szRecordName); -} - -void CIcqProto::handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) -{ - MCONTACT hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; - - if (hContact != INVALID_CONTACT_ID && wItemType == SSI_ITEM_BUDDY) { // a contact was removed from our list - if (getWord(hContact, DBSETTING_SERVLIST_ID, 0) == wItemId) { - delSetting(hContact, DBSETTING_SERVLIST_ID); - delSetting(hContact, DBSETTING_SERVLIST_GROUP); - delSetting(hContact, "Auth"); - - char str[MAX_PATH]; - char msg[MAX_PATH]; - char *nick = NickFromHandleUtf(hContact); - - mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("User \"%s\" was removed from server list."), msg, MAX_PATH), nick); - icq_LogMessage(LOG_WARNING, str); - SAFE_FREE(&nick); - } - } - // Release server-list ID - FreeServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM); -} - -void CIcqProto::handleRecvAuthRequest(unsigned char *buf, WORD wLen) -{ - DWORD dwUin; - uid_str szUid; - if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) - return; - - if (dwUin && IsOnSpammerList(dwUin)) { - debugLogA("Ignored Message from known Spammer"); - return; - } - - WORD wReasonLen; - unpackWord(&buf, &wReasonLen); - wLen -= 2; - if (wReasonLen > wLen) - return; - - int bAdded; - MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); - - PROTORECVEVENT pre = { 0 }; - pre.timestamp = time(NULL); - pre.lParam = sizeof(DWORD) * 2 + 5; - // Prepare reason - char *szReason = (char*)SAFE_MALLOC(wReasonLen + 1); - int nReasonLen = 0; - if (szReason) { - memcpy(szReason, buf, wReasonLen); - szReason[wReasonLen] = '\0'; - nReasonLen = strlennull(szReason); - - char *temp = (char*)_alloca(nReasonLen + 2); - if (!IsUSASCII(szReason, nReasonLen) && UTF8_IsValid(szReason) && utf8_decode_static(szReason, temp, nReasonLen + 1)) - pre.flags |= PREF_UTF; - } - - // Read nick name from DB - char *szNick = NULL; - if (dwUin) { - DBVARIANT dbv = { 0 }; - if (pre.flags & PREF_UTF) - szNick = getSettingStringUtf(hContact, "Nick", NULL); - else if (!getString(hContact, "Nick", &dbv)) { - szNick = null_strdup(dbv.pszVal); - db_free(&dbv); - } - } - else szNick = null_strdup(szUid); - - int nNickLen = strlennull(szNick); - - pre.lParam += nNickLen + nReasonLen; - - setByte(hContact, "Grant", 1); - - /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/ - char *szBlob = (char *)_alloca(pre.lParam); - char *pCurBlob = szBlob; - *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); - *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); - - if (nNickLen) { // if we have nick we add it, otherwise keep trailing zero - memcpy(pCurBlob, szNick, nNickLen); - pCurBlob += nNickLen; - } - *pCurBlob = 0; pCurBlob++; // Nick - *pCurBlob = 0; pCurBlob++; // FirstName - *pCurBlob = 0; pCurBlob++; // LastName - *pCurBlob = 0; pCurBlob++; // email - if (nReasonLen) { - memcpy(pCurBlob, szReason, nReasonLen); - pCurBlob += nReasonLen; - } - *pCurBlob = 0; // Reason - pre.szMessage = szBlob; - - // TODO: Change for new auth system, include all known informations - ProtoChainRecv(hContact, PSR_AUTH, 0, (LPARAM)&pre); - - SAFE_FREE(&szNick); - SAFE_FREE(&szReason); -} - -void CIcqProto::handleRecvAdded(unsigned char *buf, WORD wLen) -{ - DWORD dwUin; - uid_str szUid; - DWORD cbBlob; - PBYTE pBlob, pCurBlob; - int bAdded; - char* szNick; - int nNickLen; - DBVARIANT dbv = { 0 }; - - if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; - - if (dwUin && IsOnSpammerList(dwUin)) { - debugLogA("Ignored Message from known Spammer"); - return; - } - - MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); - - cbBlob = sizeof(DWORD) * 2 + 4; - - if (dwUin) { - if (getString(hContact, "Nick", &dbv)) - nNickLen = 0; - else { - szNick = dbv.pszVal; - nNickLen = strlennull(szNick); - } - } - else nNickLen = strlennull(szUid); - - cbBlob += nNickLen; - - pCurBlob = pBlob = (PBYTE)_alloca(cbBlob); - /*blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */ - *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); - *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); - if (nNickLen && dwUin) { // if we have nick we add it, otherwise keep trailing zero - memcpy(pCurBlob, szNick, nNickLen); - pCurBlob += nNickLen; - } - else { - memcpy(pCurBlob, szUid, nNickLen); - pCurBlob += nNickLen; - } - *(char *)pCurBlob = 0; pCurBlob++; - *(char *)pCurBlob = 0; pCurBlob++; - *(char *)pCurBlob = 0; pCurBlob++; - *(char *)pCurBlob = 0; - // TODO: Change for new auth system - - AddEvent(NULL, EVENTTYPE_ADDED, time(NULL), 0, cbBlob, pBlob); -} - -void CIcqProto::handleRecvAuthResponse(unsigned char *buf, WORD wLen) -{ - DWORD dwUin; - uid_str szUid; - char* szNick = NULL; - WORD nReasonLen; - char* szReason; - int bAdded; - - BYTE bResponse = 0xFF; - - if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; - - if (dwUin && IsOnSpammerList(dwUin)) { - debugLogA("Ignored Message from known Spammer"); - return; - } - - MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); - if (hContact != INVALID_CONTACT_ID) - szNick = NickFromHandle(hContact); - - if (wLen > 0) { - unpackByte(&buf, &bResponse); - wLen -= 1; - } - if (wLen >= 2) { - unpackWord(&buf, &nReasonLen); - wLen -= 2; - if (wLen >= nReasonLen) { - szReason = (char*)_alloca(nReasonLen + 1); - unpackString(&buf, szReason, nReasonLen); - szReason[nReasonLen] = '\0'; - } - } - - switch (bResponse) { - case 0: - debugLogA("Authorization request %s by %s", "denied", strUID(dwUin, szUid)); - // TODO: Add to system history as soon as new auth system is ready - break; - - case 1: - setByte(hContact, "Auth", 0); - debugLogA("Authorization request %s by %s", "granted", strUID(dwUin, szUid)); - // TODO: Add to system history as soon as new auth system is ready - break; - - default: - debugLogA("Unknown Authorization request response (%u) from %s", bResponse, strUID(dwUin, szUid)); - break; - - } - SAFE_FREE(&szNick); -} - -// Updates the visibility code used while in SSI mode. If a server ID is -// not stored in the local DB, a new ID will be added to the server list. -// -// Possible values are: -// 01 - Allow all users to see you -// 02 - Block all users from seeing you -// 03 - Allow only users in the permit list to see you -// 04 - Block only users in the invisible list from seeing you -// 05 - Allow only users in the buddy list to see you -// -void CIcqProto::updateServVisibilityCode(BYTE bCode) -{ - icq_packet packet; - WORD wVisibilityID; - WORD wCommand; - - if ((bCode > 0) && (bCode < 6)) { - cookie_servlist_action* ack; - DWORD dwCookie; - BYTE bVisibility = getByte("SrvVisibility", 0); - - if (bVisibility == bCode) // if no change was made, not necescary to update that - return; - setByte("SrvVisibility", bCode); - - // Do we have a known server visibility ID? We should, unless we just subscribed to the serv-list for the first time - if ((wVisibilityID = getWord(DBSETTING_SERVLIST_PRIVACY, 0)) == 0) { - // No, create a new random ID - wVisibilityID = GenerateServerID(SSIT_ITEM, 0); - setWord(DBSETTING_SERVLIST_PRIVACY, wVisibilityID); - wCommand = ICQ_LISTS_ADDTOLIST; - - debugLogA("Made new srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); - } - else { - debugLogA("Reused srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); - wCommand = ICQ_LISTS_UPDATEGROUP; - } - - ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (!ack) { - debugLogA("Cookie alloc failure."); - return; // out of memory, go away - } - - ack->dwAction = SSA_VISIBILITY; // update visibility - dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie - - // Build and send packet - serverPacketInit(&packet, 25); - packFNACHeader(&packet, ICQ_LISTS_FAMILY, wCommand, 0, dwCookie); - packWord(&packet, 0); // Name (null) - packWord(&packet, 0); // GroupID (0 if not relevant) - packWord(&packet, wVisibilityID); // EntryID - packWord(&packet, SSI_ITEM_VISIBILITY); // EntryType - packWord(&packet, 5); // Length in bytes of following TLV - packTLV(&packet, SSI_TLV_VISIBILITY, 1, &bCode); // TLV (Visibility) - sendServPacket(&packet); - // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or - // ICQ_LISTS_CLI_MODIFYEND when modifying the visibility code - } -} - -// Updates the avatar hash used while in SSI mode. If a server ID is -// not stored in the local DB, a new ID will be added to the server list. -void CIcqProto::updateServAvatarHash(BYTE *pHash, int size) -{ - void** pDoubleObject = NULL; - void* doubleObject = NULL; - DWORD dwOperationFlags = 0; - WORD wAvatarID; - WORD wCommand; - char szItemName[2] = { 0, 0 }; - - int bResetHash = 0; - DBVARIANT dbvHash; - if (!getSetting(NULL, "AvatarHash", &dbvHash)) { - szItemName[0] = 0x30 + dbvHash.pbVal[1]; - - if (memcmp(pHash, dbvHash.pbVal, 2) != 0) // add code to remove old hash from server - bResetHash = 1; - - db_free(&dbvHash); - } - - if (bResetHash) { // start update session - // pair the packets (need to be send in the correct order - dwOperationFlags |= SSOF_BEGIN_OPERATION | SSOF_END_OPERATION; - pDoubleObject = &doubleObject; - } - - if (bResetHash || !pHash) { - // Do we have a known server avatar ID? - if (wAvatarID = getWord(DBSETTING_SERVLIST_AVATAR, 0)) { - cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (!ack) { - debugLogA("Cookie alloc failure."); - return; // out of memory, go away - } - ack->dwAction = SSA_REMOVEAVATAR; // update avatar hash - ack->wContactId = wAvatarID; - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); // take cookie - icq_sendServerItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, wAvatarID, szItemName, NULL, 0, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); - } - } - - if (!pHash) - return; - - WORD hashsize = size - 2; - - // Do we have a known server avatar ID? We should, unless we just subscribed to the serv-list for the first time - if (bResetHash || (wAvatarID = getWord(DBSETTING_SERVLIST_AVATAR, 0)) == 0) { - // No, create a new random ID - wAvatarID = GenerateServerID(SSIT_ITEM, 0); - wCommand = ICQ_LISTS_ADDTOLIST; - debugLogA("Made new srvAvatarID, id is %u", wAvatarID); - } - else { - debugLogA("Reused srvAvatarID, id is %u", wAvatarID); - wCommand = ICQ_LISTS_UPDATEGROUP; - } - - cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); - if (!ack) { - debugLogA("Cookie alloc failure."); - return; // out of memory, go away - } - ack->dwAction = SSA_SETAVATAR; // update avatar hash - ack->wContactId = wAvatarID; - DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie - - szItemName[0] = 0x30 + pHash[1]; - - // Build the packet - WORD wTLVlen = 8 + hashsize; - - // Initialize our handy data buffer - icq_packet pBuffer; - pBuffer.wPlace = 0; - pBuffer.pData = (BYTE *)_alloca(wTLVlen); - pBuffer.wLen = wTLVlen; - - packTLV(&pBuffer, SSI_TLV_NAME, 0, NULL); // TLV (Name) - packTLV(&pBuffer, SSI_TLV_AVATARHASH, hashsize, pHash + 2); // TLV (Hash) - - icq_sendServerItem(dwCookie, wCommand, 0, wAvatarID, szItemName, pBuffer.pData, wTLVlen, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); - // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or - // ICQ_LISTS_CLI_MODIFYEND when modifying the avatar hash -} - -// Should be called before the server list is modified. When all -// modifications are done, call icq_sendServerEndOperation(). -// Called automatically thru server-list update board! -void CIcqProto::icq_sendServerBeginOperation(int bImport) -{ - WORD wImportID = getWord("SrvImportID", 0); - - if (bImport && wImportID) { // we should be importing, check if already have import item - if (getDword("ImportTS", 0) + 604800 < getDword("LogonTS", 0)) { // is the timestamp week older, clear it and begin new import - DWORD dwCookie; - cookie_servlist_action* ack; - - if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) { // we have cookie good, go on - ack->dwAction = SSA_IMPORT; - dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); - - icq_sendSimpleItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, "ImportTime", 0, wImportID, SSI_ITEM_IMPORTTIME, SSOP_ITEM_ACTION | SSOF_SEND_DIRECTLY, 100); - } - } - } - - icq_packet packet; - serverPacketInit(&packet, (WORD)(bImport ? 14 : 10)); - packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYSTART); - if (bImport) - packDWord(&packet, 1 << 0x10); - sendServPacket(&packet); -} - -// Should be called after the server list has been modified to inform -// the server that we are done. -// Called automatically thru server-list update board! -void CIcqProto::icq_sendServerEndOperation() -{ - icq_packet packet; - serverPacketInit(&packet, 10); - packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYEND); - sendServPacket(&packet); -} - -// Sent when the last roster packet has been received -void CIcqProto::sendRosterAck(void) -{ - icq_packet packet; - serverPacketInit(&packet, 10); - packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GOTLIST); - sendServPacket(&packet); - - debugLogA("Sent SNAC(x13,x07) - CLI_ROSTERACK"); -} +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// Copyright © 2012-2014 Miranda NG Team +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + +static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength); + + +void CIcqProto::handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info) +{ + switch (pSnacHeader->wSubtype) { + case ICQ_LISTS_ACK: // UPDATE_ACK + if (wBufferLength >= 2) { + WORD wError; + cookie_servlist_action* sc; + + unpackWord(&pBuffer, &wError); + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // look for action cookie + debugLogA("Received expected server list ack, action: %d, result: %d", sc->dwAction, wError); + FreeCookie(pSnacHeader->dwRef); // release cookie + + if (sc->dwAction == SSA_ACTION_GROUP) { // group cookie, handle sub-items + int i; + + debugLogA("Server-List: Grouped action contains %d actions.", sc->dwGroupCount); + + pBuffer -= 2; // revoke unpack + if (wBufferLength != 2 * sc->dwGroupCount) + debugLogA("Error: Server list ack does not contain expected amount of result codes (%u != %u)", wBufferLength / 2, sc->dwGroupCount); + + for (i = 0; i < sc->dwGroupCount; i++) { + if (wBufferLength >= 2) { // get proper result code + unpackWord(&pBuffer, &wError); + wBufferLength -= 2; + } + else // missing result code, give some special + wError = -1; + + debugLogA("Action: %d, ack result: %d", sc->pGroupItems[i]->dwAction, wError); + + // call normal ack handler + handleServerCListAck(sc->pGroupItems[i], wError); + } + // Release cookie + SAFE_FREE((void**)&sc->pGroupItems); + SAFE_FREE((void**)&sc); + } + else // single ack + handleServerCListAck(sc, wError); + } + else debugLogA("Received unexpected server list ack %u", wError); + } + break; + + case ICQ_LISTS_SRV_REPLYLISTS: + /* received server-list rights */ + handleServerCListRightsReply(pBuffer, wBufferLength); + debugLogA("Server sent SNAC(x13,x03) - SRV_REPLYLISTS"); + break; + + case ICQ_LISTS_LIST: // SRV_REPLYROSTER + { + cookie_servlist_action* sc; + BOOL blWork = bIsSyncingCL; + bIsSyncingCL = TRUE; // this is not used if cookie takes place + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // we do it by reliable cookie + if (!sc->lParam) { // is this first packet ? + ResetSettingsOnListReload(); + sc->lParam = 1; + } + handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); + if (!(pSnacHeader->wFlags & 0x0001)) // was that last packet ? + ReleaseCookie(pSnacHeader->dwRef); // yes, release cookie + } + else { // use old fake + if (!blWork) // this can fail on some crazy situations + ResetSettingsOnListReload(); + + handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); + } + } + break; + + case ICQ_LISTS_UPTODATE: // SRV_REPLYROSTEROK + bIsSyncingCL = FALSE; + { + cookie_servlist_action* sc; + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // we requested servlist check + debugLogA("Server stated roster is ok."); + ReleaseCookie(pSnacHeader->dwRef); + LoadServerIDs(); + } + else debugLogA("Server sent unexpected SNAC(x13,x0F) - SRV_REPLYROSTEROK"); + + // This will activate the server side list + sendRosterAck(); // this must be here, cause of failures during cookie alloc + handleServUINSettings(wListenPort, info); + } + break; + + case ICQ_LISTS_CLI_MODIFYSTART: + debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYSTART, "Server is modifying contact list"); + break; + + case ICQ_LISTS_CLI_MODIFYEND: + debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYEND, "End of server modification"); + break; + + case ICQ_LISTS_ADDTOLIST: + case ICQ_LISTS_UPDATEGROUP: + case ICQ_LISTS_REMOVEFROMLIST: + { + int nItems = 0; + + while (wBufferLength >= 10) { + WORD wGroupId, wItemId, wItemType, wTlvLen; + uid_str szRecordName; + + if (unpackServerListItem(&pBuffer, &wBufferLength, szRecordName, &wGroupId, &wItemId, &wItemType, &wTlvLen)) { + BYTE *buf = pBuffer; + oscar_tlv_chain *pChain = NULL; + + nItems++; + + // parse possible item's data + if (wBufferLength >= wTlvLen && wTlvLen > 0) { + pChain = readIntoTLVChain(&buf, wTlvLen, 0); + pBuffer += wTlvLen; + wBufferLength -= wTlvLen; + } + else if (wTlvLen > 0) + wBufferLength = 0; + + // process item change + if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) + handleServerCListItemAdd(szRecordName, wGroupId, wItemId, wItemType, pChain); + else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) + handleServerCListItemUpdate(szRecordName, wGroupId, wItemId, wItemType, pChain); + else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) + handleServerCListItemDelete(szRecordName, wGroupId, wItemId, wItemType, pChain); + + // release memory + disposeChain(&pChain); + } + } + { + // log packet basics + char *szChange; + + if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) + szChange = "Server added %u item(s) to list"; + else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) + szChange = "Server updated %u item(s) on list"; + else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) + szChange = "Server removed %u item(s) from list"; + + char szLogText[MAX_PATH]; + mir_snprintf(szLogText, MAX_PATH, szChange, nItems); + debugLogA("Server sent SNAC(x13,x%02x) - %s", pSnacHeader->wSubtype, szLogText); + } + } + break; + + case ICQ_LISTS_AUTHREQUEST: + handleRecvAuthRequest(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_SRV_AUTHRESPONSE: + handleRecvAuthResponse(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_AUTHGRANTED: + debugLogA("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_AUTHGRANTED, "User granted us future authorization"); + break; + + case ICQ_LISTS_YOUWEREADDED: + handleRecvAdded(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_ERROR: + if (wBufferLength >= 2) { + WORD wError; + cookie_servlist_action* sc; + + unpackWord(&pBuffer, &wError); + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) { // look for action cookie + debugLogA("Received server list error, action: %d, result: %d", sc->dwAction, wError); + FreeCookie(pSnacHeader->dwRef); // release cookie + + if (sc->dwAction == SSA_CHECK_ROSTER) { // the serv-list is unavailable turn it off + icq_LogMessage(LOG_ERROR, LPGEN("Server contact list is unavailable, Miranda will use local contact list.")); + m_bSsiEnabled = 0; + handleServUINSettings(wListenPort, info); + } + /// FIXME: properly release pending operations & cookie memory + SAFE_FREE((void**)&sc); + } + else LogFamilyError(ICQ_LISTS_FAMILY, wError); + } + break; + + default: + debugLogA("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LISTS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength) +{ + WORD wRecordNameLen; + + // The name of the entry. If this is a group header, then this + // is the name of the group. If it is a plain contact list entry, + // then it's the UIN of the contact. + unpackWord(pbuf, &wRecordNameLen); + if (*pwLen < 10 + wRecordNameLen || wRecordNameLen >= MAX_PATH) + return 0; // Failure + + unpackString(pbuf, pszRecordName, wRecordNameLen); + if (pszRecordName) + pszRecordName[wRecordNameLen] = '\0'; + + // The group identifier this entry belongs to. If 0, this is meta information or + // a contact without a group + unpackWord(pbuf, pwGroupId); + + // The ID of this entry. Group headers have ID 0. Otherwise, this + // is a random number generated when the user is added to the + // contact list, or when the user is ignored. See CLI_ADDBUDDY. + unpackWord(pbuf, pwItemId); + + // This field indicates what type of entry this is + unpackWord(pbuf, pwItemType); + + // The length in bytes of the following TLV chain + unpackWord(pbuf, pwTlvLength); + + *pwLen -= wRecordNameLen + 10; + + return 1; // Success +} + + +void CIcqProto::handleServerCListRightsReply(BYTE *buf, WORD wLen) +{ + /* received list rights, store the item limits for future use */ + oscar_tlv_chain* chain; + + memset(m_wServerListLimits, -1, sizeof(m_wServerListLimits)); + m_wServerListGroupMaxContacts = 0; + m_wServerListRecordNameMaxLength = 0xFFFF; + + if (chain = readIntoTLVChain(&buf, wLen, 0)) { + // determine max number of contacts in a group + m_wServerListGroupMaxContacts = chain->getWord(0x0C, 1); + // determine length limit for server-list item's name + m_wServerListRecordNameMaxLength = chain->getWord(0x06, 1); + + if (oscar_tlv *pTLV = chain->getTLV(0x04, 1)) { // limits for item types + WORD *pLimits = (WORD*)pTLV->pData; + for (int i = 0; i < pTLV->wLen / 2; i++) { + m_wServerListLimits[i] = (pLimits[i] & 0xFF) << 8 | (pLimits[i] >> 8); + + if (i + 1 >= SIZEOF(m_wServerListLimits)) + break; + } + + debugLogA("SSI: Max %d contacts (%d per group), %d groups, %d permit, %d deny, %d ignore items.", m_wServerListLimits[SSI_ITEM_BUDDY], m_wServerListGroupMaxContacts, m_wServerListLimits[SSI_ITEM_GROUP], m_wServerListLimits[SSI_ITEM_PERMIT], m_wServerListLimits[SSI_ITEM_DENY], m_wServerListLimits[SSI_ITEM_IGNORE]); + } + + disposeChain(&chain); + } +} + +DWORD CIcqProto::updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags) +{ + cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) { + debugLogA("Updating of group on server list failed (malloc error)"); + return 0; + } + ack->dwAction = SSA_GROUP_UPDATE; + ack->szGroupName = getServListGroupName(wGroupId); + ack->wGroupId = wGroupId; + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + return icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, ack->wGroupId, ack->szGroupName, groupData, groupSize, dwOperationFlags); +} + +void CIcqProto::handleServerCListAck(cookie_servlist_action* sc, WORD wError) +{ + switch (sc->dwAction) { + case SSA_VISIBILITY: + if (wError) + debugLogA("Server visibility update failed, error %d", wError); + break; + + case SSA_CONTACT_UPDATE: + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, wError ? PENDING_RESULT_FAILED : PENDING_RESULT_SUCCESS); + if (wError) { + debugLogA("Updating of server contact failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Updating of server contact failed.")); + } + break; + + case SSA_PRIVACY_ADD: + if (wError) { + debugLogA("Adding of privacy item to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of privacy item to server list failed.")); + } + break; + + case SSA_PRIVACY_REMOVE: + if (wError) { + debugLogA("Removing of privacy item from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of privacy item from server list failed.")); + } + FreeServerID(sc->wContactId, SSIT_ITEM); // release server id + break; + + case SSA_CONTACT_ADD: + if (wError) { + if (wError == 0xE) { // server refused to add contact w/o auth, add with + debugLogA("Contact could not be added without authorization, add with await auth flag."); + + setByte(sc->hContact, "Auth", 1); // we need auth + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); + icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wGroupId, sc->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL); + + sc = NULL; // we do not want it to be freed now + break; + } + FreeServerID(sc->wContactId, SSIT_ITEM); + + debugLogA("Adding of contact to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of contact to server list failed.")); + + servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + else { + void* groupData; + int groupSize; + + setWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wContactId); + setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wGroupId); + + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) { // the group is not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); + SAFE_FREE((void**)&groupData); + } + else { // this should never happen + debugLogA("Group update failed."); + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + } + break; + + case SSA_GROUP_ADD: + if (wError) { + FreeServerID(sc->wGroupId, SSIT_GROUP); + debugLogA("Adding of group to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of group to server list failed.")); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); + } + else { // group added, we need to update master group + void* groupData; + int groupSize; + + setServListGroupName(sc->wGroupId, sc->szGroupName); // add group to namelist + { // add group to known + char *szCListGroup = getServListGroupCListPath(sc->wGroupId); + + // create link to the original CList group + setServListGroupLinkID(sc->szGroup, sc->wGroupId); + + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); + SAFE_FREE((void**)&szCListGroup); + } + + groupData = collectGroups(&groupSize); + groupData = SAFE_REALLOC(groupData, groupSize + 2); + *(((WORD*)groupData) + (groupSize >> 1)) = sc->wGroupId; // add this new group id + groupSize += 2; + + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) { + ack->dwAction = SSA_GROUP_UPDATE; + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, SSOF_END_OPERATION); + } + else // end server modifications here + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); + + SAFE_FREE((void**)&groupData); + } + if (sc->szGroup != sc->szGroupName) + SAFE_FREE((void**)&sc->szGroup); + + SAFE_FREE((void**)&sc->szGroupName); + break; + + case SSA_CONTACT_REMOVE: + if (!wError) { + void* groupData; + int groupSize; + + setWord(sc->hContact, DBSETTING_SERVLIST_ID, 0); // clear the values + setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, 0); + + FreeServerID(sc->wContactId, SSIT_ITEM); + + servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) { // the group is still not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); + } + else // the group is empty, delete it + { + char *szGroup = getServListGroupCListPath(sc->wGroupId); + + servlistRemoveGroup(szGroup, sc->wGroupId); + SAFE_FREE((void**)&szGroup); + } + SAFE_FREE((void**)&groupData); // free the memory + } + else { + debugLogA("Removing of contact from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of contact from server list failed.")); + + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + break; + + case SSA_GROUP_UPDATE: + if (wError) { + debugLogA("Updating of group on server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Updating of group on server list failed.")); + } + SAFE_FREE((void**)&sc->szGroupName); + break; + + case SSA_GROUP_REMOVE: + SAFE_FREE((void**)&sc->szGroupName); + if (wError) { + debugLogA("Removing of group from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of group from server list failed.")); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + SAFE_FREE((void**)&sc->szGroup); + } + else { // group removed, we need to update master group + void* groupData; + int groupSize; + + setServListGroupName(sc->wGroupId, NULL); // clear group from namelist + FreeServerID(sc->wGroupId, SSIT_GROUP); + removeGroupPathLinks(sc->wGroupId); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_SUCCESS); + SAFE_FREE((void**)&sc->szGroup); + + groupData = collectGroups(&groupSize); + sc->wGroupId = 0; + sc->dwAction = SSA_GROUP_UPDATE; + sc->szGroupName = NULL; + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, sc); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, sc->szGroupName, groupData, groupSize, SSOF_END_OPERATION); + // end server modifications here + + sc = NULL; // we do not want to be freed here + + SAFE_FREE((void**)&groupData); + } + break; + + case SSA_CONTACT_SET_GROUP: + // we moved contact to another group + if (sc->lParam == -1) // the first was an error + break; + + if (wError) { + if (wError == 0x0E && sc->lParam == 1) { // second ack - adding failed with error 0x0E, try to add with AVAIT_AUTH flag + if (!getByte(sc->hContact, "Auth", 0)) { // we tried without AWAIT_AUTH, try again with it + debugLogA("Contact could not be added without authorization, add with await auth flag."); + setByte(sc->hContact, "Auth", 1); // we need auth + } + else { // we tried with AWAIT_AUTH, try again without + debugLogA("Contact count not be added awaiting authorization, try authorized."); + setByte(sc->hContact, "Auth", 0); + } + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); + icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wNewGroupId, sc->wNewContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL); + + sc->lParam = 2; // do not cycle + sc = NULL; // we do not want to be freed here + break; + } + FreeServerID(sc->wNewContactId, SSIT_ITEM); + debugLogA("Moving of user to another group on server list failed, error %d", wError); + icq_LogMessage(LOG_ERROR, LPGEN("Moving of user to another group on server list failed.")); + + servlistPendingRemoveContact(sc->hContact, 0, (WORD)(sc->lParam ? sc->wGroupId : sc->wNewGroupId), PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + + if (!sc->lParam) { // is this first ack ? + sc->lParam = -1; + sc = NULL; // this can't be freed here + } + break; + } + + if (sc->lParam) { // is this the second ack ? + void* groupData; + int groupSize; + int bEnd = 1; // shall we end the sever modifications + + setWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wNewContactId); + setWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wNewGroupId); + + servlistPendingRemoveContact(sc->hContact, sc->wNewContactId, sc->wNewGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) // update the group we moved from + { // the group is still not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, 0); + SAFE_FREE((void**)&groupData); // free the memory + } + else { // the group is empty, delete it + char* szGroup = getServListGroupCListPath(sc->wGroupId); + + servlistRemoveGroup(szGroup, sc->wGroupId); + SAFE_FREE((void**)&szGroup); + bEnd = 0; // here the modifications go on + } + + groupData = collectBuddyGroup(sc->wNewGroupId, &groupSize); // update the group we moved to + updateServerGroupData(sc->wNewGroupId, groupData, groupSize, bEnd ? SSOF_END_OPERATION : 0); + // end server modifications here + SAFE_FREE((void**)&groupData); + + } + else // contact was deleted from server-list + { + delSetting(sc->hContact, DBSETTING_SERVLIST_ID); + delSetting(sc->hContact, DBSETTING_SERVLIST_GROUP); + FreeServerID(sc->wContactId, SSIT_ITEM); // release old contact id + sc->lParam = 1; + sc = NULL; // wait for second ack + } + break; + + case SSA_CONTACT_FIX_AUTH: + if (wError) { // FIXME: something failed, we should handle it properly + } + break; + + case SSA_GROUP_RENAME: + if (wError) { + debugLogA("Renaming of server group failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Renaming of server group failed.")); + + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_FAILED); + } + else { + setServListGroupName(sc->wGroupId, sc->szGroupName); + removeGroupPathLinks(sc->wGroupId); + { // add group to known + char *szCListGroup = getServListGroupCListPath(sc->wGroupId); + + /// FIXME: need to create link to the new group name before unique item name correction as well + setServListGroupLinkID(szCListGroup, sc->wGroupId); + SAFE_FREE((void**)&szCListGroup); + } + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); + } + SAFE_FREE((void**)&sc->szGroupName); + SAFE_FREE((void**)&sc->szGroup); + break; + + case SSA_SETAVATAR: + if (wError) { + debugLogA("Uploading of avatar hash failed."); + if (sc->wGroupId) { // is avatar added or updated? + FreeServerID(sc->wContactId, SSIT_ITEM); + delSetting(DBSETTING_SERVLIST_AVATAR); // to fix old versions + } + } + else setWord(DBSETTING_SERVLIST_AVATAR, sc->wContactId); + break; + + case SSA_REMOVEAVATAR: + if (wError) + debugLogA("Removing of avatar hash failed."); + else { + FreeServerID(sc->wContactId, SSIT_ITEM); + delSetting(DBSETTING_SERVLIST_AVATAR); + } + break; + + case SSA_SERVLIST_ACK: + ProtoBroadcastAck(sc->hContact, ICQACKTYPE_SERVERCLIST, wError ? ACKRESULT_FAILED : ACKRESULT_SUCCESS, (HANDLE)sc->lParam, wError); + break; + + case SSA_IMPORT: + if (wError) + debugLogA("Re-starting import sequence failed, error %d", wError); + else { + setWord("SrvImportID", 0); + delSetting("ImportTS"); + } + break; + + default: + debugLogA("Server ack cookie type (%d) not recognized.", sc->dwAction); + } + + SAFE_FREE((void**)&sc); // free the memory + return; +} + +MCONTACT CIcqProto::HContactFromRecordName(const char* szRecordName, int *bAdded) +{ + MCONTACT hContact = INVALID_CONTACT_ID; + + if (!IsStringUIN(szRecordName)) // probably AIM contact + hContact = HContactFromUID(0, szRecordName, bAdded); + else { // this should be ICQ number + DWORD dwUin = atoi(szRecordName); + hContact = HContactFromUIN(dwUin, bAdded); + } + return hContact; +} + +int CIcqProto::getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf) /// FIXME: need to keep original order +{ + // get server-list item's TLV data + oscar_tlv_chain* list = pChain; + int datalen = 0; + icq_packet pBuf; + + // Initialize our handy data buffer + pBuf.wPlace = 0; + pBuf.pData = buf; + + while (list) { // collect non-standard TLVs and save them to DB + if (list->tlv.wType != SSI_TLV_AWAITING_AUTH && + list->tlv.wType != SSI_TLV_NAME && + list->tlv.wType != SSI_TLV_COMMENT && + list->tlv.wType != SSI_TLV_METAINFO_TOKEN && + list->tlv.wType != SSI_TLV_METAINFO_TIME) { // only TLVs which we do not handle on our own + packTLV(&pBuf, list->tlv.wType, list->tlv.wLen, list->tlv.pData); + + datalen += list->tlv.wLen + 4; + } + list = list->next; + } + return datalen; +} + +void CIcqProto::handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info) +{ + BYTE bySSIVersion; + WORD wRecordCount; + WORD wRecord; + WORD wGroupId; + WORD wItemId; + WORD wTlvType; + WORD wTlvLength; + BOOL bIsLastPacket; + uid_str szRecordName; + oscar_tlv_chain* pChain = NULL; + oscar_tlv* pTLV = NULL; + char *szActiveSrvGroup = NULL; + WORD wActiveSrvGroupId = -1; + + + // If flag bit 1 is set, this is not the last + // packet. If it is 0, this is the last packet + // and there will be a timestamp at the end. + if (wFlags & 0x0001) + bIsLastPacket = FALSE; + else + bIsLastPacket = TRUE; + + if (wLen < 3) + return; + + // Version number of SSI protocol? + unpackByte(&buf, &bySSIVersion); + wLen -= 1; + + // Total count of following entries. This is the size of the server + // side contact list and should be saved and sent with CLI_CHECKROSTER. + // NOTE: When the entries are split up in several packets, each packet + // has it's own count and they must be added to get the total size of + // server list. + unpackWord(&buf, &wRecordCount); + wLen -= 2; + debugLogA("SSI: number of entries is %u, version is %u", wRecordCount, bySSIVersion); + + // Loop over all items in the packet + for (wRecord = 0; wRecord < wRecordCount; wRecord++) { + debugLogA("SSI: parsing record %u", wRecord + 1); + + if (wLen < 10) { // minimum: name length (zero), group ID, item ID, empty TLV + debugLogA("Warning: SSI parsing error (%d)", 0); + break; + } + + if (!unpackServerListItem(&buf, &wLen, szRecordName, &wGroupId, &wItemId, &wTlvType, &wTlvLength)) { // unpack basic structure + debugLogA("Warning: SSI parsing error (%d)", 1); + break; + } + + debugLogA("Name: '%s', GroupID: %u, EntryID: %u, EntryType: %u, TLVlength: %u", + szRecordName, wGroupId, wItemId, wTlvType, wTlvLength); + + if (wLen < wTlvLength) { + debugLogA("Warning: SSI parsing error (%d)", 2); + break; + } + + // Initialize the tlv chain + if (wTlvLength > 0) { + pChain = readIntoTLVChain(&buf, wTlvLength, 0); + wLen -= wTlvLength; + } + else pChain = NULL; + + switch (wTlvType) { + case SSI_ITEM_BUDDY: + { + /* this is a contact */ + int bAdded; + MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_CONTACT_ID) { + int bRegroup = 0; + int bNicked = 0; + + if (bAdded) { // Not already on list: added + debugLogA("SSI added new %s contact '%s'", "ICQ", szRecordName); + + AddJustAddedContact(hContact); + } + else { // we should add new contacts and this contact was just added, show it + if (IsContactJustAdded(hContact)) { + setContactHidden(hContact, 0); + bAdded = 1; // we want details for new contacts + } + else debugLogA("SSI ignoring existing contact '%s'", szRecordName); + + // Contact on server is always on list + db_set_b(hContact, "CList", "NotOnList", 0); + } + + // Save group and item ID + setWord(hContact, DBSETTING_SERVLIST_ID, wItemId); + setWord(hContact, DBSETTING_SERVLIST_GROUP, wGroupId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + if (!bAdded && getByte("LoadServerDetails", DEFAULT_SS_LOAD)) { // check if the contact has been moved on the server + if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) { + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + } + char *szLocalGroup = getContactCListGroup(hContact); + + if (!strlennull(szLocalGroup)) { // no CListGroup + SAFE_FREE(&szLocalGroup); + + szLocalGroup = null_strdup(DEFAULT_SS_GROUP); + } + + if (strcmpnull(szActiveSrvGroup, szLocalGroup) && + (strlennull(szActiveSrvGroup) >= strlennull(szLocalGroup) || (szActiveSrvGroup && _strnicmp(szActiveSrvGroup, szLocalGroup, strlennull(szLocalGroup))))) { // contact moved to new group or sub-group or not to master group + bRegroup = 1; + } + if (bRegroup && !stricmpnull(DEFAULT_SS_GROUP, szActiveSrvGroup)) /// TODO: invent something more clever for "root" group + { // is it the default "General" group ? + bRegroup = 0; // if yes, do not move to it - cause it would hide the contact + } + SAFE_FREE(&szLocalGroup); + } + + if (bRegroup || bAdded) { // if we should load server details or contact was just added, update its group + if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) { + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + } + + if (szActiveSrvGroup) { // try to get Miranda Group path from groupid, if succeeded save to db + moveContactToCListGroup(hContact, szActiveSrvGroup); + } + } + + if (pChain) { // Look for nickname TLV and copy it to the db if necessary + if (pTLV = pChain->getTLV(SSI_TLV_NAME, 1)) { + if (pTLV->pData && (pTLV->wLen > 0)) { + char *pszNick; + WORD wNickLength; + + wNickLength = pTLV->wLen; + + pszNick = (char*)SAFE_MALLOC(wNickLength + 1); + // Copy buffer to utf-8 buffer + memcpy(pszNick, pTLV->pData, wNickLength); + pszNick[wNickLength] = 0; // Terminate string + + debugLogA("Nickname is '%s'", pszNick); + + bNicked = 1; + + // Write nickname to database + if (getByte("LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) { // if just added contact, save details always - does no harm + char *szOldNick; + + if (szOldNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL)) { + if ((strcmpnull(szOldNick, pszNick)) && (strlennull(pszNick) > 0)) { // check if the truncated nick changed, i.e. do not overwrite locally stored longer nick + if (strlennull(szOldNick) <= strlennull(pszNick) || strncmp(szOldNick, pszNick, null_strcut(szOldNick, MAX_SSI_TLV_NAME_SIZE))) { + // Yes, we really do need to delete it first. Otherwise the CLUI nick + // cache isn't updated (I'll look into it) + db_unset(hContact, "CList", "MyHandle"); + db_set_utf(hContact, "CList", "MyHandle", pszNick); + } + } + SAFE_FREE(&szOldNick); + } + else if (strlennull(pszNick) > 0) { + db_unset(hContact, "CList", "MyHandle"); + db_set_utf(hContact, "CList", "MyHandle", pszNick); + } + } + SAFE_FREE(&pszNick); + } + else debugLogA("Invalid nickname"); + } + if (bAdded && !bNicked) + icq_QueueUser(hContact); // queue user without nick for fast auto info update + + // Look for comment TLV and copy it to the db if necessary + if (pTLV = pChain->getTLV(SSI_TLV_COMMENT, 1)) { + if (pTLV->pData && (pTLV->wLen > 0)) { + char *pszComment; + WORD wCommentLength; + + + wCommentLength = pTLV->wLen; + + pszComment = (char*)SAFE_MALLOC(wCommentLength + 1); + // Copy buffer to utf-8 buffer + memcpy(pszComment, pTLV->pData, wCommentLength); + pszComment[wCommentLength] = 0; // Terminate string + + debugLogA("Comment is '%s'", pszComment); + + // Write comment to database + if (getByte("LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) { // if just added contact, save details always - does no harm + char *szOldComment; + + if (szOldComment = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL)) { + if ((strcmpnull(szOldComment, pszComment)) && (strlennull(pszComment) > 0)) // check if the truncated comment changed, i.e. do not overwrite locally stored longer comment + if (strlennull(szOldComment) <= strlennull(pszComment) || strncmp((char*)szOldComment, (char*)pszComment, null_strcut(szOldComment, MAX_SSI_TLV_COMMENT_SIZE))) + db_set_utf(hContact, "UserInfo", "MyNotes", pszComment); + + SAFE_FREE((void**)&szOldComment); + } + else if (strlennull(pszComment) > 0) + db_set_utf(hContact, "UserInfo", "MyNotes", pszComment); + } + SAFE_FREE((void**)&pszComment); + } + else debugLogA("Invalid comment"); + } + + // Look for need-authorization TLV + if (pChain->getTLV(SSI_TLV_AWAITING_AUTH, 1)) { + setByte(hContact, "Auth", 1); + debugLogA("SSI contact need authorization"); + } + else setByte(hContact, "Auth", 0); + + if (pTLV = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1)) { + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pTLV->pData, pTLV->wLen); + if (pChain->getTLV(SSI_TLV_METAINFO_TIME, 1)) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); + debugLogA("SSI contact has meta info token"); + } + else { + delSetting(hContact, DBSETTING_METAINFO_TOKEN); + delSetting(hContact, DBSETTING_METAINFO_TIME); + } + + { // store server-list item's TLV data + BYTE* data = (BYTE*)SAFE_MALLOC(wTlvLength); + int datalen = getServerDataFromItemTLV(pChain, data); + + if (datalen > 0) + setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); + else + delSetting(hContact, DBSETTING_SERVLIST_DATA); + + SAFE_FREE((void**)&data); + } + } + } + else // failed to add or other error + debugLogA("SSI failed to handle %s Item '%s'", "Buddy", szRecordName); + } + break; + + case SSI_ITEM_GROUP: + if ((wGroupId == 0) && (wItemId == 0)) { + /* list of groups. wTlvType=1, data is TLV(C8) containing list of WORDs which */ + /* is the group ids + /* we don't need to use this. Our processing is on-the-fly */ + /* this record is always sent first in the first packet only, */ + } + else if (wGroupId != 0) { + /* wGroupId != 0: a group record */ + if (wItemId == 0) { /* no item ID: this is a group */ + /* pszRecordName is the name of the group */ + ReserveServerID(wGroupId, SSIT_GROUP, 0); + + setServListGroupName(wGroupId, szRecordName); + + debugLogA("Group %s added to known groups.", szRecordName); + + /* demangle full grouppath, set it to known */ + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + + /* TLV contains a TLV(C8) with a list of WORDs of contained contact IDs */ + /* our processing is good enough that we don't need this duplication */ + } + else debugLogA("Unhandled type 0x01, wItemID != 0"); + } + else debugLogA("Unhandled type 0x01"); + break; + + case SSI_ITEM_PERMIT: + { + /* item on visible list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + int bAdded; + MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_CONTACT_ID) { + if (bAdded) { + debugLogA("SSI added new %s contact '%s'", "Permit", szRecordName); + // It wasn't previously in the list, we hide it so it only appears in the visible list + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else debugLogA("SSI %s contact already exists '%s'", "Permit", szRecordName); + + // Save permit ID + setWord(hContact, DBSETTING_SERVLIST_PERMIT, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + // Set apparent mode + setWord(hContact, "ApparentMode", ID_STATUS_ONLINE); + debugLogA("Visible-contact (%s)", szRecordName); + } + else { // failed to add or other error + debugLogA("SSI failed to handle %s Item '%s'", "Permit", szRecordName); + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_DENY: + { + /* Item on invisible list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + int bAdded; + MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_CONTACT_ID) { + if (bAdded) { + /* not already on list: added */ + debugLogA("SSI added new %s contact '%s'", "Deny", szRecordName); + // It wasn't previously in the list, we hide it so it only appears in the visible list + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else debugLogA("SSI %s contact already exists '%s'", "Deny", szRecordName); + + // Save Deny ID + setWord(hContact, DBSETTING_SERVLIST_DENY, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + // Set apparent mode + setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); + debugLogA("Invisible-contact (%s)", szRecordName); + } + else { // failed to add or other error + debugLogA("SSI failed to handle %s Item '%s'", "Deny", szRecordName); + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_VISIBILITY: /* My visibility settings */ + // Look for visibility TLV + if (BYTE bVisibility = pChain->getByte(SSI_TLV_VISIBILITY, 1)) { // found it, store the id, we do not need current visibility - we do not rely on it + setWord(DBSETTING_SERVLIST_PRIVACY, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + debugLogA("Visibility is %u", bVisibility); + } + else ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + break; + + case SSI_ITEM_IGNORE: + { + /* item on ignore list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + int bAdded; + MCONTACT hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_CONTACT_ID) { + if (bAdded) { + /* not already on list: add */ + debugLogA("SSI added new %s contact '%s'", "Ignore", szRecordName); + // It wasn't previously in the list, we hide it + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else debugLogA("SSI %s contact already exists '%s'", "Ignore", szRecordName); + + // Save Ignore ID + setWord(hContact, DBSETTING_SERVLIST_IGNORE, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + // Set apparent mode & ignore + setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); + // set ignore all events + CallService(MS_IGNORE_IGNORE, hContact, IGNOREEVENT_ALL); + debugLogA("Ignore-contact (%s)", szRecordName); + } + else { // failed to add or other error + debugLogA("SSI failed to handle %s Item '%s'", "Ignore", szRecordName); + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_UNKNOWN2: + debugLogA("SSI unknown type 0x11"); + + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + break; + + case SSI_ITEM_IMPORTTIME: + if (wGroupId == 0) { + /* time our list was first imported */ + /* pszRecordName is "Import Time" */ + /* data is TLV(13) {TLV(D4) {time_t importTime}} */ + setDword("ImportTS", pChain->getDWord(SSI_TLV_TIMESTAMP, 1)); + setWord("SrvImportID", wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + debugLogA("SSI %s item recognized", "first import"); + } + break; + + case SSI_ITEM_BUDDYICON: + if (wGroupId == 0) { + /* our avatar MD5-hash */ + /* pszRecordName is "1" */ + /* data is TLV(D5) hash */ + /* we ignore this, just save the id */ + /* cause we get the hash again after login */ + if (!strcmpnull(szRecordName, "12")) { // need to handle Photo Item separately + setWord(DBSETTING_SERVLIST_PHOTO, wItemId); + debugLogA("SSI %s item recognized", "Photo"); + } + else { + setWord(DBSETTING_SERVLIST_AVATAR, wItemId); + debugLogA("SSI %s item recognized", "Avatar"); + } + ReserveServerID(wItemId, SSIT_ITEM, 0); + } + break; + + case SSI_ITEM_METAINFO: + if (wGroupId == 0) { + /* our meta info token & last update time */ + /* pszRecordName is "ICQ-MDIR" */ + /* data is TLV(15C) and TLV(15D) */ + oscar_tlv* pToken = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv* pTime = pChain->getTLV(SSI_TLV_METAINFO_TIME, 1); + if (pToken) + setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(NULL, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); + + setWord(DBSETTING_SERVLIST_METAINFO, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + debugLogA("SSI %s item recognized", "Meta info"); + } + break; + + case SSI_ITEM_CLIENTDATA: + if (wGroupId == 0) { + /* ICQ2k ShortcutBar Items */ + /* data is TLV(CD) text */ + if (wItemId) + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + + case SSI_ITEM_SAVED: + case SSI_ITEM_PREAUTH: + break; + + default: + debugLogA("SSI unhandled item %2x", wTlvType); + + if (wItemId) + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + break; + } + + disposeChain(&pChain); + } // end for + + // Release Memory + SAFE_FREE(&szActiveSrvGroup); + + debugLogA("Bytes left: %u", wLen); + + setWord("SrvRecordCount", (WORD)(wRecord + getWord("SrvRecordCount", 0))); + + if (bIsLastPacket) { + // No contacts left to sync + bIsSyncingCL = FALSE; + + StoreServerIDs(); + + icq_RescanInfoUpdate(); + + if (wLen >= 4) { + DWORD dwLastUpdateTime; + + /* finally we get a time_t of the last update time */ + unpackDWord(&buf, &dwLastUpdateTime); + setDword("SrvLastUpdate", dwLastUpdateTime); + debugLogA("Last update of server list was (%u) %s", dwLastUpdateTime, time2text(dwLastUpdateTime)); + + sendRosterAck(); + handleServUINSettings(wListenPort, info); + + servlistProcessLogin(); + } + else debugLogA("Last packet missed update time..."); + + if (getWord("SrvRecordCount", 0) == 0) { // we got empty serv-list, create master group + cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) { + DWORD dwCookie; + + ack->dwAction = SSA_GROUP_UPDATE; + ack->szGroupName = null_strdup(""); + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, 0, ack->szGroupName, NULL, 0, 0); + } + } + // serv-list sync finished, clear just added contacts + FlushJustAddedContacts(); + } + else debugLogA("Waiting for more packets"); +} + +void CIcqProto::handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + if (wItemType == SSI_ITEM_IMPORTTIME) { + if (pItemData) { + setDword("ImportTS", pItemData->getDWord(SSI_TLV_TIMESTAMP, 1)); + setWord("SrvImportID", wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + debugLogA("Server added Import timestamp to list"); + return; + } + } + // Reserve server-list ID + ReserveServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM, SSIF_UNHANDLED); +} + +void CIcqProto::handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + MCONTACT hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; + + if (hContact != INVALID_CONTACT_ID && wItemType == SSI_ITEM_BUDDY) { // a contact was updated on server + if (pItemData) { + oscar_tlv *pAuth = pItemData->getTLV(SSI_TLV_AWAITING_AUTH, 1); + BYTE bAuth = getByte(hContact, "Auth", 0); + + if (bAuth && !pAuth) { // server authorized our contact + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + setByte(hContact, "Auth", 0); + mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" was authorized in the server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + else if (!bAuth && pAuth) { // server took away authorization of our contact + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + setByte(hContact, "Auth", 1); + mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" lost its authorization in the server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + { + // update metainfo data + DBVARIANT dbv = { 0 }; + oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) { + if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) { + if (!pToken) + debugLogA("Contact %s, meta info token removed", szRecordName); + else + debugLogA("Contact %s, meta info token changed", szRecordName); + + // user info was changed, refresh + if (IsMetaInfoChanged(hContact)) + icq_QueueUser(hContact); + } + + db_free(&dbv); + } + else if (pToken) { + debugLogA("Contact %s, meta info token added", szRecordName); + + // user info was changed, refresh + if (IsMetaInfoChanged(hContact)) + icq_QueueUser(hContact); + } + + if (pToken) + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); + } + { + // update server's data - otherwise consequent operations can fail with 0x0E + BYTE *data = (BYTE*)_alloca(pItemData->getChainLength()); + int datalen = getServerDataFromItemTLV(pItemData, data); + + if (datalen > 0) + setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); + else + delSetting(hContact, DBSETTING_SERVLIST_DATA); + } + } + } + else if (wItemType == SSI_ITEM_METAINFO) { // owner MetaInfo data updated + if (pItemData) { + DBVARIANT dbv = { 0 }; + oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) { + if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) { + if (!pToken) + debugLogA("Owner meta info token removed"); + else + debugLogA("Owner meta info token changed"); + } + + db_free(&dbv); + } + + if (pToken) + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); + } + } + else if (wItemType == SSI_ITEM_GROUP) // group updated + debugLogA("Server updated our group \"%s\" on list", szRecordName); +} + +void CIcqProto::handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + MCONTACT hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; + + if (hContact != INVALID_CONTACT_ID && wItemType == SSI_ITEM_BUDDY) { // a contact was removed from our list + if (getWord(hContact, DBSETTING_SERVLIST_ID, 0) == wItemId) { + delSetting(hContact, DBSETTING_SERVLIST_ID); + delSetting(hContact, DBSETTING_SERVLIST_GROUP); + delSetting(hContact, "Auth"); + + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + mir_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("User \"%s\" was removed from server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + } + // Release server-list ID + FreeServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM); +} + +void CIcqProto::handleRecvAuthRequest(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) + return; + + if (dwUin && IsOnSpammerList(dwUin)) { + debugLogA("Ignored Message from known Spammer"); + return; + } + + WORD wReasonLen; + unpackWord(&buf, &wReasonLen); + wLen -= 2; + if (wReasonLen > wLen) + return; + + int bAdded; + MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); + + PROTORECVEVENT pre = { 0 }; + pre.timestamp = time(NULL); + pre.lParam = sizeof(DWORD) * 2 + 5; + // Prepare reason + char *szReason = (char*)SAFE_MALLOC(wReasonLen + 1); + int nReasonLen = 0; + if (szReason) { + memcpy(szReason, buf, wReasonLen); + szReason[wReasonLen] = '\0'; + nReasonLen = strlennull(szReason); + + char *temp = (char*)_alloca(nReasonLen + 2); + if (!IsUSASCII(szReason, nReasonLen) && UTF8_IsValid(szReason) && utf8_decode_static(szReason, temp, nReasonLen + 1)) + pre.flags |= PREF_UTF; + } + + // Read nick name from DB + char *szNick = NULL; + if (dwUin) { + DBVARIANT dbv = { 0 }; + if (pre.flags & PREF_UTF) + szNick = getSettingStringUtf(hContact, "Nick", NULL); + else if (!getString(hContact, "Nick", &dbv)) { + szNick = null_strdup(dbv.pszVal); + db_free(&dbv); + } + } + else szNick = null_strdup(szUid); + + int nNickLen = strlennull(szNick); + + pre.lParam += nNickLen + nReasonLen; + + setByte(hContact, "Grant", 1); + + /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/ + char *szBlob = (char *)_alloca(pre.lParam); + char *pCurBlob = szBlob; + *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); + *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); + + if (nNickLen) { // if we have nick we add it, otherwise keep trailing zero + memcpy(pCurBlob, szNick, nNickLen); + pCurBlob += nNickLen; + } + *pCurBlob = 0; pCurBlob++; // Nick + *pCurBlob = 0; pCurBlob++; // FirstName + *pCurBlob = 0; pCurBlob++; // LastName + *pCurBlob = 0; pCurBlob++; // email + if (nReasonLen) { + memcpy(pCurBlob, szReason, nReasonLen); + pCurBlob += nReasonLen; + } + *pCurBlob = 0; // Reason + pre.szMessage = szBlob; + + // TODO: Change for new auth system, include all known informations + ProtoChainRecv(hContact, PSR_AUTH, 0, (LPARAM)&pre); + + SAFE_FREE(&szNick); + SAFE_FREE(&szReason); +} + +void CIcqProto::handleRecvAdded(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + DWORD cbBlob; + PBYTE pBlob, pCurBlob; + int bAdded; + char* szNick; + int nNickLen; + DBVARIANT dbv = { 0 }; + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (dwUin && IsOnSpammerList(dwUin)) { + debugLogA("Ignored Message from known Spammer"); + return; + } + + MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); + + cbBlob = sizeof(DWORD) * 2 + 4; + + if (dwUin) { + if (getString(hContact, "Nick", &dbv)) + nNickLen = 0; + else { + szNick = dbv.pszVal; + nNickLen = strlennull(szNick); + } + } + else nNickLen = strlennull(szUid); + + cbBlob += nNickLen; + + pCurBlob = pBlob = (PBYTE)_alloca(cbBlob); + /*blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */ + *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); + *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); + if (nNickLen && dwUin) { // if we have nick we add it, otherwise keep trailing zero + memcpy(pCurBlob, szNick, nNickLen); + pCurBlob += nNickLen; + } + else { + memcpy(pCurBlob, szUid, nNickLen); + pCurBlob += nNickLen; + } + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; + // TODO: Change for new auth system + + AddEvent(NULL, EVENTTYPE_ADDED, time(NULL), 0, cbBlob, pBlob); +} + +void CIcqProto::handleRecvAuthResponse(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + char* szNick = NULL; + WORD nReasonLen; + char* szReason; + int bAdded; + + BYTE bResponse = 0xFF; + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (dwUin && IsOnSpammerList(dwUin)) { + debugLogA("Ignored Message from known Spammer"); + return; + } + + MCONTACT hContact = HContactFromUID(dwUin, szUid, &bAdded); + if (hContact != INVALID_CONTACT_ID) + szNick = NickFromHandle(hContact); + + if (wLen > 0) { + unpackByte(&buf, &bResponse); + wLen -= 1; + } + if (wLen >= 2) { + unpackWord(&buf, &nReasonLen); + wLen -= 2; + if (wLen >= nReasonLen) { + szReason = (char*)_alloca(nReasonLen + 1); + unpackString(&buf, szReason, nReasonLen); + szReason[nReasonLen] = '\0'; + } + } + + switch (bResponse) { + case 0: + debugLogA("Authorization request %s by %s", "denied", strUID(dwUin, szUid)); + // TODO: Add to system history as soon as new auth system is ready + break; + + case 1: + setByte(hContact, "Auth", 0); + debugLogA("Authorization request %s by %s", "granted", strUID(dwUin, szUid)); + // TODO: Add to system history as soon as new auth system is ready + break; + + default: + debugLogA("Unknown Authorization request response (%u) from %s", bResponse, strUID(dwUin, szUid)); + break; + + } + SAFE_FREE(&szNick); +} + +// Updates the visibility code used while in SSI mode. If a server ID is +// not stored in the local DB, a new ID will be added to the server list. +// +// Possible values are: +// 01 - Allow all users to see you +// 02 - Block all users from seeing you +// 03 - Allow only users in the permit list to see you +// 04 - Block only users in the invisible list from seeing you +// 05 - Allow only users in the buddy list to see you +// +void CIcqProto::updateServVisibilityCode(BYTE bCode) +{ + icq_packet packet; + WORD wVisibilityID; + WORD wCommand; + + if ((bCode > 0) && (bCode < 6)) { + cookie_servlist_action* ack; + DWORD dwCookie; + BYTE bVisibility = getByte("SrvVisibility", 0); + + if (bVisibility == bCode) // if no change was made, not necescary to update that + return; + setByte("SrvVisibility", bCode); + + // Do we have a known server visibility ID? We should, unless we just subscribed to the serv-list for the first time + if ((wVisibilityID = getWord(DBSETTING_SERVLIST_PRIVACY, 0)) == 0) { + // No, create a new random ID + wVisibilityID = GenerateServerID(SSIT_ITEM, 0); + setWord(DBSETTING_SERVLIST_PRIVACY, wVisibilityID); + wCommand = ICQ_LISTS_ADDTOLIST; + + debugLogA("Made new srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); + } + else { + debugLogA("Reused srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); + wCommand = ICQ_LISTS_UPDATEGROUP; + } + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) { + debugLogA("Cookie alloc failure."); + return; // out of memory, go away + } + + ack->dwAction = SSA_VISIBILITY; // update visibility + dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie + + // Build and send packet + serverPacketInit(&packet, 25); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, wCommand, 0, dwCookie); + packWord(&packet, 0); // Name (null) + packWord(&packet, 0); // GroupID (0 if not relevant) + packWord(&packet, wVisibilityID); // EntryID + packWord(&packet, SSI_ITEM_VISIBILITY); // EntryType + packWord(&packet, 5); // Length in bytes of following TLV + packTLV(&packet, SSI_TLV_VISIBILITY, 1, &bCode); // TLV (Visibility) + sendServPacket(&packet); + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when modifying the visibility code + } +} + +// Updates the avatar hash used while in SSI mode. If a server ID is +// not stored in the local DB, a new ID will be added to the server list. +void CIcqProto::updateServAvatarHash(BYTE *pHash, int size) +{ + void** pDoubleObject = NULL; + void* doubleObject = NULL; + DWORD dwOperationFlags = 0; + WORD wAvatarID; + WORD wCommand; + char szItemName[2] = { 0, 0 }; + int bResetHash = 0; + DBVARIANT dbvHash; + + if (!pHash) + return; + + if (!getSetting(NULL, "AvatarHash", &dbvHash)) { + szItemName[0] = 0x30 + dbvHash.pbVal[1]; + + if (memcmp(pHash, dbvHash.pbVal, 2) != 0) // add code to remove old hash from server + bResetHash = 1; + + db_free(&dbvHash); + } + + if (bResetHash) { // start update session + // pair the packets (need to be send in the correct order + dwOperationFlags |= SSOF_BEGIN_OPERATION | SSOF_END_OPERATION; + pDoubleObject = &doubleObject; + } + + if (bResetHash) { + // Do we have a known server avatar ID? + if (wAvatarID = getWord(DBSETTING_SERVLIST_AVATAR, 0)) { + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) { + debugLogA("Cookie alloc failure."); + return; // out of memory, go away + } + ack->dwAction = SSA_REMOVEAVATAR; // update avatar hash + ack->wContactId = wAvatarID; + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); // take cookie + icq_sendServerItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, wAvatarID, szItemName, NULL, 0, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); + } + } + + WORD hashsize = size - 2; + + // Do we have a known server avatar ID? We should, unless we just subscribed to the serv-list for the first time + if (bResetHash || (wAvatarID = getWord(DBSETTING_SERVLIST_AVATAR, 0)) == 0) { + // No, create a new random ID + wAvatarID = GenerateServerID(SSIT_ITEM, 0); + wCommand = ICQ_LISTS_ADDTOLIST; + debugLogA("Made new srvAvatarID, id is %u", wAvatarID); + } + else { + debugLogA("Reused srvAvatarID, id is %u", wAvatarID); + wCommand = ICQ_LISTS_UPDATEGROUP; + } + + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) { + debugLogA("Cookie alloc failure."); + return; // out of memory, go away + } + ack->dwAction = SSA_SETAVATAR; // update avatar hash + ack->wContactId = wAvatarID; + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie + + szItemName[0] = 0x30 + pHash[1]; + + // Build the packet + WORD wTLVlen = 8 + hashsize; + + // Initialize our handy data buffer + icq_packet pBuffer; + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + packTLV(&pBuffer, SSI_TLV_NAME, 0, NULL); // TLV (Name) + packTLV(&pBuffer, SSI_TLV_AVATARHASH, hashsize, pHash + 2); // TLV (Hash) + + icq_sendServerItem(dwCookie, wCommand, 0, wAvatarID, szItemName, pBuffer.pData, wTLVlen, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when modifying the avatar hash +} + +// Should be called before the server list is modified. When all +// modifications are done, call icq_sendServerEndOperation(). +// Called automatically thru server-list update board! +void CIcqProto::icq_sendServerBeginOperation(int bImport) +{ + WORD wImportID = getWord("SrvImportID", 0); + + if (bImport && wImportID) { // we should be importing, check if already have import item + if (getDword("ImportTS", 0) + 604800 < getDword("LogonTS", 0)) { // is the timestamp week older, clear it and begin new import + DWORD dwCookie; + cookie_servlist_action* ack; + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) { // we have cookie good, go on + ack->dwAction = SSA_IMPORT; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); + + icq_sendSimpleItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, "ImportTime", 0, wImportID, SSI_ITEM_IMPORTTIME, SSOP_ITEM_ACTION | SSOF_SEND_DIRECTLY, 100); + } + } + } + + icq_packet packet; + serverPacketInit(&packet, (WORD)(bImport ? 14 : 10)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYSTART); + if (bImport) + packDWord(&packet, 1 << 0x10); + sendServPacket(&packet); +} + +// Should be called after the server list has been modified to inform +// the server that we are done. +// Called automatically thru server-list update board! +void CIcqProto::icq_sendServerEndOperation() +{ + icq_packet packet; + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYEND); + sendServPacket(&packet); +} + +// Sent when the last roster packet has been received +void CIcqProto::sendRosterAck(void) +{ + icq_packet packet; + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GOTLIST); + sendServPacket(&packet); + + debugLogA("Sent SNAC(x13,x07) - CLI_ROSTERACK"); +} diff --git a/protocols/IcqOscarJ/src/icq_avatar.cpp b/protocols/IcqOscarJ/src/icq_avatar.cpp index 0787c06764..6a9f733296 100644 --- a/protocols/IcqOscarJ/src/icq_avatar.cpp +++ b/protocols/IcqOscarJ/src/icq_avatar.cpp @@ -283,7 +283,7 @@ void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BY else if (memcmp(hash, pData + 4, 0x10)) { // we have different avatar, sync that if (m_bSsiEnabled && getByte("ForceOurAvatar", 1)) { // we want our avatar, update hash DWORD dwPaFormat = ::ProtoGetAvatarFileFormat(file); - BYTE *pHash = (BYTE*)_alloca(0x14); + BYTE pHash[0x14]; debugLogA("Our avatar is different, setting our new hash."); @@ -291,7 +291,7 @@ void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BY pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; pHash[2] = 1; // state of the hash pHash[3] = 0x10; // len of the hash - memcpy(pHash + 4, hash, 0x10); + memcpy((pHash + 4), hash, 0x10); updateServAvatarHash(pHash, 0x14); } else { // get avatar from server @@ -346,7 +346,7 @@ void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BY SAFE_FREE((void**)&hash); } else { - BYTE *pHash = (BYTE*)_alloca(0x14); + BYTE pHash[0x14]; debugLogA("Our file is different, set our new hash."); @@ -354,7 +354,7 @@ void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BY pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; pHash[2] = 1; // state of the hash pHash[3] = 0x10; // len of the hash - memcpy(pHash + 4, hash, 0x10); + memcpy((pHash + 4), hash, 0x10); updateServAvatarHash(pHash, 0x14); SAFE_FREE((void**)&hash); diff --git a/protocols/IcqOscarJ/src/icqosc_svcs.cpp b/protocols/IcqOscarJ/src/icqosc_svcs.cpp index 18accdf412..52ff1339da 100644 --- a/protocols/IcqOscarJ/src/icqosc_svcs.cpp +++ b/protocols/IcqOscarJ/src/icqosc_svcs.cpp @@ -509,13 +509,13 @@ INT_PTR CIcqProto::SetMyAvatar(WPARAM wParam, LPARAM lParam) BYTE *hash = calcMD5HashOfFile(tszMyFile); if (hash) { - BYTE *ihash = (BYTE*)_alloca(0x14); + BYTE ihash[0x14]; // upload hash to server ihash[0] = 0; //unknown ihash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; //hash type ihash[2] = 1; //hash status ihash[3] = 0x10; //hash len - memcpy(ihash + 4, hash, 0x10); + memcpy((ihash + 4), hash, 0x10); updateServAvatarHash(ihash, 0x14); if (setSettingBlob(NULL, "AvatarHash", ihash, 0x14)) diff --git a/protocols/JabberG/src/jabber_search.cpp b/protocols/JabberG/src/jabber_search.cpp index 78e2feaabb..b63f8e4672 100644 --- a/protocols/JabberG/src/jabber_search.cpp +++ b/protocols/JabberG/src/jabber_search.cpp @@ -1,763 +1,769 @@ -/* - -Jabber Protocol Plugin for Miranda NG - -Copyright (c) 2002-04 Santithorn Bunchua -Copyright (c) 2005-12 George Hazan -Copyright (c) 2007 Artem Shpynov -Copyright (c) 2012-14 Miranda NG project - -Module implements a search according to XEP-0055: Jabber Search -http://www.xmpp.org/extensions/xep-0055.html - -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 -#include "jabber_iq.h" -#include "jabber_caps.h" - -/////////////////////////////////////////////////////////////////////////////// -// Subclassing of IDC_FRAME to implement more user-friendly fields scrolling -// -static int JabberSearchFrameProc(HWND hwnd, int msg, WPARAM wParam, LPARAM lParam) -{ - if (msg == WM_COMMAND && lParam != 0) { - HWND hwndDlg=GetParent(hwnd); - JabberSearchData * dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); - if (dat && lParam) { - int pos=dat->curPos; - RECT MineRect; - RECT FrameRect; - GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME),&FrameRect); - GetWindowRect((HWND)lParam, &MineRect); - if (MineRect.top-10 < FrameRect.top) { - pos=dat->curPos+(MineRect.top-14-FrameRect.top); - if (pos<0) pos=0; - } - else if (MineRect.bottom > FrameRect.bottom) { - pos=dat->curPos+(MineRect.bottom-FrameRect.bottom); - if (dat->frameHeight+pos>dat->CurrentHeight) - pos=dat->CurrentHeight-dat->frameHeight; - } - if (pos != dat->curPos) { - ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, NULL, &(dat->frameRect)); - SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); - RECT Invalid=dat->frameRect; - if (dat->curPos - pos >0) - Invalid.bottom=Invalid.top+(dat->curPos - pos); - else - Invalid.top=Invalid.bottom+(dat->curPos - pos); - - RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), NULL, NULL, RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW |RDW_ALLCHILDREN); - dat->curPos = pos; - } - } - if (HIWORD(wParam)==EN_SETFOCUS) { //Transmit focus set notification to parent window - PostMessage(GetParent(hwndDlg),WM_COMMAND, MAKEWPARAM(0,EN_SETFOCUS), (LPARAM)hwndDlg); - } - } - - if (msg == WM_PAINT) { - PAINTSTRUCT ps; - HDC hdc=BeginPaint(hwnd, &ps); - FillRect(hdc,&(ps.rcPaint),GetSysColorBrush(COLOR_BTNFACE)); - EndPaint(hwnd, &ps); - } - - return DefWindowProc(hwnd,msg,wParam,lParam); -} - -/////////////////////////////////////////////////////////////////////////////// -// Add Search field to form -// -static int JabberSearchAddField(HWND hwndDlg, Data* FieldDat) -{ - if (!FieldDat || !FieldDat->Label || !FieldDat->Var) - return FALSE; - - HFONT hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); - HWND hwndParent=GetDlgItem(hwndDlg,IDC_FRAME); - LONG_PTR frameExStyle = GetWindowLongPtr(hwndParent, GWL_EXSTYLE); - frameExStyle |= WS_EX_CONTROLPARENT; - SetWindowLongPtr(hwndParent, GWL_EXSTYLE, frameExStyle); - SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_FRAME),GWLP_WNDPROC,(LONG_PTR)JabberSearchFrameProc); - - int CornerX=1; - int CornerY=1; - RECT rect; - GetClientRect(hwndParent,&rect); - int width=rect.right-5-CornerX; - - int Order=(FieldDat->bHidden) ? -1 : FieldDat->Order; - - HWND hwndLabel=CreateWindowEx(0,_T("STATIC"),(LPCTSTR)TranslateTS(FieldDat->Label),WS_CHILD, CornerX, CornerY + Order*40, width, 13,hwndParent,NULL,hInst,0); - HWND hwndVar=CreateWindowEx(0|WS_EX_CLIENTEDGE,_T("EDIT"),(LPCTSTR)FieldDat->defValue,WS_CHILD|WS_TABSTOP, CornerX+5, CornerY + Order*40+14, width ,20,hwndParent,NULL,hInst,0); - SendMessage(hwndLabel, WM_SETFONT, (WPARAM)hFont,0); - SendMessage(hwndVar, WM_SETFONT, (WPARAM)hFont,0); - if (!FieldDat->bHidden) { - ShowWindow(hwndLabel,SW_SHOW); - ShowWindow(hwndVar,SW_SHOW); - EnableWindow(hwndLabel,!FieldDat->bReadOnly); - SendMessage(hwndVar, EM_SETREADONLY, (WPARAM)FieldDat->bReadOnly,0); - } - //remade list - //reallocation - JabberSearchData *dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); - if (dat) { - dat->pJSInf=(JabberSearchFieldsInfo*)realloc(dat->pJSInf, sizeof(JabberSearchFieldsInfo)*(dat->nJSInfCount+1)); - dat->pJSInf[dat->nJSInfCount].hwndCaptionItem=hwndLabel; - dat->pJSInf[dat->nJSInfCount].hwndValueItem=hwndVar; - dat->pJSInf[dat->nJSInfCount].szFieldCaption=_tcsdup(FieldDat->Label); - dat->pJSInf[dat->nJSInfCount].szFieldName=_tcsdup(FieldDat->Var); - dat->nJSInfCount++; - } - return CornerY + Order*40+14 +20; -} - -//////////////////////////////////////////////////////////////////////////////// -// Available search field request result handler (XEP-0055. Examples 2, 7) - -void CJabberProto::OnIqResultGetSearchFields(HXML iqNode, CJabberIqInfo *pInfo) -{ - if (!searchHandleDlg) - return; - - LPCTSTR type = xmlGetAttrValue(iqNode, _T("type")); - if (type == NULL) - return; - - if (!lstrcmp(type, _T("result"))) { - HXML queryNode = xmlGetNthChild(iqNode, _T("query"), 1); - HXML xNode = xmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); - - ShowWindow(searchHandleDlg,SW_HIDE); - if (xNode) { - //1. Form - PostMessage(searchHandleDlg, WM_USER+11, (WPARAM)xi.copyNode(xNode), 0); - HXML xcNode = xmlGetNthChild(xNode, _T("instructions"), 1); - if (xcNode) - SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, xmlGetText(xcNode)); - } - else { - int Order=0; - for (int i=0; ; i++) { - HXML chNode = xmlGetChild(queryNode, i); - if (!chNode) - break; - - if (!_tcsicmp(xmlGetName(chNode), _T("instructions")) && xmlGetText(chNode)) - SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,TranslateTS(xmlGetText(chNode))); - else if (xmlGetName(chNode)) { - Data *MyData=(Data*)malloc(sizeof(Data)); - memset(MyData,0,sizeof(Data)); - - MyData->Label = mir_tstrdup(xmlGetName(chNode)); - MyData->Var = mir_tstrdup(xmlGetName(chNode)); - MyData->defValue = mir_tstrdup(xmlGetText(chNode)); - MyData->Order = Order; - if (MyData->defValue) MyData->bReadOnly = TRUE; - PostMessage(searchHandleDlg,WM_USER+10,FALSE,(LPARAM)MyData); - Order++; - } - } - } - - const TCHAR *szFrom = xmlGetAttrValue(iqNode, _T("from")); - if (szFrom) - SearchAddToRecent(szFrom,searchHandleDlg); - PostMessage(searchHandleDlg,WM_USER+10,0,0); - ShowWindow(searchHandleDlg,SW_SHOW); - } - else if (!lstrcmp(type, _T("error"))) { - const TCHAR *code=NULL; - const TCHAR *description=NULL; - TCHAR buff[255]; - HXML errorNode = xmlGetChild(iqNode, "error"); - if (errorNode) { - code = xmlGetAttrValue(errorNode, _T("code")); - description = xmlGetText(errorNode); - } - mir_sntprintf(buff, SIZEOF(buff), TranslateT("Error %s %s\r\nPlease select other server"), code ? code : _T(""), description ? description : _T("")); - SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,buff); - } - else SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateT("Error: unknown reply received\r\nPlease select other server")); -} - -////////////////////////////////////////////////////////////////////////////////////////// -// Return results to search dialog -// The pmFields is the pointer to map of Not unical but ordered -// This can help to made result parser routines more simple - -void CJabberProto::SearchReturnResults(HANDLE id, void * pvUsersInfo, U_TCHAR_MAP * pmAllFields) -{ - LIST ListOfNonEmptyFields(20,(LIST::FTSortFunc)TCharKeyCmp); - LIST ListOfFields(20); - LIST* plUsersInfo = (LIST*)pvUsersInfo; - int i, nUsersFound = plUsersInfo->getCount(); - - // lets fill the ListOfNonEmptyFields but in users order - for (i=0; i < nUsersFound; i++) { - U_TCHAR_MAP* pmUserData = (U_TCHAR_MAP*)plUsersInfo->operator [](i); - int nUserFields = pmUserData->getCount(); - for (int j=0; j < nUserFields; j++) { - TCHAR *var = pmUserData->getKeyName(j); - if (var && ListOfNonEmptyFields.getIndex(var) < 0) - ListOfNonEmptyFields.insert(var); - } } - - // now fill the ListOfFields but order is from pmAllFields - int nAllCount = pmAllFields->getCount(); - for (i=0; i < nAllCount; i++) { - TCHAR * var=pmAllFields->getUnOrderedKeyName(i); - if (var && ListOfNonEmptyFields.getIndex(var) < 0) - continue; - ListOfFields.insert(var); - } - - // now lets transfer field names - int nFieldCount = ListOfFields.getCount(); - - JABBER_CUSTOMSEARCHRESULTS Results={0}; - Results.nSize=sizeof(Results); - Results.pszFields=(TCHAR**)mir_alloc(sizeof(TCHAR*)*nFieldCount); - Results.nFieldCount=nFieldCount; - - /* Sending Columns Titles */ - for (i=0; i < nFieldCount; i++) { - TCHAR *var = ListOfFields[i]; - if (var) - Results.pszFields[i] = pmAllFields->operator [](var); - } - - Results.jsr.hdr.cbSize = 0; // sending column names - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results); - - /* Sending Users Data */ - Results.jsr.hdr.cbSize = sizeof(Results.jsr); // sending user data - - for (i=0; i < nUsersFound; i++) { - TCHAR buff[200]; buff[0] = 0; - Results.jsr.jid[0]=0; - U_TCHAR_MAP * pmUserData = (U_TCHAR_MAP *) plUsersInfo->operator [](i); - for (int j=0; j < nFieldCount; j++) { - TCHAR* var = ListOfFields[j]; - TCHAR* value = pmUserData->operator [](var); - Results.pszFields[j] = value ? value : (TCHAR *)_T(" "); - if (!_tcsicmp(var,_T("jid")) && value) - _tcsncpy_s(Results.jsr.jid, value, _TRUNCATE); - } - { - TCHAR * nickfields[]={ _T("nick"), _T("nickname"), - _T("fullname"), _T("name"), - _T("given"), _T("first"), - _T("jid"), NULL }; - TCHAR * nick=NULL; - int k=0; - while (nickfields[k] && !nick) nick=pmUserData->operator [](nickfields[k++]); - if (_tcsicmp(nick, Results.jsr.jid)) - mir_sntprintf(buff, SIZEOF(buff), _T("%s (%s)"), nick, Results.jsr.jid); - else - _tcsncpy_s(buff, nick, _TRUNCATE); - Results.jsr.hdr.nick = nick ? buff : NULL; - Results.jsr.hdr.flags = PSR_TCHAR; - } - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results); - Results.jsr.hdr.nick=NULL; - - } - mir_free(Results.pszFields); -} - -void DestroyKey(TCHAR* key) -{ - mir_free(key); -} - -TCHAR* CopyKey(TCHAR* key) -{ - return mir_tstrdup(key); -} - -//////////////////////////////////////////////////////////////////////////////// -// Search field request result handler (XEP-0055. Examples 3, 8) - -void CJabberProto::OnIqResultAdvancedSearch(HXML iqNode, CJabberIqInfo *pInfo) -{ - const TCHAR *type; - int id; - - U_TCHAR_MAP mColumnsNames(10); - LIST SearchResults(2); - - if (((id = JabberGetPacketID(iqNode)) == -1) || ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL)) { - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); - return; - } - - if (!lstrcmp(type, _T("result"))) { - HXML queryNode = xmlGetNthChild(iqNode, _T("query"), 1); - HXML xNode = xmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); - if (xNode) { - //1. Form search results info - HXML reportNode = xmlGetNthChild(xNode, _T("reported"), 1); - if (reportNode) { - int i = 1; - while (HXML fieldNode = xmlGetNthChild(reportNode, _T("field"), i++)) { - TCHAR *var = (TCHAR*)xmlGetAttrValue(fieldNode, _T("var")); - if (var) { - TCHAR *Label = (TCHAR*)xmlGetAttrValue(fieldNode, _T("label")); - mColumnsNames.insert(var, (Label != NULL) ? Label : var); - } } } - - int i=1; - HXML itemNode; - while (itemNode = xmlGetNthChild(xNode, _T("item"), i++)) { - U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10); - int j = 1; - while (HXML fieldNode = xmlGetNthChild(itemNode, _T("field"), j++)) { - if (TCHAR* var = (TCHAR*)xmlGetAttrValue(fieldNode, _T("var"))) { - if (TCHAR* Text = (TCHAR*)xmlGetText(xmlGetChild(fieldNode, _T("value")))) { - if (!mColumnsNames[var]) - mColumnsNames.insert(var,var); - pUserColumn->insert(var,Text); - } } } - - SearchResults.insert((void*)pUserColumn); - } - } - else { - //2. Field list search results info - int i=1; - while (HXML itemNode = xmlGetNthChild(queryNode, _T("item"), i++)) { - U_TCHAR_MAP *pUserColumn=new U_TCHAR_MAP(10); - - TCHAR *jid = (TCHAR*)xmlGetAttrValue(itemNode, _T("jid")); - TCHAR *keyReturned; - mColumnsNames.insertCopyKey(_T("jid"),_T("jid"),&keyReturned, CopyKey, DestroyKey); - mColumnsNames.insert(_T("jid"), keyReturned); - pUserColumn->insertCopyKey(_T("jid"), jid, NULL, CopyKey, DestroyKey); - - for (int j=0; ; j++) { - HXML child = xmlGetChild(itemNode, j); - if (!child) - break; - - const TCHAR *szColumnName = xmlGetName(child); - if (szColumnName) { - LPCTSTR ptszChild = xmlGetText(child); - if (ptszChild && *ptszChild) { - mColumnsNames.insertCopyKey((TCHAR*)szColumnName,_T(""),&keyReturned, CopyKey, DestroyKey); - mColumnsNames.insert((TCHAR*)szColumnName,keyReturned); - pUserColumn->insertCopyKey((TCHAR*)szColumnName, (TCHAR*)ptszChild, NULL, CopyKey, DestroyKey); - } } } - - SearchResults.insert((void*)pUserColumn); - } } - } - else if (!lstrcmp(type, _T("error"))) { - const TCHAR *code = NULL; - const TCHAR *description = NULL; - TCHAR buff[255]; - HXML errorNode = xmlGetChild(iqNode , "error"); - if (errorNode) { - code = xmlGetAttrValue(errorNode, _T("code")); - description = xmlGetText(errorNode); - } - - mir_sntprintf(buff, SIZEOF(buff), TranslateT("Error %s %s\r\nTry to specify more detailed"), code ? code : _T(""), description ? description : _T("")); - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); - if (searchHandleDlg) - SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,buff); - else - MessageBox(NULL, buff, TranslateT("Search error"), MB_OK|MB_ICONSTOP); - return; - } - - SearchReturnResults((HANDLE)id, (void*)&SearchResults, (U_TCHAR_MAP *)&mColumnsNames); - - for (int i=0; i < SearchResults.getCount(); i++) - delete ((U_TCHAR_MAP *)SearchResults[i]); - - //send success to finish searching - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); -} - -static BOOL CALLBACK DeleteChildWindowsProc(HWND hwnd, LPARAM) -{ - DestroyWindow(hwnd); - return TRUE; -} - -static void JabberSearchFreeData(HWND hwndDlg, JabberSearchData * dat) -{ - //lock - if (!dat->fSearchRequestIsXForm && dat->nJSInfCount && dat->pJSInf) { - for (int i=0; i < dat->nJSInfCount; i++) { - if (dat->pJSInf[i].hwndValueItem) - DestroyWindow(dat->pJSInf[i].hwndValueItem); - if (dat->pJSInf[i].hwndCaptionItem) - DestroyWindow(dat->pJSInf[i].hwndCaptionItem); - if (dat->pJSInf[i].szFieldCaption) - free(dat->pJSInf[i].szFieldCaption); - if (dat->pJSInf[i].szFieldName) - free(dat->pJSInf[i].szFieldName); - } - free(dat->pJSInf); - dat->pJSInf=NULL; - } - else EnumChildWindows(GetDlgItem(hwndDlg,IDC_FRAME),DeleteChildWindowsProc,0); - - if (dat->xNode) - xi.destroyNode(dat->xNode); - - SendMessage(GetDlgItem(hwndDlg,IDC_FRAME), WM_SETFONT, (WPARAM)SendMessage(hwndDlg, WM_GETFONT, 0, 0),0); - dat->nJSInfCount=0; - ShowWindow(GetDlgItem(hwndDlg,IDC_VSCROLL),SW_HIDE); - SetDlgItemText(hwndDlg,IDC_INSTRUCTIONS,TranslateT("Select/type search service URL above and press ")); - //unlock -} - -static void JabberSearchRefreshFrameScroll(HWND hwndDlg, JabberSearchData * dat) -{ - HWND hFrame = GetDlgItem(hwndDlg, IDC_FRAME); - HWND hwndScroll = GetDlgItem(hwndDlg, IDC_VSCROLL); - RECT rc; - GetClientRect(hFrame, &rc); - GetClientRect(hFrame, &dat->frameRect); - dat->frameHeight = rc.bottom-rc.top; - if (dat->frameHeight < dat->CurrentHeight) { - ShowWindow(hwndScroll, SW_SHOW); - EnableWindow(hwndScroll, TRUE); - } - else ShowWindow(hwndScroll, SW_HIDE); - - SetScrollRange(hwndScroll, SB_CTL, 0, dat->CurrentHeight-dat->frameHeight, FALSE); -} - -int CJabberProto::SearchRenewFields(HWND hwndDlg, JabberSearchData * dat) -{ - TCHAR szServerName[100]; - EnableWindow(GetDlgItem(hwndDlg, IDC_GO),FALSE); - GetDlgItemText(hwndDlg,IDC_SERVER,szServerName,SIZEOF(szServerName)); - dat->CurrentHeight = 0; - dat->curPos = 0; - SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); - - JabberSearchFreeData(hwndDlg, dat); - JabberSearchRefreshFrameScroll(hwndDlg, dat); - - SetDlgItemText(hwndDlg,IDC_INSTRUCTIONS,m_bJabberOnline ? TranslateT("Please wait...\r\nConnecting search server...") : TranslateT("You have to be connected to server")); - - if (!m_bJabberOnline) - return 0; - - searchHandleDlg = hwndDlg; - - CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultGetSearchFields, JABBER_IQ_TYPE_GET, szServerName); - m_ThreadInfo->send( XmlNodeIq(pInfo) << XQUERY(_T("jabber:iq:search"))); - return pInfo->GetIqId(); -} - -static void JabberSearchAddUrlToRecentCombo(HWND hwndDlg, const TCHAR *szAddr) -{ - int lResult = SendMessage(GetDlgItem(hwndDlg,IDC_SERVER), (UINT) CB_FINDSTRING, 0, (LPARAM)szAddr); - if (lResult == -1) - SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szAddr); -} - -void CJabberProto::SearchDeleteFromRecent(const TCHAR *szAddr, BOOL deleteLastFromDB) -{ - //search in recent - for (int i=0; i<10; i++) { - char key[30]; - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", i); - ptrT szValue( getTStringA(key)); - if (szValue == NULL || _tcsicmp(szAddr, szValue)) - continue; - - for (int j=i; j < 10; j++) { - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j + 1); - szValue = getTStringA(key); - if (szValue != NULL) { - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); - setTString(NULL, key, szValue); - } - else { - if (deleteLastFromDB) { - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); - delSetting(NULL,key); - } - break; - } } - break; -} } - -void CJabberProto::SearchAddToRecent(const TCHAR *szAddr, HWND hwndDialog) -{ - char key[30]; - SearchDeleteFromRecent(szAddr); - - for (int j=9; j > 0; j--) { - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j - 1); - ptrT szValue( getTStringA(key)); - if (szValue != NULL) { - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); - setTString(NULL, key, szValue); - } } - - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", 0); - setTString(key, szAddr); - if (hwndDialog) - JabberSearchAddUrlToRecentCombo(hwndDialog, szAddr); -} - -static INT_PTR CALLBACK JabberSearchAdvancedDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - JabberSearchData* dat = (JabberSearchData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - switch (msg) { - case WM_INITDIALOG: - TranslateDialogDefault(hwndDlg); - { - dat = (JabberSearchData *)mir_calloc(sizeof(JabberSearchData)); - dat->ppro = (CJabberProto*)lParam; - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); - - /* Server Combo box */ - ptrA jud(dat->ppro->getStringA("Jud")); - char *szServerName = (jud == NULL) ? "users.jabber.org": jud; - SetDlgItemTextA(hwndDlg,IDC_SERVER,szServerName); - SendDlgItemMessageA(hwndDlg,IDC_SERVER,CB_ADDSTRING,0,(LPARAM)szServerName); - //TO DO: Add Transports here - int i, transpCount = dat->ppro->m_lstTransports.getCount(); - for (i=0; i < transpCount; i++) { - TCHAR *szTransp = dat->ppro->m_lstTransports[i]; - if (szTransp) - JabberSearchAddUrlToRecentCombo(hwndDlg, szTransp); - } - - for (i=0; i < 10; i++) { - char key[30]; - mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", i); - ptrT szValue( dat->ppro->getTStringA(key)); - if (szValue != NULL) - JabberSearchAddUrlToRecentCombo(hwndDlg, szValue); - } - - //TO DO: Add 4 recently used - dat->lastRequestIq = dat->ppro->SearchRenewFields(hwndDlg,dat); - } - return TRUE; - - case WM_COMMAND: - if (LOWORD(wParam) == IDC_SERVER) { - switch (HIWORD(wParam)) { - case CBN_SETFOCUS: - PostMessage(GetParent(hwndDlg),WM_COMMAND, MAKEWPARAM(0,EN_SETFOCUS), (LPARAM)hwndDlg); - return TRUE; - - case CBN_EDITCHANGE: - EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); - return TRUE; - - case CBN_EDITUPDATE: - JabberSearchFreeData(hwndDlg, dat); - EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); - return TRUE; - - case CBN_SELENDOK: - EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); - PostMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_GO,BN_CLICKED),0); - return TRUE; - } - } - else if (LOWORD(wParam) == IDC_GO && HIWORD(wParam) == BN_CLICKED) { - dat->ppro->SearchRenewFields(hwndDlg, dat); - return TRUE; - } - break; - - case WM_SIZE: - { - //Resize IDC_FRAME to take full size - RECT rcForm; - GetWindowRect(hwndDlg, &rcForm); - RECT rcFrame; - GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &rcFrame); - rcFrame.bottom = rcForm.bottom; - SetWindowPos(GetDlgItem(hwndDlg,IDC_FRAME),NULL,0,0,rcFrame.right-rcFrame.left,rcFrame.bottom-rcFrame.top,SWP_NOZORDER|SWP_NOMOVE); - GetWindowRect(GetDlgItem(hwndDlg,IDC_VSCROLL), &rcForm); - SetWindowPos(GetDlgItem(hwndDlg,IDC_VSCROLL),NULL,0,0,rcForm.right-rcForm.left,rcFrame.bottom-rcFrame.top,SWP_NOZORDER|SWP_NOMOVE); - JabberSearchRefreshFrameScroll(hwndDlg, dat); - } - return TRUE; - - case WM_USER+11: - { - dat->fSearchRequestIsXForm=TRUE; - dat->xNode = (HXML)wParam; - JabberFormCreateUI(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode, &dat->CurrentHeight,TRUE); - ShowWindow(GetDlgItem(hwndDlg, IDC_FRAME), SW_SHOW); - dat->nJSInfCount=1; - return TRUE; - } - case WM_USER+10: - { - Data* MyDat = (Data*)lParam; - if (MyDat) { - dat->fSearchRequestIsXForm = (BOOL)wParam; - dat->CurrentHeight = JabberSearchAddField(hwndDlg,MyDat); - mir_free(MyDat->Label); - mir_free(MyDat->Var); - mir_free(MyDat->defValue); - free(MyDat); - } - else - { - JabberSearchRefreshFrameScroll(hwndDlg,dat); - ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - 0, NULL, &(dat->frameRect)); - SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); - dat->curPos=0; - } - return TRUE; - } - case WM_MOUSEWHEEL: - { - int zDelta = GET_WHEEL_DELTA_WPARAM(wParam); - if (zDelta) { - int nScrollLines=0; - SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,(void*)&nScrollLines,0); - for (int i=0; i<(nScrollLines+1)/2; i++) - SendMessage(hwndDlg,WM_VSCROLL, (zDelta<0)?SB_LINEDOWN:SB_LINEUP,0); - } } - return TRUE; - - case WM_VSCROLL: - { - int pos; - if (dat != NULL) { - pos = dat->curPos; - switch (LOWORD(wParam)) { - case SB_LINEDOWN: - pos += 10; - break; - case SB_LINEUP: - pos -= 10; - break; - case SB_PAGEDOWN: - pos += (dat->CurrentHeight - 10); - break; - case SB_PAGEUP: - pos -= (dat->CurrentHeight - 10); - break; - case SB_THUMBTRACK: - pos = HIWORD(wParam); - break; - } - if (pos > (dat->CurrentHeight - dat->frameHeight)) - pos = dat->CurrentHeight - dat->frameHeight; - if (pos < 0) - pos = 0; - if (dat->curPos != pos) { - ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, NULL , &(dat->frameRect)); - SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); - RECT Invalid=dat->frameRect; - if (dat->curPos - pos >0) - Invalid.bottom=Invalid.top+(dat->curPos - pos); - else - Invalid.top=Invalid.bottom+(dat->curPos - pos); - - RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), NULL, NULL, RDW_UPDATENOW |RDW_ALLCHILDREN); - dat->curPos = pos; - } } } - return TRUE; - - case WM_DESTROY: - JabberSearchFreeData(hwndDlg, dat); - JabberFormDestroyUI(GetDlgItem(hwndDlg, IDC_FRAME)); - mir_free(dat); - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); - return TRUE; - } - return FALSE; -} - -HWND __cdecl CJabberProto::CreateExtendedSearchUI(HWND parent) -{ - if (parent && hInst) { - ptrT szServer( getTStringA("LoginServer")); - if (szServer == NULL || _tcsicmp(szServer, _T("S.ms"))) - return CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SEARCHUSER), parent, JabberSearchAdvancedDlgProc, (LPARAM)this); - } - - return 0; // Failure -} - -////////////////////////////////////////////////////////////////////////// -// The function formats request to server - -HWND __cdecl CJabberProto::SearchAdvanced(HWND hwndDlg) -{ - if (!m_bJabberOnline || !hwndDlg) - return 0; //error - - JabberSearchData * dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); - if (!dat) - return 0; //error - - // check if server connected (at least one field exists) - if (dat->nJSInfCount == 0) - return 0; - - // formating request - BOOL fRequestNotEmpty=FALSE; - - // get server name - TCHAR szServerName[100]; - GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, SIZEOF(szServerName)); - - // formating query - CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultAdvancedSearch, JABBER_IQ_TYPE_SET, szServerName); - XmlNodeIq iq(pInfo); - HXML query = iq << XQUERY(_T("jabber:iq:search")); - - if (m_tszSelectedLang) - iq << XATTR(_T("xml:lang"), m_tszSelectedLang); // i'm sure :) - - // next can be 2 cases: - // Forms: XEP-0055 Example 7 - if (dat->fSearchRequestIsXForm) { - fRequestNotEmpty=TRUE; - HXML n = JabberFormGetData(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode); - xmlAddChild(query, n); - xi.destroyNode(n); - } - else { //and Simple fields: XEP-0055 Example 3 - for (int i=0; inJSInfCount; i++) { - TCHAR szFieldValue[100]; - GetWindowText(dat->pJSInf[i].hwndValueItem, szFieldValue, SIZEOF(szFieldValue)); - if (szFieldValue[0] != 0) { - xmlAddChild(query, dat->pJSInf[i].szFieldName, szFieldValue); - fRequestNotEmpty=TRUE; - } } } - - if (fRequestNotEmpty) { - m_ThreadInfo->send(iq); - return (HWND)pInfo->GetIqId(); - } - return 0; -} +/* + +Jabber Protocol Plugin for Miranda NG + +Copyright (c) 2002-04 Santithorn Bunchua +Copyright (c) 2005-12 George Hazan +Copyright (c) 2007 Artem Shpynov +Copyright (c) 2012-14 Miranda NG project + +Module implements a search according to XEP-0055: Jabber Search +http://www.xmpp.org/extensions/xep-0055.html + +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 +#include "jabber_iq.h" +#include "jabber_caps.h" + +/////////////////////////////////////////////////////////////////////////////// +// Subclassing of IDC_FRAME to implement more user-friendly fields scrolling +// +static int JabberSearchFrameProc(HWND hwnd, int msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_COMMAND && lParam != 0) { + HWND hwndDlg=GetParent(hwnd); + JabberSearchData * dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + if (dat && lParam) { + int pos=dat->curPos; + RECT MineRect; + RECT FrameRect; + GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME),&FrameRect); + GetWindowRect((HWND)lParam, &MineRect); + if (MineRect.top-10 < FrameRect.top) { + pos=dat->curPos+(MineRect.top-14-FrameRect.top); + if (pos<0) pos=0; + } + else if (MineRect.bottom > FrameRect.bottom) { + pos=dat->curPos+(MineRect.bottom-FrameRect.bottom); + if (dat->frameHeight+pos>dat->CurrentHeight) + pos=dat->CurrentHeight-dat->frameHeight; + } + if (pos != dat->curPos) { + ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, NULL, &(dat->frameRect)); + SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); + RECT Invalid=dat->frameRect; + if (dat->curPos - pos >0) + Invalid.bottom=Invalid.top+(dat->curPos - pos); + else + Invalid.top=Invalid.bottom+(dat->curPos - pos); + + RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), NULL, NULL, RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW |RDW_ALLCHILDREN); + dat->curPos = pos; + } + } + if (HIWORD(wParam)==EN_SETFOCUS) { //Transmit focus set notification to parent window + PostMessage(GetParent(hwndDlg),WM_COMMAND, MAKEWPARAM(0,EN_SETFOCUS), (LPARAM)hwndDlg); + } + } + + if (msg == WM_PAINT) { + PAINTSTRUCT ps; + HDC hdc=BeginPaint(hwnd, &ps); + FillRect(hdc,&(ps.rcPaint),GetSysColorBrush(COLOR_BTNFACE)); + EndPaint(hwnd, &ps); + } + + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +/////////////////////////////////////////////////////////////////////////////// +// Add Search field to form +// +static int JabberSearchAddField(HWND hwndDlg, Data* FieldDat) +{ + if (!FieldDat || !FieldDat->Label || !FieldDat->Var) + return FALSE; + + HFONT hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); + HWND hwndParent=GetDlgItem(hwndDlg,IDC_FRAME); + LONG_PTR frameExStyle = GetWindowLongPtr(hwndParent, GWL_EXSTYLE); + frameExStyle |= WS_EX_CONTROLPARENT; + SetWindowLongPtr(hwndParent, GWL_EXSTYLE, frameExStyle); + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_FRAME),GWLP_WNDPROC,(LONG_PTR)JabberSearchFrameProc); + + int CornerX=1; + int CornerY=1; + RECT rect; + GetClientRect(hwndParent,&rect); + int width=rect.right-5-CornerX; + + int Order=(FieldDat->bHidden) ? -1 : FieldDat->Order; + + HWND hwndLabel=CreateWindowEx(0,_T("STATIC"),(LPCTSTR)TranslateTS(FieldDat->Label),WS_CHILD, CornerX, CornerY + Order*40, width, 13,hwndParent,NULL,hInst,0); + HWND hwndVar=CreateWindowEx(0|WS_EX_CLIENTEDGE,_T("EDIT"),(LPCTSTR)FieldDat->defValue,WS_CHILD|WS_TABSTOP, CornerX+5, CornerY + Order*40+14, width ,20,hwndParent,NULL,hInst,0); + SendMessage(hwndLabel, WM_SETFONT, (WPARAM)hFont,0); + SendMessage(hwndVar, WM_SETFONT, (WPARAM)hFont,0); + if (!FieldDat->bHidden) { + ShowWindow(hwndLabel,SW_SHOW); + ShowWindow(hwndVar,SW_SHOW); + EnableWindow(hwndLabel,!FieldDat->bReadOnly); + SendMessage(hwndVar, EM_SETREADONLY, (WPARAM)FieldDat->bReadOnly,0); + } + //remade list + //reallocation + JabberSearchData *dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + if (dat) { + dat->pJSInf=(JabberSearchFieldsInfo*)realloc(dat->pJSInf, sizeof(JabberSearchFieldsInfo)*(dat->nJSInfCount+1)); + dat->pJSInf[dat->nJSInfCount].hwndCaptionItem=hwndLabel; + dat->pJSInf[dat->nJSInfCount].hwndValueItem=hwndVar; + dat->pJSInf[dat->nJSInfCount].szFieldCaption=_tcsdup(FieldDat->Label); + dat->pJSInf[dat->nJSInfCount].szFieldName=_tcsdup(FieldDat->Var); + dat->nJSInfCount++; + } + return CornerY + Order*40+14 +20; +} + +//////////////////////////////////////////////////////////////////////////////// +// Available search field request result handler (XEP-0055. Examples 2, 7) + +void CJabberProto::OnIqResultGetSearchFields(HXML iqNode, CJabberIqInfo *pInfo) +{ + if (!searchHandleDlg) + return; + + LPCTSTR type = xmlGetAttrValue(iqNode, _T("type")); + if (type == NULL) + return; + + if (!lstrcmp(type, _T("result"))) { + HXML queryNode = xmlGetNthChild(iqNode, _T("query"), 1); + HXML xNode = xmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); + + ShowWindow(searchHandleDlg,SW_HIDE); + if (xNode) { + //1. Form + PostMessage(searchHandleDlg, WM_USER+11, (WPARAM)xi.copyNode(xNode), 0); + HXML xcNode = xmlGetNthChild(xNode, _T("instructions"), 1); + if (xcNode) + SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, xmlGetText(xcNode)); + } + else { + int Order=0; + for (int i=0; ; i++) { + HXML chNode = xmlGetChild(queryNode, i); + if (!chNode) + break; + + if (!_tcsicmp(xmlGetName(chNode), _T("instructions")) && xmlGetText(chNode)) + SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,TranslateTS(xmlGetText(chNode))); + else if (xmlGetName(chNode)) { + Data *MyData=(Data*)malloc(sizeof(Data)); + memset(MyData,0,sizeof(Data)); + + MyData->Label = mir_tstrdup(xmlGetName(chNode)); + MyData->Var = mir_tstrdup(xmlGetName(chNode)); + MyData->defValue = mir_tstrdup(xmlGetText(chNode)); + MyData->Order = Order; + if (MyData->defValue) MyData->bReadOnly = TRUE; + PostMessage(searchHandleDlg,WM_USER+10,FALSE,(LPARAM)MyData); + Order++; + } + } + } + + const TCHAR *szFrom = xmlGetAttrValue(iqNode, _T("from")); + if (szFrom) + SearchAddToRecent(szFrom,searchHandleDlg); + PostMessage(searchHandleDlg,WM_USER+10,0,0); + ShowWindow(searchHandleDlg,SW_SHOW); + } + else if (!lstrcmp(type, _T("error"))) { + const TCHAR *code=NULL; + const TCHAR *description=NULL; + TCHAR buff[255]; + HXML errorNode = xmlGetChild(iqNode, "error"); + if (errorNode) { + code = xmlGetAttrValue(errorNode, _T("code")); + description = xmlGetText(errorNode); + } + mir_sntprintf(buff, SIZEOF(buff), TranslateT("Error %s %s\r\nPlease select other server"), code ? code : _T(""), description ? description : _T("")); + SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,buff); + } + else SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateT("Error: unknown reply received\r\nPlease select other server")); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// Return results to search dialog +// The pmFields is the pointer to map of Not unical but ordered +// This can help to made result parser routines more simple + +void CJabberProto::SearchReturnResults(HANDLE id, void * pvUsersInfo, U_TCHAR_MAP * pmAllFields) +{ + LIST ListOfNonEmptyFields(20,(LIST::FTSortFunc)TCharKeyCmp); + LIST ListOfFields(20); + LIST* plUsersInfo = (LIST*)pvUsersInfo; + int i, nUsersFound = plUsersInfo->getCount(); + + // lets fill the ListOfNonEmptyFields but in users order + for (i=0; i < nUsersFound; i++) { + U_TCHAR_MAP* pmUserData = (U_TCHAR_MAP*)plUsersInfo->operator [](i); + int nUserFields = pmUserData->getCount(); + for (int j=0; j < nUserFields; j++) { + TCHAR *var = pmUserData->getKeyName(j); + if (var && ListOfNonEmptyFields.getIndex(var) < 0) + ListOfNonEmptyFields.insert(var); + } } + + // now fill the ListOfFields but order is from pmAllFields + int nAllCount = pmAllFields->getCount(); + for (i=0; i < nAllCount; i++) { + TCHAR * var=pmAllFields->getUnOrderedKeyName(i); + if (var && ListOfNonEmptyFields.getIndex(var) < 0) + continue; + ListOfFields.insert(var); + } + + // now lets transfer field names + int nFieldCount = ListOfFields.getCount(); + + JABBER_CUSTOMSEARCHRESULTS Results={0}; + Results.nSize=sizeof(Results); + Results.pszFields=(TCHAR**)mir_alloc(sizeof(TCHAR*)*nFieldCount); + Results.nFieldCount=nFieldCount; + + /* Sending Columns Titles */ + for (i=0; i < nFieldCount; i++) { + TCHAR *var = ListOfFields[i]; + if (var) + Results.pszFields[i] = pmAllFields->operator [](var); + } + + Results.jsr.hdr.cbSize = 0; // sending column names + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results); + + /* Sending Users Data */ + Results.jsr.hdr.cbSize = sizeof(Results.jsr); // sending user data + + for (i=0; i < nUsersFound; i++) { + TCHAR buff[200]; + buff[0] = 0; + Results.jsr.jid[0] = 0; + U_TCHAR_MAP * pmUserData = (U_TCHAR_MAP *) plUsersInfo->operator [](i); + for (int j=0; j < nFieldCount; j++) { + TCHAR* var = ListOfFields[j]; + TCHAR* value = pmUserData->operator [](var); + Results.pszFields[j] = value ? value : (TCHAR *)_T(" "); + if (!_tcsicmp(var,_T("jid")) && value) + _tcsncpy_s(Results.jsr.jid, value, _TRUNCATE); + } + { + TCHAR * nickfields[]={ _T("nick"), _T("nickname"), + _T("fullname"), _T("name"), + _T("given"), _T("first"), + _T("jid"), NULL }; + TCHAR * nick = NULL; + int k = 0; + while (nickfields[k] && !nick) { + nick = pmUserData->operator [](nickfields[k++]); + } + if (nick) { + if (_tcsicmp(nick, Results.jsr.jid)) { + mir_sntprintf(buff, SIZEOF(buff), _T("%s (%s)"), nick, Results.jsr.jid); + } else { + _tcsncpy_s(buff, nick, _TRUNCATE); + } + nick = buff; + } + Results.jsr.hdr.nick = nick; + Results.jsr.hdr.flags = PSR_TCHAR; + } + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results); + Results.jsr.hdr.nick=NULL; + } + mir_free(Results.pszFields); +} + +void DestroyKey(TCHAR* key) +{ + mir_free(key); +} + +TCHAR* CopyKey(TCHAR* key) +{ + return mir_tstrdup(key); +} + +//////////////////////////////////////////////////////////////////////////////// +// Search field request result handler (XEP-0055. Examples 3, 8) + +void CJabberProto::OnIqResultAdvancedSearch(HXML iqNode, CJabberIqInfo *pInfo) +{ + const TCHAR *type; + int id; + + U_TCHAR_MAP mColumnsNames(10); + LIST SearchResults(2); + + if (((id = JabberGetPacketID(iqNode)) == -1) || ((type = xmlGetAttrValue(iqNode, _T("type"))) == NULL)) { + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); + return; + } + + if (!lstrcmp(type, _T("result"))) { + HXML queryNode = xmlGetNthChild(iqNode, _T("query"), 1); + HXML xNode = xmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); + if (xNode) { + //1. Form search results info + HXML reportNode = xmlGetNthChild(xNode, _T("reported"), 1); + if (reportNode) { + int i = 1; + while (HXML fieldNode = xmlGetNthChild(reportNode, _T("field"), i++)) { + TCHAR *var = (TCHAR*)xmlGetAttrValue(fieldNode, _T("var")); + if (var) { + TCHAR *Label = (TCHAR*)xmlGetAttrValue(fieldNode, _T("label")); + mColumnsNames.insert(var, (Label != NULL) ? Label : var); + } } } + + int i=1; + HXML itemNode; + while (itemNode = xmlGetNthChild(xNode, _T("item"), i++)) { + U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10); + int j = 1; + while (HXML fieldNode = xmlGetNthChild(itemNode, _T("field"), j++)) { + if (TCHAR* var = (TCHAR*)xmlGetAttrValue(fieldNode, _T("var"))) { + if (TCHAR* Text = (TCHAR*)xmlGetText(xmlGetChild(fieldNode, _T("value")))) { + if (!mColumnsNames[var]) + mColumnsNames.insert(var,var); + pUserColumn->insert(var,Text); + } } } + + SearchResults.insert((void*)pUserColumn); + } + } + else { + //2. Field list search results info + int i=1; + while (HXML itemNode = xmlGetNthChild(queryNode, _T("item"), i++)) { + U_TCHAR_MAP *pUserColumn=new U_TCHAR_MAP(10); + + TCHAR *jid = (TCHAR*)xmlGetAttrValue(itemNode, _T("jid")); + TCHAR *keyReturned; + mColumnsNames.insertCopyKey(_T("jid"),_T("jid"),&keyReturned, CopyKey, DestroyKey); + mColumnsNames.insert(_T("jid"), keyReturned); + pUserColumn->insertCopyKey(_T("jid"), jid, NULL, CopyKey, DestroyKey); + + for (int j=0; ; j++) { + HXML child = xmlGetChild(itemNode, j); + if (!child) + break; + + const TCHAR *szColumnName = xmlGetName(child); + if (szColumnName) { + LPCTSTR ptszChild = xmlGetText(child); + if (ptszChild && *ptszChild) { + mColumnsNames.insertCopyKey((TCHAR*)szColumnName,_T(""),&keyReturned, CopyKey, DestroyKey); + mColumnsNames.insert((TCHAR*)szColumnName,keyReturned); + pUserColumn->insertCopyKey((TCHAR*)szColumnName, (TCHAR*)ptszChild, NULL, CopyKey, DestroyKey); + } } } + + SearchResults.insert((void*)pUserColumn); + } } + } + else if (!lstrcmp(type, _T("error"))) { + const TCHAR *code = NULL; + const TCHAR *description = NULL; + TCHAR buff[255]; + HXML errorNode = xmlGetChild(iqNode , "error"); + if (errorNode) { + code = xmlGetAttrValue(errorNode, _T("code")); + description = xmlGetText(errorNode); + } + + mir_sntprintf(buff, SIZEOF(buff), TranslateT("Error %s %s\r\nTry to specify more detailed"), code ? code : _T(""), description ? description : _T("")); + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); + if (searchHandleDlg) + SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,buff); + else + MessageBox(NULL, buff, TranslateT("Search error"), MB_OK|MB_ICONSTOP); + return; + } + + SearchReturnResults((HANDLE)id, (void*)&SearchResults, (U_TCHAR_MAP *)&mColumnsNames); + + for (int i=0; i < SearchResults.getCount(); i++) + delete ((U_TCHAR_MAP *)SearchResults[i]); + + //send success to finish searching + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); +} + +static BOOL CALLBACK DeleteChildWindowsProc(HWND hwnd, LPARAM) +{ + DestroyWindow(hwnd); + return TRUE; +} + +static void JabberSearchFreeData(HWND hwndDlg, JabberSearchData * dat) +{ + //lock + if (!dat->fSearchRequestIsXForm && dat->nJSInfCount && dat->pJSInf) { + for (int i=0; i < dat->nJSInfCount; i++) { + if (dat->pJSInf[i].hwndValueItem) + DestroyWindow(dat->pJSInf[i].hwndValueItem); + if (dat->pJSInf[i].hwndCaptionItem) + DestroyWindow(dat->pJSInf[i].hwndCaptionItem); + if (dat->pJSInf[i].szFieldCaption) + free(dat->pJSInf[i].szFieldCaption); + if (dat->pJSInf[i].szFieldName) + free(dat->pJSInf[i].szFieldName); + } + free(dat->pJSInf); + dat->pJSInf=NULL; + } + else EnumChildWindows(GetDlgItem(hwndDlg,IDC_FRAME),DeleteChildWindowsProc,0); + + if (dat->xNode) + xi.destroyNode(dat->xNode); + + SendMessage(GetDlgItem(hwndDlg,IDC_FRAME), WM_SETFONT, (WPARAM)SendMessage(hwndDlg, WM_GETFONT, 0, 0),0); + dat->nJSInfCount=0; + ShowWindow(GetDlgItem(hwndDlg,IDC_VSCROLL),SW_HIDE); + SetDlgItemText(hwndDlg,IDC_INSTRUCTIONS,TranslateT("Select/type search service URL above and press ")); + //unlock +} + +static void JabberSearchRefreshFrameScroll(HWND hwndDlg, JabberSearchData * dat) +{ + HWND hFrame = GetDlgItem(hwndDlg, IDC_FRAME); + HWND hwndScroll = GetDlgItem(hwndDlg, IDC_VSCROLL); + RECT rc; + GetClientRect(hFrame, &rc); + GetClientRect(hFrame, &dat->frameRect); + dat->frameHeight = rc.bottom-rc.top; + if (dat->frameHeight < dat->CurrentHeight) { + ShowWindow(hwndScroll, SW_SHOW); + EnableWindow(hwndScroll, TRUE); + } + else ShowWindow(hwndScroll, SW_HIDE); + + SetScrollRange(hwndScroll, SB_CTL, 0, dat->CurrentHeight-dat->frameHeight, FALSE); +} + +int CJabberProto::SearchRenewFields(HWND hwndDlg, JabberSearchData * dat) +{ + TCHAR szServerName[100]; + EnableWindow(GetDlgItem(hwndDlg, IDC_GO),FALSE); + GetDlgItemText(hwndDlg,IDC_SERVER,szServerName,SIZEOF(szServerName)); + dat->CurrentHeight = 0; + dat->curPos = 0; + SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); + + JabberSearchFreeData(hwndDlg, dat); + JabberSearchRefreshFrameScroll(hwndDlg, dat); + + SetDlgItemText(hwndDlg,IDC_INSTRUCTIONS,m_bJabberOnline ? TranslateT("Please wait...\r\nConnecting search server...") : TranslateT("You have to be connected to server")); + + if (!m_bJabberOnline) + return 0; + + searchHandleDlg = hwndDlg; + + CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultGetSearchFields, JABBER_IQ_TYPE_GET, szServerName); + m_ThreadInfo->send( XmlNodeIq(pInfo) << XQUERY(_T("jabber:iq:search"))); + return pInfo->GetIqId(); +} + +static void JabberSearchAddUrlToRecentCombo(HWND hwndDlg, const TCHAR *szAddr) +{ + int lResult = SendMessage(GetDlgItem(hwndDlg,IDC_SERVER), (UINT) CB_FINDSTRING, 0, (LPARAM)szAddr); + if (lResult == -1) + SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szAddr); +} + +void CJabberProto::SearchDeleteFromRecent(const TCHAR *szAddr, BOOL deleteLastFromDB) +{ + //search in recent + for (int i=0; i<10; i++) { + char key[30]; + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", i); + ptrT szValue( getTStringA(key)); + if (szValue == NULL || _tcsicmp(szAddr, szValue)) + continue; + + for (int j=i; j < 10; j++) { + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j + 1); + szValue = getTStringA(key); + if (szValue != NULL) { + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); + setTString(NULL, key, szValue); + } + else { + if (deleteLastFromDB) { + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); + delSetting(NULL,key); + } + break; + } } + break; +} } + +void CJabberProto::SearchAddToRecent(const TCHAR *szAddr, HWND hwndDialog) +{ + char key[30]; + SearchDeleteFromRecent(szAddr); + + for (int j=9; j > 0; j--) { + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j - 1); + ptrT szValue( getTStringA(key)); + if (szValue != NULL) { + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", j); + setTString(NULL, key, szValue); + } } + + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", 0); + setTString(key, szAddr); + if (hwndDialog) + JabberSearchAddUrlToRecentCombo(hwndDialog, szAddr); +} + +static INT_PTR CALLBACK JabberSearchAdvancedDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + JabberSearchData* dat = (JabberSearchData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + dat = (JabberSearchData *)mir_calloc(sizeof(JabberSearchData)); + dat->ppro = (CJabberProto*)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + /* Server Combo box */ + ptrA jud(dat->ppro->getStringA("Jud")); + char *szServerName = (jud == NULL) ? "users.jabber.org": jud; + SetDlgItemTextA(hwndDlg,IDC_SERVER,szServerName); + SendDlgItemMessageA(hwndDlg,IDC_SERVER,CB_ADDSTRING,0,(LPARAM)szServerName); + //TO DO: Add Transports here + int i, transpCount = dat->ppro->m_lstTransports.getCount(); + for (i=0; i < transpCount; i++) { + TCHAR *szTransp = dat->ppro->m_lstTransports[i]; + if (szTransp) + JabberSearchAddUrlToRecentCombo(hwndDlg, szTransp); + } + + for (i=0; i < 10; i++) { + char key[30]; + mir_snprintf(key, SIZEOF(key), "RecentlySearched_%d", i); + ptrT szValue( dat->ppro->getTStringA(key)); + if (szValue != NULL) + JabberSearchAddUrlToRecentCombo(hwndDlg, szValue); + } + + //TO DO: Add 4 recently used + dat->lastRequestIq = dat->ppro->SearchRenewFields(hwndDlg,dat); + } + return TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDC_SERVER) { + switch (HIWORD(wParam)) { + case CBN_SETFOCUS: + PostMessage(GetParent(hwndDlg),WM_COMMAND, MAKEWPARAM(0,EN_SETFOCUS), (LPARAM)hwndDlg); + return TRUE; + + case CBN_EDITCHANGE: + EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); + return TRUE; + + case CBN_EDITUPDATE: + JabberSearchFreeData(hwndDlg, dat); + EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); + return TRUE; + + case CBN_SELENDOK: + EnableWindow(GetDlgItem(hwndDlg, IDC_GO),TRUE); + PostMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_GO,BN_CLICKED),0); + return TRUE; + } + } + else if (LOWORD(wParam) == IDC_GO && HIWORD(wParam) == BN_CLICKED) { + dat->ppro->SearchRenewFields(hwndDlg, dat); + return TRUE; + } + break; + + case WM_SIZE: + { + //Resize IDC_FRAME to take full size + RECT rcForm; + GetWindowRect(hwndDlg, &rcForm); + RECT rcFrame; + GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &rcFrame); + rcFrame.bottom = rcForm.bottom; + SetWindowPos(GetDlgItem(hwndDlg,IDC_FRAME),NULL,0,0,rcFrame.right-rcFrame.left,rcFrame.bottom-rcFrame.top,SWP_NOZORDER|SWP_NOMOVE); + GetWindowRect(GetDlgItem(hwndDlg,IDC_VSCROLL), &rcForm); + SetWindowPos(GetDlgItem(hwndDlg,IDC_VSCROLL),NULL,0,0,rcForm.right-rcForm.left,rcFrame.bottom-rcFrame.top,SWP_NOZORDER|SWP_NOMOVE); + JabberSearchRefreshFrameScroll(hwndDlg, dat); + } + return TRUE; + + case WM_USER+11: + { + dat->fSearchRequestIsXForm=TRUE; + dat->xNode = (HXML)wParam; + JabberFormCreateUI(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode, &dat->CurrentHeight,TRUE); + ShowWindow(GetDlgItem(hwndDlg, IDC_FRAME), SW_SHOW); + dat->nJSInfCount=1; + return TRUE; + } + case WM_USER+10: + { + Data* MyDat = (Data*)lParam; + if (MyDat) { + dat->fSearchRequestIsXForm = (BOOL)wParam; + dat->CurrentHeight = JabberSearchAddField(hwndDlg,MyDat); + mir_free(MyDat->Label); + mir_free(MyDat->Var); + mir_free(MyDat->defValue); + free(MyDat); + } + else + { + JabberSearchRefreshFrameScroll(hwndDlg,dat); + ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - 0, NULL, &(dat->frameRect)); + SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); + dat->curPos=0; + } + return TRUE; + } + case WM_MOUSEWHEEL: + { + int zDelta = GET_WHEEL_DELTA_WPARAM(wParam); + if (zDelta) { + int nScrollLines=0; + SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,(void*)&nScrollLines,0); + for (int i=0; i<(nScrollLines+1)/2; i++) + SendMessage(hwndDlg,WM_VSCROLL, (zDelta<0)?SB_LINEDOWN:SB_LINEUP,0); + } } + return TRUE; + + case WM_VSCROLL: + { + int pos; + if (dat != NULL) { + pos = dat->curPos; + switch (LOWORD(wParam)) { + case SB_LINEDOWN: + pos += 10; + break; + case SB_LINEUP: + pos -= 10; + break; + case SB_PAGEDOWN: + pos += (dat->CurrentHeight - 10); + break; + case SB_PAGEUP: + pos -= (dat->CurrentHeight - 10); + break; + case SB_THUMBTRACK: + pos = HIWORD(wParam); + break; + } + if (pos > (dat->CurrentHeight - dat->frameHeight)) + pos = dat->CurrentHeight - dat->frameHeight; + if (pos < 0) + pos = 0; + if (dat->curPos != pos) { + ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, NULL , &(dat->frameRect)); + SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); + RECT Invalid=dat->frameRect; + if (dat->curPos - pos >0) + Invalid.bottom=Invalid.top+(dat->curPos - pos); + else + Invalid.top=Invalid.bottom+(dat->curPos - pos); + + RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), NULL, NULL, RDW_UPDATENOW |RDW_ALLCHILDREN); + dat->curPos = pos; + } } } + return TRUE; + + case WM_DESTROY: + JabberSearchFreeData(hwndDlg, dat); + JabberFormDestroyUI(GetDlgItem(hwndDlg, IDC_FRAME)); + mir_free(dat); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + return TRUE; + } + return FALSE; +} + +HWND __cdecl CJabberProto::CreateExtendedSearchUI(HWND parent) +{ + if (parent && hInst) { + ptrT szServer( getTStringA("LoginServer")); + if (szServer == NULL || _tcsicmp(szServer, _T("S.ms"))) + return CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SEARCHUSER), parent, JabberSearchAdvancedDlgProc, (LPARAM)this); + } + + return 0; // Failure +} + +////////////////////////////////////////////////////////////////////////// +// The function formats request to server + +HWND __cdecl CJabberProto::SearchAdvanced(HWND hwndDlg) +{ + if (!m_bJabberOnline || !hwndDlg) + return 0; //error + + JabberSearchData * dat=(JabberSearchData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + if (!dat) + return 0; //error + + // check if server connected (at least one field exists) + if (dat->nJSInfCount == 0) + return 0; + + // formating request + BOOL fRequestNotEmpty=FALSE; + + // get server name + TCHAR szServerName[100]; + GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, SIZEOF(szServerName)); + + // formating query + CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultAdvancedSearch, JABBER_IQ_TYPE_SET, szServerName); + XmlNodeIq iq(pInfo); + HXML query = iq << XQUERY(_T("jabber:iq:search")); + + if (m_tszSelectedLang) + iq << XATTR(_T("xml:lang"), m_tszSelectedLang); // i'm sure :) + + // next can be 2 cases: + // Forms: XEP-0055 Example 7 + if (dat->fSearchRequestIsXForm) { + fRequestNotEmpty=TRUE; + HXML n = JabberFormGetData(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode); + xmlAddChild(query, n); + xi.destroyNode(n); + } + else { //and Simple fields: XEP-0055 Example 3 + for (int i=0; inJSInfCount; i++) { + TCHAR szFieldValue[100]; + GetWindowText(dat->pJSInf[i].hwndValueItem, szFieldValue, SIZEOF(szFieldValue)); + if (szFieldValue[0] != 0) { + xmlAddChild(query, dat->pJSInf[i].szFieldName, szFieldValue); + fRequestNotEmpty=TRUE; + } } } + + if (fRequestNotEmpty) { + m_ThreadInfo->send(iq); + return (HWND)pInfo->GetIqId(); + } + return 0; +} diff --git a/protocols/JabberG/src/jabber_userinfo.cpp b/protocols/JabberG/src/jabber_userinfo.cpp index 785b6c6a66..cc320b01c0 100644 --- a/protocols/JabberG/src/jabber_userinfo.cpp +++ b/protocols/JabberG/src/jabber_userinfo.cpp @@ -1,847 +1,850 @@ -/* - -Jabber Protocol Plugin for Miranda NG - -Copyright (c) 2002-04 Santithorn Bunchua -Copyright (c) 2005-12 George Hazan -Copyright (c) 2007 Maxim Mluhov -Copyright (c) 2012-14 Miranda NG project - -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 -#include -#include - -#include "jabber_list.h" - -static HANDLE hUserInfoList = NULL; - -struct UserInfoStringBuf -{ - enum { STRINGBUF_INCREMENT = 1024 }; - - TCHAR *buf; - int size; - int offset; - - UserInfoStringBuf() { buf = 0; size = 0; offset = 0; } - ~UserInfoStringBuf() { mir_free(buf); } - - void append(TCHAR *str) { - if (!str) return; - - int length = lstrlen(str); - if (size - offset < length + 1) { - size += (length + STRINGBUF_INCREMENT); - buf = (TCHAR *)mir_realloc(buf, size * sizeof(TCHAR)); - } - lstrcpy(buf + offset, str); - offset += length; - } - - TCHAR *allocate(int length) { - if (size - offset < length) { - size += (length + STRINGBUF_INCREMENT); - buf = (TCHAR *)mir_realloc(buf, size * sizeof(TCHAR)); - } - return buf + offset; - } - - void actualize() { - if (buf) offset = lstrlen(buf); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// JabberUserInfoDlgProc - main user info dialog - -struct JabberUserInfoDlgData -{ - MCONTACT hContact; - CJabberProto *ppro; - JABBER_LIST_ITEM *item; - int resourcesCount; -}; - -enum -{ - INFOLINE_DELETE = 0x80000000, - INFOLINE_MASK = 0x7fffffff, - INFOLINE_BAD_ID = 0x7fffffff, - - INFOLINE_NAME = 1, - INFOLINE_MOOD, - INFOLINE_ACTIVITY, - INFOLINE_TUNE, - INFOLINE_OFFLINE, - INFOLINE_MESSAGE, - INFOLINE_SOFTWARE, - INFOLINE_VERSION, - INFOLINE_SYSTEM, - INFOLINE_PRIORITY, - INFOLINE_IDLE, - INFOLINE_CAPS, - INFOLINE_SOFTWARE_INFORMATION, - INFOLINE_SUBSCRIPTION, - INFOLINE_LOGOFF, - INFOLINE_LOGOFF_MSG, - INFOLINE_LASTACTIVE, -}; - -__forceinline DWORD sttInfoLineId(DWORD res, DWORD type, DWORD line=0) -{ - return - (type << 24) & 0x7f000000 | - (res << 12) & 0x00fff000 | - (line ) & 0x00000fff; -} - -static HTREEITEM sttFindInfoLine(HWND hwndTree, HTREEITEM htiRoot, LPARAM id=INFOLINE_BAD_ID) -{ - if (id == INFOLINE_BAD_ID) return NULL; - for (HTREEITEM hti = TreeView_GetChild(hwndTree, htiRoot); hti; hti = TreeView_GetNextSibling(hwndTree, hti)) - { - TVITEMEX tvi = {0}; - tvi.mask = TVIF_HANDLE|TVIF_PARAM; - tvi.hItem = hti; - TreeView_GetItem(hwndTree, &tvi); - if ((tvi.lParam&INFOLINE_MASK) == (id&INFOLINE_MASK)) - return hti; - } - return NULL; -} - -void sttCleanupInfo(HWND hwndTree, int stage) -{ - HTREEITEM hItem = TreeView_GetRoot(hwndTree); - while (hItem) { - TVITEMEX tvi = {0}; - tvi.mask = TVIF_HANDLE|TVIF_PARAM; - tvi.hItem = hItem; - TreeView_GetItem(hwndTree, &tvi); - - switch (stage) { - case 0: - tvi.lParam |= INFOLINE_DELETE; - TreeView_SetItem(hwndTree, &tvi); - break; - - case 1: - if (tvi.lParam & INFOLINE_DELETE) { - hItem = TreeView_GetNextSibling(hwndTree, hItem); - TreeView_DeleteItem(hwndTree, tvi.hItem); - continue; - } - break; - } - - HTREEITEM hItemTmp = 0; - if (hItemTmp = TreeView_GetChild(hwndTree, hItem)) - hItem = hItemTmp; - else if (hItemTmp = TreeView_GetNextSibling(hwndTree, hItem)) - hItem = hItemTmp; - else { - while (1) { - if (!(hItem = TreeView_GetParent(hwndTree, hItem))) break; - if (hItemTmp = TreeView_GetNextSibling(hwndTree, hItem)) { - hItem = hItemTmp; - break; - } - } - } - } -} - -static HTREEITEM sttFillInfoLine(HWND hwndTree, HTREEITEM htiRoot, HICON hIcon, TCHAR *title, TCHAR *value, LPARAM id=INFOLINE_BAD_ID, bool expand=false) -{ - HTREEITEM hti = sttFindInfoLine(hwndTree, htiRoot, id); - - TCHAR buf[256]; - if (title) - mir_sntprintf(buf, SIZEOF(buf), _T("%s: %s"), title, value); - else - lstrcpyn(buf, value, SIZEOF(buf)); - - TVINSERTSTRUCT tvis = {0}; - tvis.hParent = htiRoot; - tvis.hInsertAfter = TVI_LAST; - tvis.itemex.mask = TVIF_TEXT|TVIF_PARAM; - tvis.itemex.pszText = buf; - tvis.itemex.lParam = id; - - if (hIcon) { - HIMAGELIST himl = TreeView_GetImageList(hwndTree, TVSIL_NORMAL); - tvis.itemex.mask |= TVIF_IMAGE|TVIF_SELECTEDIMAGE; - tvis.itemex.iImage = - tvis.itemex.iSelectedImage = ImageList_AddIcon(himl, hIcon); - g_ReleaseIcon(hIcon); - } - - if (hti) { - tvis.itemex.mask |= TVIF_HANDLE; - tvis.itemex.hItem = hti; - TreeView_SetItem(hwndTree, &tvis.itemex); - } - else { - tvis.itemex.mask |= TVIF_STATE; - tvis.itemex.stateMask = TVIS_EXPANDED; - tvis.itemex.state = expand ? TVIS_EXPANDED : 0; - hti = TreeView_InsertItem(hwndTree, &tvis); - } - - return hti; -} - -static void sttFillResourceInfo(CJabberProto *ppro, HWND hwndTree, HTREEITEM htiRoot, JABBER_LIST_ITEM *item, int resource) -{ - TCHAR buf[256]; - HTREEITEM htiResource = htiRoot; - pResourceStatus r = resource ? item->arResources[resource-1] : item->getTemp(); - - if (r->m_tszResourceName && *r->m_tszResourceName) - htiResource = sttFillInfoLine(hwndTree, htiRoot, LoadSkinnedProtoIcon(ppro->m_szModuleName, r->m_iStatus), - TranslateT("Resource"), r->m_tszResourceName, sttInfoLineId(resource, INFOLINE_NAME), true); - - // StatusMsg - sttFillInfoLine(hwndTree, htiResource, NULL /*LoadSkinnedIcon(SKINICON_EVENT_MESSAGE)*/, - TranslateT("Message"), r->m_tszStatusMessage ? r->m_tszStatusMessage : TranslateT(""), - sttInfoLineId(resource, INFOLINE_MESSAGE)); - - // Software - HICON hIcon = NULL; - if ( ServiceExists(MS_FP_GETCLIENTICONT)) { - if (r->m_tszSoftware != NULL) { - mir_sntprintf(buf, SIZEOF(buf), _T("%s %s"), r->m_tszSoftware, r->m_tszSoftwareVersion); - hIcon = Finger_GetClientIcon(buf, 0); - } - } - - sttFillInfoLine(hwndTree, htiResource, hIcon, TranslateT("Software"), - r->m_tszSoftware ? r->m_tszSoftware : TranslateT(""), - sttInfoLineId(resource, INFOLINE_SOFTWARE)); - - if (hIcon) - DestroyIcon(hIcon); - - // Version - sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Version"), - r->m_tszSoftwareVersion ? r->m_tszSoftwareVersion : TranslateT(""), - sttInfoLineId(resource, INFOLINE_VERSION)); - - // System - sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("System"), - r->m_tszOs ? r->m_tszOs : TranslateT(""), - sttInfoLineId(resource, INFOLINE_SYSTEM)); - - // Resource priority - TCHAR szPriority[128]; - mir_sntprintf(szPriority, SIZEOF(szPriority), _T("%d"), (int)r->m_iPriority); - sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Resource priority"), szPriority, sttInfoLineId(resource, INFOLINE_PRIORITY)); - - // Idle - if (r->m_dwIdleStartTime > 0) { - lstrcpyn(buf, _tctime(&r->m_dwIdleStartTime), SIZEOF(buf)); - int len = lstrlen(buf); - if (len > 0) buf[len-1] = 0; - } - else if (!r->m_dwIdleStartTime) - lstrcpyn(buf, TranslateT("unknown"), SIZEOF(buf)); - else - lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); - - sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Idle since"), buf, sttInfoLineId(resource, INFOLINE_IDLE)); - - // caps - mir_sntprintf(buf, SIZEOF(buf), _T("%s/%s"), item->jid, r->m_tszResourceName); - JabberCapsBits jcb = ppro->GetResourceCapabilites(buf, TRUE); - - if (!(jcb & JABBER_RESOURCE_CAPS_ERROR)) { - HTREEITEM htiCaps = sttFillInfoLine(hwndTree, htiResource, ppro->LoadIconEx("main"), NULL, TranslateT("Client capabilities"), sttInfoLineId(resource, INFOLINE_CAPS)); - int i; - for (i=0; g_JabberFeatCapPairs[i].szFeature; i++) - if (jcb & g_JabberFeatCapPairs[i].jcbCap) { - TCHAR szDescription[ 1024 ]; - if (g_JabberFeatCapPairs[i].tszDescription) - mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(g_JabberFeatCapPairs[i].tszDescription), g_JabberFeatCapPairs[i].szFeature); - else - mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s"), g_JabberFeatCapPairs[i].szFeature); - sttFillInfoLine(hwndTree, htiCaps, NULL, NULL, szDescription, sttInfoLineId(resource, INFOLINE_CAPS, i)); - } - - for (int j = 0; j < ppro->m_lstJabberFeatCapPairsDynamic.getCount(); j++, i++) - if (jcb & ppro->m_lstJabberFeatCapPairsDynamic[j]->jcbCap) { - TCHAR szDescription[ 1024 ]; - if (ppro->m_lstJabberFeatCapPairsDynamic[j]->szDescription) - mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(ppro->m_lstJabberFeatCapPairsDynamic[j]->szDescription), ppro->m_lstJabberFeatCapPairsDynamic[j]->szFeature); - else - mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s"), ppro->m_lstJabberFeatCapPairsDynamic[j]->szFeature); - sttFillInfoLine(hwndTree, htiCaps, NULL, NULL, szDescription, sttInfoLineId(resource, INFOLINE_CAPS, i)); - } - } - - // Software info - HTREEITEM htiSoftwareInfo = sttFillInfoLine(hwndTree, htiResource, ppro->LoadIconEx("main"), NULL, TranslateT("Software information"), sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION)); - int nLineId = 0; - if (r->m_tszOs) - sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Operating system"), r->m_tszOs, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); - if (r->m_tszOsVersion) - sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Operating system version"), r->m_tszOsVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); - if (r->m_tszSoftware) - sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Software"), r->m_tszSoftware, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); - if (r->m_tszSoftwareVersion) - sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Software version"), r->m_tszSoftwareVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); - if (r->m_tszXMirandaCoreVersion) - sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Miranda core version"), r->m_tszXMirandaCoreVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); -} - -static void sttFillAdvStatusInfo(CJabberProto *ppro, HWND hwndTree, HTREEITEM htiRoot, DWORD dwInfoLine, MCONTACT hContact, TCHAR *szTitle, char *pszSlot) -{ - char *szAdvStatusIcon = ppro->ReadAdvStatusA(hContact, pszSlot, ADVSTATUS_VAL_ICON); - TCHAR *szAdvStatusTitle = ppro->ReadAdvStatusT(hContact, pszSlot, ADVSTATUS_VAL_TITLE); - TCHAR *szAdvStatusText = ppro->ReadAdvStatusT(hContact, pszSlot, ADVSTATUS_VAL_TEXT); - - if (szAdvStatusIcon && szAdvStatusTitle && *szAdvStatusTitle) { - TCHAR szText[2048]; - if (szAdvStatusText && *szAdvStatusText) - mir_sntprintf(szText, 2047, _T("%s (%s)"), TranslateTS(szAdvStatusTitle), szAdvStatusText); - else - mir_sntprintf(szText, 2047, _T("%s"), TranslateTS(szAdvStatusTitle)); - sttFillInfoLine(hwndTree, htiRoot, Skin_GetIcon(szAdvStatusIcon), szTitle, szText, dwInfoLine); - } - - mir_free(szAdvStatusIcon); - mir_free(szAdvStatusTitle); - mir_free(szAdvStatusText); -} - -static void sttFillUserInfo(CJabberProto *ppro, HWND hwndTree, JABBER_LIST_ITEM *item) -{ - SendMessage(hwndTree, WM_SETREDRAW, FALSE, 0); - - sttCleanupInfo(hwndTree, 0); - - HTREEITEM htiRoot = sttFillInfoLine(hwndTree, NULL, ppro->LoadIconEx("main"), _T("JID"), item->jid, sttInfoLineId(0, INFOLINE_NAME), true); - TCHAR buf[256]; - - if (MCONTACT hContact = ppro->HContactFromJID(item->jid)) { - sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_MOOD), hContact, TranslateT("Mood"), ADVSTATUS_MOOD); - sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_ACTIVITY), hContact, TranslateT("Activity"), ADVSTATUS_ACTIVITY); - sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_TUNE), hContact, TranslateT("Tune"), ADVSTATUS_TUNE); - } - - // subscription - switch (item->subscription) { - case SUB_BOTH: - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("both"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); - break; - case SUB_TO: - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("to"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); - break; - case SUB_FROM: - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("from"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); - break; - default: - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("none"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); - break; - } - - // logoff - JABBER_RESOURCE_STATUS *r = item->getTemp(); - if (r->m_dwIdleStartTime > 0) { - lstrcpyn(buf, _tctime(&r->m_dwIdleStartTime), SIZEOF(buf)); - int len = lstrlen(buf); - if (len > 0) buf[len-1] = 0; - } - else if (!r->m_dwIdleStartTime) - lstrcpyn(buf, TranslateT("unknown"), SIZEOF(buf)); - else - lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); - - sttFillInfoLine(hwndTree, htiRoot, NULL, - (item->jid && _tcschr(item->jid, _T('@'))) ? TranslateT("Last logoff time") : TranslateT("Uptime"), buf, - sttInfoLineId(0, INFOLINE_LOGOFF)); - - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Logoff message"), - r->m_tszStatusMessage ? r->m_tszStatusMessage : TranslateT(""), sttInfoLineId(0, INFOLINE_LOGOFF_MSG)); - - // activity - if (item->m_pLastSeenResource) - lstrcpyn(buf, item->m_pLastSeenResource->m_tszResourceName, SIZEOF(buf)); - else - lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); - - sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Last active resource"), buf, - sttInfoLineId(0, INFOLINE_LASTACTIVE)); - - // resources - if (item->arResources.getCount()) { - for (int i=0; i < item->arResources.getCount(); i++) - sttFillResourceInfo(ppro, hwndTree, htiRoot, item, i+1); - } - else if (!_tcschr(item->jid, _T('@')) || (r->m_iStatus != ID_STATUS_OFFLINE)) - sttFillResourceInfo(ppro, hwndTree, htiRoot, item, 0); - - sttCleanupInfo(hwndTree, 1); - SendMessage(hwndTree, WM_SETREDRAW, TRUE, 0); - - RedrawWindow(hwndTree, NULL, NULL, RDW_INVALIDATE); -} - -static void sttGetNodeText(HWND hwndTree, HTREEITEM hti, UserInfoStringBuf *buf, int indent = 0) -{ - for (int i=0; i < indent; i++) - buf->append(_T("\t")); - - TVITEMEX tvi = {0}; - tvi.mask = TVIF_HANDLE|TVIF_TEXT|TVIF_STATE; - tvi.hItem = hti; - tvi.cchTextMax = 256; - tvi.pszText = buf->allocate(tvi.cchTextMax); - if (!TreeView_GetItem(hwndTree, &tvi)) { // failure, maybe item was removed... - buf->buf[ buf->offset ] = 0; - buf->actualize(); - return; - } - - buf->actualize(); - buf->append(_T("\r\n")); - - if (tvi.state & TVIS_EXPANDED) - for (hti = TreeView_GetChild(hwndTree, hti); hti; hti = TreeView_GetNextSibling(hwndTree, hti)) - sttGetNodeText(hwndTree, hti, buf, indent + 1); -} - -static INT_PTR CALLBACK JabberUserInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - JabberUserInfoDlgData *dat = (JabberUserInfoDlgData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - - switch (msg) { - case WM_INITDIALOG: - // lParam is hContact - TranslateDialogDefault(hwndDlg); - - SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadSkinnedIconBig(SKINICON_OTHER_USERDETAILS)); - SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadSkinnedIcon(SKINICON_OTHER_USERDETAILS)); - - dat = (JabberUserInfoDlgData *)mir_alloc(sizeof(JabberUserInfoDlgData)); - ZeroMemory(dat, sizeof(JabberUserInfoDlgData)); - dat->resourcesCount = -1; - - if (CallService(MS_DB_CONTACT_IS, (WPARAM)lParam, 0)) - dat->hContact = lParam; - else if (!IsBadReadPtr((void*)lParam, sizeof(JABBER_LIST_ITEM))) { - dat->hContact = NULL; - dat->item = (JABBER_LIST_ITEM *)lParam; - } - - { - RECT rc; GetClientRect(hwndDlg, &rc); - MoveWindow(GetDlgItem(hwndDlg, IDC_TV_INFO), 5, 5, rc.right-10, rc.bottom-10, TRUE); - - HIMAGELIST himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR|ILC_COLOR32|ILC_MASK, 5, 1); - ImageList_AddIcon_Icolib(himl, LoadSkinnedIcon(SKINICON_OTHER_SMALLDOT)); - TreeView_SetImageList(GetDlgItem(hwndDlg, IDC_TV_INFO), himl, TVSIL_NORMAL); - - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); - WindowList_Add(hUserInfoList, hwndDlg, dat->hContact); - } - break; - - case WM_JABBER_REFRESH: - if (!dat) break; - - if (!dat->item) { - ptrT jid( dat->ppro->getTStringA(dat->hContact, "jid")); - if (jid == NULL) - break; - - if (!(dat->item = dat->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid))) - dat->item = dat->ppro->ListGetItemPtr(LIST_ROSTER, jid); - - if (!dat->item) { - HWND hwndTree = GetDlgItem(hwndDlg, IDC_TV_INFO); - TreeView_DeleteAllItems(hwndTree); - HTREEITEM htiRoot = sttFillInfoLine(hwndTree, NULL, dat->ppro->LoadIconEx("main"), _T("JID"), jid, sttInfoLineId(0, INFOLINE_NAME), true); - sttFillInfoLine(hwndTree, htiRoot, dat->ppro->LoadIconEx("vcard"), NULL, - TranslateT("Please switch online to see more details.")); - break; - } - } - sttFillUserInfo(dat->ppro, GetDlgItem(hwndDlg, IDC_TV_INFO), dat->item); - break; - - case WM_SIZE: - MoveWindow(GetDlgItem(hwndDlg, IDC_TV_INFO), 5, 5, LOWORD(lParam)-10, HIWORD(lParam)-10, TRUE); - break; - - case WM_CONTEXTMENU: - if (GetWindowLongPtr((HWND)wParam, GWL_ID) == IDC_TV_INFO) { - HWND hwndTree = GetDlgItem(hwndDlg, IDC_TV_INFO); - POINT pt = { (signed short)LOWORD(lParam), (signed short)HIWORD(lParam) }; - HTREEITEM hItem = 0; - - if ((pt.x == -1) && (pt.y == -1)) { - if (hItem = TreeView_GetSelection(hwndTree)) { - RECT rc; - TreeView_GetItemRect(hwndTree, hItem, &rc, TRUE); - pt.x = rc.left; - pt.y = rc.bottom; - ClientToScreen(hwndTree, &pt); - } - } - else { - TVHITTESTINFO tvhti = {0}; - tvhti.pt = pt; - ScreenToClient(hwndTree, &tvhti.pt); - TreeView_HitTest(hwndTree, &tvhti); - if (tvhti.flags & TVHT_ONITEM) { - hItem = tvhti.hItem; - TreeView_Select(hwndTree, hItem, TVGN_CARET); - } } - - if (hItem) { - HMENU hMenu = CreatePopupMenu(); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy")); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy only this value")); - AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); - int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL); - if (nReturnCmd == 1) { - UserInfoStringBuf buf; - sttGetNodeText(hwndTree, hItem, &buf); - JabberCopyText(hwndDlg, buf.buf); - } - else if (nReturnCmd == 2) { - TCHAR szBuffer[ 1024 ]; - TVITEMEX tvi = {0}; - tvi.mask = TVIF_HANDLE|TVIF_TEXT|TVIF_STATE; - tvi.hItem = hItem; - tvi.cchTextMax = SIZEOF(szBuffer); - tvi.pszText = szBuffer; - if (TreeView_GetItem(hwndTree, &tvi)) { - if (TCHAR *str = _tcsstr(szBuffer, _T(": "))) - JabberCopyText(hwndDlg, str+2); - else - JabberCopyText(hwndDlg, szBuffer); - } } - DestroyMenu(hMenu); - } } - break; - - case WM_NOTIFY: - if (((LPNMHDR)lParam)->idFrom == 0) { - switch (((LPNMHDR)lParam)->code) { - case PSN_INFOCHANGED: - { - MCONTACT hContact = (MCONTACT)((LPPSHNOTIFY)lParam)->lParam; - SendMessage(hwndDlg, WM_JABBER_REFRESH, 0, hContact); - } - break; - - case PSN_PARAMCHANGED: - dat->ppro = (CJabberProto*)((PSHNOTIFY*)lParam)->lParam; - if (dat->hContact != NULL) { - ptrT jid( dat->ppro->getTStringA(dat->hContact, "jid")); - if (jid != NULL) - if (!(dat->item = dat->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid))) - dat->item = dat->ppro->ListGetItemPtr(LIST_ROSTER, jid); - } - break; - } } - break; - - case WM_CLOSE: - DestroyWindow(hwndDlg); - break; - - case WM_DESTROY: - WindowList_Remove(hUserInfoList, hwndDlg); - if (dat) { - mir_free(dat); - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); - } - ImageList_Destroy(TreeView_SetImageList(GetDlgItem(hwndDlg, IDC_TV_INFO), NULL, TVSIL_NORMAL)); - WindowFreeIcon(hwndDlg); - break; - } - return FALSE; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// JabberUserPhotoDlgProc - Jabber photo dialog - -struct USER_PHOTO_INFO -{ - MCONTACT hContact; - HBITMAP hBitmap; - CJabberProto *ppro; -}; - -static INT_PTR CALLBACK JabberUserPhotoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - USER_PHOTO_INFO *photoInfo; - - photoInfo = (USER_PHOTO_INFO *) GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - - switch (msg) { - case WM_INITDIALOG: - // lParam is hContact - TranslateDialogDefault(hwndDlg); - photoInfo = (USER_PHOTO_INFO *) mir_alloc(sizeof(USER_PHOTO_INFO)); - photoInfo->hContact = lParam; - photoInfo->ppro = NULL; - photoInfo->hBitmap = NULL; - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR) photoInfo); - SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadImage(hInst, MAKEINTRESOURCE(IDI_SAVE), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0)); - SendDlgItemMessage(hwndDlg, IDC_SAVE, BUTTONSETASFLATBTN, TRUE, 0); - ShowWindow(GetDlgItem(hwndDlg, IDC_LOAD), SW_HIDE); - ShowWindow(GetDlgItem(hwndDlg, IDC_DELETE), SW_HIDE); - break; - - case WM_NOTIFY: - switch (((LPNMHDR)lParam)->idFrom) { - case 0: - switch (((LPNMHDR)lParam)->code) { - case PSN_INFOCHANGED: - SendMessage(hwndDlg, WM_JABBER_REFRESH, 0, 0); - break; - - case PSN_PARAMCHANGED: - photoInfo->ppro = (CJabberProto*)((PSHNOTIFY*)lParam)->lParam; - break; - } - break; - } - break; - - case WM_JABBER_REFRESH: - { - if (photoInfo->hBitmap) { - DeleteObject(photoInfo->hBitmap); - photoInfo->hBitmap = NULL; - } - ShowWindow(GetDlgItem(hwndDlg, IDC_SAVE), SW_HIDE); - ptrT jid( photoInfo->ppro->getTStringA(photoInfo->hContact, "jid")); - if (jid != NULL) { - JABBER_LIST_ITEM *item = photoInfo->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid); - if (item == NULL) - item = photoInfo->ppro->ListGetItemPtr(LIST_ROSTER, jid); - if (item != NULL) { - if (item->photoFileName) { - photoInfo->ppro->debugLog(_T("Showing picture from %s"), item->photoFileName); - photoInfo->hBitmap = (HBITMAP) CallService(MS_UTILS_LOADBITMAPT, 0, (LPARAM)item->photoFileName); - FIP->FI_Premultiply(photoInfo->hBitmap); - ShowWindow(GetDlgItem(hwndDlg, IDC_SAVE), SW_SHOW); - } - } - } - InvalidateRect(hwndDlg, NULL, TRUE); - UpdateWindow(hwndDlg); - } - break; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_SAVE: - static TCHAR szFilter[512]; - - ptrT jid(photoInfo->ppro->getTStringA(photoInfo->hContact, "jid")); - if (jid == NULL) - break; - - JABBER_LIST_ITEM *item = photoInfo->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid); - if (item == NULL) - if ((item = photoInfo->ppro->ListGetItemPtr(LIST_ROSTER, jid)) == NULL) - break; - - switch (ProtoGetAvatarFileFormat(item->photoFileName)) { - case PA_FORMAT_BMP: - mir_sntprintf(szFilter, SIZEOF(szFilter), _T("BMP %s (*.bmp)%c*.BMP"), TranslateT("format"), 0); - break; - - case PA_FORMAT_GIF: - mir_sntprintf(szFilter, SIZEOF(szFilter), _T("GIF %s (*.gif)%c*.GIF"), TranslateT("format"), 0); - break; - - case PA_FORMAT_JPEG: - mir_sntprintf(szFilter, SIZEOF(szFilter), _T("JPEG %s (*.jpg;*.jpeg)%c*.JPG;*.JPEG"), TranslateT("format"), 0); - break; - - default: - mir_sntprintf(szFilter, SIZEOF(szFilter), _T("%s (*.*)%c*.*"), TranslateT("Unknown format"), 0); - } - - TCHAR szFileName[MAX_PATH]; szFileName[0] = '\0'; - OPENFILENAME ofn = { 0 }; - ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; - ofn.hwndOwner = hwndDlg; - ofn.lpstrFilter = szFilter; - ofn.lpstrFile = szFileName; - ofn.nMaxFile = _MAX_PATH; - ofn.Flags = OFN_OVERWRITEPROMPT; - if ( GetSaveFileName(&ofn)) { - photoInfo->ppro->debugLog(_T("File selected is %s"), szFileName); - CopyFile(item->photoFileName, szFileName, FALSE); - } - } - break; - - case WM_PAINT: - if (!photoInfo->ppro->m_bJabberOnline) - SetDlgItemText(hwndDlg, IDC_CANVAS, TranslateT("")); - else if (!photoInfo->hBitmap) - SetDlgItemText(hwndDlg, IDC_CANVAS, TranslateT("")); - else { - BITMAP bm; - POINT ptSize, ptOrg, pt, ptFitSize; - RECT rect; - - SetDlgItemTextA(hwndDlg, IDC_CANVAS, ""); - HBITMAP hBitmap = photoInfo->hBitmap; - HWND hwndCanvas = GetDlgItem(hwndDlg, IDC_CANVAS); - HDC hdcCanvas = GetDC(hwndCanvas); - HDC hdcMem = CreateCompatibleDC(hdcCanvas); - SelectObject(hdcMem, hBitmap); - SetMapMode(hdcMem, GetMapMode(hdcCanvas)); - GetObject(hBitmap, sizeof(BITMAP), (LPVOID) &bm); - ptSize.x = bm.bmWidth; - ptSize.y = bm.bmHeight; - DPtoLP(hdcCanvas, &ptSize, 1); - ptOrg.x = ptOrg.y = 0; - DPtoLP(hdcMem, &ptOrg, 1); - GetClientRect(hwndCanvas, &rect); - InvalidateRect(hwndCanvas, NULL, TRUE); - UpdateWindow(hwndCanvas); - if (ptSize.x<=rect.right && ptSize.y<=rect.bottom) { - pt.x = (rect.right - ptSize.x)/2; - pt.y = (rect.bottom - ptSize.y)/2; - ptFitSize = ptSize; - } - else { - if (((float)(ptSize.x-rect.right))/ptSize.x > ((float)(ptSize.y-rect.bottom))/ptSize.y) { - ptFitSize.x = rect.right; - ptFitSize.y = (ptSize.y*rect.right)/ptSize.x; - pt.x = 0; - pt.y = (rect.bottom - ptFitSize.y)/2; - } - else { - ptFitSize.x = (ptSize.x*rect.bottom)/ptSize.y; - ptFitSize.y = rect.bottom; - pt.x = (rect.right - ptFitSize.x)/2; - pt.y = 0; - } - } - - if (IsThemeActive()) { - RECT rc; GetClientRect(hwndCanvas, &rc); - DrawThemeParentBackground(hwndCanvas, hdcCanvas, &rc); - } - else { - RECT rc; GetClientRect(hwndCanvas, &rc); - FillRect(hdcCanvas, &rc, (HBRUSH)GetSysColorBrush(COLOR_BTNFACE)); - } - - if (bm.bmBitsPixel == 32) { - BLENDFUNCTION bf = {0}; - bf.AlphaFormat = AC_SRC_ALPHA; - bf.BlendOp = AC_SRC_OVER; - bf.SourceConstantAlpha = 255; - GdiAlphaBlend(hdcCanvas, pt.x, pt.y, ptFitSize.x, ptFitSize.y, hdcMem, ptOrg.x, ptOrg.y, ptSize.x, ptSize.y, bf); - } - else { - SetStretchBltMode(hdcCanvas, COLORONCOLOR); - StretchBlt(hdcCanvas, pt.x, pt.y, ptFitSize.x, ptFitSize.y, hdcMem, ptOrg.x, ptOrg.y, ptSize.x, ptSize.y, SRCCOPY); - } - - DeleteDC(hdcMem); - } - break; - - case WM_DESTROY: - DestroyIcon((HICON)SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, 0)); - if (photoInfo->hBitmap) { - photoInfo->ppro->debugLogA("Delete bitmap"); - DeleteObject(photoInfo->hBitmap); - } - if (photoInfo) mir_free(photoInfo); - break; - } - return FALSE; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// OnInfoInit - initializes user info option dialogs - -int CJabberProto::OnUserInfoInit(WPARAM wParam, LPARAM lParam) -{ - if (!CallService(MS_PROTO_ISPROTOCOLLOADED, 0, (LPARAM)m_szModuleName)) - return 0; - - MCONTACT hContact = lParam; - if (hContact == NULL) { - // Show our vcard - OnUserInfoInit_VCard(wParam, lParam); - return 0; - } - - char *szProto = GetContactProto(hContact); - if (szProto != NULL && !strcmp(szProto, m_szModuleName)) { - OPTIONSDIALOGPAGE odp = { sizeof(odp) }; - odp.hInstance = hInst; - odp.dwInitParam = (LPARAM)this; - - odp.pfnDlgProc = JabberUserInfoDlgProc; - odp.position = -2000000000; - odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_JABBER); - odp.pszTitle = LPGEN("Account"); - UserInfo_AddPage(wParam, &odp); - - odp.pfnDlgProc = JabberUserPhotoDlgProc; - odp.position = 2000000000; - odp.pszTemplate = MAKEINTRESOURCEA(IDD_VCARD_PHOTO); - odp.pszTitle = LPGEN("Photo"); - UserInfo_AddPage(wParam, &odp); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// JabberUserInfoUpdate - -void JabberUserInfoInit() -{ - hUserInfoList = WindowList_Create(); -} - -void JabberUserInfoUninit() -{ - WindowList_Destroy(hUserInfoList); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// JabberUserInfoUpdate - -void JabberUserInfoUpdate(MCONTACT hContact) -{ - if (!hContact) - WindowList_BroadcastAsync(hUserInfoList, WM_JABBER_REFRESH, 0, 0); - else if (HWND hwnd = WindowList_Find(hUserInfoList, hContact)) - PostMessage(hwnd, WM_JABBER_REFRESH, 0, 0); -} +/* + +Jabber Protocol Plugin for Miranda NG + +Copyright (c) 2002-04 Santithorn Bunchua +Copyright (c) 2005-12 George Hazan +Copyright (c) 2007 Maxim Mluhov +Copyright (c) 2012-14 Miranda NG project + +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 +#include +#include + +#include "jabber_list.h" + +static HANDLE hUserInfoList = NULL; + +struct UserInfoStringBuf +{ + enum { STRINGBUF_INCREMENT = 1024 }; + + TCHAR *buf; + int size; + int offset; + + UserInfoStringBuf() { buf = 0; size = 0; offset = 0; } + ~UserInfoStringBuf() { mir_free(buf); } + + void append(TCHAR *str) { + if (!str) return; + + int length = lstrlen(str); + if (size - offset < length + 1) { + size += (length + STRINGBUF_INCREMENT); + buf = (TCHAR *)mir_realloc(buf, size * sizeof(TCHAR)); + } + lstrcpy(buf + offset, str); + offset += length; + } + + TCHAR *allocate(int length) { + if (size - offset < length) { + size += (length + STRINGBUF_INCREMENT); + buf = (TCHAR *)mir_realloc(buf, size * sizeof(TCHAR)); + } + return buf + offset; + } + + void actualize() { + if (buf) offset = lstrlen(buf); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// JabberUserInfoDlgProc - main user info dialog + +struct JabberUserInfoDlgData +{ + MCONTACT hContact; + CJabberProto *ppro; + JABBER_LIST_ITEM *item; + int resourcesCount; +}; + +enum +{ + INFOLINE_DELETE = 0x80000000, + INFOLINE_MASK = 0x7fffffff, + INFOLINE_BAD_ID = 0x7fffffff, + + INFOLINE_NAME = 1, + INFOLINE_MOOD, + INFOLINE_ACTIVITY, + INFOLINE_TUNE, + INFOLINE_OFFLINE, + INFOLINE_MESSAGE, + INFOLINE_SOFTWARE, + INFOLINE_VERSION, + INFOLINE_SYSTEM, + INFOLINE_PRIORITY, + INFOLINE_IDLE, + INFOLINE_CAPS, + INFOLINE_SOFTWARE_INFORMATION, + INFOLINE_SUBSCRIPTION, + INFOLINE_LOGOFF, + INFOLINE_LOGOFF_MSG, + INFOLINE_LASTACTIVE, +}; + +__forceinline DWORD sttInfoLineId(DWORD res, DWORD type, DWORD line=0) +{ + return + (type << 24) & 0x7f000000 | + (res << 12) & 0x00fff000 | + (line ) & 0x00000fff; +} + +static HTREEITEM sttFindInfoLine(HWND hwndTree, HTREEITEM htiRoot, LPARAM id=INFOLINE_BAD_ID) +{ + if (id == INFOLINE_BAD_ID) return NULL; + for (HTREEITEM hti = TreeView_GetChild(hwndTree, htiRoot); hti; hti = TreeView_GetNextSibling(hwndTree, hti)) + { + TVITEMEX tvi = {0}; + tvi.mask = TVIF_HANDLE|TVIF_PARAM; + tvi.hItem = hti; + TreeView_GetItem(hwndTree, &tvi); + if ((tvi.lParam&INFOLINE_MASK) == (id&INFOLINE_MASK)) + return hti; + } + return NULL; +} + +void sttCleanupInfo(HWND hwndTree, int stage) +{ + HTREEITEM hItem = TreeView_GetRoot(hwndTree); + while (hItem) { + TVITEMEX tvi = {0}; + tvi.mask = TVIF_HANDLE|TVIF_PARAM; + tvi.hItem = hItem; + TreeView_GetItem(hwndTree, &tvi); + + switch (stage) { + case 0: + tvi.lParam |= INFOLINE_DELETE; + TreeView_SetItem(hwndTree, &tvi); + break; + + case 1: + if (tvi.lParam & INFOLINE_DELETE) { + hItem = TreeView_GetNextSibling(hwndTree, hItem); + TreeView_DeleteItem(hwndTree, tvi.hItem); + continue; + } + break; + } + + HTREEITEM hItemTmp = 0; + if (hItemTmp = TreeView_GetChild(hwndTree, hItem)) + hItem = hItemTmp; + else if (hItemTmp = TreeView_GetNextSibling(hwndTree, hItem)) + hItem = hItemTmp; + else { + while (1) { + if (!(hItem = TreeView_GetParent(hwndTree, hItem))) break; + if (hItemTmp = TreeView_GetNextSibling(hwndTree, hItem)) { + hItem = hItemTmp; + break; + } + } + } + } +} + +static HTREEITEM sttFillInfoLine(HWND hwndTree, HTREEITEM htiRoot, HICON hIcon, TCHAR *title, TCHAR *value, LPARAM id=INFOLINE_BAD_ID, bool expand=false) +{ + HTREEITEM hti = sttFindInfoLine(hwndTree, htiRoot, id); + + TCHAR buf[256]; + if (title) + mir_sntprintf(buf, SIZEOF(buf), _T("%s: %s"), title, value); + else + lstrcpyn(buf, value, SIZEOF(buf)); + + TVINSERTSTRUCT tvis = {0}; + tvis.hParent = htiRoot; + tvis.hInsertAfter = TVI_LAST; + tvis.itemex.mask = TVIF_TEXT|TVIF_PARAM; + tvis.itemex.pszText = buf; + tvis.itemex.lParam = id; + + if (hIcon) { + HIMAGELIST himl = TreeView_GetImageList(hwndTree, TVSIL_NORMAL); + tvis.itemex.mask |= TVIF_IMAGE|TVIF_SELECTEDIMAGE; + tvis.itemex.iImage = + tvis.itemex.iSelectedImage = ImageList_AddIcon(himl, hIcon); + g_ReleaseIcon(hIcon); + } + + if (hti) { + tvis.itemex.mask |= TVIF_HANDLE; + tvis.itemex.hItem = hti; + TreeView_SetItem(hwndTree, &tvis.itemex); + } + else { + tvis.itemex.mask |= TVIF_STATE; + tvis.itemex.stateMask = TVIS_EXPANDED; + tvis.itemex.state = expand ? TVIS_EXPANDED : 0; + hti = TreeView_InsertItem(hwndTree, &tvis); + } + + return hti; +} + +static void sttFillResourceInfo(CJabberProto *ppro, HWND hwndTree, HTREEITEM htiRoot, JABBER_LIST_ITEM *item, int resource) +{ + TCHAR buf[256]; + HTREEITEM htiResource = htiRoot; + pResourceStatus r = resource ? item->arResources[resource-1] : item->getTemp(); + + if (r->m_tszResourceName && *r->m_tszResourceName) + htiResource = sttFillInfoLine(hwndTree, htiRoot, LoadSkinnedProtoIcon(ppro->m_szModuleName, r->m_iStatus), + TranslateT("Resource"), r->m_tszResourceName, sttInfoLineId(resource, INFOLINE_NAME), true); + + // StatusMsg + sttFillInfoLine(hwndTree, htiResource, NULL /*LoadSkinnedIcon(SKINICON_EVENT_MESSAGE)*/, + TranslateT("Message"), r->m_tszStatusMessage ? r->m_tszStatusMessage : TranslateT(""), + sttInfoLineId(resource, INFOLINE_MESSAGE)); + + // Software + HICON hIcon = NULL; + if ( ServiceExists(MS_FP_GETCLIENTICONT)) { + if (r->m_tszSoftware != NULL) { + mir_sntprintf(buf, SIZEOF(buf), _T("%s %s"), r->m_tszSoftware, r->m_tszSoftwareVersion); + hIcon = Finger_GetClientIcon(buf, 0); + } + } + + sttFillInfoLine(hwndTree, htiResource, hIcon, TranslateT("Software"), + r->m_tszSoftware ? r->m_tszSoftware : TranslateT(""), + sttInfoLineId(resource, INFOLINE_SOFTWARE)); + + if (hIcon) + DestroyIcon(hIcon); + + // Version + sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Version"), + r->m_tszSoftwareVersion ? r->m_tszSoftwareVersion : TranslateT(""), + sttInfoLineId(resource, INFOLINE_VERSION)); + + // System + sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("System"), + r->m_tszOs ? r->m_tszOs : TranslateT(""), + sttInfoLineId(resource, INFOLINE_SYSTEM)); + + // Resource priority + TCHAR szPriority[128]; + mir_sntprintf(szPriority, SIZEOF(szPriority), _T("%d"), (int)r->m_iPriority); + sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Resource priority"), szPriority, sttInfoLineId(resource, INFOLINE_PRIORITY)); + + // Idle + if (r->m_dwIdleStartTime > 0) { + lstrcpyn(buf, _tctime(&r->m_dwIdleStartTime), SIZEOF(buf)); + int len = lstrlen(buf); + if (len > 0) buf[len-1] = 0; + } + else if (!r->m_dwIdleStartTime) + lstrcpyn(buf, TranslateT("unknown"), SIZEOF(buf)); + else + lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); + + sttFillInfoLine(hwndTree, htiResource, NULL, TranslateT("Idle since"), buf, sttInfoLineId(resource, INFOLINE_IDLE)); + + // caps + mir_sntprintf(buf, SIZEOF(buf), _T("%s/%s"), item->jid, r->m_tszResourceName); + JabberCapsBits jcb = ppro->GetResourceCapabilites(buf, TRUE); + + if (!(jcb & JABBER_RESOURCE_CAPS_ERROR)) { + HTREEITEM htiCaps = sttFillInfoLine(hwndTree, htiResource, ppro->LoadIconEx("main"), NULL, TranslateT("Client capabilities"), sttInfoLineId(resource, INFOLINE_CAPS)); + int i; + for (i=0; g_JabberFeatCapPairs[i].szFeature; i++) + if (jcb & g_JabberFeatCapPairs[i].jcbCap) { + TCHAR szDescription[ 1024 ]; + if (g_JabberFeatCapPairs[i].tszDescription) + mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(g_JabberFeatCapPairs[i].tszDescription), g_JabberFeatCapPairs[i].szFeature); + else + mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s"), g_JabberFeatCapPairs[i].szFeature); + sttFillInfoLine(hwndTree, htiCaps, NULL, NULL, szDescription, sttInfoLineId(resource, INFOLINE_CAPS, i)); + } + + for (int j = 0; j < ppro->m_lstJabberFeatCapPairsDynamic.getCount(); j++, i++) + if (jcb & ppro->m_lstJabberFeatCapPairsDynamic[j]->jcbCap) { + TCHAR szDescription[ 1024 ]; + if (ppro->m_lstJabberFeatCapPairsDynamic[j]->szDescription) + mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(ppro->m_lstJabberFeatCapPairsDynamic[j]->szDescription), ppro->m_lstJabberFeatCapPairsDynamic[j]->szFeature); + else + mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s"), ppro->m_lstJabberFeatCapPairsDynamic[j]->szFeature); + sttFillInfoLine(hwndTree, htiCaps, NULL, NULL, szDescription, sttInfoLineId(resource, INFOLINE_CAPS, i)); + } + } + + // Software info + HTREEITEM htiSoftwareInfo = sttFillInfoLine(hwndTree, htiResource, ppro->LoadIconEx("main"), NULL, TranslateT("Software information"), sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION)); + int nLineId = 0; + if (r->m_tszOs) + sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Operating system"), r->m_tszOs, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); + if (r->m_tszOsVersion) + sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Operating system version"), r->m_tszOsVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); + if (r->m_tszSoftware) + sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Software"), r->m_tszSoftware, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); + if (r->m_tszSoftwareVersion) + sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Software version"), r->m_tszSoftwareVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); + if (r->m_tszXMirandaCoreVersion) + sttFillInfoLine(hwndTree, htiSoftwareInfo, NULL, TranslateT("Miranda core version"), r->m_tszXMirandaCoreVersion, sttInfoLineId(resource, INFOLINE_SOFTWARE_INFORMATION, nLineId++)); +} + +static void sttFillAdvStatusInfo(CJabberProto *ppro, HWND hwndTree, HTREEITEM htiRoot, DWORD dwInfoLine, MCONTACT hContact, TCHAR *szTitle, char *pszSlot) +{ + char *szAdvStatusIcon = ppro->ReadAdvStatusA(hContact, pszSlot, ADVSTATUS_VAL_ICON); + TCHAR *szAdvStatusTitle = ppro->ReadAdvStatusT(hContact, pszSlot, ADVSTATUS_VAL_TITLE); + TCHAR *szAdvStatusText = ppro->ReadAdvStatusT(hContact, pszSlot, ADVSTATUS_VAL_TEXT); + + if (szAdvStatusIcon && szAdvStatusTitle && *szAdvStatusTitle) { + TCHAR szText[2048]; + if (szAdvStatusText && *szAdvStatusText) + mir_sntprintf(szText, 2047, _T("%s (%s)"), TranslateTS(szAdvStatusTitle), szAdvStatusText); + else + mir_sntprintf(szText, 2047, _T("%s"), TranslateTS(szAdvStatusTitle)); + sttFillInfoLine(hwndTree, htiRoot, Skin_GetIcon(szAdvStatusIcon), szTitle, szText, dwInfoLine); + } + + mir_free(szAdvStatusIcon); + mir_free(szAdvStatusTitle); + mir_free(szAdvStatusText); +} + +static void sttFillUserInfo(CJabberProto *ppro, HWND hwndTree, JABBER_LIST_ITEM *item) +{ + SendMessage(hwndTree, WM_SETREDRAW, FALSE, 0); + + sttCleanupInfo(hwndTree, 0); + + HTREEITEM htiRoot = sttFillInfoLine(hwndTree, NULL, ppro->LoadIconEx("main"), _T("JID"), item->jid, sttInfoLineId(0, INFOLINE_NAME), true); + TCHAR buf[256]; + + if (MCONTACT hContact = ppro->HContactFromJID(item->jid)) { + sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_MOOD), hContact, TranslateT("Mood"), ADVSTATUS_MOOD); + sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_ACTIVITY), hContact, TranslateT("Activity"), ADVSTATUS_ACTIVITY); + sttFillAdvStatusInfo(ppro, hwndTree, htiRoot, sttInfoLineId(0, INFOLINE_TUNE), hContact, TranslateT("Tune"), ADVSTATUS_TUNE); + } + + // subscription + switch (item->subscription) { + case SUB_BOTH: + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("both"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); + break; + case SUB_TO: + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("to"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); + break; + case SUB_FROM: + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("from"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); + break; + default: + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Subscription"), TranslateT("none"), sttInfoLineId(0, INFOLINE_SUBSCRIPTION)); + break; + } + + // logoff + JABBER_RESOURCE_STATUS *r = item->getTemp(); + if (r->m_dwIdleStartTime > 0) { + lstrcpyn(buf, _tctime(&r->m_dwIdleStartTime), SIZEOF(buf)); + int len = lstrlen(buf); + if (len > 0) buf[len-1] = 0; + } + else if (!r->m_dwIdleStartTime) + lstrcpyn(buf, TranslateT("unknown"), SIZEOF(buf)); + else + lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); + + sttFillInfoLine(hwndTree, htiRoot, NULL, + (item->jid && _tcschr(item->jid, _T('@'))) ? TranslateT("Last logoff time") : TranslateT("Uptime"), buf, + sttInfoLineId(0, INFOLINE_LOGOFF)); + + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Logoff message"), + r->m_tszStatusMessage ? r->m_tszStatusMessage : TranslateT(""), sttInfoLineId(0, INFOLINE_LOGOFF_MSG)); + + // activity + if (item->m_pLastSeenResource) + lstrcpyn(buf, item->m_pLastSeenResource->m_tszResourceName, SIZEOF(buf)); + else + lstrcpyn(buf, TranslateT(""), SIZEOF(buf)); + + sttFillInfoLine(hwndTree, htiRoot, NULL, TranslateT("Last active resource"), buf, + sttInfoLineId(0, INFOLINE_LASTACTIVE)); + + // resources + if (item->arResources.getCount()) { + for (int i=0; i < item->arResources.getCount(); i++) + sttFillResourceInfo(ppro, hwndTree, htiRoot, item, i+1); + } + else if (!_tcschr(item->jid, _T('@')) || (r->m_iStatus != ID_STATUS_OFFLINE)) + sttFillResourceInfo(ppro, hwndTree, htiRoot, item, 0); + + sttCleanupInfo(hwndTree, 1); + SendMessage(hwndTree, WM_SETREDRAW, TRUE, 0); + + RedrawWindow(hwndTree, NULL, NULL, RDW_INVALIDATE); +} + +static void sttGetNodeText(HWND hwndTree, HTREEITEM hti, UserInfoStringBuf *buf, int indent = 0) +{ + for (int i=0; i < indent; i++) + buf->append(_T("\t")); + + TVITEMEX tvi = {0}; + tvi.mask = TVIF_HANDLE|TVIF_TEXT|TVIF_STATE; + tvi.hItem = hti; + tvi.cchTextMax = 256; + tvi.pszText = buf->allocate(tvi.cchTextMax); + if (!TreeView_GetItem(hwndTree, &tvi)) { // failure, maybe item was removed... + buf->buf[ buf->offset ] = 0; + buf->actualize(); + return; + } + + buf->actualize(); + buf->append(_T("\r\n")); + + if (tvi.state & TVIS_EXPANDED) + for (hti = TreeView_GetChild(hwndTree, hti); hti; hti = TreeView_GetNextSibling(hwndTree, hti)) + sttGetNodeText(hwndTree, hti, buf, indent + 1); +} + +static INT_PTR CALLBACK JabberUserInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + JabberUserInfoDlgData *dat = (JabberUserInfoDlgData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + // lParam is hContact + TranslateDialogDefault(hwndDlg); + + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadSkinnedIconBig(SKINICON_OTHER_USERDETAILS)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadSkinnedIcon(SKINICON_OTHER_USERDETAILS)); + + dat = (JabberUserInfoDlgData *)mir_alloc(sizeof(JabberUserInfoDlgData)); + ZeroMemory(dat, sizeof(JabberUserInfoDlgData)); + dat->resourcesCount = -1; + + if (CallService(MS_DB_CONTACT_IS, (WPARAM)lParam, 0)) + dat->hContact = lParam; + else if (!IsBadReadPtr((void*)lParam, sizeof(JABBER_LIST_ITEM))) { + dat->hContact = NULL; + dat->item = (JABBER_LIST_ITEM *)lParam; + } + + { + RECT rc; GetClientRect(hwndDlg, &rc); + MoveWindow(GetDlgItem(hwndDlg, IDC_TV_INFO), 5, 5, rc.right-10, rc.bottom-10, TRUE); + + HIMAGELIST himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR|ILC_COLOR32|ILC_MASK, 5, 1); + ImageList_AddIcon_Icolib(himl, LoadSkinnedIcon(SKINICON_OTHER_SMALLDOT)); + TreeView_SetImageList(GetDlgItem(hwndDlg, IDC_TV_INFO), himl, TVSIL_NORMAL); + + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + WindowList_Add(hUserInfoList, hwndDlg, dat->hContact); + } + break; + + case WM_JABBER_REFRESH: + if (!dat) break; + + if (!dat->item) { + ptrT jid( dat->ppro->getTStringA(dat->hContact, "jid")); + if (jid == NULL) + break; + + if (!(dat->item = dat->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid))) + dat->item = dat->ppro->ListGetItemPtr(LIST_ROSTER, jid); + + if (!dat->item) { + HWND hwndTree = GetDlgItem(hwndDlg, IDC_TV_INFO); + TreeView_DeleteAllItems(hwndTree); + HTREEITEM htiRoot = sttFillInfoLine(hwndTree, NULL, dat->ppro->LoadIconEx("main"), _T("JID"), jid, sttInfoLineId(0, INFOLINE_NAME), true); + sttFillInfoLine(hwndTree, htiRoot, dat->ppro->LoadIconEx("vcard"), NULL, + TranslateT("Please switch online to see more details.")); + break; + } + } + sttFillUserInfo(dat->ppro, GetDlgItem(hwndDlg, IDC_TV_INFO), dat->item); + break; + + case WM_SIZE: + MoveWindow(GetDlgItem(hwndDlg, IDC_TV_INFO), 5, 5, LOWORD(lParam)-10, HIWORD(lParam)-10, TRUE); + break; + + case WM_CONTEXTMENU: + if (GetWindowLongPtr((HWND)wParam, GWL_ID) == IDC_TV_INFO) { + HWND hwndTree = GetDlgItem(hwndDlg, IDC_TV_INFO); + POINT pt = { (signed short)LOWORD(lParam), (signed short)HIWORD(lParam) }; + HTREEITEM hItem = 0; + + if ((pt.x == -1) && (pt.y == -1)) { + if (hItem = TreeView_GetSelection(hwndTree)) { + RECT rc; + TreeView_GetItemRect(hwndTree, hItem, &rc, TRUE); + pt.x = rc.left; + pt.y = rc.bottom; + ClientToScreen(hwndTree, &pt); + } + } + else { + TVHITTESTINFO tvhti = {0}; + tvhti.pt = pt; + ScreenToClient(hwndTree, &tvhti.pt); + TreeView_HitTest(hwndTree, &tvhti); + if (tvhti.flags & TVHT_ONITEM) { + hItem = tvhti.hItem; + TreeView_Select(hwndTree, hItem, TVGN_CARET); + } } + + if (hItem) { + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy")); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy only this value")); + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); + int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL); + if (nReturnCmd == 1) { + UserInfoStringBuf buf; + sttGetNodeText(hwndTree, hItem, &buf); + JabberCopyText(hwndDlg, buf.buf); + } + else if (nReturnCmd == 2) { + TCHAR szBuffer[ 1024 ]; + TVITEMEX tvi = {0}; + tvi.mask = TVIF_HANDLE|TVIF_TEXT|TVIF_STATE; + tvi.hItem = hItem; + tvi.cchTextMax = SIZEOF(szBuffer); + tvi.pszText = szBuffer; + if (TreeView_GetItem(hwndTree, &tvi)) { + if (TCHAR *str = _tcsstr(szBuffer, _T(": "))) + JabberCopyText(hwndDlg, str+2); + else + JabberCopyText(hwndDlg, szBuffer); + } } + DestroyMenu(hMenu); + } } + break; + + case WM_NOTIFY: + if (((LPNMHDR)lParam)->idFrom == 0) { + switch (((LPNMHDR)lParam)->code) { + case PSN_INFOCHANGED: + { + MCONTACT hContact = (MCONTACT)((LPPSHNOTIFY)lParam)->lParam; + SendMessage(hwndDlg, WM_JABBER_REFRESH, 0, hContact); + } + break; + + case PSN_PARAMCHANGED: + dat->ppro = (CJabberProto*)((PSHNOTIFY*)lParam)->lParam; + if (dat->hContact != NULL) { + ptrT jid( dat->ppro->getTStringA(dat->hContact, "jid")); + if (jid != NULL) + if (!(dat->item = dat->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid))) + dat->item = dat->ppro->ListGetItemPtr(LIST_ROSTER, jid); + } + break; + } } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + WindowList_Remove(hUserInfoList, hwndDlg); + if (dat) { + mir_free(dat); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + } + ImageList_Destroy(TreeView_SetImageList(GetDlgItem(hwndDlg, IDC_TV_INFO), NULL, TVSIL_NORMAL)); + WindowFreeIcon(hwndDlg); + break; + } + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// JabberUserPhotoDlgProc - Jabber photo dialog + +struct USER_PHOTO_INFO +{ + MCONTACT hContact; + HBITMAP hBitmap; + CJabberProto *ppro; +}; + +static INT_PTR CALLBACK JabberUserPhotoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + USER_PHOTO_INFO *photoInfo; + + photoInfo = (USER_PHOTO_INFO *) GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + // lParam is hContact + TranslateDialogDefault(hwndDlg); + photoInfo = (USER_PHOTO_INFO *) mir_alloc(sizeof(USER_PHOTO_INFO)); + photoInfo->hContact = lParam; + photoInfo->ppro = NULL; + photoInfo->hBitmap = NULL; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR) photoInfo); + SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadImage(hInst, MAKEINTRESOURCE(IDI_SAVE), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0)); + SendDlgItemMessage(hwndDlg, IDC_SAVE, BUTTONSETASFLATBTN, TRUE, 0); + ShowWindow(GetDlgItem(hwndDlg, IDC_LOAD), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_DELETE), SW_HIDE); + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_INFOCHANGED: + SendMessage(hwndDlg, WM_JABBER_REFRESH, 0, 0); + break; + + case PSN_PARAMCHANGED: + photoInfo->ppro = (CJabberProto*)((PSHNOTIFY*)lParam)->lParam; + break; + } + break; + } + break; + + case WM_JABBER_REFRESH: + { + if (photoInfo->hBitmap) { + DeleteObject(photoInfo->hBitmap); + photoInfo->hBitmap = NULL; + } + ShowWindow(GetDlgItem(hwndDlg, IDC_SAVE), SW_HIDE); + ptrT jid( photoInfo->ppro->getTStringA(photoInfo->hContact, "jid")); + if (jid != NULL) { + JABBER_LIST_ITEM *item = photoInfo->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid); + if (item == NULL) + item = photoInfo->ppro->ListGetItemPtr(LIST_ROSTER, jid); + if (item != NULL) { + if (item->photoFileName) { + photoInfo->ppro->debugLog(_T("Showing picture from %s"), item->photoFileName); + photoInfo->hBitmap = (HBITMAP) CallService(MS_UTILS_LOADBITMAPT, 0, (LPARAM)item->photoFileName); + FIP->FI_Premultiply(photoInfo->hBitmap); + ShowWindow(GetDlgItem(hwndDlg, IDC_SAVE), SW_SHOW); + } + } + } + InvalidateRect(hwndDlg, NULL, TRUE); + UpdateWindow(hwndDlg); + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_SAVE: + static TCHAR szFilter[512]; + + ptrT jid(photoInfo->ppro->getTStringA(photoInfo->hContact, "jid")); + if (jid == NULL) + break; + + JABBER_LIST_ITEM *item = photoInfo->ppro->ListGetItemPtr(LIST_VCARD_TEMP, jid); + if (item == NULL) + if ((item = photoInfo->ppro->ListGetItemPtr(LIST_ROSTER, jid)) == NULL) + break; + + switch (ProtoGetAvatarFileFormat(item->photoFileName)) { + case PA_FORMAT_BMP: + mir_sntprintf(szFilter, SIZEOF(szFilter), _T("BMP %s (*.bmp)%c*.BMP"), TranslateT("format"), 0); + break; + + case PA_FORMAT_GIF: + mir_sntprintf(szFilter, SIZEOF(szFilter), _T("GIF %s (*.gif)%c*.GIF"), TranslateT("format"), 0); + break; + + case PA_FORMAT_JPEG: + mir_sntprintf(szFilter, SIZEOF(szFilter), _T("JPEG %s (*.jpg;*.jpeg)%c*.JPG;*.JPEG"), TranslateT("format"), 0); + break; + + default: + mir_sntprintf(szFilter, SIZEOF(szFilter), _T("%s (*.*)%c*.*"), TranslateT("Unknown format"), 0); + } + + TCHAR szFileName[MAX_PATH]; szFileName[0] = '\0'; + OPENFILENAME ofn = { 0 }; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.lpstrFilter = szFilter; + ofn.lpstrFile = szFileName; + ofn.nMaxFile = _MAX_PATH; + ofn.Flags = OFN_OVERWRITEPROMPT; + if ( GetSaveFileName(&ofn)) { + photoInfo->ppro->debugLog(_T("File selected is %s"), szFileName); + CopyFile(item->photoFileName, szFileName, FALSE); + } + } + break; + + case WM_PAINT: + if (!photoInfo->ppro->m_bJabberOnline) + SetDlgItemText(hwndDlg, IDC_CANVAS, TranslateT("")); + else if (!photoInfo->hBitmap) + SetDlgItemText(hwndDlg, IDC_CANVAS, TranslateT("")); + else { + BITMAP bm; + POINT ptSize, ptOrg, pt, ptFitSize; + RECT rect; + + SetDlgItemTextA(hwndDlg, IDC_CANVAS, ""); + HBITMAP hBitmap = photoInfo->hBitmap; + HWND hwndCanvas = GetDlgItem(hwndDlg, IDC_CANVAS); + HDC hdcCanvas = GetDC(hwndCanvas); + HDC hdcMem = CreateCompatibleDC(hdcCanvas); + SelectObject(hdcMem, hBitmap); + SetMapMode(hdcMem, GetMapMode(hdcCanvas)); + GetObject(hBitmap, sizeof(BITMAP), (LPVOID) &bm); + ptSize.x = bm.bmWidth; + ptSize.y = bm.bmHeight; + DPtoLP(hdcCanvas, &ptSize, 1); + ptOrg.x = ptOrg.y = 0; + DPtoLP(hdcMem, &ptOrg, 1); + GetClientRect(hwndCanvas, &rect); + InvalidateRect(hwndCanvas, NULL, TRUE); + UpdateWindow(hwndCanvas); + if (ptSize.x<=rect.right && ptSize.y<=rect.bottom) { + pt.x = (rect.right - ptSize.x)/2; + pt.y = (rect.bottom - ptSize.y)/2; + ptFitSize = ptSize; + } + else { + if (((float)(ptSize.x-rect.right))/ptSize.x > ((float)(ptSize.y-rect.bottom))/ptSize.y) { + ptFitSize.x = rect.right; + ptFitSize.y = (ptSize.y*rect.right)/ptSize.x; + pt.x = 0; + pt.y = (rect.bottom - ptFitSize.y)/2; + } + else { + ptFitSize.x = (ptSize.x*rect.bottom)/ptSize.y; + ptFitSize.y = rect.bottom; + pt.x = (rect.right - ptFitSize.x)/2; + pt.y = 0; + } + } + + if (IsThemeActive()) { + RECT rc; GetClientRect(hwndCanvas, &rc); + DrawThemeParentBackground(hwndCanvas, hdcCanvas, &rc); + } + else { + RECT rc; GetClientRect(hwndCanvas, &rc); + FillRect(hdcCanvas, &rc, (HBRUSH)GetSysColorBrush(COLOR_BTNFACE)); + } + + if (bm.bmBitsPixel == 32) { + BLENDFUNCTION bf = {0}; + bf.AlphaFormat = AC_SRC_ALPHA; + bf.BlendOp = AC_SRC_OVER; + bf.SourceConstantAlpha = 255; + GdiAlphaBlend(hdcCanvas, pt.x, pt.y, ptFitSize.x, ptFitSize.y, hdcMem, ptOrg.x, ptOrg.y, ptSize.x, ptSize.y, bf); + } + else { + SetStretchBltMode(hdcCanvas, COLORONCOLOR); + StretchBlt(hdcCanvas, pt.x, pt.y, ptFitSize.x, ptFitSize.y, hdcMem, ptOrg.x, ptOrg.y, ptSize.x, ptSize.y, SRCCOPY); + } + + DeleteDC(hdcMem); + } + break; + + case WM_DESTROY: + DestroyIcon((HICON)SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, 0)); + if (!photoInfo) + break; + if (photoInfo->hBitmap) { + photoInfo->ppro->debugLogA("Delete bitmap"); + DeleteObject(photoInfo->hBitmap); + } + mir_free(photoInfo); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + break; + } + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// OnInfoInit - initializes user info option dialogs + +int CJabberProto::OnUserInfoInit(WPARAM wParam, LPARAM lParam) +{ + if (!CallService(MS_PROTO_ISPROTOCOLLOADED, 0, (LPARAM)m_szModuleName)) + return 0; + + MCONTACT hContact = lParam; + if (hContact == NULL) { + // Show our vcard + OnUserInfoInit_VCard(wParam, lParam); + return 0; + } + + char *szProto = GetContactProto(hContact); + if (szProto != NULL && !strcmp(szProto, m_szModuleName)) { + OPTIONSDIALOGPAGE odp = { sizeof(odp) }; + odp.hInstance = hInst; + odp.dwInitParam = (LPARAM)this; + + odp.pfnDlgProc = JabberUserInfoDlgProc; + odp.position = -2000000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_JABBER); + odp.pszTitle = LPGEN("Account"); + UserInfo_AddPage(wParam, &odp); + + odp.pfnDlgProc = JabberUserPhotoDlgProc; + odp.position = 2000000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_VCARD_PHOTO); + odp.pszTitle = LPGEN("Photo"); + UserInfo_AddPage(wParam, &odp); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// JabberUserInfoUpdate + +void JabberUserInfoInit() +{ + hUserInfoList = WindowList_Create(); +} + +void JabberUserInfoUninit() +{ + WindowList_Destroy(hUserInfoList); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// JabberUserInfoUpdate + +void JabberUserInfoUpdate(MCONTACT hContact) +{ + if (!hContact) + WindowList_BroadcastAsync(hUserInfoList, WM_JABBER_REFRESH, 0, 0); + else if (HWND hwnd = WindowList_Find(hUserInfoList, hContact)) + PostMessage(hwnd, WM_JABBER_REFRESH, 0, 0); +} -- cgit v1.2.3