From cb4a46e7fbe62d788e66ed6121c717a2d22a4d7c Mon Sep 17 00:00:00 2001 From: watcherhd Date: Thu, 21 Apr 2011 14:14:52 +0000 Subject: svn.miranda.im is moving to a new home! git-svn-id: http://miranda-plugins.googlecode.com/svn/trunk@7 e753b5eb-9565-29b2-b5c5-2cc6f99dfbcb --- icqj_mod/icq_avatar.c | 1275 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1275 insertions(+) create mode 100644 icqj_mod/icq_avatar.c (limited to 'icqj_mod/icq_avatar.c') diff --git a/icqj_mod/icq_avatar.c b/icqj_mod/icq_avatar.c new file mode 100644 index 0000000..81c6bc0 --- /dev/null +++ b/icqj_mod/icq_avatar.c @@ -0,0 +1,1275 @@ +// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005,2006 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// +// File name : $Source: /cvsroot/miranda/miranda/protocols/IcqOscarJ/icq_avatar.c,v $ +// Revision : $Revision: 2940 $ +// Last change on : $Date: 2006-05-23 20:46:36 +0200 (Tue, 23 May 2006) $ +// Last change by : $Author: jokusoftware $ +// +// DESCRIPTION: +// +// Manages Avatar connection, provides internal service for handling avatars +// +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + +BOOL AvatarsReady = FALSE; // states if avatar connection established and ready for requests + + +typedef struct avatarthreadstartinfo_t +{ + HANDLE hConnection; // handle to the connection + char* pCookie; + WORD wCookieLen; + HANDLE hAvatarPacketRecver; + int stopThread; // horrible, but simple - signal for thread to stop + WORD wLocalSequence; + CRITICAL_SECTION localSeqMutex; + int pendingLogin; + int paused; + HANDLE runContact[4]; + DWORD runTime[4]; + int runCount; +} avatarthreadstartinfo; + +typedef struct avatarrequest_t +{ + int type; + DWORD dwUin; + char *szUid; + HANDLE hContact; + char *hash; + unsigned int hashlen; + char *szFile; + char *pData; + unsigned int cbData; + WORD wRef; + void *pNext; // invalid, but reused - spare realloc +} avatarrequest; + +avatarthreadstartinfo* currentAvatarThread; +int pendingAvatarsStart = 1; +static avatarrequest* pendingRequests = NULL; + +extern CRITICAL_SECTION cookieMutex; + +static int sendAvatarPacket(icq_packet* pPacket, avatarthreadstartinfo* atsi /*= currentAvatarThread*/); + +static DWORD __stdcall icq_avatarThread(avatarthreadstartinfo *atsi); +int handleAvatarPackets(unsigned char* buf, int buflen, avatarthreadstartinfo* atsi); + +void handleAvatarLogin(unsigned char *buf, WORD datalen, avatarthreadstartinfo *atsi); +void handleAvatarData(unsigned char *pBuffer, WORD wBufferLength, avatarthreadstartinfo *atsi); + +void handleAvatarServiceFam(unsigned char* pBuffer, WORD wBufferLength, snac_header* pSnacHeader, avatarthreadstartinfo *atsi); +void handleAvatarFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, avatarthreadstartinfo *atsi); + + +char* loadMyAvatarFileName() +{ + DBVARIANT dbvFile = {0}; + + if (!ICQGetContactSetting(NULL, "AvatarFile", &dbvFile)) + { + char tmp[MAX_PATH];; + + CallService(MS_UTILS_PATHTOABSOLUTE, (WPARAM)dbvFile.pszVal, (LPARAM)tmp); + ICQFreeVariant(&dbvFile); + return null_strdup(tmp); + } + return NULL; +} + + + +void storeMyAvatarFileName(char* szFile) +{ + char tmp[MAX_PATH]; + + CallService(MS_UTILS_PATHTORELATIVE, (WPARAM)szFile, (LPARAM)tmp); + ICQWriteContactSettingString(NULL, "AvatarFile", tmp); +} + + + +void GetFullAvatarFileName(int dwUin, char* szUid, int dwFormat, char* pszDest, int cbLen) +{ + GetAvatarFileName(dwUin, szUid, pszDest, cbLen); + AddAvatarExt(dwFormat, pszDest); +} + + + +void GetAvatarFileName(int dwUin, char* szUid, char* pszDest, int cbLen) +{ + int tPathLen; + + CallService(MS_DB_GETPROFILEPATH, cbLen, (LPARAM)pszDest); + + tPathLen = strlennull(pszDest); + tPathLen += null_snprintf(pszDest + tPathLen, MAX_PATH-tPathLen, "\\%s\\", gpszICQProtoName); + CreateDirectory(pszDest, NULL); + + if (dwUin != 0) + { + ltoa(dwUin, pszDest + tPathLen, 10); + } + else if (szUid) + { + strcpy(pszDest + tPathLen, szUid); + } + else + { + char szBuf[MAX_PATH]; + + if (CallService(MS_DB_GETPROFILENAME, 250 - tPathLen, (LPARAM)szBuf)) + strcpy(pszDest + tPathLen, "avatar" ); + else + { + char* szLastDot = strstr(szBuf, "."); + if (szLastDot) while (strstr(szLastDot+1, ".")) szLastDot = strstr(szLastDot+1, "."); + if (szLastDot) szLastDot[0] = '\0'; + strcpy(pszDest + tPathLen, szBuf); + strcat(pszDest + tPathLen, "_avt"); + } + } +} + + + +void AddAvatarExt(int dwFormat, char* pszDest) +{ + if (dwFormat == PA_FORMAT_JPEG) + strcat(pszDest, ".jpg"); + else if (dwFormat == PA_FORMAT_GIF) + strcat(pszDest, ".gif"); + else if (dwFormat == PA_FORMAT_PNG) + strcat(pszDest, ".png"); + else if (dwFormat == PA_FORMAT_BMP) + strcat(pszDest, ".bmp"); + else if (dwFormat == PA_FORMAT_XML) + strcat(pszDest, ".xml"); + else if (dwFormat == PA_FORMAT_SWF) + strcat(pszDest, ".swf"); + else + strcat(pszDest, ".dat"); +} + + + +int DetectAvatarFormatBuffer(char* pBuffer) +{ + if (!strncmp(pBuffer, "%PNG", 4)) + { + return PA_FORMAT_PNG; + } + if (!strncmp(pBuffer, "GIF8", 4)) + { + return PA_FORMAT_GIF; + } + if (!strnicmp(pBuffer, "pendingLogin) // this is not safe... + { + NetLog_Server("Avatar, Multiple start thread attempt, ignored."); + SAFE_FREE(&cookie); + return; + } + pendingAvatarsStart = 0; + NetLog_Server("Avatar: Connect failed"); + + EnterCriticalSection(&cookieMutex); // wait for ready queue, reused cs + { // check if any upload request is not in the queue + avatarrequest* ar; + void** par = &pendingRequests; + int bYet = 0; + ar = pendingRequests; + while (ar) + { + if (ar->type == 2) + { // we found it, return error + void *tmp; + + if (!bYet) + { + icq_LogMessage(LOG_WARNING, "Error uploading avatar to server, server temporarily unavailable."); + } + bYet = 1; + SAFE_FREE(&ar->pData); // remove upload request from queue + tmp = ar; + ar = ar->pNext; + *par = ar; + SAFE_FREE(&tmp); + continue; + } + par = &ar->pNext; + ar = ar->pNext; + } + } + LeaveCriticalSection(&cookieMutex); + + SAFE_FREE(&cookie); + + return; + } + atsi = currentAvatarThread; + if (atsi && atsi->pendingLogin) // this is not safe... + { + NetLog_Server("Avatar, Multiple start thread attempt, ignored."); + Netlib_CloseHandle(hConn); + SAFE_FREE(&cookie); + return; + } + + AvatarsReady = FALSE; // the old connection should not be used anymore + + atsi = (avatarthreadstartinfo*)SAFE_MALLOC(sizeof(avatarthreadstartinfo)); + atsi->pendingLogin = 1; + // Randomize sequence + atsi->wLocalSequence = (WORD)RandRange(0, 0x7fff); + atsi->hConnection = hConn; + atsi->pCookie = cookie; + atsi->wCookieLen = cookieLen; + currentAvatarThread = atsi; // we store only current thread + tid.hThread = (HANDLE)forkthreadex(NULL, 0, icq_avatarThread, atsi, 0, &tid.dwThreadId); + CloseHandle(tid.hThread); + + return; +} + + + +void StopAvatarThread() +{ + AvatarsReady = FALSE; // the connection are about to close + + if (currentAvatarThread) + { + currentAvatarThread->stopThread = 1; // make the thread stop + currentAvatarThread = NULL; // the thread will finish in background + } + return; +} + + + +static void NetLog_Hash(const char* pszIdent, unsigned char* pHash) +{ + NetLog_Server("%s Hash: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", pszIdent, pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7], pHash[8], pHash[9], pHash[10], pHash[11], pHash[12], pHash[13], pHash[14], pHash[15], pHash[16], pHash[17], pHash[18], pHash[19]); +} + + + +// handle Contact's avatar hash +void handleAvatarContactHash(DWORD dwUIN, char* szUID, HANDLE hContact, unsigned char* pHash, unsigned int nHashLen, WORD wOldStatus) +{ + DBVARIANT dbv; + BOOL bJob = FALSE; + char szAvatar[MAX_PATH]; + int dwPaFormat; + + if (nHashLen >= 0x14) + { + // check if it is really avatar (it can be also AIM's online message) + if (pHash[0] != 0 || (pHash[1] != 1 && pHash[1] != 8)) return; + + if (nHashLen == 0x18 && pHash[3] == 0) + { // icq probably set two avatars, get something from that + memcpy(pHash, pHash+4, 0x14); + } + + if (gbAvatarsEnabled) + { // check settings, should we request avatar immediatelly + BYTE bAutoLoad = ICQGetContactSettingByte(NULL, "AvatarsAutoLoad", DEFAULT_LOAD_AVATARS); + + if (ICQGetContactSetting(hContact, "AvatarHash", &dbv)) + { // we not found old hash, i.e. get new avatar + int fileState = IsAvatarSaved(hContact, pHash); + + // check saved hash and file, if equal only store hash + if (!fileState) + { // hashes are the same + dwPaFormat = ICQGetContactSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN); + + GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, szAvatar, MAX_PATH); + if (access(szAvatar, 0) == 0) + { // the file is there, link to contactphoto, save hash + NetLog_Server("Avatar is known, hash stored, linked to file."); + + ICQWriteContactSettingBlob(hContact, "AvatarHash", pHash, 0x14); + + if (dwPaFormat != PA_FORMAT_UNKNOWN && dwPaFormat != PA_FORMAT_XML) + LinkContactPhotoToFile(hContact, szAvatar); + else // the format is not supported unlink + LinkContactPhotoToFile(hContact, NULL); + + ICQBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + } + else // the file is lost, request avatar again + bJob = TRUE; + } + else + { // the hash is not the one we want, request avatar + if (fileState == 2) + { // the hash is different, unlink contactphoto + LinkContactPhotoToFile(hContact, NULL); + } + bJob = TRUE; + } + } + else + { // we found hash check if it changed or not + NetLog_Hash("Old", dbv.pbVal); + if ((dbv.cpbVal != 0x14) || memcmp(dbv.pbVal, pHash, 0x14)) + { // the hash is different, request new avatar + LinkContactPhotoToFile(hContact, NULL); // unlink photo + bJob = TRUE; + } + else + { // the hash does not changed, check if we have correct file + int fileState = IsAvatarSaved(hContact, pHash); + + // we should have file, check if the file really exists + if (!fileState) + { + dwPaFormat = ICQGetContactSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN); + if (dwPaFormat == PA_FORMAT_UNKNOWN) + { // we do not know the format, get avatar again + bJob = TRUE; + } + else + { + GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, szAvatar, MAX_PATH); + if (access(szAvatar, 0) == 0) + { // the file exists, so try to update photo setting + if (dwPaFormat != PA_FORMAT_XML && dwPaFormat != PA_FORMAT_UNKNOWN) + { + LinkContactPhotoToFile(hContact, szAvatar); + } + } + else // the file was lost, get it again + bJob = TRUE; + } + } + else + { // the hash is not the one we want, request avatar + if (fileState == 2) + { // the hash is different, unlink contactphoto + LinkContactPhotoToFile(hContact, NULL); + } + bJob = TRUE; + } + } + ICQFreeVariant(&dbv); + } + + if (bJob) + { + NetLog_Hash("New", pHash); + NetLog_Server("User has Avatar, new hash stored."); + + ICQWriteContactSettingBlob(hContact, "AvatarHash", pHash, 0x14); + + ICQBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + + if (bAutoLoad) + { // auto-load is on, so request the avatar now, otherwise we are done + GetAvatarFileName(dwUIN, szUID, szAvatar, MAX_PATH); + GetAvatarData(hContact, dwUIN, szUID, pHash, 0x14 /*nHashLen*/, szAvatar); + } // avatar request sent or added to queue + } + else + { + NetLog_Server("User has Avatar."); + } + } + } + else if (wOldStatus == ID_STATUS_OFFLINE) + { // if user were offline, and now hash not found, clear the hash + ICQDeleteContactSetting(hContact, "AvatarHash"); // TODO: need more testing + } +} + + + +static avatarrequest* CreateAvatarRequest(int type) +{ + avatarrequest *ar; + + ar = (avatarrequest*)SAFE_MALLOC(sizeof(avatarrequest)); + if (ar) + ar->type = type; + + return ar; +} + + + +// request avatar data from server +int GetAvatarData(HANDLE hContact, DWORD dwUin, char* szUid, char* hash, unsigned int hashlen, char* file) +{ + avatarthreadstartinfo* atsi; + + atsi = currentAvatarThread; // we take avatar thread - horrible, but realiable + if (AvatarsReady && !atsi->paused) // check if we are ready + { + icq_packet packet; + BYTE nUinLen; + DWORD dwCookie; + avatarcookie* ack; + int i; + DWORD dwNow = GetTickCount(); + + EnterCriticalSection(&cookieMutex); // reused... + for(i = 0; i < atsi->runCount;) + { // look for timeouted requests + if (atsi->runTime[i] < dwNow) + { // found outdated, remove + atsi->runContact[i] = atsi->runContact[atsi->runCount - 1]; + atsi->runTime[i] = atsi->runTime[atsi->runCount - 1]; + atsi->runCount--; + } + else + i++; + } + + for(i = 0; i < atsi->runCount; i++) + { + if (atsi->runContact[i] == hContact) + { + LeaveCriticalSection(&cookieMutex); + NetLog_Server("Ignoring duplicate get %d avatar request.", dwUin); + + return 0; + } + } + + if (atsi->runCount < 4) + { // 4 concurent requests at most + atsi->runContact[atsi->runCount] = hContact; + atsi->runTime[atsi->runCount] = GetTickCount() + 30000; // 30sec to complete request + atsi->runCount++; + LeaveCriticalSection(&cookieMutex); + + nUinLen = getUIDLen(dwUin, szUid); + + ack = (avatarcookie*)SAFE_MALLOC(sizeof(avatarcookie)); + if (!ack) return 0; // out of memory, go away + ack->dwUin = 1; //dwUin; // I should be damned for this - only to identify get request + ack->hContact = hContact; + ack->hash = (char*)SAFE_MALLOC(hashlen); + memcpy(ack->hash, hash, hashlen); // copy the data + ack->hashlen = hashlen; + ack->szFile = null_strdup(file); // we duplicate the string + dwCookie = AllocateCookie(CKT_AVATAR, ICQ_AVATAR_GET_REQUEST, dwUin, ack); + + serverPacketInit(&packet, (WORD)(12 + nUinLen + hashlen)); + packFNACHeaderFull(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_GET_REQUEST, 0, dwCookie); + packUID(&packet, dwUin, szUid); + packByte(&packet, 1); // unknown, probably type of request: 1 = get icon :) + packBuffer(&packet, hash, (unsigned short)hashlen); + + if (sendAvatarPacket(&packet, atsi)) + { + NetLog_Server("Request to get %d avatar image sent.", dwUin); + + return dwCookie; + } + FreeCookie(dwCookie); // sending failed, free resources + SAFE_FREE(&ack->szFile); + SAFE_FREE(&ack->hash); + SAFE_FREE(&ack); + } + else + LeaveCriticalSection(&cookieMutex); + } + // we failed to send request, or avatar thread not ready + EnterCriticalSection(&cookieMutex); // wait for ready queue, reused cs + { // check if any request for this user is not already in the queue + avatarrequest* ar; + int bYet = 0; + ar = pendingRequests; + while (ar) + { + if (ar->hContact == hContact) + { // we found it, return error + LeaveCriticalSection(&cookieMutex); + NetLog_Server("Ignoring duplicate get %d avatar request.", dwUin); + + if (!AvatarsReady && !pendingAvatarsStart) + { + icq_requestnewfamily(ICQ_AVATAR_FAMILY, StartAvatarThread); + pendingAvatarsStart = 1; + } + return 0; + } + ar = ar->pNext; + } + // add request to queue, processed after successful login + ar = CreateAvatarRequest(1); // get avatar + if (!ar) + { // out of memory, go away + LeaveCriticalSection(&cookieMutex); + return 0; + } + ar->dwUin = dwUin; + ar->szUid = null_strdup(szUid); + ar->hContact = hContact; + ar->hash = (char*)SAFE_MALLOC(hashlen); + memcpy(ar->hash, hash, hashlen); // copy the data + ar->hashlen = hashlen; + ar->szFile = null_strdup(file); // duplicate the string + ar->pNext = pendingRequests; + pendingRequests = ar; + } + LeaveCriticalSection(&cookieMutex); + + NetLog_Server("Request to get %d avatar image added to queue.", dwUin); + + if (!AvatarsReady && !pendingAvatarsStart) + { + icq_requestnewfamily(0x10, StartAvatarThread); + pendingAvatarsStart = 1; + } + + return -1; // we added to queue +} + + + +// upload avatar data to server +int SetAvatarData(HANDLE hContact, WORD wRef, char* data, unsigned int datalen) +{ + avatarthreadstartinfo* atsi; + + atsi = currentAvatarThread; // we take avatar thread - horrible, but realiable + if (AvatarsReady && !atsi->paused) // check if we are ready + { + icq_packet packet; + DWORD dwCookie; + avatarcookie* ack; + + ack = (avatarcookie*)SAFE_MALLOC(sizeof(avatarcookie)); + if (!ack) return 0; // out of memory, go away + ack->hContact = hContact; + ack->cbData = datalen; + + dwCookie = AllocateCookie(CKT_AVATAR, ICQ_AVATAR_UPLOAD_REQUEST, 0, ack); + + serverPacketInit(&packet, (WORD)(14 + datalen)); + packFNACHeaderFull(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_UPLOAD_REQUEST, 0, dwCookie); + packWord(&packet, wRef); // unknown, probably reference + packWord(&packet, (WORD)datalen); + packBuffer(&packet, data, (unsigned short)datalen); + + if (sendAvatarPacket(&packet, atsi)) + { + NetLog_Server("Upload avatar packet sent."); + + return dwCookie; + } + ReleaseCookie(dwCookie); // failed to send, free resources + } + // we failed to send request, or avatar thread not ready + EnterCriticalSection(&cookieMutex); // wait for ready queue, reused cs + { // check if any request for this user is not already in the queue + avatarrequest* ar; + int bYet = 0; + ar = pendingRequests; + while (ar) + { + if (ar->hContact == hContact) + { // we found it, return error + LeaveCriticalSection(&cookieMutex); + NetLog_Server("Ignoring duplicate upload avatar request."); + + if (!AvatarsReady && !pendingAvatarsStart) + { + icq_requestnewfamily(0x10, StartAvatarThread); + pendingAvatarsStart = 1; + } + return 0; + } + ar = ar->pNext; + } + // add request to queue, processed after successful login + ar = CreateAvatarRequest(2); // upload avatar + if (!ar) + { // out of memory, go away + LeaveCriticalSection(&cookieMutex); + return 0; + } + ar->hContact = hContact; + ar->pData = (char*)SAFE_MALLOC(datalen); + if (!ar->pData) + { // alloc failed + LeaveCriticalSection(&cookieMutex); + SAFE_FREE(&ar); + return 0; + } + memcpy(ar->pData, data, datalen); // copy the data + ar->cbData = datalen; + ar->wRef = wRef; + ar->pNext = pendingRequests; + pendingRequests = ar; + } + LeaveCriticalSection(&cookieMutex); + + NetLog_Server("Request to upload avatar image added to queue."); + + if (!AvatarsReady && !pendingAvatarsStart) + { + pendingAvatarsStart = 1; + icq_requestnewfamily(0x10, StartAvatarThread); + } + + return -1; // we added to queue +} + + + +static DWORD __stdcall icq_avatarThread(avatarthreadstartinfo *atsi) +{ + // This is the "infinite" loop that receives the packets from the ICQ avatar server + { + int recvResult; + NETLIBPACKETRECVER packetRecv = {0}; + DWORD wLastKeepAlive = 0; // we send keep-alive at most one per 30secs + + InitializeCriticalSection(&atsi->localSeqMutex); + + atsi->hAvatarPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)atsi->hConnection, 8192); + packetRecv.cbSize = sizeof(packetRecv); + packetRecv.dwTimeout = 60000; // timeout every minute - for stopThread to work + while(!atsi->stopThread) + { + recvResult = CallService(MS_NETLIB_GETMOREPACKETS,(WPARAM)atsi->hAvatarPacketRecver, (LPARAM)&packetRecv); + + if (recvResult == 0) + { + NetLog_Server("Clean closure of avatar socket"); + break; + } + + if (recvResult == SOCKET_ERROR) + { + if (GetLastError() == ERROR_TIMEOUT) + { // timeout, check if we should be still running + if (Miranda_Terminated()) + atsi->stopThread = 1; // we must stop here, cause due to a hack in netlib, we always get timeout, even if the connection is already dead +#ifdef _DEBUG + else + NetLog_Server("Avatar Thread is Idle."); +#endif + if (GetTickCount() > wLastKeepAlive) + { // limit frequency (HACK: on some systems select() does not work well) + if (ICQGetContactSettingByte(NULL, "KeepAlive", 0)) + { // send keep-alive packet + icq_packet packet; + + packet.wLen = 0; + write_flap(&packet, ICQ_PING_CHAN); + sendAvatarPacket(&packet, atsi); + } + wLastKeepAlive = GetTickCount() + 57000; + } + else + { // this is bad, the system does not handle select() properly +#ifdef _DEBUG + NetLog_Server("Avatar Thread is Forcing Idle."); +#endif + SleepEx(500, TRUE); // wait some time, can we do anything else ?? + } + continue; + } + NetLog_Server("Abortive closure of avatar socket"); + break; + } + + // Deal with the packet + packetRecv.bytesUsed = handleAvatarPackets(packetRecv.buffer, packetRecv.bytesAvailable, atsi); + + if ((AvatarsReady == TRUE) && (packetRecv.bytesAvailable == packetRecv.bytesUsed) && !atsi->paused) // no packets pending + { // process request queue + EnterCriticalSection(&cookieMutex); + + while (pendingRequests && atsi->runCount < 3) // pick up an request and send it - happens immediatelly after login + { // do not fill queue to top, leave one place free + avatarrequest* reqdata = pendingRequests; + pendingRequests = reqdata->pNext; + +#ifdef _DEBUG + NetLog_Server("Picked up the %d request from queue.", reqdata->dwUin); +#endif + switch (reqdata->type) + { + case 1: // get avatar + GetAvatarData(reqdata->hContact, reqdata->dwUin, reqdata->szUid, reqdata->hash, reqdata->hashlen, reqdata->szFile); + + SAFE_FREE(&reqdata->szUid); + SAFE_FREE(&reqdata->szFile); + SAFE_FREE(&reqdata->hash); // as soon as it will be copied + break; + case 2: // set avatar + SetAvatarData(reqdata->hContact, reqdata->wRef, reqdata->pData, reqdata->cbData); + + SAFE_FREE(&reqdata->pData); + break; + } + SAFE_FREE(&reqdata); + } + + LeaveCriticalSection(&cookieMutex); + } + } + Netlib_CloseHandle(atsi->hAvatarPacketRecver); // Close the packet receiver + } + Netlib_CloseHandle(atsi->hConnection); // Close the connection + + DeleteCriticalSection(&atsi->localSeqMutex); + + SAFE_FREE(&atsi->pCookie); + if (currentAvatarThread == atsi) // if we stoped by error or unexpectedly, clear global variable + { + AvatarsReady = FALSE; // we are not ready + pendingAvatarsStart = 0; + currentAvatarThread = NULL; // this is horrible, rewrite + } + SAFE_FREE(&atsi); + + NetLog_Server("%s thread ended.", "Avatar"); + + return 0; +} + + + +int handleAvatarPackets(unsigned char* buf, int buflen, avatarthreadstartinfo* atsi) +{ + BYTE channel; + WORD sequence; + WORD datalen; + int bytesUsed = 0; + + while (buflen > 0) + { + // All FLAPS begin with 0x2a + if (*buf++ != FLAP_MARKER) + break; + + if (buflen < 6) + break; + + unpackByte(&buf, &channel); + unpackWord(&buf, &sequence); + unpackWord(&buf, &datalen); + + if (buflen < 6 + datalen) + break; + +#ifdef _DEBUG + NetLog_Server("Avatar FLAP: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen); +#endif + + switch (channel) + { + case ICQ_LOGIN_CHAN: + handleAvatarLogin(buf, datalen, atsi); + break; + + case ICQ_DATA_CHAN: + handleAvatarData(buf, datalen, atsi); + break; + + default: + NetLog_Server("Warning: Unhandled %s FLAP Channel: Channel %u, Seq %u, Length %u bytes", "Avatar", channel, sequence, datalen); + break; + } + + /* Increase pointers so we can check for more FLAPs */ + buf += datalen; + buflen -= (datalen + 6); + bytesUsed += (datalen + 6); + } + + return bytesUsed; +} + + + +static int sendAvatarPacket(icq_packet* pPacket, avatarthreadstartinfo* atsi) +{ + int lResult = 0; + + // This critsec makes sure that the sequence order doesn't get screwed up + EnterCriticalSection(&atsi->localSeqMutex); + + if (atsi->hConnection) + { + int nRetries; + int nSendResult; + + // :IMPORTANT: + // The FLAP sequence must be a WORD. When it reaches 0xFFFF it should wrap to + // 0x0000, otherwise we'll get kicked by server. + atsi->wLocalSequence++; + + // Pack sequence number + pPacket->pData[2] = ((atsi->wLocalSequence & 0xff00) >> 8); + pPacket->pData[3] = (atsi->wLocalSequence & 0x00ff); + + for (nRetries = 3; nRetries >= 0; nRetries--) + { + nSendResult = Netlib_Send(atsi->hConnection, (const char *)pPacket->pData, pPacket->wLen, 0); + + if (nSendResult != SOCKET_ERROR) + break; + + Sleep(1000); + } + + // Send error + if (nSendResult == SOCKET_ERROR) + { // thread stops automatically + NetLog_Server("Your connection with the ICQ avatar server was abortively closed"); + } + else + { + lResult = 1; // packet sent successfully + } + } + else + { + NetLog_Server("Error: Failed to send packet (no connection)"); + } + + LeaveCriticalSection(&atsi->localSeqMutex); + + SAFE_FREE(&pPacket->pData); + + return lResult; +} + + + +void handleAvatarLogin(unsigned char *buf, WORD datalen, avatarthreadstartinfo *atsi) +{ + icq_packet packet; + + if (*(DWORD*)buf == 0x1000000) + { // here check if we received SRV_HELLO + atsi->wLocalSequence = (WORD)RandRange(0, 0xffff); + + serverCookieInit(&packet, atsi->pCookie, atsi->wCookieLen); + sendAvatarPacket(&packet, atsi); + +#ifdef _DEBUG + NetLog_Server("Sent CLI_IDENT to %s server", "avatar"); +#endif + + SAFE_FREE(&atsi->pCookie); + atsi->wCookieLen = 0; + } + else + { + NetLog_Server("Invalid Avatar Server response, Ch1."); + } +} + + + +void handleAvatarData(unsigned char *pBuffer, WORD wBufferLength, avatarthreadstartinfo *atsi) +{ + snac_header snacHeader = {0}; + + if (!unpackSnacHeader(&snacHeader, &pBuffer, &wBufferLength) || !snacHeader.bValid) + { + NetLog_Server("Error: Failed to parse SNAC header"); + } + else + { +#ifdef _DEBUG + NetLog_Server(" Received SNAC(x%02X,x%02X)", snacHeader.wFamily, snacHeader.wSubtype); +#endif + + switch (snacHeader.wFamily) + { + + case ICQ_SERVICE_FAMILY: + handleAvatarServiceFam(pBuffer, wBufferLength, &snacHeader, atsi); + break; + + case ICQ_AVATAR_FAMILY: + handleAvatarFam(pBuffer, wBufferLength, &snacHeader, atsi); + break; + + default: + NetLog_Server("Ignoring SNAC(x%02X,x%02X) - FAMILYx%02X not implemented", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wFamily); + break; + } + } +} + + + +void handleAvatarServiceFam(unsigned char* pBuffer, WORD wBufferLength, snac_header* pSnacHeader, avatarthreadstartinfo *atsi) +{ + icq_packet packet; + + switch (pSnacHeader->wSubtype) + { + + case ICQ_SERVER_READY: +#ifdef _DEBUG + NetLog_Server("Avatar server is ready and is requesting my Family versions"); + NetLog_Server("Sending my Families"); +#endif + + // Miranda mimics the behaviour of Icq5 + serverPacketInit(&packet, 18); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES); + packDWord(&packet, 0x00010004); + packDWord(&packet, 0x00100001); + sendAvatarPacket(&packet, atsi); + break; + + case ICQ_SERVER_FAMILIES2: + /* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands. + * We send a rate request packet */ +#ifdef _DEBUG + NetLog_Server("Server told me his Family versions"); + NetLog_Server("Requesting Rate Information"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO); + sendAvatarPacket(&packet, atsi); + break; + + case ICQ_SERVER_RATE_INFO: +#ifdef _DEBUG + NetLog_Server("Server sent Rate Info"); + NetLog_Server("Sending Rate Info Ack"); +#endif + /* Don't really care about this now, just send the ack */ + serverPacketInit(&packet, 20); // TODO: add rate management to request queue (0.5+) + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_RATE_ACK); + packDWord(&packet, 0x00010002); + packDWord(&packet, 0x00030004); + packWord(&packet, 0x0005); + sendAvatarPacket(&packet, atsi); + + // send cli_ready + serverPacketInit(&packet, 26); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_READY); + packDWord(&packet, 0x00010004); + packDWord(&packet, 0x001008E4); + packDWord(&packet, 0x00100001); + packDWord(&packet, 0x001008E4); + sendAvatarPacket(&packet, atsi); + + AvatarsReady = TRUE; // we are ready to process requests + pendingAvatarsStart = 0; + atsi->pendingLogin = 0; + + NetLog_Server(" *** Yeehah, avatar login sequence complete"); + break; + +/* case ICQ_SERVER_PAUSE: + NetLog_Server("Avatar server is going down in a few seconds... (Flags: %u, Ref: %u)", pSnacHeader->wFlags, pSnacHeader->dwRef); + // This is the list of groups that we want to have on the next server + serverPacketInit(&packet, 14); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_PAUSE_ACK); + packWord(&packet,ICQ_SERVICE_FAMILY); + packWord(&packet,ICQ_AVATAR_FAMILY); + sendAvatarPacket(&packet, atsi); +#ifdef _DEBUG + NetLog_Server("Sent server pause ack"); +#endif + break; // TODO: avatar migration is not working, should be ?*/ + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + + +void handleAvatarFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, avatarthreadstartinfo *atsi) +{ + switch (pSnacHeader->wSubtype) + { + case ICQ_AVATAR_GET_REPLY: // received avatar data, store to file + { // handle new avatar, notify + avatarcookie* ac; + + if (FindCookie(pSnacHeader->dwRef, NULL, &ac)) + { + BYTE len; + WORD datalen; + int out; + char* szMyFile = (char*)_alloca(strlennull(ac->szFile)+10); + PROTO_AVATAR_INFORMATION ai; + int i; + + EnterCriticalSection(&cookieMutex); + for(i = 0; i < atsi->runCount; i++) + { // look for our record + if (atsi->runContact[i] == ac->hContact) + { // found remove + atsi->runContact[i] = atsi->runContact[atsi->runCount - 1]; + atsi->runTime[i] = atsi->runTime[atsi->runCount - 1]; + atsi->runCount--; + break; + } + } + LeaveCriticalSection(&cookieMutex); + + strcpy(szMyFile, ac->szFile); + + ai.cbSize = sizeof ai; + ai.format = PA_FORMAT_JPEG; // this is for error only + ai.hContact = ac->hContact; + strcpy(ai.filename, ac->szFile); + AddAvatarExt(PA_FORMAT_JPEG, ai.filename); + + FreeCookie(pSnacHeader->dwRef); + unpackByte(&pBuffer, &len); + if (wBufferLength < ((ac->hashlen)<<1)+4+len) + { + NetLog_Server("Received invalid avatar reply."); + + ICQBroadcastAck(ac->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0); + + SAFE_FREE(&ac->szFile); + SAFE_FREE(&ac->hash); + SAFE_FREE(&ac); + + break; + } + + pBuffer += len; + pBuffer += (ac->hashlen<<1) + 1; + unpackWord(&pBuffer, &datalen); + + wBufferLength -= 4 + len + (ac->hashlen<<1); + if (datalen > wBufferLength) + { + datalen = wBufferLength; + NetLog_Server("Avatar reply broken, trying to do my best."); + } + + if (datalen > 4) + { // store to file... + int dwPaFormat; + + NetLog_Server("Received user avatar, storing (%d bytes).", datalen); + + dwPaFormat = DetectAvatarFormatBuffer(pBuffer); + ICQWriteContactSettingByte(ac->hContact, "AvatarType", (BYTE)dwPaFormat); + ai.format = dwPaFormat; // set the format + AddAvatarExt(dwPaFormat, szMyFile); + strcpy(ai.filename, szMyFile); + + out = _open(szMyFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); + if (out) + { + DBVARIANT dbv; + + _write(out, pBuffer, datalen); + _close(out); + + if (dwPaFormat != PA_FORMAT_XML && dwPaFormat != PA_FORMAT_UNKNOWN) + LinkContactPhotoToFile(ac->hContact, szMyFile); // this should not be here, but no other simple solution available + + if (!ac->hContact) // our avatar, set filename + storeMyAvatarFileName(szMyFile); + else + { // contact's avatar set hash + if (!ICQGetContactSetting(ac->hContact, "AvatarHash", &dbv)) + { + if (ICQWriteContactSettingBlob(ac->hContact, "AvatarSaved", dbv.pbVal, dbv.cpbVal)) + NetLog_Server("Failed to set file hash."); + + ICQFreeVariant(&dbv); + } + else + { + NetLog_Server("Warning: DB error (no hash in DB)."); + // the hash was lost, try to fix that + if (ICQWriteContactSettingBlob(ac->hContact, "AvatarSaved", ac->hash, ac->hashlen) || + ICQWriteContactSettingBlob(ac->hContact, "AvatarHash", ac->hash, ac->hashlen)) + { + NetLog_Server("Failed to save avatar hash to DB"); + } + } + } + + ICQBroadcastAck(ac->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai, 0); + } + } + else + { // the avatar is empty + NetLog_Server("Received empty avatar, nothing written.", datalen); + + ICQBroadcastAck(ac->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0); + } + SAFE_FREE(&ac->szFile); + SAFE_FREE(&ac->hash); + SAFE_FREE(&ac); + } + else + { + NetLog_Server("Warning: Received unexpected Avatar Reply SNAC(x10,x07)."); + } + + break; + } + case ICQ_AVATAR_UPLOAD_ACK: + { + // upload completed, notify + BYTE res; + unpackByte(&pBuffer, &res); + if (!res && (wBufferLength == 0x15)) + { + avatarcookie* ac; + if (FindCookie(pSnacHeader->dwRef, NULL, &ac)) + { + // here we store the local hash + ReleaseCookie(pSnacHeader->dwRef); + } + else + { + NetLog_Server("Warning: Received unexpected Upload Avatar Reply SNAC(x10,x03)."); + } + } + else if (res) + { + NetLog_Server("Error uploading avatar to server, #%d", res); + icq_LogMessage(LOG_WARNING, "Error uploading avatar to server, server refused to accept the image."); + } + else + NetLog_Server("Received invalid upload avatar ack."); + + break; + } + case ICQ_ERROR: + { + WORD wError; + avatarcookie *ack; + + if (FindCookie(pSnacHeader->dwRef, NULL, &ack)) + { + if (ack->dwUin) + { + NetLog_Server("Error: Avatar request failed"); + SAFE_FREE(&ack->szFile); + SAFE_FREE(&ack->hash); + } + else + { + NetLog_Server("Error: Avatar upload failed"); + } + ReleaseCookie(pSnacHeader->dwRef); + } + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_AVATAR_FAMILY, wError); + break; + } + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_AVATAR_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + } +} -- cgit v1.2.3