summaryrefslogtreecommitdiff
path: root/protocols/Gadu-Gadu/src
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Gadu-Gadu/src')
-rw-r--r--protocols/Gadu-Gadu/src/avatar.cpp459
-rw-r--r--protocols/Gadu-Gadu/src/core.cpp1756
-rw-r--r--protocols/Gadu-Gadu/src/dialogs.cpp1016
-rw-r--r--protocols/Gadu-Gadu/src/dynstuff.cpp612
-rw-r--r--protocols/Gadu-Gadu/src/dynstuff.h70
-rw-r--r--protocols/Gadu-Gadu/src/filetransfer.cpp977
-rw-r--r--protocols/Gadu-Gadu/src/gg.cpp523
-rw-r--r--protocols/Gadu-Gadu/src/gg.h341
-rw-r--r--protocols/Gadu-Gadu/src/gg_proto.cpp804
-rw-r--r--protocols/Gadu-Gadu/src/gg_proto.h295
-rw-r--r--protocols/Gadu-Gadu/src/groupchat.cpp669
-rw-r--r--protocols/Gadu-Gadu/src/icolib.cpp109
-rw-r--r--protocols/Gadu-Gadu/src/image.cpp1191
-rw-r--r--protocols/Gadu-Gadu/src/import.cpp651
-rw-r--r--protocols/Gadu-Gadu/src/keepalive.cpp92
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/COPYING504
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/common.c975
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/compat.h36
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/dcc.c1363
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/dcc7.c1655
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/events.c2864
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/http.c544
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/internal.h48
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/libgadu.c2353
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/libgadu.h2311
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/obsolete.c238
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/protocol.h277
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/pthread.c78
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/pthread.h56
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/pubdir.c862
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/pubdir50.c557
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/resolver.c766
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/resolver.h33
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/sha1.c308
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/win32.c65
-rw-r--r--protocols/Gadu-Gadu/src/libgadu/win32.h75
-rw-r--r--protocols/Gadu-Gadu/src/links.cpp173
-rw-r--r--protocols/Gadu-Gadu/src/oauth.cpp584
-rw-r--r--protocols/Gadu-Gadu/src/ownerinfo.cpp78
-rw-r--r--protocols/Gadu-Gadu/src/popups.cpp174
-rw-r--r--protocols/Gadu-Gadu/src/resource.h147
-rw-r--r--protocols/Gadu-Gadu/src/services.cpp330
-rw-r--r--protocols/Gadu-Gadu/src/sessions.cpp445
-rw-r--r--protocols/Gadu-Gadu/src/token.cpp161
-rw-r--r--protocols/Gadu-Gadu/src/userutils.cpp329
-rw-r--r--protocols/Gadu-Gadu/src/version.h23
46 files changed, 27977 insertions, 0 deletions
diff --git a/protocols/Gadu-Gadu/src/avatar.cpp b/protocols/Gadu-Gadu/src/avatar.cpp
new file mode 100644
index 0000000000..ace7871b6a
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/avatar.cpp
@@ -0,0 +1,459 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include <io.h>
+#include <fcntl.h>
+#include "protocol.h"
+
+//////////////////////////////////////////////////////////
+// Avatars support
+void GGPROTO::getAvatarFilename(HANDLE hContact, TCHAR *pszDest, int cbLen)
+{
+ int tPathLen;
+ TCHAR *path = (TCHAR*)alloca(cbLen * sizeof(TCHAR));
+ TCHAR *avatartype = NULL;
+
+ if (hAvatarsFolder == NULL || FoldersGetCustomPathT(hAvatarsFolder, path, cbLen, _T(""))) {
+ mir_ptr<TCHAR> tmpPath( Utils_ReplaceVarsT( _T("%miranda_avatarcache%")));
+ tPathLen = mir_sntprintf(pszDest, cbLen, _T("%s\\%s"), (TCHAR*)tmpPath, m_tszUserName);
+ }
+ else {
+ _tcscpy(pszDest, path);
+ tPathLen = (int)_tcslen(pszDest);
+ }
+
+ if (_taccess(pszDest, 0))
+ CallService(MS_UTILS_CREATEDIRTREE, 0, (LPARAM)pszDest);
+
+ switch (db_get_b(hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE)) {
+ case PA_FORMAT_JPEG: avatartype = _T("jpg"); break;
+ case PA_FORMAT_GIF: avatartype = _T("gif"); break;
+ case PA_FORMAT_PNG: avatartype = _T("png"); break;
+ }
+
+ if (hContact != NULL) {
+ DBVARIANT dbv;
+ if (!db_get_s(hContact, m_szModuleName, GG_KEY_AVATARHASH, &dbv, DBVT_ASCIIZ)) {
+ mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s.%s"), dbv.pszVal, avatartype);
+ DBFreeVariant(&dbv);
+ }
+ }
+ else mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s avatar.%s"), m_szModuleName, avatartype);
+}
+
+void GGPROTO::getAvatarFileInfo(uin_t uin, char **avatarurl, int *type)
+{
+ NETLIBHTTPREQUEST req = {0};
+ NETLIBHTTPREQUEST *resp;
+ char szUrl[128];
+ *avatarurl = NULL;
+ *type = PA_FORMAT_UNKNOWN;
+
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_GET;
+ req.szUrl = szUrl;
+ mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%d/0.xml", uin);
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+ HXML hXml;
+ TCHAR *xmlAction;
+ TCHAR *tag;
+
+ xmlAction = mir_a2t(resp->pData);
+ tag = mir_a2t("result");
+ hXml = xi.parseString(xmlAction, 0, tag);
+
+ if (hXml != NULL) {
+ HXML node;
+ char *blank;
+
+ mir_free(tag); tag = mir_a2t("users/user/avatars/avatar");
+ node = xi.getChildByPath(hXml, tag, 0);
+ mir_free(tag); tag = mir_a2t("blank");
+ blank = (node != NULL) ? mir_t2a(xi.getAttrValue(node, tag)) : NULL;
+
+ if (blank != NULL && strcmp(blank, "1")) {
+ mir_free(tag); tag = mir_a2t("users/user/avatars/avatar/bigAvatar");
+ node = xi.getChildByPath(hXml, tag, 0);
+ *avatarurl = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ mir_free(tag); tag = mir_a2t("users/user/avatars/avatar/originBigAvatar");
+ node = xi.getChildByPath(hXml, tag, 0);
+ if (node != NULL) {
+ char *orgavurl = mir_t2a(xi.getText(node));
+ char *avtype = strrchr(orgavurl, '.');
+ avtype++;
+ if (!_stricmp(avtype, "jpg"))
+ *type = PA_FORMAT_JPEG;
+ else if (!_stricmp(avtype, "gif"))
+ *type = PA_FORMAT_GIF;
+ else if (!_stricmp(avtype, "png"))
+ *type = PA_FORMAT_PNG;
+ mir_free(orgavurl);
+ }
+ }
+ else *avatarurl = mir_strdup("");
+ mir_free(blank);
+ xi.destroyNode(hXml);
+ }
+ mir_free(tag);
+ mir_free(xmlAction);
+ }
+ else netlog("gg_getavatarfileinfo(): Invalid response code from HTTP request");
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_getavatarfileinfo(): No response from HTTP request");
+}
+
+char *gg_avatarhash(char *param)
+{
+ mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE];
+ char *result;
+ int i;
+
+ if (param == NULL || (result = (char *)mir_alloc(MIR_SHA1_HASH_SIZE * 2 + 1)) == NULL)
+ return NULL;
+
+ mir_sha1_hash((BYTE*)param, (int)strlen(param), digest);
+ for (i = 0; i < MIR_SHA1_HASH_SIZE; i++)
+ sprintf(result + (i<<1), "%02x", digest[i]);
+
+ return result;
+}
+
+typedef struct
+{
+ HANDLE hContact;
+ char *AvatarURL;
+} GGGETAVATARDATA;
+
+void GGPROTO::getAvatar(HANDLE hContact, char *szAvatarURL)
+{
+ if (pth_avatar.dwThreadId) {
+ GGGETAVATARDATA *data = (GGGETAVATARDATA*)mir_alloc(sizeof(GGGETAVATARDATA));
+ data->hContact = hContact;
+ data->AvatarURL = mir_strdup(szAvatarURL);
+ EnterCriticalSection(&avatar_mutex);
+ list_add(&avatar_transfers, data, 0);
+ LeaveCriticalSection(&avatar_mutex);
+ }
+}
+
+typedef struct
+{
+ HANDLE hContact;
+ int iWaitFor;
+} GGREQUESTAVATARDATA;
+
+void GGPROTO::requestAvatar(HANDLE hContact, int iWaitFor)
+{
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)
+ && pth_avatar.dwThreadId) {
+ GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA*)mir_alloc(sizeof(GGREQUESTAVATARDATA));
+ data->hContact = hContact;
+ data->iWaitFor = iWaitFor;
+ EnterCriticalSection(&avatar_mutex);
+ list_add(&avatar_requests, data, 0);
+ LeaveCriticalSection(&avatar_mutex);
+ }
+}
+
+void __cdecl GGPROTO::avatarrequestthread(void*)
+{
+ list_t l;
+
+ netlog("gg_avatarrequestthread(): Avatar Request Thread Starting");
+ while (pth_avatar.dwThreadId)
+ {
+ EnterCriticalSection(&avatar_mutex);
+ if (avatar_requests) {
+ GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)avatar_requests->data;
+ char *AvatarURL;
+ int AvatarType, iWaitFor = data->iWaitFor;
+ HANDLE hContact = data->hContact;
+
+ list_remove(&avatar_requests, data, 0);
+ mir_free(data);
+ LeaveCriticalSection(&avatar_mutex);
+
+ getAvatarFileInfo( db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0), &AvatarURL, &AvatarType);
+ if (AvatarURL != NULL && strlen(AvatarURL) > 0)
+ db_set_s(hContact, m_szModuleName, GG_KEY_AVATARURL, AvatarURL);
+ else
+ db_unset(hContact, m_szModuleName, GG_KEY_AVATARURL);
+ db_set_b(hContact, m_szModuleName, GG_KEY_AVATARTYPE, (BYTE)AvatarType);
+ db_set_b(hContact, m_szModuleName, GG_KEY_AVATARREQUESTED, 1);
+
+ if (iWaitFor) {
+ PROTO_AVATAR_INFORMATIONT pai = {0};
+ pai.cbSize = sizeof(pai);
+ pai.hContact = hContact;
+ if (getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai) != GAIR_WAITFOR)
+ ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&pai, 0);
+ }
+ else ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, 0, 0);
+ }
+ else LeaveCriticalSection(&avatar_mutex);
+
+ EnterCriticalSection(&avatar_mutex);
+ if (avatar_transfers) {
+ GGGETAVATARDATA *data = (GGGETAVATARDATA *)avatar_transfers->data;
+ NETLIBHTTPREQUEST req = {0};
+ NETLIBHTTPREQUEST *resp;
+ PROTO_AVATAR_INFORMATIONT pai = {0};
+ int result = 0;
+
+ pai.cbSize = sizeof(pai);
+ pai.hContact = data->hContact;
+ pai.format = db_get_b(pai.hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE);
+
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_GET;
+ req.szUrl = data->AvatarURL;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+ int file_fd;
+
+ getAvatarFilename(pai.hContact, pai.filename, sizeof(pai.filename));
+ file_fd = _topen(pai.filename, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
+ if (file_fd != -1) {
+ _write(file_fd, resp->pData, resp->dataLength);
+ _close(file_fd);
+ result = 1;
+ }
+ }
+ else netlog("gg_avatarrequestthread(): Invalid response code from HTTP request");
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_avatarrequestthread(): No response from HTTP request");
+
+ ProtoBroadcastAck(m_szModuleName, pai.hContact, ACKTYPE_AVATAR,
+ result ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)&pai, 0);
+
+ if (!pai.hContact)
+ CallService(MS_AV_REPORTMYAVATARCHANGED, (WPARAM)m_szModuleName, 0);
+
+ list_remove(&avatar_transfers, data, 0);
+ mir_free(data->AvatarURL);
+ mir_free(data);
+ }
+ LeaveCriticalSection(&avatar_mutex);
+ SleepEx(100, FALSE);
+ }
+
+ for (l = avatar_requests; l; l = l->next) {
+ GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)l->data;
+ mir_free(data);
+ }
+ for (l = avatar_transfers; l; l = l->next) {
+ GGGETAVATARDATA *data = (GGGETAVATARDATA *)l->data;
+ mir_free(data->AvatarURL);
+ mir_free(data);
+ }
+ list_destroy(avatar_requests, 0);
+ list_destroy(avatar_transfers, 0);
+ netlog("gg_avatarrequestthread(): Avatar Request Thread Ending");
+}
+
+void GGPROTO::initavatarrequestthread()
+{
+ DWORD exitCode = 0;
+
+ GetExitCodeThread(pth_avatar.hThread, &exitCode);
+ if (exitCode != STILL_ACTIVE) {
+ avatar_requests = avatar_transfers = NULL;
+ pth_avatar.hThread = forkthreadex(&GGPROTO::avatarrequestthread, NULL, &pth_avatar.dwThreadId);
+ }
+}
+
+void GGPROTO::uninitavatarrequestthread()
+{
+ pth_avatar.dwThreadId = 0;
+#ifdef DEBUGMODE
+ netlog("gg_uninitavatarrequestthread(): Waiting until Avatar Request Thread finished, if needed.");
+#endif
+ threadwait(&pth_avatar);
+}
+
+void __cdecl GGPROTO::getuseravatarthread(void*)
+{
+ char *AvatarURL;
+ int AvatarType;
+
+ getAvatarFileInfo( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), &AvatarURL, &AvatarType);
+ if (AvatarURL != NULL && strlen(AvatarURL) > 0)
+ db_set_s(NULL, m_szModuleName, GG_KEY_AVATARURL, AvatarURL);
+ else
+ db_unset(NULL, m_szModuleName, GG_KEY_AVATARURL);
+ db_set_b(NULL, m_szModuleName, GG_KEY_AVATARTYPE, (BYTE)AvatarType);
+ db_set_b(NULL, m_szModuleName, GG_KEY_AVATARREQUESTED, 1);
+ mir_free(AvatarURL);
+
+ PROTO_AVATAR_INFORMATIONT pai = {0};
+ pai.cbSize = sizeof(pai);
+ getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai);
+}
+
+void GGPROTO::getUserAvatar()
+{
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)
+ && db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))
+ forkthread(&GGPROTO::getuseravatarthread, NULL);
+}
+
+void __cdecl GGPROTO::setavatarthread(void *param)
+{
+ NETLIBHTTPHEADER httpHeaders[4];
+ NETLIBHTTPREQUEST req = {0};
+ NETLIBHTTPREQUEST *resp;
+ TCHAR *szFilename = (TCHAR*)param;
+ const char *contentend = "\r\n--AaB03x--\r\n";
+ char szUrl[128], uin[32], *authHeader, *data, *avatardata, content[256], image_ext[4], image_type[11];
+ int file_fd, avatardatalen, datalen, contentlen, contentendlen, res = 0, repeat = 0;
+
+ netlog("gg_setavatar(): Trying to set user avatar using %s...", szFilename);
+ UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin);
+
+ file_fd = _topen(szFilename, _O_RDONLY | _O_BINARY, _S_IREAD);
+ if (file_fd == -1) {
+ netlog("gg_setavatar(): Failed to open avatar file (%s).", strerror(errno));
+ mir_free(szFilename);
+ getUserAvatar();
+ return;
+ }
+ avatardatalen = _filelength(file_fd);
+ avatardata = (char *)mir_alloc(avatardatalen);
+
+ _read(file_fd, avatardata, avatardatalen);
+ _close(file_fd);
+
+ TCHAR *fileext = _tcsrchr(szFilename, '.');
+ fileext++;
+ if (!_tcsicmp(fileext, _T("jpg"))) {
+ strcpy(image_ext, "jpg");
+ strcpy(image_type, "image/jpeg");
+ }
+ else if (!_tcsicmp(fileext, _T("gif"))) {
+ strcpy(image_ext, "gif");
+ strcpy(image_type, "image/gif");
+ }
+ else {
+ strcpy(image_ext, "png");
+ strcpy(image_type, "image/png");
+ }
+
+ mir_snprintf(content, 256, "--AaB03x\r\nContent-Disposition: form-data; name=\"_method\"\r\n\r\nPUT\r\n--AaB03x\r\nContent-Disposition: form-data; name=\"avatar\"; filename=\"%s.%s\"\r\nContent-Type: %s\r\n\r\n",
+ uin, image_ext, image_type);
+ contentlen = (int)strlen(content);
+ contentendlen = (int)strlen(contentend);
+
+ datalen = contentlen + avatardatalen + contentendlen;
+ data = (char *)mir_alloc(datalen);
+ memcpy(data, content, contentlen);
+ memcpy(data + contentlen, avatardata, avatardatalen);
+ memcpy(data + contentlen + avatardatalen, contentend, contentendlen);
+
+ mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%s/0.xml", uin);
+ oauth_checktoken(0);
+ authHeader = oauth_header("PUT", szUrl);
+
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_POST;
+ req.szUrl = szUrl;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11;
+ req.headersCount = 4;
+ req.headers = httpHeaders;
+ httpHeaders[0].szName = "User-Agent";
+ httpHeaders[0].szValue = GG8_VERSION;
+ httpHeaders[1].szName = "Authorization";
+ httpHeaders[1].szValue = authHeader;
+ httpHeaders[2].szName = "Accept";
+ httpHeaders[2].szValue = "*/*";
+ httpHeaders[3].szName = "Content-Type";
+ httpHeaders[3].szValue = "multipart/form-data; boundary=AaB03x";
+ req.pData = data;
+ req.dataLength = datalen;
+
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+#ifdef DEBUGMODE
+ netlog("%s", resp->pData);
+#endif
+ res = 1;
+ }
+ else netlog("gg_setavatar(): Invalid response code from HTTP request");
+ if (resp->resultCode == 403 || resp->resultCode == 401)
+ repeat = 1;
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_setavatar(): No response from HTTP request");
+
+ if (repeat) { // Access Token expired - we need to obtain new
+ mir_free(authHeader);
+ oauth_checktoken(1);
+ authHeader = oauth_header("PUT", szUrl);
+
+ ZeroMemory(&req, sizeof(req));
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_POST;
+ req.szUrl = szUrl;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11;
+ req.headersCount = 4;
+ req.headers = httpHeaders;
+ req.pData = data;
+ req.dataLength = datalen;
+
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+#ifdef DEBUGMODE
+ netlog("%s", resp->pData);
+#endif
+ res = 1;
+ }
+ else netlog("gg_setavatar(): Invalid response code from HTTP request");
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_setavatar(): No response from HTTP request");
+ }
+
+ mir_free(authHeader);
+ mir_free(avatardata);
+ mir_free(data);
+
+ if (res)
+ netlog("gg_setavatar(): User avatar set successfully.");
+ else
+ netlog("gg_setavatar(): Failed to set user avatar.");
+
+ mir_free(szFilename);
+ getUserAvatar();
+}
+
+void GGPROTO::setAvatar(const TCHAR *szFilename)
+{
+ forkthread(&GGPROTO::setavatarthread, mir_tstrdup(szFilename));
+}
diff --git a/protocols/Gadu-Gadu/src/core.cpp b/protocols/Gadu-Gadu/src/core.cpp
new file mode 100644
index 0000000000..63d5d37b24
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/core.cpp
@@ -0,0 +1,1756 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include <errno.h>
+#include <io.h>
+
+////////////////////////////////////////////////////////////
+// Swap bits in DWORD
+uint32_t swap32(uint32_t x)
+{
+ return (uint32_t)
+ (((x & (uint32_t) 0x000000ffU) << 24) |
+ ((x & (uint32_t) 0x0000ff00U) << 8) |
+ ((x & (uint32_t) 0x00ff0000U) >> 8) |
+ ((x & (uint32_t) 0xff000000U) >> 24));
+}
+
+////////////////////////////////////////////////////////////
+// Is online function
+
+int GGPROTO::isonline()
+{
+ mir_cslock lck(sess_mutex);
+ return (sess != NULL);
+}
+
+////////////////////////////////////////////////////////////
+// Send disconnect request and wait for server thread to die
+void GGPROTO::disconnect()
+{
+ // If main loop then send disconnect request
+ if (isonline())
+ {
+ // Fetch proper status msg
+ char *szMsg = NULL;
+
+ // Loadup status
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_LEAVESTATUSMSG, GG_KEYDEF_LEAVESTATUSMSG))
+ {
+ DBVARIANT dbv;
+ switch (db_get_w(NULL, m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS)) {
+ case ID_STATUS_ONLINE:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.online);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_ONLINE, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_AWAY:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.away);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_AWAY, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_DND:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.dnd);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_DND, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_FREECHAT:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.freechat);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_FREECHAT, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_INVISIBLE:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.invisible);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_INVISIBLE, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ default:
+ // Set last status
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(getstatusmsg(m_iStatus));
+ LeaveCriticalSection(&modemsg_mutex);
+ }
+ }
+
+ EnterCriticalSection(&sess_mutex);
+ // Check if it has message
+ if (szMsg)
+ {
+ gg_change_status_descr(sess, GG_STATUS_NOT_AVAIL_DESCR, szMsg);
+ mir_free(szMsg);
+ // Wait for disconnection acknowledge
+ }
+ else
+ {
+ gg_change_status(sess, GG_STATUS_NOT_AVAIL);
+ // Send logoff immediately
+ gg_logoff(sess);
+ }
+ LeaveCriticalSection(&sess_mutex);
+ }
+ // Else cancel connection attempt
+ else if (sock)
+ closesocket(sock);
+}
+
+////////////////////////////////////////////////////////////
+// DNS lookup function
+uint32_t gg_dnslookup(GGPROTO *gg, char *host)
+{
+ uint32_t ip;
+ struct hostent *he;
+
+ ip = inet_addr(host);
+ if (ip != INADDR_NONE)
+ {
+#ifdef DEBUGMODE
+ gg->netlog("gg_dnslookup(): Parameter \"%s\" is already IP number.", host);
+#endif
+ return ip;
+ }
+ he = gethostbyname(host);
+ if (he)
+ {
+ ip = *(uint32_t *) he->h_addr_list[0];
+#ifdef DEBUGMODE
+ gg->netlog("gg_dnslookup(): Parameter \"%s\" was resolved to %d.%d.%d.%d.", host,
+ LOBYTE(LOWORD(ip)), HIBYTE(LOWORD(ip)), LOBYTE(HIWORD(ip)), HIBYTE(HIWORD(ip)));
+#endif
+ return ip;
+ }
+ gg->netlog("gg_dnslookup(): Cannot resolve hostname \"%s\".", host);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// Host list decoder
+typedef struct
+{
+ char hostname[128];
+ int port;
+} GGHOST;
+#define ISHOSTALPHA(a) (((a) >= '0' && (a) <= '9') || ((a) >= 'a' && (a) <= 'z') || (a) == '.' || (a) == '-')
+int gg_decodehosts(char *var, GGHOST *hosts, int max)
+{
+ int hp = 0;
+ char *hostname = NULL;
+ char *portname = NULL;
+
+ while(var && *var && hp < max)
+ {
+ if (ISHOSTALPHA(*var))
+ {
+ hostname = var;
+
+ while(var && *var && ISHOSTALPHA(*var)) var ++;
+
+ if (var && *var == ':' && var++ && *var && isdigit(*var))
+ {
+ *(var - 1) = 0;
+ portname = var;
+ while(var && *var && isdigit(*var)) var++;
+ if (*var) { *var = 0; var ++; }
+ }
+ else
+ if (*var) { *var = 0; var ++; }
+
+ // Insert new item
+ hosts[hp].hostname[127] = 0;
+ strncpy(hosts[hp].hostname, hostname, 127);
+ hosts[hp].port = portname ? atoi(portname) : 443;
+ hp ++;
+
+ // Zero the names
+ hostname = NULL;
+ portname = NULL;
+ }
+ else
+ var ++;
+ }
+ return hp;
+}
+
+////////////////////////////////////////////////////////////
+// Main connection session thread
+void __cdecl GGPROTO::mainthread(void *)
+{
+ // Miranda variables
+ NETLIBUSERSETTINGS nlus = {0};
+ DBVARIANT dbv;
+ // Gadu-Gadu variables
+ gg_login_params p = {0};
+ gg_event *e;
+ // Host cycling variables
+ int hostnum = 0, hostcount = 0;
+ GGHOST hosts[64];
+ // Gadu-gadu login errors
+ static const struct tagReason { int type; TCHAR *str; } reason[] = {
+ { GG_FAILURE_RESOLVING, LPGENT("Miranda was unable to resolve the name of the Gadu-Gadu server to its numeric address.") },
+ { GG_FAILURE_CONNECTING, LPGENT("Miranda was unable to make a connection with a server. It is likely that the server is down, in which case you should wait for a while and try again later.") },
+ { GG_FAILURE_INVALID, LPGENT("Received invalid server response.") },
+ { GG_FAILURE_READING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") },
+ { GG_FAILURE_WRITING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") },
+ { GG_FAILURE_PASSWORD, LPGENT("Your Gadu-Gadu number and password combination was rejected by the Gadu-Gadu server. Please check login details at M->Options->Network->Gadu-Gadu and try again.") },
+ { GG_FAILURE_404, LPGENT("Connecting to Gadu-Gadu hub failed.") },
+ { GG_FAILURE_TLS, LPGENT("Cannot establish secure connection.") },
+ { GG_FAILURE_NEED_EMAIL, LPGENT("Server disconnected asking you for changing your e-mail.") },
+ { GG_FAILURE_INTRUDER, LPGENT("Too many login attempts with invalid password.") },
+ { GG_FAILURE_UNAVAILABLE, LPGENT("Gadu-Gadu servers are now down. Try again later.") },
+ { 0, LPGENT("Unknown") }
+ };
+ time_t logonTime = 0;
+ time_t timeDeviation = db_get_w(NULL, m_szModuleName, GG_KEY_TIMEDEVIATION, GG_KEYDEF_TIMEDEVIATION);
+ int gg_failno = 0;
+
+ netlog("gg_mainthread(%x): Server Thread Starting", this);
+#ifdef DEBUGMODE
+ gg_debug_level = GG_DEBUG_NET | GG_DEBUG_TRAFFIC | GG_DEBUG_FUNCTION | GG_DEBUG_MISC;
+#else
+ gg_debug_level = 0;
+#endif
+
+ // Broadcast that service is connecting
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+
+ // Client version and misc settings
+ p.client_version = GG_DEFAULT_CLIENT_VERSION;
+ p.protocol_version = GG_DEFAULT_PROTOCOL_VERSION;
+ p.protocol_features = GG_FEATURE_DND_FFC | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION | GG_FEATURE_MULTILOGON;
+ p.encoding = GG_ENCODING_CP1250;
+ p.status_flags = GG_STATUS_FLAG_UNKNOWN;
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWLINKS, GG_KEYDEF_SHOWLINKS))
+ p.status_flags |= GG_STATUS_FLAG_SPAM;
+
+ // Use audio
+ /* p.has_audio = 1; */
+
+ // Use async connections
+ /* p.async = 1; */
+
+ // Send Era Omnix info if set
+ p.era_omnix = db_get_b(NULL, m_szModuleName, "EraOmnix", 0);
+
+ // Setup proxy
+ nlus.cbSize = sizeof(nlus);
+ if (CallService(MS_NETLIB_GETUSERSETTINGS, (WPARAM)netlib, (LPARAM)&nlus))
+ {
+ if (nlus.useProxy)
+ netlog("gg_mainthread(%x): Using proxy %s:%d.", this, nlus.szProxyServer, nlus.wProxyPort);
+ gg_proxy_enabled = nlus.useProxy;
+ gg_proxy_host = nlus.szProxyServer;
+ gg_proxy_port = nlus.wProxyPort;
+ if (nlus.useProxyAuth)
+ {
+ gg_proxy_username = nlus.szProxyAuthUser;
+ gg_proxy_password = nlus.szProxyAuthPassword;
+ }
+ else
+ gg_proxy_username = gg_proxy_password = NULL;
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): Failed loading proxy settings.", this);
+ gg_proxy_enabled = 0;
+ }
+
+ // Check out manual host setting
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_MANUALHOST, GG_KEYDEF_MANUALHOST))
+ {
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_SERVERHOSTS, &dbv, DBVT_ASCIIZ))
+ {
+ hostcount = gg_decodehosts(dbv.pszVal, hosts, 64);
+ DBFreeVariant(&dbv);
+ }
+ }
+
+ // Readup password
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ p.password = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): No password specified. Exiting.", this);
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ return;
+ }
+
+ // Readup number
+ if (!(p.uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ netlog("gg_mainthread(%x): No Gadu-Gadu number specified. Exiting.", this);
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ mir_free(p.password);
+ return;
+ }
+
+ // Readup SSL/TLS setting
+ if (p.tls = db_get_b(NULL, m_szModuleName, GG_KEY_SSLCONN, GG_KEYDEF_SSLCONN))
+ netlog("gg_mainthread(%x): Using TLS/SSL for connections.", this);
+
+ // Gadu-Gadu accepts image sizes upto 255
+ p.image_size = 255;
+
+ ////////////////////////////// DCC STARTUP /////////////////////////////
+ // Uin is ok so startup dcc if not started already
+ if (!dcc)
+ {
+ hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dccstart();
+
+ // Wait for DCC
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Waiting DCC service to start...", this);
+#endif
+ while (WaitForSingleObjectEx(hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
+ CloseHandle(hEvent); hEvent = NULL;
+ }
+ // Check if dcc is running and setup forwarding port
+ if (dcc && db_get_b(NULL, m_szModuleName, GG_KEY_FORWARDING, GG_KEYDEF_FORWARDING))
+ {
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_FORWARDHOST, &dbv, DBVT_ASCIIZ))
+ {
+ if (!(p.external_addr = gg_dnslookup(this, dbv.pszVal)))
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("External direct connections hostname %S is invalid. Disabling external host forwarding."), dbv.pszVal);
+ showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX);
+ }
+ else
+ netlog("gg_mainthread(%x): Loading forwarding host %s and port %d.", dbv.pszVal, p.external_port, this);
+ if (p.external_addr) p.external_port = db_get_w(NULL, m_szModuleName, GG_KEY_FORWARDPORT, GG_KEYDEF_FORWARDPORT);
+ DBFreeVariant(&dbv);
+ }
+ }
+ // Setup client port
+ if (dcc) p.client_port = dcc->port;
+
+retry:
+ // Loadup startup status & description
+ EnterCriticalSection(&modemsg_mutex);
+ p.status_descr = mir_strdup(getstatusmsg(m_iDesiredStatus));
+ p.status = status_m2gg(m_iDesiredStatus, p.status_descr != NULL);
+
+ netlog("gg_mainthread(%x): Connecting with number %d, status %d and description \"%s\".", this, p.uin, m_iDesiredStatus,
+ p.status_descr ? p.status_descr : "<none>");
+ LeaveCriticalSection(&modemsg_mutex);
+
+ // Check manual hosts
+ if (hostnum < hostcount)
+ {
+ if (!(p.server_addr = gg_dnslookup(this, hosts[hostnum].hostname)))
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Server hostname %S is invalid. Using default hostname provided by the network."), hosts[hostnum].hostname);
+ showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX);
+ }
+ else
+ {
+ p.server_port = hosts[hostnum].port;
+ netlog("gg_mainthread(%x): Connecting to manually specified host %s (%d.%d.%d.%d) and port %d.", this,
+ hosts[hostnum].hostname, LOBYTE(LOWORD(p.server_addr)), HIBYTE(LOWORD(p.server_addr)),
+ LOBYTE(HIWORD(p.server_addr)), HIBYTE(HIWORD(p.server_addr)), p.server_port);
+ }
+ }
+ else
+ p.server_port = p.server_addr = 0;
+
+ // Send login request
+ if (!(sess = gg_login(&p, &sock, &gg_failno)))
+ {
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ // Check if connection attempt wasn't cancelled by the user
+ if (m_iDesiredStatus != ID_STATUS_OFFLINE)
+ {
+ TCHAR error[128], *perror = NULL;
+ // Lookup for error desciption
+ if (errno == EACCES) {
+ for (int i = 0; reason[i].type; i++) if (reason[i].type == gg_failno) {
+ perror = TranslateTS(reason[i].str);
+ break;
+ }
+ }
+ if (!perror) {
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Connection cannot be established because of error:\n\t%s"), _tcserror(errno));
+ perror = error;
+ }
+ netlog("gg_mainthread(%x): %s", this, perror);
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWCERRORS, GG_KEYDEF_SHOWCERRORS))
+ showpopup(m_tszUserName, perror, GG_POPUP_ERROR | GG_POPUP_ALLOW_MSGBOX | GG_POPUP_ONCE);
+
+ // Check if we should reconnect
+ if ((gg_failno >= GG_FAILURE_RESOLVING && gg_failno != GG_FAILURE_PASSWORD && gg_failno != GG_FAILURE_INTRUDER && gg_failno != GG_FAILURE_UNAVAILABLE)
+ && errno == EACCES
+ && (db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT) || (hostnum < hostcount - 1)))
+ {
+ DWORD dwInterval = db_get_dw(NULL, m_szModuleName, GG_KEY_RECONNINTERVAL, GG_KEYDEF_RECONNINTERVAL), dwResult;
+ BOOL bRetry = TRUE;
+
+ hConnStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dwResult = WaitForSingleObjectEx(hConnStopEvent, dwInterval, TRUE);
+ if ((dwResult == WAIT_OBJECT_0 && m_iDesiredStatus == ID_STATUS_OFFLINE)
+ || (dwResult == WAIT_IO_COMPLETION && Miranda_Terminated()))
+ bRetry = FALSE;
+ CloseHandle(hConnStopEvent);
+ hConnStopEvent = NULL;
+
+ // Reconnect to the next server on the list
+ if (bRetry)
+ {
+ if (hostnum < hostcount - 1) hostnum++;
+ mir_free(p.status_descr);
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+ goto retry;
+ }
+ }
+ // We cannot do more about this
+ EnterCriticalSection(&modemsg_mutex);
+ m_iDesiredStatus = ID_STATUS_OFFLINE;
+ LeaveCriticalSection(&modemsg_mutex);
+ }
+ else
+ netlog("gg_mainthread(%x)): Connection attempt cancelled by the user.", this);
+ }
+ else
+ {
+ // Successfully connected
+ logonTime = time(NULL);
+ db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, logonTime);
+ EnterCriticalSection(&sess_mutex);
+ sess = sess;
+ LeaveCriticalSection(&sess_mutex);
+ // Subscribe users status notifications
+ notifyall();
+ // Set startup status
+ if (m_iDesiredStatus != status_gg2m(p.status))
+ refreshstatus(m_iDesiredStatus);
+ else
+ {
+ broadcastnewstatus(m_iDesiredStatus);
+ // Change status of the contact with our own UIN (if got yourself added to the contact list)
+ changecontactstatus(p.uin, p.status, p.status_descr, 0, 0, 0, 0);
+ }
+ if (check_first_conn) // First connection to the account
+ {
+ // Start search for user data
+ GetInfo(NULL, 0);
+ // Fetch user avatar
+ getUserAvatar();
+ check_first_conn = 0;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // Main loop
+ while(isonline())
+ {
+ // Connection broken/closed
+ if (!(e = gg_watch_fd(sess)))
+ {
+ netlog("gg_mainthread(%x): Connection closed.", this);
+ EnterCriticalSection(&sess_mutex);
+ gg_free_session(sess);
+ sess = NULL;
+ LeaveCriticalSection(&sess_mutex);
+ break;
+ }
+ else
+ netlog("gg_mainthread(%x): Event: %s", this, ggdebug_eventtype(e));
+
+ switch(e->type)
+ {
+ // Client connected
+ case GG_EVENT_CONN_SUCCESS:
+ // Nada
+ break;
+
+ // Client disconnected or connection failure
+ case GG_EVENT_CONN_FAILED:
+ case GG_EVENT_DISCONNECT:
+ EnterCriticalSection(&sess_mutex);
+ gg_free_session(sess);
+ sess = NULL;
+ LeaveCriticalSection(&sess_mutex);
+ break;
+
+ // Client allowed to disconnect
+ case GG_EVENT_DISCONNECT_ACK:
+ // Send logoff
+ gg_logoff(sess);
+ break;
+
+ // Received ackowledge
+ case GG_EVENT_ACK:
+ if (e->event.ack.seq && e->event.ack.recipient)
+ {
+ ProtoBroadcastAck(m_szModuleName, getcontact((DWORD)e->event.ack.recipient, 0, 0, NULL),
+ ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) e->event.ack.seq, 0);
+ }
+ break;
+
+ // Statuslist notify (deprecated)
+ case GG_EVENT_NOTIFY:
+ case GG_EVENT_NOTIFY_DESCR:
+ {
+ struct gg_notify_reply *n;
+
+ n = (e->type == GG_EVENT_NOTIFY) ? e->event.notify : e->event.notify_descr.notify;
+
+ for (; n->uin; n++)
+ {
+ char *descr = (e->type == GG_EVENT_NOTIFY_DESCR) ? e->event.notify_descr.descr : NULL;
+ changecontactstatus(n->uin, n->status, descr, 0, n->remote_ip, n->remote_port, n->version);
+ }
+ break;
+ }
+ // Statuslist notify (version >= 6.0)
+ case GG_EVENT_NOTIFY60:
+ {
+ uin_t uin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0);
+ int i;
+ for(i = 0; e->event.notify60[i].uin; i++) {
+ if (e->event.notify60[i].uin == uin) continue;
+ changecontactstatus(e->event.notify60[i].uin, e->event.notify60[i].status, e->event.notify60[i].descr,
+ e->event.notify60[i].time, e->event.notify60[i].remote_ip, e->event.notify60[i].remote_port,
+ e->event.notify60[i].version);
+ requestAvatar(getcontact(e->event.notify60[i].uin, 0, 0, NULL), 0);
+ }
+ break;
+ }
+
+ // Pubdir search reply && read own data reply
+ case GG_EVENT_PUBDIR50_SEARCH_REPLY:
+ case GG_EVENT_PUBDIR50_READ:
+ case GG_EVENT_PUBDIR50_WRITE:
+ {
+ gg_pubdir50_t res = e->event.pubdir50;
+ int i, count;
+
+ if (e->type == GG_EVENT_PUBDIR50_SEARCH_REPLY)
+ {
+ netlog("gg_mainthread(%x): Got user info.", this);
+ // Store next search UIN
+ if (res->seq == GG_SEQ_SEARCH)
+ next_uin = gg_pubdir50_next(res);
+ }
+ else if (e->type == GG_EVENT_PUBDIR50_READ)
+ {
+ netlog("gg_mainthread(%x): Got owner info.", this);
+ }
+ else if (e->type == GG_EVENT_PUBDIR50_WRITE)
+ {
+ netlog("gg_mainthread(%x): Public directory save succesful.", this);
+ // Update user details
+ GetInfo(NULL, 0);
+ }
+
+ if ((count = gg_pubdir50_count(res)) > 0)
+ {
+ for (i = 0; i < count; i++)
+ {
+ // Loadup fields
+ const char *__fmnumber = gg_pubdir50_get(res, i, GG_PUBDIR50_UIN);
+ const char *__nick = gg_pubdir50_get(res, i, GG_PUBDIR50_NICKNAME);
+ const char *__firstname = gg_pubdir50_get(res, i, GG_PUBDIR50_FIRSTNAME);
+ const char *__lastname = gg_pubdir50_get(res, i, GG_PUBDIR50_LASTNAME);
+ const char *__familyname = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYNAME);
+ const char *__birthyear = gg_pubdir50_get(res, i, GG_PUBDIR50_BIRTHYEAR);
+ const char *__city = gg_pubdir50_get(res, i, GG_PUBDIR50_CITY);
+ const char *__origincity = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYCITY);
+ const char *__gender = gg_pubdir50_get(res, i, GG_PUBDIR50_GENDER);
+ const char *__status = gg_pubdir50_get(res, i, GG_PUBDIR50_STATUS);
+ uin_t uin = __fmnumber ? atoi(__fmnumber) : 0;
+
+ HANDLE hContact = (res->seq == GG_SEQ_CHINFO) ? NULL : getcontact(uin, 0, 0, NULL);
+ netlog("gg_mainthread(%x): Search result for uin %d, seq %d.", this, uin, res->seq);
+ if (res->seq == GG_SEQ_SEARCH)
+ {
+ char strFmt1[64];
+ char strFmt2[64];
+
+ mir_snprintf(strFmt2, sizeof(strFmt2), "%s", (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, status_gg2m(atoi(__status)), 0));
+ if (__city)
+ {
+ mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %s", Translate("City:"), __city);
+ strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2));
+ }
+ if (__birthyear)
+ {
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ int br = atoi(__birthyear);
+
+ if (br < (lt->tm_year + 1900) && br > 1900)
+ {
+ mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %d", Translate("Age:"), (lt->tm_year + 1900) - br);
+ strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2));
+ }
+ }
+
+ GGSEARCHRESULT sr;
+ memset(&sr, 0, sizeof(sr));
+ sr.cbSize = sizeof(sr);
+ sr.nick = mir_a2t(__nick);
+ sr.firstName = mir_a2t(__firstname);
+ sr.lastName = mir_a2t(__lastname);
+ sr.email = mir_a2t(strFmt2);
+ sr.id = mir_a2t(_ultoa(uin, strFmt1, 10));
+ sr.uin = uin;
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE) 1, (LPARAM)&sr);
+ mir_free(sr.nick);
+ mir_free(sr.firstName);
+ mir_free(sr.lastName);
+ mir_free(sr.email);
+ mir_free(sr.id);
+ }
+
+ if (((res->seq == GG_SEQ_INFO || res->seq == GG_SEQ_GETNICK) && hContact != NULL)
+ || res->seq == GG_SEQ_CHINFO)
+ {
+ // Change nickname if it's not present
+ if (__nick && (res->seq == GG_SEQ_GETNICK || res->seq == GG_SEQ_CHINFO))
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, __nick);
+
+ if (__nick)
+ db_set_s(hContact, m_szModuleName, "NickName", __nick);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "NickName");
+
+ // Change other info
+ if (__city)
+ db_set_s(hContact, m_szModuleName, "City", __city);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "City");
+
+ if (__firstname)
+ db_set_s(hContact, m_szModuleName, "FirstName", __firstname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "FirstName");
+
+ if (__lastname)
+ db_set_s(hContact, m_szModuleName, "LastName", __lastname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "LastName");
+
+ if (__familyname)
+ db_set_s(hContact, m_szModuleName, "FamilyName", __familyname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "FamilyName");
+
+ if (__origincity)
+ db_set_s(hContact, m_szModuleName, "CityOrigin", __origincity);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "CityOrigin");
+
+ if (__birthyear)
+ {
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ int br = atoi(__birthyear);
+ if (br > 0)
+ {
+ db_set_w(hContact, m_szModuleName, "Age", (WORD)(lt->tm_year + 1900 - br));
+ db_set_w(hContact, m_szModuleName, "BirthYear", (WORD)br);
+ }
+ }
+ else if (res->seq == GG_SEQ_CHINFO)
+ {
+ db_unset(NULL, m_szModuleName, "Age");
+ db_unset(NULL, m_szModuleName, "BirthYear");
+ }
+
+ // Gadu-Gadu Male <-> Female
+ if (__gender)
+ {
+ if (res->seq == GG_SEQ_CHINFO)
+ db_set_b(hContact, m_szModuleName, "Gender",
+ (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_SET_MALE) ? 'M' :
+ (!strcmp(__gender, GG_PUBDIR50_GENDER_SET_FEMALE) ? 'F' : '?')));
+ else
+ db_set_b(hContact, m_szModuleName, "Gender",
+ (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_MALE) ? 'M' :
+ (!strcmp(__gender, GG_PUBDIR50_GENDER_FEMALE) ? 'F' : '?')));
+ }
+ else if (res->seq == GG_SEQ_CHINFO)
+ {
+ db_unset(NULL, m_szModuleName, "Gender");
+ }
+
+ netlog("gg_mainthread(%x): Setting user info for uin %d.", this, uin);
+ ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
+ }
+ }
+ }
+ if (res->seq == GG_SEQ_SEARCH)
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
+ break;
+ }
+
+ // Status (deprecated)
+ case GG_EVENT_STATUS:
+ changecontactstatus(e->event.status.uin, e->event.status.status, e->event.status.descr, 0, 0, 0, 0);
+ break;
+
+ // Status (version >= 6.0)
+ case GG_EVENT_STATUS60:
+ {
+ HANDLE hContact = getcontact(e->event.status60.uin, 0, 0, NULL);
+ int oldstatus = db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE);
+ uin_t uin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0);
+
+ if (e->event.status60.uin == uin)
+ {
+ // Status was changed by the user simultaneously logged on using different Miranda account or IM client
+ int iStatus = status_gg2m(e->event.status60.status);
+ CallProtoService(m_szModuleName, PS_SETAWAYMSG, iStatus, (LPARAM)e->event.status60.descr);
+ CallProtoService(m_szModuleName, PS_SETSTATUS, iStatus, 0);
+ }
+
+ changecontactstatus(e->event.status60.uin, e->event.status60.status, e->event.status60.descr,
+ e->event.status60.time, e->event.status60.remote_ip, e->event.status60.remote_port, e->event.status60.version);
+
+ if (oldstatus == ID_STATUS_OFFLINE && db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE) != ID_STATUS_OFFLINE)
+ requestAvatar(hContact, 0);
+ }
+ break;
+
+ // Received userlist / or put info
+ case GG_EVENT_USERLIST:
+ switch (e->event.userlist.type) {
+ case GG_USERLIST_GET_REPLY:
+ if (e->event.userlist.reply) {
+ parsecontacts(e->event.userlist.reply);
+ MessageBox(NULL, TranslateT("List import successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+ break;
+
+ case GG_USERLIST_PUT_REPLY:
+ if (is_list_remove)
+ MessageBox(NULL, TranslateT("List remove successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ else
+ MessageBox(NULL, TranslateT("List export successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ break;
+ }
+ break;
+
+ // Received message
+ case GG_EVENT_MSG:
+ // This is CTCP request
+ if ((e->event.msg.msgclass & GG_CLASS_CTCP))
+ {
+ dccconnect(e->event.msg.sender);
+ }
+ // Check if not conference and block
+ else if (!e->event.msg.recipients_count || gc_enabled)
+ {
+ // Check if groupchat
+ if (e->event.msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF))
+ {
+ char *chat = gc_getchat(e->event.msg.sender, e->event.msg.recipients, e->event.msg.recipients_count);
+ if (chat)
+ {
+ char id[32];
+ GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ time_t t = time(NULL);
+
+ UIN2ID(e->event.msg.sender, id);
+
+ gcevent.pszUID = id;
+ gcevent.pszText = e->event.msg.message;
+ gcevent.pszNick = (char *) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) getcontact(e->event.msg.sender, 1, 0, NULL), 0);
+ gcevent.time = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time;
+ gcevent.dwFlags = GCEF_ADDTOLOG;
+ netlog("gg_mainthread(%x): Conference message to room %s & id %s.", this, chat, id);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ }
+ }
+ // Check if not empty message ( who needs it? )
+ else if (!e->event.msg.recipients_count && e->event.msg.message && *e->event.msg.message && strcmp(e->event.msg.message, "\xA0\0"))
+ {
+ CCSDATA ccs = {0};
+ PROTORECVEVENT pre = {0};
+ time_t t = time(NULL);
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = getcontact(e->event.msg.sender, 1, 0, NULL);
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time;
+ pre.szMessage = e->event.msg.message;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs);
+ }
+
+ // RichEdit format included (image)
+ if (e->event.msg.formats_length &&
+ db_get_b(NULL, m_szModuleName, GG_KEY_IMGRECEIVE, GG_KEYDEF_IMGRECEIVE) &&
+ !(db_get_dw(getcontact(e->event.msg.sender, 1, 0, NULL), "Ignore", "Mask1", 0) & IGNOREEVENT_MESSAGE))
+ {
+ char *formats = (char*)e->event.msg.formats;
+ int len = 0, formats_len = e->event.msg.formats_length, add_ptr;
+
+ while (len < formats_len)
+ {
+ add_ptr = sizeof(struct gg_msg_richtext_format);
+ if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_IMAGE)
+ {
+ struct gg_msg_richtext_image *image = (struct gg_msg_richtext_image *)(formats + add_ptr);
+ EnterCriticalSection(&sess_mutex);
+ gg_image_request(sess, e->event.msg.sender, image->size, image->crc32);
+ LeaveCriticalSection(&sess_mutex);
+
+ netlog("gg_mainthread: image request sent!");
+ add_ptr += sizeof(struct gg_msg_richtext_image);
+ }
+ if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_COLOR)
+ add_ptr += sizeof(struct gg_msg_richtext_color);
+ len += add_ptr;
+ formats += add_ptr;
+ }
+ }
+ }
+ break;
+
+ // Message sent from concurrent user session
+ case GG_EVENT_MULTILOGON_MSG:
+ if (e->event.multilogon_msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF))
+ {
+ char *chat = gc_getchat(e->event.multilogon_msg.sender, e->event.multilogon_msg.recipients, e->event.multilogon_msg.recipients_count);
+ if (chat)
+ {
+ char id[32];
+ DBVARIANT dbv;
+ GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+
+ UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), id);
+
+ gcevent.pszUID = id;
+ gcevent.pszText = e->event.multilogon_msg.message;
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ))
+ gcevent.pszNick = dbv.pszVal;
+ else
+ gcevent.pszNick = Translate("Me");
+ gcevent.time = e->event.multilogon_msg.time;
+ gcevent.bIsMe = 1;
+ gcevent.dwFlags = GCEF_ADDTOLOG;
+ netlog("gg_mainthread(%x): Sent conference message to room %s.", this, chat);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ if (gcevent.pszNick == dbv.pszVal) DBFreeVariant(&dbv);
+ }
+ }
+ else if (!e->event.multilogon_msg.recipients_count && e->event.multilogon_msg.message && *e->event.multilogon_msg.message
+ && strcmp(e->event.multilogon_msg.message, "\xA0\0"))
+ {
+ DBEVENTINFO dbei = {0};
+ dbei.cbSize = sizeof(dbei);
+ dbei.szModule = m_szModuleName;
+ dbei.timestamp = (DWORD)e->event.multilogon_msg.time;
+ dbei.flags = DBEF_SENT;
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.cbBlob = (DWORD)strlen(e->event.multilogon_msg.message) + 1;
+ dbei.pBlob = (PBYTE)e->event.multilogon_msg.message;
+ CallService(MS_DB_EVENT_ADD, (WPARAM)getcontact(e->event.multilogon_msg.sender, 1, 0, NULL), (LPARAM)&dbei);
+ }
+ break;
+
+ // Information on active concurrent sessions
+ case GG_EVENT_MULTILOGON_INFO:
+ {
+ list_t l;
+ int* iIndexes = NULL, i;
+ netlog("gg_mainthread(): Concurrent sessions count: %d.", e->event.multilogon_info.count);
+ if (e->event.multilogon_info.count > 0)
+ iIndexes = (int*)mir_calloc(e->event.multilogon_info.count * sizeof(int));
+ EnterCriticalSection(&sessions_mutex);
+ for (l = sessions; l; l = l->next)
+ {
+ struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data;
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ if (!memcmp(&sess->id, &e->event.multilogon_info.sessions[i].id, sizeof(gg_multilogon_id_t)) && iIndexes)
+ {
+ iIndexes[i]++;
+ break;
+ }
+ }
+ mir_free(sess->name);
+ mir_free(sess);
+ }
+ list_destroy(sessions, 0);
+ sessions = NULL;
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ gg_multilogon_session* sess = (gg_multilogon_session*)mir_alloc(sizeof(struct gg_multilogon_session));
+ memcpy(sess, &e->event.multilogon_info.sessions[i], sizeof(struct gg_multilogon_session));
+ sess->name = mir_strdup(*e->event.multilogon_info.sessions[i].name != '\0'
+ ? e->event.multilogon_info.sessions[i].name
+ : Translate("Unknown client"));
+ list_add(&sessions, sess, 0);
+ }
+ LeaveCriticalSection(&sessions_mutex);
+ sessions_updatedlg();
+ if (ServiceExists(MS_POPUP_ADDPOPUPCLASS))
+ {
+ const TCHAR* szText = time(NULL) - logonTime > 3
+ ? TranslateT("You have logged in at another location")
+ : TranslateT("You are logged in at another location");
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ TCHAR szMsg[MAX_SECONDLINE];
+ if (iIndexes && iIndexes[i])
+ continue;
+
+ mir_sntprintf(szMsg, SIZEOF(szMsg), _T("%s (%s)"), szText,
+ *e->event.multilogon_info.sessions[i].name != '\0' ?
+ _A2T(e->event.multilogon_info.sessions[i].name) : TranslateT("Unknown client"));
+ showpopup(m_tszUserName, szMsg, GG_POPUP_MULTILOGON);
+ }
+ }
+ mir_free(iIndexes);
+ }
+ break;
+
+ // Image reply sent
+ case GG_EVENT_IMAGE_REPLY:
+ // Get rid of empty image
+ if (e->event.image_reply.size && e->event.image_reply.image)
+ {
+ HANDLE hContact = getcontact(e->event.image_reply.sender, 1, 0, NULL);
+ void *img = (void *)img_loadpicture(e, 0);
+
+ if (!img)
+ break;
+
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 1 || img_opened(e->event.image_reply.sender))
+ {
+ img_display(hContact, img);
+ }
+ else if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 2)
+ {
+ img_displayasmsg(hContact, img);
+ }
+ else
+ {
+ CLISTEVENT cle = {0};
+ char service[128];
+ mir_snprintf(service, sizeof(service), GGS_RECVIMAGE, m_szModuleName);
+
+ cle.cbSize = sizeof(cle);
+ cle.hContact = hContact;
+ cle.hIcon = LoadIconEx("image", FALSE);
+ cle.flags = CLEF_URGENT;
+ cle.hDbEvent = (HANDLE)"img";
+ cle.lParam = (LPARAM)img;
+ cle.pszService = service;
+ cle.pszTooltip = Translate("Incoming image");
+ CallService(MS_CLIST_ADDEVENT, 0, (LPARAM)&cle);
+ ReleaseIconEx("image", FALSE);
+ }
+ }
+ break;
+
+ // Image send request
+ case GG_EVENT_IMAGE_REQUEST:
+ img_sendonrequest(e);
+ break;
+
+ // Incoming direct connection
+ case GG_EVENT_DCC7_NEW:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_new;
+ netlog("gg_mainthread(%x): Incoming direct connection.", this);
+ dcc7->contact = getcontact(dcc7->peer_uin, 0, 0, NULL);
+
+ // Check if user is on the list and if it is my uin
+ if (!dcc7->contact || db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, -1) != dcc7->uin) {
+ gg_dcc7_free(dcc7);
+ e->event.dcc7_new = NULL;
+ break;
+ }
+
+ // Add to waiting transfers
+ EnterCriticalSection(&ft_mutex);
+ list_add(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ //////////////////////////////////////////////////
+ // Add file recv request
+
+ netlog("gg_mainthread(%x): Client: %d, File ack filename \"%s\" size %d.", this, dcc7->peer_uin,
+ dcc7->filename, dcc7->size);
+
+ TCHAR* filenameT = mir_utf8decodeT((char*)dcc7->filename);
+
+ PROTORECVFILET pre = {0};
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = filenameT;
+ pre.ptszFiles = &filenameT;
+ pre.lParam = (LPARAM)dcc7;
+
+ CCSDATA ccs = { dcc7->contact, PSR_FILE, 0, (LPARAM)&pre };
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ mir_free(filenameT);
+ e->event.dcc7_new = NULL;
+ }
+ break;
+
+ // Direct connection rejected
+ case GG_EVENT_DCC7_REJECT:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_reject.dcc7;
+ if (dcc7->type == GG_SESSION_DCC7_SEND)
+ {
+ netlog("gg_mainthread(%x): File transfer denied by client %d (reason = %d).", this, dcc7->peer_uin, e->event.dcc7_reject.reason);
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DENIED, dcc7, 0);
+
+ // Remove from watches and free
+ EnterCriticalSection(&ft_mutex);
+ list_remove(&watches, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+ gg_dcc7_free(dcc7);
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): File transfer aborted by client %d.", this, dcc7->peer_uin);
+
+ // Remove transfer from waiting list
+ EnterCriticalSection(&ft_mutex);
+ list_remove(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+ }
+ }
+ break;
+
+ // Direct connection error
+ case GG_EVENT_DCC7_ERROR:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_error_ex.dcc7;
+ switch (e->event.dcc7_error)
+ {
+ case GG_ERROR_DCC7_HANDSHAKE:
+ netlog("gg_mainthread(%x): Client: %d, Handshake error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_NET:
+ netlog("gg_mainthread(%x): Client: %d, Network error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_FILE:
+ netlog("gg_mainthread(%x): Client: %d, File read/write error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_EOF:
+ netlog("gg_mainthread(%x): Client: %d, End of file/connection error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_REFUSED:
+ netlog("gg_mainthread(%x): Client: %d, Connection refused error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_RELAY:
+ netlog("gg_mainthread(%x): Client: %d, Relay connection error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ default:
+ netlog("gg_mainthread(%x): Client: %d, Unknown error.", this, dcc7 ? dcc7->peer_uin : 0);
+ }
+ if (!dcc7) break;
+
+ // Remove from watches
+ list_remove(&watches, dcc7, 0);
+
+ // Close file & fail
+ if (dcc7->file_fd != -1)
+ {
+ _close(dcc7->file_fd);
+ dcc7->file_fd = -1;
+ }
+
+ if (dcc7->contact)
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0);
+
+ // Free dcc
+ gg_dcc7_free(dcc7);
+ }
+ break;
+
+ case GG_EVENT_XML_ACTION:
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) {
+ HXML hXml;
+ TCHAR *xmlAction;
+ TCHAR *tag;
+
+ xmlAction = mir_a2t(e->event.xml_action.data);
+ tag = mir_a2t("events");
+ hXml = xi.parseString(xmlAction, 0, tag);
+
+ if (hXml != NULL) {
+ HXML node;
+ char *type, *sender;
+
+ mir_free(tag);
+ tag = mir_a2t("event/type");
+ node = xi.getChildByPath(hXml, tag, 0);
+ type = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ mir_free(tag);
+ tag = mir_a2t("event/sender");
+ node = xi.getChildByPath(hXml, tag, 0);
+ sender = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+ netlog("gg_mainthread(%x): XML Action type: %s.", this, type != NULL ? type : "unknown");
+ // Avatar change notify
+ if (type != NULL && !strcmp(type, "28")) {
+ netlog("gg_mainthread(%x): Client %s changed his avatar.", this, sender);
+ requestAvatar(getcontact(atoi(sender), 0, 0, NULL), 0);
+ }
+ mir_free(type);
+ mir_free(sender);
+ xi.destroyNode(hXml);
+ }
+ mir_free(tag);
+ mir_free(xmlAction);
+ }
+ break;
+
+ case GG_EVENT_TYPING_NOTIFICATION:
+ {
+ HANDLE hContact = getcontact(e->event.typing_notification.uin, 0, 0, NULL);
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Typing notification from %d (%d).", this,
+ e->event.typing_notification.uin, e->event.typing_notification.length);
+#endif
+ CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact,
+ e->event.typing_notification.length > 0 ? 7 : PROTOTYPE_CONTACTTYPING_OFF);
+ }
+ break;
+ }
+ // Free event struct
+ gg_free_event(e);
+ }
+
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ setalloffline();
+ db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, 0);
+
+ // If it was unwanted disconnection reconnect
+ if (m_iDesiredStatus != ID_STATUS_OFFLINE
+ && db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT))
+ {
+ netlog("gg_mainthread(%x): Unintentional disconnection detected. Going to reconnect...", this);
+ hostnum = 0;
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+ mir_free(p.status_descr);
+ goto retry;
+ }
+
+ mir_free(p.password);
+ mir_free(p.status_descr);
+
+ // Destroy concurrent sessions list
+ {
+ list_t l;
+ EnterCriticalSection(&sessions_mutex);
+ for (l = sessions; l; l = l->next)
+ {
+ struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data;
+ mir_free(sess->name);
+ mir_free(sess);
+ }
+ list_destroy(sessions, 0);
+ sessions = NULL;
+ LeaveCriticalSection(&sessions_mutex);
+ }
+
+ // Stop dcc server
+ pth_dcc.dwThreadId = 0;
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Waiting until DCC Server Thread finished, if needed.", this);
+#endif
+ threadwait(&pth_dcc);
+
+ netlog("gg_mainthread(%x): Server Thread Ending", this);
+ return;
+}
+
+////////////////////////////////////////////////////////////
+// Change status function
+void GGPROTO::broadcastnewstatus(int newStatus)
+{
+ int oldStatus;
+
+ EnterCriticalSection(&modemsg_mutex);
+ oldStatus = m_iStatus;
+ if (oldStatus == newStatus)
+ {
+ LeaveCriticalSection(&modemsg_mutex);
+ return;
+ }
+ m_iStatus = newStatus;
+ LeaveCriticalSection(&modemsg_mutex);
+
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, newStatus);
+
+ netlog("gg_broadcastnewstatus(): Broadcast new status: %d.", newStatus);
+}
+
+////////////////////////////////////////////////////////////
+// When contact is deleted
+int GGPROTO::contactdeleted(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE) wParam;
+ uin_t uin; int type;
+ DBVARIANT dbv;
+
+ uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0);
+ type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0);
+
+ // Terminate conference if contact is deleted
+ if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ) && gc_enabled)
+ {
+ GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CONTROL};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ GGGC *chat = gc_lookup(dbv.pszVal);
+
+ netlog("gg_gc_event(): Terminating chat %x, id %s from contact list...", chat, dbv.pszVal);
+ if (chat)
+ {
+ // Destroy chat entry
+ free(chat->recipients);
+ list_remove(&chats, chat, 1);
+ // Terminate chat window / shouldn't cascade entry is deleted
+ CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gcevent);
+ CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gcevent);
+ }
+
+ DBFreeVariant(&dbv);
+ return 0;
+ }
+
+ if (uin && isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
+ LeaveCriticalSection(&sess_mutex);
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// When db settings changed
+
+int GGPROTO::dbsettingchanged(WPARAM wParam, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ HANDLE hContact = (HANDLE) wParam;
+ char *szProto = NULL;
+
+ // Check if the contact is NULL or we are not online
+ if (!isonline())
+ return 0;
+
+ // If contact has been blocked
+ if (!strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_BLOCK))
+ {
+ notifyuser(hContact, 1);
+ return 0;
+ }
+
+ // Contact is being renamed
+ if (gc_enabled && !strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_NICK)
+ && cws->value.pszVal)
+ {
+ // Groupchat window contact is being renamed
+ DBVARIANT dbv;
+ int type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0);
+ if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ))
+ {
+ // Most important... check redundancy (fucking cascading)
+ static int cascade = 0;
+ if (!cascade && dbv.pszVal)
+ {
+ GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CHANGESESSIONAME};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ gcevent.pszText = cws->value.pszVal;
+ netlog("gg_dbsettingchanged(): Conference %s was renamed to %s.", dbv.pszVal, cws->value.pszVal);
+ // Mark cascading
+ /* FIXME */ cascade = 1;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ /* FIXME */ cascade = 0;
+ }
+ DBFreeVariant(&dbv);
+ }
+ else
+ // Change contact name on all chats
+ gc_changenick(hContact, cws->value.pszVal);
+ }
+
+ // Contact list changes
+ if (!strcmp(cws->szModule, "CList"))
+ {
+ // If name changed... change nick
+ if (!strcmp(cws->szSetting, "MyHandle") && cws->value.type == DBVT_ASCIIZ && cws->value.pszVal)
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, cws->value.pszVal);
+
+ // If not on list changed
+ if (!strcmp(cws->szSetting, "NotOnList"))
+ {
+ if (db_get_b(hContact, "CList", "Hidden", 0))
+ return 0;
+ // Notify user normally this time if added to the list permanently
+ if (cws->value.type == DBVT_DELETED || (cws->value.type == DBVT_BYTE && cws->value.bVal == 0))
+ notifyuser(hContact, 1);
+ }
+ }
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// All users set offline
+
+void GGPROTO::setalloffline()
+{
+ netlog("gg_setalloffline(): Setting buddies offline");
+ db_set_w(NULL, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+ HANDLE hContact = db_find_first();
+ while (hContact)
+ {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName))
+ {
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+ // Clear IP and port settings
+ db_unset(hContact, m_szModuleName, GG_KEY_CLIENTIP);
+ db_unset(hContact, m_szModuleName, GG_KEY_CLIENTPORT);
+ // Delete status descr
+ db_unset(hContact, "CList", GG_KEY_STATUSDESCR);
+ }
+ hContact = db_find_next(hContact);
+ }
+#ifdef DEBUGMODE
+ netlog("gg_setalloffline(): End");
+#endif
+}
+
+////////////////////////////////////////////////////////////
+// All users set offline
+
+void GGPROTO::notifyuser(HANDLE hContact, int refresh)
+{
+ uin_t uin;
+ if (!hContact) return;
+ if (isonline() && (uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ // Check if user should be invisible
+ // Or be blocked ?
+ if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) ||
+ db_get_b(hContact, "CList", "NotOnList", 0))
+ {
+ mir_cslock l(sess_mutex);
+ if (refresh) {
+ gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
+ gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED);
+ }
+
+ gg_add_notify_ex(sess, uin, GG_USER_OFFLINE);
+ }
+ else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0))
+ {
+ mir_cslock l(sess_mutex);
+ if (refresh)
+ gg_remove_notify_ex(sess, uin, GG_USER_OFFLINE);
+
+ gg_add_notify_ex(sess, uin, GG_USER_BLOCKED);
+ }
+ else {
+ mir_cslock l(sess_mutex);
+ if (refresh)
+ gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED);
+
+ gg_add_notify_ex(sess, uin, GG_USER_NORMAL);
+ }
+ }
+}
+
+void GGPROTO::notifyall()
+{
+ HANDLE hContact;
+ char *szProto;
+ int count = 0, cc = 0;
+ uin_t *uins;
+ char *types;
+
+ netlog("gg_notifyall(): Subscribing notification to all users");
+ // Readup count
+ hContact = db_find_first();
+ while (hContact)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName)) count ++;
+ hContact = db_find_next(hContact);
+ }
+
+ // Readup list
+ /* FIXME: If we have nothing on the list but we omit gg_notify_ex we have problem with receiving any contacts */
+ if (count == 0)
+ {
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_notify_ex(sess, NULL, NULL, 0);
+ LeaveCriticalSection(&sess_mutex);
+ }
+ return;
+ }
+ uins = (uin_t*)calloc(sizeof(uin_t), count);
+ types = (char*)calloc(sizeof(char), count);
+
+ hContact = db_find_first();
+ while (hContact && cc < count)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName) && (uins[cc] = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) ||
+ db_get_b(hContact, "CList", "NotOnList", 0))
+ types[cc] = GG_USER_OFFLINE;
+ else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0))
+ types[cc] = GG_USER_BLOCKED;
+ else
+ types[cc] = GG_USER_NORMAL;
+ cc ++;
+ }
+ hContact = db_find_next(hContact);
+ }
+ if (cc < count) count = cc;
+
+ // Send notification
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_notify_ex(sess, uins, types, count);
+ LeaveCriticalSection(&sess_mutex);
+ }
+
+ // Free variables
+ free(uins); free(types);
+}
+
+////////////////////////////////////////////////////////////
+// Get contact by uin
+
+HANDLE GGPROTO::getcontact(uin_t uin, int create, int inlist, TCHAR *szNick)
+{
+ // Look for contact in DB
+ HANDLE hContact = db_find_first();
+ while (hContact) {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName)) {
+ if ((uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0) == uin
+ && db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 0)
+ {
+ if (inlist) {
+ db_unset(hContact, "CList", "NotOnList");
+ db_unset(hContact, "CList", "Hidden");
+ }
+ return hContact;
+ }
+ }
+ hContact = db_find_next(hContact);
+ }
+ if (!create) return NULL;
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_ADD, 0, 0);
+ if (!hContact) {
+ netlog("gg_getcontact(): Failed to create Gadu-Gadu contact %s", szNick);
+ return NULL;
+ }
+
+ if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM) hContact, (LPARAM) m_szModuleName) != 0) {
+ // For some reason we failed to register the protocol for this contact
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0);
+ netlog("Failed to register GG contact %d", uin);
+ return NULL;
+ }
+
+ netlog("gg_getcontact(): Added buddy: %d", uin);
+ if (!inlist)
+ db_set_b(hContact, "CList", "NotOnList", 1);
+
+ db_set_dw(hContact, m_szModuleName, GG_KEY_UIN, (DWORD) uin);
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+
+ // If nick specified use it
+ if (szNick)
+ db_set_ts(hContact, m_szModuleName, GG_KEY_NICK, szNick);
+ else if (isonline()) {
+ gg_pubdir50_t req;
+
+ // Search for that nick
+ if (req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)) {
+ // Add uin and search it
+ gg_pubdir50_add(req, GG_PUBDIR50_UIN, ditoa(uin));
+ gg_pubdir50_seq_set(req, GG_SEQ_GETNICK);
+ EnterCriticalSection(&sess_mutex);
+ gg_pubdir50(sess, req);
+ LeaveCriticalSection(&sess_mutex);
+ gg_pubdir50_free(req);
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, ditoa(uin));
+ netlog("gg_getcontact(): Search for nick on uin: %d", uin);
+ }
+ }
+
+ // Add to notify list and pull avatar for the new contact
+ if (isonline())
+ {
+ PROTO_AVATAR_INFORMATIONT pai = {0};
+
+ EnterCriticalSection(&sess_mutex);
+ gg_add_notify_ex(sess, uin, (char)(inlist ? GG_USER_NORMAL : GG_USER_OFFLINE));
+ LeaveCriticalSection(&sess_mutex);
+
+ pai.cbSize = sizeof(pai);
+ pai.hContact = hContact;
+ getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai);
+
+ // Change status of the contact with our own UIN (if got yourself added to the contact list)
+ if (db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0) == uin) {
+ char *szMsg;
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(getstatusmsg(m_iStatus));
+ LeaveCriticalSection(&modemsg_mutex);
+ changecontactstatus(uin, status_m2gg(m_iStatus, szMsg != NULL), szMsg, 0, 0, 0, 0);
+ mir_free(szMsg);
+ }
+ }
+
+ // TODO server side list & add buddy
+ return hContact;
+}
+
+////////////////////////////////////////////////////////////
+// Status conversion
+
+int GGPROTO::status_m2gg(int status, int descr)
+{
+ // check frends only
+ int mask = db_get_b(NULL, m_szModuleName, GG_KEY_FRIENDSONLY, GG_KEYDEF_FRIENDSONLY) ? GG_STATUS_FRIENDS_MASK : 0;
+
+ if (descr)
+ {
+ switch(status)
+ {
+ case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL_DESCR | mask;
+ case ID_STATUS_ONLINE: return GG_STATUS_AVAIL_DESCR | mask;
+ case ID_STATUS_AWAY: return GG_STATUS_BUSY_DESCR | mask;
+ case ID_STATUS_DND: return GG_STATUS_DND_DESCR | mask;
+ case ID_STATUS_FREECHAT: return GG_STATUS_FFC_DESCR | mask;
+ case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE_DESCR | mask;
+ default: return GG_STATUS_BUSY_DESCR | mask;
+ }
+ }
+ else
+ {
+ switch(status)
+ {
+ case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL | mask;
+ case ID_STATUS_ONLINE: return GG_STATUS_AVAIL | mask;
+ case ID_STATUS_AWAY: return GG_STATUS_BUSY | mask;
+ case ID_STATUS_DND: return GG_STATUS_DND | mask;
+ case ID_STATUS_FREECHAT: return GG_STATUS_FFC | mask;
+ case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE | mask;
+ default: return GG_STATUS_BUSY | mask;
+ }
+ }
+}
+
+int GGPROTO::status_gg2m(int status)
+{
+ // ignore additional flags
+ status = GG_S(status);
+
+ // when user has status description but is offline (show it invisible)
+ if (status == GG_STATUS_NOT_AVAIL_DESCR && db_get_b(NULL, m_szModuleName, GG_KEY_SHOWINVISIBLE, GG_KEYDEF_SHOWINVISIBLE))
+ return ID_STATUS_INVISIBLE;
+
+ // rest of cases
+ switch(status)
+ {
+ case GG_STATUS_NOT_AVAIL:
+ case GG_STATUS_NOT_AVAIL_DESCR:
+ return ID_STATUS_OFFLINE;
+
+ case GG_STATUS_AVAIL:
+ case GG_STATUS_AVAIL_DESCR:
+ return ID_STATUS_ONLINE;
+
+ case GG_STATUS_BUSY:
+ case GG_STATUS_BUSY_DESCR:
+ return ID_STATUS_AWAY;
+
+ case GG_STATUS_DND:
+ case GG_STATUS_DND_DESCR:
+ return ID_STATUS_DND;
+
+ case GG_STATUS_FFC:
+ case GG_STATUS_FFC_DESCR:
+ return ID_STATUS_FREECHAT;
+
+ case GG_STATUS_INVISIBLE:
+ case GG_STATUS_INVISIBLE_DESCR:
+ return ID_STATUS_INVISIBLE;
+
+ case GG_STATUS_BLOCKED:
+ return ID_STATUS_NA;
+
+ default:
+ return ID_STATUS_OFFLINE;
+ }
+}
+
+////////////////////////////////////////////////////////////
+// Called when contact status is changed
+
+void GGPROTO::changecontactstatus(uin_t uin, int status, const char *idescr, int time, uint32_t remote_ip, uint16_t remote_port, uint32_t version)
+{
+ HANDLE hContact = getcontact(uin, 0, 0, NULL);
+
+ // Check if contact is on list
+ if (!hContact) return;
+
+ // Write contact status
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)status_gg2m(status));
+
+ // Check if there's description and if it's not empty
+ if (idescr && *idescr)
+ {
+ netlog("gg_changecontactstatus(): Saving for %d status descr \"%s\".", uin, idescr);
+ db_set_s(hContact, "CList", GG_KEY_STATUSDESCR, idescr);
+ }
+ else
+ // Remove status if there's nothing
+ db_unset(hContact, "CList", GG_KEY_STATUSDESCR);
+
+ // Store contact ip and port
+ if (remote_ip) db_set_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, (DWORD) swap32(remote_ip));
+ if (remote_port) db_set_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, (WORD) remote_port);
+ if (version)
+ {
+ char sversion[48];
+ db_set_dw(hContact, m_szModuleName, GG_KEY_CLIENTVERSION, (DWORD) version);
+ mir_snprintf(sversion, sizeof(sversion), "%sGadu-Gadu %s", (version & 0x00ffffff) > 0x2b ? "Nowe " : "", gg_version2string(version));
+ db_set_s(hContact, m_szModuleName, "MirVer", sversion);
+ }
+}
+
+////////////////////////////////////////////////////////////
+// Returns GG client version string from packet version
+const char *gg_version2string(int v)
+{
+ const char *pstr = "???";
+ v &= 0x00ffffff;
+ switch(v)
+ {
+ case 0x2e:
+ pstr = "8.0 build 8283"; break;
+ case 0x2d:
+ pstr = "8.0 build 4881"; break;
+ case 0x2b:
+ pstr = "< 8.0"; break;
+ case 0x2a:
+ pstr = "7.7 build 3315"; break;
+ case 0x29:
+ pstr = "7.6 build 1688"; break;
+ case 0x28:
+ pstr = "7.5 build 2201"; break;
+ case 0x27:
+ pstr = "7.0 build 22"; break;
+ case 0x26:
+ pstr = "7.0 build 20"; break;
+ case 0x25:
+ pstr = "7.0 build 1"; break;
+ case 0x24:
+ pstr = "6.1 (155) / 7.6 (1359)"; break;
+ case 0x22:
+ pstr = "6.0 build 140"; break;
+ case 0x21:
+ pstr = "6.0 build 133"; break;
+ case 0x20:
+ pstr = "6.0b"; break;
+ case 0x1e:
+ pstr = "5.7b build 121"; break;
+ case 0x1c:
+ pstr = "5.7b"; break;
+ case 0x1b:
+ pstr = "5.0.5"; break;
+ case 0x19:
+ pstr = "5.0.3"; break;
+ case 0x18:
+ pstr = "5.0.0-1"; break;
+ case 0x17:
+ pstr = "4.9.2"; break;
+ case 0x16:
+ pstr = "4.9.1"; break;
+ case 0x15:
+ pstr = "4.8.9"; break;
+ case 0x14:
+ pstr = "4.8.1-3"; break;
+ case 0x11:
+ pstr = "4.6.1-10"; break;
+ case 0x10:
+ pstr = "4.5.15-22"; break;
+ case 0x0f:
+ pstr = "4.5.12"; break;
+ case 0x0b:
+ pstr = "4.0.25-30"; break;
+ default:
+ if (v < 0x0b)
+ pstr = "< 4.0.25";
+ else if (v > 0x2e)
+ pstr = ">= 8.0";
+ break;
+ }
+ return pstr;
+}
diff --git a/protocols/Gadu-Gadu/src/dialogs.cpp b/protocols/Gadu-Gadu/src/dialogs.cpp
new file mode 100644
index 0000000000..345624715b
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/dialogs.cpp
@@ -0,0 +1,1016 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+static INT_PTR CALLBACK gg_genoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static INT_PTR CALLBACK gg_confoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static INT_PTR CALLBACK gg_advoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+extern INT_PTR CALLBACK gg_userutildlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+////////////////////////////////////////////////////////////////////////////////
+// SetValue
+
+#define SVS_NORMAL 0
+#define SVS_GENDER 1
+#define SVS_ZEROISUNSPEC 2
+#define SVS_IP 3
+#define SVS_COUNTRY 4
+#define SVS_MONTH 5
+#define SVS_SIGNED 6
+#define SVS_TIMEZONE 7
+#define SVS_GGVERSION 9
+
+static void SetValue(HWND hwndDlg, int idCtrl, HANDLE hContact, char *szModule, char *szSetting, int special, int disableIfUndef)
+{
+ DBVARIANT dbv = {0};
+ char str[80], *pstr = NULL;
+ int unspecified = 0;
+
+ dbv.type = DBVT_DELETED;
+ if (szModule == NULL) unspecified = 1;
+ else unspecified = DBGetContactSettingW(hContact, szModule, szSetting, &dbv);
+ if (!unspecified) {
+ switch (dbv.type) {
+ case DBVT_BYTE:
+ if (special == SVS_GENDER) {
+ if (dbv.cVal == 'M') pstr = Translate("Male");
+ else if (dbv.cVal == 'F') pstr = Translate("Female");
+ else unspecified = 1;
+ }
+ else if (special == SVS_MONTH) {
+ if (dbv.bVal > 0 && dbv.bVal <= 12) {
+ pstr = str;
+ GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1 - 1 + dbv.bVal, str, SIZEOF(str));
+ }
+ else unspecified = 1;
+ }
+ else if (special == SVS_TIMEZONE) {
+ if (dbv.cVal == -100) unspecified = 1;
+ else {
+ pstr = str;
+ mir_snprintf(str, SIZEOF(str), dbv.cVal ? "GMT%+d:%02d" : "GMT", -dbv.cVal / 2, (dbv.cVal & 1) * 30);
+ }
+ }
+ else {
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.bVal == 0);
+ pstr = _itoa(special == SVS_SIGNED ? dbv.cVal : dbv.bVal, str, 10);
+ }
+ break;
+ case DBVT_WORD:
+ if (special == SVS_COUNTRY) {
+ pstr = (char*)CallService(MS_UTILS_GETCOUNTRYBYNUMBER, dbv.wVal, 0);
+ unspecified = pstr == NULL;
+ }
+ else {
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.wVal == 0);
+ pstr = _itoa(special == SVS_SIGNED ? dbv.sVal : dbv.wVal, str, 10);
+ }
+ break;
+ case DBVT_DWORD:
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.dVal == 0);
+ if (special == SVS_IP) {
+ struct in_addr ia;
+ ia.S_un.S_addr = htonl(dbv.dVal);
+ pstr = inet_ntoa(ia);
+ if (dbv.dVal == 0) unspecified = 1;
+ }
+ else if (special == SVS_GGVERSION)
+ pstr = (char *)gg_version2string(dbv.dVal);
+ else
+ pstr = _itoa(special == SVS_SIGNED ? dbv.lVal : dbv.dVal, str, 10);
+ break;
+ case DBVT_ASCIIZ:
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0');
+ pstr = dbv.pszVal;
+ break;
+ default: pstr = str; lstrcpyA(str, "???"); break;
+ }
+ }
+
+ if (disableIfUndef) {
+ EnableWindow(GetDlgItem(hwndDlg, idCtrl), !unspecified);
+ if (unspecified)
+ SetDlgItemText(hwndDlg, idCtrl, TranslateT("<not specified>"));
+ else
+ SetDlgItemTextA(hwndDlg, idCtrl, pstr);
+ }
+ else {
+ EnableWindow(GetDlgItem(hwndDlg, idCtrl), TRUE);
+ if (!unspecified)
+ SetDlgItemTextA(hwndDlg, idCtrl, pstr);
+ }
+ DBFreeVariant(&dbv);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Options Page : Init
+
+int GGPROTO::options_init(WPARAM wParam, LPARAM lParam)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.cbSize = sizeof(odp);
+ odp.flags = ODPF_TCHAR;
+ odp.position = 1003000;
+ odp.hInstance = hInstance;
+ odp.ptszGroup = LPGENT("Network");
+ odp.ptszTitle = m_tszUserName;
+ odp.dwInitParam = (LPARAM)this;
+
+ odp.ptszTab = LPGENT("General");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_GENERAL);
+ odp.pfnDlgProc = gg_genoptsdlgproc;
+ odp.flags = ODPF_TCHAR | ODPF_BOLDGROUPS | ODPF_DONTTRANSLATE;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Conference");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_CONFERENCE);
+ odp.pfnDlgProc = gg_confoptsdlgproc;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Advanced");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_ADVANCED);
+ odp.pfnDlgProc = gg_advoptsdlgproc;
+ odp.flags |= ODPF_EXPERTONLY;
+ Options_AddPage(wParam, &odp);
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if new user data has been filled in for specified account
+void GGPROTO::checknewuser(uin_t uin, const char* passwd)
+{
+ char oldpasswd[128];
+ DBVARIANT dbv;
+ uin_t olduin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0);
+
+ oldpasswd[0] = '\0';
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ if (dbv.pszVal) strcpy(oldpasswd, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ if (uin > 0 && strlen(passwd) > 0 && (uin != olduin || strcmp(oldpasswd, passwd)))
+ check_first_conn = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Options Page : Proc
+
+static void gg_optsdlgcheck(HWND hwndDlg)
+{
+ TCHAR text[128];
+ GetDlgItemText(hwndDlg, IDC_UIN, text, SIZEOF(text));
+ if (text[0]) {
+ GetDlgItemText(hwndDlg, IDC_EMAIL, text, SIZEOF(text));
+ if (text[0])
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_SHOW);
+ else
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_HIDE);
+ }
+ else {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_SHOW);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Proc: General options dialog
+static INT_PTR CALLBACK gg_genoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ DBVARIANT dbv;
+ DWORD num;
+ GGPROTO *gg = (GGPROTO *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
+
+ TranslateDialogDefault(hwndDlg);
+ if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0))
+ {
+ SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_HIDE);
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE);
+ }
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE);
+ }
+
+ CheckDlgButton(hwndDlg, IDC_FRIENDSONLY, db_get_b(NULL, gg->m_szModuleName, GG_KEY_FRIENDSONLY, GG_KEYDEF_FRIENDSONLY));
+ CheckDlgButton(hwndDlg, IDC_SHOWINVISIBLE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWINVISIBLE, GG_KEYDEF_SHOWINVISIBLE));
+ CheckDlgButton(hwndDlg, IDC_LEAVESTATUSMSG, db_get_b(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUSMSG, GG_KEYDEF_LEAVESTATUSMSG));
+ if (gg->gc_enabled)
+ CheckDlgButton(hwndDlg, IDC_IGNORECONF, db_get_b(NULL, gg->m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF));
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IGNORECONF), FALSE);
+ CheckDlgButton(hwndDlg, IDC_IGNORECONF, TRUE);
+ }
+ CheckDlgButton(hwndDlg, IDC_IMGRECEIVE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_IMGRECEIVE, GG_KEYDEF_IMGRECEIVE));
+ CheckDlgButton(hwndDlg, IDC_SHOWLINKS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWLINKS, GG_KEYDEF_SHOWLINKS));
+ CheckDlgButton(hwndDlg, IDC_ENABLEAVATARS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS));
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_LEAVESTATUS), IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMGMETHOD), IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE));
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("<Last Status>")); // 0
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Online")); // ID_STATUS_ONLINE
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Away")); // ID_STATUS_AWAY
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("DND")); // ID_STATUS_DND
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Free for chat")); // ID_STATUS_FREECHAT
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Invisible")); // ID_STATUS_INVISIBLE
+ switch(db_get_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS)) {
+ case ID_STATUS_ONLINE:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 1, 0);
+ break;
+ case ID_STATUS_AWAY:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 2, 0);
+ break;
+ case ID_STATUS_DND:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 3, 0);
+ break;
+ case ID_STATUS_FREECHAT:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 4, 0);
+ break;
+ case ID_STATUS_INVISIBLE:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 5, 0);
+ break;
+ default:
+ SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 0, 0);
+ }
+
+ SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("System tray icon"));
+ SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("Popup window"));
+ SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("Message with [img] BBCode"));
+ SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_SETCURSEL,
+ db_get_b(NULL, gg->m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD), 0);
+ break;
+ }
+ case WM_COMMAND:
+ {
+ if ((LOWORD(wParam) == IDC_UIN || LOWORD(wParam) == IDC_PASSWORD || LOWORD(wParam) == IDC_EMAIL)
+ && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus()))
+ return 0;
+
+ switch (LOWORD(wParam)) {
+ case IDC_EMAIL:
+ case IDC_UIN:
+ gg_optsdlgcheck(hwndDlg);
+ break;
+
+ case IDC_LEAVESTATUSMSG:
+ EnableWindow(GetDlgItem(hwndDlg, IDC_LEAVESTATUS), IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG));
+ break;
+
+ case IDC_IMGRECEIVE:
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMGMETHOD), IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE));
+ break;
+
+ case IDC_LOSTPASS:
+ {
+ char email[128];
+ uin_t uin;
+ GetDlgItemTextA(hwndDlg, IDC_UIN, email, sizeof(email));
+ uin = atoi(email);
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email));
+ if (!strlen(email))
+ MessageBox(NULL, TranslateT("You need to specify your registration e-mail first."),
+ gg->m_tszUserName, MB_OK | MB_ICONEXCLAMATION);
+ else if (MessageBox(NULL,
+ TranslateT("Your password will be sent to your registration e-mail.\nDo you want to continue ?"),
+ gg->m_tszUserName,
+ MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
+ gg->remindpassword(uin, email);
+ return FALSE;
+ }
+ case IDC_CREATEACCOUNT:
+ case IDC_REMOVEACCOUNT:
+ if (gg->isonline())
+ {
+ if (MessageBox(
+ NULL,
+ TranslateT("You should disconnect before making any permanent changes with your account.\nDo you want to disconnect now ?"),
+ gg->m_tszUserName,
+ MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
+ break;
+ else
+ gg->disconnect();
+ }
+ case IDC_CHPASS:
+ case IDC_CHEMAIL:
+ {
+ // Readup data
+ GGUSERUTILDLGDATA dat;
+ int ret;
+ char pass[128], email[128];
+ GetDlgItemTextA(hwndDlg, IDC_UIN, pass, sizeof(pass));
+ dat.uin = atoi(pass);
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass));
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email));
+ dat.pass = pass;
+ dat.email = email;
+ dat.gg = gg;
+ if (LOWORD(wParam) == IDC_CREATEACCOUNT)
+ {
+ dat.mode = GG_USERUTIL_CREATE;
+ ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CREATEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat);
+ }
+ else if (LOWORD(wParam) == IDC_CHPASS)
+ {
+ dat.mode = GG_USERUTIL_PASS;
+ ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHPASS), hwndDlg, gg_userutildlgproc, (LPARAM)&dat);
+ }
+ else if (LOWORD(wParam) == IDC_CHEMAIL)
+ {
+ dat.mode = GG_USERUTIL_EMAIL;
+ ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHEMAIL), hwndDlg, gg_userutildlgproc, (LPARAM)&dat);
+ }
+ else
+ {
+ dat.mode = GG_USERUTIL_REMOVE;
+ ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_REMOVEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat);
+ }
+
+ if (ret == IDOK)
+ {
+ DBVARIANT dbv;
+ DWORD num;
+ // Show reload required window
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW);
+
+ // Update uin
+ if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0))
+ SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num));
+ else
+ SetDlgItemTextA(hwndDlg, IDC_UIN, "");
+
+ // Update password
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_PASSWORD, "");
+
+ // Update e-mail
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_EMAIL, "");
+
+ // Update links
+ gg_optsdlgcheck(hwndDlg);
+
+ // Remove details
+ if (LOWORD(wParam) != IDC_CHPASS && LOWORD(wParam) != IDC_CHEMAIL)
+ {
+ db_unset(NULL, gg->m_szModuleName, GG_KEY_NICK);
+ db_unset(NULL, gg->m_szModuleName, "NickName");
+ db_unset(NULL, gg->m_szModuleName, "City");
+ db_unset(NULL, gg->m_szModuleName, "FirstName");
+ db_unset(NULL, gg->m_szModuleName, "LastName");
+ db_unset(NULL, gg->m_szModuleName, "FamilyName");
+ db_unset(NULL, gg->m_szModuleName, "CityOrigin");
+ db_unset(NULL, gg->m_szModuleName, "Age");
+ db_unset(NULL, gg->m_szModuleName, "BirthYear");
+ db_unset(NULL, gg->m_szModuleName, "Gender");
+ }
+ }
+ }
+ break;
+ }
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ case WM_NOTIFY:
+ {
+ switch (((LPNMHDR) lParam)->code) {
+ case PSN_APPLY:
+ {
+ int status_flags = GG_STATUS_FLAG_UNKNOWN;
+ char str[128];
+ uin_t uin;
+
+ // Write Gadu-Gadu number & password
+ GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str));
+ uin = atoi(str);
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(str));
+ CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), (LPARAM) str);
+ gg->checknewuser(uin, str);
+ db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, uin);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, str);
+
+ // Write Gadu-Gadu email
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, str, sizeof(str));
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, str);
+
+ // Write checkboxes
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_FRIENDSONLY, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_FRIENDSONLY));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWINVISIBLE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWINVISIBLE));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUSMSG, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG));
+ if (gg->gc_enabled)
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_IGNORECONF, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_IGNORECONF));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_IMGRECEIVE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWLINKS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWLINKS));
+ if (IsDlgButtonChecked(hwndDlg, IDC_SHOWLINKS))
+ status_flags |= GG_STATUS_FLAG_SPAM;
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_change_status_flags(gg->sess, status_flags);
+ LeaveCriticalSection(&gg->sess_mutex);
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_ENABLEAVATARS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ENABLEAVATARS));
+
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_IMGMETHOD,
+ (BYTE)SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_GETCURSEL, 0, 0));
+
+ // Write leave status
+ switch(SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_GETCURSEL, 0, 0))
+ {
+ case 1:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_ONLINE);
+ break;
+ case 2:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_AWAY);
+ break;
+ case 3:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_DND);
+ break;
+ case 4:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_FREECHAT);
+ break;
+ case 5:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_INVISIBLE);
+ break;
+ default:
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Proc: Conference options dialog
+
+static INT_PTR CALLBACK gg_confoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ DWORD num;
+ GGPROTO *gg = (GGPROTO *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
+
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_SETCURSEL,
+ db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL), 0);
+
+ if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL))
+ SetDlgItemTextA(hwndDlg, IDC_GC_COUNT_TOTAL, ditoa(num));
+
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_SETCURSEL,
+ db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN), 0);
+
+ if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN))
+ SetDlgItemTextA(hwndDlg, IDC_GC_COUNT_UNKNOWN, ditoa(num));
+
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore"));
+ SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_SETCURSEL,
+ db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT), 0);
+ break;
+ }
+ case WM_COMMAND:
+ {
+ if ((LOWORD(wParam) == IDC_GC_COUNT_TOTAL || LOWORD(wParam) == IDC_GC_COUNT_UNKNOWN)
+ && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus()))
+ return 0;
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ case WM_NOTIFY:
+ {
+ switch (((LPNMHDR) lParam)->code) {
+ case PSN_APPLY:
+ {
+ char str[128];
+
+ // Write groupchat policy
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_TOTAL,
+ (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_GETCURSEL, 0, 0));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN,
+ (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_GETCURSEL, 0, 0));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_DEFAULT,
+ (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_GETCURSEL, 0, 0));
+
+ GetDlgItemTextA(hwndDlg, IDC_GC_COUNT_TOTAL, str, sizeof(str));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_TOTAL, (WORD)atoi(str));
+ GetDlgItemTextA(hwndDlg, IDC_GC_COUNT_UNKNOWN, str, sizeof(str));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, (WORD)atoi(str));
+
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Proc: Advanced options dialog
+static INT_PTR CALLBACK gg_advoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ DBVARIANT dbv;
+ DWORD num;
+ GGPROTO *gg = (GGPROTO *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
+
+ TranslateDialogDefault(hwndDlg);
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_SERVERHOSTS, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_HOST, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_HOST, GG_KEYDEF_SERVERHOSTS);
+
+ CheckDlgButton(hwndDlg, IDC_KEEPALIVE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_KEEPALIVE, GG_KEYDEF_KEEPALIVE));
+ CheckDlgButton(hwndDlg, IDC_SHOWCERRORS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWCERRORS, GG_KEYDEF_SHOWCERRORS));
+ CheckDlgButton(hwndDlg, IDC_ARECONNECT, db_get_b(NULL, gg->m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT));
+ CheckDlgButton(hwndDlg, IDC_MSGACK, db_get_b(NULL, gg->m_szModuleName, GG_KEY_MSGACK, GG_KEYDEF_MSGACK));
+ CheckDlgButton(hwndDlg, IDC_MANUALHOST, db_get_b(NULL, gg->m_szModuleName, GG_KEY_MANUALHOST, GG_KEYDEF_MANUALHOST));
+ CheckDlgButton(hwndDlg, IDC_SSLCONN, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SSLCONN, GG_KEYDEF_SSLCONN));
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_HOST), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PORT), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST));
+
+ CheckDlgButton(hwndDlg, IDC_DIRECTCONNS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_DIRECTCONNS, GG_KEYDEF_DIRECTCONNS));
+ if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_DIRECTPORT, GG_KEYDEF_DIRECTPORT))
+ SetDlgItemTextA(hwndDlg, IDC_DIRECTPORT, ditoa(num));
+ CheckDlgButton(hwndDlg, IDC_FORWARDING, db_get_b(NULL, gg->m_szModuleName, GG_KEY_FORWARDING, GG_KEYDEF_FORWARDING));
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_FORWARDHOST, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_FORWARDHOST, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_FORWARDPORT, GG_KEYDEF_FORWARDPORT))
+ SetDlgItemTextA(hwndDlg, IDC_FORWARDPORT, ditoa(num));
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_DIRECTPORT), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDING), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDPORT), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDHOST), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ break;
+ }
+ case WM_COMMAND:
+ {
+ if ((LOWORD(wParam) == IDC_DIRECTPORT || LOWORD(wParam) == IDC_FORWARDHOST || LOWORD(wParam) == IDC_FORWARDPORT)
+ && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus()))
+ return 0;
+ switch (LOWORD(wParam)) {
+ case IDC_MANUALHOST:
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_HOST), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PORT), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW);
+ break;
+ }
+ case IDC_DIRECTCONNS:
+ case IDC_FORWARDING:
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_DIRECTPORT), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDING), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDPORT), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDHOST), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW);
+ break;
+ }
+ }
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ case WM_NOTIFY:
+ {
+ switch (((LPNMHDR) lParam)->code) {
+ case PSN_APPLY:
+ {
+ char str[512];
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_KEEPALIVE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_KEEPALIVE));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWCERRORS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWCERRORS));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_ARECONNECT, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ARECONNECT));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_MSGACK, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_MSGACK));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_MANUALHOST, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_SSLCONN, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SSLCONN));
+
+ // Transfer settings
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_DIRECTCONNS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS));
+ db_set_b(NULL, gg->m_szModuleName, GG_KEY_FORWARDING, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_FORWARDING));
+
+ // Write custom servers
+ GetDlgItemTextA(hwndDlg, IDC_HOST, str, sizeof(str));
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_SERVERHOSTS, str);
+
+ // Write direct port
+ GetDlgItemTextA(hwndDlg, IDC_DIRECTPORT, str, sizeof(str));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_DIRECTPORT, (WORD)atoi(str));
+ // Write forwarding host
+ GetDlgItemTextA(hwndDlg, IDC_FORWARDHOST, str, sizeof(str));
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_FORWARDHOST, str);
+ GetDlgItemTextA(hwndDlg, IDC_FORWARDPORT, str, sizeof(str));
+ db_set_w(NULL, gg->m_szModuleName, GG_KEY_FORWARDPORT, (WORD)atoi(str));
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Info Page : Data
+struct GGDETAILSDLGDATA
+{
+ GGPROTO *gg;
+ HANDLE hContact;
+ int disableUpdate;
+ int updating;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Info Page : Proc
+static INT_PTR CALLBACK gg_detailsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ struct GGDETAILSDLGDATA *dat = (struct GGDETAILSDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ dat = (struct GGDETAILSDLGDATA *)mir_alloc(sizeof(struct GGDETAILSDLGDATA));
+ dat->hContact=(HANDLE)lParam;
+ dat->disableUpdate = FALSE;
+ dat->updating = FALSE;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
+ // Add genders
+ if (!dat->hContact)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)_T("")); // 0
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Female")); // 1
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Male")); // 2
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_PARAMCHANGED:
+ dat->gg = (GGPROTO *)((LPPSHNOTIFY)lParam)->lParam;
+ break;
+
+ case PSN_INFOCHANGED:
+ {
+ char *szProto;
+ HANDLE hContact = (HANDLE)((LPPSHNOTIFY)lParam)->lParam;
+ GGPROTO *gg = dat->gg;
+
+ // Show updated message
+ if (dat && dat->updating)
+ {
+ MessageBox(NULL, TranslateT("Your details has been uploaded to the public directory."),
+ gg->m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ dat->updating = FALSE;
+ break;
+ }
+
+ if (hContact == NULL)
+ szProto = gg->m_szModuleName;
+ else
+ szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+ if (szProto == NULL)
+ break;
+
+ // Disable when updating
+ if (dat) dat->disableUpdate = TRUE;
+
+ SetValue(hwndDlg, IDC_UIN, hContact, szProto, GG_KEY_UIN, 0, hContact != NULL);
+ SetValue(hwndDlg, IDC_REALIP, hContact, szProto, GG_KEY_CLIENTIP, SVS_IP, hContact != NULL);
+ SetValue(hwndDlg, IDC_PORT, hContact, szProto, GG_KEY_CLIENTPORT, SVS_ZEROISUNSPEC, hContact != NULL);
+ SetValue(hwndDlg, IDC_VERSION, hContact, szProto, GG_KEY_CLIENTVERSION, SVS_GGVERSION, hContact != NULL);
+
+ SetValue(hwndDlg, IDC_FIRSTNAME, hContact, szProto, "FirstName", SVS_NORMAL, hContact != NULL);
+ SetValue(hwndDlg, IDC_LASTNAME, hContact, szProto, "LastName", SVS_NORMAL, hContact != NULL);
+ SetValue(hwndDlg, IDC_NICKNAME, hContact, szProto, "NickName", SVS_NORMAL, hContact != NULL);
+ SetValue(hwndDlg, IDC_BIRTHYEAR, hContact, szProto, "BirthYear", SVS_ZEROISUNSPEC, hContact != NULL);
+ SetValue(hwndDlg, IDC_CITY, hContact, szProto, "City", SVS_NORMAL, hContact != NULL);
+ SetValue(hwndDlg, IDC_FAMILYNAME, hContact, szProto, "FamilyName", SVS_NORMAL, hContact != NULL);
+ SetValue(hwndDlg, IDC_CITYORIGIN, hContact, szProto, "CityOrigin", SVS_NORMAL, hContact != NULL);
+
+ if (hContact)
+ {
+ SetValue(hwndDlg, IDC_GENDER, hContact, szProto, "Gender", SVS_GENDER, hContact != NULL);
+ SetValue(hwndDlg, IDC_STATUSDESCR, hContact, "CList", GG_KEY_STATUSDESCR, SVS_NORMAL, hContact != NULL);
+ }
+ else switch((char)db_get_b(hContact, gg->m_szModuleName, "Gender", (BYTE)'?'))
+ {
+ case 'F':
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 1, 0);
+ break;
+ case 'M':
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 2, 0);
+ break;
+ default:
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 0, 0);
+ }
+
+ // Disable when updating
+ if (dat) dat->disableUpdate = FALSE;
+ break;
+ }
+ }
+ break;
+ }
+ break;
+ case WM_COMMAND:
+ if (dat && !dat->hContact && LOWORD(wParam) == IDC_SAVE && HIWORD(wParam) == BN_CLICKED)
+ {
+ // Save user data
+ char text[256];
+ gg_pubdir50_t req;
+ GGPROTO *gg = dat->gg;
+
+ if (!gg->isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be logged in before you can change your details."),
+ gg->m_tszUserName, MB_OK | MB_ICONSTOP);
+ break;
+ }
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SAVE), FALSE);
+
+ req = gg_pubdir50_new(GG_PUBDIR50_WRITE);
+
+ GetDlgItemTextA(hwndDlg, IDC_FIRSTNAME, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, text);
+
+ GetDlgItemTextA(hwndDlg, IDC_LASTNAME, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, text);
+
+ GetDlgItemTextA(hwndDlg, IDC_NICKNAME, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, text);
+
+ GetDlgItemTextA(hwndDlg, IDC_CITY, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_CITY, text);
+
+ // Gadu-Gadu Female <-> Male
+ switch(SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_GETCURSEL, 0, 0))
+ {
+ case 1:
+ gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_FEMALE);
+ break;
+ case 2:
+ gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_MALE);
+ break;
+ default:
+ gg_pubdir50_add(req, GG_PUBDIR50_GENDER, "");
+ }
+
+ GetDlgItemTextA(hwndDlg, IDC_BIRTHYEAR, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, text);
+
+ GetDlgItemTextA(hwndDlg, IDC_FAMILYNAME, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FAMILYNAME, text);
+
+ GetDlgItemTextA(hwndDlg, IDC_CITYORIGIN, text, sizeof(text));
+ if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FAMILYCITY, text);
+
+ // Run update
+ gg_pubdir50_seq_set(req, GG_SEQ_CHINFO);
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_pubdir50(gg->sess, req);
+ LeaveCriticalSection(&gg->sess_mutex);
+ dat->updating = TRUE;
+
+ gg_pubdir50_free(req);
+ }
+
+ if (dat && !dat->hContact && !dat->disableUpdate && (HIWORD(wParam) == EN_CHANGE && (
+ LOWORD(wParam) == IDC_NICKNAME || LOWORD(wParam) == IDC_FIRSTNAME || LOWORD(wParam) == IDC_LASTNAME || LOWORD(wParam) == IDC_FAMILYNAME ||
+ LOWORD(wParam) == IDC_CITY || LOWORD(wParam) == IDC_CITYORIGIN || LOWORD(wParam) == IDC_BIRTHYEAR) ||
+ HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_GENDER))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SAVE), TRUE);
+
+ switch(LOWORD(wParam)) {
+ case IDCANCEL:
+ SendMessage(GetParent(hwndDlg),msg,wParam,lParam);
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ if (dat) mir_free(dat);
+ break;
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Info Page : Init
+
+int GGPROTO::details_init(WPARAM wParam, LPARAM lParam)
+{
+ char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, lParam, 0);
+ if ((szProto == NULL || strcmp(szProto, m_szModuleName)) && lParam || lParam && db_get_b((HANDLE)lParam, m_szModuleName, "ChatRoom", 0))
+ return 0;
+
+ // Here goes init
+ {
+ OPTIONSDIALOGPAGE odp = {0};
+
+ odp.cbSize = sizeof(odp);
+ odp.flags = ODPF_DONTTRANSLATE | ODPF_TCHAR;
+ odp.hInstance = hInstance;
+ odp.pfnDlgProc = gg_detailsdlgproc;
+ odp.position = -1900000000;
+ odp.pszTemplate = ((HANDLE)lParam != NULL) ? MAKEINTRESOURCEA(IDD_INFO_GG) : MAKEINTRESOURCEA(IDD_CHINFO_GG);
+ odp.ptszTitle = m_tszUserName;
+ odp.dwInitParam = (LPARAM)this;
+ UserInfo_AddPage(wParam, &odp);
+ }
+
+ // Start search for user data
+ if ((HANDLE)lParam == NULL)
+ GetInfo(NULL, 0);
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Proc: Account manager options dialog
+INT_PTR CALLBACK gg_acc_mgr_guidlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+////////////////////////////////////////////////////////////////////////////////////////////
+{
+ GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ DBVARIANT dbv;
+ DWORD num;
+ GGPROTO *gg = (GGPROTO *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
+
+ TranslateDialogDefault(hwndDlg);
+ if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0))
+ SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num));
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_CREATEACCOUNT:
+ {
+ // Readup data
+ GGUSERUTILDLGDATA dat;
+ int ret;
+ char pass[128], email[128];
+ GetDlgItemTextA(hwndDlg, IDC_UIN, pass, sizeof(pass));
+ dat.uin = atoi(pass);
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass));
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email));
+ dat.pass = pass;
+ dat.email = email;
+ dat.gg = gg;
+ dat.mode = GG_USERUTIL_CREATE;
+ ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CREATEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat);
+
+ if (ret == IDOK)
+ {
+ DBVARIANT dbv;
+ DWORD num;
+ // Show reload required window
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW);
+
+ // Update uin
+ if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0))
+ SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num));
+ else
+ SetDlgItemTextA(hwndDlg, IDC_UIN, "");
+
+ // Update password
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_PASSWORD, "");
+
+ // Update e-mail
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) {
+ SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_EMAIL, "");
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch(((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR) lParam)->code) {
+ case PSN_APPLY:
+ {
+ char str[128];
+ uin_t uin;
+
+ // Write Gadu-Gadu number & password
+ GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str));
+ uin = atoi(str);
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(str));
+ CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), (LPARAM) str);
+ gg->checknewuser(uin, str);
+ db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, uin);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, str);
+
+ // Write Gadu-Gadu email
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, str, sizeof(str));
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, str);
+ }
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
diff --git a/protocols/Gadu-Gadu/src/dynstuff.cpp b/protocols/Gadu-Gadu/src/dynstuff.cpp
new file mode 100644
index 0000000000..9ae41daf59
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/dynstuff.cpp
@@ -0,0 +1,612 @@
+/* $Id: dynstuff.c 11259 2010-02-17 04:47:22Z borkra $ */
+
+/*
+ * (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Dawid Jarosz <dawjar@poczta.onet.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "gg.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * list_add_sorted()
+ *
+ * dodaje do listy dany element. przy okazji może też skopiować zawarto¶ć.
+ * je¶li poda się jako ostatni parametr funkcję porównuj±c± zawarto¶ć
+ * elementów, może posortować od razu.
+ *
+ * - list - wskaĽnik do listy,
+ * - data - wskaĽnik do elementu,
+ * - alloc_size - rozmiar elementu, je¶li chcemy go skopiować.
+ *
+ * zwraca wskaĽnik zaalokowanego elementu lub NULL w przpadku błędu.
+ */
+void *list_add_sorted(list_t *list, void *data, int alloc_size, int (*comparision)(void *, void *))
+{
+ if (!list) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ list_t newlist = (list_t)malloc(sizeof(struct list));
+
+ newlist->data = data;
+ newlist->next = NULL;
+
+ if (alloc_size) {
+ newlist->data = malloc(alloc_size);
+ memcpy(newlist->data, data, alloc_size);
+ }
+
+ list_t tmp;
+ if (!(tmp = *list)) {
+ *list = newlist;
+ } else {
+ if (!comparision) {
+ while (tmp->next)
+ tmp = tmp->next;
+ tmp->next = newlist;
+ } else {
+ list_t prev = NULL;
+
+ while (comparision(newlist->data, tmp->data) > 0) {
+ prev = tmp;
+ tmp = tmp->next;
+ if (!tmp)
+ break;
+ }
+
+ if (!prev) {
+ tmp = *list;
+ *list = newlist;
+ newlist->next = tmp;
+ } else {
+ prev->next = newlist;
+ newlist->next = tmp;
+ }
+ }
+ }
+
+ return newlist->data;
+}
+
+/*
+ * list_add()
+ *
+ * wrapper do list_add_sorted(), który zachowuje poprzedni± składnię.
+ */
+void *list_add(list_t *list, void *data, int alloc_size)
+{
+ return list_add_sorted(list, data, alloc_size, NULL);
+}
+
+/*
+ * list_remove()
+ *
+ * usuwa z listy wpis z podanym elementem.
+ *
+ * - list - wskaĽnik do listy,
+ * - data - element,
+ * - free_data - zwolnić pamięć po elemencie.
+ */
+int list_remove(list_t *list, void *data, int free_data)
+{
+ list_t tmp, last = NULL;
+
+ if (!list || !*list) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ tmp = *list;
+ if (tmp->data == data) {
+ *list = tmp->next;
+ } else {
+ for (; tmp && tmp->data != data; tmp = tmp->next)
+ last = tmp;
+ if (!tmp) {
+ errno = ENOENT;
+ return -1;
+ }
+ last->next = tmp->next;
+ }
+
+ if (free_data)
+ free(tmp->data);
+ free(tmp);
+
+ return 0;
+}
+
+/*
+ * list_count()
+ *
+ * zwraca ilo¶ć elementów w danej li¶cie.
+ *
+ * - list - lista.
+ */
+int list_count(list_t list)
+{
+ int count = 0;
+
+ for (; list; list = list->next)
+ count++;
+
+ return count;
+}
+
+/*
+ * list_destroy()
+ *
+ * niszczy wszystkie elementy listy.
+ *
+ * - list - lista,
+ * - free_data - czy zwalniać bufor danych?
+ */
+int list_destroy(list_t list, int free_data)
+{
+ list_t tmp;
+
+ while (list) {
+ if (free_data)
+ free(list->data);
+
+ tmp = list->next;
+
+ free(list);
+
+ list = tmp;
+ }
+
+ return 0;
+}
+
+/*
+ * string_realloc()
+ *
+ * upewnia się, że w stringu będzie wystarczaj±co dużo miejsca.
+ *
+ * - s - ci±g znaków,
+ * - count - wymagana ilo¶ć znaków (bez końcowego '\0').
+ */
+static void string_realloc(string_t s, int count)
+{
+ char *tmp;
+
+ if (s->str && count + 1 <= s->size)
+ return;
+
+ tmp = (char*)realloc(s->str, count + 81);
+ if (!s->str)
+ *tmp = 0;
+ tmp[count + 80] = 0;
+ s->size = count + 81;
+ s->str = tmp;
+}
+
+/*
+ * string_append_c()
+ *
+ * dodaje do danego ci±gu jeden znak, alokuj±c przy tym odpowiedni± ilo¶ć
+ * pamięci.
+ *
+ * - s - ci±g znaków.
+ * - c - znaczek do dopisania.
+ */
+int string_append_c(string_t s, char c)
+{
+ if (!s) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ string_realloc(s, s->len + 1);
+
+ s->str[s->len + 1] = 0;
+ s->str[s->len++] = c;
+
+ return 0;
+}
+
+/*
+ * string_append_n()
+ *
+ * dodaje tekst do bufora alokuj±c odpowiedni± ilo¶ć pamięci.
+ *
+ * - s - ci±g znaków,
+ * - str - tekst do dopisania,
+ * - count - ile znaków tego tekstu dopisać? (-1 znaczy, że cały).
+ */
+int string_append_n(string_t s, const char *str, int count)
+{
+ if (!s || !str) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (count == -1)
+ count = (int)strlen(str);
+
+ string_realloc(s, s->len + count);
+
+ s->str[s->len + count] = 0;
+ strncpy(s->str + s->len, str, count);
+
+ s->len += count;
+
+ return 0;
+}
+
+int string_append(string_t s, const char *str)
+{
+ return string_append_n(s, str, -1);
+}
+
+/*
+ * string_insert_n()
+ *
+ * wstawia tekst w podane miejsce bufora.
+ *
+ * - s - ci±g znaków,
+ * - index - miejsce, gdzie mamy wpisać (liczone od 0),
+ * - str - tekst do dopisania,
+ * - count - ilo¶ć znaków do dopisania (-1 znaczy, że wszystkie).
+ */
+void string_insert_n(string_t s, int index, const char *str, int count)
+{
+ if (!s || !str)
+ return;
+
+ if (count == -1)
+ count = (int)strlen(str);
+
+ if (index > s->len)
+ index = s->len;
+
+ string_realloc(s, s->len + count);
+
+ memmove(s->str + index + count, s->str + index, s->len + 1 - index);
+ memmove(s->str + index, str, count);
+
+ s->len += count;
+}
+
+void string_insert(string_t s, int index, const char *str)
+{
+ string_insert_n(s, index, str, -1);
+}
+
+/*
+ * string_init()
+ *
+ * inicjuje strukturę string. alokuje pamięć i przypisuje pierwsz± warto¶ć.
+ *
+ * - value - je¶li NULL, ci±g jest pusty, inaczej kopiuje tam.
+ *
+ * zwraca zaalokowan± strukturę `string'.
+ */
+string_t string_init(const char *value)
+{
+ string_t tmp = (string_t)malloc(sizeof(struct string));
+
+ if (!value)
+ value = "";
+
+ tmp->str = _strdup(value);
+ tmp->len = (int)strlen(value);
+ tmp->size = (int)strlen(value) + 1;
+
+ return tmp;
+}
+
+/*
+ * string_clear()
+ *
+ * czy¶ci zawarto¶ć struktury `string'.
+ *
+ * - s - ci±g znaków.
+ */
+void string_clear(string_t s)
+{
+ if (!s)
+ return;
+
+ if (s->size > 160) {
+ s->str = (char*)realloc(s->str, 80);
+ s->size = 80;
+ }
+
+ s->str[0] = 0;
+ s->len = 0;
+}
+
+/*
+ * string_free()
+ *
+ * zwalnia pamięć po strukturze string i może też zwolnić pamięć po samym
+ * ci±gu znaków.
+ *
+ * - s - struktura, któr± wycinamy,
+ * - free_string - zwolnić pamięć po ci±gu znaków?
+ *
+ * je¶li free_string=0 zwraca wskaĽnik do ci±gu, inaczej NULL.
+ */
+char *string_free(string_t s, int free_string)
+{
+ char *tmp = NULL;
+
+ if (!s)
+ return NULL;
+
+ if (free_string)
+ free(s->str);
+ else
+ tmp = s->str;
+
+ free(s);
+
+ return tmp;
+}
+
+/*
+ * _itoa()
+ *
+ * prosta funkcja, która zwraca tekstow± reprezentację liczby. w obrębie
+ * danego wywołania jakiej¶ funkcji lub wyrażenia może być wywołania 10
+ * razy, ponieważ tyle mamy statycznych buforów. lepsze to niż ci±głe
+ * tworzenie tymczasowych buforów na stosie i sprintf()owanie.
+ *
+ * - i - liczba do zamiany.
+ *
+ * zwraca adres do bufora, którego _NIE_NALEŻY_ zwalniać.
+ */
+
+const char *ditoa(long int i)
+{
+ static char bufs[10][16];
+ static int index = 0;
+ char *tmp = bufs[index++];
+
+ if (index > 9)
+ index = 0;
+
+ mir_snprintf(tmp, 16, "%ld", i);
+ return tmp;
+}
+
+/*
+ * array_make()
+ *
+ * tworzy tablicę tekstów z jednego, rozdzielonego podanymi znakami.
+ *
+ * - string - tekst wej¶ciowy,
+ * - sep - lista elementów oddzielaj±cych,
+ * - max - maksymalna ilo¶ć elementów tablicy. je¶li równe 0, nie ma
+ * ograniczeń rozmiaru tablicy.
+ * - trim - czy większ± ilo¶ć elementów oddzielaj±cych traktować jako
+ * jeden (na przykład spacje, tabulacja itp.)
+ * - quotes - czy pola mog± być zapisywane w cudzysłowiach lub
+ * apostrofach z escapowanymi znakami.
+ *
+ * zaalokowan± tablicę z zaalokowanymi ci±gami znaków, któr± należy
+ * zwolnić funkcj± array_free()
+ */
+char **array_make(const char *string, const char *sep, int max, int trim, int quotes)
+{
+ const char *p, *q;
+ char **result = NULL;
+ int items = 0, last = 0;
+
+ if (!string || !sep)
+ goto failure;
+
+ for (p = string; ; ) {
+ int len = 0;
+ char *token = NULL;
+
+ if (max && items >= max - 1)
+ last = 1;
+
+ if (trim) {
+ while (*p && strchr(sep, *p))
+ p++;
+ if (!*p)
+ break;
+ }
+
+ if (!last && quotes && (*p == '\'' || *p == '\"')) {
+ char sep = *p;
+
+ for (q = p + 1, len = 0; *q; q++, len++) {
+ if (*q == '\\') {
+ q++;
+ if (!*q)
+ break;
+ } else if (*q == sep)
+ break;
+ }
+
+ if ((token = (char*)calloc(1, len + 1))) {
+ char *r = token;
+
+ for (q = p + 1; *q; q++, r++) {
+ if (*q == '\\') {
+ q++;
+
+ if (!*q)
+ break;
+
+ switch (*q) {
+ case 'n':
+ *r = '\n';
+ break;
+ case 'r':
+ *r = '\r';
+ break;
+ case 't':
+ *r = '\t';
+ break;
+ default:
+ *r = *q;
+ }
+ } else if (*q == sep) {
+ break;
+ } else
+ *r = *q;
+ }
+
+ *r = 0;
+ }
+
+ p = (*q) ? q + 1 : q;
+
+ } else {
+ for (q = p, len = 0; *q && (last || !strchr(sep, *q)); q++, len++);
+ token = (char*)calloc(1, len + 1);
+ strncpy(token, p, len);
+ token[len] = 0;
+ p = q;
+ }
+
+ result = (char**)realloc(result, (items + 2) * sizeof(char*));
+ result[items] = token;
+ result[++items] = NULL;
+
+ if (!*p)
+ break;
+
+ p++;
+ }
+
+failure:
+ if (!items)
+ result = (char**)calloc(1, sizeof(char*));
+
+ return result;
+}
+
+/*
+ * array_count()
+ *
+ * zwraca ilo¶ć elementów tablicy.
+ */
+int array_count(char **array)
+{
+ int result = 0;
+
+ if (!array)
+ return 0;
+
+ while (*array) {
+ result++;
+ array++;
+ }
+
+ return result;
+}
+
+/*
+ * array_add()
+ *
+ * dodaje element do tablicy.
+ */
+void array_add(char ***array, char *string)
+{
+ int count = array_count(*array);
+
+ *array = (char**)realloc(*array, (count + 2) * sizeof(char*));
+ (*array)[count + 1] = NULL;
+ (*array)[count] = string;
+}
+
+/*
+ * array_join()
+ *
+ * ł±czy elementy tablicy w jeden string oddzielaj±c elementy odpowiednim
+ * separatorem.
+ *
+ * - array - wskaĽnik do tablicy,
+ * - sep - seperator.
+ *
+ * zwrócony ci±g znaków należy zwolnić.
+ */
+char *array_join(char **array, const char *sep)
+{
+ string_t s = string_init(NULL);
+ int i;
+
+ if (!array)
+ return _strdup("");
+
+ for (i = 0; array[i]; i++) {
+ if (i)
+ string_append(s, sep);
+
+ string_append(s, array[i]);
+ }
+
+ return string_free(s, 0);
+}
+
+/*
+ * array_contains()
+ *
+ * stwierdza, czy tablica zawiera podany element.
+ *
+ * - array - tablica,
+ * - string - szukany ci±g znaków,
+ * - casesensitive - czy mamy zwracać uwagę na wielko¶ć znaków?
+ *
+ * 0/1
+ */
+int array_contains(char **array, const char *string, int casesensitive)
+{
+ int i;
+
+ if (!array || !string)
+ return 0;
+
+ for (i = 0; array[i]; i++) {
+ if (casesensitive && !strcmp(array[i], string))
+ return 1;
+ if (!casesensitive && !strcasecmp(array[i], string))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * array_free()
+ *
+ * zwalnia pamieć zajmowan± przez tablicę.
+ */
+void array_free(char **array)
+{
+ char **tmp;
+
+ if (!array)
+ return;
+
+ for (tmp = array; *tmp; tmp++)
+ free(*tmp);
+
+ free(array);
+}
diff --git a/protocols/Gadu-Gadu/src/dynstuff.h b/protocols/Gadu-Gadu/src/dynstuff.h
new file mode 100644
index 0000000000..8e36c67ede
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/dynstuff.h
@@ -0,0 +1,70 @@
+/* $Id: dynstuff.h 4366 2006-12-20 22:40:37Z ono $ */
+
+/*
+ * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Dawid Jarosz <dawjar@poczta.onet.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __DYNSTUFF_H
+#define __DYNSTUFF_H
+
+/* listy */
+
+struct list {
+ void *data;
+ struct list *next;
+};
+
+typedef struct list * list_t;
+
+void *list_add(list_t *list, void *data, int alloc_size);
+void *list_add_sorted(list_t *list, void *data, int alloc_size, int (*comparision)(void *, void *));
+int list_remove(list_t *list, void *data, int free_data);
+int list_count(list_t list);
+int list_destroy(list_t list, int free_data);
+
+/* stringi */
+
+struct string {
+ char *str;
+ int len, size;
+};
+
+typedef struct string * string_t;
+
+string_t string_init(const char *str);
+int string_append(string_t s, const char *str);
+int string_append_n(string_t s, const char *str, int count);
+int string_append_c(string_t s, char ch);
+void string_insert(string_t s, int index, const char *str);
+void string_insert_n(string_t s, int index, const char *str, int count);
+void string_clear(string_t s);
+char *string_free(string_t s, int free_string);
+
+/* tablice stringów */
+
+char **array_make(const char *string, const char *sep, int max, int trim, int quotes);
+char *array_join(char **array, const char *sep);
+void array_add(char ***array, char *string);
+int array_count(char **array);
+int array_contains(char **array, const char *string, int casesensitive);
+void array_free(char **array);
+
+/* rozszerzenia libców */
+
+const char *ditoa(long int i);
+
+#endif /* __DYNSTUFF_H */
diff --git a/protocols/Gadu-Gadu/src/filetransfer.cpp b/protocols/Gadu-Gadu/src/filetransfer.cpp
new file mode 100644
index 0000000000..e7560e16a2
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/filetransfer.cpp
@@ -0,0 +1,977 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+
+void GGPROTO::dccstart()
+{
+ DWORD exitCode = 0;
+
+ if (dcc) return;
+
+ // Startup dcc thread
+ GetExitCodeThread(pth_dcc.hThread, &exitCode);
+ // Check if dcc thread isn't running already
+ if (exitCode == STILL_ACTIVE)
+ {
+#ifdef DEBUGMODE
+ netlog("gg_dccstart(): DCC thread still active. Exiting...");
+#endif
+ // Signalize mainthread it's started
+ if (hEvent) SetEvent(hEvent);
+ return;
+ }
+
+ // Check if we wan't direct connections
+ if (!db_get_b(NULL, m_szModuleName, GG_KEY_DIRECTCONNS, GG_KEYDEF_DIRECTCONNS))
+ {
+ netlog("gg_dccstart(): No direct connections setup.");
+ if (hEvent) SetEvent(hEvent);
+ return;
+ }
+
+ // Start thread
+ pth_dcc.hThread = forkthreadex(&GGPROTO::dccmainthread, NULL, &pth_dcc.dwThreadId);
+}
+
+void GGPROTO::dccconnect(uin_t uin)
+{
+ struct gg_dcc *dcc;
+ HANDLE hContact = getcontact(uin, 0, 0, NULL);
+ DWORD ip, myuin; WORD port;
+
+ netlog("gg_dccconnect(): Connecting to uin %d.", uin);
+
+ // If unknown user or not on list ignore
+ if (!hContact) return;
+
+ // Read user IP and port
+ ip = swap32(db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, 0));
+ port = db_get_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, 0);
+ myuin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0);
+
+ // If not port nor ip nor my uin (?) specified
+ if (!ip || !port || !uin) return;
+
+ if (!(dcc = gg_dcc_get_file(ip, port, myuin, uin)))
+ return;
+
+ // Add client dcc to watches
+ EnterCriticalSection(&ft_mutex);
+ list_add(&watches, dcc, 0);
+ LeaveCriticalSection(&ft_mutex);
+}
+
+//////////////////////////////////////////////////////////
+// THREAD: File transfer fail
+struct ftfaildata
+{
+ HANDLE hContact;
+ HANDLE hProcess;
+};
+
+void __cdecl GGPROTO::ftfailthread(void *param)
+{
+ struct ftfaildata *ft = (struct ftfaildata *)param;
+ SleepEx(100, FALSE);
+ netlog("gg_ftfailthread(): Sending failed file transfer.");
+ ProtoBroadcastAck(m_szModuleName, ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft->hProcess, 0);
+ free(ft);
+}
+
+HANDLE ftfail(GGPROTO *gg, HANDLE hContact)
+{
+ ftfaildata *ft = (ftfaildata*)malloc(sizeof(struct ftfaildata));
+#ifdef DEBUGMODE
+ gg->netlog("gg_ftfail(): Failing file transfer...");
+#endif
+ srand(time(NULL));
+ ft->hProcess = (HANDLE)rand();
+ ft->hContact = hContact;
+ gg->forkthread(&GGPROTO::ftfailthread, ft);
+ return ft->hProcess;
+}
+
+////////////////////////////////////////////////////////////
+// Main DCC connection session thread
+
+// Info refresh min time (msec) / half-sec
+#define GGSTATREFRESHEVERY 500
+
+void __cdecl GGPROTO::dccmainthread(void*)
+{
+ uin_t uin;
+ gg_event *e;
+ struct timeval tv;
+ fd_set rd, wd;
+ int ret;
+ SOCKET maxfd;
+ DWORD tick;
+ list_t l;
+ char filename[MAX_PATH];
+
+ // Zero up lists
+ watches = transfers = requests = l = NULL;
+
+ netlog("gg_dccmainthread(): DCC Server Thread Starting");
+
+ // Readup number
+ if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ netlog("gg_dccmainthread(): No Gadu-Gadu number specified. Exiting.");
+ if (hEvent) SetEvent(hEvent);
+ return;
+ }
+
+ // Create listen socket on config direct port
+ if (!(dcc = gg_dcc_socket_create(uin, (uint16_t)db_get_w(NULL, m_szModuleName, GG_KEY_DIRECTPORT, GG_KEYDEF_DIRECTPORT))))
+ {
+ netlog("gg_dccmainthread(): Cannot create DCC listen socket. Exiting.");
+ // Signalize mainthread we haven't start
+ if (hEvent) SetEvent(hEvent);
+ return;
+ }
+
+ gg_dcc_port = dcc->port;
+ gg_dcc_ip = inet_addr("255.255.255.255");
+ netlog("gg_dccmainthread(): Listening on port %d.", gg_dcc_port);
+
+ // Signalize mainthread we started
+ if (hEvent) SetEvent(hEvent);
+
+ // Add main dcc handler to watches
+ list_add(&watches, dcc, 0);
+
+ // Do while we are in the main server thread
+ while(pth_dcc.dwThreadId && dcc)
+ {
+ // Timeouts
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ // Prepare descriptiors for select
+ FD_ZERO(&rd);
+ FD_ZERO(&wd);
+
+ for (maxfd = 0, l = watches; l; l = l->next)
+ {
+ gg_common *w = (gg_common*)l->data;
+
+ if (!w || w->state == GG_STATE_ERROR || w->state == GG_STATE_IDLE || w->state == GG_STATE_DONE)
+ continue;
+
+ // Check if it's proper descriptor
+ if (w->fd == -1) continue;
+
+ if (w->fd > maxfd)
+ maxfd = w->fd;
+ if ((w->check & GG_CHECK_READ))
+ FD_SET(w->fd, &rd);
+ if ((w->check & GG_CHECK_WRITE))
+ FD_SET(w->fd, &wd);
+ }
+
+ // Wait for data on selects
+ ret = select(maxfd + 1, &rd, &wd, NULL, &tv);
+
+ // Check for select error
+ if (ret == -1)
+ {
+ if (errno == EBADF)
+ netlog("gg_dccmainthread(): Bad descriptor on select().");
+ else if (errno != EINTR)
+ netlog("gg_dccmainthread(): Unknown error on select().");
+ continue;
+ }
+
+ // Process watches (carefull with l)
+ l = watches;
+ EnterCriticalSection(&ft_mutex);
+ while (l)
+ {
+ struct gg_common *c = (gg_common*)l->data;
+ struct gg_dcc *dcc = (gg_dcc*)l->data;
+ struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data;
+ l = l->next;
+
+ switch (c->type)
+ {
+ default:
+ if (!dcc || (!FD_ISSET(dcc->fd, &rd) && !FD_ISSET(dcc->fd, &wd)))
+ continue;
+
+ /////////////////////////////////////////////////////////////////
+ // Process DCC events
+
+ // Connection broken/closed
+ if (!(e = gg_dcc_socket_watch_fd(dcc)))
+ {
+ netlog("gg_dccmainthread(): Socket closed.");
+ // Remove socket and _close
+ list_remove(&watches, dcc, 0);
+ gg_dcc_socket_free(dcc);
+
+ // Check if it's main socket
+ if (dcc == dcc) dcc = NULL;
+ continue;
+ }
+ else netlog("gg_dccmainthread(): Event: %s", ggdebug_eventtype(e));
+
+ switch(e->type)
+ {
+ // Client connected
+ case GG_EVENT_DCC_NEW:
+ list_add(&watches, e->event.dcc_new, 0);
+ e->event.dcc_new = NULL;
+ break;
+
+ //
+ case GG_EVENT_NONE:
+ // If transfer in progress do status
+ if (dcc->file_fd != -1 && dcc->offset > 0 && (((tick = GetTickCount()) - dcc->tick) > GGSTATREFRESHEVERY))
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+ dcc->tick = tick;
+ strncpy(filename, dcc->folder, sizeof(filename));
+ strncat(filename, (char*)dcc->file_info.filename, sizeof(filename) - strlen(filename));
+ memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS);
+ pfts.hContact = (HANDLE)dcc->contact;
+ pfts.flags = (dcc->type == GG_SESSION_DCC_SEND);
+ pfts.pszFiles = NULL;
+ pfts.totalFiles = 1;
+ pfts.currentFileNumber = 0;
+ pfts.totalBytes = dcc->file_info.size;
+ pfts.totalProgress = dcc->offset;
+ pfts.szWorkingDir = dcc->folder;
+ pfts.szCurrentFile = filename;
+ pfts.currentFileSize = dcc->file_info.size;
+ pfts.currentFileProgress = dcc->offset;
+ pfts.currentFileTime = 0;
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc, (LPARAM)&pfts);
+ }
+ break;
+
+ // Connection was successfuly ended
+ case GG_EVENT_DCC_DONE:
+ netlog("gg_dccmainthread(): Client: %d, Transfer done ! Closing connection.", dcc->peer_uin);
+ // Remove from watches
+ list_remove(&watches, dcc, 0);
+ // Close file & success
+ if (dcc->file_fd != -1)
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+ strncpy(filename, dcc->folder, sizeof(filename));
+ strncat(filename, (char*)dcc->file_info.filename, sizeof(filename) - strlen(filename));
+ memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS);
+ pfts.hContact = (HANDLE)dcc->contact;
+ pfts.flags = (dcc->type == GG_SESSION_DCC_SEND);
+ pfts.pszFiles = NULL;
+ pfts.totalFiles = 1;
+ pfts.currentFileNumber = 0;
+ pfts.totalBytes = dcc->file_info.size;
+ pfts.totalProgress = dcc->file_info.size;
+ pfts.szWorkingDir = dcc->folder;
+ pfts.szCurrentFile = filename;
+ pfts.currentFileSize = dcc->file_info.size;
+ pfts.currentFileProgress = dcc->file_info.size;
+ pfts.currentFileTime = 0;
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc, (LPARAM)&pfts);
+ _close(dcc->file_fd); dcc->file_fd = -1;
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dcc, 0);
+ }
+ // Free dcc
+ gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL;
+ break;
+
+ // Client error
+ case GG_EVENT_DCC_ERROR:
+ switch (e->event.dcc_error)
+ {
+ case GG_ERROR_DCC_HANDSHAKE:
+ netlog("gg_dccmainthread(): Client: %d, Handshake error.", dcc->peer_uin);
+ break;
+ case GG_ERROR_DCC_NET:
+ netlog("gg_dccmainthread(): Client: %d, Network error.", dcc->peer_uin);
+ break;
+ case GG_ERROR_DCC_FILE:
+ netlog("gg_dccmainthread(): Client: %d, File read/write error.", dcc->peer_uin);
+ break;
+ case GG_ERROR_DCC_EOF:
+ netlog("gg_dccmainthread(): Client: %d, End of file/connection error.", dcc->peer_uin);
+ break;
+ case GG_ERROR_DCC_REFUSED:
+ netlog("gg_dccmainthread(): Client: %d, Connection refused error.", dcc->peer_uin);
+ break;
+ default:
+ netlog("gg_dccmainthread(): Client: %d, Unknown error.", dcc->peer_uin);
+ }
+ // Don't do anything if it's main socket
+ if (dcc == dcc) break;
+
+ // Remove from watches
+ list_remove(&watches, dcc, 0);
+
+ // Close file & fail
+ if (dcc->contact)
+ {
+ _close(dcc->file_fd); dcc->file_fd = -1;
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0);
+ }
+ // Free dcc
+ gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL;
+ break;
+
+ // Need file acknowledgement
+ case GG_EVENT_DCC_NEED_FILE_ACK:
+ netlog("gg_dccmainthread(): Client: %d, File ack filename \"%s\" size %d.", dcc->peer_uin,
+ dcc->file_info.filename, dcc->file_info.size);
+ // Do not watch for transfer until user accept it
+ list_remove(&watches, dcc, 0);
+ // Add to waiting transfers
+ list_add(&transfers, dcc, 0);
+
+ //////////////////////////////////////////////////
+ // Add file recv request
+ {
+ // Make new ggtransfer struct
+ dcc->contact = getcontact(dcc->peer_uin, 0, 0, NULL);
+ TCHAR* filenameT = mir_utf8decodeT((char*)dcc->file_info.filename);
+
+ PROTORECVFILET pre = {0};
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = filenameT;
+ pre.ptszFiles = &filenameT;
+ pre.lParam = (LPARAM)dcc7;
+
+ CCSDATA ccs = { dcc7->contact, PSR_FILE, 0, (LPARAM)&pre };
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ mir_free(filenameT);
+ }
+ break;
+
+ // Need client accept
+ case GG_EVENT_DCC_CLIENT_ACCEPT:
+ netlog("gg_dccmainthread(): Client: %d, Client accept.", dcc->peer_uin);
+ // Check if user is on the list and if it is my uin
+ if (getcontact(dcc->peer_uin, 0, 0, NULL) &&
+ db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, -1) == dcc->uin)
+ break;
+
+ // Kill unauthorized dcc
+ list_remove(&watches, dcc, 0);
+ gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL;
+ break;
+
+ // Client connected as we wished to (callback)
+ case GG_EVENT_DCC_CALLBACK:
+ {
+ int found = 0;
+ netlog("gg_dccmainthread(): Callback from client %d.", dcc->peer_uin);
+ // Seek for stored callback request
+ for (l = requests; l; l = l->next)
+ {
+ struct gg_dcc *req = (gg_dcc*)l->data;
+
+ if (req && req->peer_uin == dcc->peer_uin)
+ {
+ gg_dcc_set_type(dcc, GG_SESSION_DCC_SEND);
+ found = 1;
+
+ // Copy data req ===> dcc
+ dcc->folder = req->folder;
+ dcc->contact = req->contact;
+ dcc->file_fd = req->file_fd;
+ memcpy(&dcc->file_info, &req->file_info, sizeof(struct gg_file_info));
+ // Copy data back to dcc ===> req
+ memcpy(req, dcc, sizeof(struct gg_dcc));
+
+ // Remove request
+ list_remove(&requests, req, 0);
+ // Remove dcc from watches
+ list_remove(&watches, dcc, 0);
+ // Add request to watches
+ list_add(&watches, req, 0);
+ // Free old dat
+ gg_free_dcc(dcc);
+ netlog("gg_dccmainthread(): Found stored request to client %d, filename \"%s\" size %d, folder \"%s\".",
+ req->peer_uin, req->file_info.filename, req->file_info.size, req->folder);
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ netlog("gg_dccmainthread(): Unknown request to client %d.", dcc->peer_uin);
+ // Kill unauthorized dcc
+ list_remove(&watches, dcc, 0);
+ gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL;
+ }
+ break;
+ }
+ }
+
+ // Free event
+ gg_free_event(e);
+ break;
+
+ case GG_SESSION_DCC7_SOCKET:
+ case GG_SESSION_DCC7_GET:
+ case GG_SESSION_DCC7_SEND:
+ case GG_SESSION_DCC7_VOICE:
+ if (!dcc7 || (!FD_ISSET(dcc7->fd, &rd) && !FD_ISSET(dcc7->fd, &wd)))
+ continue;
+
+ /////////////////////////////////////////////////////////////////
+ // Process DCC7 events
+
+ // Connection broken/closed
+ if (!(e = gg_dcc7_watch_fd(dcc7)))
+ {
+ netlog("gg_dccmainthread(): Socket closed.");
+ // Remove socket and _close
+ list_remove(&watches, dcc7, 0);
+ gg_dcc7_free(dcc7);
+ continue;
+ }
+ else netlog("gg_dccmainthread(): Event: %s", ggdebug_eventtype(e));
+
+ switch(e->type)
+ {
+ //
+ case GG_EVENT_NONE:
+ // If transfer in progress do status
+ if (dcc7->file_fd != -1 && dcc7->offset > 0 && (((tick = GetTickCount()) - dcc7->tick) > GGSTATREFRESHEVERY))
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+ dcc7->tick = tick;
+ strncpy(filename, dcc7->folder, sizeof(filename));
+ strncat(filename, (char*)dcc7->filename, sizeof(filename) - strlen(filename));
+ memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS);
+ pfts.hContact = (HANDLE)dcc7->contact;
+ pfts.flags = (dcc7->type == GG_SESSION_DCC7_SEND);
+ pfts.pszFiles = NULL;
+ pfts.totalFiles = 1;
+ pfts.currentFileNumber = 0;
+ pfts.totalBytes = dcc7->size;
+ pfts.totalProgress = dcc7->offset;
+ pfts.szWorkingDir = dcc7->folder;
+ pfts.szCurrentFile = filename;
+ pfts.currentFileSize = dcc7->size;
+ pfts.currentFileProgress = dcc7->offset;
+ pfts.currentFileTime = 0;
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc7, (LPARAM)&pfts);
+ }
+ break;
+
+ // Connection was successfuly ended
+ case GG_EVENT_DCC7_DONE:
+ netlog("gg_dccmainthread(): Client: %d, Transfer done ! Closing connection.", dcc->peer_uin);
+ // Remove from watches
+ list_remove(&watches, dcc7, 0);
+ // Close file & success
+ if (dcc7->file_fd != -1)
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+ strncpy(filename, dcc7->folder, sizeof(filename));
+ strncat(filename, (char*)dcc7->filename, sizeof(filename) - strlen(filename));
+ memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS);
+ pfts.hContact = (HANDLE)dcc7->contact;
+ pfts.flags = (dcc7->type == GG_SESSION_DCC7_SEND);
+ pfts.pszFiles = NULL;
+ pfts.totalFiles = 1;
+ pfts.currentFileNumber = 0;
+ pfts.totalBytes = dcc7->size;
+ pfts.totalProgress = dcc7->size;
+ pfts.szWorkingDir = dcc7->folder;
+ pfts.szCurrentFile = filename;
+ pfts.currentFileSize = dcc7->size;
+ pfts.currentFileProgress = dcc7->size;
+ pfts.currentFileTime = 0;
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc7, (LPARAM)&pfts);
+ _close(dcc7->file_fd); dcc7->file_fd = -1;
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dcc7, 0);
+ }
+ // Free dcc
+ gg_dcc7_free(dcc7);
+ break;
+
+ // Client error
+ case GG_EVENT_DCC7_ERROR:
+ switch (e->event.dcc7_error)
+ {
+ case GG_ERROR_DCC7_HANDSHAKE:
+ netlog("gg_dccmainthread(): Client: %d, Handshake error.", dcc7->peer_uin);
+ break;
+ case GG_ERROR_DCC7_NET:
+ netlog("gg_dccmainthread(): Client: %d, Network error.", dcc7->peer_uin);
+ break;
+ case GG_ERROR_DCC7_FILE:
+ netlog("gg_dccmainthread(): Client: %d, File read/write error.", dcc7->peer_uin);
+ break;
+ case GG_ERROR_DCC7_EOF:
+ netlog("gg_dccmainthread(): Client: %d, End of file/connection error.", dcc7->peer_uin);
+ break;
+ case GG_ERROR_DCC7_REFUSED:
+ netlog("gg_dccmainthread(): Client: %d, Connection refused error.", dcc7->peer_uin);
+ break;
+ case GG_ERROR_DCC7_RELAY:
+ netlog("gg_dccmainthread(): Client: %d, Relay connection error.", dcc7->peer_uin);
+ break;
+ default:
+ netlog("gg_dccmainthread(): Client: %d, Unknown error.", dcc7->peer_uin);
+ }
+ // Remove from watches
+ list_remove(&watches, dcc7, 0);
+
+ // Close file & fail
+ if (dcc7->file_fd != -1)
+ {
+ _close(dcc7->file_fd);
+ dcc7->file_fd = -1;
+ }
+
+ if (dcc7->contact)
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0);
+
+ // Free dcc
+ gg_dcc7_free(dcc7);
+ break;
+ }
+
+ // Free event
+ gg_free_event(e);
+ break;
+ }
+ }
+ LeaveCriticalSection(&ft_mutex);
+ }
+
+ // Close all dcc client sockets
+ for (l = watches; l; l = l->next)
+ {
+ struct gg_common *c = (gg_common*)l->data;
+ if (!c) continue;
+ if (c->type == GG_SESSION_DCC7_SOCKET || c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET)
+ {
+ struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data;
+ gg_dcc7_free(dcc7);
+ }
+ else
+ {
+ struct gg_dcc *dcc = (gg_dcc*)l->data;
+ gg_dcc_socket_free(dcc);
+
+ // Check if it's main socket
+ if (dcc == dcc) dcc = NULL;
+ }
+ }
+ // Close all waiting for aknowledgle transfers
+ for (l = transfers; l; l = l->next)
+ {
+ struct gg_common *c = (gg_common*)l->data;
+ if (!c) continue;
+ if (c->type == GG_SESSION_DCC7_SOCKET || c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET)
+ {
+ struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data;
+ gg_dcc7_free(dcc7);
+ }
+ else
+ {
+ struct gg_dcc *dcc = (gg_dcc*)l->data;
+ gg_dcc_socket_free(dcc);
+ }
+ }
+ // Close all waiting dcc requests
+ for (l = requests; l; l = l->next)
+ {
+ struct gg_dcc *dcc = (gg_dcc*)l->data;
+ if (dcc) gg_free_dcc(dcc);
+ }
+ list_destroy(watches, 0);
+ list_destroy(transfers, 0);
+ list_destroy(requests, 0);
+
+ gg_dcc_port = 0;
+ gg_dcc_ip = 0;
+ netlog("gg_dccmainthread(): DCC Server Thread Ending");
+}
+
+HANDLE GGPROTO::dccfileallow(HANDLE hTransfer, const PROTOCHAR* szPath)
+{
+ struct gg_dcc *dcc = (struct gg_dcc *) hTransfer;
+ char fileName[MAX_PATH], *path = mir_t2a(szPath);
+ strncpy(fileName, path, sizeof(fileName));
+ strncat(fileName, (char*)dcc->file_info.filename, sizeof(fileName) - strlen(fileName));
+ dcc->folder = _strdup((char *) path);
+ dcc->tick = 0;
+ mir_free(path);
+
+ // Remove transfer from waiting list
+ EnterCriticalSection(&ft_mutex);
+ list_remove(&transfers, dcc, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ // Open file for appending and check if ok
+ if ((dcc->file_fd = _open(fileName, _O_WRONLY | _O_APPEND | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE)) == -1)
+ {
+ netlog("gg_dccfileallow(): Failed to create file \"%s\".", fileName);
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0);
+ // Free transfer
+ gg_free_dcc(dcc);
+ return 0;
+ }
+
+ // Put an offset to the file
+ dcc->offset = _lseek(dcc->file_fd, 0, SEEK_END);
+
+ // Add to watches and start transfer
+ EnterCriticalSection(&ft_mutex);
+ list_add(&watches, dcc, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ netlog("gg_dccfileallow(): Receiving file \"%s\" from %d.", dcc->file_info.filename, dcc->peer_uin);
+
+ return hTransfer;
+}
+
+HANDLE GGPROTO::dcc7fileallow(HANDLE hTransfer, const PROTOCHAR* szPath)
+{
+ struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer;
+ char fileName[MAX_PATH], *path = mir_t2a(szPath);
+ int iFtRemoveRes;
+ strncpy(fileName, path, sizeof(fileName));
+ strncat(fileName, (char*)dcc7->filename, sizeof(fileName) - strlen(fileName));
+ dcc7->folder = _strdup((char *) path);
+ dcc7->tick = 0;
+ mir_free(path);
+
+ // Remove transfer from waiting list
+ EnterCriticalSection(&ft_mutex);
+ iFtRemoveRes = list_remove(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ if (iFtRemoveRes == -1)
+ {
+ netlog("gg_dcc7fileallow(): File transfer denied.");
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DENIED, dcc7, 0);
+ // Free transfer
+ gg_dcc7_free(dcc7);
+ return 0;
+ }
+
+ // Open file for appending and check if ok
+ if ((dcc7->file_fd = _open(fileName, _O_WRONLY | _O_APPEND | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE)) == -1)
+ {
+ netlog("gg_dcc7fileallow(): Failed to create file \"%s\".", fileName);
+ gg_dcc7_reject(dcc7, GG_DCC7_REJECT_USER);
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0);
+ // Free transfer
+ gg_dcc7_free(dcc7);
+ return 0;
+ }
+
+ // Put an offset to the file
+ dcc7->offset = _lseek(dcc7->file_fd, 0, SEEK_END);
+ gg_dcc7_accept(dcc7, dcc7->offset);
+
+ // Add to watches and start transfer
+ EnterCriticalSection(&ft_mutex);
+ list_add(&watches, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ netlog("gg_dcc7fileallow(): Receiving file \"%s\" from %d.", dcc7->filename, dcc7->peer_uin);
+
+ return hTransfer;
+}
+
+int GGPROTO::dccfiledeny(HANDLE hTransfer)
+{
+ struct gg_dcc *dcc = (struct gg_dcc *) hTransfer;
+
+ // Remove transfer from any list
+ EnterCriticalSection(&ft_mutex);
+ if (watches) list_remove(&watches, dcc, 0);
+ if (requests) list_remove(&requests, dcc, 0);
+ if (transfers) list_remove(&transfers, dcc, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ netlog("gg_dccfiledeny(): Rejected file \"%s\" from/to %d.", dcc->file_info.filename, dcc->peer_uin);
+
+ // Free transfer
+ gg_free_dcc(dcc);
+
+ return 0;
+}
+
+int GGPROTO::dcc7filedeny(HANDLE hTransfer)
+{
+ struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer;
+
+ gg_dcc7_reject(dcc7, GG_DCC7_REJECT_USER);
+
+ // Remove transfer from any list
+ EnterCriticalSection(&ft_mutex);
+ if (watches) list_remove(&watches, dcc7, 0);
+ if (transfers) list_remove(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ netlog("gg_dcc7filedeny(): Rejected file \"%s\" from/to %d.", dcc7->filename, dcc7->peer_uin);
+
+ // Free transfer
+ gg_dcc7_free(dcc7);
+
+ return 0;
+}
+
+int GGPROTO::dccfilecancel(HANDLE hTransfer)
+{
+ struct gg_dcc *dcc = (struct gg_dcc *) hTransfer;
+
+ // Remove transfer from any list
+ EnterCriticalSection(&ft_mutex);
+ if (watches) list_remove(&watches, dcc, 0);
+ if (requests) list_remove(&requests, dcc, 0);
+ if (transfers) list_remove(&transfers, dcc, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ // Send failed info
+ ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0);
+ // Close file
+ if (dcc->file_fd != -1)
+ {
+ _close(dcc->file_fd);
+ dcc->file_fd = -1;
+ }
+
+ netlog("gg_dccfilecancel(): Canceled file \"%s\" from/to %d.", dcc->file_info.filename, dcc->peer_uin);
+
+ // Free transfer
+ gg_free_dcc(dcc);
+
+ return 0;
+}
+
+int GGPROTO::dcc7filecancel(HANDLE hTransfer)
+{
+ struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer;
+
+ if (dcc7->type == GG_SESSION_DCC7_SEND && dcc7->state == GG_STATE_WAITING_FOR_ACCEPT)
+ gg_dcc7_abort(dcc7);
+
+ // Remove transfer from any list
+ EnterCriticalSection(&ft_mutex);
+ if (watches) list_remove(&watches, dcc7, 0);
+ if (transfers) list_remove(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ // Send failed info
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0);
+ // Close file
+ if (dcc7->file_fd != -1)
+ {
+ _close(dcc7->file_fd);
+ dcc7->file_fd = -1;
+ }
+
+ netlog("gg_dcc7filecancel(): Canceled file \"%s\" from/to %d.", dcc7->filename, dcc7->peer_uin);
+
+ // Free transfer
+ gg_dcc7_free(dcc7);
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// File receiving allowed
+
+HANDLE GGPROTO::FileAllow(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath)
+{
+ // Check if its proper dcc
+ struct gg_common *c = (struct gg_common *) hTransfer;
+ if (!c)
+ return NULL;
+
+ if (c->type == GG_SESSION_DCC7_GET)
+ return dcc7fileallow(hTransfer, szPath);
+
+ return dccfileallow(hTransfer, szPath);
+}
+
+////////////////////////////////////////////////////////////
+// File transfer canceled
+
+int GGPROTO::FileCancel(HANDLE hContact, HANDLE hTransfer)
+{
+ // Check if its proper dcc
+ struct gg_common *c = (struct gg_common *) hTransfer;
+ if (!c)
+ return 0;
+
+ if (c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET)
+ return dcc7filecancel(hTransfer);
+
+ return dccfilecancel(hTransfer);
+}
+
+////////////////////////////////////////////////////////////
+// File receiving denied
+
+int GGPROTO::FileDeny(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason)
+{
+ // Check if its proper dcc
+ struct gg_common *c = (struct gg_common *) hTransfer;
+ if (!c)
+ return 0;
+
+ if (c->type == GG_SESSION_DCC7_GET)
+ return dcc7filedeny(hTransfer);
+
+ return dccfiledeny(hTransfer);
+}
+
+////////////////////////////////////////////////////////////
+// Called when received an file
+
+int GGPROTO::RecvFile(HANDLE hContact, PROTOFILEEVENT* pre)
+{
+ return Proto_RecvFile(hContact, pre);
+}
+
+////////////////////////////////////////////////////////////
+// Called when user sends a file
+
+HANDLE GGPROTO::SendFile(HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles)
+{
+ char *bslash, *filename;
+ struct gg_dcc *dcc;
+ DWORD ip, ver;
+ WORD port;
+ uin_t myuin, uin;
+
+ // Check if main dcc thread is on
+ if (!isonline())
+ return ftfail(this, hContact);
+
+ filename = mir_t2a(ppszFiles[0]);
+
+ // Read user IP and port
+ ip = swap32(db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, 0));
+ port = db_get_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, 0);
+ myuin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0);
+ uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0);
+ ver = db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTVERSION, 0);
+
+ // Use DCC7 if a contact is using at least version 7.6 or unknown version
+ if ((ver & 0x00ffffff) >= 0x29 || !ver) {
+ struct gg_dcc7 *dcc7;
+
+ EnterCriticalSection(&sess_mutex);
+ if (!(dcc7 = gg_dcc7_send_file(sess, uin, filename, NULL, NULL))) {
+ LeaveCriticalSection(&sess_mutex);
+ netlog("gg_sendfile(): Failed to send file \"%s\".", filename);
+ mir_free(filename);
+ return ftfail(this, hContact);
+ }
+ LeaveCriticalSection(&sess_mutex);
+
+ netlog("gg_sendfile(): Sending file \"%s\" to %d.", filename, uin);
+
+ // Add dcc to watches
+ list_add(&watches, dcc7, 0);
+
+ // Store handle
+ dcc7->contact = hContact;
+ dcc7->folder = _strdup(filename);
+ dcc7->tick = 0;
+ // Make folder name
+ bslash = strrchr(dcc7->folder, '\\');
+ if (bslash)
+ *(bslash + 1) = 0;
+ else
+ *(dcc7->folder) = 0;
+ mir_free(filename);
+ return dcc7;
+ }
+
+ // Return if bad connection info
+ if (!port || !uin || !myuin)
+ {
+ netlog("gg_sendfile(): Bad contact uin or my uin. Exit.");
+ mir_free(filename);
+ return ftfail(this, hContact);
+ }
+
+ // Try to connect if not ask user to connect me
+ if ((ip && port >= 10 && !(dcc = gg_dcc_send_file(ip, port, myuin, uin))) || (port < 10 && port > 0))
+ {
+ // Make fake dcc structure
+ dcc = (gg_dcc*)malloc(sizeof(struct gg_dcc));
+ memset(dcc, 0, sizeof(struct gg_dcc));
+ // Fill up structures
+ dcc->uin = myuin;
+ dcc->peer_uin = uin;
+ dcc->fd = -1;
+ dcc->type = GG_SESSION_DCC_SEND;
+ netlog("gg_sendfile(): Requesting user to connect us and scheduling gg_dcc struct for a later use.");
+ EnterCriticalSection(&sess_mutex);
+ gg_dcc_request(sess, uin);
+ LeaveCriticalSection(&sess_mutex);
+ list_add(&requests, dcc, 0);
+ }
+
+ // Write filename
+ if (gg_dcc_fill_file_info(dcc, filename) == -1)
+ {
+ netlog("gg_sendfile(): Cannot open and file fileinfo \"%s\".", filename);
+ gg_free_dcc(dcc);
+ mir_free(filename);
+ return ftfail(this, hContact);
+ }
+
+ netlog("gg_sendfile(): Sending file \"%s\" to %d in %s mode.", filename, uin, (dcc->fd != -1) ? "active" : "passive");
+
+ // Add dcc to watches if not passive
+ if (dcc->fd != -1) list_add(&watches, dcc, 0);
+
+ // Store handle
+ dcc->contact = hContact;
+ dcc->folder = _strdup(filename);
+ dcc->tick = 0;
+ // Make folder name
+ bslash = strrchr(dcc->folder, '\\');
+ if (bslash)
+ *(bslash + 1) = 0;
+ else
+ *(dcc->folder) = 0;
+
+ mir_free(filename);
+ return dcc;
+}
+
diff --git a/protocols/Gadu-Gadu/src/gg.cpp b/protocols/Gadu-Gadu/src/gg.cpp
new file mode 100644
index 0000000000..5bd9f6ceb5
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/gg.cpp
@@ -0,0 +1,523 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include "version.h"
+#include <errno.h>
+
+// Plugin info
+PLUGININFOEX pluginInfo = {
+ sizeof(PLUGININFOEX),
+ "Gadu-Gadu Protocol",
+ __VERSION_DWORD,
+ "Provides support for Gadu-Gadu protocol",
+ "Bartosz Białek, Adam Strzelecki",
+ "dezred"/*antispam*/"@"/*antispam*/"gmail"/*antispam*/"."/*antispam*/"com",
+ "© 2009-2012 Bartosz Białek, 2003-2009 Adam Strzelecki",
+ "http://miranda-ng.org/",
+ UNICODE_AWARE,
+ // {F3FF65F3-250E-416A-BEE9-58C93F85AB33}
+ { 0xf3ff65f3, 0x250e, 0x416a, { 0xbe, 0xe9, 0x58, 0xc9, 0x3f, 0x85, 0xab, 0x33 } }
+};
+
+// Other variables
+HINSTANCE hInstance;
+
+XML_API xi;
+SSL_API si;
+CLIST_INTERFACE *pcli;
+int hLangpack;
+list_t g_Instances;
+
+// Event hooks
+static HANDLE hHookModulesLoaded = NULL;
+static HANDLE hHookPreShutdown = NULL;
+
+static unsigned long crc_table[256];
+
+//////////////////////////////////////////////////////////
+// Extra winsock function for error description
+
+TCHAR* ws_strerror(int code)
+{
+ static TCHAR err_desc[160];
+
+ // Not a windows error display WinSock
+ if (code == 0)
+ {
+ TCHAR buff[128];
+ int len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, WSAGetLastError(), 0, buff, SIZEOF(buff), NULL);
+ if (len == 0)
+ mir_sntprintf(err_desc, SIZEOF(err_desc), _T("WinSock %u: Unknown error."), WSAGetLastError());
+ else
+ mir_sntprintf(err_desc, SIZEOF(err_desc), _T("WinSock %d: %s"), WSAGetLastError(), buff);
+ return err_desc;
+ }
+
+ // Return normal error
+ return _tcserror(code);
+}
+
+//////////////////////////////////////////////////////////
+// Build the crc table
+void crc_gentable(void)
+{
+ unsigned long crc, poly;
+ int i, j;
+
+ poly = 0xEDB88320L;
+ for (i = 0; i < 256; i++)
+ {
+ crc = i;
+ for (j = 8; j > 0; j--)
+ {
+ if (crc & 1)
+ crc = (crc >> 1) ^ poly;
+ else
+ crc >>= 1;
+ }
+ crc_table[i] = crc;
+ }
+}
+
+//////////////////////////////////////////////////////////
+// Calculate the crc value
+unsigned long crc_get(char *mem)
+{
+ register unsigned long crc = 0xFFFFFFFF;
+ while(mem && *mem)
+ crc = ((crc>>8) & 0x00FFFFFF) ^ crc_table[(crc ^ *(mem++)) & 0xFF];
+
+ return (crc ^ 0xFFFFFFFF);
+}
+
+//////////////////////////////////////////////////////////
+// http_error_string()
+//
+// returns http error text
+const char *http_error_string(int h)
+{
+ switch (h)
+ {
+ case 0:
+ return Translate((errno == ENOMEM) ? "HTTP failed memory" : "HTTP failed connecting");
+ case GG_ERROR_RESOLVING:
+ return Translate("HTTP failed resolving");
+ case GG_ERROR_CONNECTING:
+ return Translate("HTTP failed connecting");
+ case GG_ERROR_READING:
+ return Translate("HTTP failed reading");
+ case GG_ERROR_WRITING:
+ return Translate("HTTP failed writing");
+ }
+
+ return Translate("Unknown HTTP error");
+}
+
+//////////////////////////////////////////////////////////
+// Gets plugin info
+
+extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD mirandaVersion)
+{
+ return &pluginInfo;
+}
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST};
+
+//////////////////////////////////////////////////////////
+// Cleanups from last plugin
+
+void GGPROTO::cleanuplastplugin(DWORD version)
+{
+ HANDLE hContact;
+ char *szProto;
+
+ // Remove bad e-mail and phones from
+ if (version < PLUGIN_MAKE_VERSION(0, 0, 1, 4))
+ {
+#ifdef DEBUGMODE
+ netlog("gg_cleanuplastplugin(%d): Cleaning junk Phone settings from < 0.0.1.4 ...", version);
+#endif
+ // Look for contact in DB
+ hContact = db_find_first();
+ while (hContact)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName))
+ {
+ // Do contact cleanup
+ db_unset(hContact, m_szModuleName, GG_KEY_EMAIL);
+ db_unset(hContact, m_szModuleName, "Phone");
+ }
+ hContact = db_find_next(hContact);
+ }
+ }
+
+ // Remove GG entries for non GG contacts
+ if (version < PLUGIN_MAKE_VERSION(0, 0, 3, 5))
+ {
+#ifdef DEBUGMODE
+ netlog("gg_cleanuplastplugin(%d): Cleaning junk Nick settings from < 0.0.3.5 ...", version);
+#endif
+ // Look for contact in DB
+ hContact = db_find_first();
+ while (hContact)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && strcmp(szProto, m_szModuleName))
+ {
+ // Do nick entry cleanup
+ db_unset(hContact, m_szModuleName, GG_KEY_NICK);
+ }
+ hContact = db_find_next(hContact);
+ }
+ }
+
+ // Remove old unneeded entry
+ if (version < PLUGIN_MAKE_VERSION(0, 0, 5, 3))
+ db_unset(NULL, m_szModuleName, "ShowNotOnMyList");
+
+ // Store this plugin version
+ db_set_dw(NULL, m_szModuleName, GG_PLUGINVERSION, pluginInfo.version);
+}
+
+//////////////////////////////////////////////////////////
+// When Miranda loaded its modules
+static int gg_modulesloaded(WPARAM wParam, LPARAM lParam)
+{
+ // Get SSL API
+ mir_getSI(&si);
+
+ // File Association Manager support
+ gg_links_init();
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// When Miranda starting shutdown sequence
+static int gg_preshutdown(WPARAM wParam, LPARAM lParam)
+{
+ gg_links_destroy();
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// Gets protocol instance associated with a contact
+static GGPROTO* gg_getprotoinstance(HANDLE hContact)
+{
+ char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+ list_t l = g_Instances;
+
+ if (szProto == NULL)
+ return NULL;
+
+ for (; l; l = l->next)
+ {
+ GGPROTO* gg = (GGPROTO*)l->data;
+ if (strcmp(szProto, gg->m_szModuleName) == 0)
+ return gg;
+ }
+
+ return NULL;
+}
+
+//////////////////////////////////////////////////////////
+// Handles PrebuildContactMenu event
+static int gg_prebuildcontactmenu(WPARAM wParam, LPARAM lParam)
+{
+ const HANDLE hContact = (HANDLE)wParam;
+ CLISTMENUITEM mi = {0};
+ GGPROTO* gg = gg_getprotoinstance(hContact);
+
+ if (gg == NULL)
+ return 0;
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIM_NAME | CMIM_FLAGS | CMIF_ICONFROMICOLIB;
+ if ( db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0) == db_get_b(NULL, gg->m_szModuleName, GG_KEY_UIN, 0) ||
+ db_get_b(hContact, gg->m_szModuleName, "ChatRoom", 0) ||
+ db_get_b(hContact, "CList", "NotOnList", 0))
+ mi.flags |= CMIF_HIDDEN;
+ mi.pszName = db_get_b(hContact, gg->m_szModuleName, GG_KEY_BLOCK, 0) ? LPGEN("&Unblock") : LPGEN("&Block");
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)gg->hBlockMenuItem, (LPARAM)&mi);
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// Contact block service function
+INT_PTR GGPROTO::blockuser(WPARAM wParam, LPARAM lParam)
+{
+ const HANDLE hContact = (HANDLE)wParam;
+ db_set_b(hContact, m_szModuleName, GG_KEY_BLOCK, !db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0));
+ notifyuser(hContact, 1);
+ return 0;
+}
+
+
+//////////////////////////////////////////////////////////
+// Contact blocking initialization
+
+#define GGS_BLOCKUSER "%s/BlockUser"
+void GGPROTO::block_init()
+{
+ CLISTMENUITEM mi = {0};
+ char service[64];
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIF_ICONFROMICOLIB;
+
+ mir_snprintf(service, sizeof(service), GGS_BLOCKUSER, m_szModuleName);
+ createObjService(service, &GGPROTO::blockuser);
+ mi.position = -500050000;
+ mi.icolibItem = GetIconHandle(IDI_BLOCK);
+ mi.pszName = LPGEN("&Block");
+ mi.pszService = service;
+ mi.pszContactOwner = m_szModuleName;
+ hBlockMenuItem = Menu_AddContactMenuItem(&mi);
+
+ hPrebuildMenuHook = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, gg_prebuildcontactmenu);
+}
+
+//////////////////////////////////////////////////////////
+// Contact blocking uninitialization
+
+void GGPROTO::block_uninit()
+{
+ UnhookEvent(hPrebuildMenuHook);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hBlockMenuItem, 0);
+}
+
+//////////////////////////////////////////////////////////
+// Menus initialization
+void GGPROTO::menus_init()
+{
+ HGENMENU hGCRoot, hCLRoot, hRoot = MO_GetProtoRootMenu(m_szModuleName);
+ CLISTMENUITEM mi = {0};
+
+ mi.cbSize = sizeof(mi);
+ if (hRoot == NULL)
+ {
+ mi.ptszName = m_tszUserName;
+ mi.position = 500090000;
+ mi.hParentMenu = HGENMENU_ROOT;
+ mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTPOPUP | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
+ mi.icolibItem = GetIconHandle(IDI_GG);
+ hGCRoot = hCLRoot = hRoot = hMenuRoot = Menu_AddProtoMenuItem(&mi);
+ }
+ else
+ {
+ mi.hParentMenu = hRoot;
+ mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE | CMIF_TCHAR;
+
+ mi.ptszName = LPGENT("Conference");
+ mi.position = 200001;
+ mi.icolibItem = GetIconHandle(IDI_CONFERENCE);
+ hGCRoot = Menu_AddProtoMenuItem(&mi);
+
+ mi.ptszName = LPGENT("Contact list");
+ mi.position = 200002;
+ mi.icolibItem = GetIconHandle(IDI_LIST);
+ hCLRoot = Menu_AddProtoMenuItem(&mi);
+
+ if (hMenuRoot)
+ CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)hMenuRoot, 0);
+ hMenuRoot = NULL;
+ }
+
+ gc_menus_init(hGCRoot);
+ import_init(hCLRoot);
+ sessions_menus_init(hRoot);
+}
+
+//////////////////////////////////////////////////////////
+// Module instance initialization
+
+static GGPROTO *gg_proto_init(const char* pszProtoName, const TCHAR* tszUserName)
+{
+ GGPROTO *gg = new GGPROTO(pszProtoName, tszUserName);
+ list_add(&g_Instances, gg, 0);
+ return gg;
+}
+
+//////////////////////////////////////////////////////////
+// Module instance uninitialization
+
+static int gg_proto_uninit(PROTO_INTERFACE *proto)
+{
+ GGPROTO *gg = (GGPROTO *)proto;
+ list_remove(&g_Instances, gg, 0);
+ delete gg;
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// When plugin is loaded
+
+extern "C" int __declspec(dllexport) Load(void)
+{
+ mir_getXI(&xi);
+ mir_getLP(&pluginInfo);
+
+ pcli = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, (LPARAM)hInstance);
+
+ // Hook system events
+ hHookModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, gg_modulesloaded);
+ hHookPreShutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, gg_preshutdown);
+
+ // Prepare protocol name
+ PROTOCOLDESCRIPTOR pd = { 0 };
+ pd.cbSize = sizeof(pd);
+ pd.szName = GGDEF_PROTO;
+ pd.fnInit = (pfnInitProto)gg_proto_init;
+ pd.fnUninit = (pfnUninitProto)gg_proto_uninit;
+ pd.type = PROTOTYPE_PROTOCOL;
+
+ // Register module
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM) &pd);
+ gg_links_instancemenu_init();
+
+ // Instance list
+ g_Instances = NULL;
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// When plugin is unloaded
+
+extern "C" int __declspec(dllexport) Unload()
+{
+ LocalEventUnhook(hHookModulesLoaded);
+ LocalEventUnhook(hHookPreShutdown);
+
+ // Cleanup WinSock
+ WSACleanup();
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// DEBUGING FUNCTIONS
+struct
+{
+ int type;
+ char *text;
+}
+static const ggdebug_eventype2string[] =
+{
+ {GG_EVENT_NONE, "GG_EVENT_NONE"},
+ {GG_EVENT_MSG, "GG_EVENT_MSG"},
+ {GG_EVENT_NOTIFY, "GG_EVENT_NOTIFY"},
+ {GG_EVENT_NOTIFY_DESCR, "GG_EVENT_NOTIFY_DESCR"},
+ {GG_EVENT_STATUS, "GG_EVENT_STATUS"},
+ {GG_EVENT_ACK, "GG_EVENT_ACK"},
+ {GG_EVENT_PONG, "GG_EVENT_PONG"},
+ {GG_EVENT_CONN_FAILED, "GG_EVENT_CONN_FAILED"},
+ {GG_EVENT_CONN_SUCCESS, "GG_EVENT_CONN_SUCCESS"},
+ {GG_EVENT_DISCONNECT, "GG_EVENT_DISCONNECT"},
+ {GG_EVENT_DCC_NEW, "GG_EVENT_DCC_NEW"},
+ {GG_EVENT_DCC_ERROR, "GG_EVENT_DCC_ERROR"},
+ {GG_EVENT_DCC_DONE, "GG_EVENT_DCC_DONE"},
+ {GG_EVENT_DCC_CLIENT_ACCEPT, "GG_EVENT_DCC_CLIENT_ACCEPT"},
+ {GG_EVENT_DCC_CALLBACK, "GG_EVENT_DCC_CALLBACK"},
+ {GG_EVENT_DCC_NEED_FILE_INFO, "GG_EVENT_DCC_NEED_FILE_INFO"},
+ {GG_EVENT_DCC_NEED_FILE_ACK, "GG_EVENT_DCC_NEED_FILE_ACK"},
+ {GG_EVENT_DCC_NEED_VOICE_ACK, "GG_EVENT_DCC_NEED_VOICE_ACK"},
+ {GG_EVENT_DCC_VOICE_DATA, "GG_EVENT_DCC_VOICE_DATA"},
+ {GG_EVENT_PUBDIR50_SEARCH_REPLY,"GG_EVENT_PUBDIR50_SEARCH_REPLY"},
+ {GG_EVENT_PUBDIR50_READ, "GG_EVENT_PUBDIR50_READ"},
+ {GG_EVENT_PUBDIR50_WRITE, "GG_EVENT_PUBDIR50_WRITE"},
+ {GG_EVENT_STATUS60, "GG_EVENT_STATUS60"},
+ {GG_EVENT_NOTIFY60, "GG_EVENT_NOTIFY60"},
+ {GG_EVENT_USERLIST, "GG_EVENT_USERLIST"},
+ {GG_EVENT_IMAGE_REQUEST, "GG_EVENT_IMAGE_REQUEST"},
+ {GG_EVENT_IMAGE_REPLY, "GG_EVENT_IMAGE_REPLY"},
+ {GG_EVENT_DCC_ACK, "GG_EVENT_DCC_ACK"},
+ {GG_EVENT_DCC7_NEW, "GG_EVENT_DCC7_NEW"},
+ {GG_EVENT_DCC7_ACCEPT, "GG_EVENT_DCC7_ACCEPT"},
+ {GG_EVENT_DCC7_REJECT, "GG_EVENT_DCC7_REJECT"},
+ {GG_EVENT_DCC7_CONNECTED, "GG_EVENT_DCC7_CONNECTED"},
+ {GG_EVENT_DCC7_ERROR, "GG_EVENT_DCC7_ERROR"},
+ {GG_EVENT_DCC7_DONE, "GG_EVENT_DCC7_DONE"},
+ {GG_EVENT_DCC7_PENDING, "GG_EVENT_DCC7_PENDING"},
+ {GG_EVENT_XML_EVENT, "GG_EVENT_XML_EVENT"},
+ {GG_EVENT_DISCONNECT_ACK, "GG_EVENT_DISCONNECT_ACK"},
+ {GG_EVENT_XML_ACTION, "GG_EVENT_XML_ACTION"},
+ {GG_EVENT_TYPING_NOTIFICATION, "GG_EVENT_TYPING_NOTIFICATION"},
+ {GG_EVENT_USER_DATA, "GG_EVENT_USER_DATA"},
+ {GG_EVENT_MULTILOGON_MSG, "GG_EVENT_MULTILOGON_MSG"},
+ {GG_EVENT_MULTILOGON_INFO, "GG_EVENT_MULTILOGON_INFO"},
+ {-1, "<unknown event>"}
+};
+
+const char *ggdebug_eventtype(gg_event *e)
+{
+ int i;
+ for(i = 0; ggdebug_eventype2string[i].type != -1; i++)
+ if (ggdebug_eventype2string[i].type == e->type)
+ return ggdebug_eventype2string[i].text;
+ return ggdebug_eventype2string[i].text;
+}
+
+#ifdef DEBUGMODE
+void gg_debughandler(int level, const char *format, va_list ap)
+{
+ char szText[1024], *szFormat = _strdup(format);
+ // Kill end line
+ char *nl = strrchr(szFormat, '\n');
+ if (nl) *nl = 0;
+
+ strncpy(szText, "[libgadu] \0", sizeof(szText));
+
+ mir_vsnprintf(szText + strlen(szText), sizeof(szText) - strlen(szText), szFormat, ap);
+ CallService(MS_NETLIB_LOG, (WPARAM) NULL, (LPARAM) szText);
+ free(szFormat);
+}
+#endif
+
+//////////////////////////////////////////////////////////
+// Log funcion
+
+int GGPROTO::netlog(const char *fmt, ...)
+{
+ va_list va;
+ char szText[1024];
+
+ va_start(va, fmt);
+ mir_vsnprintf(szText, sizeof(szText), fmt, va);
+ va_end(va);
+ return CallService(MS_NETLIB_LOG, (WPARAM)netlib, (LPARAM) szText);
+}
+
+//////////////////////////////////////////////////////////
+// main DLL function
+
+BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
+{
+ crc_gentable();
+ hInstance = hInst;
+#ifdef DEBUGMODE
+ gg_debug_handler = gg_debughandler;
+#endif
+ return TRUE;
+}
diff --git a/protocols/Gadu-Gadu/src/gg.h b/protocols/Gadu-Gadu/src/gg.h
new file mode 100644
index 0000000000..742f06b093
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/gg.h
@@ -0,0 +1,341 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GG_H
+#define GG_H
+
+#define MIRANDA_VER 0x0A00
+
+#if defined(__DEBUG__) || defined(_DEBUG) || defined(DEBUG)
+#define DEBUGMODE // Debug Mode
+#endif
+
+#if _WIN32_WINNT < 0x0501
+#define _WIN32_WINNT 0x0501
+#endif
+
+#include <m_stdhdr.h>
+
+// Windows headers
+// Visual C++ .NET tries to include winsock.h
+// which is very ver bad
+#if (_MSC_VER >= 1300)
+#include <winsock2.h>
+#else
+#include <windows.h>
+#endif
+#include <commctrl.h>
+#include <commdlg.h>
+#include <process.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/stat.h>
+
+// Miranda IM headers
+#include <newpluginapi.h>
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_database.h>
+#include <m_netlib.h>
+#include <m_protocols.h>
+#include <m_protomod.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_langpack.h>
+#include <m_skin.h>
+#include <m_utils.h>
+#include <m_ignore.h>
+#include <m_clist.h>
+#include <m_clistint.h>
+#include <m_options.h>
+#include <m_userinfo.h>
+#include <m_clui.h>
+#include <m_button.h>
+#include <m_clc.h>
+#include <m_message.h>
+#include <m_icolib.h>
+#include <m_imgsrvc.h>
+#include <m_genmenu.h>
+#include <m_file.h>
+#include <m_avatars.h>
+#include <m_xml.h>
+#include <m_chat.h>
+#include <m_popup.h>
+#include <win2k.h>
+
+// Custom profile folders plugin header
+#include "m_folders.h"
+
+// Visual C++ extras
+#ifdef _MSC_VER
+#define vsnprintf _vsnprintf
+#define snprintf _snprintf
+#define GGINLINE
+#else
+#define GGINLINE inline
+#endif
+
+// Plugin headers
+#include "resource.h"
+
+// libgadu headers
+extern "C" {
+#include "libgadu/libgadu.h"
+#include "dynstuff.h"
+};
+
+// Search
+// Extended search result structure, used for all searches
+struct GGSEARCHRESULT : public PROTOSEARCHRESULT
+{
+ uin_t uin;
+};
+
+typedef struct
+{
+ HANDLE hThread;
+ UINT dwThreadId;
+} GGTHREAD;
+
+typedef struct
+{
+ uin_t *recipients;
+ int recipients_count;
+ char id[32];
+ BOOL ignore;
+} GGGC;
+
+typedef struct
+{
+ char id[256];
+ char val[256];
+} GGTOKEN;
+
+#if 0 /* #ifdef DEBUGMODE */
+#define EnterCriticalSection(lpCS) {netlog(gg,"EnterCriticalSection @ %s:%d", __FILE__, __LINE__); EnterCriticalSection(lpCS);}
+#define LeaveCriticalSection(lpCS) {netlog(gg,"LeaveCriticalSection @ %s:%d", __FILE__, __LINE__); LeaveCriticalSection(lpCS);}
+#endif
+
+
+// Wrappers of the old interface
+#define GGDEF_PROTO "GG" // Default Proto
+#define GGDEF_PROTONAME "Gadu-Gadu" // Default ProtoName
+
+
+// Process handles / seqs
+#define GG_SEQ_INFO 100
+#define GG_SEQ_SEARCH 200
+#define GG_SEQ_GETNICK 300
+#define GG_SEQ_CHINFO 400
+
+// Services
+#define GGS_IMPORT_SERVER "%s/ImportFromServer"
+#define GGS_REMOVE_SERVER "%s/RemoveFromServer"
+#define GGS_IMPORT_TEXT "%s/ImportFromText"
+#define GGS_EXPORT_SERVER "%s/ExportFromServer"
+#define GGS_EXPORT_TEXT "%s/ExportFromText"
+
+#define GGS_SENDIMAGE "%s/SendImage"
+#define GGS_RECVIMAGE "%s/RecvImage"
+
+// Keys
+#define GG_PLUGINVERSION "Version" // Plugin version.. user for cleanup from previous versions
+
+#define GG_KEY_UIN "UIN" // Uin - unique number
+#define GG_KEY_PASSWORD "Password" // Password
+#define GG_KEY_EMAIL "e-mail" // E-mail
+#define GG_KEY_STATUS "Status" // Status
+#define GG_KEY_NICK "Nick" // Nick
+#define GG_KEY_STATUSDESCR "StatusMsg" // Users status description, to be compatible with MWClist
+ // should be stored in "CList" group
+#define GG_KEY_TOKEN "Token" // OAuth Access Token
+#define GG_KEY_TOKENSECRET "TokenSecret" // OAuth Access Token Secret
+
+#define GG_KEY_KEEPALIVE "KeepAlive" // Keep-alive support
+#define GG_KEYDEF_KEEPALIVE 1
+
+#define GG_KEY_SHOWCERRORS "ShowCErrors" // Show connection errors
+#define GG_KEYDEF_SHOWCERRORS 1
+
+#define GG_KEY_ARECONNECT "AReconnect" // Automatically reconnect
+#define GG_KEYDEF_ARECONNECT 0
+
+#define GG_KEY_LEAVESTATUSMSG "LeaveStatusMsg"// Leave status msg when disconnected
+#define GG_KEYDEF_LEAVESTATUSMSG 0
+#define GG_KEY_LEAVESTATUS "LeaveStatus"
+#define GG_KEYDEF_LEAVESTATUS 0
+
+#define GG_KEY_FRIENDSONLY "FriendsOnly" // Friend only visibility
+#define GG_KEYDEF_FRIENDSONLY 0
+
+#define GG_KEY_SHOWLINKS "ShowLinks" // Show links from unknown contacts
+#define GG_KEYDEF_SHOWLINKS 0
+
+#define GG_KEY_ENABLEAVATARS "EnableAvatars" // Enable avatars support
+#define GG_KEYDEF_ENABLEAVATARS 1
+
+#define GG_KEY_AVATARHASH "AvatarHash" // Contact's avatar hash
+
+#define GG_KEY_AVATARURL "AvatarURL" // Contact's avatar URL
+
+#define GG_KEY_AVATARTYPE "AvatarType" // Contact's avatar format
+#define GG_KEYDEF_AVATARTYPE PA_FORMAT_UNKNOWN
+
+#define GG_KEY_AVATARREQUESTED "AvatarRequested" // When contact's avatar is requested
+#define GG_KEYDEF_AVATARREQUESTED 0
+
+#define GG_KEY_SHOWINVISIBLE "ShowInvisible" // Show invisible users when described
+#define GG_KEYDEF_SHOWINVISIBLE 0
+
+#define GG_KEY_IGNORECONF "IgnoreConf" // Ignore incoming conference messages
+#define GG_KEYDEF_IGNORECONF 0
+
+#define GG_KEY_IMGRECEIVE "ReceiveImg" // Popup image window automatically
+#define GG_KEYDEF_IMGRECEIVE 1
+
+#define GG_KEY_IMGMETHOD "PopupImg" // Popup image window automatically
+#define GG_KEYDEF_IMGMETHOD 1
+
+#define GG_KEY_MSGACK "MessageAck" // Acknowledge when sending msg
+#define GG_KEYDEF_MSGACK 1
+
+#define GG_KEY_MANUALHOST "ManualHost" // Specify by hand server host/port
+#define GG_KEYDEF_MANUALHOST 0
+#define GG_KEY_SSLCONN "SSLConnection" // Use SSL/TLS for connections
+#define GG_KEYDEF_SSLCONN 0
+#define GG_KEY_SERVERHOSTS "ServerHosts" // NL separated list of hosts for server connection
+#define GG_KEYDEF_SERVERHOSTS "91.197.13.54\r\n91.197.13.66\r\n91.197.13.69\r\n91.197.13.72\r\n91.197.13.75\r\n91.197.13.81"
+
+#define GG_KEY_CLIENTIP "IP" // Contact IP (by notify)
+#define GG_KEY_CLIENTPORT "ClientPort" // Contact port
+#define GG_KEY_CLIENTVERSION "ClientVersion" // Contact app version
+
+#define GG_KEY_DIRECTCONNS "DirectConns" // Use direct connections
+#define GG_KEYDEF_DIRECTCONNS 1
+#define GG_KEY_DIRECTPORT "DirectPort" // Direct connections port
+#define GG_KEYDEF_DIRECTPORT 1550
+
+#define GG_KEY_FORWARDING "Forwarding" // Use forwarding
+#define GG_KEYDEF_FORWARDING 0
+#define GG_KEY_FORWARDHOST "ForwardHost" // Forwarding host (firewall)
+#define GG_KEY_FORWARDPORT "ForwardPort" // Forwarding port (firewall port)
+#define GG_KEYDEF_FORWARDPORT 1550 // Forwarding port (firewall port)
+
+#define GG_KEY_GC_POLICY_UNKNOWN "GCPolicyUnknown"
+#define GG_KEYDEF_GC_POLICY_UNKNOWN 1
+
+#define GG_KEY_GC_COUNT_UNKNOWN "GCCountUnknown"
+#define GG_KEYDEF_GC_COUNT_UNKNOWN 5
+
+#define GG_KEY_GC_POLICY_TOTAL "GCPolicyTotal"
+#define GG_KEYDEF_GC_POLICY_TOTAL 1
+
+#define GG_KEY_GC_COUNT_TOTAL "GCCountTotal"
+#define GG_KEYDEF_GC_COUNT_TOTAL 10
+
+#define GG_KEY_GC_POLICY_DEFAULT "GCPolicyDefault"
+#define GG_KEYDEF_GC_POLICY_DEFAULT 0
+
+#define GG_KEY_BLOCK "Block" // Contact is blocked
+#define GG_KEY_APPARENT "ApparentMode" // Visible list
+
+#define GG_KEY_TIMEDEVIATION "TimeDeviation" // Max time deviation for connections (seconds)
+#define GG_KEYDEF_TIMEDEVIATION 300
+
+#define GG_KEY_LOGONTIME "LogonTS"
+
+#define GG_KEY_RECONNINTERVAL "ReconnectInterval"
+#define GG_KEYDEF_RECONNINTERVAL 3000
+
+// chpassdlgproc() multipurpose dialog proc modes
+#define GG_USERUTIL_PASS 0
+#define GG_USERUTIL_CREATE 1
+#define GG_USERUTIL_REMOVE 2
+#define GG_USERUTIL_EMAIL 3
+
+// popup flags
+#define GG_POPUP_ALLOW_MSGBOX 1
+#define GG_POPUP_ONCE 2
+#define GG_POPUP_ERROR 4
+#define GG_POPUP_WARNING 8
+#define GG_POPUP_MULTILOGON 16
+
+#define LocalEventUnhook(hook) if (hook) UnhookEvent(hook)
+
+// Some MSVC compatibility with gcc
+#ifdef _MSC_VER
+#ifndef strcasecmp
+#define strcasecmp _strcmpi
+#endif
+#ifndef strncasecmp
+#define strncasecmp _strnicmp
+#endif
+#endif
+
+// Global variables
+/////////////////////////////////////////////////
+
+extern HINSTANCE hInstance;
+extern CLIST_INTERFACE *pcli;
+extern list_t g_Instances;
+extern PLUGININFOEX pluginInfo;
+
+// Screen saver
+#ifndef SPI_GETSCREENSAVERRUNNING
+#define SPI_GETSCREENSAVERRUNNING 114
+#endif
+
+/////////////////////////////////////////////////
+// Methods
+
+/* Helper functions */
+const char *http_error_string(int h);
+unsigned long crc_get(char *mem);
+int gg_normalizestatus(int status);
+char *gg_status2db(int status, const char *suffix);
+TCHAR *ws_strerror(int code);
+uint32_t swap32(uint32_t x);
+const char *gg_version2string(int v);
+
+/* Avatar functions */
+char *gg_avatarhash(char *param);
+
+/* IcoLib functions */
+void gg_icolib_init();
+HICON LoadIconEx(const char* name, BOOL big);
+HANDLE GetIconHandle(int iconId);
+void ReleaseIconEx(const char* name, BOOL big);
+void WindowSetIcon(HWND hWnd, const char* name);
+void WindowFreeIcon(HWND hWnd);
+
+/* URI parser functions */
+void gg_links_instancemenu_init();
+void gg_links_init();
+void gg_links_destroy();
+
+#define UIN2ID(uin,id) _itoa(uin,id,10)
+
+// Debug functions
+const char *ggdebug_eventtype(gg_event *e);
+
+#include "gg_proto.h"
+
+#endif
diff --git a/protocols/Gadu-Gadu/src/gg_proto.cpp b/protocols/Gadu-Gadu/src/gg_proto.cpp
new file mode 100644
index 0000000000..0a0db3a173
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/gg_proto.cpp
@@ -0,0 +1,804 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+
+GGPROTO::GGPROTO(const char* pszProtoName, const TCHAR* tszUserName)
+{
+ // Init mutexes
+ InitializeCriticalSection(&sess_mutex);
+ InitializeCriticalSection(&ft_mutex);
+ InitializeCriticalSection(&img_mutex);
+ InitializeCriticalSection(&modemsg_mutex);
+ InitializeCriticalSection(&avatar_mutex);
+ InitializeCriticalSection(&sessions_mutex);
+
+ // Init instance names
+ m_szModuleName = mir_strdup(pszProtoName);
+ m_tszUserName = mir_tstrdup(tszUserName);
+ m_szProtoName = GGDEF_PROTONAME;
+ m_iVersion = 2;
+
+ // Register netlib user
+ TCHAR name[128];
+ mir_sntprintf(name, SIZEOF(name), TranslateT("%s connection"), m_tszUserName);
+
+ NETLIBUSER nlu = { 0 };
+ nlu.cbSize = sizeof(nlu);
+ nlu.flags = NUF_TCHAR | NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.ptszDescriptiveName = name;
+
+ netlib = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
+
+ // Register services
+ createProtoService(PS_GETAVATARCAPS, &GGPROTO::getavatarcaps);
+ createProtoService(PS_GETAVATARINFOT, &GGPROTO::getavatarinfo);
+ createProtoService(PS_GETMYAVATAR, &GGPROTO::getmyavatar);
+ createProtoService(PS_SETMYAVATAR, &GGPROTO::setmyavatar);
+
+ createProtoService(PS_GETMYAWAYMSG, &GGPROTO::getmyawaymsg);
+ createProtoService(PS_CREATEACCMGRUI, &GGPROTO::get_acc_mgr_gui);
+
+ createProtoService(PS_LEAVECHAT, &GGPROTO::leavechat);
+
+ // Offline contacts and clear logon time
+ setalloffline();
+ db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, 0);
+
+ DWORD dwVersion;
+ if ((dwVersion = db_get_dw(NULL, m_szModuleName, GG_PLUGINVERSION, 0)) < pluginInfo.version)
+ cleanuplastplugin(dwVersion);
+
+ links_instance_init();
+ initavatarrequestthread();
+
+ TCHAR szPath[MAX_PATH];
+ TCHAR *tmpPath = Utils_ReplaceVarsT( _T("%miranda_avatarcache%"));
+ mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s"), tmpPath, m_szModuleName);
+ mir_free(tmpPath);
+ hAvatarsFolder = FoldersRegisterCustomPathT(m_szModuleName, "Avatars", szPath);
+
+ tmpPath = Utils_ReplaceVarsT( _T("%miranda_userdata%"));
+ mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s\\ImageCache"), tmpPath, m_szModuleName);
+ mir_free(tmpPath);
+ hImagesFolder = FoldersRegisterCustomPathT(m_szModuleName, "Images", szPath);
+}
+
+GGPROTO::~GGPROTO()
+{
+#ifdef DEBUGMODE
+ netlog("gg_proto_uninit(): destroying protocol interface");
+#endif
+
+ // Destroy modules
+ block_uninit();
+ img_destroy();
+ keepalive_destroy();
+ gc_destroy();
+
+ if (hMenuRoot)
+ CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)hMenuRoot, 0);
+
+ // Close handles
+ Netlib_CloseHandle(netlib);
+
+ // Destroy mutexes
+ DeleteCriticalSection(&sess_mutex);
+ DeleteCriticalSection(&ft_mutex);
+ DeleteCriticalSection(&img_mutex);
+ DeleteCriticalSection(&modemsg_mutex);
+ DeleteCriticalSection(&avatar_mutex);
+ DeleteCriticalSection(&sessions_mutex);
+
+ // Free status messages
+ if (modemsg.online) mir_free(modemsg.online);
+ if (modemsg.away) mir_free(modemsg.away);
+ if (modemsg.dnd) mir_free(modemsg.dnd);
+ if (modemsg.freechat) mir_free(modemsg.freechat);
+ if (modemsg.invisible) mir_free(modemsg.invisible);
+ if (modemsg.offline) mir_free(modemsg.offline);
+
+ mir_free(m_szModuleName);
+ mir_free(m_tszUserName);
+}
+
+//////////////////////////////////////////////////////////
+// Dummies for function that have to be implemented
+
+HANDLE GGPROTO::AddToListByEvent(int flags, int iContact, HANDLE hDbEvent) { return NULL; }
+int GGPROTO::Authorize(HANDLE hDbEvent) { return 1; }
+int GGPROTO::AuthDeny(HANDLE hDbEvent, const TCHAR *szReason) { return 1; }
+int GGPROTO::AuthRecv(HANDLE hContact, PROTORECVEVENT *pre) { return 1; }
+int GGPROTO::AuthRequest(HANDLE hContact, const TCHAR *szMessage) { return 1; }
+HANDLE GGPROTO::ChangeInfo(int iInfoType, void *pInfoData) { return NULL; }
+int GGPROTO::FileResume(HANDLE hTransfer, int *action, const PROTOCHAR** szFilename) { return 1; }
+HANDLE GGPROTO::SearchByEmail(const PROTOCHAR *email) { return NULL; }
+int GGPROTO::RecvContacts(HANDLE hContact, PROTORECVEVENT *pre) { return 1; }
+int GGPROTO::RecvUrl(HANDLE hContact, PROTORECVEVENT *pre) { return 1; }
+int GGPROTO::SendContacts(HANDLE hContact, int flags, int nContacts, HANDLE *hContactsList) { return 1; }
+int GGPROTO::SendUrl(HANDLE hContact, int flags, const char *url) { return 1; }
+int GGPROTO::RecvAwayMsg(HANDLE hContact, int mode, PROTORECVEVENT *evt) { return 1; }
+int GGPROTO::SendAwayMsg(HANDLE hContact, HANDLE hProcess, const char *msg) { return 1; }
+
+//////////////////////////////////////////////////////////
+// when contact is added to list
+
+HANDLE GGPROTO::AddToList(int flags, PROTOSEARCHRESULT *psr)
+{
+ GGSEARCHRESULT *sr = (GGSEARCHRESULT *)psr;
+ uin_t uin;
+
+ if (psr->cbSize == sizeof(GGSEARCHRESULT))
+ uin = sr->uin;
+ else
+ uin = _ttoi(psr->id);
+
+ return getcontact(uin, 1, flags & PALF_TEMPORARY ? 0 : 1, sr->nick);
+}
+
+//////////////////////////////////////////////////////////
+// checks proto capabilities
+
+DWORD_PTR GGPROTO::GetCaps(int type, HANDLE hContact)
+{
+ switch (type) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_BASICSEARCH | PF1_EXTSEARCH | PF1_EXTSEARCHUI | PF1_SEARCHBYNAME |
+ PF1_MODEMSG | PF1_NUMERICUSERID | PF1_VISLIST | PF1_FILE;
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE |
+ PF2_LONGAWAY;
+ case PFLAGNUM_3:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE;
+ case PFLAGNUM_4:
+ return PF4_NOCUSTOMAUTH | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_IMSENDOFFLINE;
+ case PFLAGNUM_5:
+ return PF2_LONGAWAY;
+ case PFLAG_UNIQUEIDTEXT:
+ return (DWORD_PTR) Translate("Gadu-Gadu Number");
+ case PFLAG_UNIQUEIDSETTING:
+ return (DWORD_PTR) GG_KEY_UIN;
+ }
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// loads protocol icon
+
+HICON GGPROTO::GetIcon(int iconIndex)
+{
+ if (LOWORD(iconIndex) == PLI_PROTOCOL)
+ {
+ if (iconIndex & PLIF_ICOLIBHANDLE)
+ return (HICON)GetIconHandle(IDI_GG);
+
+ BOOL big = (iconIndex & PLIF_SMALL) == 0;
+ HICON hIcon = LoadIconEx("main", big);
+
+ if (iconIndex & PLIF_ICOLIB)
+ return hIcon;
+
+ hIcon = CopyIcon(hIcon);
+ ReleaseIconEx("main", big);
+ return hIcon;
+ }
+
+ return (HICON)NULL;
+}
+
+//////////////////////////////////////////////////////////
+// user info request
+
+void __cdecl GGPROTO::cmdgetinfothread(void *hContact)
+{
+ SleepEx(100, FALSE);
+ netlog("gg_cmdgetinfothread(): Failed info retreival.");
+ ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE) 1, 0);
+}
+
+int GGPROTO::GetInfo(HANDLE hContact, int infoType)
+{
+ gg_pubdir50_t req;
+
+ // Custom contact info
+ if (hContact)
+ {
+ if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)))
+ {
+ forkthread(&GGPROTO::cmdgetinfothread, hContact);
+ return 1;
+ }
+
+ // Add uin and search it
+ gg_pubdir50_add(req, GG_PUBDIR50_UIN, ditoa((uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0)));
+ gg_pubdir50_seq_set(req, GG_SEQ_INFO);
+
+ netlog("gg_getinfo(): Requesting user info.", req->seq);
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ if (!gg_pubdir50(sess, req))
+ {
+ LeaveCriticalSection(&sess_mutex);
+ forkthread(&GGPROTO::cmdgetinfothread, hContact);
+ return 1;
+ }
+ LeaveCriticalSection(&sess_mutex);
+ }
+ }
+ // Own contact info
+ else
+ {
+ if (!(req = gg_pubdir50_new(GG_PUBDIR50_READ)))
+ {
+ forkthread(&GGPROTO::cmdgetinfothread, hContact);
+ return 1;
+ }
+
+ // Add seq
+ gg_pubdir50_seq_set(req, GG_SEQ_CHINFO);
+
+ netlog("gg_getinfo(): Requesting owner info.", req->seq);
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ if (!gg_pubdir50(sess, req))
+ {
+ LeaveCriticalSection(&sess_mutex);
+ forkthread(&GGPROTO::cmdgetinfothread, hContact);
+ return 1;
+ }
+ LeaveCriticalSection(&sess_mutex);
+ }
+ }
+ netlog("gg_getinfo(): Seq %d.", req->seq);
+ gg_pubdir50_free(req);
+
+ return 1;
+}
+
+//////////////////////////////////////////////////////////
+// when basic search
+
+void __cdecl GGPROTO::searchthread(void *)
+{
+ SleepEx(100, FALSE);
+ netlog("gg_searchthread(): Failed search.");
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)1, 0);
+}
+
+HANDLE GGPROTO::SearchBasic(const PROTOCHAR *id)
+{
+ if (!isonline())
+ return (HANDLE)0;
+
+ gg_pubdir50_t req;
+ if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH))) {
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HANDLE)1;
+ }
+
+ TCHAR *ida = mir_tstrdup(id);
+
+ // Add uin and search it
+ gg_pubdir50_add(req, GG_PUBDIR50_UIN, _T2A(ida));
+ gg_pubdir50_seq_set(req, GG_SEQ_SEARCH);
+
+ mir_free(ida);
+
+ EnterCriticalSection(&sess_mutex);
+ if (!gg_pubdir50(sess, req))
+ {
+ LeaveCriticalSection(&sess_mutex);
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HANDLE)1;
+ }
+ LeaveCriticalSection(&sess_mutex);
+ netlog("gg_basicsearch(): Seq %d.", req->seq);
+ gg_pubdir50_free(req);
+
+ return (HANDLE)1;
+}
+
+//////////////////////////////////////////////////////////
+// search by details
+
+HANDLE GGPROTO::SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *lastName)
+{
+ gg_pubdir50_t req;
+ unsigned long crc;
+ char data[512] = "\0";
+
+ // Check if connected and if there's a search data
+ if (!isonline())
+ return 0;
+
+ if (!nick && !firstName && !lastName)
+ return 0;
+
+ if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)))
+ {
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HANDLE)1;
+ }
+
+ // Add uin and search it
+ if (nick)
+ {
+ char *nickA = mir_t2a(nick);
+ gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, nickA);
+ strncat(data, nickA, sizeof(data) - strlen(data));
+ mir_free(nickA);
+ }
+ strncat(data, ".", sizeof(data) - strlen(data));
+
+ if (firstName)
+ {
+ char *firstNameA = mir_t2a(firstName);
+ gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, firstNameA);
+ strncat(data, firstNameA, sizeof(data) - strlen(data));
+ mir_free(firstNameA);
+ }
+ strncat(data, ".", sizeof(data) - strlen(data));
+
+ if (lastName)
+ {
+ char *lastNameA = mir_t2a(lastName);
+ gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, lastNameA);
+ strncat(data, lastNameA, sizeof(data) - strlen(data));
+ mir_free(lastNameA);
+ }
+ strncat(data, ".", sizeof(data) - strlen(data));
+
+ // Count crc & check if the data was equal if yes do same search with shift
+ crc = crc_get(data);
+
+ if (crc == last_crc && next_uin)
+ gg_pubdir50_add(req, GG_PUBDIR50_START, ditoa(next_uin));
+ else
+ last_crc = crc;
+
+ gg_pubdir50_seq_set(req, GG_SEQ_SEARCH);
+
+ EnterCriticalSection(&sess_mutex);
+ if (!gg_pubdir50(sess, req))
+ {
+ LeaveCriticalSection(&sess_mutex);
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HANDLE)1;
+ }
+ LeaveCriticalSection(&sess_mutex);
+ netlog("gg_searchbyname(): Seq %d.", req->seq);
+ gg_pubdir50_free(req);
+
+ return (HANDLE)1;
+}
+
+//////////////////////////////////////////////////////////
+// search by advanced
+
+HWND GGPROTO::SearchAdvanced(HWND hwndDlg)
+{
+ gg_pubdir50_t req;
+ char text[64], data[512] = "\0";
+ unsigned long crc;
+
+ // Check if connected
+ if (!isonline()) return (HWND)0;
+
+ if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)))
+ {
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HWND)1;
+ }
+
+ // Fetch search data
+ GetDlgItemTextA(hwndDlg, IDC_FIRSTNAME, text, sizeof(text));
+ if (strlen(text))
+ {
+ gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, text);
+ strncat(data, text, sizeof(data) - strlen(data));
+ }
+ /* 1 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ GetDlgItemTextA(hwndDlg, IDC_LASTNAME, text, sizeof(text));
+ if (strlen(text))
+ {
+ gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, text);
+ strncat(data, text, sizeof(data) - strlen(data));
+ }
+ /* 2 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ GetDlgItemTextA(hwndDlg, IDC_NICKNAME, text, sizeof(text));
+ if (strlen(text))
+ {
+ gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, text);
+ strncat(data, text, sizeof(data) - strlen(data));
+ }
+ /* 3 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ GetDlgItemTextA(hwndDlg, IDC_CITY, text, sizeof(text));
+ if (strlen(text))
+ {
+ gg_pubdir50_add(req, GG_PUBDIR50_CITY, text);
+ strncat(data, text, sizeof(data) - strlen(data));
+ }
+ /* 4 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ GetDlgItemTextA(hwndDlg, IDC_AGEFROM, text, sizeof(text));
+ if (strlen(text))
+ {
+ int yearTo = atoi(text);
+ int yearFrom;
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ int ay = lt->tm_year + 1900;
+ char age[16];
+
+ GetDlgItemTextA(hwndDlg, IDC_AGETO, age, sizeof(age));
+ yearFrom = atoi(age);
+
+ // Count & fix ranges
+ if (!yearTo)
+ yearTo = ay;
+ else
+ yearTo = ay - yearTo;
+ if (!yearFrom)
+ yearFrom = 0;
+ else
+ yearFrom = ay - yearFrom;
+ mir_snprintf(text, sizeof(text), "%d %d", yearFrom, yearTo);
+
+ gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, text);
+ strncat(data, text, sizeof(data) - strlen(data));
+ }
+ /* 5 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ switch(SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_GETCURSEL, 0, 0))
+ {
+ case 1:
+ gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_FEMALE);
+ strncat(data, GG_PUBDIR50_GENDER_MALE, sizeof(data) - strlen(data));
+ break;
+ case 2:
+ gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_MALE);
+ strncat(data, GG_PUBDIR50_GENDER_FEMALE, sizeof(data) - strlen(data));
+ break;
+ }
+ /* 6 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_ONLYCONNECTED))
+ {
+ gg_pubdir50_add(req, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE);
+ strncat(data, GG_PUBDIR50_ACTIVE_TRUE, sizeof(data) - strlen(data));
+ }
+ /* 7 */ strncat(data, ".", sizeof(data) - strlen(data));
+
+ // No data entered
+ if (strlen(data) <= 7 || (strlen(data) == 8 && IsDlgButtonChecked(hwndDlg, IDC_ONLYCONNECTED))) return (HWND)0;
+
+ // Count crc & check if the data was equal if yes do same search with shift
+ crc = crc_get(data);
+
+ if (crc == last_crc && next_uin)
+ gg_pubdir50_add(req, GG_PUBDIR50_START, ditoa(next_uin));
+ else
+ last_crc = crc;
+
+ gg_pubdir50_seq_set(req, GG_SEQ_SEARCH);
+
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ if (!gg_pubdir50(sess, req))
+ {
+ LeaveCriticalSection(&sess_mutex);
+ forkthread(&GGPROTO::searchthread, NULL);
+ return (HWND)1;
+ }
+ LeaveCriticalSection(&sess_mutex);
+ }
+ netlog("gg_searchbyadvanced(): Seq %d.", req->seq);
+ gg_pubdir50_free(req);
+
+ return (HWND)1;
+}
+
+//////////////////////////////////////////////////////////
+// create adv search dialog
+
+static INT_PTR CALLBACK gg_advancedsearchdlgproc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)
+{
+ switch(message) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)_T("")); // 0
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Female")); // 1
+ SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Male")); // 2
+ return TRUE;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ SendMessage(GetParent(hwndDlg), WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED), (LPARAM)GetDlgItem(GetParent(hwndDlg),IDOK));
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+HWND GGPROTO::CreateExtendedSearchUI(HWND owner)
+{
+ return CreateDialogParam(hInstance,
+ MAKEINTRESOURCE(IDD_GGADVANCEDSEARCH), owner, gg_advancedsearchdlgproc, (LPARAM)this);
+}
+
+//////////////////////////////////////////////////////////
+// when messsage received
+
+int GGPROTO::RecvMsg(HANDLE hContact, PROTORECVEVENT *pre)
+{
+ return Proto_RecvMessage(hContact, pre);
+}
+
+//////////////////////////////////////////////////////////
+// when messsage sent
+
+typedef struct
+{
+ HANDLE hContact;
+ int seq;
+} GG_SEQ_ACK;
+
+void __cdecl GGPROTO::sendackthread(void *ack)
+{
+ SleepEx(100, FALSE);
+ ProtoBroadcastAck(m_szModuleName, ((GG_SEQ_ACK *)ack)->hContact,
+ ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) ((GG_SEQ_ACK *)ack)->seq, 0);
+ mir_free(ack);
+}
+
+int GGPROTO::SendMsg(HANDLE hContact, int flags, const char *msg)
+{
+ uin_t uin;
+
+ if (msg && isonline() && (uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ int seq;
+ EnterCriticalSection(&sess_mutex);
+ seq = gg_send_message(sess, GG_CLASS_CHAT, uin, (BYTE*)msg);
+ LeaveCriticalSection(&sess_mutex);
+ if (!db_get_b(NULL, m_szModuleName, GG_KEY_MSGACK, GG_KEYDEF_MSGACK))
+ {
+ // Auto-ack message without waiting for server ack
+ GG_SEQ_ACK *ack = (GG_SEQ_ACK*)mir_alloc(sizeof(GG_SEQ_ACK));
+ if (ack)
+ {
+ ack->seq = seq;
+ ack->hContact = hContact;
+ forkthread(&GGPROTO::sendackthread, ack);
+ }
+ }
+ return seq;
+ }
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// visible lists
+
+int GGPROTO::SetApparentMode(HANDLE hContact, int mode)
+{
+ db_set_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD)mode);
+ notifyuser(hContact, 1);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// sets protocol status
+
+int GGPROTO::SetStatus(int iNewStatus)
+{
+ int nNewStatus = gg_normalizestatus(iNewStatus);
+
+ EnterCriticalSection(&modemsg_mutex);
+ m_iDesiredStatus = nNewStatus;
+ LeaveCriticalSection(&modemsg_mutex);
+
+ // If waiting for connection retry attempt then signal to stop that
+ if (hConnStopEvent) SetEvent(hConnStopEvent);
+
+ if (m_iStatus == nNewStatus) return 0;
+ netlog("gg_setstatus(): PS_SETSTATUS(%d) normalized to %d.", iNewStatus, nNewStatus);
+ refreshstatus(nNewStatus);
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// when away message is requested
+
+void __cdecl GGPROTO::getawaymsgthread(void *hContact)
+{
+ DBVARIANT dbv;
+
+ SleepEx(100, FALSE);
+ if (!db_get_s(hContact, "CList", GG_KEY_STATUSDESCR, &dbv, DBVT_TCHAR))
+ {
+ ProtoBroadcastAck(m_szProtoName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE) 1, (LPARAM) dbv.ptszVal);
+ netlog("gg_getawaymsg(): Reading away msg <" TCHAR_STR_PARAM ">.", dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ ProtoBroadcastAck(m_szProtoName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE) 1, (LPARAM) NULL);
+}
+
+HANDLE GGPROTO::GetAwayMsg(HANDLE hContact)
+{
+ forkthread(&GGPROTO::getawaymsgthread, hContact);
+ return (HANDLE)1;
+}
+
+//////////////////////////////////////////////////////////
+// when away message is being set
+
+int GGPROTO::SetAwayMsg(int iStatus, const PROTOCHAR *msgt)
+{
+ int status = gg_normalizestatus(iStatus);
+ char **szMsg;
+ char *msg = mir_t2a(msgt);
+
+ netlog("gg_setawaymsg(): PS_SETAWAYMSG(%d, \"%s\").", iStatus, msg);
+
+ EnterCriticalSection(&modemsg_mutex);
+ // Select proper msg
+ switch(status)
+ {
+ case ID_STATUS_ONLINE:
+ szMsg = &modemsg.online;
+ break;
+ case ID_STATUS_AWAY:
+ szMsg = &modemsg.away;
+ break;
+ case ID_STATUS_DND:
+ szMsg = &modemsg.dnd;
+ break;
+ case ID_STATUS_FREECHAT:
+ szMsg = &modemsg.freechat;
+ break;
+ case ID_STATUS_INVISIBLE:
+ szMsg = &modemsg.invisible;
+ break;
+ default:
+ LeaveCriticalSection(&modemsg_mutex);
+ mir_free(msg);
+ return 1;
+ }
+
+ // Check if we change status here somehow
+ if (*szMsg && msg && !strcmp(*szMsg, msg)
+ || !*szMsg && (!msg || !*msg))
+ {
+ if (status == m_iDesiredStatus && m_iDesiredStatus == m_iStatus)
+ {
+ netlog("gg_setawaymsg(): Message hasn't been changed, return.");
+ LeaveCriticalSection(&modemsg_mutex);
+ mir_free(msg);
+ return 0;
+ }
+ }
+ else
+ {
+ if (*szMsg)
+ mir_free(*szMsg);
+ *szMsg = msg && *msg ? mir_strdup(msg) : NULL;
+#ifdef DEBUGMODE
+ netlog("gg_setawaymsg(): Message changed.");
+#endif
+ }
+ LeaveCriticalSection(&modemsg_mutex);
+
+ // Change the status if it was desired by PS_SETSTATUS
+ if (status == m_iDesiredStatus)
+ refreshstatus(status);
+
+ mir_free(msg);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// sends a notification that the user is typing a message
+
+int GGPROTO::UserIsTyping(HANDLE hContact, int type)
+{
+ uin_t uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0);
+
+ if (!uin || !isonline()) return 0;
+
+ if (type == PROTOTYPE_SELFTYPING_ON || type == PROTOTYPE_SELFTYPING_OFF) {
+ EnterCriticalSection(&sess_mutex);
+ gg_typing_notification(sess, uin, (type == PROTOTYPE_SELFTYPING_ON));
+ LeaveCriticalSection(&sess_mutex);
+ }
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// Custom protocol event
+
+int GGPROTO::OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam)
+{
+ switch( eventType ) {
+ case EV_PROTO_ONLOAD:
+ {
+ hookProtoEvent(ME_OPT_INITIALISE, &GGPROTO::options_init);
+ hookProtoEvent(ME_USERINFO_INITIALISE, &GGPROTO::details_init);
+
+ // Init misc stuff
+ gg_icolib_init();
+ initpopups();
+ gc_init();
+ keepalive_init();
+ img_init();
+ block_init();
+
+ // Try to fetch user avatar
+ getUserAvatar();
+ break;
+ }
+ case EV_PROTO_ONEXIT:
+ // Stop avatar request thread
+ uninitavatarrequestthread();
+
+ // Stop main connection session thread
+ threadwait(&pth_sess);
+ img_shutdown();
+ sessions_closedlg();
+ break;
+
+ case EV_PROTO_ONOPTIONS:
+ return options_init(wParam, lParam);
+
+ case EV_PROTO_ONMENU:
+ menus_init();
+ break;
+
+ case EV_PROTO_ONRENAME:
+ if (hMenuRoot) {
+ CLISTMENUITEM mi = {0};
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIM_NAME | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
+ mi.ptszName = m_tszUserName;
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuRoot, (LPARAM)&mi);
+ }
+ break;
+
+ case EV_PROTO_ONCONTACTDELETED:
+ return contactdeleted(wParam, lParam);
+
+ case EV_PROTO_DBSETTINGSCHANGED:
+ return dbsettingchanged(wParam, lParam);
+ }
+ return TRUE;
+}
diff --git a/protocols/Gadu-Gadu/src/gg_proto.h b/protocols/Gadu-Gadu/src/gg_proto.h
new file mode 100644
index 0000000000..8765392328
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/gg_proto.h
@@ -0,0 +1,295 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GGPROTO_H
+#define GGPROTO_H
+
+struct GGPROTO;
+typedef void ( __cdecl GGPROTO::*GGThreadFunc )( void* );
+typedef int ( __cdecl GGPROTO::*GGEventFunc )( WPARAM, LPARAM );
+typedef INT_PTR ( __cdecl GGPROTO::*GGServiceFunc )( WPARAM, LPARAM );
+
+struct GGPROTO : public PROTO_INTERFACE, public MZeroedObject
+{
+ GGPROTO( const char*, const TCHAR* );
+ ~GGPROTO();
+
+ //====================================================================================
+ // PROTO_INTERFACE
+ //====================================================================================
+
+ virtual HANDLE __cdecl AddToList( int flags, PROTOSEARCHRESULT* psr );
+ virtual HANDLE __cdecl AddToListByEvent( int flags, int iContact, HANDLE hDbEvent );
+
+ virtual int __cdecl Authorize( HANDLE hDbEvent );
+ virtual int __cdecl AuthDeny( HANDLE hDbEvent, const TCHAR* szReason );
+ virtual int __cdecl AuthRecv( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl AuthRequest( HANDLE hContact, const TCHAR* szMessage );
+
+ virtual HANDLE __cdecl ChangeInfo( int iInfoType, void* pInfoData );
+
+ virtual HANDLE __cdecl FileAllow( HANDLE hContact, HANDLE hTransfer, const TCHAR* szPath );
+ virtual int __cdecl FileCancel( HANDLE hContact, HANDLE hTransfer );
+ virtual int __cdecl FileDeny( HANDLE hContact, HANDLE hTransfer, const TCHAR* szReason );
+ virtual int __cdecl FileResume( HANDLE hTransfer, int* action, const TCHAR** szFilename );
+
+ virtual DWORD_PTR __cdecl GetCaps( int type, HANDLE hContact = NULL );
+ virtual HICON __cdecl GetIcon( int iconIndex );
+ virtual int __cdecl GetInfo( HANDLE hContact, int infoType );
+
+ virtual HANDLE __cdecl SearchBasic( const TCHAR* id );
+ virtual HANDLE __cdecl SearchByEmail( const TCHAR* email );
+ virtual HANDLE __cdecl SearchByName( const TCHAR* nick, const TCHAR* firstName, const TCHAR* lastName );
+ virtual HWND __cdecl SearchAdvanced( HWND owner );
+ virtual HWND __cdecl CreateExtendedSearchUI( HWND owner );
+
+ virtual int __cdecl RecvContacts( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl RecvFile( HANDLE hContact, PROTORECVFILET* );
+ virtual int __cdecl RecvMsg( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl RecvUrl( HANDLE hContact, PROTORECVEVENT* );
+
+ virtual int __cdecl SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList );
+ virtual HANDLE __cdecl SendFile( HANDLE hContact, const TCHAR* szDescription, TCHAR** ppszFiles );
+ virtual int __cdecl SendMsg( HANDLE hContact, int flags, const char* msg );
+ virtual int __cdecl SendUrl( HANDLE hContact, int flags, const char* url );
+
+ virtual int __cdecl SetApparentMode( HANDLE hContact, int mode );
+ virtual int __cdecl SetStatus( int iNewStatus );
+
+ virtual HANDLE __cdecl GetAwayMsg( HANDLE hContact );
+ virtual int __cdecl RecvAwayMsg( HANDLE hContact, int mode, PROTORECVEVENT* evt );
+ virtual int __cdecl SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg );
+ virtual int __cdecl SetAwayMsg( int m_iStatus, const TCHAR* msg );
+
+ virtual int __cdecl UserIsTyping( HANDLE hContact, int type );
+
+ virtual int __cdecl OnEvent( PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam );
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // Services
+
+ INT_PTR __cdecl blockuser(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl getmyawaymsg(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl get_acc_mgr_gui(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl leavechat(WPARAM wParam, LPARAM lParam);
+
+ void __cdecl sendackthread(void *);
+ void __cdecl searchthread(void *);
+ void __cdecl cmdgetinfothread(void *hContact);
+ void __cdecl getawaymsgthread(void *hContact);
+ void __cdecl dccmainthread(void *);
+ void __cdecl ftfailthread(void *param);
+ void __cdecl remindpasswordthread(void *param);
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ /* Helper functions */
+ int status_m2gg(int status, int descr);
+ int status_gg2m(int status);
+ void checknewuser(uin_t uin, const char* passwd);
+
+ /* Thread functions */
+ void forkthread(GGThreadFunc pFunc, void *param);
+ HANDLE forkthreadex(GGThreadFunc pFunc, void *param, UINT *threadId);
+ void threadwait(GGTHREAD *thread);
+
+ /* Global GG functions */
+ void notifyuser(HANDLE hContact, int refresh);
+ void setalloffline();
+ void disconnect();
+ HANDLE getcontact(uin_t uin, int create, int inlist, TCHAR *nick);
+ void __cdecl mainthread(void *empty);
+ int isonline();
+ int refreshstatus(int status);
+
+ void broadcastnewstatus(int newStatus);
+ void cleanuplastplugin(DWORD version);
+ int contactdeleted(WPARAM wParam, LPARAM lParam);
+ int dbsettingchanged(WPARAM wParam, LPARAM lParam);
+ void notifyall();
+ void changecontactstatus(uin_t uin, int status, const char *idescr, int time, uint32_t remote_ip, uint16_t remote_port, uint32_t version);
+ char *getstatusmsg(int status);
+ void dccstart();
+ void dccconnect(uin_t uin);
+ int gettoken(GGTOKEN *token);
+ void parsecontacts(char *contacts);
+ void remindpassword(uin_t uin, const char *email);
+ void menus_init();
+
+ /* Avatar functions */
+ void getAvatarFilename(HANDLE hContact, TCHAR *pszDest, int cbLen);
+ void getAvatar(HANDLE hContact, char *szAvatarURL);
+ void requestAvatar(HANDLE hContact, int iWaitFor);
+ void getUserAvatar();
+ void setAvatar(const TCHAR *szFilename);
+ void getAvatarFileInfo(uin_t uin, char **avatarurl, int *type);
+
+ INT_PTR __cdecl getavatarcaps(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl getavatarinfo(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl getmyavatar(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl setmyavatar(WPARAM wParam, LPARAM lParam);
+
+ void initavatarrequestthread();
+ void uninitavatarrequestthread();
+
+ void __cdecl avatarrequestthread(void*);
+ void __cdecl getuseravatarthread(void*);
+ void __cdecl setavatarthread(void*);
+
+ /* File transfer functions */
+ HANDLE fileallow(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath);
+ int filecancel(HANDLE hContact, HANDLE hTransfer);
+ int filedeny(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason);
+ int recvfile(HANDLE hContact, PROTOFILEEVENT* pre);
+ HANDLE sendfile(HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles);
+
+ HANDLE dccfileallow(HANDLE hTransfer, const PROTOCHAR* szPath);
+ HANDLE dcc7fileallow(HANDLE hTransfer, const PROTOCHAR* szPath);
+
+ int dccfiledeny(HANDLE hTransfer);
+ int dcc7filedeny(HANDLE hTransfer);
+
+ int dccfilecancel(HANDLE hTransfer);
+ int dcc7filecancel(HANDLE hTransfer);
+
+ /* Import module */
+ void import_init(HGENMENU hRoot);
+
+ INT_PTR __cdecl import_server(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl import_text(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl remove_server(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl export_server(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl export_text(WPARAM wParam, LPARAM lParam);
+
+ /* Keep-alive module */
+ void keepalive_init();
+ void keepalive_destroy();
+
+ /* Image reception functions */
+ int img_init();
+ int img_destroy();
+ int img_shutdown();
+ int img_sendonrequest(gg_event* e);
+ BOOL img_opened(uin_t uin);
+ void *img_loadpicture(gg_event* e, TCHAR *szFileName);
+ int img_display(HANDLE hContact, void *img);
+ int img_displayasmsg(HANDLE hContact, void *img);
+
+ void __cdecl img_dlgcallthread(void *param);
+
+ INT_PTR __cdecl img_recvimage(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl img_sendimg(WPARAM wParam, LPARAM lParam);
+
+ void links_instance_init();
+
+ /* OAuth functions */
+ char *oauth_header(const char *httpmethod, const char *url);
+ int oauth_checktoken(int force);
+ int oauth_receivetoken();
+
+ /* UI page initializers */
+ int __cdecl options_init(WPARAM wParam, LPARAM lParam);
+ int __cdecl details_init(WPARAM wParam, LPARAM lParam);
+
+ /* Groupchat functions */
+ int gc_init();
+ void gc_menus_init(HGENMENU hRoot);
+ int gc_destroy();
+ char * gc_getchat(uin_t sender, uin_t *recipients, int recipients_count);
+ GGGC *gc_lookup(char *id);
+ int gc_changenick(HANDLE hContact, char *pszNick);
+ #define UIN2ID(uin,id) _itoa(uin,id,10)
+
+ int __cdecl gc_event(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl gc_openconf(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl gc_clearignored(WPARAM wParam, LPARAM lParam);
+
+ /* Popups functions */
+ void initpopups();
+ void showpopup(const TCHAR* nickname, const TCHAR* msg, int flags);
+
+ /* Sessions functions */
+ INT_PTR __cdecl sessions_view(WPARAM wParam, LPARAM lParam);
+ void sessions_updatedlg();
+ BOOL sessions_closedlg();
+ void sessions_menus_init(HGENMENU hRoot);
+
+ /* Event helpers */
+ void createObjService(const char* szService, GGServiceFunc serviceProc);
+ void createProtoService(const char* szService, GGServiceFunc serviceProc);
+ HANDLE hookProtoEvent(const char*, GGEventFunc);
+ void forkThread(GGThreadFunc, void* );
+ HANDLE forkThreadEx(GGThreadFunc, void*, UINT* threadID = NULL);
+
+ // Debug functions
+ int netlog(const char *fmt, ...);
+
+ void block_init();
+ void block_uninit();
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ CRITICAL_SECTION ft_mutex, sess_mutex, img_mutex, modemsg_mutex, avatar_mutex, sessions_mutex;
+ list_t watches, transfers, requests, chats, imagedlgs, avatar_requests, avatar_transfers, sessions;
+ int gc_enabled, gc_id, is_list_remove, check_first_conn;
+ uin_t next_uin;
+ unsigned long last_crc;
+ GGTHREAD pth_dcc;
+ GGTHREAD pth_sess;
+ GGTHREAD pth_avatar;
+ struct gg_session *sess;
+ struct gg_dcc *dcc;
+ HANDLE hEvent;
+ HANDLE hConnStopEvent;
+ SOCKET sock;
+ UINT_PTR timer;
+ struct
+ {
+ char *online;
+ char *away;
+ char *dnd;
+ char *freechat;
+ char *invisible;
+ char *offline;
+ } modemsg;
+ HANDLE netlib;
+ HGENMENU hMenuRoot;
+ HGENMENU hMainMenu[7];
+ HANDLE hPrebuildMenuHook;
+ HANDLE hBlockMenuItem;
+ HANDLE hImageMenuItem;
+ HANDLE hInstanceMenuItem;
+ HANDLE hAvatarsFolder;
+ HANDLE hImagesFolder;
+ HWND hwndSessionsDlg;
+};
+
+typedef struct
+{
+ int mode;
+ uin_t uin;
+ char *pass;
+ char *email;
+ GGPROTO *gg;
+} GGUSERUTILDLGDATA;
+
+#endif
diff --git a/protocols/Gadu-Gadu/src/groupchat.cpp b/protocols/Gadu-Gadu/src/groupchat.cpp
new file mode 100644
index 0000000000..4089c8fac3
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/groupchat.cpp
@@ -0,0 +1,669 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+#include "m_metacontacts.h"
+
+#define GG_GC_GETCHAT "%s/GCGetChat"
+#define GGS_OPEN_CONF "%s/OpenConf"
+#define GGS_CLEAR_IGNORED "%s/ClearIgnored"
+
+////////////////////////////////////////////////////////////////////////////////
+// Inits Gadu-Gadu groupchat module using chat.dll
+
+int GGPROTO::gc_init()
+{
+ if (ServiceExists(MS_GC_REGISTER))
+ {
+ char service[64];
+ GCREGISTER gcr = {0};
+
+ // Register Gadu-Gadu proto
+ gcr.cbSize = sizeof(GCREGISTER);
+ gcr.dwFlags = GC_TCHAR;
+ gcr.iMaxText = 0;
+ gcr.nColors = 0;
+ gcr.pColors = 0;
+ gcr.ptszModuleDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr);
+ hookProtoEvent(ME_GC_EVENT, &GGPROTO::gc_event);
+ gc_enabled = TRUE;
+ // create & hook event
+ mir_snprintf(service, 64, GG_GC_GETCHAT, m_szModuleName);
+ netlog("gg_gc_init(): Registered with groupchat plugin.");
+ }
+ else
+ netlog("gg_gc_init(): Cannot register with groupchat plugin !!!");
+
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Groupchat menus initialization
+
+void GGPROTO::gc_menus_init(HGENMENU hRoot)
+{
+ if (gc_enabled)
+ {
+ CLISTMENUITEM mi = {0};
+ char service[64];
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE;
+ mi.hParentMenu = hRoot;
+
+ // Conferencing
+ mir_snprintf(service, sizeof(service), GGS_OPEN_CONF, m_szModuleName);
+ createObjService(service, &GGPROTO::gc_openconf);
+ mi.position = 2000050001;
+ mi.icolibItem = GetIconHandle(IDI_CONFERENCE);
+ mi.pszName = LPGEN("Open &conference...");
+ mi.pszService = service;
+ hMainMenu[0] = Menu_AddProtoMenuItem(&mi);
+
+ // Clear ignored conferences
+ mir_snprintf(service, sizeof(service), GGS_CLEAR_IGNORED, m_szModuleName);
+ createObjService(service, &GGPROTO::gc_clearignored);
+ mi.position = 2000050002;
+ mi.icolibItem = GetIconHandle(IDI_CLEAR_CONFERENCE);
+ mi.pszName = LPGEN("&Clear ignored conferences");
+ mi.pszService = service;
+ hMainMenu[1] = Menu_AddProtoMenuItem(&mi);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Releases Gadu-Gadu groupchat module using chat.dll
+
+int GGPROTO::gc_destroy()
+{
+ list_t l;
+ for(l = chats; l; l = l->next)
+ {
+ GGGC *chat = (GGGC *)l->data;
+ if (chat->recipients) free(chat->recipients);
+ }
+ list_destroy(chats, 1); chats = NULL;
+ return 1;
+}
+
+GGGC* GGPROTO::gc_lookup(char *id)
+{
+ GGGC *chat;
+ list_t l;
+
+ for(l = chats; l; l = l->next)
+ {
+ chat = (GGGC *)l->data;
+ if (chat && !strcmp(chat->id, id))
+ return chat;
+ }
+
+ return NULL;
+}
+
+int GGPROTO::gc_event(WPARAM wParam, LPARAM lParam)
+{
+ GCHOOK *gch = (GCHOOK *)lParam;
+ GGGC *chat = NULL;
+ uin_t uin;
+
+ // Check if we got our protocol, and fields are set
+ if (!gch
+ || !gch->pDest
+ || !gch->pDest->pszID
+ || !gch->pDest->pszModule
+ || lstrcmpiA(gch->pDest->pszModule, m_szModuleName)
+ || !(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))
+ || !(chat = gc_lookup(gch->pDest->pszID)))
+ return 0;
+
+ // Window terminated
+ if (gch->pDest->iType == SESSION_TERMINATE)
+ {
+ HANDLE hContact = NULL;
+ netlog("gg_gc_event(): Terminating chat %x, id %s from chat window...", chat, gch->pDest->pszID);
+ // Destroy chat entry
+ free(chat->recipients);
+ list_remove(&chats, chat, 1);
+ // Remove contact from contact list (duh!) should be done by chat.dll !!
+ hContact = db_find_first();
+ while (hContact)
+ {
+ DBVARIANT dbv;
+ if (!db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ))
+ {
+ if (dbv.pszVal && !strcmp(gch->pDest->pszID, dbv.pszVal))
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
+ DBFreeVariant(&dbv);
+ }
+ hContact = db_find_next(hContact);
+ }
+ return 1;
+ }
+
+ // Message typed / send only if online
+ if (isonline() && (gch->pDest->iType == GC_USER_MESSAGE) && gch->pszText)
+ {
+ char id[32];
+ DBVARIANT dbv;
+ GCDEST gcdest = {m_szModuleName, gch->pDest->pszID, GC_EVENT_MESSAGE};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ int lc;
+
+ UIN2ID(uin, id);
+
+ gcevent.pszUID = id;
+ gcevent.pszText = gch->pszText;
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ))
+ gcevent.pszNick = dbv.pszVal;
+ else
+ gcevent.pszNick = Translate("Me");
+
+ // Get rid of CRLF at back
+ lc = (int)strlen(gch->pszText) - 1;
+ while(lc >= 0 && (gch->pszText[lc] == '\n' || gch->pszText[lc] == '\r')) gch->pszText[lc --] = 0;
+ gcevent.time = time(NULL);
+ gcevent.bIsMe = 1;
+ gcevent.dwFlags = GCEF_ADDTOLOG;
+ netlog("gg_gc_event(): Sending conference message to room %s, \"%s\".", gch->pDest->pszID, gch->pszText);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ if (gcevent.pszNick == dbv.pszVal) DBFreeVariant(&dbv);
+ EnterCriticalSection(&sess_mutex);
+ gg_send_message_confer(sess, GG_CLASS_CHAT, chat->recipients_count, chat->recipients, (BYTE*)gch->pszText);
+ LeaveCriticalSection(&sess_mutex);
+ return 1;
+ }
+
+ // Privmessage selected
+ if (gch->pDest->iType == GC_USER_PRIVMESS)
+ {
+ HANDLE hContact = NULL;
+ if ((uin = atoi(gch->pszUID)) && (hContact = getcontact(uin, 1, 0, NULL)))
+ CallService(MS_MSG_SENDMESSAGE, (WPARAM)hContact, (LPARAM)0);
+ }
+ netlog("gg_gc_event(): Unhandled event %d, chat %x, uin %d, text \"%s\".", gch->pDest->iType, chat, uin, gch->pszText);
+
+ return 0;
+}
+
+typedef struct _gg_gc_echat
+{
+ uin_t sender;
+ uin_t *recipients;
+ int recipients_count;
+ char * chat_id;
+} gg_gc_echat;
+
+////////////////////////////////////////////////////////////////////////////////
+// This is main groupchat initialization routine
+
+char* GGPROTO::gc_getchat(uin_t sender, uin_t *recipients, int recipients_count)
+{
+ list_t l; int i;
+ GGGC *chat;
+ char id[32];
+ uin_t uin; DBVARIANT dbv;
+ GCDEST gcdest = {m_szModuleName, 0, GC_EVENT_ADDGROUP};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+
+ netlog("gg_gc_getchat(): Count %d.", recipients_count);
+ if (!recipients) return NULL;
+
+ // Look for existing chat
+ for(l = chats; l; l = l->next)
+ {
+ GGGC *chat = (GGGC *)l->data;
+ if (!chat) continue;
+
+ if (chat->recipients_count == recipients_count + (sender ? 1 : 0))
+ {
+ int i, j, found = 0, sok = (sender == 0);
+ if (!sok) for(i = 0; i < chat->recipients_count; i++)
+ if (sender == chat->recipients[i])
+ {
+ sok = 1;
+ break;
+ }
+ if (sok)
+ for(i = 0; i < chat->recipients_count; i++)
+ for(j = 0; j < recipients_count; j++)
+ if (recipients[j] == chat->recipients[i]) found++;
+ // Found all recipients
+ if (found == recipients_count)
+ {
+ if (chat->ignore)
+ netlog("gg_gc_getchat(): Ignoring existing id %s, size %d.", chat->id, chat->recipients_count);
+ else
+ netlog("gg_gc_getchat(): Returning existing id %s, size %d.", chat->id, chat->recipients_count);
+ return !(chat->ignore) ? chat->id : NULL;
+ }
+ }
+ }
+
+ // Make new uin list to chat mapping
+ chat = (GGGC *)malloc(sizeof(GGGC));
+ UIN2ID(gc_id ++, chat->id); chat->ignore = FALSE;
+
+ // Check groupchat policy (new) / only for incoming
+ if (sender)
+ {
+ int unknown = (getcontact(sender, 0, 0, NULL) == NULL),
+ unknownSender = unknown;
+ for(i = 0; i < recipients_count; i++)
+ if (!getcontact(recipients[i], 0, 0, NULL))
+ unknown ++;
+ if ((db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT) == 2) ||
+ (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL) == 2 &&
+ recipients_count >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL)) ||
+ (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN) == 2 &&
+ unknown >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN)))
+ chat->ignore = TRUE;
+ if (!chat->ignore && ((db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT) == 1) ||
+ (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL) == 1 &&
+ recipients_count >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL)) ||
+ (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN) == 1 &&
+ unknown >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN))))
+ {
+ TCHAR *senderName = unknownSender ?
+ TranslateT("Unknown") : pcli->pfnGetContactDisplayName(getcontact(sender, 0, 0, NULL), 0);
+ TCHAR error[256];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("%s has initiated conference with %d participants (%d unknowns).\nDo you want do participate ?"),
+ senderName, recipients_count + 1, unknown);
+ chat->ignore = MessageBox(NULL, error, m_tszUserName, MB_OKCANCEL | MB_ICONEXCLAMATION) != IDOK;
+ }
+ if (chat->ignore)
+ {
+ // Copy recipient list
+ chat->recipients_count = recipients_count + (sender ? 1 : 0);
+ chat->recipients = (uin_t *)calloc(chat->recipients_count, sizeof(uin_t));
+ for(i = 0; i < recipients_count; i++)
+ chat->recipients[i] = recipients[i];
+ if (sender) chat->recipients[i] = sender;
+ netlog("gg_gc_getchat(): Ignoring new chat %s, count %d.", chat->id, chat->recipients_count);
+ list_add(&chats, chat, 0);
+ return NULL;
+ }
+ }
+
+ // Create new chat window
+ TCHAR status[256];
+ TCHAR *senderName = sender ? pcli->pfnGetContactDisplayName(getcontact(sender, 1, 0, NULL), 0) : NULL;
+ mir_sntprintf(status, 255, (sender) ? TranslateT("%s initiated the conference.") : TranslateT("This is my own conference."), senderName);
+ GCSESSION gcwindow = { 0 };
+ gcwindow.cbSize = sizeof(GCSESSION);
+ gcwindow.iType = GCW_CHATROOM;
+ gcwindow.pszModule = m_szModuleName;
+ gcwindow.ptszName = sender ? senderName : TranslateT("Conference");
+ gcwindow.pszID = chat->id;
+ gcwindow.dwFlags = GC_TCHAR;
+ gcwindow.dwItemData = (DWORD)chat;
+ gcwindow.ptszStatusbarText = status;
+
+ // Here we put nice new hash sign
+ TCHAR *name = (TCHAR*)calloc(_tcslen(gcwindow.ptszName) + 2, sizeof(TCHAR));
+ *name = '#'; _tcscpy(name + 1, gcwindow.ptszName);
+ gcwindow.ptszName = name;
+ // Create new room
+ if (CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM) &gcwindow))
+ {
+ netlog("gg_gc_getchat(): Cannot create new chat window %s.", chat->id);
+ free(name);
+ free(chat);
+ return NULL;
+ }
+ free(name);
+
+ gcdest.pszID = chat->id;
+ gcevent.pszUID = id;
+ gcevent.dwFlags = GC_TCHAR | GCEF_ADDTOLOG;
+ gcevent.time = 0;
+
+ // Add normal group
+ gcevent.ptszStatus = TranslateT("Participants");
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ gcdest.iType = GC_EVENT_JOIN;
+
+ // Add myself
+ if (uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))
+ {
+ UIN2ID(uin, id);
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_TCHAR)) {
+ gcevent.ptszNick = NEWTSTR_ALLOCA(dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else gcevent.ptszNick = TranslateT("Me");
+ gcevent.bIsMe = 1;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ netlog("gg_gc_getchat(): Myself %s: %S (%S) to the list...", gcevent.pszUID, gcevent.ptszNick, gcevent.ptszStatus);
+ }
+ else netlog("gg_gc_getchat(): Myself adding failed with uin %d !!!", uin);
+
+ // Copy recipient list
+ chat->recipients_count = recipients_count + (sender ? 1 : 0);
+ chat->recipients = (uin_t *)calloc(chat->recipients_count, sizeof(uin_t));
+ for(i = 0; i < recipients_count; i++)
+ chat->recipients[i] = recipients[i];
+ if (sender) chat->recipients[i] = sender;
+
+ // Add contacts
+ for(i = 0; i < chat->recipients_count; i++) {
+ HANDLE hContact = getcontact(chat->recipients[i], 1, 0, NULL);
+ UIN2ID(chat->recipients[i], id);
+ if (hContact && (name = pcli->pfnGetContactDisplayName(hContact, 0)) != NULL)
+ gcevent.ptszNick = name;
+ else
+ gcevent.ptszNick = TranslateT("'Unknown'");
+ gcevent.bIsMe = 0;
+ gcevent.dwFlags = GC_TCHAR;
+ netlog("gg_gc_getchat(): Added %s: %S (%S) to the list...", gcevent.pszUID, gcevent.ptszNick, gcevent.pszStatus);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ }
+ gcdest.iType = GC_EVENT_CONTROL;
+ CallServiceSync(MS_GC_EVENT, SESSION_INITDONE, (LPARAM)&gcevent);
+ CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gcevent);
+
+ netlog("gg_gc_getchat(): Returning new chat window %s, count %d.", chat->id, chat->recipients_count);
+ list_add(&chats, chat, 0);
+ return chat->id;
+}
+
+static HANDLE gg_getsubcontact(GGPROTO* gg, HANDLE hContact)
+{
+ char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+ char* szMetaProto = (char*)CallService(MS_MC_GETPROTOCOLNAME, 0, 0);
+
+ if (szProto && szMetaProto && (INT_PTR)szMetaProto != CALLSERVICE_NOTFOUND && !lstrcmpA(szProto, szMetaProto))
+ {
+ int nSubContacts = (int)CallService(MS_MC_GETNUMCONTACTS, (WPARAM)hContact, 0), i;
+ HANDLE hMetaContact;
+ for (i = 0; i < nSubContacts; i++)
+ {
+ hMetaContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hContact, i);
+ szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hMetaContact, 0);
+ if (szProto && !lstrcmpA(szProto, gg->m_szModuleName))
+ return hMetaContact;
+ }
+ }
+ return NULL;
+}
+
+static void gg_gc_resetclistopts(HWND hwndList)
+{
+ int i;
+ SendMessage(hwndList, CLM_SETLEFTMARGIN, 2, 0);
+ SendMessage(hwndList, CLM_SETBKBITMAP, 0, (LPARAM)(HBITMAP)NULL);
+ SendMessage(hwndList, CLM_SETBKCOLOR, GetSysColor(COLOR_WINDOW), 0);
+ SendMessage(hwndList, CLM_SETGREYOUTFLAGS, 0, 0);
+ SendMessage(hwndList, CLM_SETINDENT, 10, 0);
+ SendMessage(hwndList, CLM_SETHIDEEMPTYGROUPS, (WPARAM)TRUE, 0);
+ for (i = 0; i <= FONTID_MAX; i++)
+ SendMessage(hwndList, CLM_SETTEXTCOLOR, i, GetSysColor(COLOR_WINDOWTEXT));
+}
+
+static int gg_gc_countcheckmarks(HWND hwndList)
+{
+ int count = 0;
+ HANDLE hItem, hContact = db_find_first();
+ while (hContact)
+ {
+ hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem && SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0))
+ count++;
+ hContact = db_find_next(hContact);
+ }
+ return count;
+}
+
+#define HM_SUBCONTACTSCHANGED (WM_USER + 100)
+
+static INT_PTR CALLBACK gg_gc_openconfdlg(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch(message)
+ {
+ case WM_INITDIALOG:
+ {
+ CLCINFOITEM cii = {0};
+ HANDLE hMetaContactsEvent;
+
+ SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam);
+ TranslateDialogDefault(hwndDlg);
+ WindowSetIcon(hwndDlg, "conference");
+ gg_gc_resetclistopts(GetDlgItem(hwndDlg, IDC_CLIST));
+
+ // Hook MetaContacts event (if available)
+ hMetaContactsEvent = HookEventMessage(ME_MC_SUBCONTACTSCHANGED, hwndDlg, HM_SUBCONTACTSCHANGED);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hMetaContactsEvent);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ {
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST);
+ GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER);
+ int count = 0, i = 0;
+ // Check if connected
+ if (!gg->isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be connected to open new conference."),
+ gg->m_tszUserName, MB_OK | MB_ICONSTOP);
+ }
+ else if (hwndList && (count = gg_gc_countcheckmarks(hwndList)) >= 2)
+ {
+ // Create new participiants table
+ char* chat;
+ uin_t* participants = (uin_t*)calloc(count, sizeof(uin_t));
+ HANDLE hItem, hContact = db_find_first();
+ gg->netlog("gg_gc_getchat(): Opening new conference for %d contacts.", count);
+ while (hContact && i < count)
+ {
+ hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem && SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0))
+ {
+ HANDLE hMetaContact = gg_getsubcontact(gg, hContact); // MetaContacts support
+ participants[i++] = db_get_dw(hMetaContact ? hMetaContact : hContact, gg->m_szModuleName, GG_KEY_UIN, 0);
+ }
+ hContact = db_find_next(hContact);
+ }
+ if (count > i) i = count;
+ chat = gg->gc_getchat(0, participants, count);
+ if (chat)
+ {
+ GCDEST gcdest = {gg->m_szModuleName, chat, GC_EVENT_CONTROL};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ CallServiceSync(MS_GC_EVENT, WINDOW_VISIBLE, (LPARAM)&gcevent);
+ }
+ free(participants);
+ }
+ }
+
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ switch(((NMHDR*)lParam)->idFrom)
+ {
+ case IDC_CLIST:
+ {
+ switch(((NMHDR*)lParam)->code)
+ {
+ case CLN_OPTIONSCHANGED:
+ gg_gc_resetclistopts(GetDlgItem(hwndDlg, IDC_CLIST));
+ break;
+
+ case CLN_NEWCONTACT:
+ case CLN_CONTACTMOVED:
+ case CLN_LISTREBUILT:
+ {
+ HANDLE hContact;
+ HANDLE hItem;
+ char* szProto;
+ uin_t uin;
+ GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER);
+
+ if (!gg) break;
+
+ // Delete non-gg contacts
+ hContact = db_find_first();
+ while (hContact)
+ {
+ hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ {
+ HANDLE hMetaContact = gg_getsubcontact(gg, hContact); // MetaContacts support
+ if (hMetaContact)
+ {
+ szProto = gg->m_szModuleName;
+ uin = (uin_t)db_get_dw(hMetaContact, gg->m_szModuleName, GG_KEY_UIN, 0);
+ }
+ else
+ {
+ szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+ uin = (uin_t)db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0);
+ }
+
+ if (szProto == NULL || lstrcmpA(szProto, gg->m_szModuleName) || !uin || uin == db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0))
+ SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_DELETEITEM, (WPARAM)hItem, 0);
+ }
+ hContact = db_find_next(hContact);
+ }
+ }
+ break;
+
+ case CLN_CHECKCHANGED:
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), gg_gc_countcheckmarks(GetDlgItem(hwndDlg, IDC_CLIST)) >= 2);
+ break;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case HM_SUBCONTACTSCHANGED:
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST);
+ SendMessage(hwndList, CLM_AUTOREBUILD, 0, 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), gg_gc_countcheckmarks(hwndList) >= 2);
+ break;
+ }
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ {
+ HANDLE hMetaContactsEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (hMetaContactsEvent) UnhookEvent(hMetaContactsEvent);
+ WindowFreeIcon(hwndDlg);
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+INT_PTR GGPROTO::gc_clearignored(WPARAM wParam, LPARAM lParam)
+{
+ list_t l = chats; BOOL cleared = FALSE;
+ while(l)
+ {
+ GGGC *chat = (GGGC *)l->data;
+ l = l->next;
+ if (chat->ignore)
+ {
+ if (chat->recipients) free(chat->recipients);
+ list_remove(&chats, chat, 1);
+ cleared = TRUE;
+ }
+ }
+ MessageBox( NULL,
+ cleared ?
+ TranslateT("All ignored conferences are now unignored and the conference policy will act again.") :
+ TranslateT("There are no ignored conferences."),
+ m_tszUserName, MB_OK | MB_ICONINFORMATION
+ );
+
+ return 0;
+}
+
+INT_PTR GGPROTO::gc_openconf(WPARAM wParam, LPARAM lParam)
+{
+ // Check if connected
+ if (!isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be connected to open new conference."),
+ m_tszUserName, MB_OK | MB_ICONSTOP
+ );
+ return 0;
+ }
+
+ CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_CONFERENCE), NULL, gg_gc_openconfdlg, (LPARAM)this);
+ return 1;
+}
+
+int GGPROTO::gc_changenick(HANDLE hContact, char *pszNick)
+{
+ list_t l;
+ uin_t uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0);
+ if (!uin || !pszNick) return 0;
+
+ netlog("gg_gc_changenick(): Nickname for uin %d changed to %s.", uin, pszNick);
+ // Lookup for chats having this nick
+ for(l = chats; l; l = l->next) {
+ GGGC *chat = (GGGC *)l->data;
+ if (chat->recipients && chat->recipients_count)
+ for(int i = 0; i < chat->recipients_count; i++)
+ // Rename this window if it's exising in the chat
+ if (chat->recipients[i] == uin)
+ {
+ char id[32];
+ GCEVENT gce = {sizeof(GCEVENT)};
+ GCDEST gcd;
+
+ UIN2ID(uin, id);
+ gcd.iType = GC_EVENT_NICK;
+ gcd.pszModule = m_szModuleName;
+ gce.pDest = &gcd;
+ gcd.pszID = chat->id;
+ gce.pszUID = id;
+ gce.pszText = pszNick;
+ netlog("gg_gc_changenick(): Found room %s with uin %d, sending nick change %s.", chat->id, uin, id);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ break;
+ }
+ }
+
+ return 1;
+}
diff --git a/protocols/Gadu-Gadu/src/icolib.cpp b/protocols/Gadu-Gadu/src/icolib.cpp
new file mode 100644
index 0000000000..f99540ac76
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/icolib.cpp
@@ -0,0 +1,109 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2007 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+struct tagiconList
+{
+ char* szDescr;
+ char* szName;
+ int defIconID;
+}
+static const iconList[] =
+{
+ { LPGEN("Protocol icon"), "main", IDI_GG },
+ { LPGEN("Import list from server"), "importserver", IDI_IMPORT_SERVER },
+ { LPGEN("Import list from text file"), "importtext", IDI_IMPORT_TEXT },
+ { LPGEN("Remove list from server"), "removeserver", IDI_REMOVE_SERVER },
+ { LPGEN("Export list to server"), "exportserver", IDI_EXPORT_SERVER },
+ { LPGEN("Export list to text file"), "exporttext", IDI_EXPORT_TEXT },
+ { LPGEN("Account settings"), "settings", IDI_SETTINGS },
+ { LPGEN("Contact list"), "list", IDI_LIST },
+ { LPGEN("Block user"), "block", IDI_BLOCK },
+ { LPGEN("Previous image"), "previous", IDI_PREV },
+ { LPGEN("Next image"), "next", IDI_NEXT },
+ { LPGEN("Send image"), "image", IDI_IMAGE },
+ { LPGEN("Save image"), "save", IDI_SAVE },
+ { LPGEN("Delete image"), "delete", IDI_DELETE },
+ { LPGEN("Open new conference"), "conference", IDI_CONFERENCE },
+ { LPGEN("Clear ignored conferences"), "clearignored", IDI_CLEAR_CONFERENCE },
+ { LPGEN("Concurrent sessions"), "sessions", IDI_SESSIONS }
+};
+
+HANDLE hIconLibItem[SIZEOF(iconList)];
+
+void gg_icolib_init()
+{
+ TCHAR szFile[MAX_PATH];
+ GetModuleFileName(hInstance, szFile, MAX_PATH);
+
+ char szSectionName[100];
+ mir_snprintf(szSectionName, sizeof( szSectionName ), "%s/%s", LPGEN("Protocols"), LPGEN(GGDEF_PROTO));
+
+ SKINICONDESC sid = {0};
+ sid.cbSize = sizeof(SKINICONDESC);
+ sid.ptszDefaultFile = szFile;
+ sid.pszSection = szSectionName;
+ sid.flags = SIDF_PATH_TCHAR;
+
+ for (int i = 0; i < SIZEOF(iconList); i++) {
+ char szSettingName[100];
+ mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, iconList[i].szName);
+ sid.pszName = szSettingName;
+ sid.pszDescription = (char*)iconList[i].szDescr;
+ sid.iDefaultIndex = -iconList[i].defIconID;
+ hIconLibItem[i] = Skin_AddIcon(&sid);
+ }
+}
+
+HICON LoadIconEx(const char* name, BOOL big)
+{
+ char szSettingName[100];
+ mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, name);
+ return (HICON)CallService(MS_SKIN2_GETICON, big, (LPARAM)szSettingName);
+}
+
+HANDLE GetIconHandle(int iconId)
+{
+ int i;
+ for(i = 0; i < SIZEOF(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return hIconLibItem[i];
+ return NULL;
+}
+
+void ReleaseIconEx(const char* name, BOOL big)
+{
+ char szSettingName[100];
+ mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, name);
+ CallService(big ? MS_SKIN2_RELEASEICONBIG : MS_SKIN2_RELEASEICON, 0, (LPARAM)szSettingName);
+}
+
+void WindowSetIcon(HWND hWnd, const char* name)
+{
+ SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIconEx(name, TRUE));
+ SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIconEx(name, FALSE));
+}
+
+void WindowFreeIcon(HWND hWnd)
+{
+ CallService(MS_SKIN2_RELEASEICONBIG, SendMessage(hWnd, WM_SETICON, ICON_BIG, 0), 0);
+ CallService(MS_SKIN2_RELEASEICON, SendMessage(hWnd, WM_SETICON, ICON_SMALL, 0), 0);
+}
diff --git a/protocols/Gadu-Gadu/src/image.cpp b/protocols/Gadu-Gadu/src/image.cpp
new file mode 100644
index 0000000000..0ad225ad06
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/image.cpp
@@ -0,0 +1,1191 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include <io.h>
+
+#define WM_ADDIMAGE WM_USER + 1
+#define WM_SENDIMG WM_USER + 2
+#define WM_CHOOSEIMG WM_USER + 3
+#define TIMERID_FLASHWND WM_USER + 4
+
+////////////////////////////////////////////////////////////////////////////
+// Image Window : Data
+
+// tablica zawiera uin kolesia i uchwyt do okna, okno zawiera GGIMAGEDLGDATA
+// ktore przechowuje handle kontaktu, i wskaznik na pierwszy obrazek
+// obrazki sa poukladane jako lista jednokierunkowa.
+// przy tworzeniu okna podaje sie handle do kontaktu
+// dodajac obrazek tworzy sie element listy i wysyla do okna
+// wyswietlajac obrazek idzie po liscie jednokierunkowej
+
+typedef struct _GGIMAGEENTRY
+{
+ HBITMAP hBitmap;
+ TCHAR *lpszFileName;
+ char *lpData;
+ unsigned long nSize;
+ struct _GGIMAGEENTRY *lpNext;
+ uint32_t crc32;
+} GGIMAGEENTRY;
+
+typedef struct
+{
+ HANDLE hContact;
+ HANDLE hEvent;
+ HWND hWnd;
+ uin_t uin;
+ int nImg, nImgTotal;
+ GGIMAGEENTRY *lpImages;
+ SIZE minSize;
+ BOOL bReceiving;
+ GGPROTO *gg;
+} GGIMAGEDLGDATA;
+
+// Prototypes
+int gg_img_remove(GGIMAGEDLGDATA *dat);
+
+////////////////////////////////////////////////////////////////////////////
+// Image Module : Adding item to contact menu, creating sync objects
+
+int GGPROTO::img_init()
+{
+ CLISTMENUITEM mi = {0};
+ char service[64];
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIF_ICONFROMICOLIB;
+
+ // Send image contact menu item
+ mir_snprintf(service, sizeof(service), GGS_SENDIMAGE, m_szModuleName);
+ createObjService(service, &GGPROTO::img_sendimg);
+ mi.position = -2000010000;
+ mi.icolibItem = GetIconHandle(IDI_IMAGE);
+ mi.pszName = LPGEN("&Image");
+ mi.pszService = service;
+ mi.pszContactOwner = m_szModuleName;
+ hImageMenuItem = Menu_AddContactMenuItem(&mi);
+
+ // Receive image
+ mir_snprintf(service, sizeof(service), GGS_RECVIMAGE, m_szModuleName);
+ createObjService(service, &GGPROTO::img_recvimage);
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Module : closing dialogs, sync objects
+int GGPROTO::img_shutdown()
+{
+ list_t l;
+#ifdef DEBUGMODE
+ netlog("gg_img_shutdown(): Closing all dialogs...");
+#endif
+ // Rather destroy window instead of just removing structures
+ for (l = imagedlgs; l;)
+ {
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data;
+ l = l->next;
+
+ if (dat && dat->hWnd)
+ {
+ if (IsWindow(dat->hWnd))
+ {
+ // Post message async, since it maybe be different thread
+ if (!PostMessage(dat->hWnd, WM_CLOSE, 0, 0))
+ netlog("gg_img_shutdown(): Image dlg %x cannot be released !!", dat->hWnd);
+ }
+ else
+ netlog("gg_img_shutdown(): Image dlg %x not exists, but structure does !!", dat->hWnd);
+ }
+ }
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Module : destroying list
+
+int GGPROTO::img_destroy()
+{
+ // Release all dialogs
+ while (imagedlgs && gg_img_remove((GGIMAGEDLGDATA *)imagedlgs->data));
+
+ // Destroy list
+ list_destroy(imagedlgs, 1);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hImageMenuItem, (LPARAM) 0);
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Window : Frees image entry structure
+
+static int gg_img_releasepicture(void *img)
+{
+ if (!img)
+ return FALSE;
+ if (((GGIMAGEENTRY *)img)->lpszFileName)
+ free(((GGIMAGEENTRY *)img)->lpszFileName);
+ if (((GGIMAGEENTRY *)img)->hBitmap)
+ DeleteObject(((GGIMAGEENTRY *)img)->hBitmap);
+ if (((GGIMAGEENTRY *)img)->lpData)
+ free(((GGIMAGEENTRY *)img)->lpData);
+ free(img);
+
+ return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Painting image
+int gg_img_paint(HWND hwnd, GGIMAGEENTRY *dat)
+{
+ PAINTSTRUCT paintStruct;
+ HDC hdc = BeginPaint(hwnd, &paintStruct);
+ RECT rc;
+
+ GetWindowRect(GetDlgItem(hwnd, IDC_IMG_IMAGE), &rc);
+ ScreenToClient(hwnd, (POINT *)&rc.left);
+ ScreenToClient(hwnd, (POINT *)&rc.right);
+ FillRect(hdc, &rc, (HBRUSH)GetSysColorBrush(COLOR_WINDOW));
+
+ if (dat->hBitmap)
+ {
+ HDC hdcBmp = NULL;
+ int nWidth, nHeight;
+ BITMAP bmp;
+
+ GetObject(dat->hBitmap, sizeof(bmp), &bmp);
+ nWidth = bmp.bmWidth; nHeight = bmp.bmHeight;
+
+ hdcBmp = CreateCompatibleDC(hdc);
+ SelectObject(hdcBmp, dat->hBitmap);
+ if (hdcBmp)
+ {
+ SetStretchBltMode(hdc, HALFTONE);
+ // Draw bitmap
+ if (nWidth > (rc.right-rc.left) || nHeight > (rc.bottom-rc.top))
+ {
+ if ((double)nWidth / (double)nHeight > (double) (rc.right-rc.left) / (double)(rc.bottom-rc.top))
+ {
+ StretchBlt(hdc,
+ rc.left,
+ ((rc.top + rc.bottom) - (rc.right - rc.left) * nHeight / nWidth) / 2,
+ (rc.right - rc.left),
+ (rc.right - rc.left) * nHeight / nWidth,
+ hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY);
+ }
+ else
+ {
+ StretchBlt(hdc,
+ ((rc.left + rc.right) - (rc.bottom - rc.top) * nWidth / nHeight) / 2,
+ rc.top,
+ (rc.bottom - rc.top) * nWidth / nHeight,
+ (rc.bottom - rc.top),
+ hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY);
+ }
+ }
+ else
+ {
+ BitBlt(hdc,
+ (rc.left + rc.right - nWidth) / 2,
+ (rc.top + rc.bottom - nHeight) / 2,
+ nWidth, nHeight,
+ hdcBmp, 0, 0, SRCCOPY);
+ }
+ DeleteDC(hdcBmp);
+ }
+ }
+ EndPaint(hwnd, &paintStruct);
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Returns supported image filters
+
+TCHAR *gg_img_getfilter(TCHAR *szFilter, int nSize)
+{
+ TCHAR *szFilterName, *szFilterMask;
+ TCHAR *pFilter = szFilter;
+
+ // Match relative to ImgDecoder presence
+ szFilterName = TranslateT("Image files (*.bmp,*.gif,*.jpeg,*.jpg,*.png)");
+ szFilterMask = _T("*.bmp;*.gif;*.jpeg;*.jpg;*.png");
+
+ // Make up filter
+ _tcsncpy(pFilter, szFilterName, nSize);
+ pFilter += _tcslen(pFilter) + 1;
+ if (pFilter >= szFilter + nSize) return NULL;
+ _tcsncpy(pFilter, szFilterMask, nSize - (pFilter - szFilter));
+ pFilter += _tcslen(pFilter) + 1;
+ if (pFilter >= szFilter + nSize) return NULL;
+ *pFilter = 0;
+
+ return szFilter;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Save specified image entry
+
+int gg_img_saveimage(HWND hwnd, GGIMAGEENTRY *dat)
+{
+ if (!dat)
+ return FALSE;
+
+ GGPROTO* gg = ((GGIMAGEDLGDATA *)GetWindowLongPtr(hwnd, GWLP_USERDATA))->gg;
+
+ TCHAR szFilter[128];
+ gg_img_getfilter(szFilter, SIZEOF(szFilter));
+
+ TCHAR szFileName[MAX_PATH];
+ _tcsncpy(szFileName, dat->lpszFileName, SIZEOF(szFileName));
+
+ OPENFILENAME ofn = {0};
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ ofn.hwndOwner = hwnd;
+ ofn.hInstance = hInstance;
+ ofn.lpstrFile = szFileName;
+ ofn.lpstrFilter = szFilter;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT;
+ if (GetSaveFileName(&ofn))
+ {
+ FILE *fp = _tfopen(szFileName, _T("w+b"));
+ if (fp)
+ {
+ fwrite(dat->lpData, dat->nSize, 1, fp);
+ fclose(fp);
+ gg->netlog("gg_img_saveimage(): Image saved to %s.", szFileName);
+ }
+ else
+ {
+ gg->netlog("gg_img_saveimage(): Cannot save image to %s.", szFileName);
+ MessageBox(hwnd, TranslateT("Image cannot be written to disk."), gg->m_tszUserName, MB_OK | MB_ICONERROR);
+ }
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Fit window size to image size
+
+BOOL gg_img_fit(HWND hwndDlg)
+{
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ RECT dlgRect, imgRect, wrkRect;
+ int nWidth, nHeight;
+ int rWidth = 0, rHeight = 0;
+ int oWidth = 0, oHeight = 0;
+ BITMAP bmp;
+ GGIMAGEENTRY *img = NULL;
+ HDC hdc;
+
+ // Check if image is loaded
+ if (!dat || !dat->lpImages || !dat->lpImages->hBitmap)
+ return FALSE;
+
+ img = dat->lpImages;
+
+ // Go to last image
+ while (img->lpNext && dat->lpImages->hBitmap)
+ img = img->lpNext;
+
+ // Get rects of display
+ GetWindowRect(hwndDlg, &dlgRect);
+ GetClientRect(GetDlgItem(hwndDlg, IDC_IMG_IMAGE), &imgRect);
+
+ hdc = GetDC(hwndDlg);
+
+ GetObject(img->hBitmap, sizeof(bmp), &bmp);
+ nWidth = bmp.bmWidth; nHeight = bmp.bmHeight;
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &wrkRect, 0);
+
+ ReleaseDC(hwndDlg, hdc);
+
+ if ((imgRect.right - imgRect.left) < nWidth)
+ rWidth = nWidth - imgRect.right + imgRect.left;
+ if ((imgRect.bottom - imgRect.top) < nWidth)
+ rHeight = nHeight - imgRect.bottom + imgRect.top;
+
+ // Check if anything needs resize
+ if (!rWidth && !rHeight)
+ return FALSE;
+
+ oWidth = dlgRect.right - dlgRect.left + rWidth;
+ oHeight = dlgRect.bottom - dlgRect.top + rHeight;
+
+ if (oHeight > wrkRect.bottom - wrkRect.top)
+ {
+ oWidth = (int)((double)(wrkRect.bottom - wrkRect.top + imgRect.bottom - imgRect.top - dlgRect.bottom + dlgRect.top) * nWidth / nHeight)
+ - imgRect.right + imgRect.left + dlgRect.right - dlgRect.left;
+ if (oWidth < dlgRect.right - dlgRect.left)
+ oWidth = dlgRect.right - dlgRect.left;
+ oHeight = wrkRect.bottom - wrkRect.top;
+ }
+ if (oWidth > wrkRect.right - wrkRect.left)
+ {
+ oHeight = (int)((double)(wrkRect.right - wrkRect.left + imgRect.right - imgRect.left - dlgRect.right + dlgRect.left) * nHeight / nWidth)
+ - imgRect.bottom + imgRect.top + dlgRect.bottom - dlgRect.top;
+ if (oHeight < dlgRect.bottom - dlgRect.top)
+ oHeight = dlgRect.bottom - dlgRect.top;
+ oWidth = wrkRect.right - wrkRect.left;
+ }
+ SetWindowPos(hwndDlg, NULL,
+ (wrkRect.left + wrkRect.right - oWidth) / 2,
+ (wrkRect.top + wrkRect.bottom - oHeight) / 2,
+ oWidth, oHeight,
+ SWP_SHOWWINDOW | SWP_NOZORDER /* | SWP_NOACTIVATE */);
+
+ return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Dialog resizer procedure
+static int sttImageDlgResizer(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL* urc)
+{
+ switch (urc->wId)
+ {
+ case IDC_IMG_PREV:
+ case IDC_IMG_NEXT:
+ case IDC_IMG_DELETE:
+ case IDC_IMG_SAVE:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP;
+ case IDC_IMG_IMAGE:
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH;
+ case IDC_IMG_SEND:
+ case IDC_IMG_CANCEL:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Send / Recv main dialog procedure
+static INT_PTR CALLBACK gg_img_dlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ RECT rect;
+
+ TranslateDialogDefault(hwndDlg);
+ // This should be already initialized
+ // InitCommonControls();
+
+ // Get dialog data
+ dat = (GGIMAGEDLGDATA *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
+
+ // Save dialog handle
+ dat->hWnd = hwndDlg;
+
+ // Send event if someone's waiting
+ if (dat->hEvent) SetEvent(dat->hEvent);
+ else dat->gg->netlog("gg_img_dlgproc(): Creation event not found, but someone might be waiting.");
+
+ // Making buttons flat
+ SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONSETASFLATBTN, TRUE, 0);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONSETASFLATBTN, TRUE, 0);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONSETASFLATBTN, TRUE, 0);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONSETASFLATBTN, TRUE, 0);
+
+ // Setting images for buttons
+ SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("previous", FALSE));
+ SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("next", FALSE));
+ SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("delete", FALSE));
+ SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("save", FALSE));
+
+ // Setting tooltips for buttons
+ SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Previous image"), BATF_TCHAR);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Next image"), BATF_TCHAR);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Delete image from the list"), BATF_TCHAR);
+ SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save image to disk"), BATF_TCHAR);
+
+ // Set main window image
+ WindowSetIcon(hwndDlg, "image");
+
+ TCHAR *szName = pcli->pfnGetContactDisplayName(dat->hContact, 0), szTitle[128];
+ if (dat->bReceiving)
+ mir_sntprintf(szTitle, SIZEOF(szTitle), TranslateT("Image from %s"), szName);
+ else
+ mir_sntprintf(szTitle, SIZEOF(szTitle), TranslateT("Image for %s"), szName);
+ SetWindowText(hwndDlg, szTitle);
+
+ // Store client extents
+ GetClientRect(hwndDlg, &rect);
+ dat->minSize.cx = rect.right - rect.left;
+ dat->minSize.cy = rect.bottom - rect.top;
+ }
+ return TRUE;
+
+ case WM_SIZE:
+ {
+ UTILRESIZEDIALOG urd = {0};
+ urd.cbSize = sizeof(urd);
+ urd.hInstance = hInstance;
+ urd.hwndDlg = hwndDlg;
+ urd.lpTemplate = dat->bReceiving ? MAKEINTRESOURCEA(IDD_IMAGE_RECV) : MAKEINTRESOURCEA(IDD_IMAGE_SEND);
+ urd.pfnResizer = sttImageDlgResizer;
+ CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd);
+ if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
+ InvalidateRect(hwndDlg, NULL, FALSE);
+ return 0;
+ }
+
+ case WM_SIZING:
+ {
+ RECT *pRect = (RECT *)lParam;
+ if (pRect->right - pRect->left < dat->minSize.cx)
+ {
+ if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT)
+ pRect->left = pRect->right - dat->minSize.cx;
+ else
+ pRect->right = pRect->left + dat->minSize.cx;
+ }
+ if (pRect->bottom - pRect->top < dat->minSize.cy)
+ {
+ if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT)
+ pRect->top = pRect->bottom - dat->minSize.cy;
+ else
+ pRect->bottom = pRect->top + dat->minSize.cy;
+ }
+ }
+ return TRUE;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ // Flash the window
+ case WM_TIMER:
+ if (wParam == TIMERID_FLASHWND)
+ FlashWindow(hwndDlg, TRUE);
+ break;
+
+ // Kill the timer
+ case WM_ACTIVATE:
+ if (LOWORD(wParam) != WA_ACTIVE)
+ break;
+ case WM_MOUSEACTIVATE:
+ if (KillTimer(hwndDlg, TIMERID_FLASHWND))
+ FlashWindow(hwndDlg, FALSE);
+ break;
+
+ case WM_PAINT:
+ if (dat->lpImages)
+ {
+ GGIMAGEENTRY *img = dat->lpImages;
+ int i;
+
+ for (i = 1; img && (i < dat->nImg); i++)
+ img = img->lpNext;
+
+ if (!img)
+ {
+ dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot paint the window.");
+ return FALSE;
+ }
+
+ if (dat->bReceiving)
+ {
+ TCHAR szTitle[128];
+ mir_sntprintf(szTitle, SIZEOF(szTitle), _T("%s (%d / %d)"), img->lpszFileName, dat->nImg, dat->nImgTotal);
+ SetDlgItemText(hwndDlg, IDC_IMG_NAME, szTitle);
+ }
+ else
+ SetDlgItemText(hwndDlg, IDC_IMG_NAME, img->lpszFileName);
+ gg_img_paint(hwndDlg, img);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (dat)
+ {
+ // Deleting all image entries
+ GGIMAGEENTRY *temp, *img = dat->lpImages;
+ GGPROTO *gg = dat->gg;
+ while (temp = img)
+ {
+ img = img->lpNext;
+ gg_img_releasepicture(temp);
+ }
+ ReleaseIconEx("previous", FALSE);
+ ReleaseIconEx("next", FALSE);
+ ReleaseIconEx("delete", FALSE);
+ ReleaseIconEx("save", FALSE);
+ WindowFreeIcon(hwndDlg);
+ EnterCriticalSection(&gg->img_mutex);
+ list_remove(&gg->imagedlgs, dat, 1);
+ LeaveCriticalSection(&gg->img_mutex);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_IMG_CANCEL:
+ EndDialog(hwndDlg, 0);
+ return TRUE;
+
+ case IDC_IMG_PREV:
+ if (dat->nImg > 1)
+ {
+ dat->nImg--;
+ InvalidateRect(hwndDlg, NULL, FALSE);
+ }
+ return TRUE;
+
+ case IDC_IMG_NEXT:
+ if (dat->nImg < dat->nImgTotal)
+ {
+ dat->nImg++;
+ InvalidateRect(hwndDlg, NULL, FALSE);
+ }
+ return TRUE;
+
+ case IDC_IMG_DELETE:
+ {
+ GGIMAGEENTRY *del, *img = dat->lpImages;
+ if (dat->nImg == 1)
+ {
+ del = dat->lpImages;
+ dat->lpImages = img->lpNext;
+ }
+ else
+ {
+ int i;
+ for (i = 1; img && (i < dat->nImg - 1); i++)
+ img = img->lpNext;
+ if (!img)
+ {
+ dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot delete it from the list.");
+ return FALSE;
+ }
+ del = img->lpNext;
+ img->lpNext = del->lpNext;
+ dat->nImg --;
+ }
+
+ if ((-- dat->nImgTotal) == 0)
+ EndDialog(hwndDlg, 0);
+ else
+ InvalidateRect(hwndDlg, NULL, FALSE);
+
+ gg_img_releasepicture(del);
+ }
+ return TRUE;
+
+ case IDC_IMG_SAVE:
+ {
+ GGIMAGEENTRY *img = dat->lpImages;
+ int i;
+
+ for (i = 1; img && (i < dat->nImg); i++)
+ img = img->lpNext;
+ if (!img)
+ {
+ dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot launch saving.");
+ return FALSE;
+ }
+ gg_img_saveimage(hwndDlg, img);
+ }
+ return TRUE;
+
+ case IDC_IMG_SEND:
+ {
+ unsigned char format[20];
+ char *msg = "\xA0\0";
+ GGPROTO *gg = dat->gg;
+
+ if (dat->lpImages && gg->isonline())
+ {
+ uin_t uin = (uin_t)db_get_dw(dat->hContact, gg->m_szModuleName, GG_KEY_UIN, 0);
+ struct gg_msg_richtext_format *r = NULL;
+ struct gg_msg_richtext_image *p = NULL;
+ LPVOID pvData = NULL;
+ int len;
+
+ ((struct gg_msg_richtext*)format)->flag = 2;
+
+ r = (struct gg_msg_richtext_format *)(format + sizeof(struct gg_msg_richtext));
+ r->position = 0;
+ r->font = GG_FONT_IMAGE;
+
+ p = (struct gg_msg_richtext_image *)(format + sizeof(struct gg_msg_richtext) + sizeof(struct gg_msg_richtext_format));
+ p->unknown1 = 0x109;
+ p->size = dat->lpImages->nSize;
+
+ dat->lpImages->crc32 = p->crc32 = gg_fix32(gg_crc32(0, (BYTE*)dat->lpImages->lpData, dat->lpImages->nSize));
+
+ len = sizeof(struct gg_msg_richtext_format) + sizeof(struct gg_msg_richtext_image);
+ ((struct gg_msg_richtext*)format)->length = len;
+
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_send_message_richtext(gg->sess, GG_CLASS_CHAT, (uin_t)uin, (unsigned char*)msg, format, len + sizeof(struct gg_msg_richtext));
+ LeaveCriticalSection(&gg->sess_mutex);
+
+ // Protect dat from releasing
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)0);
+
+ EndDialog(hwndDlg, 0);
+ }
+ return TRUE;
+ }
+ break;
+ }
+ break;
+
+ case WM_ADDIMAGE: // lParam == GGIMAGEENTRY *dat
+ {
+ GGIMAGEENTRY *lpImage = (GGIMAGEENTRY *)lParam;
+ GGIMAGEENTRY *lpImages = dat->lpImages;
+
+ if (!dat->lpImages) // first image entry
+ dat->lpImages = lpImage;
+ else // adding at the end of the list
+ {
+ while (lpImages->lpNext)
+ lpImages = lpImages->lpNext;
+ lpImages->lpNext = lpImage;
+ }
+ dat->nImg = ++ dat->nImgTotal;
+ }
+ // Fit window to image
+ if (!gg_img_fit(hwndDlg))
+ InvalidateRect(hwndDlg, NULL, FALSE);
+ return TRUE;
+
+ case WM_CHOOSEIMG:
+ {
+ TCHAR szFilter[128];
+ TCHAR szFileName[MAX_PATH];
+ OPENFILENAME ofn = {0};
+
+ gg_img_getfilter(szFilter, sizeof(szFilter));
+ *szFileName = 0;
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ ofn.hwndOwner = hwndDlg;
+ ofn.hInstance = hInstance;
+ ofn.lpstrFilter = szFilter;
+ ofn.lpstrFile = szFileName;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.lpstrTitle = TranslateT("Select picture to send");
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
+ if (GetOpenFileName(&ofn))
+ {
+ if (dat->lpImages)
+ gg_img_releasepicture(dat->lpImages);
+ if (!(dat->lpImages = (GGIMAGEENTRY *)dat->gg->img_loadpicture(0, szFileName)))
+ {
+ EndDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ if (!gg_img_fit(hwndDlg))
+ InvalidateRect(hwndDlg, NULL, FALSE);
+ }
+ else
+ {
+ EndDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image dialog call thread
+
+void __cdecl GGPROTO::img_dlgcallthread(void *param)
+{
+ HWND hMIWnd = 0; //(HWND) CallService(MS_CLUI_GETHWND, 0, 0);
+
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)param;
+ DialogBoxParam(hInstance, dat->bReceiving ? MAKEINTRESOURCE(IDD_IMAGE_RECV) : MAKEINTRESOURCE(IDD_IMAGE_SEND),
+ hMIWnd, gg_img_dlgproc, (LPARAM) dat);
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Open dialog receive for specified contact
+GGIMAGEDLGDATA *gg_img_recvdlg(GGPROTO *gg, HANDLE hContact)
+{
+ // Create dialog data
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA));
+ dat->hContact = hContact;
+ dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dat->bReceiving = TRUE;
+ dat->gg = gg;
+ ResetEvent(dat->hEvent);
+ gg->forkthread(&GGPROTO::img_dlgcallthread, dat);
+ return dat;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Checks if an image is already saved to the specified path
+// Returns 1 if yes, 0 if no or -1 if different image on this path is found
+int gg_img_isexists(TCHAR *szPath, GGIMAGEENTRY *dat)
+{
+ struct _stat st;
+
+ if (_tstat(szPath, &st) != 0)
+ return 0;
+
+ if (st.st_size == dat->nSize)
+ {
+ FILE *fp = _tfopen(szPath, _T("rb"));
+ if (!fp) return 0;
+ char *lpData = (char*)mir_alloc(dat->nSize);
+ if (fread(lpData, 1, dat->nSize, fp) == dat->nSize)
+ {
+ if (dat->crc32 == gg_fix32(gg_crc32(0, (BYTE*)lpData, dat->nSize)) ||
+ memcmp(lpData, dat->lpData, dat->nSize) == 0)
+ {
+ mir_free(lpData);
+ fclose(fp);
+ return 1;
+ }
+ }
+ mir_free(lpData);
+ fclose(fp);
+ }
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Determine if image's file name has the proper extension
+TCHAR *gg_img_hasextension(TCHAR *filename)
+{
+ if (filename != NULL && *filename != '\0')
+ {
+ TCHAR *imgtype = _tcsrchr(filename, '.');
+ if (imgtype != NULL)
+ {
+ size_t len = _tcslen(imgtype);
+ imgtype++;
+ if (len == 4 && (_tcsicmp(imgtype, _T("bmp")) == 0 ||
+ _tcsicmp(imgtype, _T("gif")) == 0 ||
+ _tcsicmp(imgtype, _T("jpg")) == 0 ||
+ _tcsicmp(imgtype, _T("png")) == 0))
+ return --imgtype;
+ if (len == 5 && _tcsicmp(imgtype, _T("jpeg")) == 0)
+ return --imgtype;
+ }
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Display received image using message with [img] BBCode
+
+int GGPROTO::img_displayasmsg(HANDLE hContact, void *img)
+{
+ GGIMAGEENTRY *dat = (GGIMAGEENTRY *)img;
+ TCHAR szPath[MAX_PATH], path[MAX_PATH], *pImgext, imgext[6];
+ size_t tPathLen;
+ int i, res;
+
+ if (hImagesFolder == NULL || FoldersGetCustomPathT(hImagesFolder, path, MAX_PATH, _T(""))) {
+ TCHAR *tmpPath = Utils_ReplaceVarsT( _T("%miranda_userdata%"));
+ tPathLen = mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s\\ImageCache"), tmpPath, m_tszUserName);
+ mir_free(tmpPath);
+ }
+ else {
+ _tcscpy(szPath, path);
+ tPathLen = _tcslen(szPath);
+ }
+
+ if ( _taccess(szPath, 0))
+ CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)szPath);
+
+ mir_sntprintf(szPath + tPathLen, MAX_PATH - tPathLen, _T("\\%s"), dat->lpszFileName);
+ if ((pImgext = gg_img_hasextension(szPath)) == NULL)
+ pImgext = szPath + _tcslen(szPath);
+ mir_sntprintf(imgext, SIZEOF(imgext), _T("%s"), pImgext);
+ for (i = 1; ; ++i)
+ {
+ if ((res = gg_img_isexists(szPath, dat)) != -1) break;
+ mir_sntprintf(szPath, MAX_PATH, _T("%.*s (%u)%s"), pImgext - szPath, szPath, i, imgext);
+ }
+
+ if (res == 0) {
+ // Image file not found, thus create it
+ FILE *fp = _tfopen(szPath, _T("w+b"));
+ if (fp) {
+ res = fwrite(dat->lpData, dat->nSize, 1, fp) > 0;
+ fclose(fp);
+ }
+ }
+
+ if (res != 0) {
+ char image_msg[MAX_PATH + 11];
+ CCSDATA ccs = {0};
+ PROTORECVEVENT pre = {0};
+
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = hContact;
+ ccs.lParam = (LPARAM)&pre;
+ mir_snprintf(image_msg, SIZEOF(image_msg), "[img]%s[/img]", szPath);
+ pre.timestamp = time(NULL);
+ pre.szMessage = image_msg;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs);
+ netlog("gg_img_displayasmsg: Image saved to %s.", szPath);
+ }
+ else
+ {
+ netlog("gg_img_displayasmsg: Cannot save image to %s.", szPath);
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Return if uin has it's window already opened
+
+BOOL GGPROTO::img_opened(uin_t uin)
+{
+ list_t l = imagedlgs;
+ while (l)
+ {
+ GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data;
+ if (dat->uin == uin)
+ return TRUE;
+ l = l->next;
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Module : Looking for window entry, create if not found
+
+int GGPROTO::img_display(HANDLE hContact, void *img)
+{
+ list_t l = imagedlgs;
+ GGIMAGEDLGDATA *dat;
+
+ if (!img) return FALSE;
+
+ // Look for already open dialog
+ EnterCriticalSection(&img_mutex);
+ while (l)
+ {
+ dat = (GGIMAGEDLGDATA *)l->data;
+ if (dat->bReceiving && dat->hContact == hContact)
+ break;
+ l = l->next;
+ }
+
+ if (!l) dat = NULL;
+
+ if (!dat)
+ {
+ dat = gg_img_recvdlg(this, hContact);
+ dat->uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0);
+
+ while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
+ CloseHandle(dat->hEvent);
+ dat->hEvent = NULL;
+
+ list_add(&imagedlgs, dat, 0);
+ }
+ LeaveCriticalSection(&img_mutex);
+
+ SendMessage(dat->hWnd, WM_ADDIMAGE, 0, (LPARAM)img);
+ if (/*db_get_b(NULL, "Chat", "FlashWindowHighlight", 0) != 0 && */
+ GetActiveWindow() != dat->hWnd && GetForegroundWindow() != dat->hWnd)
+ SetTimer(dat->hWnd, TIMERID_FLASHWND, 900, NULL);
+
+ /* DEPRECATED: No more grabbing the focus... just flashing
+ SetForegroundWindow(dat->hWnd);
+ SetFocus(dat->hWnd);
+ */
+
+ return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Helper function to determine image file format and the right extension
+
+const TCHAR *gg_img_guessfileextension(const char *lpData)
+{
+ if (lpData != NULL)
+ {
+ if (memcmp(lpData, "BM", 2) == 0)
+ return _T(".bmp");
+ if (memcmp(lpData, "GIF8", 4) == 0)
+ return _T(".gif");
+ if (memcmp(lpData, "\xFF\xD8", 2) == 0)
+ return _T(".jpg");
+ if (memcmp(lpData, "\x89PNG", 4) == 0)
+ return _T(".png");
+ }
+ return _T("");
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Window : Loading picture and sending for display
+
+void* GGPROTO::img_loadpicture(gg_event* e, TCHAR *szFileName)
+{
+ GGIMAGEENTRY *dat;
+
+ if (!szFileName &&
+ (!e || !e->event.image_reply.size || !e->event.image_reply.image || !e->event.image_reply.filename))
+ return NULL;
+
+ dat = (GGIMAGEENTRY *)calloc(1, sizeof(GGIMAGEENTRY));
+ if (dat == NULL)
+ return NULL;
+
+ // Copy the file name
+ if (szFileName)
+ {
+ FILE *fp = _tfopen(szFileName, _T("rb"));
+ if (!fp) {
+ free(dat);
+ netlog("gg_img_loadpicture(): fopen(\"%s\", \"rb\") failed.", szFileName);
+ return NULL;
+ }
+ fseek(fp, 0, SEEK_END);
+ dat->nSize = ftell(fp);
+ if (dat->nSize <= 0)
+ {
+ fclose(fp);
+ free(dat);
+ netlog("gg_img_loadpicture(): Zero file size \"%s\" failed.", szFileName);
+ return NULL;
+ }
+ // Maximum acceptable image size
+ if (dat->nSize > 255 * 1024)
+ {
+ fclose(fp);
+ free(dat);
+ netlog("gg_img_loadpicture(): Image size of \"%s\" exceeds 255 KB.", szFileName);
+ MessageBox(NULL, TranslateT("Image exceeds maximum allowed size of 255 KB."), m_tszUserName, MB_OK | MB_ICONEXCLAMATION);
+ return NULL;
+ }
+ fseek(fp, 0, SEEK_SET);
+ dat->lpData = (char*)malloc(dat->nSize);
+ if (fread(dat->lpData, 1, dat->nSize, fp) < dat->nSize)
+ {
+ free(dat->lpData);
+ fclose(fp);
+ free(dat);
+ netlog("gg_img_loadpicture(): Reading file \"%s\" failed.", szFileName);
+ return NULL;
+ }
+ fclose(fp);
+ dat->lpszFileName = _tcsdup(szFileName);
+ }
+ // Copy picture from packet
+ else if (e && e->event.image_reply.filename)
+ {
+ dat->nSize = e->event.image_reply.size;
+ dat->lpData = (char*)malloc(dat->nSize);
+ memcpy(dat->lpData, e->event.image_reply.image, dat->nSize);
+
+ mir_ptr<TCHAR> tmpFileName( mir_a2t(e->event.image_reply.filename));
+ if (!gg_img_hasextension(tmpFileName)) {
+ // Add missing file extension
+ const TCHAR *szImgType = gg_img_guessfileextension(dat->lpData);
+ if (*szImgType) {
+ dat->lpszFileName = (TCHAR*)calloc(sizeof(TCHAR), lstrlen(tmpFileName) + lstrlen(szImgType) + 1);
+ if (dat->lpszFileName != NULL) {
+ _tcscpy(dat->lpszFileName, tmpFileName);
+ _tcscat(dat->lpszFileName, szImgType);
+ }
+ }
+ }
+
+ if (dat->lpszFileName == NULL)
+ dat->lpszFileName = _tcsdup( _A2T( e->event.image_reply.filename));
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ // Loading picture using Miranda Image services
+
+ // Load image from memory
+ if (!szFileName)
+ {
+ IMGSRVC_MEMIO memio;
+ memio.iLen = dat->nSize;
+ memio.pBuf = (void *)dat->lpData;
+ memio.fif = FIF_UNKNOWN; /* detect */
+ memio.flags = 0;
+ dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOADFROMMEM, (WPARAM) &memio, 0);
+ }
+ // Load image from file
+ else
+ dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOAD, (WPARAM) szFileName, 0);
+
+ // If everything is fine return the handle
+ if (dat->hBitmap) return dat;
+
+ netlog("gg_img_loadpicture(): MS_IMG_LOAD(MEM) failed.");
+ if (dat)
+ {
+ if (dat->lpData)
+ free(dat->lpData);
+ if (dat->lpszFileName)
+ free(dat->lpszFileName);
+ free(dat);
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Image Recv : AddEvent proc
+INT_PTR GGPROTO::img_recvimage(WPARAM wParam, LPARAM lParam)
+{
+ CLISTEVENT *cle = (CLISTEVENT *)lParam;
+ GGIMAGEENTRY *img = (GGIMAGEENTRY *)cle->lParam;
+
+ netlog("gg_img_recvimage(%x, %x): Popup new image.", wParam, lParam);
+ if (!img) return FALSE;
+
+ img_display(cle->hContact, img);
+
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Windows queue management
+////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////
+// Removes dat structure
+int gg_img_remove(GGIMAGEDLGDATA *dat)
+{
+ GGIMAGEENTRY *temp = NULL, *img = NULL;
+ GGPROTO *gg;
+
+ if (!dat) return FALSE;
+ gg = dat->gg;
+
+ EnterCriticalSection(&gg->img_mutex);
+
+ // Remove the structure
+ img = dat->lpImages;
+
+ // Destroy picture handle
+ while (temp = img)
+ {
+ img = img->lpNext;
+ gg_img_releasepicture(temp);
+ }
+
+ // Remove from list
+ list_remove(&gg->imagedlgs, dat, 1);
+ LeaveCriticalSection(&gg->img_mutex);
+
+ return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+GGIMAGEDLGDATA* gg_img_find(GGPROTO *gg, uin_t uin, uint32_t crc32)
+{
+ int res = 0;
+ list_t l = gg->imagedlgs;
+ GGIMAGEDLGDATA *dat;
+
+ EnterCriticalSection(&gg->img_mutex);
+ while (l)
+ {
+ uin_t c_uin;
+
+ dat = (GGIMAGEDLGDATA *)l->data;
+ if (!dat) break;
+
+ c_uin = db_get_dw(dat->hContact, dat->gg->m_szModuleName, GG_KEY_UIN, 0);
+
+ if (!dat->bReceiving && dat->lpImages && dat->lpImages->crc32 == crc32 && c_uin == uin)
+ {
+ LeaveCriticalSection(&gg->img_mutex);
+ return dat;
+ }
+
+ l = l->next;
+ }
+ LeaveCriticalSection(&gg->img_mutex);
+
+ gg->netlog("gg_img_find(): Image not found on the list. It might be released before calling this function.");
+ return NULL;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Image Module : Send on Request
+
+BOOL GGPROTO::img_sendonrequest(gg_event* e)
+{
+ GGIMAGEDLGDATA *dat = gg_img_find(this, e->event.image_request.sender, e->event.image_request.crc32);
+ if (!this || !dat || !isonline())
+ return FALSE;
+
+ EnterCriticalSection(&sess_mutex);
+ gg_image_reply(sess, e->event.image_request.sender, dat->lpImages->lpszFileName, dat->lpImages->lpData, dat->lpImages->nSize);
+ LeaveCriticalSection(&sess_mutex);
+
+ gg_img_remove(dat);
+
+ return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Send Image : Run (Thread and main)
+
+INT_PTR GGPROTO::img_sendimg(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE)wParam;
+ GGIMAGEDLGDATA *dat = NULL;
+
+ EnterCriticalSection(&img_mutex);
+ if (!dat)
+ {
+ dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA));
+ dat->hContact = hContact;
+ dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dat->gg = this;
+ ResetEvent(dat->hEvent);
+
+ // Create new dialog
+ forkthread(&GGPROTO::img_dlgcallthread, dat);
+
+ while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
+ CloseHandle(dat->hEvent);
+ dat->hEvent = NULL;
+
+ list_add(&imagedlgs, dat, 0);
+ }
+
+ // Request choose dialog
+ SendMessage(dat->hWnd, WM_CHOOSEIMG, 0, 0);
+ LeaveCriticalSection(&img_mutex);
+
+ return 0;
+}
diff --git a/protocols/Gadu-Gadu/src/import.cpp b/protocols/Gadu-Gadu/src/import.cpp
new file mode 100644
index 0000000000..e4ff066d11
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/import.cpp
@@ -0,0 +1,651 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Checks if a group already exists in Miranda with
+// the specified name.
+// Returns 1 if a group with the name exists, returns 0 otherwise.
+int GroupNameExists(const char *name)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ int i;
+
+ for (i = 0; ; i++) {
+ _itoa(i, idstr, 10);
+ if (db_get_s(NULL, "CListGroups", idstr, &dbv, DBVT_ASCIIZ)) break;
+ if (!strcmp(dbv.pszVal + 1, name)) {
+ DBFreeVariant(&dbv);
+ return 1;
+ }
+ DBFreeVariant(&dbv);
+ }
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Creates a group with a specified name in the
+// Miranda contact list.
+// Returns proper group name
+
+char *CreateGroup(char *groupName)
+{
+ int groupId;
+ char groupIdStr[11];
+ char groupName2[127];
+ char *p;
+ DBVARIANT dbv;
+
+ // Cleanup group name from weird characters
+
+ // Skip first break
+ while(*groupName && *groupName == '\\') groupName++;
+
+ p = strrchr(groupName, '\\');
+ // Cleanup end
+ while(p && !(*(p + 1)))
+ {
+ *p = 0;
+ p = strrchr(groupName, '\\');
+ }
+ // Create upper groups
+ if (p)
+ {
+ *p = 0;
+ CreateGroup(groupName);
+ *p = '\\';
+ }
+
+ // Is this a duplicate?
+ if (!GroupNameExists(groupName))
+ {
+ lstrcpynA(groupName2 + 1, groupName, (int)strlen(groupName) + 1);
+
+ // Find an unused id
+ for (groupId = 0; ; groupId++) {
+ _itoa(groupId, groupIdStr,10);
+ if (db_get_s(NULL, "CListGroups", groupIdStr, &dbv, DBVT_ASCIIZ))
+ break;
+ DBFreeVariant(&dbv);
+ }
+
+ groupName2[0] = 1|GROUPF_EXPANDED; // 1 is required so we never get '\0'
+ db_set_s(NULL, "CListGroups", groupIdStr, groupName2);
+ }
+ return groupName;
+}
+
+char *gg_makecontacts(GGPROTO *gg, int cr)
+{
+ string_t s = string_init(NULL);
+ char *contacts;
+
+ // Readup contacts
+ HANDLE hContact = db_find_first();
+ while (hContact)
+ {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, gg->m_szModuleName) && !db_get_b(hContact, gg->m_szModuleName, "ChatRoom", 0))
+ {
+ DBVARIANT dbv;
+
+ // Readup FirstName
+ if (!db_get_s(hContact, gg->m_szModuleName, "FirstName", &dbv, DBVT_ASCIIZ))
+ {
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ string_append_c(s, ';');
+ // Readup LastName
+ if (!db_get_s(hContact, gg->m_szModuleName, "LastName", &dbv, DBVT_ASCIIZ))
+ {
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ string_append_c(s, ';');
+
+ // Readup Nick
+ if (!db_get_s(hContact, "CList", "MyHandle", &dbv, DBVT_ASCIIZ) || !db_get_s(hContact, gg->m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ))
+ {
+ DBVARIANT dbv2;
+ if (!db_get_s(hContact, gg->m_szModuleName, "NickName", &dbv2, DBVT_ASCIIZ))
+ {
+ string_append(s, dbv2.pszVal);
+ DBFreeVariant(&dbv2);
+ }
+ else
+ string_append(s, dbv.pszVal);
+ string_append_c(s, ';');
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ string_append_c(s, ';');
+ string_append_c(s, ';');
+
+ // Readup Phone (fixed: uses stored editable phones)
+ if (!db_get_s(hContact, "UserInfo", "MyPhone0", &dbv, DBVT_ASCIIZ))
+ {
+ // Remove SMS postfix
+ char *sms = strstr(dbv.pszVal, " SMS");
+ if (sms) *sms = 0;
+
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ string_append_c(s, ';');
+ // Readup Group
+ if (!db_get_s(hContact, "CList", "Group", &dbv, DBVT_ASCIIZ))
+ {
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ string_append_c(s, ';');
+ // Readup Uin
+ string_append(s, ditoa(db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0)));
+ string_append_c(s, ';');
+ // Readup Mail (fixed: uses stored editable mails)
+ if (!db_get_s(hContact, "UserInfo", "Mye-mail0", &dbv, DBVT_ASCIIZ))
+ {
+ string_append(s, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (cr)
+ string_append(s, ";0;;0;\r\n");
+ else
+ string_append(s, ";0;;0;\n");
+ }
+ hContact = db_find_next(hContact);
+ }
+
+ contacts = string_free(s, 0);
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_makecontacts(): \n%s", contacts);
+#endif
+
+ return contacts;
+}
+
+char *strndup(char *str, int c)
+{
+ char *ret = (char*)malloc(c + 1);
+ ret[c] = 0;
+ strncpy(ret, str, c);
+ return ret;
+}
+
+void GGPROTO::parsecontacts(char *contacts)
+{
+ char *p = strchr(contacts, ':'), *n;
+ char *strFirstName, *strLastName, *strNickname, *strNick, *strPhone, *strGroup, *strUin, *strMail;
+ uin_t uin;
+
+ // Skip to proper data
+ if (p && p < strchr(contacts, ';')) p++;
+ else p = contacts;
+
+ while(p)
+ {
+ // Processing line
+ strFirstName = strLastName = strNickname = strNick = strPhone = strGroup = strUin = strMail = NULL;
+ uin = 0;
+
+ // FirstName
+ if (p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strFirstName = strndup(p, (n - p));
+ p = (n + 1);
+ }
+ // LastName
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strLastName = strndup(p, (n - p));
+ p = (n + 1);
+ }
+ // Nickname
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strNickname = strndup(p, (n - p));
+ p = (n + 1);
+ }
+ // Nick
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strNick = strndup(p, (n - p));
+ p = (n + 1);
+ }
+ // Phone
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p)
+ {
+ strPhone = (char*)malloc((n - p) + 5);
+ strncpy(strPhone, p, (n - p));
+ strcpy((strPhone + (n - p)), " SMS"); // Add SMS postfix
+ }
+ p = (n + 1);
+ }
+ // Group
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strGroup = strndup(p, (n - p));
+ p = (n + 1);
+ }
+ // Uin
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p)
+ {
+ strUin = strndup(p, (n - p));
+ uin = atoi(strUin);
+ }
+ p = (n + 1);
+ }
+ // Mail
+ if (n && p)
+ {
+ n = strchr(p, ';');
+ if (n && n != p) strMail = strndup(p, (n - p));
+ n = strchr(p, '\n');
+ p = (n + 1);
+ }
+ if (!n) p = NULL;
+
+ // Loadup contact
+ if (uin && strNick)
+ {
+ HANDLE hContact = getcontact(uin, 1, 1, _A2T(strNick));
+#ifdef DEBUGMODE
+ netlog("gg_parsecontacts(): Found contact %d with nickname \"%s\".", uin, strNick);
+#endif
+ // Write group
+ if (hContact && strGroup)
+ db_set_s(hContact, "CList", "Group", CreateGroup(strGroup));
+
+ // Write misc data
+ if (hContact && strFirstName) db_set_s(hContact, m_szModuleName, "FirstName", strFirstName);
+ if (hContact && strLastName) db_set_s(hContact, m_szModuleName, "LastName", strLastName);
+ if (hContact && strPhone) db_set_s(hContact, "UserInfo", "MyPhone0", strPhone); // Store now in User Info
+ if (hContact && strMail) db_set_s(hContact, "UserInfo", "Mye-mail0", strMail); // Store now in User Info
+ }
+
+ // Release stuff
+ if (strFirstName) free(strFirstName);
+ if (strLastName) free(strLastName);
+ if (strNickname) free(strNickname);
+ if (strNick) free(strNick);
+ if (strPhone) free(strPhone);
+ if (strGroup) free(strGroup);
+ if (strUin) free(strUin);
+ if (strMail) free(strMail);
+ }
+}
+
+//////////////////////////////////////////////////////////
+// import from server
+
+INT_PTR GGPROTO::import_server(WPARAM wParam, LPARAM lParam)
+{
+ char *password;
+ uin_t uin;
+ DBVARIANT dbv;
+
+ // Check if connected
+ if (!isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be connected before you can import/export contacts from/to server."),
+ m_tszUserName, MB_OK | MB_ICONSTOP
+ );
+ return 0;
+ }
+
+ // Readup password
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ password = _strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else return 0;
+
+ if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ return 0;
+
+ // Making contacts list
+ EnterCriticalSection(&sess_mutex);
+ if (gg_userlist_request(sess, GG_USERLIST_GET, NULL) == -1)
+ {
+ TCHAR error[128];
+ LeaveCriticalSection(&sess_mutex);
+ mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be imported because of error:\n\t%s"), _tcserror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_import_server(): Cannot import list because of \"%s\".", strerror(errno));
+ }
+ LeaveCriticalSection(&sess_mutex);
+ free(password);
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// remove from server
+
+INT_PTR GGPROTO::remove_server(WPARAM wParam, LPARAM lParam)
+{
+ char *password;
+ uin_t uin;
+ DBVARIANT dbv;
+
+ // Check if connected
+ if (!isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be connected before you can import/export contacts from/to server."),
+ m_tszUserName, MB_OK | MB_ICONSTOP
+ );
+ return 0;
+ }
+
+ // Readup password
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ password = _strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else return 0;
+
+ if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ return 0;
+
+ // Making contacts list
+ EnterCriticalSection(&sess_mutex);
+ if (gg_userlist_request(sess, GG_USERLIST_PUT, NULL) == -1)
+ {
+ TCHAR error[128];
+ LeaveCriticalSection(&sess_mutex);
+ mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be removeed because of error:\n\t%s"), strerror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_remove_server(): Cannot remove list because of \"%s\".", strerror(errno));
+ }
+ LeaveCriticalSection(&sess_mutex);
+
+ // Set list removal
+ is_list_remove = TRUE;
+ free(password);
+
+ return 0;
+}
+
+INT_PTR GGPROTO::import_text(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR str[MAX_PATH];
+ TCHAR filter[512], *pfilter;
+ struct _stat st;
+ FILE *f;
+
+ OPENFILENAME ofn = {0};
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ _tcsncpy(filter, TranslateT("Text files"), SIZEOF(filter));
+ _tcsncat(filter, _T(" (*.txt)"), SIZEOF(filter) - _tcslen(filter));
+ pfilter = filter + _tcslen(filter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+
+ _tcsncpy(pfilter, _T("*.TXT"), SIZEOF(filter) - (pfilter - filter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+ _tcsncpy(pfilter, TranslateT("All Files"), SIZEOF(filter) - (pfilter - filter));
+ _tcsncat(pfilter, _T(" (*)"), SIZEOF(filter) - (pfilter - filter) - _tcslen(pfilter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+
+ _tcsncpy(pfilter, _T("*"), SIZEOF(filter) - (pfilter - filter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+
+ *pfilter = '\0';
+ ofn.lpstrFilter = filter;
+ ofn.lpstrFile = str;
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
+ ofn.nMaxFile = sizeof(str);
+ ofn.nMaxFileTitle = MAX_PATH;
+ ofn.lpstrDefExt = _T("txt");
+
+#ifdef DEBUGMODE
+ netlog("gg_import_text()");
+#endif
+ if (!GetOpenFileName(&ofn)) return 0;
+
+ f = _tfopen(str, _T("r"));
+ _tstat(str, &st);
+
+ if (f && st.st_size)
+ {
+ char *contacts = (char*)malloc(st.st_size * sizeof(char));
+ fread(contacts, sizeof(char), st.st_size, f);
+ fclose(f);
+ parsecontacts(contacts);
+ free(contacts);
+
+ MessageBox(NULL, TranslateT("List import successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+ else
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be imported from file \"%s\" because of error:\n\t%s"), str, _tcserror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_import_text(): Cannot import list from file \"%S\" because of \"%s\".", str, strerror(errno));
+ }
+
+ return 0;
+}
+
+INT_PTR GGPROTO::export_text(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR str[MAX_PATH];
+ OPENFILENAME ofn = {0};
+ TCHAR filter[512], *pfilter;
+ FILE *f;
+
+ _tcsncpy(str, TranslateT("contacts"), sizeof(str));
+ _tcsncat(str, _T(".txt"), sizeof(str) - _tcslen(str));
+
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ _tcsncpy(filter, TranslateT("Text files"), SIZEOF(filter));
+ _tcsncat(filter, _T(" (*.txt)"), SIZEOF(filter) - _tcslen(filter));
+ pfilter = filter + _tcslen(filter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+ _tcsncpy(pfilter, _T("*.TXT"), SIZEOF(filter) - (pfilter - filter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+ _tcsncpy(pfilter, TranslateT("All Files"), SIZEOF(filter) - (pfilter - filter));
+ _tcsncat(pfilter, _T(" (*)"), SIZEOF(filter) - (pfilter - filter) - _tcslen(pfilter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+ _tcsncpy(pfilter, _T("*"), SIZEOF(filter) - (pfilter - filter));
+ pfilter = pfilter + _tcslen(pfilter) + 1;
+ if (pfilter >= filter + SIZEOF(filter))
+ return 0;
+ *pfilter = '\0';
+ ofn.lpstrFilter = filter;
+ ofn.lpstrFile = str;
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
+ ofn.nMaxFile = sizeof(str);
+ ofn.nMaxFileTitle = MAX_PATH;
+ ofn.lpstrDefExt = _T("txt");
+
+#ifdef DEBUGMODE
+ netlog("gg_export_text(%s).", str);
+#endif
+ if (!GetSaveFileName(&ofn)) return 0;
+
+ if (f = _tfopen(str, _T("w"))) {
+ char *contacts = gg_makecontacts(this, 0);
+ fwrite(contacts, sizeof(char), strlen(contacts), f);
+ fclose(f);
+ free(contacts);
+
+ MessageBox(NULL, TranslateT("List export successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+ else
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be exported to file \"%s\" because of error:\n\t%s"), str, _tcserror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_import_text(): Cannot export list to file \"%s\" because of \"%s\".", str, strerror(errno));
+ }
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// export to server
+
+INT_PTR GGPROTO::export_server(WPARAM wParam, LPARAM lParam)
+{
+ char *password, *contacts;
+ uin_t uin;
+ DBVARIANT dbv;
+
+ // Check if connected
+ if (!isonline())
+ {
+ MessageBox(NULL,
+ TranslateT("You have to be connected before you can import/export contacts from/to server."),
+ m_tszUserName, MB_OK | MB_ICONSTOP
+ );
+ return 0;
+ }
+
+ // Readup password
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ password = _strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else return 0;
+
+ if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ return 0;
+
+ // Making contacts list
+ contacts = gg_makecontacts(this, 1);
+
+#ifdef DEBUGMODE
+ netlog("gg_userlist_request(%s).", contacts);
+#endif
+
+ EnterCriticalSection(&sess_mutex);
+ if (gg_userlist_request(sess, GG_USERLIST_PUT, contacts) == -1)
+ {
+ TCHAR error[128];
+ LeaveCriticalSection(&sess_mutex);
+ mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be exported because of error:\n\t%s"), _tcserror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_export_server(): Cannot export list because of \"%s\".", strerror(errno));
+ }
+ LeaveCriticalSection(&sess_mutex);
+
+ // Set list removal
+ is_list_remove = FALSE;
+ free(contacts);
+ free(password);
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// Import menus and stuff
+
+void GGPROTO::import_init(HGENMENU hRoot)
+{
+ CLISTMENUITEM mi = {0};
+ char service[64];
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE;
+ mi.hParentMenu = hRoot;
+
+ // Import from server item
+ mir_snprintf(service, sizeof(service), GGS_IMPORT_SERVER, m_szModuleName);
+ createObjService(service, &GGPROTO::import_server);
+ mi.position = 2000500001;
+ mi.icolibItem = GetIconHandle(IDI_IMPORT_SERVER);
+ mi.pszName = LPGEN("Import List From &Server");
+ mi.pszService = service;
+ hMainMenu[2] = Menu_AddProtoMenuItem(&mi);
+
+ // Import from textfile
+ mir_snprintf(service, sizeof(service), GGS_IMPORT_TEXT, m_szModuleName);
+ createObjService(service, &GGPROTO::import_text);
+ mi.position = 2000500002;
+ mi.icolibItem = GetIconHandle(IDI_IMPORT_TEXT);
+ mi.pszName = LPGEN("Import List From &Text File...");
+ mi.pszService = service;
+ hMainMenu[3] = Menu_AddProtoMenuItem(&mi);
+
+ // Remove from server
+ mir_snprintf(service, sizeof(service), GGS_REMOVE_SERVER, m_szModuleName);
+ createObjService(service, &GGPROTO::remove_server);
+ mi.position = 2000500003;
+ mi.icolibItem = GetIconHandle(IDI_REMOVE_SERVER);
+ mi.pszName = LPGEN("&Remove List From Server");
+ mi.pszService = service;
+ hMainMenu[4] = Menu_AddProtoMenuItem(&mi);
+
+ // Export to server
+ mir_snprintf(service, sizeof(service), GGS_EXPORT_SERVER, m_szModuleName);
+ createObjService(service, &GGPROTO::export_server);
+ mi.position = 2005000001;
+ mi.icolibItem = GetIconHandle(IDI_EXPORT_SERVER);
+ mi.pszName = LPGEN("Export List To &Server");
+ mi.pszService = service;
+ hMainMenu[5] = Menu_AddProtoMenuItem(&mi);
+
+ // Export to textfile
+ mir_snprintf(service, sizeof(service), GGS_EXPORT_TEXT, m_szModuleName);
+ createObjService(service, &GGPROTO::export_text);
+ mi.position = 2005000002;
+ mi.icolibItem = GetIconHandle(IDI_EXPORT_TEXT);
+ mi.pszName = LPGEN("Export List To &Text File...");
+ mi.pszService = service;
+ hMainMenu[6] = Menu_AddProtoMenuItem(&mi);
+}
diff --git a/protocols/Gadu-Gadu/src/keepalive.cpp b/protocols/Gadu-Gadu/src/keepalive.cpp
new file mode 100644
index 0000000000..02b661c222
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/keepalive.cpp
@@ -0,0 +1,92 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+/* NOTE: Eventhough SetTimer seems to support UINT_PTR for idEvent, it seems that TimerProc
+ * does not get full pointer but just 2 byte lower bytes.
+ */
+#define MAX_TIMERS 8
+GGPROTO *g_timers[MAX_TIMERS] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+static VOID CALLBACK gg_keepalive(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime)
+{
+ int i;
+
+ //Search for GGPROTO* context
+ for(i = 0; i < MAX_TIMERS; i++)
+ if (g_timers[i]->timer == idEvent)
+ break;
+
+ if (i < MAX_TIMERS)
+ {
+ GGPROTO *gg = g_timers[i];
+ if (gg->isonline())
+ {
+ #ifdef DEBUGMODE
+ gg->netlog("Sending keep-alive");
+ #endif
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_ping(gg->sess);
+ LeaveCriticalSection(&gg->sess_mutex);
+ }
+ }
+}
+
+void GGPROTO::keepalive_init()
+{
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_KEEPALIVE, GG_KEYDEF_KEEPALIVE))
+ {
+ int i;
+ for(i = 0; i < MAX_TIMERS && g_timers[i] != NULL; i++);
+ if (i < MAX_TIMERS)
+ {
+ #ifdef DEBUGMODE
+ netlog("gg_keepalive_init(): Initializing Timer %d", i);
+ #endif
+ timer = SetTimer(NULL, 0, 1000 * 30, gg_keepalive);
+ g_timers[i] = this;
+ }
+ }
+}
+
+void GGPROTO::keepalive_destroy()
+{
+#ifdef DEBUGMODE
+ netlog("gg_destroykeepalive(): Killing Timer");
+#endif
+ if (timer)
+ {
+ int i;
+ KillTimer(NULL, timer);
+ for(i = 0; i < MAX_TIMERS; i++)
+ if (g_timers[i] == this) {
+ g_timers[i] = NULL;
+ break;
+ }
+ timer = 0;
+#ifdef DEBUGMODE
+ netlog("gg_destroykeepalive(): Killed Timer %d", i);
+#endif
+ }
+#ifdef DEBUGMODE
+ netlog("gg_destroykeepalive(): End");
+#endif
+}
diff --git a/protocols/Gadu-Gadu/src/libgadu/COPYING b/protocols/Gadu-Gadu/src/libgadu/COPYING
new file mode 100644
index 0000000000..6e816402b7
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/protocols/Gadu-Gadu/src/libgadu/common.c b/protocols/Gadu-Gadu/src/libgadu/common.c
new file mode 100644
index 0000000000..b9b41c0547
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/common.c
@@ -0,0 +1,975 @@
+/* coding: UTF-8 */
+/* $Id: common.c 13762 2011-08-09 12:35:16Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+/*
+ * Funkcje konwersji między UTF-8 i CP1250 są oparte o kod biblioteki iconv.
+ * Informacje o prawach autorskich oryginalnego kodu zamieszczono poniĹĽej:
+ *
+ * Copyright (C) 1999-2001, 2004 Free Software Foundation, Inc.
+ * This file is part of the GNU LIBICONV Library.
+ *
+ * The GNU LIBICONV Library is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * The GNU LIBICONV Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the GNU LIBICONV Library; see the file COPYING.LIB.
+ * If not, write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * \file common.c
+ *
+ * \brief Funkcje wykorzystywane przez różne moduły biblioteki
+ */
+#include <sys/types.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef sun
+# include <sys/filio.h>
+#endif
+#endif /* _WIN32 */
+
+#include <errno.h>
+#include <fcntl.h>
+#ifndef _WIN32
+#include <netdb.h>
+#endif /* _WIN32 */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif /* _WIN32 */
+
+#include "libgadu.h"
+#include "internal.h"
+
+/**
+ * Plik, do którego będą przekazywane informacje odpluskwiania.
+ *
+ * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację
+ * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub
+ * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane
+ * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr).
+ *
+ * \ingroup debug
+ */
+FILE *gg_debug_file = NULL;
+
+#ifndef GG_DEBUG_DISABLE
+
+/**
+ * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji.
+ *
+ * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w
+ * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana.
+ * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu.
+ *
+ * \param sess Struktura sesji (może być \c NULL)
+ * \param level Poziom informacji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentĂłw (zgodna z \c printf)
+ */
+static void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap)
+{
+ if (gg_debug_handler_session)
+ (*gg_debug_handler_session)(sess, level, format, ap);
+ else if (gg_debug_handler)
+ (*gg_debug_handler)(level, format, ap);
+ else if (gg_debug_level & level)
+ vfprintf(gg_debug_file ? gg_debug_file : stderr, format, ap);
+}
+
+
+/**
+ * \internal Przekazuje informacjÄ™ odpluskawiania.
+ *
+ * \param level Poziom wiadomości
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \ingroup debug
+ */
+void gg_debug(int level, const char *format, ...)
+{
+ va_list ap;
+ int old_errno = errno;
+ va_start(ap, format);
+ gg_debug_common(NULL, level, format, ap);
+ va_end(ap);
+ errno = old_errno;
+}
+
+/**
+ * \internal Przekazuje informacjÄ™ odpluskwiania zwiÄ…zanÄ… z sesjÄ….
+ *
+ * \param sess Struktura sesji
+ * \param level Poziom wiadomości
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \ingroup debug
+ */
+void gg_debug_session(struct gg_session *sess, int level, const char *format, ...)
+{
+ va_list ap;
+ int old_errno = errno;
+ va_start(ap, format);
+ gg_debug_common(sess, level, format, ap);
+ va_end(ap);
+ errno = old_errno;
+}
+
+/**
+ * \internal Przekazuje informację odpluskwiania związane z zawartością pamięci.
+ *
+ * \param sess Struktura sesji
+ * \param buf Adres w pamięci
+ * \param buf_length Ilość danych do wyświetlenia
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \ingroup debug
+ */
+void gg_debug_dump_session(struct gg_session *sess, const void *buf, unsigned int buf_length, const char *format, ...)
+{
+ va_list ap;
+
+ if ((gg_debug_level & GG_DEBUG_DUMP)) {
+ unsigned int i;
+
+ va_start(ap, format);
+ gg_debug_common(sess, GG_DEBUG_DUMP, format, ap);
+ for (i = 0; i < buf_length; ++i)
+ gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", ((unsigned char*) buf)[i]);
+ gg_debug_session(sess, GG_DEBUG_DUMP, "\n");
+ va_end(ap);
+ }
+}
+
+#endif
+
+/**
+ * \internal Odpowiednik funkcji \c vsprintf alokujÄ…cy miejsce na wynik.
+ *
+ * Funkcja korzysta z funkcji \c vsnprintf, sprawdzając czy dostępna funkcja
+ * systemowa jest zgodna ze standardem C99 czy wcześniejszymi.
+ *
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentĂłw (zgodna z \c printf)
+ *
+ * \return Zaalokowany bufor lub NULL, jeśli zabrakło pamięci.
+ *
+ * \ingroup helper
+ */
+char *gg_vsaprintf(const char *format, va_list ap)
+{
+ int size = 0;
+ char *buf = NULL;
+
+#ifdef GG_CONFIG_HAVE_VA_COPY
+ va_list aq;
+
+ va_copy(aq, ap);
+#else
+# ifdef GG_CONFIG_HAVE___VA_COPY
+ va_list aq;
+
+ __va_copy(aq, ap);
+# endif
+#endif
+
+#ifndef GG_CONFIG_HAVE_C99_VSNPRINTF
+ {
+ int res;
+ char *tmp;
+
+ size = 128;
+ do {
+ size *= 2;
+ if (!(tmp = realloc(buf, size))) {
+ free(buf);
+ return NULL;
+ }
+ buf = tmp;
+ res = vsnprintf(buf, size, format, ap);
+ } while (res == size - 1 || res == -1);
+ }
+#else
+ {
+ char tmp[2];
+
+ /* libce Solarisa przy buforze NULL zawsze zwracają -1, więc
+ * musimy podać coś istniejącego jako cel printf()owania. */
+ size = vsnprintf(tmp, sizeof(tmp), format, ap);
+ if (!(buf = malloc(size + 1)))
+ return NULL;
+ }
+#endif
+
+#ifdef GG_CONFIG_HAVE_VA_COPY
+ vsnprintf(buf, size + 1, format, aq);
+ va_end(aq);
+#else
+# ifdef GG_CONFIG_HAVE___VA_COPY
+ vsnprintf(buf, size + 1, format, aq);
+ va_end(aq);
+# else
+ vsnprintf(buf, size + 1, format, ap);
+# endif
+#endif
+
+ return buf;
+}
+
+/**
+ * \internal Odpowiednik funkcji \c sprintf alokujÄ…cy miejsce na wynik.
+ *
+ * Funkcja korzysta z funkcji \c vsnprintf, sprawdzając czy dostępna funkcja
+ * systemowa jest zgodna ze standardem C99 czy wcześniejszymi.
+ *
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \return Zaalokowany bufor lub NULL, jeśli zabrakło pamięci.
+ *
+ * \ingroup helper
+ */
+char *gg_saprintf(const char *format, ...)
+{
+ va_list ap;
+ char *res;
+
+ va_start(ap, format);
+ res = gg_vsaprintf(format, ap);
+ va_end(ap);
+
+ return res;
+}
+
+/**
+ * \internal Pobiera liniÄ™ tekstu z bufora.
+ *
+ * Funkcja niszczy bufor źródłowy bezpowrotnie, dzieląc go na kolejne ciągi
+ * znaków i obcina znaki końca linii.
+ *
+ * \param ptr Wskaźnik do zmiennej, która przechowuje aktualne położenie
+ * w analizowanym buforze
+ *
+ * \return Wskaźnik do kolejnej linii tekstu lub NULL, jeśli to już koniec
+ * bufora.
+ */
+char *gg_get_line(char **ptr)
+{
+ char *foo, *res;
+
+ if (!ptr || !*ptr || !strcmp(*ptr, ""))
+ return NULL;
+
+ res = *ptr;
+
+ if (!(foo = strchr(*ptr, '\n')))
+ *ptr += strlen(*ptr);
+ else {
+ size_t len;
+ *ptr = foo + 1;
+ *foo = 0;
+
+ len = strlen(res);
+
+ if (len > 1 && res[len - 1] == '\r')
+ res[len - 1] = 0;
+ }
+
+ return res;
+}
+
+/**
+ * \internal Czyta liniÄ™ tekstu z gniazda.
+ *
+ * Funkcja czyta tekst znak po znaku, więc nie jest efektywna, ale dzięki
+ * brakowi buforowania, nie koliduje z innymi funkcjami odczytu.
+ *
+ * \param sock Deskryptor gniazda
+ * \param buf WskaĹşnik do bufora
+ * \param length Długość bufora
+ *
+ * \return Zwraca \c buf jeśli się powiodło, lub \c NULL w przypadku błędu.
+ */
+char *gg_read_line(SOCKET sock, char *buf, int length)
+{
+ int ret;
+
+ if (!buf || length < 0)
+ return NULL;
+
+ for (; length > 1; buf++, length--) {
+ do {
+ if ((ret = gg_sock_read(sock, buf, 1)) == -1 && errno != EINTR && errno != EAGAIN) {
+ gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno));
+ *buf = 0;
+ return NULL;
+ } else if (ret == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n");
+ *buf = 0;
+ return NULL;
+ }
+ } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (*buf == '\n') {
+ buf++;
+ break;
+ }
+ }
+
+ *buf = 0;
+ return buf;
+}
+
+/**
+ * \internal Nawiązuje połączenie TCP.
+ *
+ * \param addr WskaĹşnik na strukturÄ™ \c in_addr z adresem serwera
+ * \param port Port serwera
+ * \param async Flaga asynchronicznego połączenia
+ *
+ * \return Deskryptor gniazda lub -1 w przypadku błędu
+ *
+ * \ingroup helper
+ */
+#ifdef GG_CONFIG_MIRANDA
+SOCKET gg_connect_internal(void *addr, int port, int async, SOCKET *gg_sock)
+#else
+SOCKET gg_connect(void *addr, int port, int async)
+#endif
+{
+ SOCKET sock;
+ int one = 1, errno2;
+ struct sockaddr_in sin;
+ struct in_addr *a = addr;
+ struct sockaddr_in myaddr;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async);
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno));
+ return -1;
+ }
+
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sin_family = AF_INET;
+
+ myaddr.sin_addr.s_addr = gg_local_ip;
+
+ if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno));
+ errno2 = errno;
+ gg_sock_close(sock);
+ errno = errno2;
+ return -1;
+ }
+
+#ifdef GG_CONFIG_MIRANDA
+ if (gg_sock) *gg_sock = sock;
+#endif
+
+ if (async) {
+#ifdef FIONBIO
+ if (ioctl(sock, FIONBIO, &one) == -1) {
+#else
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
+#endif
+ gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno));
+ errno2 = errno;
+ gg_sock_close(sock);
+ errno = errno2;
+ return -1;
+ }
+ }
+
+ sin.sin_port = htons((uint16_t)port);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = a->s_addr;
+
+ errno = 0;
+ if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) {
+ if (errno && (!async || errno != EINPROGRESS)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno));
+ errno2 = errno;
+ gg_sock_close(sock);
+ errno = errno2;
+ return -1;
+ }
+ gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n");
+ }
+
+ return sock;
+}
+
+#ifdef GG_CONFIG_MIRANDA
+SOCKET gg_connect(void *addr, int port, int async)
+{
+ return gg_connect_internal(addr, port, async, 0);
+}
+#endif
+
+/**
+ * \internal Usuwa znaki końca linii.
+ *
+ * Funkcja działa bezpośrednio na buforze.
+ *
+ * \param line Bufor z tekstem
+ *
+ * \ingroup helper
+ */
+void gg_chomp(char *line)
+{
+ size_t len;
+
+ if (!line)
+ return;
+
+ len = strlen(line);
+
+ if (len > 0 && line[len - 1] == '\n')
+ line[--len] = 0;
+ if (len > 0 && line[len - 1] == '\r')
+ line[--len] = 0;
+}
+
+/**
+ * \internal Koduje ciÄ…g znakĂłw do postacji adresu HTTP.
+ *
+ * Zamienia znaki niedrukowalne, spoza ASCII i majÄ…ce specjalne znaczenie
+ * dla protokołu HTTP na encje postaci \c %XX, gdzie \c XX jest szesnastkową
+ * wartością znaku.
+ *
+ * \param str CiÄ…g znakĂłw do zakodowania
+ *
+ * \return Zaalokowany bufor lub \c NULL w przypadku błędu.
+ *
+ * \ingroup helper
+ */
+char *gg_urlencode(const char *str)
+{
+ char *q, *buf, hex[] = "0123456789abcdef";
+ const char *p;
+ unsigned int size = 0;
+
+ if (!str)
+ str = "";
+
+ for (p = str; *p; p++, size++) {
+ if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-'))
+ size += 2;
+ }
+
+ if (!(buf = malloc(size + 1)))
+ return NULL;
+
+ for (p = str, q = buf; *p; p++, q++) {
+ if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-'))
+ *q = *p;
+ else {
+ if (*p == ' ')
+ *q = '+';
+ else {
+ *q++ = '%';
+ *q++ = hex[*p >> 4 & 15];
+ *q = hex[*p & 15];
+ }
+ }
+ }
+
+ *q = 0;
+
+ return buf;
+}
+
+/**
+ * \internal Wyznacza skrót dla usług HTTP.
+ *
+ * Funkcja jest wykorzystywana do wyznaczania skrótu adresu e-mail, hasła
+ * i innych wartości przekazywanych jako parametry usług HTTP.
+ *
+ * W parametrze \c format należy umieścić znaki określające postać kolejnych
+ * parametrów: \c 's' jeśli parametr jest ciągiem znaków, \c 'u' jeśli jest
+ * liczbÄ….
+ *
+ * \param format Format kolejnych parametrĂłw (niezgodny z \c printf)
+ *
+ * \return Wartość skrótu
+ */
+int gg_http_hash(const char *format, ...)
+{
+ unsigned int a, c, i, j;
+ va_list ap;
+ int b = -1;
+
+ va_start(ap, format);
+
+ for (j = 0; j < strlen(format); j++) {
+ char *arg, buf[16];
+
+ if (format[j] == 'u') {
+ snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t));
+ arg = buf;
+ } else {
+ if (!(arg = va_arg(ap, char*)))
+ arg = "";
+ }
+
+ i = 0;
+ while ((c = (unsigned char) arg[i++]) != 0) {
+ a = (c ^ b) + (c << 8);
+ b = (a >> 24) | (a << 8);
+ }
+ }
+
+ va_end(ap);
+
+ return (b < 0 ? -b : b);
+}
+
+/**
+ * \internal Zestaw znakĂłw kodowania base64.
+ */
+static char gg_base64_charset[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * \internal Koduje ciÄ…g znakĂłw do base64.
+ *
+ * Wynik funkcji należy zwolnić za pomocą \c free.
+ *
+ * \param buf Bufor z danami do zakodowania
+ *
+ * \return Zaalokowany bufor z zakodowanymi danymi
+ *
+ * \ingroup helper
+ */
+char *gg_base64_encode(const char *buf)
+{
+ char *out, *res;
+ unsigned int i = 0, j = 0, k = 0, len = (unsigned int)strlen(buf);
+
+ res = out = malloc((len / 3 + 1) * 4 + 2);
+
+ if (!res)
+ return NULL;
+
+ while (j <= len) {
+ switch (i % 4) {
+ case 0:
+ k = (buf[j] & 252) >> 2;
+ break;
+ case 1:
+ if (j < len)
+ k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4);
+ else
+ k = (buf[j] & 3) << 4;
+
+ j++;
+ break;
+ case 2:
+ if (j < len)
+ k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6);
+ else
+ k = (buf[j] & 15) << 2;
+
+ j++;
+ break;
+ case 3:
+ k = buf[j++] & 63;
+ break;
+ }
+ *out++ = gg_base64_charset[k];
+ i++;
+ }
+
+ if (i % 4)
+ for (j = 0; j < 4 - (i % 4); j++, out++)
+ *out = '=';
+
+ *out = 0;
+
+ return res;
+}
+
+/**
+ * \internal Dekoduje ciÄ…g znakĂłw zapisany w base64.
+ *
+ * Wynik funkcji należy zwolnić za pomocą \c free.
+ *
+ * \param buf Bufor źródłowy z danymi do zdekodowania
+ *
+ * \return Zaalokowany bufor ze zdekodowanymi danymi
+ *
+ * \ingroup helper
+ */
+char *gg_base64_decode(const char *buf)
+{
+ char *res, *save, *foo, val;
+ const char *end;
+ unsigned int index = 0;
+
+ if (!buf)
+ return NULL;
+
+ save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2);
+
+ if (!save)
+ return NULL;
+
+ end = buf + strlen(buf);
+
+ while (*buf && buf < end) {
+ if (*buf == '\r' || *buf == '\n') {
+ buf++;
+ continue;
+ }
+ if (!(foo = strchr(gg_base64_charset, *buf)))
+ foo = gg_base64_charset;
+ val = (int)(foo - gg_base64_charset);
+ buf++;
+ switch (index) {
+ case 0:
+ *res |= val << 2;
+ break;
+ case 1:
+ *res++ |= val >> 4;
+ *res |= val << 4;
+ break;
+ case 2:
+ *res++ |= val >> 2;
+ *res |= val << 6;
+ break;
+ case 3:
+ *res++ |= val;
+ break;
+ }
+ index++;
+ index %= 4;
+ }
+ *res = 0;
+
+ return save;
+}
+
+/**
+ * \internal Tworzy nagłówek autoryzacji serwera pośredniczącego.
+ *
+ * Dane pobiera ze zmiennych globalnych \c gg_proxy_username i
+ * \c gg_proxy_password.
+ *
+ * \return Zaalokowany bufor z tekstem lub NULL, jeśli serwer pośredniczący
+ * nie jest uĹĽywany lub nie wymaga autoryzacji.
+ */
+char *gg_proxy_auth()
+{
+ char *tmp, *enc, *out;
+ size_t tmp_size;
+
+ if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password)
+ return NULL;
+
+ if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2))))
+ return NULL;
+
+ snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password);
+
+ if (!(enc = gg_base64_encode(tmp))) {
+ free(tmp);
+ return NULL;
+ }
+
+ free(tmp);
+
+ if (!(out = malloc(strlen(enc) + 40))) {
+ free(enc);
+ return NULL;
+ }
+
+ snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc);
+
+ free(enc);
+
+ return out;
+}
+
+/**
+ * \internal Tablica pomocnicza do wyznaczania sumy kontrolnej.
+ */
+static uint32_t gg_crc32_table[256];
+
+/**
+ * \internal Flaga wypełnienia tablicy pomocniczej do wyznaczania sumy
+ * kontrolnej.
+ */
+static int gg_crc32_initialized = 0;
+
+/**
+ * \internal Tworzy tablicÄ™ pomocniczÄ… do wyznaczania sumy kontrolnej.
+ */
+static void gg_crc32_make_table(void)
+{
+ uint32_t h = 1;
+ unsigned int i, j;
+
+ memset(gg_crc32_table, 0, sizeof(gg_crc32_table));
+
+ for (i = 128; i; i >>= 1) {
+ h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0);
+
+ for (j = 0; j < 256; j += 2 * i)
+ gg_crc32_table[i + j] = gg_crc32_table[j] ^ h;
+ }
+
+ gg_crc32_initialized = 1;
+}
+
+/**
+ * Wyznacza sumÄ™ kontrolnÄ… CRC32.
+ *
+ * \param crc Suma kontrola poprzedniego bloku danych lub 0 jeśli liczona
+ * jest suma kontrolna pierwszego bloku
+ * \param buf Bufor danych
+ * \param len Długość bufora danych
+ *
+ * \return Suma kontrolna.
+ */
+uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len)
+{
+ if (!gg_crc32_initialized)
+ gg_crc32_make_table();
+
+ if (!buf || len < 0)
+ return crc;
+
+ crc ^= 0xffffffffL;
+
+ while (len--)
+ crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff];
+
+ return crc ^ 0xffffffffL;
+}
+
+/**
+ * \internal Tablica konwersji między CP1250 a UTF-8.
+ */
+static const uint16_t table_cp1250[] = {
+ 0x20ac, '?', 0x201a, '?', 0x201e, 0x2026, 0x2020, 0x2021,
+ '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179,
+ '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+ '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a,
+ 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b,
+ 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c,
+ 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7,
+ 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e,
+ 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7,
+ 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df,
+ 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7,
+ 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f,
+ 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7,
+ 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9,
+};
+
+/**
+ * \internal Zamienia tekst kodowany CP1250 na UTF-8.
+ *
+ * \param b Tekst źródłowy w CP1250.
+ *
+ * \return Zaalokowany bufor z tekstem w UTF-8.
+ */
+char *gg_cp_to_utf8(const char *b)
+{
+ unsigned char *buf = (unsigned char *) b;
+ char *newbuf;
+ int newlen = 0;
+ int i, j;
+
+ for (i = 0; buf[i]; i++) {
+ uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80];
+
+ if (znak < 0x80) newlen += 1;
+ else if (znak < 0x800) newlen += 2;
+ else newlen += 3;
+ }
+
+ if (!(newbuf = malloc(newlen+1))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_cp_to_utf8() not enough memory\n");
+ return NULL;
+ }
+
+ for (i = 0, j = 0; buf[i]; i++) {
+ uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80];
+ int count;
+
+ if (znak < 0x80) count = 1;
+ else if (znak < 0x800) count = 2;
+ else count = 3;
+
+ switch (count) {
+ case 3: newbuf[j+2] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0x800;
+ case 2: newbuf[j+1] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0xc0;
+ case 1: newbuf[j] = (char)znak;
+ }
+ j += count;
+ }
+ newbuf[j] = '\0';
+
+ return newbuf;
+}
+
+/**
+ * \internal Dekoduje jeden znak UTF-8.
+ *
+ * \note Funkcja nie jest kompletnÄ… implementacjÄ… UTF-8, a wersjÄ… uproszczonÄ…
+ * do potrzeb kodowania CP1250.
+ *
+ * \param s Tekst źródłowy.
+ * \param n Długość tekstu źródłowego.
+ * \param ch WskaĹşnik na wynik dekodowania.
+ *
+ * \return Długość zdekodowanej sekwencji w bajtach lub wartość mniejsza
+ * od zera w przypadku błędu.
+ */
+static int gg_utf8_helper(unsigned char *s, int n, uint16_t *ch)
+{
+ unsigned char c = s[0];
+
+ if (c < 0x80) {
+ *ch = c;
+ return 1;
+ }
+
+ if (c < 0xc2)
+ return -1;
+
+ if (c < 0xe0) {
+ if (n < 2)
+ return -2;
+ if (!((s[1] ^ 0x80) < 0x40))
+ return -1;
+ *ch = ((uint16_t) (c & 0x1f) << 6) | (uint16_t) (s[1] ^ 0x80);
+ return 2;
+ }
+
+ if (c < 0xf0) {
+ if (n < 3)
+ return -2;
+ if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40 && (c >= 0xe1 || s[1] >= 0xa0)))
+ return -1;
+ *ch = ((uint16_t) (c & 0x0f) << 12) | ((uint16_t) (s[1] ^ 0x80) << 6) | (uint16_t) (s[2] ^ 0x80);
+ return 3;
+ }
+
+ return -1;
+}
+
+/**
+ * \internal Zamienia tekst kodowany UTF-8 na CP1250.
+ *
+ * \param b Tekst źródłowy w UTF-8.
+ *
+ * \return Zaalokowany bufor z tekstem w CP1250.
+ */
+char *gg_utf8_to_cp(const char *b)
+{
+ unsigned char *buf = (unsigned char *) b;
+ char *newbuf;
+ int newlen = 0;
+ int len;
+ int i, j;
+
+ len = (int)strlen(b);
+
+ for (i = 0; i < len; newlen++) {
+ uint16_t discard;
+ int ret;
+
+ ret = gg_utf8_helper(&buf[i], len - i, &discard);
+
+ if (ret > 0)
+ i += ret;
+ else
+ i++;
+ }
+
+ if (!(newbuf = malloc(newlen+1))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_utf8_to_cp() not enough memory\n");
+ return NULL;
+ }
+
+ for (i = 0, j = 0; buf[i]; j++) {
+ uint16_t znak;
+ int ret, k;
+
+ ret = gg_utf8_helper(&buf[i], len - i, &znak);
+
+ if (ret > 0) {
+ i += ret;
+ } else {
+ znak = '?';
+ i++;
+ }
+
+ if (znak < 0x80) {
+ newbuf[j] = (char)znak;
+ continue;
+ }
+
+ newbuf[j] = '?';
+
+ for (k = 0; k < (sizeof(table_cp1250)/sizeof(table_cp1250[0])); k++) {
+ if (table_cp1250[k] == znak) {
+ newbuf[j] = (0x80 | k);
+ break;
+ }
+ }
+ }
+ newbuf[j] = '\0';
+
+ return newbuf;
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/compat.h b/protocols/Gadu-Gadu/src/libgadu/compat.h
new file mode 100644
index 0000000000..1aa17d1e4a
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/compat.h
@@ -0,0 +1,36 @@
+/* coding: UTF-8 */
+/* $Id: compat.h 11075 2009-12-20 15:01:43Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 compat.h
+ *
+ * \brief Makra zapewniające kompatybilność API na różnych systemach
+ */
+
+#ifndef __COMPAT_H
+#define __COMPAT_H
+
+#ifdef sun
+# define INADDR_NONE ((in_addr_t) 0xffffffff)
+#endif
+
+#endif
diff --git a/protocols/Gadu-Gadu/src/libgadu/dcc.c b/protocols/Gadu-Gadu/src/libgadu/dcc.c
new file mode 100644
index 0000000000..2fbe1b430c
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/dcc.c
@@ -0,0 +1,1363 @@
+/* coding: UTF-8 */
+/* $Id: dcc.c 12145 2010-07-07 23:49:04Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2008 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Tomasz Chiliński <chilek@chilan.com>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 dcc.c
+ *
+ * \brief Obsługa połączeń bezpośrednich do wersji Gadu-Gadu 6.x
+ */
+
+#ifndef _WIN64
+#define _USE_32BIT_TIME_T
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef sun
+# include <sys/filio.h>
+#endif
+#endif /* _WIN32 */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include "compat.h"
+#include "libgadu.h"
+
+#ifdef _WIN32
+#undef small
+#endif
+
+#ifndef GG_DEBUG_DISABLE
+
+/**
+ * \internal Przekazuje zawartość pakietu do odpluskwiania.
+ *
+ * \param prefix Prefiks informacji
+ * \param fd Deskryptor gniazda
+ * \param buf Bufor z danumi
+ * \param size Rozmiar bufora z danymi
+ */
+static void gg_dcc_debug_data(const char *prefix, SOCKET fd, const void *buf, unsigned int size)
+{
+ unsigned int i;
+
+ gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size);
+
+ for (i = 0; i < size; i++)
+ gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]);
+
+ gg_debug(GG_DEBUG_MISC, "\n");
+}
+#else
+#define gg_dcc_debug_data(a,b,c,d) do { } while (0)
+#endif
+
+/**
+ * Wysyła żądanie zwrotnego połączenia bezpośredniego.
+ *
+ * Funkcję wykorzystuje się, jeśli nie ma możliwości połączenia się z odbiorcą
+ * pliku lub rozmowy głosowej. Po otrzymaniu żądania druga strona spróbuje
+ * nawiązać zwrotne połączenie bezpośrednie z nadawcą.
+ * gg_dcc_request()
+ *
+ * \param sess Struktura sesji
+ * \param uin Numer odbiorcy
+ *
+ * \return Patrz \c gg_send_message_ctcp()
+ *
+ * \ingroup dcc6
+ */
+int gg_dcc_request(struct gg_session *sess, uin_t uin)
+{
+ return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, (unsigned char*) "\002", 1);
+}
+
+/**
+ * \internal Zamienia znacznik czasu w postaci uniksowej na format API WIN32.
+ *
+ * \note Funkcja działa jedynie gdy kompilator obsługuje typ danych
+ * \c long \c long.
+ *
+ * \param ut Czas w postaci uniksowej
+ * \param ft Czas w postaci API WIN32
+ */
+static void gg_dcc_fill_filetime(time_t ut, uint32_t *ft)
+{
+#ifdef GG_CONFIG_HAVE_LONG_LONG
+ unsigned long long tmp;
+
+ tmp = ut;
+ tmp += 11644473600LL;
+ tmp *= 10000000LL;
+
+#ifndef GG_CONFIG_BIGENDIAN
+ ft[0] = (uint32_t) tmp;
+ ft[1] = (uint32_t) (tmp >> 32);
+#else
+ ft[0] = gg_fix32((uint32_t) (tmp >> 32));
+ ft[1] = gg_fix32((uint32_t) tmp);
+#endif
+
+#endif
+}
+
+/**
+ * Wypełnia pola struktury \c gg_dcc niezbędne do wysłania pliku.
+ *
+ * \note Większą funkcjonalność zapewnia funkcja \c gg_dcc_fill_file_info2().
+ *
+ * \param d Struktura połączenia
+ * \param filename Nazwa pliku
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename)
+{
+ return gg_dcc_fill_file_info2(d, filename, filename);
+}
+
+/**
+ * Wypełnia pola struktury \c gg_dcc niezbędne do wysłania pliku.
+ *
+ * \param d Struktura połączenia
+ * \param filename Nazwa pliku zapisywana w strukturze
+ * \param local_filename Nazwa pliku w lokalnym systemie plikĂłw
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename)
+{
+ struct stat st;
+ const char *name, *ext, *p;
+ unsigned char *q;
+ int i, j;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename);
+
+ if (!d || d->type != GG_SESSION_DCC_SEND) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (stat(local_filename, &st) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno));
+ return -1;
+ }
+
+ if ((st.st_mode & S_IFDIR)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((d->file_fd = open(local_filename, O_RDONLY | O_BINARY)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&d->file_info, 0, sizeof(d->file_info));
+
+ if (!(st.st_mode & S_IWUSR))
+ d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY);
+
+ gg_dcc_fill_filetime(st.st_atime, d->file_info.atime);
+ gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime);
+ gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime);
+
+ d->file_info.size = gg_fix32(st.st_size);
+ d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */
+
+#ifdef _WIN32
+ if (!(name = strrchr(filename, '\\')))
+#else
+ if (!(name = strrchr(filename, '/')))
+#endif
+ name = filename;
+ else
+ name++;
+
+ if (!(ext = strrchr(name, '.')))
+ ext = name + strlen(name);
+
+ for (i = 0, p = name; i < 8 && p < ext; i++, p++)
+ d->file_info.short_filename[i] = toupper(name[i]);
+
+ if (i == 8 && p < ext) {
+ d->file_info.short_filename[6] = '~';
+ d->file_info.short_filename[7] = '1';
+ }
+
+ if (strlen(ext) > 0) {
+ for (j = 0; *ext && j < 4; j++, p++)
+ d->file_info.short_filename[i + j] = toupper(ext[j]);
+ }
+
+ for (q = d->file_info.short_filename; *q; q++) {
+ if (*q == 185) {
+ *q = 165;
+ } else if (*q == 230) {
+ *q = 198;
+ } else if (*q == 234) {
+ *q = 202;
+ } else if (*q == 179) {
+ *q = 163;
+ } else if (*q == 241) {
+ *q = 209;
+ } else if (*q == 243) {
+ *q = 211;
+ } else if (*q == 156) {
+ *q = 140;
+ } else if (*q == 159) {
+ *q = 143;
+ } else if (*q == 191) {
+ *q = 175;
+ }
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename);
+ strncpy((char*) d->file_info.filename, name, sizeof(d->file_info.filename) - 1);
+
+ return 0;
+}
+
+/**
+ * \internal Rozpoczyna połączenie bezpośrednie z danym klientem.
+ *
+ * \param ip Adres IP odbiorcy
+ * \param port Port odbiorcy
+ * \param my_uin WĹ‚asny numer
+ * \param peer_uin Numer odbiorcy
+ * \param type Rodzaj połączenia (\c GG_SESSION_DCC_SEND lub \c GG_SESSION_DCC_GET)
+ *
+ * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu
+ */
+static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type)
+{
+ struct gg_dcc *d = NULL;
+ struct in_addr addr;
+
+ addr.s_addr = ip;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET");
+
+ if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!(d = (void*) calloc(1, sizeof(*d)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n");
+ return NULL;
+ }
+
+ d->check = GG_CHECK_WRITE;
+ d->state = GG_STATE_CONNECTING;
+ d->type = type;
+ d->timeout = GG_DEFAULT_TIMEOUT;
+ d->file_fd = -1;
+ d->active = 1;
+ d->fd = -1;
+ d->uin = my_uin;
+ d->peer_uin = peer_uin;
+
+ if ((d->fd = gg_connect(&addr, port, 1)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n");
+ free(d);
+ return NULL;
+ }
+
+ return d;
+}
+
+/**
+ * Rozpoczyna odbieranie pliku przez zwrotne połączenie bezpośrednie.
+ *
+ * \param ip Adres IP nadawcy
+ * \param port Port nadawcy
+ * \param my_uin WĹ‚asny numer
+ * \param peer_uin Numer nadawcy
+ *
+ * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_get_file() handing over to gg_dcc_transfer()\n");
+
+ return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_GET);
+}
+
+/**
+ * Rozpoczyna wysyłanie pliku.
+ *
+ * \param ip Adres IP odbiorcy
+ * \param port Port odbiorcy
+ * \param my_uin WĹ‚asny numer
+ * \param peer_uin Numer odbiorcy
+ *
+ * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_send_file() handing over to gg_dcc_transfer()\n");
+
+ return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_SEND);
+}
+
+/**
+ * Rozpoczyna połączenie głosowe.
+ *
+ * \param ip Adres IP odbiorcy
+ * \param port Port odbiorcy
+ * \param my_uin WĹ‚asny numer
+ * \param peer_uin Numer odbiorcy
+ *
+ * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_chat() handing over to gg_dcc_transfer()\n");
+
+ return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_VOICE);
+}
+
+/**
+ * Ustawia typ przychodzącego połączenia bezpośredniego.
+ *
+ * Funkcję należy wywołać po otrzymaniu zdarzenia \c GG_EVENT_DCC_CALLBACK.
+ *
+ * \param d Struktura połączenia
+ * \param type Rodzaj połączenia (\c GG_SESSION_DCC_SEND lub
+ * \c GG_SESSION_DCC_VOICE)
+ *
+ * \ingroup dcc6
+ */
+void gg_dcc_set_type(struct gg_dcc *d, int type)
+{
+ d->type = type;
+ d->state = (type == GG_SESSION_DCC_SEND) ? GG_STATE_SENDING_FILE_INFO : GG_STATE_SENDING_VOICE_REQUEST;
+}
+
+/**
+ * \internal Funkcja zwrotna połączenia bezpośredniego.
+ *
+ * Pole \c callback struktury \c gg_dcc zawiera wskaĹşnik do tej funkcji.
+ * Wywołuje ona \c gg_watch_fd() i zachowuje wynik w polu \c event.
+ *
+ * \note Funkcjonalność funkcjo zwrotnej nie jest już wspierana.
+ *
+ * \param d Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc_callback(struct gg_dcc *d)
+{
+ struct gg_event *e = gg_dcc_watch_fd(d);
+
+ d->event = e;
+
+ return (e != NULL) ? 0 : -1;
+}
+
+/**
+ * Tworzy gniazdo nasłuchujące dla połączeń bezpośrednich.
+ *
+ * Funkcja przywiÄ…zuje gniazdo do pierwszego wolnego portu TCP.
+ *
+ * \param uin WĹ‚asny numer
+ * \param port Preferowany port (jeśli równy 0 lub -1, próbuje się domyślnego)
+ *
+ * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port)
+{
+ struct gg_dcc *c;
+ struct sockaddr_in sin;
+ SOCKET sock;
+ int bound = 0, errno2;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port);
+
+ if (!uin) {
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno));
+ return NULL;
+ }
+
+ if (port == 0 || port == -1)
+ port = GG_DEFAULT_DCC_PORT;
+
+ while (!bound) {
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = htons(port);
+
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port);
+ if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin)))
+ bound = 1;
+ else {
+ if (++port == 65535) {
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n");
+ gg_sock_close(sock);
+ return NULL;
+ }
+ }
+ }
+
+ if (listen(sock, 10)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno));
+ errno2 = errno;
+ gg_sock_close(sock);
+ errno = errno2;
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port);
+
+ if (!(c = malloc(sizeof(*c)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n");
+ gg_sock_close(sock);
+ return NULL;
+ }
+ memset(c, 0, sizeof(*c));
+
+ c->port = c->id = port;
+ c->fd = sock;
+ c->type = GG_SESSION_DCC_SOCKET;
+ c->uin = uin;
+ c->timeout = -1;
+ c->state = GG_STATE_LISTENING;
+ c->check = GG_CHECK_READ;
+ c->callback = gg_dcc_callback;
+ c->destroy = gg_dcc_free;
+
+ return c;
+}
+
+/**
+ * Wysyła ramkę danych połączenia głosowego.
+ *
+ * \param d Struktura połączenia
+ * \param buf Bufor z danymi
+ * \param length Długość bufora z danymi
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc6
+ */
+int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length)
+{
+ struct packet_s {
+ uint8_t type;
+ uint32_t length;
+ } GG_PACKED;
+ struct packet_s packet;
+
+ gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length);
+ if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ packet.type = 0x03; /* XXX */
+ packet.length = gg_fix32(length);
+
+ if (gg_sock_write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n");
+ return -1;
+ }
+ gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet));
+
+ if (gg_sock_write(d->fd, buf, length) < length) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n");
+ return -1;
+ }
+ gg_dcc_debug_data("write", d->fd, buf, length);
+
+ return 0;
+}
+
+/**
+ * \internal Odbiera dane z połączenia bezpośredniego z obsługą błędów.
+ *
+ * \param fd Deskryptor gniazda
+ * \param buf Bufor na dane
+ * \param size Rozmiar bufora na dane
+ */
+#define gg_dcc_read(fd, buf, size) \
+{ \
+ int tmp = gg_sock_read(fd, buf, size); \
+ \
+ if (tmp < (int) size) { \
+ if (tmp == -1) { \
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \
+ } else if (tmp == 0) { \
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \
+ } else { \
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \
+ } \
+ e->type = GG_EVENT_DCC_ERROR; \
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \
+ return e; \
+ } \
+ gg_dcc_debug_data("read", fd, buf, size); \
+}
+
+/**
+ * \internal Wysyła dane do połączenia bezpośredniego z obsługą błędów.
+ *
+ * \param fd Deskryptor gniazda
+ * \param buf Bufor z danymi
+ * \param size Rozmiar bufora z danymi
+ */
+#define gg_dcc_write(fd, buf, size) \
+{ \
+ int tmp; \
+ gg_dcc_debug_data("write", fd, buf, size); \
+ tmp = gg_sock_write(fd, buf, size); \
+ if (tmp < (int) size) { \
+ if (tmp == -1) { \
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \
+ } else { \
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \
+ } \
+ e->type = GG_EVENT_DCC_ERROR; \
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \
+ return e; \
+ } \
+}
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia
+ * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania.
+ * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free.
+ *
+ * \param h Struktura połączenia
+ *
+ * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd
+ *
+ * \ingroup dcc6
+ */
+struct gg_event *gg_dcc_watch_fd(struct gg_dcc *h)
+{
+ struct gg_event *e;
+ int foo;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_watch_fd(%p);\n", h);
+
+ if (!h || (h->type != GG_SESSION_DCC && h->type != GG_SESSION_DCC_SOCKET && h->type != GG_SESSION_DCC_SEND && h->type != GG_SESSION_DCC_GET && h->type != GG_SESSION_DCC_VOICE)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid argument\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!(e = (void*) calloc(1, sizeof(*e)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory\n");
+ return NULL;
+ }
+
+ e->type = GG_EVENT_NONE;
+
+ if (h->type == GG_SESSION_DCC_SOCKET) {
+ struct sockaddr_in sin;
+ struct gg_dcc *c;
+ SOCKET fd;
+ int one = 1;
+ unsigned int sin_len = sizeof(sin);
+
+ if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno));
+ return e;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
+
+#ifdef FIONBIO
+ if (ioctl(fd, FIONBIO, &one) == -1) {
+#else
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+#endif
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno));
+ gg_sock_close(fd);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ return e;
+ }
+
+ if (!(c = (void*) calloc(1, sizeof(*c)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n");
+
+ free(e);
+ gg_sock_close(fd);
+ return NULL;
+ }
+
+ c->fd = fd;
+ c->check = GG_CHECK_READ;
+ c->state = GG_STATE_READING_UIN_1;
+ c->type = GG_SESSION_DCC;
+ c->timeout = GG_DEFAULT_TIMEOUT;
+ c->file_fd = -1;
+ c->remote_addr = sin.sin_addr.s_addr;
+ c->remote_port = ntohs(sin.sin_port);
+
+ e->type = GG_EVENT_DCC_NEW;
+ e->event.dcc_new = c;
+
+ return e;
+ } else {
+ struct gg_dcc_tiny_packet tiny;
+ struct gg_dcc_small_packet small;
+ struct gg_dcc_big_packet big;
+ int size, tmp, res;
+ unsigned int utmp, res_size = sizeof(res);
+ char buf[1024], ack[] = "UDAG";
+
+ struct gg_dcc_file_info_packet {
+ struct gg_dcc_big_packet big;
+ struct gg_file_info file_info;
+ } GG_PACKED;
+ struct gg_dcc_file_info_packet file_info_packet;
+
+ switch (h->state) {
+ case GG_STATE_READING_UIN_1:
+ case GG_STATE_READING_UIN_2:
+ {
+ uin_t uin;
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2);
+
+ gg_dcc_read(h->fd, &uin, sizeof(uin));
+
+ if (h->state == GG_STATE_READING_UIN_1) {
+ h->state = GG_STATE_READING_UIN_2;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->peer_uin = gg_fix32(uin);
+ } else {
+ h->state = GG_STATE_SENDING_ACK;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->uin = gg_fix32(uin);
+ e->type = GG_EVENT_DCC_CLIENT_ACCEPT;
+ }
+
+ return e;
+ }
+
+ case GG_STATE_SENDING_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_SENDING_ACK\n");
+
+ gg_dcc_write(h->fd, ack, 4);
+
+ h->state = GG_STATE_READING_TYPE;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+
+ case GG_STATE_READING_TYPE:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n");
+
+ gg_dcc_read(h->fd, &small, sizeof(small));
+
+ small.type = gg_fix32(small.type);
+
+ switch (small.type) {
+ case 0x0003: /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n");
+ h->type = GG_SESSION_DCC_SEND;
+ h->state = GG_STATE_SENDING_FILE_INFO;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ e->type = GG_EVENT_DCC_CALLBACK;
+
+ break;
+
+ case 0x0002: /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n");
+ h->type = GG_SESSION_DCC_GET;
+ h->state = GG_STATE_READING_REQUEST;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->incoming = 1;
+
+ break;
+
+ default:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ }
+
+ return e;
+
+ case GG_STATE_READING_REQUEST:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n");
+
+ gg_dcc_read(h->fd, &small, sizeof(small));
+
+ small.type = gg_fix32(small.type);
+
+ switch (small.type) {
+ case 0x0001: /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n");
+ h->state = GG_STATE_READING_FILE_INFO;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ break;
+
+ case 0x0003: /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n");
+ h->state = GG_STATE_SENDING_VOICE_ACK;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DCC_TIMEOUT_VOICE_ACK;
+ h->type = GG_SESSION_DCC_VOICE;
+ e->type = GG_EVENT_DCC_NEED_VOICE_ACK;
+
+ break;
+
+ default:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ }
+
+ return e;
+
+ case GG_STATE_READING_FILE_INFO:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n");
+
+ gg_dcc_read(h->fd, &file_info_packet, sizeof(file_info_packet));
+
+ memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info));
+
+ h->file_info.mode = gg_fix32(h->file_info.mode);
+ h->file_info.size = gg_fix32(h->file_info.size);
+
+ h->state = GG_STATE_SENDING_FILE_ACK;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DCC_TIMEOUT_FILE_ACK;
+
+ e->type = GG_EVENT_DCC_NEED_FILE_ACK;
+
+ return e;
+
+ case GG_STATE_SENDING_FILE_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n");
+
+ big.type = gg_fix32(0x0006); /* XXX */
+ big.dunno1 = gg_fix32(h->offset);
+ big.dunno2 = 0;
+
+ gg_dcc_write(h->fd, &big, sizeof(big));
+
+ h->state = GG_STATE_READING_FILE_HEADER;
+ h->chunk_size = sizeof(big);
+ h->chunk_offset = 0;
+ if (!(h->chunk_buf = malloc(sizeof(big)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n");
+ free(e);
+ return NULL;
+ }
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+
+ case GG_STATE_SENDING_VOICE_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n");
+
+ tiny.type = 0x01; /* XXX */
+
+ gg_dcc_write(h->fd, &tiny, sizeof(tiny));
+
+ h->state = GG_STATE_READING_VOICE_HEADER;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ h->offset = 0;
+
+ return e;
+
+ case GG_STATE_READING_FILE_HEADER:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n");
+
+ tmp = gg_sock_read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset);
+
+ if (tmp == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno));
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+ return e;
+ }
+
+ gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset);
+
+ h->chunk_offset += tmp;
+
+ if (h->chunk_offset < h->chunk_size)
+ return e;
+
+ memcpy(&big, h->chunk_buf, sizeof(big));
+ free(h->chunk_buf);
+ h->chunk_buf = NULL;
+
+ big.type = gg_fix32(big.type);
+ h->chunk_size = gg_fix32(big.dunno1);
+ h->chunk_offset = 0;
+
+ if (big.type == 0x0005) { /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n");
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_REFUSED;
+ return e;
+ }
+
+ if (h->chunk_size == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n");
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+
+ h->state = GG_STATE_GETTING_FILE;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->established = 1;
+
+ return e;
+
+ case GG_STATE_READING_VOICE_HEADER:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n");
+
+ gg_dcc_read(h->fd, &tiny, sizeof(tiny));
+
+ switch (tiny.type) {
+ case 0x03: /* XXX */
+ h->state = GG_STATE_READING_VOICE_SIZE;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->established = 1;
+ break;
+ case 0x04: /* XXX */
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n");
+ /* XXX zwracać odpowiedni event */
+ default:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ }
+
+ return e;
+
+ case GG_STATE_READING_VOICE_SIZE:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n");
+
+ gg_dcc_read(h->fd, &small, sizeof(small));
+
+ small.type = gg_fix32(small.type);
+
+ if (small.type < 16 || small.type > sizeof(buf)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+
+ return e;
+ }
+
+ h->chunk_size = small.type;
+ h->chunk_offset = 0;
+
+ if (!(h->voice_buf = malloc(h->chunk_size))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n");
+ free(e);
+ return NULL;
+ }
+
+ h->state = GG_STATE_READING_VOICE_DATA;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+
+ case GG_STATE_READING_VOICE_DATA:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n");
+
+ tmp = gg_sock_read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset);
+ if (tmp < 1) {
+ if (tmp == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno));
+ } else {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n");
+ }
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+ return e;
+ }
+
+ gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp);
+
+ h->chunk_offset += tmp;
+
+ if (h->chunk_offset >= h->chunk_size) {
+ e->type = GG_EVENT_DCC_VOICE_DATA;
+ e->event.dcc_voice_data.data = (unsigned char*) h->voice_buf;
+ e->event.dcc_voice_data.length = h->chunk_size;
+ h->state = GG_STATE_READING_VOICE_HEADER;
+ h->voice_buf = NULL;
+ }
+
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+
+ case GG_STATE_CONNECTING:
+ {
+ uin_t uins[2];
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_CONNECTING\n");
+
+ res = 0;
+ if ((foo = gg_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size)) || res) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connection failed (fd=%d,errno=%d(%s),foo=%d,res=%d(%s))\n", h->fd, errno, strerror(errno), foo, res, strerror(res));
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ return e;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connected, sending uins\n");
+
+ uins[0] = gg_fix32(h->uin);
+ uins[1] = gg_fix32(h->peer_uin);
+
+ gg_dcc_write(h->fd, uins, sizeof(uins));
+
+ h->state = GG_STATE_READING_ACK;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+ }
+
+ case GG_STATE_READING_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_ACK\n");
+
+ gg_dcc_read(h->fd, buf, 4);
+
+ if (strncmp(buf, ack, 4)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n");
+
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+ return e;
+ }
+
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->state = GG_STATE_SENDING_REQUEST;
+
+ return e;
+
+ case GG_STATE_SENDING_VOICE_REQUEST:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n");
+
+ small.type = gg_fix32(0x0003);
+
+ gg_dcc_write(h->fd, &small, sizeof(small));
+
+ h->state = GG_STATE_READING_VOICE_ACK;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+
+ case GG_STATE_SENDING_REQUEST:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n");
+
+ small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */
+
+ gg_dcc_write(h->fd, &small, sizeof(small));
+
+ switch (h->type) {
+ case GG_SESSION_DCC_GET:
+ h->state = GG_STATE_READING_REQUEST;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ break;
+
+ case GG_SESSION_DCC_SEND:
+ h->state = GG_STATE_SENDING_FILE_INFO;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ if (h->file_fd == -1)
+ e->type = GG_EVENT_DCC_NEED_FILE_INFO;
+ break;
+
+ case GG_SESSION_DCC_VOICE:
+ h->state = GG_STATE_SENDING_VOICE_REQUEST;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ break;
+ }
+
+ return e;
+
+ case GG_STATE_SENDING_FILE_INFO:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n");
+
+ if (h->file_fd == -1) {
+ e->type = GG_EVENT_DCC_NEED_FILE_INFO;
+ return e;
+ }
+
+ small.type = gg_fix32(0x0001); /* XXX */
+
+ gg_dcc_write(h->fd, &small, sizeof(small));
+
+ file_info_packet.big.type = gg_fix32(0x0003); /* XXX */
+ file_info_packet.big.dunno1 = 0;
+ file_info_packet.big.dunno2 = 0;
+
+ memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info));
+
+ /* zostają teraz u nas, więc odwracamy z powrotem */
+ h->file_info.size = gg_fix32(h->file_info.size);
+ h->file_info.mode = gg_fix32(h->file_info.mode);
+
+ gg_dcc_write(h->fd, &file_info_packet, sizeof(file_info_packet));
+
+ h->state = GG_STATE_READING_FILE_ACK;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DCC_TIMEOUT_FILE_ACK;
+
+ return e;
+
+ case GG_STATE_READING_FILE_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_ACK\n");
+
+ gg_dcc_read(h->fd, &big, sizeof(big));
+
+ /* XXX sprawdzać wynik */
+ h->offset = gg_fix32(big.dunno1);
+
+ h->state = GG_STATE_SENDING_FILE_HEADER;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ e->type = GG_EVENT_DCC_ACK;
+
+ return e;
+
+ case GG_STATE_READING_VOICE_ACK:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n");
+
+ gg_dcc_read(h->fd, &tiny, sizeof(tiny));
+
+ if (tiny.type != 0x01) {
+ gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type);
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_REFUSED;
+ return e;
+ }
+
+ h->state = GG_STATE_READING_VOICE_HEADER;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ e->type = GG_EVENT_DCC_ACK;
+
+ return e;
+
+ case GG_STATE_SENDING_FILE_HEADER:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n");
+
+ h->chunk_offset = 0;
+
+ if ((h->chunk_size = h->file_info.size - h->offset) > 4096) {
+ h->chunk_size = 4096;
+ big.type = gg_fix32(0x0003); /* XXX */
+ } else
+ big.type = gg_fix32(0x0002); /* XXX */
+
+ big.dunno1 = gg_fix32(h->chunk_size);
+ big.dunno2 = 0;
+
+ gg_dcc_write(h->fd, &big, sizeof(big));
+
+ h->state = GG_STATE_SENDING_FILE;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->established = 1;
+
+ return e;
+
+ case GG_STATE_SENDING_FILE:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n");
+
+ if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf))
+ utmp = sizeof(buf);
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size);
+
+ /* koniec pliku? */
+ if (h->file_info.size == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n");
+ e->type = GG_EVENT_DCC_DONE;
+
+ return e;
+ }
+
+ if (h->offset >= h->file_info.size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n");
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+
+ lseek(h->file_fd, h->offset, SEEK_SET);
+
+ size = read(h->file_fd, buf, utmp);
+
+ /* błąd */
+ if (size == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno));
+
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_FILE;
+
+ return e;
+ }
+
+ /* koniec pliku? */
+ if (size == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n");
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_EOF;
+
+ return e;
+ }
+
+ /* jeśli wczytaliśmy więcej, utnijmy. */
+ if (h->offset + size > h->file_info.size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size);
+ size = h->file_info.size - h->offset;
+
+ if (size < 1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n");
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+ }
+
+ tmp = gg_sock_write(h->fd, buf, size);
+
+ if (tmp == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno));
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+ return e;
+ }
+
+ if (tmp == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (connection reset)\n");
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+ return e;
+ }
+
+ h->offset += tmp;
+
+ if (h->offset >= h->file_info.size) {
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+
+ h->chunk_offset += tmp;
+
+ if (h->chunk_offset >= h->chunk_size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n");
+ h->state = GG_STATE_SENDING_FILE_HEADER;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ } else {
+ h->state = GG_STATE_SENDING_FILE;
+ h->timeout = GG_DCC_TIMEOUT_SEND;
+ }
+
+ h->check = GG_CHECK_WRITE;
+
+ return e;
+
+ case GG_STATE_GETTING_FILE:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_GETTING_FILE\n");
+
+ if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf))
+ utmp = sizeof(buf);
+
+ if (h->offset >= h->file_info.size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n");
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+
+ size = gg_sock_read(h->fd, buf, utmp);
+
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size);
+
+ /* błąd */
+ if (size == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno));
+
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+
+ return e;
+ }
+
+ /* koniec? */
+ if (size == 0) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n");
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_EOF;
+
+ return e;
+ }
+
+ tmp = write(h->file_fd, buf, size);
+
+ if (tmp == -1 || tmp < size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno));
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_NET;
+ return e;
+ }
+
+ h->offset += size;
+
+ if (h->offset >= h->file_info.size) {
+ e->type = GG_EVENT_DCC_DONE;
+ return e;
+ }
+
+ h->chunk_offset += size;
+
+ if (h->chunk_offset >= h->chunk_size) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n");
+ h->state = GG_STATE_READING_FILE_HEADER;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ h->chunk_offset = 0;
+ h->chunk_size = sizeof(big);
+ if (!(h->chunk_buf = malloc(sizeof(big)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n");
+ free(e);
+ return NULL;
+ }
+ } else {
+ h->state = GG_STATE_GETTING_FILE;
+ h->timeout = GG_DCC_TIMEOUT_GET;
+ }
+
+ h->check = GG_CHECK_READ;
+
+ return e;
+
+ default:
+ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n");
+ e->type = GG_EVENT_DCC_ERROR;
+ e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE;
+
+ return e;
+ }
+ }
+
+ return e;
+}
+
+/**
+ * Zwalnia zasoby używane przez połączenie bezpośrednie.
+ *
+ * \param d Struktura połączenia
+ *
+ * \ingroup dcc6
+ */
+void gg_dcc_free(struct gg_dcc *d)
+{
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d);
+
+ if (!d)
+ return;
+
+ if (d->fd != -1)
+ gg_sock_close(d->fd);
+
+ free(d->chunk_buf);
+ free(d);
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/dcc7.c b/protocols/Gadu-Gadu/src/libgadu/dcc7.c
new file mode 100644
index 0000000000..f3813e6e83
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/dcc7.c
@@ -0,0 +1,1655 @@
+/* coding: UTF-8 */
+/* $Id: dcc7.c,v 1.2 2007-07-20 23:00:49 wojtekka Exp $ */
+
+/*
+ * (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Tomasz Chiliński <chilek@chilan.com>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ * Bartłomiej Zimoń <uzi18@o2.pl>
+ *
+ * Thanks to Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110,
+ * USA.
+ */
+
+/**
+ * \file dcc7.c
+ *
+ * \brief Obsługa połączeń bezpośrednich od wersji Gadu-Gadu 7.x
+ */
+
+#ifndef _WIN64
+#define _USE_32BIT_TIME_T
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef sun
+# include <sys/filio.h>
+#endif
+#endif /* _WIN32 */
+#include <time.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include "compat.h"
+#include "libgadu.h"
+#include "protocol.h"
+#include "resolver.h"
+#include "internal.h"
+
+/**
+ * \internal Dodaje połączenie bezpośrednie do sesji.
+ *
+ * \param sess Struktura sesji
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_session_add(struct gg_session *sess, struct gg_dcc7 *dcc)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_add(%p, %p)\n", sess, dcc);
+
+ if (!sess || !dcc || dcc->next) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_add() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ dcc->next = sess->dcc7_list;
+ sess->dcc7_list = dcc;
+
+ return 0;
+}
+
+/**
+ * \internal Usuwa połączenie bezpośrednie z sesji.
+ *
+ * \param sess Struktura sesji
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_session_remove(struct gg_session *sess, struct gg_dcc7 *dcc)
+{
+ struct gg_dcc7 *tmp;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_remove(%p, %p)\n", sess, dcc);
+
+ if (sess == NULL || dcc == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (sess->dcc7_list == dcc) {
+ sess->dcc7_list = dcc->next;
+ dcc->next = NULL;
+ return 0;
+ }
+
+ for (tmp = sess->dcc7_list; tmp != NULL; tmp = tmp->next) {
+ if (tmp->next == dcc) {
+ tmp->next = dcc->next;
+ dcc->next = NULL;
+ return 0;
+ }
+ }
+
+ errno = ENOENT;
+ return -1;
+}
+
+/**
+ * \internal Zwraca strukturę połączenia o danym identyfikatorze.
+ *
+ * \param sess Struktura sesji
+ * \param id Identyfikator połączenia
+ * \param uin Numer nadawcy lub odbiorcy
+ *
+ * \return Struktura połączenia lub \c NULL jeśli nie znaleziono
+ */
+static struct gg_dcc7 *gg_dcc7_session_find(struct gg_session *sess, gg_dcc7_id_t id, uin_t uin)
+{
+ struct gg_dcc7 *tmp;
+ int empty;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_find(%p, ..., %d)\n", sess, (int) uin);
+
+ empty = !memcmp(&id, "\0\0\0\0\0\0\0\0", 8);
+
+ for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) {
+ if (empty) {
+ if (tmp->peer_uin == uin && !tmp->state == GG_STATE_WAITING_FOR_ACCEPT)
+ return tmp;
+ } else {
+ if (!memcmp(&tmp->cid, &id, sizeof(id)))
+ return tmp;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * \internal Rozpoczyna proces pobierania adresu
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_get_relay_addr(struct gg_dcc7 *dcc)
+{
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_get_relay_addr(%p)\n", dcc);
+
+ if (dcc == NULL || dcc->sess == NULL) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dcc->sess->resolver_start(&dcc->fd, &dcc->resolver, GG_RELAY_HOST) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+ return -1;
+ }
+
+ dcc->state = GG_STATE_RESOLVING_RELAY;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+
+ return 0;
+}
+
+/**
+ * \internal Nawiązuje połączenie bezpośrednie
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_connect(struct gg_dcc7 *dcc)
+{
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p)\n", dcc);
+
+ if (dcc == NULL) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((dcc->fd = gg_connect(&dcc->remote_addr, dcc->remote_port, 1)) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n");
+ return -1;
+ }
+
+ dcc->state = GG_STATE_CONNECTING;
+ dcc->check = GG_CHECK_WRITE;
+ dcc->timeout = GG_DCC7_TIMEOUT_CONNECT;
+ dcc->soft_timeout = 1;
+
+ return 0;
+}
+
+/**
+ * \internal Tworzy gniazdo nasłuchujące dla połączenia bezpośredniego
+ *
+ * \param dcc Struktura połączenia
+ * \param port Preferowany port (jeśli równy 0 lub -1, próbuje się domyślnego)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_listen(struct gg_dcc7 *dcc, uint16_t port)
+{
+ struct sockaddr_in sin;
+ SOCKET fd;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_listen(%p, %d)\n", dcc, port);
+
+ if (!dcc) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() can't create socket (%s)\n", strerror(errno));
+ return -1;
+ }
+
+ // XXX losować porty?
+
+ if (!port)
+ port = GG_DEFAULT_DCC_PORT;
+
+ while (1) {
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = htons(port);
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() trying port %d\n", port);
+
+ if (!bind(fd, (struct sockaddr*) &sin, sizeof(sin)))
+ break;
+
+ if (port++ == 65535) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() no free port found\n");
+ gg_sock_close(fd);
+ errno = ENOENT;
+ return -1;
+ }
+ }
+
+ if (listen(fd, 1)) {
+ int errsv = errno;
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to listen (%s)\n", strerror(errno));
+ gg_sock_close(fd);
+ errno = errsv;
+ return -1;
+ }
+
+ dcc->fd = fd;
+ dcc->local_port = port;
+
+ dcc->state = GG_STATE_LISTENING;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DCC7_TIMEOUT_FILE_ACK;
+
+ return 0;
+}
+
+/**
+ * \internal Tworzy gniazdo nasłuchujące i wysyła jego parametry
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_listen_and_send_info(struct gg_dcc7 *dcc)
+{
+ struct gg_dcc7_info pkt;
+ uint16_t external_port;
+ uint16_t local_port;
+ uint32_t count;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc);
+
+ if (!dcc->sess->client_port)
+ local_port = dcc->sess->external_port;
+ else
+ local_port = dcc->sess->client_port;
+
+ if (gg_dcc7_listen(dcc, local_port) == -1)
+ return -1;
+
+ if (!dcc->sess->external_port || dcc->local_port != local_port)
+ external_port = dcc->local_port;
+ else
+ external_port = dcc->sess->external_port;
+
+ if (!dcc->sess->external_addr || dcc->local_port != local_port)
+ dcc->local_addr = dcc->sess->client_addr;
+ else
+ dcc->local_addr = dcc->sess->external_addr;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// dcc7_listen_and_send_info() sending IP address %s and port %d\n", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port);
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.uin = gg_fix32(dcc->peer_uin);
+ pkt.type = GG_DCC7_TYPE_P2P;
+ pkt.id = dcc->cid;
+ snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port);
+ // TODO: implement hash count
+ // we MUST fill hash to recive from server request for server connection
+ //snprintf((char*) pkt.hash, sizeof(pkt.hash), "0");
+ count = dcc->local_addr + external_port * rand();
+ snprintf((char*) pkt.hash, sizeof(pkt.hash), "%d", count);
+
+ return gg_send_packet(dcc->sess, GG_DCC7_INFO, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * \internal Odwraca połączenie po nieudanym connect()
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_reverse_connect(struct gg_dcc7 *dcc)
+{
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_reverse_connect(%p)\n", dcc);
+
+ if (dcc->reverse) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() already reverse connection\n");
+ return -1;
+ }
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() timeout, trying reverse connection\n");
+ gg_sock_close(dcc->fd);
+ dcc->fd = -1;
+ dcc->reverse = 1;
+
+ return gg_dcc7_listen_and_send_info(dcc);
+}
+
+/**
+ * \internal Wysyła do serwera żądanie nadania identyfikatora sesji
+ *
+ * \param sess Struktura sesji
+ * \param type Rodzaj połączenia (\c GG_DCC7_TYPE_FILE lub \c GG_DCC7_TYPE_VOICE)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_request_id(struct gg_session *sess, uint32_t type)
+{
+ struct gg_dcc7_id_request pkt;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_request_id(%p, %d)\n", sess, type);
+
+ if (!sess) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid parameters\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() not connected\n");
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (type != GG_DCC7_TYPE_VOICE && type != GG_DCC7_TYPE_FILE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid transfer type (%d)\n", type);
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.type = gg_fix32(type);
+
+ return gg_send_packet(sess, GG_DCC7_ID_REQUEST, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * \internal Rozpoczyna wysyłanie pliku.
+ *
+ * Funkcja jest wykorzystywana przez \c gg_dcc7_send_file() oraz
+ * \c gg_dcc_send_file_fd().
+ *
+ * \param sess Struktura sesji
+ * \param rcpt Numer odbiorcy
+ * \param fd Deskryptor pliku
+ * \param size Rozmiar pliku
+ * \param filename1250 Nazwa pliku w kodowaniu CP-1250
+ * \param hash SkrĂłt SHA-1 pliku
+ * \param seek Flaga mówiąca, czy można używać lseek()
+ *
+ * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+static struct gg_dcc7 *gg_dcc7_send_file_common(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash, int seek)
+{
+ struct gg_dcc7 *dcc = NULL;
+
+ if (!sess || !rcpt || !filename1250 || !hash || fd == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() invalid parameters\n");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (!(dcc = malloc(sizeof(struct gg_dcc7)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() not enough memory\n");
+ goto fail;
+ }
+
+ if (gg_dcc7_request_id(sess, GG_DCC7_TYPE_FILE) == -1)
+ goto fail;
+
+ memset(dcc, 0, sizeof(struct gg_dcc7));
+ dcc->type = GG_SESSION_DCC7_SEND;
+ dcc->dcc_type = GG_DCC7_TYPE_FILE;
+ dcc->state = GG_STATE_REQUESTING_ID;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ dcc->sess = sess;
+ dcc->fd = -1;
+ dcc->uin = sess->uin;
+ dcc->peer_uin = rcpt;
+ dcc->file_fd = fd;
+ dcc->size = (unsigned int)size;
+ dcc->seek = seek;
+
+ strncpy((char*) dcc->filename, filename1250, GG_DCC7_FILENAME_LEN - 1);
+ dcc->filename[GG_DCC7_FILENAME_LEN] = 0;
+
+ memcpy(dcc->hash, hash, GG_DCC7_HASH_LEN);
+
+ if (gg_dcc7_session_add(sess, dcc) == -1)
+ goto fail;
+
+ return dcc;
+
+fail:
+ free(dcc);
+ return NULL;
+}
+
+/**
+ * Rozpoczyna wysyłanie pliku o danej nazwie.
+ *
+ * \param sess Struktura sesji
+ * \param rcpt Numer odbiorcy
+ * \param filename Nazwa pliku w lokalnym systemie plikĂłw
+ * \param filename1250 Nazwa pliku w kodowaniu CP-1250
+ * \param hash Skrót SHA-1 pliku (lub \c NULL jeśli ma być wyznaczony)
+ *
+ * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash)
+{
+ struct gg_dcc7 *dcc = NULL;
+ const char *tmp;
+ char hash_buf[GG_DCC7_HASH_LEN];
+ struct stat st;
+ int fd = -1;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file(%p, %d, \"%s\", %p)\n", sess, rcpt, filename, hash);
+
+ if (!sess || !rcpt || !filename) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() invalid parameters\n");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (!filename1250)
+ filename1250 = filename;
+
+ if (stat(filename, &st) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() stat() failed (%s)\n", strerror(errno));
+ goto fail;
+ }
+
+ if ((st.st_mode & S_IFDIR)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() that's a directory\n");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if ((fd = open(filename, O_RDONLY | O_BINARY)) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() open() failed (%s)\n", strerror(errno));
+ goto fail;
+ }
+
+ if (!hash) {
+ if (gg_file_hash_sha1(fd, (uint8_t*) hash_buf) == -1)
+ goto fail;
+
+ hash = hash_buf;
+ }
+
+#ifdef _WIN32
+ if ((tmp = strrchr(filename1250, '\\')))
+#else
+ if ((tmp = strrchr(filename1250, '/')))
+#endif
+ filename1250 = tmp + 1;
+
+ if (!(dcc = gg_dcc7_send_file_common(sess, rcpt, fd, st.st_size, filename1250, hash, 1)))
+ goto fail;
+
+ return dcc;
+
+fail:
+ if (fd != -1) {
+ int errsv = errno;
+ close(fd);
+ errno = errsv;
+ }
+
+ free(dcc);
+ return NULL;
+}
+
+/**
+ * \internal Rozpoczyna wysyłanie pliku o danym deskryptorze.
+ *
+ * \note Wysyłanie pliku nie będzie działać poprawnie, jeśli deskryptor
+ * źródłowy jest w trybie nieblokującym i w pewnym momencie zabraknie danych.
+ *
+ * \param sess Struktura sesji
+ * \param rcpt Numer odbiorcy
+ * \param fd Deskryptor pliku
+ * \param size Rozmiar pliku
+ * \param filename1250 Nazwa pliku w kodowaniu CP-1250
+ * \param hash SkrĂłt SHA-1 pliku
+ *
+ * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file_fd(%p, %d, %d, %u, \"%s\", %p)\n", sess, rcpt, fd, size, filename1250, hash);
+
+ return gg_dcc7_send_file_common(sess, rcpt, fd, size, filename1250, hash, 0);
+}
+
+
+/**
+ * Potwierdza chęć odebrania pliku.
+ *
+ * \param dcc Struktura połączenia
+ * \param offset Początkowy offset przy wznawianiu przesyłania pliku
+ *
+ * \note Biblioteka nie zmienia położenia w odbieranych plikach. Jeśli offset
+ * początkowy jest różny od zera, należy ustawić go funkcją \c lseek() lub
+ * podobnÄ….
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset)
+{
+ struct gg_dcc7_accept pkt;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_accept(%p, %d)\n", dcc, offset);
+
+ if (!dcc || !dcc->sess) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_accept() invalid parameters\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.uin = gg_fix32(dcc->peer_uin);
+ pkt.id = dcc->cid;
+ pkt.offset = gg_fix32(offset);
+
+ if (gg_send_packet(dcc->sess, GG_DCC7_ACCEPT, &pkt, sizeof(pkt), NULL) == -1)
+ return -1;
+
+ dcc->offset = offset;
+
+ return gg_dcc7_listen_and_send_info(dcc);
+}
+
+/**
+ * Odrzuca próbę przesłania pliku.
+ *
+ * \param dcc Struktura połączenia
+ * \param reason PowĂłd odrzucenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason)
+{
+ struct gg_dcc7_reject pkt;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_reject(%p, %d)\n", dcc, reason);
+
+ if (!dcc || !dcc->sess) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reject() invalid parameters\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.uin = gg_fix32(dcc->peer_uin);
+ pkt.id = dcc->cid;
+ pkt.reason = gg_fix32(reason);
+
+ return gg_send_packet(dcc->sess, GG_DCC7_REJECT, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * Przerwanie żądania przesłania pliku.
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup dcc7
+ */
+int gg_dcc7_abort(struct gg_dcc7 *dcc)
+{
+ struct gg_dcc7_abort pkt;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_abort(%p)\n", dcc);
+
+ if (!dcc || !dcc->sess) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_abort() invalid parameters\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.id = dcc->cid;
+ pkt.uin_from = gg_fix32(dcc->uin);
+ pkt.uin_to = gg_fix32(dcc->peer_uin);
+
+ return gg_send_packet(dcc->sess, GG_DCC7_ABORT, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * \internal Obsługuje pakiet identyfikatora połączenia bezpośredniego.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_id_reply *p = payload;
+ struct gg_dcc7 *tmp;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_id(%p, %p, %p, %d)\n", sess, e, payload, len);
+
+ for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// checking dcc %p, state %d, type %d\n", tmp, tmp->state, tmp->dcc_type);
+
+ if (tmp->state != GG_STATE_REQUESTING_ID || tmp->dcc_type != (int)gg_fix32(p->type))
+ continue;
+
+ tmp->cid = p->id;
+
+ switch (tmp->dcc_type) {
+ case GG_DCC7_TYPE_FILE:
+ {
+ struct gg_dcc7_new s;
+
+ memset(&s, 0, sizeof(s));
+ s.id = tmp->cid;
+ s.type = gg_fix32(GG_DCC7_TYPE_FILE);
+ s.uin_from = gg_fix32(tmp->uin);
+ s.uin_to = gg_fix32(tmp->peer_uin);
+ s.size = gg_fix32(tmp->size);
+
+ strncpy((char*) s.filename, (char*) tmp->filename, GG_DCC7_FILENAME_LEN);
+
+ tmp->state = GG_STATE_WAITING_FOR_ACCEPT;
+ tmp->timeout = GG_DCC7_TIMEOUT_FILE_ACK;
+
+ return gg_send_packet(sess, GG_DCC7_NEW, &s, sizeof(s), NULL);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet akceptacji połączenia bezpośredniego.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_accept *p = payload;
+ struct gg_dcc7 *dcc;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_accept(%p, %p, %p, %d)\n", sess, e, payload, len);
+
+ if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() unknown dcc session\n");
+ // XXX wysłać reject?
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() invalid state\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ // XXX czy dla odwrotnego połączenia powinniśmy wywołać już zdarzenie GG_DCC7_ACCEPT?
+
+ dcc->offset = gg_fix32(p->offset);
+ dcc->state = GG_STATE_WAITING_FOR_INFO;
+
+ return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet informacji o połączeniu bezpośrednim.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_info *p = payload;
+ struct gg_dcc7 *dcc;
+ char *tmp;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_info(%p, %p, %p, %d)\n", sess, e, payload, len);
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "// gg_dcc7_handle_info() received address: %s, hash: %s\n", p->info, p->hash);
+
+ if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown dcc session\n");
+ return 0;
+ }
+
+ if (dcc->state == GG_STATE_CONNECTED) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() state is already connected\n");
+ return 0;
+ }
+
+ switch (p->type)
+ {
+ case GG_DCC7_TYPE_P2P:
+ if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ if (dcc->state == GG_STATE_WAITING_FOR_INFO) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() wainting for info so send one\n");
+ gg_dcc7_listen_and_send_info(dcc);
+ return 0;
+ }
+
+ break;
+
+ case GG_DCC7_TYPE_SERVER:
+ if (!(tmp = strstr(p->info, "GG"))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown info packet\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+#if defined(GG_CONFIG_HAVE_UINT64_T) && defined(GG_CONFIG_HAVE_STRTOULL)
+ {
+ uint64_t cid;
+
+ cid = strtoull(tmp + 2, NULL, 0);
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() info.str=%s, info.id=%llu, sess.id=%llu\n", tmp + 2, cid, *((unsigned long long*) &dcc->cid));
+
+ cid = gg_fix64(cid);
+
+ if (memcmp(&dcc->cid, &cid, sizeof(cid)) != 0) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid session id\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+ }
+#endif
+
+ if (gg_dcc7_get_relay_addr(dcc) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unable to retrieve relay address\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ // XXX wysyłać dopiero jeśli uda się połączyć z serwerem?
+
+ gg_send_packet(dcc->sess, GG_DCC7_INFO, payload, len, NULL);
+
+ break;
+
+ default:
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type);
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ // jeśli nadal czekamy na połączenie przychodzące, a druga strona nie
+ // daje rady i oferuje namiary na siebie, bierzemy co dajÄ….
+
+// if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) {
+// gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n");
+// e->type = GG_EVENT_DCC7_ERROR;
+// e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+// e->event.dcc7_error_ex.dcc7 = dcc;
+// return 0;
+// }
+
+ if (dcc->state == GG_STATE_LISTENING) {
+ gg_sock_close(dcc->fd);
+ dcc->fd = -1;
+ dcc->reverse = 1;
+ }
+
+ if (dcc->type == GG_SESSION_DCC7_SEND) {
+ e->type = GG_EVENT_DCC7_ACCEPT;
+ e->event.dcc7_accept.dcc7 = dcc;
+ e->event.dcc7_accept.type = gg_fix32(p->type);
+ e->event.dcc7_accept.remote_ip = dcc->remote_addr;
+ e->event.dcc7_accept.remote_port = dcc->remote_port;
+ } else {
+ e->type = GG_EVENT_DCC7_PENDING;
+ e->event.dcc7_pending.dcc7 = dcc;
+ }
+
+ if (dcc->state == GG_STATE_RESOLVING_RELAY)
+ return 0;
+
+ if (gg_dcc7_connect(dcc) == -1) {
+ if (gg_dcc7_reverse_connect(dcc) == -1) {
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_NET;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet odrzucenia połączenia bezpośredniego.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_reject *p = payload;
+ struct gg_dcc7 *dcc;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_reject(%p, %p, %p, %d)\n", sess, e, payload, len);
+
+ if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() unknown dcc session\n");
+ return 0;
+ }
+
+ if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() invalid state\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ e->type = GG_EVENT_DCC7_REJECT;
+ e->event.dcc7_reject.dcc7 = dcc;
+ e->event.dcc7_reject.reason = gg_fix32(p->reason);
+
+ // XXX ustawić state na rejected?
+
+ return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet przerwania żądania połączenia bezpośredniego.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_abort(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_aborted *p = payload;
+ struct gg_dcc7 *dcc;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_abort(%p, %p, %p, %d)\n", sess, e, payload, len);
+
+ if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(0)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() unknown dcc session\n");
+ return 0;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() state %d\n", dcc->state);
+
+ if (dcc->state != GG_STATE_IDLE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() invalid state\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return 0;
+ }
+
+ e->type = GG_EVENT_DCC7_REJECT;
+ e->event.dcc7_reject.dcc7 = dcc;
+ e->event.dcc7_reject.reason = gg_fix32(GG_DCC7_REJECT_USER);
+
+ // XXX ustawić state na rejected?
+
+ return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet nowego połączenia bezpośredniego.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+{
+ struct gg_dcc7_new *p = payload;
+ struct gg_dcc7 *dcc;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_new(%p, %p, %p, %d)\n", sess, e, payload, len);
+
+ switch (gg_fix32(p->type)) {
+ case GG_DCC7_TYPE_FILE:
+ if (!(dcc = malloc(sizeof(struct gg_dcc7)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() not enough memory\n");
+ return -1;
+ }
+
+ memset(dcc, 0, sizeof(struct gg_dcc7));
+ dcc->type = GG_SESSION_DCC7_GET;
+ dcc->dcc_type = GG_DCC7_TYPE_FILE;
+ dcc->fd = -1;
+ dcc->file_fd = -1;
+ dcc->uin = sess->uin;
+ dcc->peer_uin = gg_fix32(p->uin_from);
+ dcc->cid = p->id;
+ dcc->sess = sess;
+
+ if (gg_dcc7_session_add(sess, dcc) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n");
+ gg_dcc7_free(dcc);
+ return -1;
+ }
+
+ dcc->size = gg_fix32(p->size);
+ strncpy((char*) dcc->filename, (char*) p->filename, GG_DCC7_FILENAME_LEN - 1);
+ dcc->filename[GG_DCC7_FILENAME_LEN] = 0;
+ memcpy(dcc->hash, p->hash, GG_DCC7_HASH_LEN);
+
+ e->type = GG_EVENT_DCC7_NEW;
+ e->event.dcc7_new = dcc;
+
+ break;
+
+ case GG_DCC7_TYPE_VOICE:
+ if (!(dcc = malloc(sizeof(struct gg_dcc7)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_packet() not enough memory\n");
+ return -1;
+ }
+
+ memset(dcc, 0, sizeof(struct gg_dcc7));
+
+ dcc->type = GG_SESSION_DCC7_VOICE;
+ dcc->dcc_type = GG_DCC7_TYPE_VOICE;
+ dcc->fd = -1;
+ dcc->file_fd = -1;
+ dcc->uin = sess->uin;
+ dcc->peer_uin = gg_fix32(p->uin_from);
+ dcc->cid = p->id;
+ dcc->sess = sess;
+
+ if (gg_dcc7_session_add(sess, dcc) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n");
+ gg_dcc7_free(dcc);
+ return -1;
+ }
+
+ e->type = GG_EVENT_DCC7_NEW;
+ e->event.dcc7_new = dcc;
+
+ break;
+
+ default:
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unknown dcc type (%d) from %ld\n", gg_fix32(p->type), gg_fix32(p->uin_from));
+
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * \internal Ustawia odpowiednie stany wewnętrzne w zależności od rodzaju
+ * połączenia.
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu.
+ */
+static int gg_dcc7_postauth_fixup(struct gg_dcc7 *dcc)
+{
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_postauth_fixup(%p)\n", dcc);
+
+ if (!dcc) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_postauth_fixup() invalid parameters\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (dcc->type) {
+ case GG_SESSION_DCC7_GET:
+ dcc->state = GG_STATE_GETTING_FILE;
+ dcc->check = GG_CHECK_READ;
+ return 0;
+
+ case GG_SESSION_DCC7_SEND:
+ dcc->state = GG_STATE_SENDING_FILE;
+ dcc->check = GG_CHECK_WRITE;
+ return 0;
+
+ case GG_SESSION_DCC7_VOICE:
+ dcc->state = GG_STATE_READING_VOICE_DATA;
+ dcc->check = GG_CHECK_READ;
+ return 0;
+ }
+
+ errno = EINVAL;
+
+ return -1;
+}
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia
+ * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania.
+ * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free().
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd
+ *
+ * \ingroup dcc7
+ */
+struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *dcc)
+{
+ struct gg_event *e;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_watch_fd(%p)\n", dcc);
+
+ if (!dcc || (dcc->type != GG_SESSION_DCC7_SEND && dcc->type != GG_SESSION_DCC7_GET && dcc->type != GG_SESSION_DCC7_VOICE)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid parameters\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!(e = malloc(sizeof(struct gg_event)))) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory\n");
+ return NULL;
+ }
+
+ memset(e, 0, sizeof(struct gg_event));
+ e->type = GG_EVENT_NONE;
+
+ switch (dcc->state) {
+ case GG_STATE_LISTENING:
+ {
+ struct sockaddr_in sin;
+ SOCKET fd;
+ int one = 1;
+ unsigned int sin_len = sizeof(sin);
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_LISTENING\n");
+
+ if ((fd = accept(dcc->fd, (struct sockaddr*) &sin, &sin_len)) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() accept() failed (%s)\n", strerror(errno));
+ return e;
+ }
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
+
+#ifdef FIONBIO
+ if (ioctl(fd, FIONBIO, &one) == -1) {
+#else
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+#endif
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() can't set nonblocking (%s)\n", strerror(errno));
+ gg_sock_close(fd);
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ gg_sock_close(dcc->fd);
+ dcc->fd = fd;
+
+ dcc->state = GG_STATE_READING_ID;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ dcc->incoming = 1;
+
+ dcc->remote_port = ntohs(sin.sin_port);
+ dcc->remote_addr = sin.sin_addr.s_addr;
+
+ e->type = GG_EVENT_DCC7_CONNECTED;
+ e->event.dcc7_connected.dcc7 = dcc;
+
+ return e;
+ }
+
+ case GG_STATE_CONNECTING:
+ {
+ int res = 0, error = 0;
+ unsigned int error_size = sizeof(error);
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING\n");
+
+ dcc->soft_timeout = 0;
+
+ if (dcc->timeout == 0)
+ error = ETIMEDOUT;
+
+ if (error || (res = gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error));
+
+ if (dcc->relay) {
+ for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) {
+ dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr;
+ dcc->remote_port = dcc->relay_list[dcc->relay_index].port;
+
+ if (gg_dcc7_connect(dcc) == 0)
+ break;
+ }
+
+ if (dcc->relay_index >= dcc->relay_count) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+ } else {
+ if (gg_dcc7_reverse_connect(dcc) != -1) {
+ e->type = GG_EVENT_DCC7_PENDING;
+ e->event.dcc7_pending.dcc7 = dcc;
+ } else {
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_NET;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ }
+
+ return e;
+ }
+ }
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n");
+
+ dcc->state = GG_STATE_SENDING_ID;
+ dcc->check = GG_CHECK_WRITE;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ dcc->incoming = 0;
+
+ return e;
+ }
+
+ case GG_STATE_READING_ID:
+ {
+ int res;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n");
+
+ if (!dcc->relay) {
+ struct gg_dcc7_welcome_p2p welcome, welcome_ok;
+ welcome_ok.id = dcc->cid;
+
+ if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ if (memcmp(&welcome, &welcome_ok, sizeof(welcome))) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+ } else {
+ struct gg_dcc7_welcome_server welcome, welcome_ok;
+ welcome_ok.magic = GG_DCC7_WELCOME_SERVER;
+ welcome_ok.id = dcc->cid;
+
+ if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ if (memcmp(&welcome, &welcome_ok, sizeof(welcome)) != 0) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+ }
+
+ if (dcc->incoming) {
+ dcc->state = GG_STATE_SENDING_ID;
+ dcc->check = GG_CHECK_WRITE;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ } else {
+ gg_dcc7_postauth_fixup(dcc);
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ }
+
+ return e;
+ }
+
+ case GG_STATE_SENDING_ID:
+ {
+ int res;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n");
+
+ if (!dcc->relay) {
+ struct gg_dcc7_welcome_p2p welcome;
+
+ welcome.id = dcc->cid;
+
+ if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+ } else {
+ struct gg_dcc7_welcome_server welcome;
+
+ welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER);
+ welcome.id = dcc->cid;
+
+ if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+ }
+
+ if (dcc->incoming) {
+ gg_dcc7_postauth_fixup(dcc);
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ } else {
+ dcc->state = GG_STATE_READING_ID;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+ }
+
+ return e;
+ }
+
+ case GG_STATE_SENDING_FILE:
+ {
+ char buf[1024];
+ int chunk, res;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_SENDING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size);
+
+ if (dcc->offset >= dcc->size) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n");
+ e->type = GG_EVENT_DCC7_DONE;
+ e->event.dcc7_done.dcc7 = dcc;
+ return e;
+ }
+
+ if (dcc->seek && lseek(dcc->file_fd, dcc->offset, SEEK_SET) == (off_t) -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() lseek() failed (%s)\n", strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_FILE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ if ((chunk = dcc->size - dcc->offset) > sizeof(buf))
+ chunk = sizeof(buf);
+
+ if ((res = read(dcc->file_fd, buf, chunk)) < 1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (res=%d, %s)\n", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_FILE : GG_ERROR_DCC7_EOF;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ if ((res = gg_sock_write(dcc->fd, buf, res)) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%s)\n", strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_NET;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->offset += res;
+
+ if (dcc->offset >= dcc->size) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
+ e->type = GG_EVENT_DCC7_DONE;
+ e->event.dcc7_done.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->state = GG_STATE_SENDING_FILE;
+ dcc->check = GG_CHECK_WRITE;
+ dcc->timeout = GG_DCC7_TIMEOUT_SEND;
+
+ return e;
+ }
+
+ case GG_STATE_GETTING_FILE:
+ {
+ char buf[1024];
+ int res, wres;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_GETTING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size);
+
+ if (dcc->offset >= dcc->size) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
+ e->type = GG_EVENT_DCC7_DONE;
+ e->event.dcc7_done.dcc7 = dcc;
+ return e;
+ }
+
+ if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < 1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (fd=%d, res=%d, %s)\n", dcc->fd, res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_NET : GG_ERROR_DCC7_EOF;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ // XXX zapisywać do skutku?
+
+ if ((wres = write(dcc->file_fd, buf, res)) < res) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (fd=%d, res=%d, %s)\n", dcc->file_fd, wres, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_FILE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->offset += res;
+
+ if (dcc->offset >= dcc->size) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
+ e->type = GG_EVENT_DCC7_DONE;
+ e->event.dcc7_done.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->state = GG_STATE_GETTING_FILE;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DCC7_TIMEOUT_GET;
+
+ return e;
+ }
+
+ case GG_STATE_RESOLVING_RELAY:
+ {
+ struct in_addr addr;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n");
+
+ if (gg_sock_read(dcc->fd, &addr, sizeof(addr)) < sizeof(addr) || addr.s_addr == INADDR_NONE) {
+ int errno_save = errno;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n");
+ gg_sock_close(dcc->fd);
+ dcc->fd = -1;
+ dcc->sess->resolver_cleanup(&dcc->resolver, 0);
+ errno = errno_save;
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT);
+
+ if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->state = GG_STATE_CONNECTING_RELAY;
+ dcc->check = GG_CHECK_WRITE;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+ }
+
+ case GG_STATE_CONNECTING_RELAY:
+ {
+ int res;
+ unsigned int res_size = sizeof(res);
+ struct gg_dcc7_relay_req pkt;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n");
+
+ if (gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST);
+ pkt.len = gg_fix32(sizeof(pkt));
+ pkt.id = dcc->cid;
+ pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER);
+ pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1);
+
+ gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, &pkt, sizeof(pkt), "// gg_dcc7_watch_fd() send pkt(0x%.2x)\n", gg_fix32(pkt.magic));
+
+ if ((res = gg_sock_write(dcc->fd, &pkt, sizeof(pkt))) != sizeof(pkt)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ dcc->state = GG_STATE_READING_RELAY;
+ dcc->check = GG_CHECK_READ;
+ dcc->timeout = GG_DEFAULT_TIMEOUT;
+
+ return e;
+ }
+
+ case GG_STATE_READING_RELAY:
+ {
+ char buf[256];
+ struct gg_dcc7_relay_reply *pkt;
+ struct gg_dcc7_relay_reply_server srv;
+ int res;
+ int i;
+
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n");
+
+ if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < sizeof(*pkt)) {
+ if (res == 0)
+ errno = ECONNRESET;
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ pkt = (struct gg_dcc7_relay_reply*) buf;
+
+ if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount > 256) || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n");
+ errno = EINVAL;
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, buf, res, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic));
+
+ free(dcc->relay_list);
+
+ dcc->relay_index = 0;
+ dcc->relay_count = gg_fix32(pkt->rcount);
+ dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t));
+
+ if (dcc->relay_list == NULL) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory");
+ dcc->relay_count = 0;
+ free(e);
+ return NULL;
+ }
+
+ for (i = 0; i < dcc->relay_count; i++) {
+ struct in_addr addr;
+
+ memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv));
+ dcc->relay_list[i].addr = srv.addr;
+ dcc->relay_list[i].port = gg_fix16(srv.port);
+ dcc->relay_list[i].family = srv.family;
+
+ addr.s_addr = srv.addr;
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family);
+ }
+
+ dcc->relay = 1;
+
+ for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) {
+ dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr;
+ dcc->remote_port = dcc->relay_list[dcc->relay_index].port;
+
+ if (gg_dcc7_connect(dcc) == 0)
+ break;
+ }
+
+ if (dcc->relay_index >= dcc->relay_count) {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+ return e;
+ }
+
+ return e;
+ }
+
+ default:
+ {
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n");
+ e->type = GG_EVENT_DCC7_ERROR;
+ e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+ e->event.dcc7_error_ex.dcc7 = dcc;
+
+ return e;
+ }
+ }
+
+ return e;
+}
+
+/**
+ * Zwalnia zasoby używane przez połączenie bezpośrednie.
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \ingroup dcc7
+ */
+void gg_dcc7_free(struct gg_dcc7 *dcc)
+{
+ gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_free(%p)\n", dcc);
+
+ if (!dcc)
+ return;
+
+ if (dcc->fd != -1)
+ gg_sock_close(dcc->fd);
+
+ if (dcc->file_fd != -1)
+ close(dcc->file_fd);
+
+ if (dcc->sess)
+ gg_dcc7_session_remove(dcc->sess, dcc);
+
+ free(dcc->relay_list);
+
+ free(dcc);
+}
+
diff --git a/protocols/Gadu-Gadu/src/libgadu/events.c b/protocols/Gadu-Gadu/src/libgadu/events.c
new file mode 100644
index 0000000000..a730e9d620
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/events.c
@@ -0,0 +1,2864 @@
+/* coding: UTF-8 */
+/* $Id: events.c 13583 2011-04-12 12:51:18Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ * Arkadiusz Miśkiewicz <arekm@pld-linux.org>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 events.c
+ *
+ * \brief Obsługa zdarzeń
+ */
+
+#ifndef _WIN64
+#define _USE_32BIT_TIME_T
+#endif
+
+#include <sys/types.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif /* _WIN32 */
+
+#include "compat.h"
+#include "libgadu.h"
+#include "protocol.h"
+#include "internal.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#include <ctype.h>
+#endif /* _WIN32 */
+#ifndef GG_CONFIG_MIRANDA
+#ifdef GG_CONFIG_HAVE_OPENSSL
+# include <openssl/err.h>
+# include <openssl/x509.h>
+#endif
+#endif
+
+/**
+ * Zwalnia pamięć zajmowaną przez informację o zdarzeniu.
+ *
+ * Funkcję należy wywoływać za każdym razem gdy funkcja biblioteki zwróci
+ * strukturÄ™ \c gg_event.
+ *
+ * \param e Struktura zdarzenia
+ *
+ * \ingroup events
+ */
+void gg_event_free(struct gg_event *e)
+{
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e);
+
+ if (!e)
+ return;
+
+ switch (e->type) {
+ case GG_EVENT_MSG:
+ case GG_EVENT_MULTILOGON_MSG:
+ free(e->event.msg.message);
+ free(e->event.msg.formats);
+ free(e->event.msg.recipients);
+ free(e->event.msg.xhtml_message);
+ break;
+
+ case GG_EVENT_NOTIFY:
+ free(e->event.notify);
+ break;
+
+ case GG_EVENT_NOTIFY60:
+ {
+ int i;
+
+ for (i = 0; e->event.notify60[i].uin; i++)
+ free(e->event.notify60[i].descr);
+
+ free(e->event.notify60);
+
+ break;
+ }
+
+ case GG_EVENT_STATUS60:
+ free(e->event.status60.descr);
+ break;
+
+ case GG_EVENT_STATUS:
+ free(e->event.status.descr);
+ break;
+
+ case GG_EVENT_NOTIFY_DESCR:
+ free(e->event.notify_descr.notify);
+ free(e->event.notify_descr.descr);
+ break;
+
+ case GG_EVENT_DCC_VOICE_DATA:
+ free(e->event.dcc_voice_data.data);
+ break;
+
+ case GG_EVENT_PUBDIR50_SEARCH_REPLY:
+ case GG_EVENT_PUBDIR50_READ:
+ case GG_EVENT_PUBDIR50_WRITE:
+ gg_pubdir50_free(e->event.pubdir50);
+ break;
+
+ case GG_EVENT_USERLIST:
+ free(e->event.userlist.reply);
+ break;
+
+ case GG_EVENT_IMAGE_REPLY:
+ free(e->event.image_reply.filename);
+ free(e->event.image_reply.image);
+ break;
+
+ case GG_EVENT_XML_EVENT:
+ free(e->event.xml_event.data);
+ break;
+
+ case GG_EVENT_XML_ACTION:
+ free(e->event.xml_action.data);
+ break;
+
+ case GG_EVENT_USER_DATA:
+ {
+ unsigned i, j;
+
+ for (i = 0; i < e->event.user_data.user_count; i++) {
+ for (j = 0; j < e->event.user_data.users[i].attr_count; j++) {
+ free(e->event.user_data.users[i].attrs[j].key);
+ free(e->event.user_data.users[i].attrs[j].value);
+ }
+
+ free(e->event.user_data.users[i].attrs);
+ }
+
+ free(e->event.user_data.users);
+
+ break;
+ }
+
+ case GG_EVENT_MULTILOGON_INFO:
+ {
+ int i;
+
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ free(e->event.multilogon_info.sessions[i].name);
+
+ free(e->event.multilogon_info.sessions);
+
+ break;
+ }
+ }
+
+ free(e);
+}
+
+/** \cond internal */
+
+/**
+ * \internal Usuwa obrazek z kolejki do wysłania.
+ *
+ * \param s Struktura sesji
+ * \param q Struktura obrazka
+ * \param freeq Flaga zwolnienia elementu kolejki
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq)
+{
+ if (!s || !q) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (s->images == q)
+ s->images = q->next;
+ else {
+ struct gg_image_queue *qq;
+
+ for (qq = s->images; qq; qq = qq->next) {
+ if (qq->next == q) {
+ qq->next = q->next;
+ break;
+ }
+ }
+ }
+
+ if (freeq) {
+ free(q->image);
+ free(q->filename);
+ free(q);
+ }
+
+ return 0;
+}
+
+/**
+ * \internal Analizuje przychodzÄ…cy pakiet z obrazkiem.
+ *
+ * \param e Struktura zdarzenia
+ * \param p Bufor z danymi
+ * \param len Długość bufora
+ * \param sess Struktura sesji
+ * \param sender Numer nadawcy
+ * \param size Rozmiar pliku (z nagłówka)
+ * \param crc32 Suma kontrolna (z nagłówka)
+ */
+static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender, uint32_t size, uint32_t crc32)
+{
+ struct gg_image_queue *q, *qq;
+
+ if (!p || !sess || !e) {
+ errno = EFAULT;
+ return;
+ }
+
+ /* znajdĹş dany obrazek w kolejce danej sesji */
+
+ for (qq = sess->images, q = NULL; qq; qq = qq->next) {
+ if (sender == qq->sender && size == qq->size && crc32 == qq->crc32) {
+ q = qq;
+ break;
+ }
+ }
+
+ if (!q) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, size, crc32);
+ return;
+ }
+
+ if (p[0] == 0x05) {
+ q->done = 0;
+
+ len -= sizeof(struct gg_msg_image_reply);
+ p += sizeof(struct gg_msg_image_reply);
+
+ if (memchr(p, 0, len) == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender);
+ return;
+ }
+
+ if (!(q->filename = strdup(p))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n");
+ return;
+ }
+
+ len -= (unsigned int)strlen(p) + 1;
+ p += strlen(p) + 1;
+ } else {
+ len -= sizeof(struct gg_msg_image_reply);
+ p += sizeof(struct gg_msg_image_reply);
+ }
+
+ if (q->done + len > q->size)
+ len = q->size - q->done;
+
+ memcpy(q->image + q->done, p, len);
+ q->done += len;
+
+ /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */
+
+ if (q->done >= q->size) {
+ e->type = GG_EVENT_IMAGE_REPLY;
+ e->event.image_reply.sender = sender;
+ e->event.image_reply.size = q->size;
+ e->event.image_reply.crc32 = q->crc32;
+ e->event.image_reply.filename = q->filename;
+ e->event.image_reply.image = q->image;
+
+ gg_image_queue_remove(sess, q, 0);
+
+ free(q);
+ }
+}
+
+/**
+ * \internal Analizuje informacje rozszerzone wiadomości.
+ *
+ * \param sess Struktura sesji.
+ * \param e Struktura zdarzenia.
+ * \param sender Numer nadawcy.
+ * \param p WskaĹşnik na dane rozszerzone.
+ * \param packet_end WskaĹşnik na koniec pakietu.
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma
+ * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli
+ * wiadomość jest niepoprawna.
+ */
+static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, char *p, char *packet_end)
+{
+ while (p < packet_end) {
+ switch (*p) {
+ case 0x01: /* konferencja */
+ {
+ struct gg_msg_recipients *m = (void*) p;
+ uint32_t i, count;
+
+ if (p + sizeof(*m) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1)\n");
+ goto malformed;
+ }
+
+ memcpy(&count, &m->count, sizeof(count));
+ count = gg_fix32(count);
+ p += sizeof(*m);
+
+ if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1.5)\n");
+ goto malformed;
+ }
+
+ if (e->event.msg.recipients != NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.recipients already exist\n");
+ goto malformed;
+ }
+
+ e->event.msg.recipients = malloc(count * sizeof(uin_t));
+
+ if (e->event.msg.recipients == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for recipients data\n");
+ goto fail;
+ }
+
+ memcpy(e->event.msg.recipients, p, count * sizeof(uin_t));
+ p += count * sizeof(uin_t);
+
+ for (i = 0; i < count; i++)
+ e->event.msg.recipients[i] = gg_fix32(e->event.msg.recipients[i]);
+
+ e->event.msg.recipients_count = count;
+
+ break;
+ }
+
+ case 0x02: /* richtext */
+ {
+ uint16_t len;
+ char *buf;
+
+ if (p + 3 > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (2)\n");
+ goto malformed;
+ }
+
+ memcpy(&len, p + 1, sizeof(uint16_t));
+ len = gg_fix16(len);
+
+ if (e->event.msg.formats != NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.formats already exist\n");
+ goto malformed;
+ }
+
+ buf = malloc(len);
+
+ if (buf == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for richtext data\n");
+ goto fail;
+ }
+
+ p += 3;
+
+ if (p + len > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3)\n");
+ free(buf);
+ goto malformed;
+ }
+
+ memcpy(buf, p, len);
+
+ e->event.msg.formats = buf;
+ e->event.msg.formats_length = len;
+
+ p += len;
+
+ break;
+ }
+
+ case 0x04: /* image_request */
+ {
+ struct gg_msg_image_request *i = (void*) p;
+
+ if (p + sizeof(*i) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3.5)\n");
+ goto malformed;
+ }
+
+ if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (1)\n");
+ goto malformed;
+ }
+
+ memcpy(&e->event.image_request.size, &i->size, sizeof(i->size));
+ memcpy(&e->event.image_request.crc32, &i->crc32, sizeof(i->crc32));
+
+ e->event.image_request.sender = sender;
+ e->event.image_request.size = gg_fix32(e->event.image_request.size);
+ e->event.image_request.crc32 = gg_fix32(e->event.image_request.crc32);
+
+ e->type = GG_EVENT_IMAGE_REQUEST;
+
+ goto handled;
+ }
+
+ case 0x05: /* image_reply */
+ case 0x06:
+ {
+ struct gg_msg_image_reply *rep = (void*) p;
+ uint32_t size;
+ uint32_t crc32;
+
+ if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (2)\n");
+ goto malformed;
+ }
+
+ if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) {
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (4)\n");
+ goto malformed;
+ }
+
+ memcpy(&size, &rep->size, sizeof(size));
+ memcpy(&crc32, &rep->crc32, sizeof(crc32));
+ size = gg_fix32(size);
+ crc32 = gg_fix32(crc32);
+
+ if (p + sizeof(struct gg_msg_image_reply) == packet_end) {
+ /* pusta odpowiedĹş - klient po drugiej stronie nie ma ĹĽÄ…danego obrazka */
+
+ e->type = GG_EVENT_IMAGE_REPLY;
+ e->event.image_reply.sender = sender;
+ e->event.image_reply.size = 0;
+ e->event.image_reply.crc32 = crc32;
+ e->event.image_reply.filename = NULL;
+ e->event.image_reply.image = NULL;
+ goto handled;
+
+ }
+
+ gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender, size, crc32);
+
+ goto handled;
+ }
+
+ default:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() unknown payload 0x%.2x\n", *p);
+ p = packet_end;
+ }
+ }
+ }
+
+ return 0;
+
+handled:
+ return -1;
+
+fail:
+ return -2;
+
+malformed:
+ return -3;
+}
+
+/**
+ * \internal Analizuje przychodzący pakiet z wiadomością.
+ *
+ * Rozbija pakiet na poszczególne składniki -- tekst, informacje
+ * o konferencjach, formatowani itd.
+ *
+ * \param h WskaĹşnik do odebranego pakietu
+ * \param e Struktura zdarzenia
+ * \param sess Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess)
+{
+ struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header));
+ char *p, *packet_end = (char*) r + h->length;
+ int ctcp = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e);
+
+ if (!r->seq && !r->msgclass) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n");
+ e->type = GG_EVENT_NONE;
+ return 0;
+ }
+
+ /* znajdĹş \0 */
+ for (p = (char*) r + sizeof(*r); ; p++) {
+ if (p >= packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n");
+ goto malformed;
+ }
+
+ if (*p == 0x02 && p == packet_end - 1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n");
+ ctcp = 1;
+ break;
+ }
+
+ if (!*p)
+ break;
+ }
+
+ p++;
+
+ switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), p, packet_end)) {
+ case -1: // handled
+ return 0;
+
+ case -2: // failed
+ goto fail;
+
+ case -3: // malformed
+ goto malformed;
+ }
+
+ e->type = GG_EVENT_MSG;
+ e->event.msg.msgclass = gg_fix32(r->msgclass);
+ e->event.msg.sender = gg_fix32(r->sender);
+ e->event.msg.time = gg_fix32(r->time);
+ e->event.msg.seq = gg_fix32(r->seq);
+ if (ctcp)
+ e->event.msg.message = (unsigned char*) strdup("\x02");
+ else
+ e->event.msg.message = (unsigned char*) strdup((char*) r + sizeof(*r));
+
+
+ return 0;
+
+malformed:
+ e->type = GG_EVENT_NONE;
+ free(e->event.msg.message);
+ free(e->event.msg.recipients);
+ free(e->event.msg.formats);
+
+ return 0;
+
+fail:
+ free(e->event.msg.message);
+ free(e->event.msg.recipients);
+ free(e->event.msg.formats);
+ return -1;
+}
+
+/**
+ * \internal Zamienia tekst w formacie HTML na czysty tekst.
+ *
+ * \param dst Bufor wynikowy (może być \c NULL)
+ * \param html Tekst źródłowy
+ *
+ * \note Dokleja \c \\0 na końcu bufora wynikowego.
+ *
+ * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
+ */
+static int gg_convert_from_html(char *dst, const char *html)
+{
+ const char *src, *entity, *tag;
+ int len, in_tag, in_entity;
+
+ len = 0;
+ in_tag = 0;
+ tag = NULL;
+ in_entity = 0;
+ entity = NULL;
+
+ for (src = html; *src != 0; src++) {
+ if (*src == '<') {
+ tag = src;
+ in_tag = 1;
+ continue;
+ }
+
+ if (in_tag && (*src == '>')) {
+ if (strncmp(tag, "<br", 3) == 0) {
+ if (dst != NULL)
+ dst[len] = '\n';
+ len++;
+ }
+ in_tag = 0;
+ continue;
+ }
+
+ if (in_tag)
+ continue;
+
+ if (*src == '&') {
+ in_entity = 1;
+ entity = src;
+ continue;
+ }
+
+ if (in_entity && *src == ';') {
+ in_entity = 0;
+ if (dst != NULL) {
+ if (strncmp(entity, "&lt;", 4) == 0)
+ dst[len] = '<';
+ else if (strncmp(entity, "&gt;", 4) == 0)
+ dst[len] = '>';
+ else if (strncmp(entity, "&quot;", 6) == 0)
+ dst[len] = '"';
+ else if (strncmp(entity, "&apos;", 6) == 0)
+ dst[len] = '\'';
+ else if (strncmp(entity, "&amp;", 5) == 0)
+ dst[len] = '&';
+ else
+ dst[len] = '?';
+ }
+ len++;
+ continue;
+ }
+
+ if (in_entity && !(isalnum(*src) || *src == '#'))
+ in_entity = 0;
+
+ if (in_entity)
+ continue;
+
+ if (dst != NULL)
+ dst[len] = *src;
+
+ len++;
+ }
+
+ if (dst != NULL)
+ dst[len] = 0;
+
+ return len;
+}
+
+/**
+ * \internal Analizuje przychodzący pakiet z wiadomością protokołu Gadu-Gadu 8.0.
+ *
+ * Rozbija pakiet na poszczególne składniki -- tekst, informacje
+ * o konferencjach, formatowani itd.
+ *
+ * \param h WskaĹşnik do odebranego pakietu
+ * \param e Struktura zdarzenia
+ * \param sess Struktura sesji
+ * \param event Typ zdarzenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_handle_recv_msg80(struct gg_header *h, struct gg_event *e, struct gg_session *sess, int event)
+{
+ char *packet = (char*) h + sizeof(struct gg_header);
+ struct gg_recv_msg80 *r = (struct gg_recv_msg80*) packet;
+ uint32_t offset_plain;
+ uint32_t offset_attr;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg80(%p, %p);\n", h, e);
+
+ if (!r->seq && !r->msgclass) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n");
+ goto malformed;
+ }
+
+ offset_plain = gg_fix32(r->offset_plain);
+ offset_attr = gg_fix32(r->offset_attr);
+
+ if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= h->length) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n");
+ goto malformed;
+ }
+
+ if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > h->length) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n");
+ offset_attr = 0; /* nie parsuj attr. */
+ /* goto ignore; */
+ }
+
+ /* Normalna sytuacja, więc nie podpada pod powyższy warunek. */
+ if (offset_attr == h->length)
+ offset_attr = 0;
+
+ if (memchr(packet + offset_plain, 0, h->length - offset_plain) == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n");
+ goto malformed;
+ }
+
+ if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n");
+ goto malformed;
+ }
+
+ e->type = event;
+ e->event.msg.msgclass = gg_fix32(r->msgclass);
+ e->event.msg.sender = gg_fix32(r->sender);
+ e->event.msg.time = gg_fix32(r->time);
+ e->event.msg.seq = gg_fix32(r->seq);
+
+ if (offset_attr != 0) {
+ switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + h->length)) {
+ case -1: // handled
+ return 0;
+
+ case -2: // failed
+ goto fail;
+
+ case -3: // malformed
+ goto malformed;
+ }
+ }
+
+ if (sess->encoding == GG_ENCODING_CP1250) {
+ e->event.msg.message = (unsigned char*) strdup(packet + offset_plain);
+ } else {
+ if (offset_plain > sizeof(struct gg_recv_msg80)) {
+ int len;
+
+ len = gg_convert_from_html(NULL, packet + sizeof(struct gg_recv_msg80));
+
+ e->event.msg.message = malloc(len + 1);
+
+ if (e->event.msg.message == NULL)
+ goto fail;
+
+ gg_convert_from_html((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80));
+ } else {
+ e->event.msg.message = (unsigned char*) gg_cp_to_utf8(packet + offset_plain);
+ }
+ }
+
+ if (offset_plain > sizeof(struct gg_recv_msg80)) {
+ if (sess->encoding == GG_ENCODING_UTF8)
+ e->event.msg.xhtml_message = strdup(packet + sizeof(struct gg_recv_msg80));
+ else
+ e->event.msg.xhtml_message = gg_utf8_to_cp(packet + sizeof(struct gg_recv_msg80));
+ } else {
+ e->event.msg.xhtml_message = NULL;
+ }
+
+ return 0;
+
+fail:
+ free(e->event.msg.message);
+ free(e->event.msg.xhtml_message);
+ free(e->event.msg.recipients);
+ free(e->event.msg.formats);
+ return -1;
+
+malformed:
+ e->type = GG_EVENT_NONE;
+ free(e->event.msg.message);
+ free(e->event.msg.xhtml_message);
+ free(e->event.msg.recipients);
+ free(e->event.msg.formats);
+ return 0;
+}
+
+/**
+ * \internal Wysyła potwierdzenie odebrania wiadomości.
+ *
+ * \param sess Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+static int gg_handle_recv_msg_ack(struct gg_header *h, struct gg_session *sess)
+{
+ char *packet = (char*) h + sizeof(struct gg_header);
+ struct gg_recv_msg80 *r = (struct gg_recv_msg80*) packet;
+ struct gg_recv_msg_ack pkt;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg_ack(%p);\n", sess);
+
+ if ((sess->protocol_features & GG_FEATURE_MSG_ACK) == 0)
+ return 0;
+
+ pkt.seq = gg_fix32(r->seq);
+
+ return gg_send_packet(sess, GG_RECV_MSG_ACK, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * \internal Analizuje przychodzÄ…cy pakiet z danymi kontaktĂłw.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_handle_user_data(struct gg_session *sess, struct gg_event *e, void *packet, size_t len)
+{
+ struct gg_user_data d;
+ char *p = (char*) packet;
+ char *packet_end = (char*) packet + len;
+ struct gg_event_user_data_user *users;
+ unsigned i, j;
+ int res = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "** gg_handle_user_data(%p, %p, %p, %d);\n", sess, e, packet, len);
+
+ e->event.user_data.user_count = 0;
+ e->event.user_data.users = NULL;
+
+ if (p + sizeof(d) > packet_end)
+ goto malformed;
+
+ memcpy(&d, p, sizeof(d));
+ p += sizeof(d);
+
+ d.type = gg_fix32(d.type);
+ d.user_count = gg_fix32(d.user_count);
+
+ if (d.user_count > 0xffff) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (1)\n");
+ goto malformed;
+ }
+
+ if (d.user_count > 0) {
+ users = calloc(d.user_count, sizeof(struct gg_event_user_data_user));
+
+ if (users == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d*%d)\n", d.user_count, sizeof(struct gg_event_user_data_user));
+ goto fail;
+ }
+ } else {
+ users = NULL;
+ }
+
+ e->type = GG_EVENT_USER_DATA;
+ e->event.user_data.type = d.type;
+ e->event.user_data.user_count = d.user_count;
+ e->event.user_data.users = users;
+
+ gg_debug_session(sess, GG_DEBUG_DUMP, "type=%d, count=%d\n", d.type, d.user_count);
+
+ for (i = 0; i < d.user_count; i++) {
+ struct gg_user_data_user u;
+ struct gg_event_user_data_attr *attrs;
+
+ if (p + sizeof(u) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (2)\n");
+ goto malformed;
+ }
+
+ memcpy(&u, p, sizeof(u));
+ p += sizeof(u);
+
+ u.uin = gg_fix32(u.uin);
+ u.attr_count = gg_fix32(u.attr_count);
+
+ if (u.attr_count > 0xffff) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (2)\n");
+ goto malformed;
+ }
+
+ if (u.attr_count > 0) {
+ attrs = calloc(u.attr_count, sizeof(struct gg_event_user_data_attr));
+
+ if (attrs == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d*%d)\n", u.attr_count, sizeof(struct gg_event_user_data_attr));
+ goto fail;
+ }
+ } else {
+ attrs = NULL;
+ }
+
+ users[i].uin = u.uin;
+ users[i].attr_count = u.attr_count;
+ users[i].attrs = attrs;
+
+ gg_debug_session(sess, GG_DEBUG_DUMP, " uin=%d, count=%d\n", u.uin, u.attr_count);
+
+ for (j = 0; j < u.attr_count; j++) {
+ uint32_t key_size;
+ uint32_t attr_type;
+ uint32_t value_size;
+ char *key;
+ char *value;
+
+ if (p + sizeof(key_size) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (3)\n");
+ goto malformed;
+ }
+
+ memcpy(&key_size, p, sizeof(key_size));
+ p += sizeof(key_size);
+
+ key_size = gg_fix32(key_size);
+
+ if (key_size > 0xffff || p + key_size > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (3)\n");
+ goto malformed;
+ }
+
+ key = malloc(key_size + 1);
+
+ if (key == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d)\n", key_size + 1);
+ goto fail;
+ }
+
+ memcpy(key, p, key_size);
+ p += key_size;
+
+ key[key_size] = 0;
+
+ attrs[j].key = key;
+
+ if (p + sizeof(attr_type) + sizeof(value_size) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (4)\n");
+ goto malformed;
+ }
+
+ memcpy(&attr_type, p, sizeof(attr_type));
+ p += sizeof(attr_type);
+ memcpy(&value_size, p, sizeof(value_size));
+ p += sizeof(value_size);
+
+ attrs[j].type = gg_fix32(attr_type);
+ value_size = gg_fix32(value_size);
+
+ if (value_size > 0xffff || p + value_size > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (5)\n");
+ goto malformed;
+ }
+
+ value = malloc(value_size + 1);
+
+ if (value == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d)\n", value_size + 1);
+ goto fail;
+ }
+
+ memcpy(value, p, value_size);
+ p += value_size;
+
+ value[value_size] = 0;
+
+ attrs[j].value = value;
+
+ gg_debug_session(sess, GG_DEBUG_DUMP, " key=\"%s\", type=%d, value=\"%s\"\n", key, attr_type, value);
+ }
+ }
+
+ return 0;
+
+fail:
+ res = -1;
+
+malformed:
+ e->type = GG_EVENT_NONE;
+
+ for (i = 0; i < e->event.user_data.user_count; i++) {
+ for (j = 0; j < e->event.user_data.users[i].attr_count; j++) {
+ free(e->event.user_data.users[i].attrs[j].key);
+ free(e->event.user_data.users[i].attrs[j].value);
+ }
+
+ free(e->event.user_data.users[i].attrs);
+ }
+
+ free(e->event.user_data.users);
+
+ return res;
+}
+
+/**
+ * \internal Analizuje przychodzÄ…cy pakiet z listÄ… sesji multilogowania.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param payload Treść pakietu
+ * \param len Długość pakietu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_handle_multilogon_info(struct gg_session *sess, struct gg_event *e, void *packet, size_t len)
+{
+ char *packet_end = (char*) packet + len;
+ struct gg_multilogon_info *info = (struct gg_multilogon_info*) packet;
+ char *p = (char*) packet + sizeof(*info);
+ struct gg_multilogon_session *sessions = NULL;
+ size_t count;
+ size_t i;
+ int res = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_multilogon_info(%p, %p, %p, %d);\n", sess, e, packet, len);
+
+ count = gg_fix32(info->count);
+
+ if (count > 0xffff) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (1)\n");
+ goto malformed;
+ }
+
+ sessions = calloc(count, sizeof(struct gg_multilogon_session));
+
+ if (sessions == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d*%d)\n", count, sizeof(struct gg_multilogon_session));
+ return -1;
+ }
+
+ e->type = GG_EVENT_MULTILOGON_INFO;
+ e->event.multilogon_info.count = (int)count;
+ e->event.multilogon_info.sessions = sessions;
+
+ for (i = 0; i < count; i++) {
+ struct gg_multilogon_info_item item;
+ size_t name_size;
+
+ if (p + sizeof(item) > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (2)\n");
+ goto malformed;
+ }
+
+ memcpy(&item, p, sizeof(item));
+
+ sessions[i].id = item.conn_id;
+ sessions[i].remote_addr = item.addr;
+ sessions[i].status_flags = gg_fix32(item.flags);
+ sessions[i].protocol_features = gg_fix32(item.features);
+ sessions[i].logon_time = gg_fix32(item.logon_time);
+
+ p += sizeof(item);
+
+ name_size = gg_fix32(item.name_size);
+
+ if (name_size > 0xffff || p + name_size > packet_end) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (3)\n");
+ goto malformed;
+ }
+
+ sessions[i].name = malloc(name_size + 1);
+
+ if (sessions[i].name == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d)\n", name_size);
+ goto fail;
+ }
+
+ memcpy(sessions[i].name, p, name_size);
+ sessions[i].name[name_size] = 0;
+
+ p += name_size;
+ }
+
+ return 0;
+
+fail:
+ res = -1;
+
+malformed:
+ e->type = GG_EVENT_NONE;
+
+ for (i = 0; i < (size_t)e->event.multilogon_info.count; i++)
+ free(e->event.multilogon_info.sessions[i].name);
+
+ free(e->event.multilogon_info.sessions);
+
+ return res;
+}
+
+/**
+ * \internal Odbiera pakiet od serwera.
+ *
+ * Analizuje pakiet i wypełnia strukturę zdarzenia.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e)
+{
+ struct gg_header *h = NULL;
+ char *p;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (!(h = gg_recv_packet(sess))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail;
+ }
+
+ p = (char*) h + sizeof(struct gg_header);
+
+ switch (h->type) {
+ case GG_RECV_MSG:
+ {
+ if (h->length >= sizeof(struct gg_recv_msg)) {
+ if (gg_handle_recv_msg(h, e, sess) != -1)
+ gg_handle_recv_msg_ack(h, sess);
+ else
+ goto fail;
+ }
+
+ break;
+ }
+
+ case GG_RECV_MSG80:
+ {
+ if (h->length >= sizeof(struct gg_recv_msg80)) {
+ if (gg_handle_recv_msg80(h, e, sess, GG_EVENT_MSG) != -1)
+ gg_handle_recv_msg_ack(h, sess);
+ else
+ goto fail;
+ }
+
+ break;
+ }
+
+ case GG_RECV_OWN_MSG:
+ {
+ if (h->length >= sizeof(struct gg_recv_msg80)) {
+ if (gg_handle_recv_msg80(h, e, sess, GG_EVENT_MULTILOGON_MSG) == -1)
+ goto fail;
+ }
+
+ break;
+ }
+
+ case GG_NOTIFY_REPLY:
+ {
+ struct gg_notify_reply *n = (void*) p;
+ unsigned int count, i;
+ char *tmp;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+ if (h->length < sizeof(*n)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) {
+ e->type = GG_EVENT_NOTIFY_DESCR;
+
+ if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+ e->event.notify_descr.notify[1].uin = 0;
+ memcpy(e->event.notify_descr.notify, p, sizeof(*n));
+ e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin);
+ e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status);
+ e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port);
+ e->event.notify_descr.notify[0].version = gg_fix32(e->event.notify_descr.notify[0].version);
+
+ count = h->length - sizeof(*n);
+ if (!(tmp = malloc(count + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+ memcpy(tmp, p + sizeof(*n), count);
+ tmp[count] = 0;
+ e->event.notify_descr.descr = tmp;
+
+ } else {
+ e->type = GG_EVENT_NOTIFY;
+
+ if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ memcpy(e->event.notify, p, h->length);
+ count = h->length / sizeof(*n);
+ e->event.notify[count].uin = 0;
+
+ for (i = 0; i < count; i++) {
+ e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin);
+ e->event.notify[i].status = gg_fix32(e->event.notify[i].status);
+ e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port);
+ e->event.notify[i].version = gg_fix32(e->event.notify[i].version);
+ }
+ }
+
+ break;
+ }
+
+ case GG_STATUS:
+ {
+ struct gg_status *s = (void*) p;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+ if (h->length >= sizeof(*s)) {
+ e->type = GG_EVENT_STATUS;
+ memcpy(&e->event.status, p, sizeof(*s));
+ e->event.status.uin = gg_fix32(e->event.status.uin);
+ e->event.status.status = gg_fix32(e->event.status.status);
+ if (h->length > sizeof(*s)) {
+ int len = h->length - sizeof(*s);
+ char *buf = malloc(len + 1);
+ if (buf) {
+ memcpy(buf, p + sizeof(*s), len);
+ buf[len] = 0;
+ }
+ e->event.status.descr = buf;
+ } else
+ e->event.status.descr = NULL;
+ }
+
+ break;
+ }
+
+ case GG_NOTIFY_REPLY77:
+ case GG_NOTIFY_REPLY80BETA:
+ {
+ struct gg_notify_reply77 *n = (void*) p;
+ unsigned int length = h->length, i = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+ e->type = GG_EVENT_NOTIFY60;
+ e->event.notify60 = malloc(sizeof(*e->event.notify60));
+
+ if (!e->event.notify60) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ e->event.notify60[0].uin = 0;
+
+ while (length >= sizeof(struct gg_notify_reply77)) {
+ uin_t uin = gg_fix32(n->uin);
+ char *tmp;
+
+ e->event.notify60[i].uin = uin & 0x00ffffff;
+ e->event.notify60[i].status = n->status;
+ e->event.notify60[i].remote_ip = n->remote_ip;
+ e->event.notify60[i].remote_port = gg_fix16(n->remote_port);
+ e->event.notify60[i].version = n->version;
+ e->event.notify60[i].image_size = n->image_size;
+ e->event.notify60[i].descr = NULL;
+ e->event.notify60[i].time = 0;
+
+ if (uin & 0x40000000)
+ e->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
+ if (uin & 0x20000000)
+ e->event.notify60[i].version |= GG_HAS_AUDIO7_MASK;
+ if (uin & 0x08000000)
+ e->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
+
+ if (GG_S_D(n->status)) {
+ unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77));
+
+ if (sizeof(struct gg_notify_reply77) + descr_len <= length) {
+ char *descr;
+
+ if (!(descr = malloc(descr_len + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ memcpy(descr, (char*) n + sizeof(struct gg_notify_reply77) + 1, descr_len);
+ descr[descr_len] = 0;
+
+ if (h->type == GG_NOTIFY_REPLY80BETA && sess->encoding != GG_ENCODING_UTF8) {
+ char *cp_descr = gg_utf8_to_cp(descr);
+
+ if (!cp_descr) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ free(descr);
+ goto fail;
+ }
+
+ free(descr);
+ descr = cp_descr;
+ }
+
+ e->event.notify60[i].descr = descr;
+
+ /* XXX czas */
+
+ length -= sizeof(struct gg_notify_reply77) + descr_len + 1;
+ n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1);
+ } else {
+ length = 0;
+ }
+
+ } else {
+ length -= sizeof(struct gg_notify_reply77);
+ n = (void*) ((char*) n + sizeof(struct gg_notify_reply77));
+ }
+
+ if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ free(e->event.notify60);
+ goto fail;
+ }
+
+ e->event.notify60 = (void*) tmp;
+ e->event.notify60[++i].uin = 0;
+ }
+
+ break;
+ }
+
+ case GG_STATUS77:
+ case GG_STATUS80BETA:
+ {
+ struct gg_status77 *s = (void*) p;
+ uint32_t uin;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+ if (h->length < sizeof(*s))
+ break;
+
+ uin = gg_fix32(s->uin);
+
+ e->type = GG_EVENT_STATUS60;
+ e->event.status60.uin = uin & 0x00ffffff;
+ e->event.status60.status = s->status;
+ e->event.status60.remote_ip = s->remote_ip;
+ e->event.status60.remote_port = gg_fix16(s->remote_port);
+ e->event.status60.version = s->version;
+ e->event.status60.image_size = s->image_size;
+ e->event.status60.descr = NULL;
+ e->event.status60.time = 0;
+
+ if (uin & 0x40000000)
+ e->event.status60.version |= GG_HAS_AUDIO_MASK;
+ if (uin & 0x20000000)
+ e->event.status60.version |= GG_HAS_AUDIO7_MASK;
+ if (uin & 0x08000000)
+ e->event.status60.version |= GG_ERA_OMNIX_MASK;
+
+ if (h->length > sizeof(*s)) {
+ int len = h->length - sizeof(*s);
+ char *buf = malloc(len + 1);
+
+ /* XXX, jesli malloc() sie nie uda to robic tak samo jak przy GG_NOTIFY_REPLY* ?
+ * - goto fail; (?)
+ */
+ if (buf) {
+ memcpy(buf, (char*) p + sizeof(*s), len);
+ buf[len] = 0;
+
+ if (h->type == GG_STATUS80BETA && sess->encoding != GG_ENCODING_UTF8) {
+ char *cp_buf = gg_utf8_to_cp(buf);
+ free(buf);
+ buf = cp_buf;
+ }
+ }
+
+ e->event.status60.descr = buf;
+
+ if (len > 4 && p[h->length - 5] == 0) {
+ uint32_t t;
+ memcpy(&t, p + h->length - 4, sizeof(uint32_t));
+ e->event.status60.time = gg_fix32(t);
+ }
+ }
+
+ break;
+ }
+
+ case GG_NOTIFY_REPLY60:
+ {
+ struct gg_notify_reply60 *n = (void*) p;
+ unsigned int length = h->length, i = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+ e->type = GG_EVENT_NOTIFY60;
+ e->event.notify60 = malloc(sizeof(*e->event.notify60));
+
+ if (!e->event.notify60) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ e->event.notify60[0].uin = 0;
+
+ while (length >= sizeof(struct gg_notify_reply60)) {
+ uin_t uin = gg_fix32(n->uin);
+ char *tmp;
+
+ e->event.notify60[i].uin = uin & 0x00ffffff;
+ e->event.notify60[i].status = n->status;
+ e->event.notify60[i].remote_ip = n->remote_ip;
+ e->event.notify60[i].remote_port = gg_fix16(n->remote_port);
+ e->event.notify60[i].version = n->version;
+ e->event.notify60[i].image_size = n->image_size;
+ e->event.notify60[i].descr = NULL;
+ e->event.notify60[i].time = 0;
+
+ if (uin & 0x40000000)
+ e->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
+ if (uin & 0x08000000)
+ e->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
+
+ if (GG_S_D(n->status)) {
+ unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60));
+
+ if (sizeof(struct gg_notify_reply60) + descr_len <= length) {
+ if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len);
+ e->event.notify60[i].descr[descr_len] = 0;
+
+ /* XXX czas */
+
+ length -= sizeof(struct gg_notify_reply60) + descr_len + 1;
+ n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1);
+ } else {
+ length = 0;
+ }
+
+ } else {
+ length -= sizeof(struct gg_notify_reply60);
+ n = (void*) ((char*) n + sizeof(struct gg_notify_reply60));
+ }
+
+ if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ free(e->event.notify60);
+ goto fail;
+ }
+
+ e->event.notify60 = (void*) tmp;
+ e->event.notify60[++i].uin = 0;
+ }
+
+ break;
+ }
+
+ case GG_STATUS60:
+ {
+ struct gg_status60 *s = (void*) p;
+ uint32_t uin;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+ if (h->length < sizeof(*s))
+ break;
+
+ uin = gg_fix32(s->uin);
+
+ e->type = GG_EVENT_STATUS60;
+ e->event.status60.uin = uin & 0x00ffffff;
+ e->event.status60.status = s->status;
+ e->event.status60.remote_ip = s->remote_ip;
+ e->event.status60.remote_port = gg_fix16(s->remote_port);
+ e->event.status60.version = s->version;
+ e->event.status60.image_size = s->image_size;
+ e->event.status60.descr = NULL;
+ e->event.status60.time = 0;
+
+ if (uin & 0x40000000)
+ e->event.status60.version |= GG_HAS_AUDIO_MASK;
+ if (uin & 0x08000000)
+ e->event.status60.version |= GG_ERA_OMNIX_MASK;
+
+ if (h->length > sizeof(*s)) {
+ int len = h->length - sizeof(*s);
+ char *buf = malloc(len + 1);
+
+ if (buf) {
+ memcpy(buf, (char*) p + sizeof(*s), len);
+ buf[len] = 0;
+ }
+
+ e->event.status60.descr = buf;
+
+ if (len > 4 && p[h->length - 5] == 0) {
+ uint32_t t;
+ memcpy(&t, p + h->length - 4, sizeof(uint32_t));
+ e->event.status60.time = gg_fix32(t);
+ }
+ }
+
+ break;
+ }
+
+ case GG_STATUS80:
+ {
+ struct gg_notify_reply80 *s = (void*) p;
+ uint32_t descr_len;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+ if (h->length < sizeof(*s))
+ break;
+
+ e->type = GG_EVENT_STATUS60;
+ e->event.status60.uin = gg_fix32(s->uin);
+ e->event.status60.status = gg_fix32(s->status);
+ e->event.status60.remote_ip = s->remote_ip;
+ e->event.status60.remote_port = gg_fix16(s->remote_port);
+ e->event.status60.image_size = s->image_size;
+ e->event.status60.descr = NULL;
+ e->event.status60.version = 0x00; /* not-supported */
+ e->event.status60.time = 0; /* not-supported */
+
+ descr_len = gg_fix32(s->descr_len);
+
+ if (descr_len > 0 && h->length-sizeof(*s) >= descr_len) {
+ char *buf = malloc(descr_len + 1);
+
+ if (buf) {
+ memcpy(buf, (char*) p + sizeof(*s), descr_len);
+ buf[descr_len] = 0;
+
+ if (sess->encoding != GG_ENCODING_UTF8) {
+ char *cp_buf = gg_utf8_to_cp(buf);
+ free(buf);
+ buf = cp_buf;
+ }
+ }
+
+ e->event.status60.descr = buf;
+ }
+ break;
+ }
+
+ case GG_NOTIFY_REPLY80:
+ {
+ struct gg_notify_reply80 *n = (void*) p;
+ unsigned int length = h->length, i = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+ e->type = GG_EVENT_NOTIFY60;
+ e->event.notify60 = malloc(sizeof(*e->event.notify60));
+
+ if (!e->event.notify60) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ e->event.notify60[0].uin = 0;
+
+ while (length >= sizeof(struct gg_notify_reply80)) {
+ uint32_t descr_len;
+ char *tmp;
+
+ e->event.notify60[i].uin = gg_fix32(n->uin);
+ e->event.notify60[i].status = gg_fix32(n->status);
+ e->event.notify60[i].remote_ip = n->remote_ip;
+ e->event.notify60[i].remote_port= gg_fix16(n->remote_port);
+ e->event.notify60[i].image_size = n->image_size;
+ e->event.notify60[i].descr = NULL;
+ e->event.notify60[i].version = 0x00; /* not-supported */
+ e->event.notify60[i].time = 0; /* not-supported */
+
+ descr_len = gg_fix32(n->descr_len);
+
+ length -= sizeof(struct gg_notify_reply80);
+ n = (void*) ((char*) n + sizeof(struct gg_notify_reply80));
+
+ if (descr_len) {
+ if (length >= descr_len) {
+ /* XXX, GG_S_D(n->status) */
+ char *descr;
+
+ if (!(descr = malloc(descr_len + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ goto fail;
+ }
+
+ memcpy(descr, n, descr_len);
+ descr[descr_len] = 0;
+
+ if (sess->encoding != GG_ENCODING_UTF8) {
+ char *cp_descr = gg_utf8_to_cp(descr);
+
+ if (!cp_descr) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ free(descr);
+ goto fail;
+ }
+
+ free(descr);
+ descr = cp_descr;
+ }
+ e->event.notify60[i].descr = descr;
+
+ length -= descr_len;
+ n = (void*) ((char*) n + descr_len);
+ } else
+ length = 0;
+ }
+
+ if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+ free(e->event.notify60);
+ goto fail;
+ }
+
+ e->event.notify60 = (void*) tmp;
+ e->event.notify60[++i].uin = 0;
+ }
+ break;
+ }
+
+ case GG_SEND_MSG_ACK:
+ {
+ struct gg_send_msg_ack *s = (void*) p;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n");
+
+ if (h->length < sizeof(*s))
+ break;
+
+ e->type = GG_EVENT_ACK;
+ e->event.ack.status = gg_fix32(s->status);
+ e->event.ack.recipient = gg_fix32(s->recipient);
+ e->event.ack.seq = gg_fix32(s->seq);
+
+ break;
+ }
+
+ case GG_PONG:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n");
+
+ e->type = GG_EVENT_PONG;
+ sess->last_pong = (int)time(NULL);
+
+ break;
+ }
+
+ case GG_DISCONNECTING:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n");
+ e->type = GG_EVENT_DISCONNECT;
+ break;
+ }
+
+ case GG_DISCONNECT_ACK:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection acknowledge\n");
+ e->type = GG_EVENT_DISCONNECT_ACK;
+ break;
+ }
+
+ case GG_XML_EVENT:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n");
+ e->type = GG_EVENT_XML_EVENT;
+ if (!(e->event.xml_event.data = (char *) malloc(h->length + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML event data\n");
+ goto fail;
+ }
+ memcpy(e->event.xml_event.data, p, h->length);
+ e->event.xml_event.data[h->length] = 0;
+ break;
+ }
+
+ case GG_XML_ACTION:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML action\n");
+ e->type = GG_EVENT_XML_ACTION;
+ if (!(e->event.xml_action.data = (char *) malloc(h->length + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML action data\n");
+ goto fail;
+ }
+ memcpy(e->event.xml_action.data, p, h->length);
+ e->event.xml_action.data[h->length] = 0;
+ break;
+ }
+
+ case GG_PUBDIR50_REPLY:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n");
+ if (gg_pubdir50_handle_reply_sess(sess, e, p, h->length) == -1)
+ goto fail;
+ break;
+ }
+
+ case GG_USERLIST_REPLY:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n");
+
+ if (h->length < 1)
+ break;
+
+ /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko
+ * gdy otrzymano wszystkie odpowiedzi */
+ if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) {
+ if (--sess->userlist_blocks)
+ break;
+
+ p[0] = GG_USERLIST_PUT_REPLY;
+ }
+
+ if (h->length > 1) {
+ char *tmp;
+ unsigned int len = (sess->userlist_reply) ? (unsigned int)strlen(sess->userlist_reply) : 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len);
+
+ if (!(tmp = realloc(sess->userlist_reply, len + h->length))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n");
+ free(sess->userlist_reply);
+ sess->userlist_reply = NULL;
+ goto fail;
+ }
+
+ sess->userlist_reply = tmp;
+ sess->userlist_reply[len + h->length - 1] = 0;
+ memcpy(sess->userlist_reply + len, p + 1, h->length - 1);
+ }
+
+ if (p[0] == GG_USERLIST_GET_MORE_REPLY)
+ break;
+
+ e->type = GG_EVENT_USERLIST;
+ e->event.userlist.type = p[0];
+ e->event.userlist.reply = sess->userlist_reply;
+ sess->userlist_reply = NULL;
+
+ break;
+ }
+
+ case GG_DCC7_ID_REPLY:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n");
+
+ if (h->length < sizeof(struct gg_dcc7_id_reply))
+ break;
+
+ if (gg_dcc7_handle_id(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_DCC7_ACCEPT:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n");
+
+ if (h->length < sizeof(struct gg_dcc7_accept))
+ break;
+
+ if (gg_dcc7_handle_accept(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_DCC7_NEW:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n");
+
+ if (h->length < sizeof(struct gg_dcc7_new))
+ break;
+
+ if (gg_dcc7_handle_new(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_DCC7_REJECT:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n");
+
+ if (h->length < sizeof(struct gg_dcc7_reject))
+ break;
+
+ if (gg_dcc7_handle_reject(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_DCC7_ABORT:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 abort\n");
+
+ if (h->length < sizeof(struct gg_dcc7_aborted))
+ break;
+
+ if (gg_dcc7_handle_abort(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_DCC7_INFO:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n");
+
+ if (h->length < sizeof(struct gg_dcc7_info))
+ break;
+
+ if (gg_dcc7_handle_info(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_USER_DATA:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received user data\n");
+
+ if (h->length < sizeof(struct gg_user_data))
+ break;
+
+ if (gg_handle_user_data(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ case GG_TYPING_NOTIFICATION:
+ {
+ struct gg_typing_notification *n = (void*) p;
+ uin_t uin;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received typing notification\n");
+
+ if (h->length < sizeof(*n))
+ break;
+
+ memcpy(&uin, &n->uin, sizeof(uin_t));
+
+ e->type = GG_EVENT_TYPING_NOTIFICATION;
+ e->event.typing_notification.uin = gg_fix32(uin);
+ e->event.typing_notification.length = gg_fix16(n->length);
+
+ break;
+ }
+
+ case GG_MULTILOGON_INFO:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received multilogon info\n");
+
+ if (h->length < sizeof(struct gg_multilogon_info))
+ break;
+
+ if (gg_handle_multilogon_info(sess, e, p, h->length) == -1)
+ goto fail;
+
+ break;
+ }
+
+ default:
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type);
+ }
+
+ free(h);
+ return 0;
+
+fail:
+ free(h);
+ return -1;
+}
+
+/** \endcond */
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze sesji.
+ *
+ * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia
+ * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania.
+ * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free().
+ *
+ * \param sess Struktura sesji
+ *
+ * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd
+ *
+ * \ingroup events
+ */
+struct gg_event *gg_watch_fd(struct gg_session *sess)
+{
+ struct gg_event *e;
+ int res = 0;
+ int port = 0;
+ int errno2 = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess);
+
+ if (!sess) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ if (!(e = (void*) calloc(1, sizeof(*e)))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n");
+ return NULL;
+ }
+
+ e->type = GG_EVENT_NONE;
+
+ if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left);
+
+ res = gg_sock_write(sess->fd, sess->send_buf, sess->send_left);
+
+ if (res == -1 && errno != EAGAIN) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno));
+
+ if (sess->state == GG_STATE_READING_REPLY)
+ goto fail_connecting;
+ else
+ goto done;
+ }
+
+ if (res == sess->send_left) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n");
+ free(sess->send_buf);
+ sess->send_buf = NULL;
+ sess->send_left = 0;
+ } else if (res > 0) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res);
+
+ memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res);
+ sess->send_left -= res;
+ }
+
+ res = 0;
+ }
+
+ switch (sess->state) {
+ case GG_STATE_RESOLVING:
+ {
+ struct in_addr addr;
+ int failed = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n");
+
+ if (gg_sock_read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n");
+ failed = 1;
+ errno2 = errno;
+ }
+
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+
+ sess->resolver_cleanup(&sess->resolver, 0);
+
+ if (failed) {
+ errno = errno2;
+ goto fail_resolving;
+ }
+
+ /* jeśli jesteśmy w resolverze i mamy ustawiony port
+ * proxy, znaczy, że resolvowaliśmy proxy. zatem
+ * wpiszmy jego adres. */
+ if (sess->proxy_port)
+ sess->proxy_addr = addr.s_addr;
+
+ /* zapiszmy sobie adres huba i adres serwera (do
+ * bezpośredniego połączenia, jeśli hub leży)
+ * z resolvera. */
+ if (sess->proxy_addr && sess->proxy_port)
+ port = sess->proxy_port;
+ else {
+ sess->server_addr = sess->hub_addr = addr.s_addr;
+ port = GG_APPMSG_PORT;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port);
+
+ /* Ĺ‚Ä…czymy siÄ™ albo z hubem, albo z proxy, zaleĹĽnie
+ * od tego, co resolvowaliśmy. */
+ if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) {
+ /* jeśli w trybie asynchronicznym gg_connect()
+ * zwróci błąd, nie ma sensu próbować dalej. */
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
+ goto fail_connecting;
+ }
+
+ /* jeśli podano serwer i łączmy się przez proxy,
+ * jest to bezpośrednie połączenie, inaczej jest
+ * do huba. */
+
+ if (sess->proxy_addr && sess->proxy_port && sess->server_addr) {
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->soft_timeout = 1;
+ } else
+ sess->state = GG_STATE_CONNECTING_HUB;
+
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+
+ case GG_STATE_CONNECTING_HUB:
+ {
+ char buf[1024], *client, *auth;
+ int res = 0;
+ unsigned int res_size = sizeof(res);
+ const char *host;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n");
+
+ /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił
+ * przypadkiem jakiś błąd. */
+ if (sess->async && (gg_getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+ if (sess->proxy_addr && sess->proxy_port)
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res));
+ else
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s)\n", res, strerror(res));
+
+ goto fail_connecting;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n");
+
+ if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n");
+ goto fail_connecting;
+ }
+
+ if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port)
+ host = "http://" GG_APPMSG_HOST;
+ else
+ host = "";
+
+ auth = gg_proxy_auth();
+
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->tls) {
+ snprintf(buf, sizeof(buf) - 1,
+ "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n"
+ "Connection: close\r\n"
+ "Host: " GG_APPMSG_HOST "\r\n"
+ "%s"
+ "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+ } else
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL) {
+ snprintf(buf, sizeof(buf) - 1,
+ "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n"
+ "Connection: close\r\n"
+ "Host: " GG_APPMSG_HOST "\r\n"
+ "%s"
+ "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+ } else
+#endif
+ {
+ snprintf(buf, sizeof(buf) - 1,
+ "GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n"
+ "Host: " GG_APPMSG_HOST "\r\n"
+ "%s"
+ "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+ }
+
+ free(auth);
+ free(client);
+
+ /* zwolnij pamięć po wersji klienta. */
+ if (sess->client_version) {
+ free(sess->client_version);
+ sess->client_version = NULL;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf);
+
+ /* zapytanie jest krótkie, więc zawsze zmieści się
+ * do bufora gniazda. jeśli write() zwróci mniej,
+ * stało się coś złego. */
+ if (gg_sock_write(sess->fd, buf, (int)strlen(buf)) < (signed)strlen(buf)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n");
+
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_WRITING;
+ sess->state = GG_STATE_IDLE;
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ break;
+ }
+
+ sess->state = GG_STATE_READING_DATA;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+
+ case GG_STATE_READING_DATA:
+ {
+ char buf[1024], *tmp, *host;
+ int port = GG_DEFAULT_PORT;
+ struct in_addr addr;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n");
+
+ /* czytamy liniÄ™ z gniazda i obcinamy \r\n. */
+ gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+ gg_chomp(buf);
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf);
+
+ /* sprawdzamy, czy wszystko w porzÄ…dku. */
+ if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n");
+ goto fail_connecting;
+ }
+
+ /* ignorujemy resztę nagłówka. */
+ while (strcmp(buf, "\r\n") && strcmp(buf, ""))
+ gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+
+ /* czytamy pierwszÄ… liniÄ™ danych. */
+ gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+ gg_chomp(buf);
+
+ /* jeśli pierwsza liczba w linii nie jest równa zeru,
+ * oznacza to, że mamy wiadomość systemową. */
+ if (atoi(buf)) {
+ char tmp[1024], *foo, *sysmsg_buf = NULL;
+ int len = 0;
+
+ while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) {
+ if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n");
+ break;
+ }
+
+ sysmsg_buf = foo;
+
+ if (!len)
+ strcpy(sysmsg_buf, tmp);
+ else
+ strcat(sysmsg_buf, tmp);
+
+ len += (int)strlen(tmp);
+ }
+
+ e->type = GG_EVENT_MSG;
+ e->event.msg.msgclass = atoi(buf);
+ e->event.msg.sender = 0;
+ e->event.msg.message = (unsigned char*) sysmsg_buf;
+ }
+
+ gg_sock_close(sess->fd);
+
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf);
+
+ /* analizujemy otrzymane dane. */
+ tmp = buf;
+
+ while (*tmp && *tmp != ' ')
+ tmp++;
+ while (*tmp && *tmp == ' ')
+ tmp++;
+ while (*tmp && *tmp != ' ')
+ tmp++;
+ while (*tmp && *tmp == ' ')
+ tmp++;
+ host = tmp;
+ while (*tmp && *tmp != ' ')
+ tmp++;
+ *tmp = 0;
+
+ if ((tmp = strchr(host, ':'))) {
+ *tmp = 0;
+ port = atoi(tmp + 1);
+ }
+
+ if (!strcmp(host, "notoperating")) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno));
+ sess->fd = -1;
+ goto fail_unavailable;
+ }
+
+ addr.s_addr = inet_addr(host);
+ sess->server_addr = addr.s_addr;
+
+ if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) {
+ /* jeśli mamy proxy, łączymy się z nim. */
+ if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) {
+ /* nie wyszło? trudno. */
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail_connecting;
+ }
+
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ sess->soft_timeout = 1;
+ break;
+ }
+
+ sess->port = port;
+
+ /* Jeśli podano nazwę, nie adres serwera... */
+ if (sess->server_addr == INADDR_NONE) {
+ if (sess->resolver_start(&sess->fd, &sess->resolver, host) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail_resolving;
+ }
+
+ sess->state = GG_STATE_RESOLVING_GG;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ break;
+ }
+
+ /* łączymy się z właściwym serwerem. */
+ if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));
+
+ sess->port = GG_HTTPS_PORT;
+
+ /* nie wyszło? próbujemy portu 443. */
+ if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) {
+ /* ostatnia deska ratunku zawiodła?
+ * w takim razie zwijamy manatki. */
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail_connecting;
+ }
+ }
+
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ sess->soft_timeout = 1;
+
+ break;
+ }
+
+ case GG_STATE_RESOLVING_GG:
+ {
+ struct in_addr addr;
+ int failed = 0;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING_GG\n");
+
+ if (gg_sock_read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n");
+ failed = 1;
+ errno2 = errno;
+ }
+
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+
+ sess->resolver_cleanup(&sess->resolver, 0);
+
+ if (failed) {
+ errno = errno2;
+ goto fail_resolving;
+ }
+
+ sess->server_addr = addr.s_addr;
+
+ /* łączymy się z właściwym serwerem. */
+ if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));
+
+ sess->port = GG_HTTPS_PORT;
+
+ /* nie wyszło? próbujemy portu 443. */
+ if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) {
+ /* ostatnia deska ratunku zawiodła?
+ * w takim razie zwijamy manatki. */
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail_connecting;
+ }
+ }
+
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ sess->soft_timeout = 1;
+
+ break;
+ }
+
+ case GG_STATE_CONNECTING_GG:
+ {
+ int res = 0;
+ unsigned int res_size = sizeof(res);
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n");
+
+ sess->soft_timeout = 0;
+
+ /* jeśli wystąpił błąd podczas łączenia się... */
+ if (sess->async && (sess->timeout == 0 || gg_getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+ /* jeśli nie udało się połączenie z proxy,
+ * nie mamy czego próbować więcej. */
+ if (sess->proxy_addr && sess->proxy_port) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res));
+ goto fail_connecting;
+ }
+
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+
+#ifdef ETIMEDOUT
+ if (sess->timeout == 0)
+ errno = ETIMEDOUT;
+#endif
+
+#ifdef GG_CONFIG_HAVE_OPENSSL
+ /* jeśli logujemy się po TLS, nie próbujemy
+ * się łączyć już z niczym innym w przypadku
+ * błędu. nie dość, że nie ma sensu, to i
+ * trzeba by się bawić w tworzenie na nowo
+ * SSL i SSL_CTX. */
+
+ if (sess->ssl) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
+ goto fail_connecting;
+ }
+#endif
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res));
+
+ if (sess->port == GG_HTTPS_PORT)
+ goto fail_connecting;
+
+ sess->port = GG_HTTPS_PORT;
+
+ /* prĂłbujemy na port 443. */
+ if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail_connecting;
+ }
+
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ sess->soft_timeout = 1;
+
+ break;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n");
+
+ if (gg_proxy_http_only)
+ sess->proxy_port = 0;
+
+ /* jeśli mamy proxy, wyślijmy zapytanie. */
+ if (sess->proxy_addr && sess->proxy_port) {
+ char buf[100], *auth = gg_proxy_auth();
+ struct in_addr addr;
+
+ if (sess->server_addr)
+ addr.s_addr = sess->server_addr;
+ else
+ addr.s_addr = sess->hub_addr;
+
+ snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port);
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf);
+
+ /* wysyłamy zapytanie. jest ono na tyle krótkie,
+ * że musi się zmieścić w buforze gniazda. jeśli
+ * write() zawiedzie, stało się coś złego. */
+ if (gg_sock_write(sess->fd, buf, (int)strlen(buf)) < (signed)strlen(buf)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
+ free(auth);
+ goto fail_connecting;
+ }
+
+ if (auth) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// %s", auth);
+ if (gg_sock_write(sess->fd, auth, (int)strlen(auth)) < (signed)strlen(auth)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
+ free(auth);
+ goto fail_connecting;
+ }
+
+ free(auth);
+ }
+
+ if (gg_sock_write(sess->fd, "\r\n", 2) < 2) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
+ goto fail_connecting;
+ }
+ }
+
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->tls) {
+ sess->state = GG_STATE_TLS_NEGOTIATION;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL) {
+ SSL_set_fd(sess->ssl, (int)sess->fd);
+
+ sess->state = GG_STATE_TLS_NEGOTIATION;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+#endif
+
+ sess->state = GG_STATE_READING_KEY;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+
+#ifdef GG_CONFIG_MIRANDA
+ case GG_STATE_TLS_NEGOTIATION:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");
+
+ sess->ssl = si.connect(sess->fd, 0, 0);
+
+ if (sess->ssl == NULL) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation failed\n");
+
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_TLS;
+ sess->state = GG_STATE_IDLE;
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ break;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded\n");
+
+ sess->state = GG_STATE_READING_KEY;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+#elif GG_CONFIG_HAVE_OPENSSL
+ case GG_STATE_TLS_NEGOTIATION:
+ {
+ int res;
+ X509 *peer;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");
+
+ if ((res = SSL_connect(sess->ssl)) <= 0) {
+ int err = SSL_get_error(sess->ssl, res);
+
+ if (res == 0) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n");
+
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_TLS;
+ sess->state = GG_STATE_IDLE;
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ break;
+ }
+
+ if (err == SSL_ERROR_WANT_READ) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n");
+
+ sess->state = GG_STATE_TLS_NEGOTIATION;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n");
+
+ sess->state = GG_STATE_TLS_NEGOTIATION;
+ sess->check = GG_CHECK_WRITE;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ } else {
+ char buf[256];
+
+ ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf);
+
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_TLS;
+ sess->state = GG_STATE_IDLE;
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ break;
+ }
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl));
+
+ peer = SSL_get_peer_certificate(sess->ssl);
+
+ if (!peer)
+ gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n");
+ else {
+ char buf[256];
+
+ X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf));
+ gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf);
+
+ X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf));
+ gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf);
+ }
+
+ sess->state = GG_STATE_READING_KEY;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+
+ break;
+ }
+#endif
+
+ case GG_STATE_READING_KEY:
+ {
+ struct gg_header *h;
+ struct gg_welcome *w;
+ unsigned char *password = (unsigned char*) sess->password;
+ int ret;
+ uint8_t login_hash[64];
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n");
+
+ memset(login_hash, 0, sizeof(login_hash));
+
+ /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie
+ * siÄ™ tekstu wrzucanego przez proxy. */
+ if (sess->proxy_addr && sess->proxy_port) {
+ char buf[100];
+
+ strcpy(buf, "");
+ gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+ gg_chomp(buf);
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf);
+
+ while (strcmp(buf, "")) {
+ gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+ gg_chomp(buf);
+ if (strcmp(buf, ""))
+ gg_debug_session(sess, GG_DEBUG_MISC, "// %s\n", buf);
+ }
+
+ /* XXX niech czeka jeszcze raz w tej samej
+ * fazie. głupio, ale działa. */
+ sess->proxy_port = 0;
+
+ break;
+ }
+
+ /* czytaj pierwszy pakiet. */
+ if (!(h = gg_recv_packet(sess))) {
+ if (errno == EAGAIN) {
+ sess->check = GG_CHECK_READ;
+ break;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));
+
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_READING;
+ sess->state = GG_STATE_IDLE;
+ errno2 = errno;
+ gg_sock_close(sess->fd);
+ errno = errno2;
+ sess->fd = -1;
+ break;
+ }
+
+ if (h->type != GG_WELCOME) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n");
+ free(h);
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ errno = EINVAL;
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_INVALID;
+ sess->state = GG_STATE_IDLE;
+ break;
+ }
+
+ w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header));
+ w->key = gg_fix32(w->key);
+
+ switch (sess->hash_type) {
+ case GG_LOGIN_HASH_GG32:
+ {
+ unsigned int hash;
+
+ hash = gg_fix32(gg_login_hash(password, w->key));
+ gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash);
+ memcpy(login_hash, &hash, sizeof(hash));
+
+ break;
+ }
+
+ case GG_LOGIN_HASH_SHA1:
+ {
+ char tmp[41];
+ int i;
+
+ gg_login_hash_sha1((char*) password, w->key, login_hash);
+ for (i = 0; i < 40; i += 2)
+ snprintf(tmp + i, sizeof(tmp) - i, "%02x", login_hash[i / 2]);
+
+ gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp);
+
+ break;
+ }
+ }
+
+ free(h);
+ free(sess->password);
+ sess->password = NULL;
+
+ if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) {
+ struct sockaddr_in sin;
+ unsigned int sin_len = sizeof(sin);
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n");
+
+ if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr));
+ sess->client_addr = sin.sin_addr.s_addr;
+ } else {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n");
+ sess->client_addr = 0;
+ }
+ } else
+ sess->client_addr = gg_dcc_ip;
+
+ if (sess->protocol_version >= 0x2e) {
+ struct gg_login80 l;
+ const char *version = (sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION;
+ uint32_t tmp_version_len = gg_fix32((uint32_t)strlen(GG8_VERSION) + (uint32_t)strlen(version));
+ uint32_t tmp_descr_len = gg_fix32((sess->initial_descr) ? (uint32_t)strlen(sess->initial_descr) : 0);
+
+ memset(&l, 0, sizeof(l));
+ l.uin = gg_fix32(sess->uin);
+ memcpy(l.language, GG8_LANG, sizeof(l.language));
+ l.hash_type = sess->hash_type;
+ memcpy(l.hash, login_hash, sizeof(login_hash));
+ l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
+ l.flags = gg_fix32(sess->status_flags);
+ l.features = gg_fix32(sess->protocol_features);
+ l.image_size = sess->image_size;
+ l.dunno2 = 0x64;
+
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80 packet\n");
+ ret = gg_send_packet(sess, GG_LOGIN80,
+ &l, sizeof(l),
+ &tmp_version_len, sizeof(uint32_t), GG8_VERSION, strlen(GG8_VERSION), version, strlen(version),
+ &tmp_descr_len, sizeof(uint32_t), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
+ NULL);
+
+ } else if (sess->protocol_version == 0x2d) {
+ struct gg_login70 l;
+
+ memset(&l, 0, sizeof(l));
+ l.uin = gg_fix32(sess->uin);
+ l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr;
+ l.local_port = gg_fix16((uint16_t)((sess->external_port > 1023) ? sess->external_port : gg_dcc_port));
+ l.hash_type = sess->hash_type;
+ memcpy(l.hash, login_hash, sizeof(login_hash));
+ l.image_size = sess->image_size;
+ l.dunno2 = 0x64;
+ l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
+ l.version = gg_fix32(sess->protocol_version | sess->protocol_flags);
+
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80BETA packet\n");
+ ret = gg_send_packet(sess, GG_LOGIN80BETA,
+ &l, sizeof(l),
+ sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
+ (sess->initial_descr) ? "\0" : NULL, (sess->initial_descr) ? 1 : 0,
+ NULL);
+ } else {
+ struct gg_login70 l;
+
+ memset(&l, 0, sizeof(l));
+ l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr;
+ l.uin = gg_fix32(sess->uin);
+ l.local_port = gg_fix16((uint16_t)((sess->external_port > 1023) ? sess->external_port : gg_dcc_port));
+ l.hash_type = sess->hash_type;
+ memcpy(l.hash, login_hash, sizeof(login_hash));
+ l.image_size = sess->image_size;
+ l.dunno2 = 0xbe;
+ l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
+ l.version = gg_fix32(sess->protocol_version | sess->protocol_flags);
+
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN70 packet\n");
+ ret = gg_send_packet(sess, GG_LOGIN70,
+ &l, sizeof(l),
+ sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
+ NULL);
+ }
+
+ free(sess->initial_descr);
+ sess->initial_descr = NULL;
+
+ if (ret == -1) {
+ gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno));
+ errno2 = errno;
+ gg_sock_close(sess->fd);
+ errno = errno2;
+ sess->fd = -1;
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_WRITING;
+ sess->state = GG_STATE_IDLE;
+ break;
+ }
+
+ sess->state = GG_STATE_READING_REPLY;
+ sess->check = GG_CHECK_READ;
+
+ break;
+ }
+
+ case GG_STATE_READING_REPLY:
+ {
+ struct gg_header *h;
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n");
+
+ if (!(h = gg_recv_packet(sess))) {
+ if (errno == EAGAIN) {
+ sess->check = GG_CHECK_READ;
+ break;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_READING;
+ sess->state = GG_STATE_IDLE;
+ errno2 = errno;
+ gg_sock_close(sess->fd);
+ errno = errno2;
+ sess->fd = -1;
+ break;
+ }
+
+ if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL || h->type == GG_LOGIN80_OK) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n");
+ e->type = GG_EVENT_CONN_SUCCESS;
+ sess->state = GG_STATE_CONNECTED;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = -1;
+ sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL;
+ free(h);
+ break;
+ }
+
+ if (h->type == GG_LOGIN_FAILED || h->type == GG_LOGIN80_FAILED) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n");
+ e->event.failure = GG_FAILURE_PASSWORD;
+ errno = EACCES;
+ } else if (h->type == GG_DISCONNECTING) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n");
+ e->event.failure = GG_FAILURE_INTRUDER;
+ errno = EACCES;
+ } else {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n");
+ e->event.failure = GG_FAILURE_INVALID;
+ errno = EINVAL;
+ }
+
+ e->type = GG_EVENT_CONN_FAILED;
+ sess->state = GG_STATE_IDLE;
+ errno2 = errno;
+ gg_sock_close(sess->fd);
+ errno = errno2;
+ sess->fd = -1;
+ free(h);
+
+ break;
+ }
+
+ case GG_STATE_CONNECTED:
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n");
+
+ sess->last_event = (int)time(NULL);
+
+ if ((res = gg_watch_fd_connected(sess, e)) == -1) {
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno));
+
+ if (errno == EAGAIN) {
+ e->type = GG_EVENT_NONE;
+ res = 0;
+ } else
+ res = -1;
+ }
+
+ sess->check = GG_CHECK_READ;
+
+ break;
+ }
+ }
+
+done:
+ if (res == -1) {
+ free(e);
+ e = NULL;
+ } else {
+ if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED))
+ sess->check |= GG_CHECK_WRITE;
+ }
+
+ return e;
+
+fail_connecting:
+ if (sess->fd != -1) {
+ errno2 = errno;
+ gg_sock_close(sess->fd);
+ errno = errno2;
+ sess->fd = -1;
+ }
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_CONNECTING;
+ sess->state = GG_STATE_IDLE;
+ goto done;
+
+fail_resolving:
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_RESOLVING;
+ sess->state = GG_STATE_IDLE;
+ goto done;
+
+fail_unavailable:
+ e->type = GG_EVENT_CONN_FAILED;
+ e->event.failure = GG_FAILURE_UNAVAILABLE;
+ sess->state = GG_STATE_IDLE;
+ goto done;
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/http.c b/protocols/Gadu-Gadu/src/libgadu/http.c
new file mode 100644
index 0000000000..c070b3ee53
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/http.c
@@ -0,0 +1,544 @@
+/* coding: UTF-8 */
+/* $Id: http.c 11370 2010-03-13 16:17:54Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 http.c
+ *
+ * \brief Obsługa połączeń HTTP
+ */
+
+#include <sys/types.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif /* _WIN32 */
+
+#include "compat.h"
+#include "libgadu.h"
+#include "resolver.h"
+
+#include <ctype.h>
+#include <errno.h>
+#ifndef _WIN32
+#include <netdb.h>
+#endif /* _WIN32 */
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif /* _WIN32 */
+
+/**
+ * Rozpoczyna połączenie HTTP.
+ *
+ * Funkcja przeprowadza połączenie HTTP przy połączeniu synchronicznym,
+ * zwracając wynik w polach struktury \c gg_http, lub błąd, gdy sesja się
+ * nie powiedzie.
+ *
+ * Przy połączeniu asynchronicznym, funkcja rozpoczyna połączenie, a dalsze
+ * etapy będą przeprowadzane po wykryciu zmian (\c watch) na obserwowanym
+ * deskryptorze (\c fd) i wywołaniu funkcji \c gg_http_watch_fd().
+ *
+ * Po zakończeniu, należy zwolnić strukturę za pomocą funkcji
+ * \c gg_http_free(). Połączenie asynchroniczne można zatrzymać w każdej
+ * chwili za pomocÄ… \c gg_http_stop().
+ *
+ * \param hostname Adres serwera
+ * \param port Port serwera
+ * \param async Flaga asynchronicznego połączenia
+ * \param method Metoda HTTP
+ * \param path Ścieżka do zasobu (musi być poprzedzona znakiem '/')
+ * \param header Nagłówek zapytania plus ewentualne dane dla POST
+ *
+ * \return Zaalokowana struktura \c gg_http lub NULL, jeśli wystąpił błąd.
+ *
+ * \ingroup http
+ */
+struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header)
+{
+ struct gg_http *h;
+
+ if (!hostname || !port || !method || !path || !header) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n");
+ errno = EFAULT;
+ return NULL;
+ }
+
+ if (!(h = malloc(sizeof(*h))))
+ return NULL;
+ memset(h, 0, sizeof(*h));
+
+ h->async = async;
+ h->port = port;
+ h->fd = -1;
+ h->type = GG_SESSION_HTTP;
+
+ gg_http_set_resolver(h, GG_RESOLVER_DEFAULT);
+
+ if (gg_proxy_enabled) {
+ char *auth = gg_proxy_auth();
+
+ h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s",
+ method, hostname, port, path, (auth) ? auth :
+ "", header);
+ hostname = gg_proxy_host;
+ h->port = port = gg_proxy_port;
+ free(auth);
+
+ } else {
+ h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s",
+ method, path, header);
+ }
+
+ if (!h->query) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n");
+ free(h);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query);
+
+ if (async) {
+ if (h->resolver_start(&h->fd, &h->resolver, hostname) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n");
+ gg_http_free(h);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver);
+
+ h->state = GG_STATE_RESOLVING;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ } else {
+ struct in_addr addr;
+
+ if (gg_gethostbyname_real(hostname, &addr, 0) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n");
+ gg_http_free(h);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if ((h->fd = gg_connect(&addr, port, 0)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ gg_http_free(h);
+ return NULL;
+ }
+
+ h->state = GG_STATE_CONNECTING;
+
+ while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) {
+ if (gg_http_watch_fd(h) == -1)
+ break;
+ }
+
+ if (h->state != GG_STATE_PARSING) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n");
+ gg_http_free(h);
+ return NULL;
+ }
+ }
+
+ h->callback = gg_http_watch_fd;
+ h->destroy = gg_http_free;
+
+ return h;
+}
+
+#ifndef DOXYGEN
+
+#define gg_http_error(x) \
+ gg_sock_close(h->fd); \
+ h->fd = -1; \
+ h->state = GG_STATE_ERROR; \
+ h->error = x; \
+ return 0;
+
+#endif /* DOXYGEN */
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe
+ * \c GG_STATE_PARSING. W tym miejscu działanie przejmuje zwykle funkcja
+ * korzystająca z \c gg_http_watch_fd(). W przypadku błędu połączenia,
+ * pole \c state będzie równe \c GG_STATE_ERROR, a kod błędu znajdzie się
+ * w polu \c error.
+ *
+ * \param h Struktura połączenia
+ *
+ * \return \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup http
+ */
+int gg_http_watch_fd(struct gg_http *h)
+{
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h);
+
+ if (!h) {
+ gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (h->state == GG_STATE_RESOLVING) {
+ struct in_addr a;
+
+ gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n");
+
+ if (gg_sock_read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) {
+ gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n");
+ gg_http_error(GG_ERROR_RESOLVING);
+ }
+
+ gg_sock_close(h->fd);
+ h->fd = -1;
+
+ h->resolver_cleanup(&h->resolver, 0);
+
+ gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port);
+
+ if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ gg_http_error(GG_ERROR_CONNECTING);
+ }
+
+ h->state = GG_STATE_CONNECTING;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+
+ return 0;
+ }
+
+ if (h->state == GG_STATE_CONNECTING) {
+ int res = 0;
+ unsigned int res_size = sizeof(res);
+
+ if (h->async && (gg_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+ gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno));
+ gg_sock_close(h->fd);
+ h->fd = -1;
+ h->state = GG_STATE_ERROR;
+ h->error = GG_ERROR_CONNECTING;
+ if (res)
+ errno = res;
+ return 0;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n");
+
+ h->state = GG_STATE_SENDING_QUERY;
+ }
+
+ if (h->state == GG_STATE_SENDING_QUERY) {
+ int res;
+
+ if ((res = gg_sock_write(h->fd, h->query, (int)strlen(h->query))) < 1) {
+ gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno);
+ gg_http_error(GG_ERROR_WRITING);
+ }
+
+ if (res < (int)strlen(h->query)) {
+ gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res);
+
+ memmove(h->query, h->query + res, strlen(h->query) - res + 1);
+ h->state = GG_STATE_SENDING_QUERY;
+ h->check = GG_CHECK_WRITE;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ } else {
+ gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query));
+ free(h->query);
+ h->query = NULL;
+
+ h->state = GG_STATE_READING_HEADER;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ }
+
+ return 0;
+ }
+
+ if (h->state == GG_STATE_READING_HEADER) {
+ char buf[1024], *tmp;
+ int res;
+
+ if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) {
+ gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno);
+ if (h->header) {
+ free(h->header);
+ h->header = NULL;
+ }
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ if (!res) {
+ gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n");
+ if (h->header) {
+ free(h->header);
+ h->header = NULL;
+ }
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res);
+
+ if (!(tmp = realloc(h->header, h->header_size + res + 1))) {
+ gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n");
+ free(h->header);
+ h->header = NULL;
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ h->header = tmp;
+
+ memcpy(h->header + h->header_size, buf, res);
+ h->header_size += res;
+
+ gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size);
+
+ h->header[h->header_size] = 0;
+
+ if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) {
+ int sep_len = (*tmp == '\r') ? 4 : 2;
+ unsigned int left;
+ char *line;
+
+ left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len);
+
+ gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left);
+
+ /* HTTP/1.1 200 OK */
+ if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) {
+ gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header);
+
+ gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n");
+ free(h->header);
+ h->header = NULL;
+ gg_http_error(GG_ERROR_CONNECTING);
+ }
+
+ h->body_size = 0;
+ line = h->header;
+ *tmp = 0;
+
+ gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header);
+
+ while (line) {
+ if (!strncasecmp(line, "Content-length: ", 16)) {
+ h->body_size = atoi(line + 16);
+ }
+ line = strchr(line, '\n');
+ if (line)
+ line++;
+ }
+
+ if (h->body_size <= 0) {
+ gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n");
+ h->body_size = left;
+ }
+
+ if (left > h->body_size) {
+ gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left);
+ h->body_size = left;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size);
+
+ if (!(h->body = malloc(h->body_size + 1))) {
+ gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1);
+ free(h->header);
+ h->header = NULL;
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ if (left) {
+ memcpy(h->body, tmp + sep_len, left);
+ h->body_done = left;
+ }
+
+ h->body[left] = 0;
+
+ h->state = GG_STATE_READING_DATA;
+ h->check = GG_CHECK_READ;
+ h->timeout = GG_DEFAULT_TIMEOUT;
+ }
+
+ return 0;
+ }
+
+ if (h->state == GG_STATE_READING_DATA) {
+ char buf[1024];
+ int res;
+
+ if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) {
+ gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno);
+ if (h->body) {
+ free(h->body);
+ h->body = NULL;
+ }
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ if (!res) {
+ if (h->body_done >= h->body_size) {
+ gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n");
+ h->state = GG_STATE_PARSING;
+ gg_sock_close(h->fd);
+ h->fd = -1;
+ } else {
+ gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size);
+ if (h->body) {
+ free(h->body);
+ h->body = NULL;
+ }
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ return 0;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res);
+
+ if (h->body_done + res > h->body_size) {
+ char *tmp;
+
+ gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size);
+
+ if (!(tmp = realloc(h->body, h->body_done + res + 1))) {
+ gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1);
+ free(h->body);
+ h->body = NULL;
+ gg_http_error(GG_ERROR_READING);
+ }
+
+ h->body = tmp;
+ h->body_size = h->body_done + res;
+ }
+
+ h->body[h->body_done + res] = 0;
+ memcpy(h->body + h->body_done, buf, res);
+ h->body_done += res;
+
+ gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size);
+
+ return 0;
+ }
+
+ if (h->fd != -1)
+ gg_sock_close(h->fd);
+
+ h->fd = -1;
+ h->state = GG_STATE_ERROR;
+ h->error = 0;
+
+ return -1;
+}
+
+/**
+ * Kończy asynchroniczne połączenie HTTP.
+ *
+ * Po zatrzymaniu należy zwolnić zasoby funkcją \c gg_http_free().
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup http
+ */
+void gg_http_stop(struct gg_http *h)
+{
+ if (!h)
+ return;
+
+ if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE)
+ return;
+
+ if (h->fd != -1) {
+ gg_sock_close(h->fd);
+ h->fd = -1;
+ }
+
+ h->resolver_cleanup(&h->resolver, 1);
+}
+
+/**
+ * \internal Zwalnia pola struktury \c gg_http.
+ *
+ * Funkcja zwalnia same pola, nie zwalnia struktury.
+ *
+ * \param h Struktura połączenia
+ */
+void gg_http_free_fields(struct gg_http *h)
+{
+ if (!h)
+ return;
+
+ if (h->body) {
+ free(h->body);
+ h->body = NULL;
+ }
+
+ if (h->query) {
+ free(h->query);
+ h->query = NULL;
+ }
+
+ if (h->header) {
+ free(h->header);
+ h->header = NULL;
+ }
+}
+
+/**
+ * Zwalnia zasoby po połączeniu HTTP.
+ *
+ * Jeśli połączenie nie zostało jeszcze zakończone, jest przerywane.
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup http
+ */
+void gg_http_free(struct gg_http *h)
+{
+ if (!h)
+ return;
+
+ gg_http_stop(h);
+ gg_http_free_fields(h);
+ free(h);
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/internal.h b/protocols/Gadu-Gadu/src/libgadu/internal.h
new file mode 100644
index 0000000000..0ffd39fdd5
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/internal.h
@@ -0,0 +1,48 @@
+/* coding: UTF-8 */
+/* $Id$ */
+
+/*
+ * (C) Copyright 2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef LIBGADU_INTERNAL_H
+#define LIBGADU_INTERNAL_H
+
+#include "libgadu.h"
+
+struct gg_dcc7_relay {
+ uint32_t addr;
+ uint16_t port;
+ uint8_t family;
+};
+
+typedef struct gg_dcc7_relay gg_dcc7_relay_t;
+
+char *gg_cp_to_utf8(const char *b);
+char *gg_utf8_to_cp(const char *b);
+int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length);
+void gg_debug_dump_session(struct gg_session *sess, const void *buf, unsigned int buf_length, const char *format, ...);
+
+int gg_resolve(int *fd, int *pid, const char *hostname);
+int gg_resolve_pthread(int *fd, void **resolver, const char *hostname);
+void gg_resolve_pthread_cleanup(void *resolver, int kill);
+
+#ifdef GG_CONFIG_HAVE_UINT64_T
+uint64_t gg_fix64(uint64_t x);
+#endif
+
+#endif /* LIBGADU_INTERNAL_H */
diff --git a/protocols/Gadu-Gadu/src/libgadu/libgadu.c b/protocols/Gadu-Gadu/src/libgadu/libgadu.c
new file mode 100644
index 0000000000..f06f8a5b84
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/libgadu.c
@@ -0,0 +1,2353 @@
+/* coding: UTF-8 */
+/* $Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ * Arkadiusz Miśkiewicz <arekm@pld-linux.org>
+ * Tomasz Chiliński <chilek@chilan.com>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 libgadu.c
+ *
+ * \brief Główny moduł biblioteki
+ */
+
+#ifndef _WIN64
+#define _USE_32BIT_TIME_T
+#endif
+
+#include <sys/types.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef sun
+# include <sys/filio.h>
+#endif
+#endif /* _WIN32 */
+
+#include "compat.h"
+#include "libgadu.h"
+#include "protocol.h"
+#include "resolver.h"
+#include "internal.h"
+
+#include <errno.h>
+#ifndef _WIN32
+#include <netdb.h>
+#endif /* _WIN32 */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif /* _WIN32 */
+#if !defined(GG_CONFIG_MIRANDA) && defined(GG_CONFIG_HAVE_OPENSSL)
+# include <openssl/err.h>
+# include <openssl/rand.h>
+#endif
+
+/**
+ * Poziom rejestracji informacji odpluskwiajÄ…cych. Zmienna jest maskÄ… bitowÄ…
+ * składającą się ze stałych \c GG_DEBUG_...
+ *
+ * \ingroup debug
+ */
+int gg_debug_level = 0;
+
+/**
+ * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
+ * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, sÄ… rĂłwne
+ * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr).
+ *
+ * \param level Poziom rejestracji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentĂłw (zgodna z \c printf)
+ *
+ * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session.
+ *
+ * \ingroup debug
+ */
+void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL;
+
+/**
+ * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
+ * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, sÄ… rĂłwne
+ * \c NULL, informacje są wysyłane do standardowego wyjścia błędu.
+ *
+ * \param sess Sesja ktĂłrej dotyczy informacja lub \c NULL
+ * \param level Poziom rejestracji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentĂłw (zgodna z \c printf)
+ *
+ * \note Funkcja przesłania przez \c gg_debug_handler_session.
+ *
+ * \ingroup debug
+ */
+void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL;
+
+/**
+ * Port gniazda nasłuchującego dla połączeń bezpośrednich.
+ *
+ * \ingroup ip
+ */
+int gg_dcc_port = 0;
+
+/**
+ * Adres IP gniazda nasłuchującego dla połączeń bezpośrednich.
+ *
+ * \ingroup ip
+ */
+unsigned long gg_dcc_ip = 0;
+
+/**
+ * Adres lokalnego interfejsu IP, z którego wywoływane są wszystkie połączenia.
+ *
+ * \ingroup ip
+ */
+unsigned long gg_local_ip = 0;
+
+/**
+ * Flaga włączenia połączeń przez serwer pośredniczący.
+ *
+ * \ingroup proxy
+ */
+int gg_proxy_enabled = 0;
+
+/**
+ * Adres serwera pośredniczącego.
+ *
+ * \ingroup proxy
+ */
+char *gg_proxy_host = NULL;
+
+/**
+ * Port serwera pośredniczącego.
+ *
+ * \ingroup proxy
+ */
+int gg_proxy_port = 0;
+
+/**
+ * Flaga używania serwera pośredniczącego jedynie dla usług HTTP.
+ *
+ * \ingroup proxy
+ */
+int gg_proxy_http_only = 0;
+
+/**
+ * Nazwa użytkownika do autoryzacji serwera pośredniczącego.
+ *
+ * \ingroup proxy
+ */
+char *gg_proxy_username = NULL;
+
+/**
+ * Hasło użytkownika do autoryzacji serwera pośredniczącego.
+ *
+ * \ingroup proxy
+ */
+char *gg_proxy_password = NULL;
+
+#ifndef DOXYGEN
+
+#ifndef lint
+static char rcsid[]
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+= "$Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $";
+#endif
+
+#endif /* DOXYGEN */
+
+/**
+ * Zwraca wersjÄ™ biblioteki.
+ *
+ * \return WskaĹşnik na statyczny bufor z wersjÄ… biblioteki.
+ *
+ * \ingroup version
+ */
+const char *gg_libgadu_version()
+{
+ return GG_LIBGADU_VERSION;
+}
+
+#ifdef GG_CONFIG_HAVE_UINT64_T
+/**
+ * \internal Zamienia kolejność bajtów w 64-bitowym słowie.
+ *
+ * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
+ * big-endianowych odwraca kolejność bajtów w słowie.
+ *
+ * \param x Liczba do zamiany
+ *
+ * \return Liczba z odpowiednią kolejnością bajtów
+ *
+ * \ingroup helper
+ */
+uint64_t gg_fix64(uint64_t x)
+{
+#ifndef GG_CONFIG_BIGENDIAN
+ return x;
+#else
+ return (uint64_t)
+ (((x & (uint64_t) 0x00000000000000ffULL) << 56) |
+ ((x & (uint64_t) 0x000000000000ff00ULL) << 40) |
+ ((x & (uint64_t) 0x0000000000ff0000ULL) << 24) |
+ ((x & (uint64_t) 0x00000000ff000000ULL) << 8) |
+ ((x & (uint64_t) 0x000000ff00000000ULL) >> 8) |
+ ((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) |
+ ((x & (uint64_t) 0x00ff000000000000ULL) >> 40) |
+ ((x & (uint64_t) 0xff00000000000000ULL) >> 56));
+#endif
+}
+#endif /* GG_CONFIG_HAVE_UINT64_T */
+
+/**
+ * \internal Zamienia kolejność bajtów w 32-bitowym słowie.
+ *
+ * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
+ * big-endianowych odwraca kolejność bajtów w słowie.
+ *
+ * \param x Liczba do zamiany
+ *
+ * \return Liczba z odpowiednią kolejnością bajtów
+ *
+ * \ingroup helper
+ */
+uint32_t gg_fix32(uint32_t x)
+{
+#ifndef GG_CONFIG_BIGENDIAN
+ return x;
+#else
+ return (uint32_t)
+ (((x & (uint32_t) 0x000000ffU) << 24) |
+ ((x & (uint32_t) 0x0000ff00U) << 8) |
+ ((x & (uint32_t) 0x00ff0000U) >> 8) |
+ ((x & (uint32_t) 0xff000000U) >> 24));
+#endif
+}
+
+/**
+ * \internal Zamienia kolejność bajtów w 16-bitowym słowie.
+ *
+ * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
+ * big-endianowych zamienia kolejność bajtów w słowie.
+ *
+ * \param x Liczba do zamiany
+ *
+ * \return Liczba z odpowiednią kolejnością bajtów
+ *
+ * \ingroup helper
+ */
+uint16_t gg_fix16(uint16_t x)
+{
+#ifndef GG_CONFIG_BIGENDIAN
+ return x;
+#else
+ return (uint16_t)
+ (((x & (uint16_t) 0x00ffU) << 8) |
+ ((x & (uint16_t) 0xff00U) >> 8));
+#endif
+}
+
+/**
+ * \internal Liczy skrót z hasła i ziarna.
+ *
+ * \param password Hasło
+ * \param seed Ziarno podane przez serwer
+ *
+ * \return Wartość skrótu
+ */
+unsigned int gg_login_hash(const unsigned char *password, unsigned int seed)
+{
+ unsigned int x, y, z;
+
+ y = seed;
+
+ for (x = 0; *password; password++) {
+ x = (x & 0xffffff00) | *password;
+ y ^= x;
+ y += x;
+ x <<= 8;
+ y ^= x;
+ x <<= 8;
+ y -= x;
+ x <<= 8;
+ y ^= x;
+
+ z = y & 0x1F;
+ y = (y << z) | (y >> (32 - z));
+ }
+
+ return y;
+}
+
+/**
+ * \internal Odbiera od serwera dane binarne.
+ *
+ * Funkcja odbiera dane od serwera zajmując się SSL/TLS w razie konieczności.
+ * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi
+ * wywołaniami systemowymi.
+ *
+ * \param sess Struktura sesji
+ * \param buf Bufor na danymi
+ * \param length Długość bufora
+ *
+ * \return To samo co funkcja systemowa \c read
+ */
+int gg_read(struct gg_session *sess, char *buf, int length)
+{
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->ssl != NULL)
+ return si.read(sess->ssl, buf, length, 0);
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL) {
+ for (;;) {
+ int res, err;
+
+ res = SSL_read(sess->ssl, buf, length);
+
+ if (res < 0) {
+ err = SSL_get_error(sess->ssl, res);
+
+ if (err == SSL_ERROR_SYSCALL && errno == EINTR)
+ continue;
+
+ if (err == SSL_ERROR_WANT_READ)
+ errno = EAGAIN;
+ else if (err != SSL_ERROR_SYSCALL)
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ return res;
+ }
+ }
+#endif
+
+ return gg_sock_read(sess->fd, buf, length);
+}
+
+/**
+ * \internal Wysyła do serwera dane binarne.
+ *
+ * Funkcja wysyła dane do serwera zajmując się SSL/TLS w razie konieczności.
+ * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi
+ * wywołaniami systemowymi.
+ *
+ * \note Funkcja nie zajmuje się buforowaniem wysyłanych danych (patrz
+ * gg_write()).
+ *
+ * \param sess Struktura sesji
+ * \param buf Bufor z danymi
+ * \param length Długość bufora
+ *
+ * \return To samo co funkcja systemowa \c write
+ */
+static int gg_write_common(struct gg_session *sess, const char *buf, int length)
+{
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->ssl != NULL)
+ return si.write(sess->ssl, buf, length);
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL) {
+ for (;;) {
+ int res, err;
+
+ res = SSL_write(sess->ssl, (void *)buf, length);
+
+ if (res < 0) {
+ err = SSL_get_error(sess->ssl, res);
+
+ if (err == SSL_ERROR_SYSCALL && errno == EINTR)
+ continue;
+
+ if (err == SSL_ERROR_WANT_WRITE)
+ errno = EAGAIN;
+ else if (err != SSL_ERROR_SYSCALL)
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ return res;
+ }
+ }
+#endif
+
+ return gg_sock_write(sess->fd, buf, length);
+}
+
+
+
+/**
+ * \internal Wysyła do serwera dane binarne.
+ *
+ * Funkcja wysyła dane do serwera zajmując się TLS w razie konieczności.
+ *
+ * \param sess Struktura sesji
+ * \param buf Bufor z danymi
+ * \param length Długość bufora
+ *
+ * \return To samo co funkcja systemowa \c write
+ */
+int gg_write(struct gg_session *sess, const char *buf, int length)
+{
+ int res = 0;
+
+ if (!sess->async) {
+ int written = 0;
+
+ while (written < length) {
+ res = gg_write_common(sess, buf + written, length - written);
+
+ if (res == -1)
+ return -1;
+
+ written += res;
+ res = written;
+ }
+ } else {
+ res = 0;
+
+ if (sess->send_buf == NULL) {
+ res = gg_write_common(sess, buf, length);
+
+ if (res == -1)
+ return -1;
+ }
+
+ if (res < length) {
+ char *tmp;
+
+ if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ sess->send_buf = tmp;
+
+ memcpy(sess->send_buf + sess->send_left, buf + res, length - res);
+
+ sess->send_left += length - res;
+ }
+ }
+
+ return res;
+}
+
+/**
+ * \internal Odbiera pakiet od serwera.
+ *
+ * Funkcja odczytuje nagłówek pakietu, a następnie jego zawartość i zwraca
+ * w zaalokowanym buforze.
+ *
+ * Przy połączeniach asynchronicznych, funkcja może nie być w stanie
+ * skompletować całego pakietu -- w takim przypadku zwróci -1, a kodem błędu
+ * będzie \c EAGAIN.
+ *
+ * \param sess Struktura sesji
+ *
+ * \return WskaĹşnik do zaalokowanego bufora
+ */
+void *gg_recv_packet(struct gg_session *sess)
+{
+ struct gg_header h;
+ char *buf = NULL;
+ int ret = 0;
+ unsigned int offset, size = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess);
+
+ if (!sess) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ if (sess->recv_left < 1) {
+ if (sess->header_buf) {
+ memcpy(&h, sess->header_buf, sess->header_done);
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done);
+ free(sess->header_buf);
+ sess->header_buf = NULL;
+ } else
+ sess->header_done = 0;
+
+ while (sess->header_done < sizeof(h)) {
+ ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done);
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret);
+
+ if (!ret) {
+ errno = ECONNRESET;
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n");
+ return NULL;
+ }
+
+ if (ret == -1) {
+ if (errno == EAGAIN) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n");
+
+ if (!(sess->header_buf = malloc(sess->header_done))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n");
+ return NULL;
+ }
+
+ memcpy(sess->header_buf, &h, sess->header_done);
+
+ errno = EAGAIN;
+
+ return NULL;
+ }
+
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno));
+
+ return NULL;
+ }
+
+ sess->header_done += ret;
+
+ }
+
+ h.type = gg_fix32(h.type);
+ h.length = gg_fix32(h.length);
+ } else
+ memcpy(&h, sess->recv_buf, sizeof(h));
+
+ /* jakieĹ› sensowne limity na rozmiar pakietu */
+ if (h.length > 65535) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length);
+ errno = ERANGE;
+ return NULL;
+ }
+
+ if (sess->recv_left > 0) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n");
+ size = sess->recv_left;
+ offset = sess->recv_done;
+ buf = sess->recv_buf;
+ } else {
+ if (!(buf = malloc(sizeof(h) + h.length + 1))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n");
+ return NULL;
+ }
+
+ memcpy(buf, &h, sizeof(h));
+
+ offset = 0;
+ size = h.length;
+ }
+
+ while (size > 0) {
+ ret = gg_read(sess, buf + sizeof(h) + offset, size);
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret);
+ if (!ret) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n");
+ errno = ECONNRESET;
+ return NULL;
+ }
+ if (ret > -1 && ret <= (int)size) {
+ offset += ret;
+ size -= ret;
+ } else if (ret == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno));
+
+ if (errno == EAGAIN) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size);
+ sess->recv_buf = buf;
+ sess->recv_left = size;
+ sess->recv_done = offset;
+ return NULL;
+ }
+
+ free(buf);
+ return NULL;
+ }
+ }
+
+ sess->recv_left = 0;
+
+ gg_debug_dump_session(sess, buf, sizeof(h) + h.length, "// gg_recv_packet(0x%.2x)", h.type);
+
+ return buf;
+}
+
+/**
+ * \internal Wysyła pakiet do serwera.
+ *
+ * Funkcja konstruuje pakiet do wysłania z dowolnej liczby fragmentów. Jeśli
+ * rozmiar pakietu jest za duży, by móc go wysłać za jednym razem, pozostała
+ * część zostanie zakolejkowana i wysłana, gdy będzie to możliwe.
+ *
+ * \param sess Struktura sesji
+ * \param type Rodzaj pakietu
+ * \param ... Lista kolejnych części pakietu (wskaźnik na bufor i długość
+ * typu \c int) zakończona \c NULL
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_send_packet(struct gg_session *sess, int type, ...)
+{
+ struct gg_header *h;
+ char *tmp;
+ unsigned int tmp_length;
+ void *payload;
+ unsigned int payload_length;
+ va_list ap;
+ int res;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...);\n", sess, type);
+
+ tmp_length = sizeof(struct gg_header);
+
+ if (!(tmp = malloc(tmp_length))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n");
+ return -1;
+ }
+
+ va_start(ap, type);
+
+ payload = va_arg(ap, void *);
+
+ while (payload) {
+ char *tmp2;
+
+ payload_length = va_arg(ap, unsigned int);
+
+ if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n");
+ free(tmp);
+ va_end(ap);
+ return -1;
+ }
+
+ tmp = tmp2;
+
+ memcpy(tmp + tmp_length, payload, payload_length);
+ tmp_length += payload_length;
+
+ payload = va_arg(ap, void *);
+ }
+
+ va_end(ap);
+
+ h = (struct gg_header*) tmp;
+ h->type = gg_fix32(type);
+ h->length = gg_fix32(tmp_length - sizeof(struct gg_header));
+
+ gg_debug_dump_session(sess, tmp, tmp_length, "// gg_send_packet(0x%.2x)", gg_fix32(h->type));
+
+ res = gg_write(sess, tmp, tmp_length);
+
+ free(tmp);
+
+ if (res == -1) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
+ return -1;
+ }
+
+ if (sess->async)
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left);
+
+ if (sess->send_buf)
+ sess->check |= GG_CHECK_WRITE;
+
+ return 0;
+}
+
+/**
+ * \internal Funkcja zwrotna sesji.
+ *
+ * Pole \c callback struktury \c gg_session zawiera wskaĹşnik do tej funkcji.
+ * Wywołuje ona \c gg_watch_fd i zachowuje wynik w polu \c event.
+ *
+ * \note Korzystanie z tej funkcjonalności nie jest już zalecane.
+ *
+ * \param sess Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_session_callback(struct gg_session *sess)
+{
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ return ((sess->event = gg_watch_fd(sess)) != NULL) ? 0 : -1;
+}
+
+/**
+ * ĹÄ…czy siÄ™ z serwerem Gadu-Gadu.
+ *
+ * Przy połączeniu synchronicznym funkcja zakończy działanie po nawiązaniu
+ * połączenia lub gdy wystąpi błąd. Po udanym połączeniu należy wywoływać
+ * funkcjÄ™ \c gg_watch_fd(), ktĂłra odbiera informacje od serwera i zwraca
+ * informacje o zdarzeniach.
+ *
+ * Przy połączeniu asynchronicznym funkcja rozpocznie procedurę połączenia
+ * i zwrĂłci zaalokowanÄ… strukturÄ™. Pole \c fd struktury \c gg_session zawiera
+ * deskryptor, który należy obserwować funkcją \c select, \c poll lub za
+ * pomocą mechanizmów użytej pętli zdarzeń (Glib, Qt itp.). Pole \c check
+ * jest maską bitową mówiącą, czy biblioteka chce być informowana o możliwości
+ * odczytu danych (\c GG_CHECK_READ) czy zapisu danych (\c GG_CHECK_WRITE).
+ * Po zaobserwowaniu zmian na deskryptorze należy wywołać funkcję
+ * \c gg_watch_fd(). Podczas korzystania z połączeń asynchronicznych, w trakcie
+ * połączenia może zostać stworzony dodatkowy proces rozwiązujący nazwę
+ * serwera -- z tego powodu program musi poprawnie obsłużyć sygnał SIGCHLD.
+ *
+ * \note Po nawiązaniu połączenia z serwerem należy wysłać listę kontaktów
+ * za pomocÄ… funkcji \c gg_notify() lub \c gg_notify_ex().
+ *
+ * \param p Struktura opisująca parametry połączenia. Wymagane pola: uin,
+ * password, async.
+ *
+ * \return WskaĹşnik do zaalokowanej struktury sesji \c gg_session lub NULL
+ * w przypadku błędu.
+ *
+ * \ingroup login
+ */
+#ifdef GG_CONFIG_MIRANDA
+struct gg_session *gg_login(const struct gg_login_params *p, SOCKET *gg_sock, int *gg_failno)
+#else
+struct gg_session *gg_login(const struct gg_login_params *p)
+#endif
+{
+ struct gg_session *sess = NULL;
+ char *hostname;
+ int port;
+
+ if (!p) {
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p);
+ errno = EFAULT;
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async);
+
+ if (!(sess = malloc(sizeof(struct gg_session)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n");
+ goto fail;
+ }
+
+ memset(sess, 0, sizeof(struct gg_session));
+
+ if (!p->password || !p->uin) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n");
+ errno = EFAULT;
+ goto fail;
+ }
+
+ if (!(sess->password = strdup(p->password))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n");
+ goto fail;
+ }
+
+ if (p->hash_type < 0 || p->hash_type > GG_LOGIN_HASH_SHA1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unknown hash type (%d)\n", p->hash_type);
+ errno = EFAULT;
+ goto fail;
+ }
+
+ sess->uin = p->uin;
+ sess->state = GG_STATE_RESOLVING;
+ sess->check = GG_CHECK_READ;
+ sess->timeout = GG_DEFAULT_TIMEOUT;
+ sess->async = p->async;
+ sess->type = GG_SESSION_GG;
+ sess->initial_status = p->status;
+ sess->callback = gg_session_callback;
+ sess->destroy = gg_free_session;
+ sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT);
+ sess->server_addr = p->server_addr;
+ sess->external_port = p->external_port;
+ sess->external_addr = p->external_addr;
+ sess->client_port = p->client_port;
+
+ if (p->protocol_features == 0) {
+ sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION;
+ } else {
+ sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77));
+
+ if (!(p->protocol_features & GG_FEATURE_STATUS77))
+ sess->protocol_features |= GG_FEATURE_STATUS80;
+
+ if (!(p->protocol_features & GG_FEATURE_MSG77))
+ sess->protocol_features |= GG_FEATURE_MSG80;
+ }
+
+ if (!(sess->status_flags = p->status_flags))
+ sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM;
+
+ sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION;
+
+ if (p->era_omnix)
+ sess->protocol_flags |= GG_ERA_OMNIX_MASK;
+ if (p->has_audio)
+ sess->protocol_flags |= GG_HAS_AUDIO_MASK;
+ sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL;
+ sess->last_sysmsg = p->last_sysmsg;
+ sess->image_size = p->image_size;
+ sess->pid = -1;
+ sess->encoding = p->encoding;
+
+ if (gg_session_set_resolver(sess, p->resolver) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unsupported resolver type (%d)\n", p->resolver);
+ errno = EFAULT;
+ goto fail;
+ }
+
+ if (p->status_descr) {
+ int max_length;
+
+ if (sess->protocol_version >= 0x2d)
+ max_length = GG_STATUS_DESCR_MAXSIZE;
+ else
+ max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0;
+
+ if (sess->protocol_version >= 0x2d && p->encoding != GG_ENCODING_UTF8)
+ sess->initial_descr = gg_cp_to_utf8(p->status_descr);
+ else
+ sess->initial_descr = strdup(p->status_descr);
+
+ if (!sess->initial_descr) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n");
+ goto fail;
+ }
+
+ // XXX pamiętać, żeby nie ciąć w środku znaku utf-8
+
+ if ((signed)strlen(sess->initial_descr) > max_length)
+ sess->initial_descr[max_length] = 0;
+ }
+
+#ifdef GG_CONFIG_MIRANDA
+ sess->tls = p->tls;
+#endif
+ if (p->tls == 1) {
+#ifdef GG_CONFIG_HAVE_OPENSSL
+ char buf[1024];
+
+ OpenSSL_add_ssl_algorithms();
+
+ if (!RAND_status()) {
+ char rdata[1024];
+ struct {
+ time_t time;
+ void *ptr;
+ } rstruct;
+
+ time(&rstruct.time);
+ rstruct.ptr = (void *) &rstruct;
+
+ RAND_seed((void *) rdata, sizeof(rdata));
+ RAND_seed((void *) &rstruct, sizeof(rstruct));
+ }
+
+ sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method());
+
+ if (!sess->ssl_ctx) {
+ ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+ gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf);
+ goto fail;
+ }
+
+ SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL);
+
+ sess->ssl = SSL_new(sess->ssl_ctx);
+
+ if (!sess->ssl) {
+ ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+ gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf);
+ goto fail;
+ }
+#elif !defined(GG_CONFIG_MIRANDA)
+ gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n");
+#endif
+ }
+
+ if (gg_proxy_enabled) {
+ hostname = gg_proxy_host;
+ sess->proxy_port = port = gg_proxy_port;
+ } else {
+ hostname = GG_APPMSG_HOST;
+ port = GG_APPMSG_PORT;
+ }
+
+ if (p->hash_type)
+ sess->hash_type = p->hash_type;
+ else
+ sess->hash_type = GG_LOGIN_HASH_SHA1;
+
+ if (!p->async) {
+ struct in_addr addr;
+
+ if (!sess->server_addr) {
+ if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
+ if (gg_gethostbyname_real(hostname, &addr, 0) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname);
+#ifdef GG_CONFIG_MIRANDA
+ errno = EACCES;
+ *gg_failno = GG_FAILURE_RESOLVING;
+#endif
+ goto fail;
+ }
+ }
+ } else {
+ addr.s_addr = sess->server_addr;
+ port = sess->port;
+ }
+
+ sess->hub_addr = addr.s_addr;
+
+ if (gg_proxy_enabled)
+ sess->proxy_addr = addr.s_addr;
+
+#ifdef GG_CONFIG_MIRANDA
+ if ((sess->fd = gg_connect_internal(&addr, port, 0, gg_sock)) == -1) {
+#else
+ if ((sess->fd = gg_connect(&addr, port, 0)) == -1) {
+#endif
+ gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+
+ /* nie wyszło? próbujemy portu 443. */
+ if (sess->server_addr) {
+ sess->port = GG_HTTPS_PORT;
+
+#ifdef GG_CONFIG_MIRANDA
+ if ((sess->fd = gg_connect_internal(&addr, GG_HTTPS_PORT, 0, gg_sock)) == -1) {
+#else
+ if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, 0)) == -1) {
+#endif
+ /* ostatnia deska ratunku zawiodła?
+ * w takim razie zwijamy manatki. */
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail;
+ }
+ } else {
+ goto fail;
+ }
+ }
+
+ if (sess->server_addr)
+ sess->state = GG_STATE_CONNECTING_GG;
+ else
+ sess->state = GG_STATE_CONNECTING_HUB;
+
+ while (sess->state != GG_STATE_CONNECTED) {
+ struct gg_event *e;
+
+ if (!(e = gg_watch_fd(sess))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n");
+ goto fail;
+ }
+
+ if (e->type == GG_EVENT_CONN_FAILED) {
+ errno = EACCES;
+#ifdef GG_CONFIG_MIRANDA
+ *gg_failno = e->event.failure;
+#endif
+ gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n");
+ gg_event_free(e);
+ goto fail;
+ }
+
+ gg_event_free(e);
+ }
+
+ return sess;
+ }
+
+ if (!sess->server_addr || gg_proxy_enabled) {
+ if (sess->resolver_start(&sess->fd, &sess->resolver, hostname) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail;
+ }
+ } else {
+ if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno));
+ goto fail;
+ }
+ sess->state = GG_STATE_CONNECTING_GG;
+ sess->check = GG_CHECK_WRITE;
+ sess->soft_timeout = 1;
+ }
+
+ return sess;
+
+fail:
+ gg_free_session(sess);
+
+ return NULL;
+}
+
+/**
+ * Wysyła do serwera pakiet utrzymania połączenia.
+ *
+ * Klient powinien regularnie co minutę wysyłać pakiet utrzymania połączenia,
+ * inaczej serwer uzna, że klient stracił łączność z siecią i zerwie
+ * połączenie.
+ *
+ * \param sess Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup login
+ */
+int gg_ping(struct gg_session *sess)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ return gg_send_packet(sess, GG_PING, NULL);
+}
+
+/**
+ * Kończy połączenie z serwerem.
+ *
+ * Funkcja nie zwalnia zasobów, więc po jej wywołaniu należy użyć
+ * \c gg_free_session(). Jeśli chce się ustawić opis niedostępności, należy
+ * wcześniej wywołać funkcję \c gg_change_status_descr() lub
+ * \c gg_change_status_descr_time().
+ *
+ * \note Jeśli w buforze nadawczym połączenia z serwerem znajdują się jeszcze
+ * dane (np. z powodu strat pakietĂłw na Ĺ‚Ä…czu), prawdopodobnie zostanÄ… one
+ * utracone przy zrywaniu połączenia. Aby mieć pewność, że opis statusu
+ * zostanie zachowany, należy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR
+ * za pomocą funkcji \c gg_change_status_descr() i poczekać na zdarzenie
+ * \c GG_EVENT_DISCONNECT_ACK.
+ *
+ * \param sess Struktura sesji
+ *
+ * \ingroup login
+ */
+void gg_logoff(struct gg_session *sess)
+{
+ if (!sess)
+ return;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess);
+
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->ssl != NULL)
+ si.shutdown(sess->ssl);
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL)
+ SSL_shutdown(sess->ssl);
+#endif
+
+ sess->resolver_cleanup(&sess->resolver, 1);
+
+ if (sess->fd != -1) {
+ shutdown(sess->fd, SHUT_RDWR);
+ gg_sock_close(sess->fd);
+ sess->fd = -1;
+ }
+
+ if (sess->send_buf) {
+ free(sess->send_buf);
+ sess->send_buf = NULL;
+ sess->send_left = 0;
+ }
+}
+
+/**
+ * Zwalnia zasoby używane przez połączenie z serwerem. Funkcję należy wywołać
+ * po zamknięciu połączenia z serwerem, by nie doprowadzić do wycieku zasobów
+ * systemowych.
+ *
+ * \param sess Struktura sesji
+ *
+ * \ingroup login
+ */
+void gg_free_session(struct gg_session *sess)
+{
+ struct gg_dcc7 *dcc;
+
+ if (!sess)
+ return;
+
+ /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */
+
+ free(sess->password);
+ free(sess->initial_descr);
+ free(sess->client_version);
+ free(sess->header_buf);
+
+#ifdef GG_CONFIG_MIRANDA
+ if (sess->ssl != NULL)
+ si.sfree(sess->ssl);
+#elif GG_CONFIG_HAVE_OPENSSL
+ if (sess->ssl != NULL)
+ SSL_free(sess->ssl);
+
+ if (sess->ssl_ctx)
+ SSL_CTX_free(sess->ssl_ctx);
+#endif
+
+ sess->resolver_cleanup(&sess->resolver, 1);
+
+ if (sess->fd != -1)
+ gg_sock_close(sess->fd);
+
+ while (sess->images)
+ gg_image_queue_remove(sess, sess->images, 1);
+
+ free(sess->send_buf);
+
+ for (dcc = sess->dcc7_list; dcc; dcc = dcc->next)
+ dcc->sess = NULL;
+
+ free(sess);
+}
+
+#ifndef DOXYGEN
+
+/**
+ * \internal Funkcja wysyłająca pakiet zmiany statusu użytkownika.
+ *
+ * \param sess Struktura sesji
+ * \param status Nowy status uĹĽytkownika
+ * \param descr Opis statusu uĹĽytkownika (lub \c NULL)
+ * \param time Czas powrotu w postaci uniksowego znacznika czasu (lub 0)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup status
+ */
+static int gg_change_status_common(struct gg_session *sess, int status, const char *descr, int time)
+{
+ char *new_descr = NULL;
+ uint32_t new_time;
+ int descr_len = 0;
+ int descr_len_max;
+ int packet_type;
+ int append_null = 0;
+ int res;
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ /* XXX, obcinać stany których stary protokół niezna (czyt. dnd->aw; ffc->av) */
+
+ /* dodaj flagę obsługi połączeń głosowych zgodną z GG 7.x */
+ if ((sess->protocol_version >= 0x2a) && (sess->protocol_version < 0x2d /* ? */ ) && (sess->protocol_flags & GG_HAS_AUDIO_MASK) && !GG_S_I(status))
+ status |= GG_STATUS_VOICE_MASK;
+
+ sess->status = status;
+
+ if (sess->protocol_version >= 0x2d) {
+ if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) {
+ new_descr = gg_cp_to_utf8(descr);
+
+ if (!new_descr)
+ return -1;
+ }
+
+ if (sess->protocol_version >= 0x2e)
+ packet_type = GG_NEW_STATUS80;
+ else /* sess->protocol_version == 0x2d */
+ packet_type = GG_NEW_STATUS80BETA;
+ descr_len_max = GG_STATUS_DESCR_MAXSIZE;
+ append_null = 1;
+
+ } else {
+ packet_type = GG_NEW_STATUS;
+ descr_len_max = GG_STATUS_DESCR_MAXSIZE_PRE_8_0;
+
+ if (time != 0)
+ append_null = 1;
+ }
+
+ if (descr) {
+ descr_len = (int)strlen((new_descr) ? new_descr : descr);
+
+ if (descr_len > descr_len_max)
+ descr_len = descr_len_max;
+
+ // XXX pamiętać o tym, żeby nie ucinać w środku znaku utf-8
+ }
+
+ if (time)
+ new_time = gg_fix32(time);
+
+ if (packet_type == GG_NEW_STATUS80) {
+ struct gg_new_status80 p;
+
+ p.status = gg_fix32(status);
+ p.flags = gg_fix32(sess->status_flags);
+ p.description_size = gg_fix32(descr_len);
+ res = gg_send_packet(sess,
+ packet_type,
+ &p,
+ sizeof(p),
+ (new_descr) ? new_descr : descr,
+ descr_len,
+ NULL);
+
+ } else {
+ struct gg_new_status p;
+
+ p.status = gg_fix32(status);
+ res = gg_send_packet(sess,
+ packet_type,
+ &p,
+ sizeof(p),
+ (new_descr) ? new_descr : descr,
+ descr_len,
+ (append_null) ? "\0" : NULL,
+ (append_null) ? 1 : 0,
+ (time) ? &new_time : NULL,
+ (time) ? sizeof(new_time) : 0,
+ NULL);
+ }
+
+ free(new_descr);
+ return res;
+}
+
+#endif /* DOXYGEN */
+
+/**
+ * Zmienia status uĹĽytkownika.
+ *
+ * \param sess Struktura sesji
+ * \param status Nowy status uĹĽytkownika
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup status
+ */
+int gg_change_status(struct gg_session *sess, int status)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status);
+
+ return gg_change_status_common(sess, status, NULL, 0);
+}
+
+/**
+ * Zmienia status uĹĽytkownika na status opisowy.
+ *
+ * \param sess Struktura sesji
+ * \param status Nowy status uĹĽytkownika
+ * \param descr Opis statusu uĹĽytkownika
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup status
+ */
+int gg_change_status_descr(struct gg_session *sess, int status, const char *descr)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr);
+
+ return gg_change_status_common(sess, status, descr, 0);
+}
+
+/**
+ * Zmienia status uĹĽytkownika na status opisowy z podanym czasem powrotu.
+ *
+ * \param sess Struktura sesji
+ * \param status Nowy status uĹĽytkownika
+ * \param descr Opis statusu uĹĽytkownika
+ * \param time Czas powrotu w postaci uniksowego znacznika czasu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup status
+ */
+int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time);
+
+ return gg_change_status_common(sess, status, descr, time);
+}
+
+/**
+ * Funkcja zmieniajÄ…ca flagi statusu.
+ *
+ * \param sess Struktura sesji
+ * \param flags Nowe flagi statusu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \note Aby zmiany weszły w życie, należy ponownie ustawić status za pomocą
+ * funkcji z rodziny \c gg_change_status().
+ *
+ * \ingroup status
+ */
+int gg_change_status_flags(struct gg_session *sess, int flags)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags);
+
+ if (sess == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ sess->status_flags = flags;
+
+ return 0;
+}
+
+/**
+ * Wysyła wiadomość do użytkownika.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipient Numer adresata
+ * \param message Treść wiadomości
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message);
+
+ return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, NULL, 0);
+}
+
+/**
+ * Wysyła wiadomość formatowaną.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipient Numer adresata
+ * \param message Treść wiadomości
+ * \param format Informacje o formatowaniu
+ * \param formatlen Długość informacji o formatowaniu
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen);
+
+ return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, format, formatlen);
+}
+
+/**
+ * Wysyła wiadomość w ramach konferencji.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipients_count Liczba adresatĂłw
+ * \param recipients WskaĹşnik do tablicy z numerami adresatĂłw
+ * \param message Treść wiadomości
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message)
+{
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message);
+
+ return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0);
+}
+
+/**
+ * \internal Dodaje tekst na koniec bufora.
+ *
+ * \param dst WskaĹşnik na bufor roboczy
+ * \param pos Wskaźnik na aktualne położenie w buforze roboczym
+ * \param src Dodawany tekst
+ * \param len Długość dodawanego tekstu
+ */
+static void gg_append(char *dst, int *pos, const void *src, int len)
+{
+ if (dst != NULL)
+ memcpy(&dst[*pos], src, len);
+
+ *pos += len;
+}
+
+/**
+ * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML.
+ *
+ * \param dst Bufor wynikowy (może być \c NULL)
+ * \param src Tekst źródłowy w UTF-8
+ * \param format Atrybuty tekstu źródłowego
+ * \param format_len Długość bloku atrybutów tekstu źródłowego
+ *
+ * \note Wynikowy tekst nie jest idealnym kodem HTML, poniewaĹĽ ma jak
+ * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient.
+ *
+ * \note Dokleja \c \\0 na końcu bufora wynikowego.
+ *
+ * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
+ */
+static int gg_convert_to_html(char *dst, const char *src, const unsigned char *format, int format_len)
+{
+ const char span_fmt[] = "<span style=\"color:#%02x%02x%02x; font-family:'MS Shell Dlg 2'; font-size:9pt; \">";
+ const int span_len = 75;
+ const char img_fmt[] = "<img name=\"%02x%02x%02x%02x%02x%02x%02x%02x\">";
+ const int img_len = 29;
+ int char_pos = 0;
+ int format_idx = 0;
+ unsigned char old_attr = 0;
+ const unsigned char *color = (const unsigned char*) "\x00\x00\x00";
+ int len, i;
+ const unsigned char *format_ = (const unsigned char*) format;
+
+ len = 0;
+
+ /* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc
+ * tak czy inaczej trzeba otworzyć <span>. */
+
+ if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) {
+ if (dst != NULL)
+ sprintf(&dst[len], span_fmt, 0, 0, 0);
+
+ len += span_len;
+ }
+
+ /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek
+ * na końcu tekstu. */
+
+ for (i = 0; ; i++) {
+ /* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */
+ for (;;) {
+ unsigned char attr;
+ int attr_pos;
+
+ if (format_idx + 3 > format_len)
+ break;
+
+ attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8);
+
+ if (attr_pos != char_pos)
+ break;
+
+ attr = format_[format_idx + 2];
+
+ /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */
+
+ if (src[i] == 0)
+ attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR);
+
+ format_idx += 3;
+
+ if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) {
+ if (char_pos != 0) {
+ if ((old_attr & GG_FONT_UNDERLINE) != 0)
+ gg_append(dst, &len, "</u>", 4);
+
+ if ((old_attr & GG_FONT_ITALIC) != 0)
+ gg_append(dst, &len, "</i>", 4);
+
+ if ((old_attr & GG_FONT_BOLD) != 0)
+ gg_append(dst, &len, "</b>", 4);
+
+ if (src[i] != 0)
+ gg_append(dst, &len, "</span>", 7);
+ }
+
+ if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) {
+ color = &format_[format_idx];
+ format_idx += 3;
+ } else {
+ color = (unsigned char*) "\x00\x00\x00";
+ }
+
+ if (src[i] != 0) {
+ if (dst != NULL)
+ sprintf(&dst[len], span_fmt, color[0], color[1], color[2]);
+ len += span_len;
+ }
+ } else if (char_pos == 0 && src[0] != 0) {
+ if (dst != NULL)
+ sprintf(&dst[len], span_fmt, 0, 0, 0);
+ len += span_len;
+ }
+
+ if ((attr & GG_FONT_BOLD) != 0)
+ gg_append(dst, &len, "<b>", 3);
+
+ if ((attr & GG_FONT_ITALIC) != 0)
+ gg_append(dst, &len, "<i>", 3);
+
+ if ((attr & GG_FONT_UNDERLINE) != 0)
+ gg_append(dst, &len, "<u>", 3);
+
+ if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) {
+ if (dst != NULL) {
+ sprintf(&dst[len], img_fmt,
+ format_[format_idx + 9],
+ format_[format_idx + 8],
+ format_[format_idx + 7],
+ format_[format_idx + 6],
+ format_[format_idx + 5],
+ format_[format_idx + 4],
+ format_[format_idx + 3],
+ format_[format_idx + 2]);
+ }
+
+ len += img_len;
+ format_idx += 10;
+ }
+
+ old_attr = attr;
+ }
+
+ /* Doklej znak zachowujÄ…c htmlowe escapowanie. */
+
+ switch (src[i]) {
+ case '&':
+ gg_append(dst, &len, "&amp;", 5);
+ break;
+ case '<':
+ gg_append(dst, &len, "&lt;", 4);
+ break;
+ case '>':
+ gg_append(dst, &len, "&gt;", 4);
+ break;
+ case '\'':
+ gg_append(dst, &len, "&apos;", 6);
+ break;
+ case '\"':
+ gg_append(dst, &len, "&quot;", 6);
+ break;
+ case '\n':
+ gg_append(dst, &len, "<br>", 4);
+ break;
+ case '\r':
+ case 0:
+ break;
+ default:
+ if (dst != NULL)
+ dst[len] = src[i];
+ len++;
+ }
+
+ /* SprawdĹş, czy bajt nie jest kontynuacjÄ… znaku unikodowego. */
+
+ if ((src[i] & 0xc0) != 0xc0)
+ char_pos++;
+
+ if (src[i] == 0)
+ break;
+ }
+
+ /* Zamknij tagi. */
+
+ if ((old_attr & GG_FONT_UNDERLINE) != 0)
+ gg_append(dst, &len, "</u>", 4);
+
+ if ((old_attr & GG_FONT_ITALIC) != 0)
+ gg_append(dst, &len, "</i>", 4);
+
+ if ((old_attr & GG_FONT_BOLD) != 0)
+ gg_append(dst, &len, "</b>", 4);
+
+ if (src[0] != 0)
+ gg_append(dst, &len, "</span>", 7);
+
+ if (dst != NULL)
+ dst[len] = 0;
+
+ return len;
+}
+
+/**
+ * Wysyła wiadomość formatowaną w ramach konferencji.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipients_count Liczba adresatĂłw
+ * \param recipients WskaĹşnik do tablicy z numerami adresatĂłw
+ * \param message Treść wiadomości
+ * \param format Informacje o formatowaniu
+ * \param formatlen Długość informacji o formatowaniu
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen)
+{
+ struct gg_send_msg s;
+ struct gg_send_msg80 s80;
+ struct gg_msg_recipients r;
+ char *cp_msg = NULL;
+ char *utf_msg = NULL;
+ char *html_msg = NULL;
+ int seq_no;
+ int i, j, k;
+ uin_t *recps;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (message == NULL || recipients_count <= 0 || recipients_count > 0xffff || (recipients_count != 1 && recipients == NULL)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (sess->encoding == GG_ENCODING_UTF8) {
+ if (!(cp_msg = gg_utf8_to_cp((const char *) message)))
+ return -1;
+
+ utf_msg = (char*) message;
+ } else {
+ if (sess->protocol_version >= 0x2d) {
+ if (!(utf_msg = gg_cp_to_utf8((const char *) message)))
+ return -1;
+ }
+
+ cp_msg = (char*) message;
+ }
+
+ if (sess->protocol_version < 0x2d) {
+ if (!sess->seq)
+ sess->seq = 0x01740000 | (rand() & 0xffff);
+ seq_no = sess->seq;
+ sess->seq += (rand() % 0x300) + 0x300;
+
+ s.msgclass = gg_fix32(msgclass);
+ s.seq = gg_fix32(seq_no);
+ } else {
+ int len;
+
+ // Drobne odchylenie od protokołu. Jeśli wysyłamy kilka
+ // wiadomości w ciągu jednej sekundy, zwiększamy poprzednią
+ // wartość, żeby każda wiadomość miała unikalny numer.
+
+ seq_no = (int)time(NULL);
+
+ if (seq_no <= sess->seq)
+ seq_no = sess->seq + 1;
+
+ sess->seq = seq_no;
+
+ if (format == NULL || formatlen < 3) {
+ format = (unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00";
+ formatlen = 9;
+ }
+
+ len = gg_convert_to_html(NULL, utf_msg, format + 3, formatlen - 3);
+
+ html_msg = malloc(len + 1);
+
+ if (html_msg == NULL) {
+ seq_no = -1;
+ goto cleanup;
+ }
+
+ gg_convert_to_html(html_msg, utf_msg, format + 3, formatlen - 3);
+
+ s80.seq = gg_fix32(seq_no);
+ s80.msgclass = gg_fix32(msgclass);
+ s80.offset_plain = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1);
+ s80.offset_attr = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1 + (uint32_t)strlen(cp_msg) + 1);
+ }
+
+ if (recipients_count > 1) {
+ r.flag = 0x01;
+ r.count = gg_fix32(recipients_count - 1);
+
+ recps = malloc(sizeof(uin_t) * recipients_count);
+
+ if (!recps) {
+ seq_no = -1;
+ goto cleanup;
+ }
+
+ for (i = 0; i < recipients_count; i++) {
+ for (j = 0, k = 0; j < recipients_count; j++) {
+ if (recipients[j] != recipients[i]) {
+ recps[k] = gg_fix32(recipients[j]);
+ k++;
+ }
+ }
+
+ if (sess->protocol_version < 0x2d) {
+ s.recipient = gg_fix32(recipients[i]);
+
+ if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1)
+ seq_no = -1;
+ } else {
+ s80.recipient = gg_fix32(recipients[i]);
+
+ if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1)
+ seq_no = -1;
+ }
+ }
+
+ free(recps);
+ } else {
+ if (sess->protocol_version < 0x2d) {
+ s.recipient = gg_fix32(recipients[0]);
+
+ if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1)
+ seq_no = -1;
+ } else {
+ s80.recipient = gg_fix32(recipients[0]);
+
+ if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1)
+ seq_no = -1;
+ }
+ }
+
+cleanup:
+ if (cp_msg != (char*) message)
+ free(cp_msg);
+
+ if (utf_msg != (char*) message)
+ free(utf_msg);
+
+ free(html_msg);
+
+ return seq_no;
+}
+
+/**
+ * Wysyła wiadomość binarną przeznaczoną dla klienta.
+ *
+ * Wiadomości między klientami przesyła się np. w celu wywołania zwrotnego
+ * połączenia bezpośredniego. Funkcja zwraca losowy numer sekwencyjny,
+ * który można zignorować albo wykorzystać do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipient Numer adresata
+ * \param message Treść wiadomości
+ * \param message_len Długość wiadomości
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len)
+{
+ struct gg_send_msg s;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ s.recipient = gg_fix32(recipient);
+ s.seq = gg_fix32(0);
+ s.msgclass = gg_fix32(msgclass);
+
+ return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL);
+}
+
+/**
+ * Wysyła żądanie obrazka o podanych parametrach.
+ *
+ * Wiadomości obrazkowe nie zawierają samych obrazków, a tylko ich rozmiary
+ * i sumy kontrolne. Odbiorca najpierw szuka obrazków w swojej pamięci
+ * podręcznej i dopiero gdy ich nie znajdzie, wysyła żądanie do nadawcy.
+ * Wynik zostanie przekazany zdarzeniem \c GG_EVENT_IMAGE_REPLY.
+ *
+ * \param sess Struktura sesji
+ * \param recipient Numer adresata
+ * \param size Rozmiar obrazka w bajtach
+ * \param crc32 Suma kontrola obrazka
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup messages
+ */
+int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32)
+{
+ struct gg_send_msg s;
+ struct gg_msg_image_request r;
+ char dummy = 0;
+ int res;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (size < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ s.recipient = gg_fix32(recipient);
+ s.seq = gg_fix32(0);
+ s.msgclass = gg_fix32(GG_CLASS_MSG);
+
+ r.flag = 0x04;
+ r.size = gg_fix32(size);
+ r.crc32 = gg_fix32(crc32);
+
+ res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL);
+
+ if (!res) {
+ struct gg_image_queue *q = malloc(sizeof(*q));
+ char *buf;
+
+ if (!q) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n");
+ return -1;
+ }
+
+ buf = malloc(size);
+ if (size && !buf)
+ {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n");
+ free(q);
+ return -1;
+ }
+
+ memset(q, 0, sizeof(*q));
+
+ q->sender = recipient;
+ q->size = size;
+ q->crc32 = crc32;
+ q->image = buf;
+
+ if (!sess->images)
+ sess->images = q;
+ else {
+ struct gg_image_queue *qq;
+
+ for (qq = sess->images; qq->next; qq = qq->next)
+ ;
+
+ qq->next = q;
+ }
+ }
+
+ return res;
+}
+
+/**
+ * Wysyła żądany obrazek.
+ *
+ * \param sess Struktura sesji
+ * \param recipient Numer adresata
+ * \param filename Nazwa pliku
+ * \param image Bufor z obrazkiem
+ * \param size Rozmiar obrazka
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup messages
+ */
+int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size)
+{
+ struct gg_msg_image_reply *r;
+ struct gg_send_msg s;
+ const char *tmp;
+ char buf[1910];
+ int res = -1;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size);
+
+ if (!sess || !filename || !image) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (size < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* wytnij ścieżki, zostaw tylko nazwę pliku */
+ while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\')))
+ filename = tmp + 1;
+
+ if (strlen(filename) < 1 || strlen(filename) > 1024) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ s.recipient = gg_fix32(recipient);
+ s.seq = gg_fix32(0);
+ s.msgclass = gg_fix32(GG_CLASS_MSG);
+
+ buf[0] = 0;
+ r = (void*) &buf[1];
+
+ r->flag = 0x05;
+ r->size = gg_fix32(size);
+ r->crc32 = gg_fix32(gg_crc32(0, (unsigned char*) image, size));
+
+ while (size > 0) {
+ int buflen, chunklen;
+
+ /* \0 + struct gg_msg_image_reply */
+ buflen = sizeof(struct gg_msg_image_reply) + 1;
+
+ /* w pierwszym kawałku jest nazwa pliku */
+ if (r->flag == 0x05) {
+ strcpy(buf + buflen, filename);
+ buflen += (int)strlen(filename) + 1;
+ }
+
+ chunklen = (size >= (int)sizeof(buf) - buflen) ? ((int)sizeof(buf) - buflen) : size;
+
+ memcpy(buf + buflen, image, chunklen);
+ size -= chunklen;
+ image += chunklen;
+
+ res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL);
+
+ if (res == -1)
+ break;
+
+ r->flag = 0x06;
+ }
+
+ return res;
+}
+
+/**
+ * Wysyła do serwera listę kontaktów.
+ *
+ * Funkcja informuje serwer o liście kontaktów, których statusy będą
+ * obserwowane lub kontaktĂłw, ktĂłre bedÄ… blokowane. Dla kaĹĽdego z \c count
+ * kontaktĂłw tablica \c userlist zawiera numer, a tablica \c types rodzaj
+ * kontaktu (\c GG_USER_NORMAL, \c GG_USER_OFFLINE, \c GG_USER_BLOCKED).
+ *
+ * Listę kontaktów należy \b zawsze wysyłać po połączeniu, nawet jeśli
+ * jest pusta.
+ *
+ * \param sess Struktura sesji
+ * \param userlist WskaĹşnik do tablicy numerĂłw kontaktĂłw
+ * \param types WskaĹşnik do tablicy rodzajĂłw kontaktĂłw
+ * \param count Liczba kontaktĂłw
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count)
+{
+ struct gg_notify *n;
+ uin_t *u;
+ char *t;
+ int i, res = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (!userlist || !count)
+ return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
+
+ while (count > 0) {
+ int part_count, packet_type;
+
+ if (count > 400) {
+ part_count = 400;
+ packet_type = GG_NOTIFY_FIRST;
+ } else {
+ part_count = count;
+ packet_type = GG_NOTIFY_LAST;
+ }
+
+ if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count)))
+ return -1;
+
+ for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) {
+ n[i].uin = gg_fix32(*u);
+ n[i].dunno1 = *t;
+ }
+
+ if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
+ free(n);
+ res = -1;
+ break;
+ }
+
+ count -= part_count;
+ userlist += part_count;
+ types += part_count;
+
+ free(n);
+ }
+
+ return res;
+}
+
+/**
+ * Wysyła do serwera listę kontaktów.
+ *
+ * Funkcja jest odpowiednikiem \c gg_notify_ex(), gdzie wszystkie kontakty
+ * sÄ… rodzaju \c GG_USER_NORMAL.
+ *
+ * \param sess Struktura sesji
+ * \param userlist WskaĹşnik do tablicy numerĂłw kontaktĂłw
+ * \param count Liczba kontaktĂłw
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_notify(struct gg_session *sess, uin_t *userlist, int count)
+{
+ struct gg_notify *n;
+ uin_t *u;
+ int i, res = 0;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (!userlist || !count)
+ return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
+
+ while (count > 0) {
+ int part_count, packet_type;
+
+ if (count > 400) {
+ part_count = 400;
+ packet_type = GG_NOTIFY_FIRST;
+ } else {
+ part_count = count;
+ packet_type = GG_NOTIFY_LAST;
+ }
+
+ if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count)))
+ return -1;
+
+ for (u = userlist, i = 0; i < part_count; u++, i++) {
+ n[i].uin = gg_fix32(*u);
+ n[i].dunno1 = GG_USER_NORMAL;
+ }
+
+ if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
+ res = -1;
+ free(n);
+ break;
+ }
+
+ free(n);
+
+ userlist += part_count;
+ count -= part_count;
+ }
+
+ return res;
+}
+
+/**
+ * Dodaje kontakt.
+ *
+ * Dodaje do listy kontaktów dany numer w trakcie połączenia. Aby zmienić
+ * rodzaj kontaktu (np. z normalnego na zablokowany), należy najpierw usunąć
+ * poprzedni rodzaj, poniewaĹĽ serwer operuje na maskach bitowych.
+ *
+ * \param sess Struktura sesji
+ * \param uin Numer kontaktu
+ * \param type Rodzaj kontaktu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type)
+{
+ struct gg_add_remove a;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ a.uin = gg_fix32(uin);
+ a.dunno1 = type;
+
+ return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL);
+}
+
+/**
+ * Dodaje kontakt.
+ *
+ * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich
+ * kontaktĂłw to \c GG_USER_NORMAL.
+ *
+ * \param sess Struktura sesji
+ * \param uin Numer kontaktu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_add_notify(struct gg_session *sess, uin_t uin)
+{
+ return gg_add_notify_ex(sess, uin, GG_USER_NORMAL);
+}
+
+/**
+ * Usuwa kontakt.
+ *
+ * Usuwa z listy kontaktów dany numer w trakcie połączenia.
+ *
+ * \param sess Struktura sesji
+ * \param uin Numer kontaktu
+ * \param type Rodzaj kontaktu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type)
+{
+ struct gg_add_remove a;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type);
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ a.uin = gg_fix32(uin);
+ a.dunno1 = type;
+
+ return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL);
+}
+
+/**
+ * Usuwa kontakt.
+ *
+ * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich
+ * kontaktĂłw to \c GG_USER_NORMAL.
+ *
+ * \param sess Struktura sesji
+ * \param uin Numer kontaktu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup contacts
+ */
+int gg_remove_notify(struct gg_session *sess, uin_t uin)
+{
+ return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
+}
+
+/**
+ * Wysyła do serwera zapytanie dotyczące listy kontaktów.
+ *
+ * Funkcja służy do importu lub eksportu listy kontaktów do serwera.
+ * W odróżnieniu od funkcji \c gg_notify(), ta lista kontaktów jest przez
+ * serwer jedynie przechowywana i nie ma wpływu na połączenie. Format
+ * listy kontaktów jest ignorowany przez serwer, ale ze względu na
+ * kompatybilność z innymi klientami, należy przechowywać dane w tym samym
+ * formacie co oryginalny klient Gadu-Gadu.
+ *
+ * Program nie musi się przejmować fragmentacją listy kontaktów wynikającą
+ * z protokołu -- wysyła i odbiera kompletną listę.
+ *
+ * \param sess Struktura sesji
+ * \param type Rodzaj zapytania
+ * \param request Treść zapytania (może być równe NULL)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup importexport
+ */
+int gg_userlist_request(struct gg_session *sess, char type, const char *request)
+{
+ int len;
+
+ if (!sess) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (!request) {
+ sess->userlist_blocks = 1;
+ return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL);
+ }
+
+ len = (int)strlen(request);
+
+ sess->userlist_blocks = 0;
+
+ while (len > 2047) {
+ sess->userlist_blocks++;
+
+ if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1)
+ return -1;
+
+ if (type == GG_USERLIST_PUT)
+ type = GG_USERLIST_PUT_MORE;
+
+ request += 2047;
+ len -= 2047;
+ }
+
+ sess->userlist_blocks++;
+
+ return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL);
+}
+
+/**
+ * Informuje rozmówcę o pisaniu wiadomości.
+ *
+ * \param sess Struktura sesji
+ * \param recipient Numer adresata
+ * \param length Długość wiadomości lub 0 jeśli jest pusta
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup messages
+ */
+int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){
+ struct gg_typing_notification pkt;
+ uin_t uin;
+
+ pkt.length = gg_fix16((uint16_t)length);
+ uin = gg_fix32(recipient);
+ memcpy(&pkt.uin, &uin, sizeof(uin_t));
+
+ return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * Rozłącza inną sesję multilogowania.
+ *
+ * \param gs Struktura sesji
+ * \param conn_id Sesja do rozłączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup login
+ */
+int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id)
+{
+ struct gg_multilogon_disconnect pkt;
+
+ pkt.conn_id = conn_id;
+
+ return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL);
+}
+
+/* @} */
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/libgadu.h b/protocols/Gadu-Gadu/src/libgadu/libgadu.h
new file mode 100644
index 0000000000..d273d998e1
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/libgadu.h
@@ -0,0 +1,2311 @@
+/* coding: UTF-8 */
+/* $Id: libgadu.h 13762 2011-08-09 12:35:16Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ * Arkadiusz Miśkiewicz <arekm@pld-linux.org>
+ * Tomasz Chiliński <chilek@chilan.com>
+ * Piotr Wysocki <wysek@linux.bydg.org>
+ * Dawid Jarosz <dawjar@poczta.onet.pl>
+ * Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 libgadu.h
+ *
+ * \brief Główny plik nagłówkowy biblioteki
+ */
+
+#ifndef __GG_LIBGADU_H
+#define __GG_LIBGADU_H
+
+/* Defined if libgadu should be compatible with Miranda. */
+#define GG_CONFIG_MIRANDA
+
+#ifdef GG_CONFIG_MIRANDA
+#include <m_ssl.h>
+#endif
+
+#if defined(__cplusplus) || defined(_WIN32)
+#pragma pack(push, 1)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+/** \cond ignore */
+
+/* Defined if libgadu was compiled for bigendian machine. */
+#undef GG_CONFIG_BIGENDIAN
+
+/* Defined if this machine has gethostbyname_r(). */
+#undef GG_CONFIG_HAVE_GETHOSTBYNAME_R
+
+/* Defined if libgadu was compiled and linked with pthread support. */
+#define GG_CONFIG_HAVE_PTHREAD
+
+/* Defined if pthread resolver is the default one. */
+#define GG_CONFIG_PTHREAD_DEFAULT
+
+/* Defined if this machine has C99-compiliant vsnprintf(). */
+#undef GG_CONFIG_HAVE_C99_VSNPRINTF
+
+/* Defined if this machine has va_copy(). */
+#undef GG_CONFIG_HAVE_VA_COPY
+
+/* Defined if this machine has __va_copy(). */
+#undef GG_CONFIG_HAVE___VA_COPY
+
+/* Defined if this machine supports long long. */
+/* Visual C++ 6.0 has no long long */
+#if !defined(_MSC_VER) || (_MSC_VER >= 1300)
+#define GG_CONFIG_HAVE_LONG_LONG
+#endif
+
+/* Defined if libgadu was compiled and linked with OpenSSL support. */
+#undef GG_CONFIG_HAVE_OPENSSL
+
+/* Defined if uintX_t types are defined in <stdint.h>. */
+#undef GG_CONFIG_HAVE_STDINT_H
+
+/* Defined if uintX_t types are defined in <inttypes.h>. */
+#undef GG_CONFIG_HAVE_INTTYPES_H
+
+/* Defined if uintX_t types are defined in <sys/inttypes.h>. */
+#undef GG_CONFIG_HAVE_SYS_INTTYPES_H
+
+/* Defined if uintX_t types are defined in <sys/int_types.h>. */
+#undef GG_CONFIG_HAVE_SYS_INT_TYPES_H
+
+/* Defined if uintX_t types are defined in <sys/types.h>. */
+#undef GG_CONFIG_HAVE_SYS_TYPES_H
+
+/* MSC have no va_copy */
+#ifndef _MSC_VER
+#define GG_CONFIG_HAVE_VA_COPY
+#define GG_CONFIG_HAVE___VA_COPY
+#endif
+
+#if defined(GG_CONFIG_HAVE_OPENSSL) && !defined(GG_CONFIG_MIRANDA)
+#include <openssl/ssl.h>
+#endif
+
+#ifdef GG_CONFIG_HAVE_STDINT_H
+#include <stdint.h>
+#else
+# ifdef GG_CONFIG_HAVE_INTTYPES_H
+# include <inttypes.h>
+# else
+# ifdef GG_CONFIG_HAVE_SYS_INTTYPES_H
+# include <sys/inttypes.h>
+# else
+# ifdef GG_CONFIG_HAVE_SYS_INT_TYPES_H
+# include <sys/int_types.h>
+# else
+# ifdef GG_CONFIG_HAVE_SYS_TYPES_H
+# include <sys/types.h>
+# else
+
+#ifndef __AC_STDINT_H
+#define __AC_STDINT_H
+
+/* ISO C 9X: 7.18 Integer types <stdint.h> */
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+#ifdef GG_CONFIG_HAVE_LONG_LONG
+typedef unsigned long long uint64_t;
+#define GG_CONFIG_HAVE_UINT64_T
+#endif
+
+#ifndef __CYGWIN__
+#define __int8_t_defined
+typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed int int32_t;
+#endif
+
+#endif /* __AC_STDINT_H */
+
+# endif
+# endif
+# endif
+# endif
+#endif
+
+#ifdef _WIN32
+# define kill(pid,sig)
+# ifdef _MSC_VER
+# define vsnprintf _vsnprintf
+# define stat _stat
+# ifndef strdup
+# define strdup _strdup
+# endif
+# define strncasecmp _strnicmp
+# define vsnprintf _vsnprintf
+# define snprintf _snprintf
+# define strcasecmp _stricmp
+# define GG_CONFIG_HAVE_STRTOULL
+# define strtoull _strtoui64
+# endif
+# define gg_sock_write(sock,buf,len) send(sock,(void *)(buf),len,0)
+# define gg_sock_read(sock,buf,len) recv(sock,(void *)(buf),len,0)
+# define gg_sock_close(sock) closesocket(sock)
+# define gg_getsockopt(sock,level,name,val,len) getsockopt(sock,level,name,(char *)val,len)
+#else
+ typedef int SOCKET;
+# define gg_sock_write write
+# define gg_sock_read read
+# define gg_sock_close close
+# define gg_getsockopt getsockopt
+#endif
+
+/** \endcond */
+
+/**
+ * Numer Gadu-Gadu.
+ */
+typedef uint32_t uin_t;
+
+/**
+ * Identyfikator połączenia bezpośredniego Gadu-Gadu 7.x.
+ */
+typedef struct {
+ uint8_t id[8];
+} gg_dcc7_id_t;
+
+/**
+ * Identyfikator sesji multilogowania.
+ */
+typedef struct {
+ uint8_t id[8];
+} gg_multilogon_id_t;
+
+/**
+ * Makro deklarujÄ…ce pola wspĂłlne dla struktur sesji.
+ */
+#define gg_common_head(x) \
+ SOCKET fd; /**< Obserwowany deskryptor */ \
+ int check; /**< Informacja o ĹĽÄ…daniu odczytu/zapisu (patrz \ref gg_check_t) */ \
+ int state; /**< Aktualny stan połączenia (patrz \ref gg_state_t) */ \
+ int error; /**< Kod błędu dla \c GG_STATE_ERROR (patrz \ref gg_error_t) */ \
+ int type; /**< Rodzaj sesji (patrz \ref gg_session_t) */ \
+ int id; /**< Identyfikator sesji */ \
+ int timeout; /**< Czas pozostały do zakończenia stanu */ \
+ int (*callback)(x*); /**< Funkcja zwrotna */ \
+ void (*destroy)(x*); /**< Funkcja zwalniania zasobĂłw */
+
+/**
+ * Struktura wspólna dla wszystkich sesji i połączeń. Pozwala na proste
+ * rzutowanie niezależne od rodzaju połączenia.
+ */
+struct gg_common {
+ gg_common_head(struct gg_common)
+};
+
+struct gg_image_queue;
+
+struct gg_dcc7;
+
+struct gg_dcc7_relay;
+
+/**
+ * SposĂłb rozwiÄ…zywania nazw serwerĂłw.
+ */
+typedef enum {
+ GG_RESOLVER_DEFAULT = 0, /**< Domyślny sposób rozwiązywania nazw (jeden z poniższych) */
+ GG_RESOLVER_FORK, /**< RozwiÄ…zywanie nazw bazujÄ…ce na procesach */
+ GG_RESOLVER_PTHREAD, /**< RozwiÄ…zywanie nazw bazujÄ…ce na wÄ…tkach */
+ GG_RESOLVER_CUSTOM, /**< Funkcje rozwiÄ…zywania nazw dostarczone przed aplikacjÄ™ */
+ GG_RESOLVER_INVALID = -1 /**< Nieprawidłowy sposób rozwiązywania nazw (wynik \c gg_session_get_resolver) */
+} gg_resolver_t;
+
+/**
+ * Rodzaj kodowania znakĂłw.
+ */
+typedef enum {
+ GG_ENCODING_CP1250 = 0, /**< Kodowanie CP1250 */
+ GG_ENCODING_UTF8, /**< Kodowanie UTF-8 */
+ GG_ENCODING_INVALID = -1 /**< Nieprawidłowe kodowanie */
+} gg_encoding_t;
+
+/**
+ * Sesja Gadu-Gadu.
+ *
+ * Tworzona przez funkcjÄ™ \c gg_login(), zwalniana przez \c gg_free_session().
+ *
+ * \ingroup login
+ */
+struct gg_session {
+ gg_common_head(struct gg_session)
+
+ int async; /**< Flaga połączenia asynchronicznego */
+ int pid; /**< Numer procesu rozwiÄ…zujÄ…cego nazwÄ™ serwera */
+ int port; /**< Port serwera */
+ int seq; /**< Numer sekwencyjny ostatniej wiadomości */
+ int last_pong; /**< Czas otrzymania ostatniej ramki utrzymaniowej */
+ int last_event; /**< Czas otrzymania ostatniego pakietu */
+
+ struct gg_event *event; /**< Zdarzenie po wywołaniu \c callback */
+
+ uint32_t proxy_addr; /**< Adres serwera pośredniczącego */
+ uint16_t proxy_port; /**< Port serwera pośredniczącego */
+
+ uint32_t hub_addr; /**< Adres huba po rozwiÄ…zaniu nazwy */
+ uint32_t server_addr; /**< Adres serwera otrzymany od huba */
+
+ uint32_t client_addr; /**< Adres gniazda dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */
+ uint16_t client_port; /**< Port gniazda dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */
+
+ uint32_t external_addr; /**< Publiczny adres dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */
+ uint16_t external_port; /**< Publiczny port dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */
+
+ uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */
+ char *password; /**< Hasło (zwalniane po użyciu) */
+
+ int initial_status; /**< PoczÄ…tkowy status */
+ int status; /**< Aktualny status */
+
+ char *recv_buf; /**< Bufor na odbierany pakiety */
+ int recv_done; /**< Liczba wczytanych bajtĂłw pakietu */
+ int recv_left; /**< Liczba pozostałych do wczytania bajtów pakietu */
+
+ int protocol_version; /**< Wersja protokołu (bez flag) */
+ char *client_version; /**< Wersja klienta */
+ int last_sysmsg; /**< Numer ostatniej wiadomości systemowej */
+
+ char *initial_descr; /**< PoczÄ…tkowy opis statusu */
+
+ void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ serwera */
+
+ char *header_buf; /**< Bufor na początek nagłówka pakietu */
+ unsigned int header_done; /**< Liczba wczytanych bajtów nagłówka pakietu */
+
+#ifdef GG_CONFIG_MIRANDA
+ HSSL ssl;
+ int tls; /**< Flaga połączenia szyfrowanego */
+#elif GG_CONFIG_HAVE_OPENSSL
+ SSL *ssl; /**< Struktura TLS */
+ SSL_CTX *ssl_ctx; /**< Kontekst sesji TLS */
+#else
+ void *ssl; /**< Struktura TLS */
+ void *ssl_ctx; /**< Kontekst sesji TLS */
+#endif
+
+ int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */
+
+ char *userlist_reply; /**< Bufor z odbieranÄ… listÄ… kontaktĂłw */
+
+ int userlist_blocks; /**< Liczba części listy kontaktów */
+
+ struct gg_image_queue *images; /**< Lista wczytywanych obrazkĂłw */
+
+ int hash_type; /**< Rodzaj funkcji skrótu hasła (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1) */
+
+ char *send_buf; /**< Bufor z danymi do wysłania */
+ int send_left; /**< Liczba bajtów do wysłania */
+
+ struct gg_dcc7 *dcc7_list; /**< Lista połączeń bezpośrednich skojarzonych z sesją */
+
+ int soft_timeout; /**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_watch_fd() */
+
+ int protocol_flags; /**< Flagi protokołu */
+
+ gg_encoding_t encoding; /**< Rodzaj kodowania znakĂłw */
+
+ gg_resolver_t resolver_type; /**< SposĂłb rozwiÄ…zywania nazw serwerĂłw */
+ int (*resolver_start)(SOCKET *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */
+ void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */
+
+ int protocol_features; /**< Opcje protokołu */
+ int status_flags; /**< Flagi statusu */
+};
+
+/**
+ * Połączenie HTTP.
+ *
+ * Tworzone przez \c gg_http_connect(), zwalniane przez \c gg_http_free().
+ *
+ * \ingroup http
+ */
+struct gg_http {
+ gg_common_head(struct gg_http)
+
+ int async; /**< Flaga połączenia asynchronicznego */
+ int pid; /**< Identyfikator procesu rozwiÄ…zujÄ…cego nazwÄ™ serwera */
+ int port; /**< Port */
+
+ char *query; /**< Zapytanie HTTP */
+ char *header; /**< Odebrany nagłówek */
+ int header_size; /**< Rozmiar wczytanego nagłówka */
+ char *body; /**< Odebrana strona */
+ unsigned int body_size; /**< Rozmiar strony */
+
+ void *data; /**< Dane prywatne usługi HTTP */
+
+ char *user_data; /**< Dane prywatne uĹĽytkownika (nie sÄ… zwalniane) */
+
+ void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ */
+
+ unsigned int body_done; /**< Liczba odebranych bajtĂłw strony */
+
+ gg_resolver_t resolver_type; /**< SposĂłb rozwiÄ…zywania nazw serwerĂłw */
+ int (*resolver_start)(SOCKET *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */
+ void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */
+};
+
+/** \cond ignore */
+
+#ifdef __GNUC__
+#define GG_PACKED __attribute__ ((packed))
+#ifndef GG_IGNORE_DEPRECATED
+#define GG_DEPRECATED __attribute__ ((deprecated))
+#else
+#define GG_DEPRECATED
+#endif
+#else
+#define GG_PACKED
+#define GG_DEPRECATED
+#endif
+
+/** \endcond */
+
+#define GG_MAX_PATH 276 /**< Maksymalny rozmiar nazwy pliku w strukturze \c gg_file_info */
+
+/**
+ * Odpowiednik struktury WIN32_FIND_DATA z API WIN32.
+ *
+ * Wykorzystywana przy połączeniach bezpośrednich do wersji Gadu-Gadu 6.x.
+ */
+struct gg_file_info {
+ uint32_t mode; /**< dwFileAttributes */
+ uint32_t ctime[2]; /**< ftCreationTime */
+ uint32_t atime[2]; /**< ftLastAccessTime */
+ uint32_t mtime[2]; /**< ftLastWriteTime */
+ uint32_t size_hi; /**< nFileSizeHigh */
+ uint32_t size; /**< nFileSizeLow */
+ uint32_t reserved0; /**< dwReserved0 */
+ uint32_t reserved1; /**< dwReserved1 */
+ unsigned char filename[GG_MAX_PATH - 14]; /**< cFileName */
+ unsigned char short_filename[14]; /**< cAlternateFileName */
+} /** \cond ignore */ GG_PACKED /** \endcond */;
+
+/**
+ * Połączenie bezpośrednie do wersji Gadu-Gadu 6.x.
+ *
+ * Tworzone przez \c gg_dcc_socket_create(), \c gg_dcc_get_file(),
+ * \c gg_dcc_send_file() lub \c gg_dcc_voice_chat(), zwalniane przez
+ * \c gg_dcc_free().
+ *
+ * \ingroup dcc6
+ */
+struct gg_dcc {
+ gg_common_head(struct gg_dcc)
+
+ struct gg_event *event; /**< Zdarzenie po wywołaniu \c callback */
+
+ int active; /**< Flaga połączenia aktywnego (nieużywana) */
+ int port; /**< Port gniazda nasłuchującego */
+ uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */
+ uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony połączenia */
+ int file_fd; /**< deskryptor pliku */
+ unsigned int offset; /**< Położenie w pliku */
+ unsigned int chunk_size;
+ /**< Rozmiar kawałka pliku */
+ unsigned int chunk_offset;
+ /**< Położenie w aktualnym kawałku pliku */
+ struct gg_file_info file_info;
+ /**< Informacje o pliku */
+ int established; /**< Flaga ustanowienia połączenia */
+ char *voice_buf; /**< Bufor na pakiet połączenia głosowego */
+ int incoming; /**< Flaga połączenia przychodzącego */
+ char *chunk_buf; /**< Bufor na fragment danych */
+ uint32_t remote_addr; /**< Adres drugiej strony */
+ uint16_t remote_port; /**< Port drugiej strony */
+
+#ifdef GG_CONFIG_MIRANDA
+ void *contact;
+ char *folder;
+ uint32_t tick;
+#endif
+};
+
+#define GG_DCC7_HASH_LEN 20 /**< Maksymalny rozmiar skrótu pliku w połączeniach bezpośrenich */
+#define GG_DCC7_FILENAME_LEN 255 /**< Maksymalny rozmiar nazwy pliku w połączeniach bezpośrednich */
+#define GG_DCC7_INFO_LEN 32 /**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */
+#define GG_DCC7_INFO_HASH_LEN 32 /**< Maksymalny rozmiar skrótu ip informacji o połączeniach bezpośrednich */
+
+/**
+ * Połączenie bezpośrednie od wersji Gadu-Gadu 7.x.
+ *
+ * \ingroup dcc7
+ */
+struct gg_dcc7 {
+ gg_common_head(struct gg_dcc7)
+
+ gg_dcc7_id_t cid; /**< Identyfikator połączenia */
+
+ struct gg_event *event; /**< Struktura zdarzenia */
+
+ uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */
+ uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony połączenia */
+
+ int file_fd; /**< Deskryptor przesyłanego pliku */
+ unsigned int offset; /**< Aktualne położenie w przesyłanym pliku */
+ unsigned int size; /**< Rozmiar przesyłanego pliku */
+ unsigned char filename[GG_DCC7_FILENAME_LEN + 1];
+ /**< Nazwa przesyłanego pliku */
+ unsigned char hash[GG_DCC7_HASH_LEN];
+ /**< Skrót SHA1 przesyłanego pliku */
+
+ int dcc_type; /**< Rodzaj połączenia bezpośredniego */
+ int established; /**< Flaga ustanowienia połączenia */
+ int incoming; /**< Flaga połączenia przychodzącego */
+ int reverse; /**< Flaga połączenia zwrotnego */
+
+ uint32_t local_addr; /**< Adres lokalny */
+ uint16_t local_port; /**< Port lokalny */
+
+ uint32_t remote_addr; /**< Adres drugiej strony */
+ uint16_t remote_port; /**< Port drugiej strony */
+
+ struct gg_session *sess;
+ /**< Sesja do której przypisano połączenie */
+ struct gg_dcc7 *next; /**< Następne połączenie w liście */
+
+ int soft_timeout; /**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_dcc7_watch_fd() */
+ int seek; /**< Flaga mówiąca, że można zmieniać położenie w wysyłanym pliku */
+
+ void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ serwera */
+
+ int relay; /**< Flaga mĂłwiÄ…ca, ĹĽe laczymy sie przez serwer */
+ int relay_index; /**< Numer serwera pośredniczącego, do którego się łączymy */
+ int relay_count; /**< Rozmiar listy serwerów pośredniczących */
+ struct gg_dcc7_relay *relay_list; /**< Lista serwerów pośredniczących */
+
+#ifdef GG_CONFIG_MIRANDA
+ void *contact;
+ char *folder;
+ uint32_t tick;
+#endif
+};
+
+/**
+ * Rodzaj sesji.
+ */
+enum gg_session_t {
+ GG_SESSION_GG = 1, /**< Połączenie z serwerem Gadu-Gadu */
+ GG_SESSION_HTTP, /**< Połączenie HTTP */
+ GG_SESSION_SEARCH, /**< Wyszukiwanie w katalogu publicznym (nieaktualne) */
+ GG_SESSION_REGISTER, /**< Rejestracja nowego konta */
+ GG_SESSION_REMIND, /**< Przypominanie hasła */
+ GG_SESSION_PASSWD, /**< Zmiana hasła */
+ GG_SESSION_CHANGE, /**< Zmiana informacji w katalogu publicznym (nieaktualne) */
+ GG_SESSION_DCC, /**< Połączenie bezpośrednie (do wersji 6.x) */
+ GG_SESSION_DCC_SOCKET, /**< Gniazdo nasłuchujące (do wersji 6.x) */
+ GG_SESSION_DCC_SEND, /**< Wysyłanie pliku (do wersji 6.x) */
+ GG_SESSION_DCC_GET, /**< Odbieranie pliku (do wersji 6.x) */
+ GG_SESSION_DCC_VOICE, /**< Rozmowa głosowa (do wersji 6.x) */
+ GG_SESSION_USERLIST_GET, /**< Import listy kontaktĂłw z serwera (nieaktualne) */
+ GG_SESSION_USERLIST_PUT, /**< Eksport listy kontaktĂłw do serwera (nieaktualne) */
+ GG_SESSION_UNREGISTER, /**< Usuwanie konta */
+ GG_SESSION_USERLIST_REMOVE, /**< Usuwanie listy kontaktĂłw z serwera (nieaktualne) */
+ GG_SESSION_TOKEN, /**< Pobieranie tokenu */
+ GG_SESSION_DCC7_SOCKET, /**< Gniazdo nasłuchujące (od wersji 7.x) */
+ GG_SESSION_DCC7_SEND, /**< Wysyłanie pliku (od wersji 7.x) */
+ GG_SESSION_DCC7_GET, /**< Odbieranie pliku (od wersji 7.x) */
+ GG_SESSION_DCC7_VOICE, /**< Rozmowa głosowa (od wersji 7.x) */
+
+ GG_SESSION_USER0 = 256, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER1, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER2, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER3, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER4, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER5, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER6, /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+ GG_SESSION_USER7 /**< Rodzaj zadeklarowany dla uĹĽytkownika */
+};
+
+/**
+ * Aktualny stan sesji.
+ */
+enum gg_state_t {
+ /* wspĂłlne */
+ GG_STATE_IDLE = 0, /**< Nie dzieje siÄ™ nic */
+ GG_STATE_RESOLVING, /**< Oczekiwanie na rozwiÄ…zanie nazwy serwera */
+ GG_STATE_CONNECTING, /**< Oczekiwanie na połączenie */
+ GG_STATE_READING_DATA, /**< Oczekiwanie na dane */
+ GG_STATE_ERROR, /**< Kod błędu w polu \c error */
+
+ /* gg_session */
+ GG_STATE_CONNECTING_HUB, /**< Oczekiwanie na połączenie z hubem */
+ GG_STATE_CONNECTING_GG, /**< Oczekiwanie na połączenie z serwerem */
+ GG_STATE_READING_KEY, /**< Oczekiwanie na klucz */
+ GG_STATE_READING_REPLY, /**< Oczekiwanie na odpowiedĹş serwera */
+ GG_STATE_CONNECTED, /**< Połączono z serwerem */
+
+ /* gg_http */
+ GG_STATE_SENDING_QUERY, /**< Wysłano zapytanie HTTP */
+ GG_STATE_READING_HEADER, /**< Oczekiwanie na nagłówek HTTP */
+ GG_STATE_PARSING, /**< Przetwarzanie danych */
+ GG_STATE_DONE, /**< Połączenie zakończone */
+
+ /* gg_dcc */
+ GG_STATE_LISTENING, /* czeka na połączenia */
+ GG_STATE_READING_UIN_1, /* czeka na uin peera */
+ GG_STATE_READING_UIN_2, /* czeka na swĂłj uin */
+ GG_STATE_SENDING_ACK, /* wysyła potwierdzenie dcc */
+ GG_STATE_READING_ACK, /* czeka na potwierdzenie dcc */
+ GG_STATE_READING_REQUEST, /* czeka na komendÄ™ */
+ GG_STATE_SENDING_REQUEST, /* wysyła komendę */
+ GG_STATE_SENDING_FILE_INFO, /* wysyła informacje o pliku */
+ GG_STATE_READING_PRE_FILE_INFO, /* czeka na pakiet przed file_info */
+ GG_STATE_READING_FILE_INFO, /* czeka na informacje o pliku */
+ GG_STATE_SENDING_FILE_ACK, /* wysyła potwierdzenie pliku */
+ GG_STATE_READING_FILE_ACK, /* czeka na potwierdzenie pliku */
+ GG_STATE_SENDING_FILE_HEADER, /* wysyła nagłówek pliku */
+ GG_STATE_READING_FILE_HEADER, /* czeka na nagłówek */
+ GG_STATE_GETTING_FILE, /* odbiera plik */
+ GG_STATE_SENDING_FILE, /* wysyła plik */
+ GG_STATE_READING_VOICE_ACK, /* czeka na potwierdzenie voip */
+ GG_STATE_READING_VOICE_HEADER, /* czeka na rodzaj bloku voip */
+ GG_STATE_READING_VOICE_SIZE, /* czeka na rozmiar bloku voip */
+ GG_STATE_READING_VOICE_DATA, /* czeka na dane voip */
+ GG_STATE_SENDING_VOICE_ACK, /* wysyła potwierdzenie voip */
+ GG_STATE_SENDING_VOICE_REQUEST, /* wysyła żądanie voip */
+ GG_STATE_READING_TYPE, /* czeka na typ połączenia */
+
+ /* nowe. bez sensu jest to API. */
+ GG_STATE_TLS_NEGOTIATION, /**< Negocjacja połączenia szyfrowanego */
+
+ GG_STATE_REQUESTING_ID, /**< Oczekiwanie na nadanie identyfikatora połączenia bezpośredniego */
+ GG_STATE_WAITING_FOR_ACCEPT, /**< Oczekiwanie na potwierdzenie lub odrzucenie połączenia bezpośredniego */
+ GG_STATE_WAITING_FOR_INFO, /**< Oczekiwanie na informacje o połączeniu bezpośrednim */
+
+ GG_STATE_READING_ID, /**< Odebranie identyfikatora połączenia bezpośredniego */
+ GG_STATE_SENDING_ID, /**< Wysłano identyfikator połączenia bezpośredniego */
+ GG_STATE_RESOLVING_GG, /**< Oczekiwanie na rozwiÄ…zanie nazwy serwera Gadu-Gadu */
+
+ GG_STATE_RESOLVING_RELAY, /**< Oczekiwanie na rozwiązanie nazwy serwera pośredniczącego */
+ GG_STATE_CONNECTING_RELAY, /**< Oczekiwanie na połączenie z serwerem pośredniczącym */
+ GG_STATE_READING_RELAY /**< Odbieranie danych */
+};
+
+/**
+ * Informacja o tym, czy biblioteka chce zapisywać i/lub czytać
+ * z deskryptora. Maska bitowa.
+ *
+ * \ingroup events
+ */
+enum gg_check_t {
+ GG_CHECK_NONE = 0, /**< Nie sprawdzaj niczego */
+ GG_CHECK_WRITE = 1, /**< Sprawdź możliwość zapisu */
+ GG_CHECK_READ = 2 /**< Sprawdź możliwość odczytu */
+};
+
+/**
+ * Parametry połączenia z serwerem Gadu-Gadu. Parametry zostały przeniesione
+ * do struktury, by uniknąć zmian API po rozszerzeniu protokołu i dodaniu
+ * kolejnych opcji połączenia. Część parametrów, które nie są już aktualne
+ * lub nie mają znaczenia, została usunięta z dokumentacji.
+ *
+ * \ingroup login
+ */
+struct gg_login_params {
+ uin_t uin; /**< Numer Gadu-Gadu */
+ char *password; /**< Hasło */
+ int async; /**< Flaga asynchronicznego połączenia (domyślnie nie) */
+ int status; /**< Początkowy status użytkownika (domyślnie \c GG_STATUS_AVAIL) */
+ char *status_descr; /**< Początkowy opis użytkownika (domyślnie brak) */
+ uint32_t server_addr; /**< Adres serwera Gadu-Gadu (domyślnie pobierany automatycznie) */
+ uint16_t server_port; /**< Port serwera Gadu-Gadu (domyślnie pobierany automatycznie) */
+#ifndef DOXYGEN
+ uint32_t client_addr; /**< Adres połączeń bezpośrednich (nieaktualne) */
+ uint16_t client_port; /**< Port połączeń bezpośrednich (nieaktualne) */
+#endif
+ int protocol_version; /**< Wersja protokołu wysyłana do serwera (domyślnie najnowsza obsługiwana) */
+ char *client_version; /**< Wersja klienta wysyłana do serwera (domyślnie najnowsza znana) */
+ int has_audio; /**< Flaga obsługi połączeń głosowych */
+ int last_sysmsg; /**< Numer ostatnio odebranej wiadomości systemowej */
+ uint32_t external_addr; /**< Adres publiczny dla połączeń bezpośrednich (6.x) */
+ uint16_t external_port; /**< Port publiczny dla połączeń bezpośrednich (6.x) */
+#ifndef DOXYGEN
+ int tls; /**< Flaga połączenia szyfrowanego (nieaktualna) */
+#endif
+ int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w kilobajtach */
+#ifndef DOXYGEN
+ int era_omnix; /**< Flaga udawania klienta Era Omnix (nieaktualna) */
+#endif
+ int hash_type; /**< Rodzaj skrótu hasła (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1, domyślnie SHA1) */
+ gg_encoding_t encoding; /**< Rodzaj kodowania używanego w sesji (domyślnie CP1250) */
+ gg_resolver_t resolver; /**< SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver) */
+ int protocol_features; /**< Opcje protokołu (flagi GG_FEATURE_*). */
+ int status_flags; /**< Flagi statusu (flagi GG_STATUS_FLAG_*, patrz \ref status). */
+
+#ifndef DOXYGEN
+ char dummy[1 * sizeof(int)]; /**< \internal Miejsce na kilka kolejnych
+ parametrĂłw, ĹĽeby wraz z dodawaniem kolejnych
+ parametrów nie zmieniał się rozmiar struktury */
+#endif
+
+};
+
+#ifdef GG_CONFIG_MIRANDA
+struct gg_session *gg_login(const struct gg_login_params *p, SOCKET *gg_sock, int *gg_failno);
+#else
+struct gg_session *gg_login(const struct gg_login_params *p);
+#endif
+void gg_free_session(struct gg_session *sess);
+void gg_logoff(struct gg_session *sess);
+int gg_change_status(struct gg_session *sess, int status);
+int gg_change_status_descr(struct gg_session *sess, int status, const char *descr);
+int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time);
+int gg_change_status_flags(struct gg_session *sess, int flags);
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message);
+int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen);
+int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message);
+int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen);
+int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len);
+int gg_ping(struct gg_session *sess);
+int gg_userlist_request(struct gg_session *sess, char type, const char *request);
+int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32);
+int gg_image_reply(struct gg_session *sess, uin_t recipient, const TCHAR *filename, const char *image, int size);
+int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length);
+
+uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len);
+
+int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type);
+gg_resolver_t gg_session_get_resolver(struct gg_session *gs);
+int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int));
+
+int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type);
+gg_resolver_t gg_http_get_resolver(struct gg_http *gh);
+int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int));
+
+int gg_global_set_resolver(gg_resolver_t type);
+gg_resolver_t gg_global_get_resolver(void);
+int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int));
+
+int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id);
+
+/**
+ * Rodzaj zdarzenia.
+ *
+ * \ingroup events
+ */
+enum gg_event_t {
+ GG_EVENT_NONE = 0, /**< Nie wydarzyło się nic wartego uwagi */
+ GG_EVENT_MSG, /**< \brief Otrzymano wiadomość. Przekazuje również wiadomości systemowe od numeru 0. */
+ GG_EVENT_NOTIFY, /**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */
+ GG_EVENT_NOTIFY_DESCR, /**< \brief Informacja o statusie opisowym osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */
+ GG_EVENT_STATUS, /**< \brief Zmiana statusu osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */
+ GG_EVENT_ACK, /**< Potwierdzenie doręczenia wiadomości */
+ GG_EVENT_PONG, /**< \brief Utrzymanie połączenia. Obecnie serwer nie wysyła już do klienta ramek utrzymania połączenia, polega wyłącznie na wysyłaniu ramek przez klienta. */
+ GG_EVENT_CONN_FAILED, /**< \brief Nie udało się połączyć */
+ GG_EVENT_CONN_SUCCESS, /**< \brief Połączono z serwerem. Pierwszą rzeczą, jaką należy zrobić jest wysłanie listy kontaktów. */
+ GG_EVENT_DISCONNECT, /**< \brief Serwer zrywa połączenie. Zdarza się, gdy równolegle do serwera podłączy się druga sesja i trzeba zerwać połączenie z pierwszą. */
+
+ GG_EVENT_DCC_NEW, /**< Nowe połączenie bezpośrednie (6.x) */
+ GG_EVENT_DCC_ERROR, /**< Błąd połączenia bezpośredniego (6.x) */
+ GG_EVENT_DCC_DONE, /**< Zakończono połączenie bezpośrednie (6.x) */
+ GG_EVENT_DCC_CLIENT_ACCEPT, /**< Moment akceptacji klienta w połączeniu bezpośrednim (6.x) */
+ GG_EVENT_DCC_CALLBACK, /**< Zwrotne połączenie bezpośrednie (6.x) */
+ GG_EVENT_DCC_NEED_FILE_INFO, /**< Należy wypełnić \c file_info dla połączenia bezpośredniego (6.x) */
+ GG_EVENT_DCC_NEED_FILE_ACK, /**< Czeka na potwierdzenie pliku w połączeniu bezpośrednim (6.x) */
+ GG_EVENT_DCC_NEED_VOICE_ACK, /**< Czeka na potwierdzenie rozmowy w połączeniu bezpośrednim (6.x) */
+ GG_EVENT_DCC_VOICE_DATA, /**< Dane bezpośredniego połączenia głosowego (6.x) */
+
+ GG_EVENT_PUBDIR50_SEARCH_REPLY, /**< OdpowiedĹş katalogu publicznego */
+ GG_EVENT_PUBDIR50_READ, /**< Odczytano własne dane z katalogu publicznego */
+ GG_EVENT_PUBDIR50_WRITE, /**< Zmieniono własne dane w katalogu publicznym */
+
+ GG_EVENT_STATUS60, /**< Zmiana statusu osoby z listy kontaktĂłw */
+ GG_EVENT_NOTIFY60, /**< Informacja o statusach osób z listy kontaktów. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */
+ GG_EVENT_USERLIST, /**< Wynik importu lub eksportu listy kontaktĂłw */
+ GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesłania obrazka z wiadomości */
+ GG_EVENT_IMAGE_REPLY, /**< Przysłano obrazek z wiadomości */
+ GG_EVENT_DCC_ACK, /**< Potwierdzenie transmisji w połączeniu bezpośrednim (6.x) */
+
+ GG_EVENT_DCC7_NEW, /**< Nowe połączenie bezpośrednie (7.x) */
+ GG_EVENT_DCC7_ACCEPT, /**< Zaakceptowano połączenie bezpośrednie (7.x), nowy deskryptor */
+ GG_EVENT_DCC7_REJECT, /**< Odrzucono połączenie bezpośrednie (7.x) */
+ GG_EVENT_DCC7_CONNECTED, /**< Zestawiono połączenie bezpośrednie (7.x), nowy deskryptor */
+ GG_EVENT_DCC7_ERROR, /**< Błąd połączenia bezpośredniego (7.x) */
+ GG_EVENT_DCC7_DONE, /**< Zakończono połączenie bezpośrednie (7.x) */
+ GG_EVENT_DCC7_PENDING, /**< Trwa próba połączenia bezpośredniego (7.x), nowy deskryptor */
+
+ GG_EVENT_XML_EVENT, /**< Otrzymano komunikat systemowy (7.7) */
+ GG_EVENT_DISCONNECT_ACK, /**< \brief Potwierdzenie zakończenia sesji. Informuje o tym, że zmiana stanu na niedostępny z opisem dotarła do serwera i można zakończyć połączenie TCP. */
+ GG_EVENT_XML_ACTION,
+ GG_EVENT_TYPING_NOTIFICATION, /**< Powiadomienie o pisaniu */
+ GG_EVENT_USER_DATA, /**< Informacja o kontaktach */
+ GG_EVENT_MULTILOGON_MSG, /**< Wiadomość wysłana z innej sesji multilogowania */
+ GG_EVENT_MULTILOGON_INFO /**< Informacja o innych sesjach multilogowania */
+};
+
+#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY
+
+/**
+ * Powód nieudanego połączenia.
+ */
+enum gg_failure_t {
+ GG_FAILURE_RESOLVING = 1, /**< Nie znaleziono serwera */
+ GG_FAILURE_CONNECTING, /**< Błąd połączenia */
+ GG_FAILURE_INVALID, /**< Serwer zwrócił nieprawidłowe dane */
+ GG_FAILURE_READING, /**< Zerwano połączenie podczas odczytu */
+ GG_FAILURE_WRITING, /**< Zerwano połączenie podczas zapisu */
+ GG_FAILURE_PASSWORD, /**< Nieprawidłowe hasło */
+ GG_FAILURE_404, /**< NieuĹĽywane */
+ GG_FAILURE_TLS, /**< Błąd negocjacji szyfrowanego połączenia */
+ GG_FAILURE_NEED_EMAIL, /**< Serwer rozłączył nas z prośbą o zmianę adresu e-mail */
+ GG_FAILURE_INTRUDER, /**< Zbyt wiele prób połączenia z nieprawidłowym hasłem */
+ GG_FAILURE_UNAVAILABLE /**< Serwery są wyłączone */
+};
+
+/**
+ * Kod błędu danej operacji.
+ *
+ * Nie zawiera przesadnie szczegółowych informacji o powodach błędów, by nie
+ * komplikować ich obsługi. Jeśli wymagana jest większa dokładność, należy
+ * sprawdzić zawartość zmiennej systemowej \c errno.
+ */
+enum gg_error_t {
+ GG_ERROR_RESOLVING = 1, /**< Nie znaleziono hosta */
+ GG_ERROR_CONNECTING, /**< Błąd połączenia */
+ GG_ERROR_READING, /**< BĹ‚Ä…d odczytu/odbierania */
+ GG_ERROR_WRITING, /**< Błąd zapisu/wysyłania */
+
+ GG_ERROR_DCC_HANDSHAKE, /**< BĹ‚Ä…d negocjacji */
+ GG_ERROR_DCC_FILE, /**< BĹ‚Ä…d odczytu/zapisu pliku */
+ GG_ERROR_DCC_EOF, /**< Przedwczesny koniec pliku */
+ GG_ERROR_DCC_NET, /**< Błąd wysyłania/odbierania */
+ GG_ERROR_DCC_REFUSED, /**< Połączenie odrzucone */
+
+ GG_ERROR_DCC7_HANDSHAKE, /**< BĹ‚Ä…d negocjacji */
+ GG_ERROR_DCC7_FILE, /**< BĹ‚Ä…d odczytu/zapisu pliku */
+ GG_ERROR_DCC7_EOF, /**< Przedwczesny koniec pliku */
+ GG_ERROR_DCC7_NET, /**< Błąd wysyłania/odbierania */
+ GG_ERROR_DCC7_REFUSED, /**< Połączenie odrzucone */
+ GG_ERROR_DCC7_RELAY /**< Problem z serwerem pośredniczącym */
+};
+
+/**
+ * Pole zapytania lub odpowiedzi katalogu publicznego.
+ */
+struct gg_pubdir50_entry {
+ int num; /**< Numer wyniku */
+ char *field; /**< Nazwa pola */
+ char *value; /**< Wartość pola */
+} /* GG_DEPRECATED */;
+
+/**
+ * Zapytanie lub odpowiedĹş katalogu publicznego.
+ *
+ * Patrz \c gg_pubdir50_t.
+ */
+struct gg_pubdir50_s {
+ int count; /**< Liczba wynikĂłw odpowiedzi */
+ uin_t next; /**< Numer początkowy następnego zapytania */
+ int type; /**< Rodzaj zapytania */
+ uint32_t seq; /**< Numer sekwencyjny */
+ struct gg_pubdir50_entry *entries; /**< Pola zapytania lub odpowiedzi */
+ int entries_count; /**< Liczba pĂłl */
+} /* GG_DEPRECATED */;
+
+/**
+ * Zapytanie lub odpowiedĹş katalogu publicznego.
+ *
+ * Do pól nie należy się odwoływać bezpośrednio -- wszystkie niezbędne
+ * informacje są dostępne za pomocą funkcji \c gg_pubdir50_*
+ */
+typedef struct gg_pubdir50_s *gg_pubdir50_t;
+
+/**
+ * Opis zdarzeń \c GG_EVENT_MSG i \c GG_EVENT_MULTILOGON_MSG.
+ */
+struct gg_event_msg {
+ uin_t sender; /**< Numer nadawcy/odbiorcy */
+ int msgclass; /**< Klasa wiadomości */
+ time_t time; /**< Czas nadania */
+ char *message; /**< Treść wiadomości */
+
+ int recipients_count; /**< Liczba odbiorcĂłw konferencji */
+ uin_t *recipients; /**< Odbiorcy konferencji */
+
+ int formats_length; /**< Długość informacji o formatowaniu tekstu */
+ void *formats; /**< Informacje o formatowaniu tekstu */
+ uint32_t seq; /**< Numer sekwencyjny wiadomości */
+
+ char *xhtml_message; /**< Treść wiadomości w formacie XHTML (może być równe \c NULL, jeśli wiadomość nie zawiera treści XHTML) */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_NOTIFY_DESCR.
+ */
+struct gg_event_notify_descr {
+ struct gg_notify_reply *notify; /**< Informacje o liście kontaktów */
+ char *descr; /**< Opis status */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_STATUS.
+ */
+struct gg_event_status {
+ uin_t uin; /**< Numer Gadu-Gadu */
+ uint32_t status; /**< Nowy status */
+ char *descr; /**< Opis */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_STATUS60.
+ */
+struct gg_event_status60 {
+ uin_t uin; /**< Numer Gadu-Gadu */
+ int status; /**< Nowy status */
+ uint32_t remote_ip; /**< Adres IP dla połączeń bezpośrednich */
+ uint16_t remote_port; /**< Port dla połączeń bezpośrednich */
+ int version; /**< Wersja protokołu */
+ int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */
+ char *descr; /**< Opis statusu */
+ time_t time; /**< Czas powrotu */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60.
+ */
+struct gg_event_notify60 {
+ uin_t uin; /**< Numer Gadu-Gadu. W ostatnim elemencie jest równy 0, a pozostałe pola są niezainicjowane. */
+ int status; /**< Nowy status */
+ uint32_t remote_ip; /**< Adres IP dla połączeń bezpośrednich */
+ uint16_t remote_port; /**< Port dla połączeń bezpośrednich */
+ int version; /**< Wersja protokołu */
+ int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */
+ char *descr; /**< Opis statusu */
+ time_t time; /**< Czas powrotu */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_ACK.
+ */
+struct gg_event_ack {
+ uin_t recipient; /**< Numer odbiorcy */
+ int status; /**< Status doręczenia */
+ int seq; /**< Numer sekwencyjny wiadomości */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_USERLIST.
+ */
+struct gg_event_userlist {
+ char type; /**< Rodzaj odpowiedzi */
+ char *reply; /**< Treść odpowiedzi */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC_VOICE_DATA.
+ */
+struct gg_event_dcc_voice_data {
+ uint8_t *data; /**< Dane dźwiękowe */
+ int length; /**< Rozmiar danych dźwiękowych */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_IMAGE_REQUEST.
+ */
+struct gg_event_image_request {
+ uin_t sender; /**< Nadawca ĹĽÄ…dania */
+ uint32_t size; /**< Rozmiar obrazka */
+ uint32_t crc32; /**< Suma kontrolna CRC32 */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_IMAGE_REPLY.
+ */
+struct gg_event_image_reply {
+ uin_t sender; /**< Nadawca obrazka */
+ uint32_t size; /**< Rozmiar obrazka */
+ uint32_t crc32; /**< Suma kontrolna CRC32 */
+ char *filename; /**< Nazwa pliku */
+ char *image; /**< Bufor z obrazkiem */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_XML_EVENT.
+ */
+struct gg_event_xml_event {
+ char *data; /**< Bufor z komunikatem */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_XML_ACTION.
+ */
+struct gg_event_xml_action {
+ char *data; /**< Bufor z komunikatem */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_CONNECTED.
+ */
+struct gg_event_dcc7_connected {
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_PENDING.
+ */
+struct gg_event_dcc7_pending {
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_REJECT.
+ */
+struct gg_event_dcc7_reject {
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+ int reason; /**< powĂłd odrzucenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_ACCEPT.
+ */
+struct gg_event_dcc7_accept {
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+ int type; /**< Sposób połączenia (P2P, przez serwer) */
+ uint32_t remote_ip; /**< Adres zdalnego klienta */
+ uint16_t remote_port; /**< Port zdalnego klienta */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_DONE.
+ */
+struct gg_event_dcc7_done {
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_DCC7_ERROR.
+ *
+ * \note Odwrotna kolejność pól ma na celu zachowanie ABI.
+ */
+struct gg_event_dcc7_error {
+ enum gg_error_t error; /**< Kod błędu */
+ struct gg_dcc7 *dcc7; /**< Struktura połączenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_TYPING_NOTIFICATION.
+ */
+struct gg_event_typing_notification {
+ uin_t uin; /**< Numer rozmĂłwcy */
+ int length; /**< Długość tekstu */
+};
+
+/**
+ * Atrybut uĹĽytkownika.
+ */
+struct gg_event_user_data_attr {
+ int type; /**< Typ atrybutu */
+ char *key; /**< Klucz */
+ char *value; /**< Wartość */
+};
+
+/**
+ * Struktura opisujÄ…ca kontakt w zdarzeniu GG_EVENT_USER_DATA.
+ */
+struct gg_event_user_data_user {
+ uin_t uin; /**< Numer kontaktu */
+ size_t attr_count; /**< Liczba atrybutĂłw */
+ struct gg_event_user_data_attr *attrs; /**< Lista atrybutĂłw */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_USER_DATA.
+ */
+struct gg_event_user_data {
+ int type; /**< Rodzaj informacji o kontaktach */
+ size_t user_count; /**< Liczba kontaktĂłw */
+ struct gg_event_user_data_user *users; /**< Lista kontaktĂłw */
+};
+
+/**
+ * Struktura opisujÄ…ca sesjÄ™ multilogowania.
+ */
+struct gg_multilogon_session {
+ gg_multilogon_id_t id; /**< Identyfikator sesji */
+ char *name; /**< Nazwa sesji (podana w \c gg_login_params.client_version) */
+ uint32_t remote_addr; /**< Adres sesji */
+ int status_flags; /**< Flagi statusu sesji */
+ int protocol_features; /**< Opcje protokolu sesji */
+ time_t logon_time; /**< Czas zalogowania */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_MULTILOGON_INFO.
+ */
+struct gg_event_multilogon_info {
+ int count; /**< Liczba sesji */
+ struct gg_multilogon_session *sessions; /** Lista sesji */
+};
+
+/**
+ * Unia wszystkich zdarzeń zwracanych przez funkcje \c gg_watch_fd(),
+ * \c gg_dcc_watch_fd() i \c gg_dcc7_watch_fd().
+ *
+ * \ingroup events
+ */
+union gg_event_union {
+ enum gg_failure_t failure; /**< Błąd połączenia (\c GG_EVENT_CONN_FAILED) */
+ struct gg_notify_reply *notify; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY) */
+ struct gg_event_notify_descr notify_descr; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY_DESCR) */
+ struct gg_event_status status; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_STATUS) */
+ struct gg_event_status60 status60; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_STATUS60) */
+ struct gg_event_notify60 *notify60; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY60) */
+ struct gg_event_msg msg; /**< Otrzymano wiadomość (\c GG_EVENT_MSG) */
+ struct gg_event_ack ack; /**< Potwierdzenie wiadomości (\c GG_EVENT_ACK) */
+ struct gg_event_image_request image_request; /**< Żądanie wysłania obrazka (\c GG_EVENT_IMAGE_REQUEST) */
+ struct gg_event_image_reply image_reply; /**< OdpowiedĹş z obrazkiem (\c GG_EVENT_IMAGE_REPLY) */
+ struct gg_event_userlist userlist; /**< OdpowiedĹş listy kontaktĂłw (\c GG_EVENT_USERLIST) */
+ gg_pubdir50_t pubdir50; /**< OdpowiedĹş katalogu publicznego (\c GG_EVENT_PUBDIR50_*) */
+ struct gg_event_xml_event xml_event; /**< Zdarzenie systemowe (\c GG_EVENT_XML_EVENT) */
+ struct gg_event_xml_action xml_action; /**< Zdarzenie XML (\c GG_EVENT_XML_ACTION) */
+ struct gg_dcc *dcc_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC_NEW) */
+ enum gg_error_t dcc_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC_ERROR) */
+ struct gg_event_dcc_voice_data dcc_voice_data; /**< Dane połączenia głosowego (\c GG_EVENT_DCC_VOICE_DATA) */
+ struct gg_dcc7 *dcc7_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC7_NEW) */
+ enum gg_error_t dcc7_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC7_ERROR) */
+ struct gg_event_dcc7_error dcc7_error_ex; /**< Błąd połączenia bezpośredniego ze wskaźnikiem na strukturę połączenia (\c GG_EVENT_DCC7_ERROR) */
+ struct gg_event_dcc7_connected dcc7_connected; /**< Informacja o zestawieniu połączenia bezpośredniego (\c GG_EVENT_DCC7_CONNECTED) */
+ struct gg_event_dcc7_pending dcc7_pending; /**< Trwa próba połączenia bezpośredniego (\c GG_EVENT_DCC7_PENDING) */
+ struct gg_event_dcc7_reject dcc7_reject; /**< Odrzucono połączenia bezpośredniego (\c GG_EVENT_DCC7_REJECT) */
+ struct gg_event_dcc7_accept dcc7_accept; /**< Zaakceptowano połączenie bezpośrednie (\c GG_EVENT_DCC7_ACCEPT) */
+ struct gg_event_dcc7_done dcc7_done; /**< Zakończono połączenie bezpośrednie (\c GG_EVENT_DCC7_DONE) */
+ struct gg_event_typing_notification typing_notification; /**< Powiadomienie o pisaniu (\c GG_EVENT_TYPING_NOTIFICATION) */
+ struct gg_event_user_data user_data; /**< Informacje o kontaktach */
+ struct gg_event_msg multilogon_msg; /**< Inna sesja wysłała wiadomość (\c GG_EVENT_MULTILOGON_MSG) */
+ struct gg_event_multilogon_info multilogon_info; /**< Informacja o innych sesjach multilogowania (\c GG_EVENT_MULTILOGON_INFO) */
+};
+
+/**
+ * Opis zdarzenia.
+ *
+ * Zwracany przez funkcje \c gg_watch_fd(), \c gg_dcc_watch_fd()
+ * i \c gg_dcc7_watch_fd(). Po przeanalizowaniu należy zwolnić
+ * za pomocÄ… \c gg_event_free().
+ *
+ * \ingroup events
+ */
+struct gg_event {
+ int type; /**< Rodzaj zdarzenia */
+ union gg_event_union event; /**< Informacja o zdarzeniu */
+};
+
+struct gg_event *gg_watch_fd(struct gg_session *sess);
+void gg_event_free(struct gg_event *e);
+
+int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count);
+int gg_notify(struct gg_session *sess, uin_t *userlist, int count);
+int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type);
+int gg_add_notify(struct gg_session *sess, uin_t uin);
+int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type);
+int gg_remove_notify(struct gg_session *sess, uin_t uin);
+
+struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header);
+int gg_http_watch_fd(struct gg_http *h);
+void gg_http_stop(struct gg_http *h);
+void gg_http_free(struct gg_http *h);
+
+uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req);
+gg_pubdir50_t gg_pubdir50_new(int type);
+int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value);
+int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq);
+const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field);
+int gg_pubdir50_type(gg_pubdir50_t res);
+int gg_pubdir50_count(gg_pubdir50_t res);
+uin_t gg_pubdir50_next(gg_pubdir50_t res);
+uint32_t gg_pubdir50_seq(gg_pubdir50_t res);
+void gg_pubdir50_free(gg_pubdir50_t res);
+
+#ifndef DOXYGEN
+
+#define GG_PUBDIR50_UIN "FmNumber"
+#define GG_PUBDIR50_STATUS "FmStatus"
+#define GG_PUBDIR50_FIRSTNAME "firstname"
+#define GG_PUBDIR50_LASTNAME "lastname"
+#define GG_PUBDIR50_NICKNAME "nickname"
+#define GG_PUBDIR50_BIRTHYEAR "birthyear"
+#define GG_PUBDIR50_CITY "city"
+#define GG_PUBDIR50_GENDER "gender"
+#define GG_PUBDIR50_GENDER_FEMALE "1"
+#define GG_PUBDIR50_GENDER_MALE "2"
+#define GG_PUBDIR50_GENDER_SET_FEMALE "2"
+#define GG_PUBDIR50_GENDER_SET_MALE "1"
+#define GG_PUBDIR50_ACTIVE "ActiveOnly"
+#define GG_PUBDIR50_ACTIVE_TRUE "1"
+#define GG_PUBDIR50_START "fmstart"
+#define GG_PUBDIR50_FAMILYNAME "familyname"
+#define GG_PUBDIR50_FAMILYCITY "familycity"
+
+#else
+
+/**
+ * \ingroup pubdir50
+ *
+ * Rodzaj pola zapytania.
+ */
+enum {
+ GG_PUBDIR50_UIN, /**< Numer Gadu-Gadu */
+ GG_PUBDIR50_STATUS, /**< Status (tylko wynik wyszukiwania) */
+ GG_PUBDIR50_FIRSTNAME, /**< ImiÄ™ */
+ GG_PUBDIR50_LASTNAME, /**< Nazwisko */
+ GG_PUBDIR50_NICKNAME, /**< Pseudonim */
+ GG_PUBDIR50_BIRTHYEAR, /**< Rok urodzenia lub przedział lat oddzielony spacją */
+ GG_PUBDIR50_CITY, /**< Miejscowość */
+ GG_PUBDIR50_GENDER, /**< Płeć */
+ GG_PUBDIR50_ACTIVE, /**< Osoba dostępna (tylko wyszukiwanie) */
+ GG_PUBDIR50_START, /**< Numer poczÄ…tkowy wyszukiwania (tylko wyszukiwanie) */
+ GG_PUBDIR50_FAMILYNAME, /**< Nazwisko rodowe (tylko wysyłanie informacji o sobie) */
+ GG_PUBDIR50_FAMILYCITY, /**< Miejscowość pochodzenia (tylko wysyłanie informacji o sobie) */
+};
+
+/**
+ * \ingroup pubdir50
+ *
+ * Wartość pola GG_PUBDIR50_GENDER przy wyszukiwaniu. Brak pola oznacza dowolną płeć.
+ */
+enum {
+ GG_PUBDIR50_GENDER_FEMALE, /**< Kobieta */
+ GG_PUBDIR50_GENDER_MALE, /**< Mężczyzna */
+};
+
+/**
+ * \ingroup pubdir50
+ *
+ * Wartość pola GG_PUBDIR50_GENDER przy wysyłaniu informacji o sobie.
+ */
+enum {
+ GG_PUBDIR50_GENDER_SET_FEMALE, /**< Kobieta */
+ GG_PUBDIR50_GENDER_SET_MALE, /**< Mężczyzna */
+};
+
+/**
+ * \ingroup pubdir50
+ *
+ * Wartość pola GG_PUBDIR50_ACTIVE.
+ */
+enum {
+ GG_PUBDIR50_ACTIVE_TRUE, /**< Wyszukaj tylko osoby dostępne */
+};
+
+#endif /* DOXYGEN */
+
+/**
+ * Wynik operacji na katalogu publicznym.
+ *
+ * \ingroup http
+ */
+struct gg_pubdir {
+ int success; /**< Flaga powodzenia operacji */
+ uin_t uin; /**< Otrzymany numer lub 0 w przypadku błędu */
+};
+
+int gg_pubdir_watch_fd(struct gg_http *f);
+void gg_pubdir_free(struct gg_http *f);
+
+/**
+ * Token autoryzacji niektĂłrych operacji HTTP.
+ *
+ * \ingroup token
+ */
+struct gg_token {
+ int width; /**< Szerokość obrazka */
+ int height; /**< Wysokość obrazka */
+ int length; /**< Liczba znakĂłw w tokenie */
+ char *tokenid; /**< Identyfikator tokenu */
+};
+
+struct gg_http *gg_token(int async);
+int gg_token_watch_fd(struct gg_http *h);
+void gg_token_free(struct gg_http *h);
+
+struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async);
+#ifndef DOXYGEN
+#define gg_register_watch_fd gg_pubdir_watch_fd
+#define gg_register_free gg_pubdir_free
+#endif
+
+struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async);
+#ifndef DOXYGEN
+#define gg_unregister_watch_fd gg_pubdir_watch_fd
+#define gg_unregister_free gg_pubdir_free
+#endif
+
+struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async);
+#ifndef DOXYGEN
+#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd
+#define gg_remind_passwd_free gg_pubdir_free
+#endif
+
+struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async);
+#ifndef DOXYGEN
+#define gg_change_passwd_watch_fd gg_pubdir_watch_fd
+#define gg_change_passwd_free gg_pubdir_free
+#endif
+
+extern int gg_dcc_port;
+extern unsigned long gg_dcc_ip;
+
+int gg_dcc_request(struct gg_session *sess, uin_t uin);
+
+struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin);
+struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin);
+struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin);
+void gg_dcc_set_type(struct gg_dcc *d, int type);
+int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename);
+int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename);
+int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length);
+
+#define GG_DCC_VOICE_FRAME_LENGTH 195 /**< Rozmiar pakietu głosowego przed wersją Gadu-Gadu 5.0.5 */
+#define GG_DCC_VOICE_FRAME_LENGTH_505 326 /**< Rozmiar pakietu głosowego od wersji Gadu-Gadu 5.0.5 */
+
+struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port);
+#ifndef DOXYGEN
+#define gg_dcc_socket_free gg_dcc_free
+#define gg_dcc_socket_watch_fd gg_dcc_watch_fd
+#endif
+
+struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d);
+
+void gg_dcc_free(struct gg_dcc *c);
+
+struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *d);
+struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash);
+struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash);
+int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset);
+int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason);
+int gg_dcc7_abort(struct gg_dcc7 *dcc);
+void gg_dcc7_free(struct gg_dcc7 *d);
+
+extern int gg_debug_level;
+
+extern void (*gg_debug_handler)(int level, const char *format, va_list ap);
+extern void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap);
+
+extern FILE *gg_debug_file;
+
+/**
+ * \ingroup debug
+ * @{
+ */
+#define GG_DEBUG_NET 1 /**< Rejestracja zdarzeń związanych z siecią */
+#define GG_DEBUG_TRAFFIC 2 /**< Rejestracja ruchu sieciowego */
+#define GG_DEBUG_DUMP 4 /**< Rejestracja zawartości pakietów */
+#define GG_DEBUG_FUNCTION 8 /**< Rejestracja wywołań funkcji */
+#define GG_DEBUG_MISC 16 /**< Rejestracja różnych informacji */
+/** @} */
+
+#ifdef GG_DEBUG_DISABLE
+#define gg_debug(x, y...) do { } while(0)
+#define gg_debug_session(z, x, y...) do { } while(0)
+#else
+void gg_debug(int level, const char *format, ...);
+void gg_debug_session(struct gg_session *sess, int level, const char *format, ...);
+#endif
+
+const char *gg_libgadu_version(void);
+
+extern int gg_proxy_enabled;
+extern char *gg_proxy_host;
+extern int gg_proxy_port;
+extern char *gg_proxy_username;
+extern char *gg_proxy_password;
+extern int gg_proxy_http_only;
+
+extern unsigned long gg_local_ip;
+
+#define GG_LOGIN_HASH_GG32 0x01 /**< Algorytm Gadu-Gadu */
+#define GG_LOGIN_HASH_SHA1 0x02 /**< Algorytm SHA1 */
+
+#ifndef DOXYGEN
+
+#define GG_PUBDIR50_WRITE 0x01
+#define GG_PUBDIR50_READ 0x02
+#define GG_PUBDIR50_SEARCH 0x03
+#define GG_PUBDIR50_SEARCH_REQUEST GG_PUBDIR50_SEARCH
+#define GG_PUBDIR50_SEARCH_REPLY 0x05
+
+#else
+
+/**
+ * \ingroup pubdir50
+ *
+ * Rodzaj zapytania lub odpowiedzi katalogu publicznego.
+ */
+enum {
+ GG_PUBDIR50_WRITE, /**< Wysłanie do serwera informacji o sobie */
+ GG_PUBDIR50_READ, /**< Pobranie z serwera informacji o sobie */
+ GG_PUBDIR50_SEARCH, /**< Wyszukiwanie w katalogu publicznym */
+ GG_PUBDIR50_SEARCH_REPLY, /**< Wynik wyszukiwania w katalogu publicznym */
+};
+
+#endif /* DOXYGEN */
+
+/** \cond obsolete */
+
+#define gg_free_event gg_event_free
+#define gg_free_http gg_http_free
+#define gg_free_pubdir gg_pubdir_free
+#define gg_free_register gg_pubdir_free
+#define gg_free_remind_passwd gg_pubdir_free
+#define gg_free_dcc gg_dcc_free
+#define gg_free_change_passwd gg_pubdir_free
+
+struct gg_search_request {
+ int active;
+ unsigned int start;
+ char *nickname;
+ char *first_name;
+ char *last_name;
+ char *city;
+ int gender;
+ int min_birth;
+ int max_birth;
+ char *email;
+ char *phone;
+ uin_t uin;
+} /* GG_DEPRECATED */;
+
+struct gg_search {
+ int count;
+ struct gg_search_result *results;
+} GG_DEPRECATED;
+
+struct gg_search_result {
+ uin_t uin;
+ char *first_name;
+ char *last_name;
+ char *nickname;
+ int born;
+ int gender;
+ char *city;
+ int active;
+} GG_DEPRECATED;
+
+#define GG_GENDER_NONE 0
+#define GG_GENDER_FEMALE 1
+#define GG_GENDER_MALE 2
+
+struct gg_http *gg_search(const struct gg_search_request *r, int async) GG_DEPRECATED;
+int gg_search_watch_fd(struct gg_http *f) GG_DEPRECATED;
+void gg_free_search(struct gg_http *f) GG_DEPRECATED;
+#define gg_search_free gg_free_search
+
+const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start) GG_DEPRECATED;
+const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) GG_DEPRECATED;
+const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) GG_DEPRECATED;
+const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) GG_DEPRECATED;
+void gg_search_request_free(struct gg_search_request *r) GG_DEPRECATED;
+
+struct gg_http *gg_register(const char *email, const char *password, int async) GG_DEPRECATED;
+struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) GG_DEPRECATED;
+
+struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) GG_DEPRECATED;
+struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) GG_DEPRECATED;
+
+struct gg_http *gg_remind_passwd(uin_t uin, int async) GG_DEPRECATED;
+struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) GG_DEPRECATED;
+
+struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) GG_DEPRECATED;
+struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) GG_DEPRECATED;
+struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) GG_DEPRECATED;
+
+struct gg_change_info_request {
+ char *first_name;
+ char *last_name;
+ char *nickname;
+ char *email;
+ int born;
+ int gender;
+ char *city;
+} /* GG_DEPRECATED */;
+
+struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city) GG_DEPRECATED;
+void gg_change_info_request_free(struct gg_change_info_request *r) GG_DEPRECATED;
+
+struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) GG_DEPRECATED;
+#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd
+#define gg_change_pubdir_free gg_pubdir_free
+#define gg_free_change_pubdir gg_pubdir_free
+
+struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async) GG_DEPRECATED;
+int gg_userlist_get_watch_fd(struct gg_http *f) GG_DEPRECATED;
+void gg_userlist_get_free(struct gg_http *f) GG_DEPRECATED;
+
+struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) GG_DEPRECATED;
+int gg_userlist_put_watch_fd(struct gg_http *f) GG_DEPRECATED;
+void gg_userlist_put_free(struct gg_http *f) GG_DEPRECATED;
+
+struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async) GG_DEPRECATED;
+int gg_userlist_remove_watch_fd(struct gg_http *f) GG_DEPRECATED;
+void gg_userlist_remove_free(struct gg_http *f) GG_DEPRECATED;
+
+int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) GG_DEPRECATED;
+
+/** \endcond */
+
+int gg_file_hash_sha1(int fd, uint8_t *result) GG_DEPRECATED;
+
+#ifdef __GNUC__
+char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))) GG_DEPRECATED;
+#else
+char *gg_saprintf(const char *format, ...) GG_DEPRECATED;
+#endif
+
+char *gg_vsaprintf(const char *format, va_list ap) GG_DEPRECATED;
+
+#define gg_alloc_sprintf gg_saprintf
+
+char *gg_get_line(char **ptr) GG_DEPRECATED;
+
+SOCKET gg_connect(void *addr, int port, int async) GG_DEPRECATED;
+#ifdef GG_CONFIG_MIRANDA
+SOCKET gg_connect_internal(void *addr, int port, int async, SOCKET *gg_sock);
+#endif
+struct in_addr *gg_gethostbyname(const char *hostname) GG_DEPRECATED;
+char *gg_read_line(SOCKET sock, char *buf, int length) GG_DEPRECATED;
+void gg_chomp(char *line) GG_DEPRECATED;
+char *gg_urlencode(const char *str) GG_DEPRECATED;
+int gg_http_hash(const char *format, ...) GG_DEPRECATED;
+void gg_http_free_fields(struct gg_http *h) GG_DEPRECATED;
+int gg_read(struct gg_session *sess, char *buf, int length) GG_DEPRECATED;
+int gg_write(struct gg_session *sess, const char *buf, int length) GG_DEPRECATED;
+void *gg_recv_packet(struct gg_session *sess) GG_DEPRECATED;
+int gg_send_packet(struct gg_session *sess, int type, ...) GG_DEPRECATED;
+unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) GG_DEPRECATED;
+void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) GG_DEPRECATED;
+uint32_t gg_fix32(uint32_t x);
+uint16_t gg_fix16(uint16_t x);
+#define fix16 gg_fix16
+#define fix32 gg_fix32
+char *gg_proxy_auth(void) GG_DEPRECATED;
+char *gg_base64_encode(const char *buf) GG_DEPRECATED;
+char *gg_base64_decode(const char *buf) GG_DEPRECATED;
+int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) GG_DEPRECATED;
+
+/**
+ * Kolejka odbieranych obrazkĂłw.
+ */
+struct gg_image_queue {
+ uin_t sender; /**< Nadawca obrazka */
+ uint32_t size; /**< Rozmiar obrazka */
+ uint32_t crc32; /**< Suma kontrolna CRC32 */
+ char *filename; /**< Nazwa pliku */
+ char *image; /**< Bufor z odebranymi danymi */
+ uint32_t done; /**< Rozmiar odebranych danych */
+
+ struct gg_image_queue *next; /**< Kolejny element listy */
+} GG_DEPRECATED;
+
+int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_abort(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+
+#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl"
+#define GG_APPMSG_PORT 80
+#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl"
+#define GG_PUBDIR_PORT 80
+#define GG_REGISTER_HOST "register.gadu-gadu.pl"
+#define GG_REGISTER_PORT 80
+#define GG_REMIND_HOST "retr.gadu-gadu.pl"
+#define GG_REMIND_PORT 80
+#define GG_RELAY_HOST "relay.gadu-gadu.pl"
+#define GG_RELAY_PORT 80
+
+#define GG_DEFAULT_PORT 8074
+#define GG_HTTPS_PORT 443
+#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)"
+
+#define GG_DEFAULT_CLIENT_VERSION "10.1.0.11070"
+#define GG_DEFAULT_PROTOCOL_VERSION 0x2e
+#define GG_DEFAULT_TIMEOUT 30
+#define GG_HAS_AUDIO_MASK 0x40000000
+#define GG_HAS_AUDIO7_MASK 0x20000000
+#define GG_ERA_OMNIX_MASK 0x04000000
+#define GG_LIBGADU_VERSION "1.10.0"
+
+#ifndef DOXYGEN
+
+#define GG_FEATURE_MSG77 0x0001
+#define GG_FEATURE_STATUS77 0x0002
+#define GG_FEATURE_UNKNOWN_4 0x0004
+#define GG_FEATURE_UNKNOWN_8 0x0008
+#define GG_FEATURE_DND_FFC 0x0010
+#define GG_FEATURE_IMAGE_DESCR 0x0020
+#define GG_FEATURE_UNKNOWN_40 0x0040
+#define GG_FEATURE_UNKNOWN_80 0x0080
+#define GG_FEATURE_UNKNOWN_100 0x0100
+#define GG_FEATURE_USER_DATA 0x0200
+#define GG_FEATURE_MSG_ACK 0x0400
+#define GG_FEATURE_UNKNOWN_800 0x0800
+#define GG_FEATURE_UNKNOWN_1000 0x1000
+#define GG_FEATURE_TYPING_NOTIFICATION 0x2000
+#define GG_FEATURE_MULTILOGON 0x4000
+
+/* Poniższe makra zostały zachowane dla zgodności API */
+#define GG_FEATURE_MSG80 0
+#define GG_FEATURE_STATUS80 0
+#define GG_FEATURE_STATUS80BETA 0
+
+#define GG_FEATURE_ALL (GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION)
+
+#else
+
+/**
+ * \ingroup login
+ *
+ * Flagi opcji protokołu.
+ */
+enum {
+ GG_FEATURE_MSG77, /**< Klient życzy sobie otrzymywać wiadomości zgodnie z protokołem 7.7 */
+ GG_FEATURE_STATUS77, /**< Klient życzy sobie otrzymywać zmiany stanu zgodnie z protokołem 7.7 */
+ GG_FEATURE_DND_FFC, /**< Klient obsługuje statusy "nie przeszkadzać" i "poGGadaj ze mną" */
+ GG_FEATURE_IMAGE_DESCR, /**< Klient obsługuje opisy graficzne oraz flagę \c GG_STATUS80_DESCR_MASK */
+};
+
+
+#endif
+
+#define GG_DEFAULT_DCC_PORT 1550
+
+struct gg_header {
+ uint32_t type; /* typ pakietu */
+ uint32_t length; /* długość reszty pakietu */
+} GG_PACKED;
+
+#define GG_WELCOME 0x0001
+#define GG_NEED_EMAIL 0x0014
+
+struct gg_welcome {
+ uint32_t key; /* klucz szyfrowania hasła */
+} GG_PACKED;
+
+#define GG_LOGIN 0x000c
+
+struct gg_login {
+ uint32_t uin; /* mĂłj numerek */
+ uint32_t hash; /* hash hasła */
+ uint32_t status; /* status na dzień dobry */
+ uint32_t version; /* moja wersja klienta */
+ uint32_t local_ip; /* mĂłj adres ip */
+ uint16_t local_port; /* port, na którym słucham */
+} GG_PACKED;
+
+#define GG_LOGIN_EXT 0x0013
+
+struct gg_login_ext {
+ uint32_t uin; /* mĂłj numerek */
+ uint32_t hash; /* hash hasła */
+ uint32_t status; /* status na dzień dobry */
+ uint32_t version; /* moja wersja klienta */
+ uint32_t local_ip; /* mĂłj adres ip */
+ uint16_t local_port; /* port, na którym słucham */
+ uint32_t external_ip; /* zewnętrzny adres ip */
+ uint16_t external_port; /* zewnętrzny port */
+} GG_PACKED;
+
+#define GG_LOGIN60 0x0015
+
+struct gg_login60 {
+ uint32_t uin; /* mĂłj numerek */
+ uint32_t hash; /* hash hasła */
+ uint32_t status; /* status na dzień dobry */
+ uint32_t version; /* moja wersja klienta */
+ uint8_t dunno1; /* 0x00 */
+ uint32_t local_ip; /* mĂłj adres ip */
+ uint16_t local_port; /* port, na którym słucham */
+ uint32_t external_ip; /* zewnętrzny adres ip */
+ uint16_t external_port; /* zewnętrzny port */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno2; /* 0xbe */
+} GG_PACKED;
+
+#define GG_LOGIN70 0x0019
+
+struct gg_login70 {
+ uint32_t uin; /* mĂłj numerek */
+ uint8_t hash_type; /* rodzaj hashowania hasła */
+ uint8_t hash[64]; /* hash hasła dopełniony zerami */
+ uint32_t status; /* status na dzień dobry */
+ uint32_t version; /* moja wersja klienta */
+ uint8_t dunno1; /* 0x00 */
+ uint32_t local_ip; /* mĂłj adres ip */
+ uint16_t local_port; /* port, na którym słucham */
+ uint32_t external_ip; /* zewnętrzny adres ip (???) */
+ uint16_t external_port; /* zewnętrzny port (???) */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno2; /* 0xbe */
+} GG_PACKED;
+
+#define GG_LOGIN_OK 0x0003
+
+#define GG_LOGIN_FAILED 0x0009
+
+#define GG_PUBDIR50_REQUEST 0x0014
+
+struct gg_pubdir50_request {
+ uint8_t type; /* GG_PUBDIR50_* */
+ uint32_t seq; /* czas wysłania zapytania */
+} GG_PACKED;
+
+#define GG_PUBDIR50_REPLY 0x000e
+
+struct gg_pubdir50_reply {
+ uint8_t type; /* GG_PUBDIR50_* */
+ uint32_t seq; /* czas wysłania zapytania */
+} GG_PACKED;
+
+#define GG_NEW_STATUS 0x0002
+
+#ifndef DOXYGEN
+
+#define GG_STATUS_NOT_AVAIL 0x0001
+#define GG_STATUS_NOT_AVAIL_DESCR 0x0015
+#define GG_STATUS_FFC 0x0017
+#define GG_STATUS_FFC_DESCR 0x0018
+#define GG_STATUS_AVAIL 0x0002
+#define GG_STATUS_AVAIL_DESCR 0x0004
+#define GG_STATUS_BUSY 0x0003
+#define GG_STATUS_BUSY_DESCR 0x0005
+#define GG_STATUS_DND 0x0021
+#define GG_STATUS_DND_DESCR 0x0022
+#define GG_STATUS_INVISIBLE 0x0014
+#define GG_STATUS_INVISIBLE_DESCR 0x0016
+#define GG_STATUS_BLOCKED 0x0006
+
+#define GG_STATUS_IMAGE_MASK 0x0100
+#define GG_STATUS_DESCR_MASK 0x4000
+#define GG_STATUS_FRIENDS_MASK 0x8000
+
+#define GG_STATUS_FLAG_UNKNOWN 0x00000001
+#define GG_STATUS_FLAG_VIDEO 0x00000002
+#define GG_STATUS_FLAG_MOBILE 0x00100000
+#define GG_STATUS_FLAG_SPAM 0x00800000
+
+#else
+
+/**
+ * Rodzaje statusĂłw uĹĽytkownika.
+ *
+ * \ingroup status
+ */
+enum {
+ GG_STATUS_NOT_AVAIL, /**< Niedostępny */
+ GG_STATUS_NOT_AVAIL_DESCR, /**< Niedostępny z opisem */
+ GG_STATUS_FFC, /**< PoGGadaj ze mnÄ… */
+ GG_STATUS_FFC_DESCR, /**< PoGGadaj ze mnÄ… z opisem */
+ GG_STATUS_AVAIL, /**< Dostępny */
+ GG_STATUS_AVAIL_DESCR, /**< Dostępny z opisem */
+ GG_STATUS_BUSY, /**< Zajęty */
+ GG_STATUS_BUSY_DESCR, /**< Zajęty z opisem */
+ GG_STATUS_DND, /**< Nie przeszkadzać */
+ GG_STATUS_DND_DESCR, /**< Nie przeszakdzać z opisem */
+ GG_STATUS_INVISIBLE, /**< Niewidoczny (tylko własny status) */
+ GG_STATUS_INVISIBLE_DESCR, /**< Niewidoczny z opisem (tylko własny status) */
+ GG_STATUS_BLOCKED, /**< Zablokowany (tylko status innych) */
+ GG_STATUS_IMAGE_MASK, /**< Flaga bitowa oznaczająca opis graficzny (tylko jeśli wybrano \c GG_FEATURE_IMAGE_DESCR) */
+ GG_STATUS_DESCR_MASK, /**< Flaga bitowa oznaczająca status z opisem (tylko jeśli wybrano \c GG_FEATURE_IMAGE_DESCR) */
+ GG_STATUS_FRIENDS_MASK, /**< Flaga bitowa dostępności tylko dla znajomych */
+};
+
+/**
+ * Rodzaje statusĂłw uĹĽytkownika. Mapa bitowa.
+ *
+ * \ingroup status
+ */
+enum {
+ GG_STATUS_FLAG_UNKNOWN, /**< Przeznaczenie nieznane, ale występuje zawsze */
+ GG_STATUS_FLAG_VIDEO, /**< Klient obsługuje wideorozmowy */
+ GG_STATUS_FLAG_MOBILE, /**< Klient mobilny (ikona telefonu komĂłrkowego) */
+ GG_STATUS_FLAG_SPAM, /**< Klient chce otrzymywać linki od nieznajomych */
+};
+
+#endif /* DOXYGEN */
+
+/**
+ * \ingroup status
+ *
+ * Flaga bitowa dostepnosci informujaca ze mozemy voipowac
+ */
+
+#define GG_STATUS_VOICE_MASK 0x20000 /**< czy ma wlaczone audio (7.7) */
+
+/**
+ * \ingroup status
+ *
+ * Maksymalna długośc opisu.
+ */
+#define GG_STATUS_DESCR_MAXSIZE 255
+#define GG_STATUS_DESCR_MAXSIZE_PRE_8_0 70
+
+#define GG_STATUS_MASK 0xff
+
+/* GG_S_F() tryb tylko dla znajomych */
+#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0)
+
+/* GG_S() stan bez uwzględnienia dodatkowych flag */
+#define GG_S(x) ((x) & GG_STATUS_MASK)
+
+
+/* GG_S_FF() chętny do rozmowy */
+#define GG_S_FF(x) (GG_S(x) == GG_STATUS_FFC || GG_S(x) == GG_STATUS_FFC_DESCR)
+
+/* GG_S_AV() dostępny */
+#define GG_S_AV(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR)
+
+/* GG_S_AW() zaraz wracam */
+#define GG_S_AW(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR)
+
+/* GG_S_DD() nie przeszkadzać */
+#define GG_S_DD(x) (GG_S(x) == GG_STATUS_DND || GG_S(x) == GG_STATUS_DND_DESCR)
+
+/* GG_S_NA() niedostępny */
+#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR)
+
+/* GG_S_I() niewidoczny */
+#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR)
+
+
+/* GG_S_A() dostępny lub chętny do rozmowy */
+#define GG_S_A(x) (GG_S_FF(x) || GG_S_AV(x))
+
+/* GG_S_B() zajęty lub nie przeszkadzać */
+#define GG_S_B(x) (GG_S_AW(x) || GG_S_DD(x))
+
+
+/* GG_S_D() stan opisowy */
+#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || \
+ GG_S(x) == GG_STATUS_FFC_DESCR || \
+ GG_S(x) == GG_STATUS_AVAIL_DESCR || \
+ GG_S(x) == GG_STATUS_BUSY_DESCR || \
+ GG_S(x) == GG_STATUS_DND_DESCR || \
+ GG_S(x) == GG_STATUS_INVISIBLE_DESCR)
+
+/* GG_S_BL() blokowany lub blokujÄ…cy */
+#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED)
+
+/**
+ * Zmiana statusu (pakiet \c GG_NEW_STATUS i \c GG_NEW_STATUS80BETA)
+ */
+struct gg_new_status {
+ uint32_t status; /**< Nowy status */
+} GG_PACKED;
+
+#define GG_NOTIFY_FIRST 0x000f
+#define GG_NOTIFY_LAST 0x0010
+
+#define GG_NOTIFY 0x0010
+
+struct gg_notify {
+ uint32_t uin; /* numerek danej osoby */
+ uint8_t dunno1; /* rodzaj wpisu w liście */
+} GG_PACKED;
+
+#ifndef DOXYGEN
+
+#define GG_USER_OFFLINE 0x01
+#define GG_USER_NORMAL 0x03
+#define GG_USER_BLOCKED 0x04
+
+#else
+
+/**
+ * \ingroup contacts
+ *
+ * Rodzaj kontaktu.
+ */
+enum {
+ GG_USER_NORMAL, /**< Zwykły kontakt */
+ GG_USER_BLOCKED, /**< Zablokowany */
+ GG_USER_OFFLINE, /**< Niewidoczny dla kontaktu */
+};
+
+#endif /* DOXYGEN */
+
+#define GG_LIST_EMPTY 0x0012
+
+#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */
+
+struct gg_notify_reply {
+ uint32_t uin; /* numerek */
+ uint32_t status; /* status danej osoby */
+ uint32_t remote_ip; /* adres ip delikwenta */
+ uint16_t remote_port; /* port, na którym słucha klient */
+ uint32_t version; /* wersja klienta */
+ uint16_t dunno2; /* znowu port? */
+} GG_PACKED;
+
+#define GG_NOTIFY_REPLY60 0x0011
+
+struct gg_notify_reply60 {
+ uint32_t uin; /* numerek plus flagi w MSB */
+ uint8_t status; /* status danej osoby */
+ uint32_t remote_ip; /* adres ip delikwenta */
+ uint16_t remote_port; /* port, na którym słucha klient */
+ uint8_t version; /* wersja klienta */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno1; /* 0x00 */
+} GG_PACKED;
+
+#define GG_STATUS60 0x000f
+
+struct gg_status60 {
+ uint32_t uin; /* numerek plus flagi w MSB */
+ uint8_t status; /* status danej osoby */
+ uint32_t remote_ip; /* adres ip delikwenta */
+ uint16_t remote_port; /* port, na którym słucha klient */
+ uint8_t version; /* wersja klienta */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno1; /* 0x00 */
+} GG_PACKED;
+
+#define GG_NOTIFY_REPLY77 0x0018
+
+struct gg_notify_reply77 {
+ uint32_t uin; /* numerek plus flagi w MSB */
+ uint8_t status; /* status danej osoby */
+ uint32_t remote_ip; /* adres ip delikwenta */
+ uint16_t remote_port; /* port, na którym słucha klient */
+ uint8_t version; /* wersja klienta */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno1; /* 0x00 */
+ uint32_t dunno2; /* ? */
+} GG_PACKED;
+
+#define GG_STATUS77 0x0017
+
+struct gg_status77 {
+ uint32_t uin; /* numerek plus flagi w MSB */
+ uint8_t status; /* status danej osoby */
+ uint32_t remote_ip; /* adres ip delikwenta */
+ uint16_t remote_port; /* port, na którym słucha klient */
+ uint8_t version; /* wersja klienta */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno1; /* 0x00 */
+ uint32_t dunno2; /* ? */
+} GG_PACKED;
+
+#define GG_ADD_NOTIFY 0x000d
+#define GG_REMOVE_NOTIFY 0x000e
+
+struct gg_add_remove {
+ uint32_t uin; /* numerek */
+ uint8_t dunno1; /* bitmapa */
+} GG_PACKED;
+
+#define GG_STATUS 0x0002
+
+struct gg_status {
+ uint32_t uin; /* numerek */
+ uint32_t status; /* nowy stan */
+} GG_PACKED;
+
+#define GG_SEND_MSG 0x000b
+
+#ifndef DOXYGEN
+
+#define GG_CLASS_QUEUED 0x0001
+#define GG_CLASS_OFFLINE GG_CLASS_QUEUED
+#define GG_CLASS_MSG 0x0004
+#define GG_CLASS_CHAT 0x0008
+#define GG_CLASS_CTCP 0x0010
+#define GG_CLASS_ACK 0x0020
+#define GG_CLASS_EXT GG_CLASS_ACK /**< Dla kompatybilności wstecz */
+
+#else
+
+/**
+ * Klasy wiadomości. Wartości są maskami bitowymi, które w większości
+ * przypadków można łączyć (połączenie \c GG_CLASS_MSG i \c GG_CLASS_CHAT
+ * nie ma sensu).
+ *
+ * \ingroup messages
+ */
+enum {
+ GG_CLASS_MSG, /**< Wiadomość ma pojawić się w osobnym oknie */
+ GG_CLASS_CHAT, /**< Wiadomość ma pojawić się w oknie rozmowy */
+ GG_CLASS_CTCP, /**< Wiadomość przeznaczona dla klienta Gadu-Gadu */
+ GG_CLASS_ACK, /**< Klient nie ĹĽyczy sobie potwierdzenia */
+ GG_CLASS_QUEUED, /**< Wiadomość zakolejkowana na serwerze (tylko przy odbieraniu) */
+};
+
+#endif /* DOXYGEN */
+
+/**
+ * Maksymalna długość wiadomości.
+ *
+ * \ingroup messages
+ */
+#define GG_MSG_MAXSIZE 1989
+
+struct gg_send_msg {
+ uint32_t recipient;
+ uint32_t seq;
+ uint32_t msgclass;
+} GG_PACKED;
+
+struct gg_msg_richtext {
+ uint8_t flag;
+ uint16_t length;
+} GG_PACKED;
+
+/**
+ * Struktura opisująca formatowanie tekstu. W zależności od wartości pola
+ * \c font, zaraz za tą strukturą może wystąpić \c gg_msg_richtext_color
+ * lub \c gg_msg_richtext_image.
+ *
+ * \ingroup messages
+ */
+struct gg_msg_richtext_format {
+ uint16_t position; /**< PoczÄ…tkowy znak formatowania (liczony od 0) */
+ uint8_t font; /**< Atrybuty formatowania */
+} GG_PACKED;
+
+#ifndef DOXYGEN
+
+#define GG_FONT_BOLD 0x01
+#define GG_FONT_ITALIC 0x02
+#define GG_FONT_UNDERLINE 0x04
+#define GG_FONT_COLOR 0x08
+#define GG_FONT_IMAGE 0x80
+
+#else
+
+/**
+ * Atrybuty formatowania wiadomości.
+ *
+ * \ingroup messages
+ */
+enum {
+ GG_FONT_BOLD,
+ GG_FONT_ITALIC,
+ GG_FONT_UNDERLINE,
+ GG_FONT_COLOR,
+ GG_FONT_IMAGE
+};
+
+#endif /* DOXYGEN */
+
+/**
+ * Struktura opisujÄ…cÄ… kolor tekstu dla atrybutu \c GG_FONT_COLOR.
+ *
+ * \ingroup messages
+ */
+struct gg_msg_richtext_color {
+ uint8_t red; /**< Składowa czerwona koloru */
+ uint8_t green; /**< Składowa zielona koloru */
+ uint8_t blue; /**< Składowa niebieska koloru */
+} GG_PACKED;
+
+/**
+ * Strukturya opisująca obrazek wstawiony do wiadomości dla atrubutu
+ * \c GG_FONT_IMAGE.
+ *
+ * \ingroup messages
+ */
+struct gg_msg_richtext_image {
+ uint16_t unknown1; /**< Nieznane pole o wartości 0x0109 */
+ uint32_t size; /**< Rozmiar obrazka */
+ uint32_t crc32; /**< Suma kontrolna CRC32 obrazka */
+} GG_PACKED;
+
+struct gg_msg_recipients {
+ uint8_t flag;
+ uint32_t count;
+} GG_PACKED;
+
+struct gg_msg_image_request {
+ uint8_t flag;
+ uint32_t size;
+ uint32_t crc32;
+} GG_PACKED;
+
+struct gg_msg_image_reply {
+ uint8_t flag;
+ uint32_t size;
+ uint32_t crc32;
+ /* char filename[]; */
+ /* char image[]; */
+} GG_PACKED;
+
+#define GG_SEND_MSG_ACK 0x0005
+
+#ifndef DOXYGEN
+
+#define GG_ACK_BLOCKED 0x0001
+#define GG_ACK_DELIVERED 0x0002
+#define GG_ACK_QUEUED 0x0003
+#define GG_ACK_MBOXFULL 0x0004
+#define GG_ACK_NOT_DELIVERED 0x0006
+
+#else
+
+/**
+ * Status doręczenia wiadomości.
+ *
+ * \ingroup messages
+ */
+enum
+{
+ GG_ACK_DELIVERED, /**< Wiadomość dostarczono. */
+ GG_ACK_QUEUED, /**< Wiadomość zakolejkowano z powodu niedostępności odbiorcy. */
+ GG_ACK_BLOCKED, /**< Wiadomość zablokowana przez serwer (spam, świąteczne ograniczenia itd.) */
+ GG_ACK_MBOXFULL, /**< Wiadomości nie dostarczono z powodu zapełnionej kolejki wiadomości odbiorcy. */
+ GG_ACK_NOT_DELIVERED /**< Wiadomości nie dostarczono (tylko dla \c GG_CLASS_CTCP). */
+};
+
+#endif /* DOXYGEN */
+
+struct gg_send_msg_ack {
+ uint32_t status;
+ uint32_t recipient;
+ uint32_t seq;
+} GG_PACKED;
+
+#define GG_RECV_MSG 0x000a
+
+struct gg_recv_msg {
+ uint32_t sender;
+ uint32_t seq;
+ uint32_t time;
+ uint32_t msgclass;
+} GG_PACKED;
+
+#define GG_PING 0x0008
+
+#define GG_PONG 0x0007
+
+#define GG_DISCONNECTING 0x000b
+
+#define GG_USERLIST_REQUEST 0x0016
+
+#define GG_XML_EVENT 0x0027
+
+#ifndef DOXYGEN
+
+#define GG_USERLIST_PUT 0x00
+#define GG_USERLIST_PUT_MORE 0x01
+#define GG_USERLIST_GET 0x02
+
+#else
+
+/**
+ * \ingroup importexport
+ *
+ * Rodzaj zapytania.
+ */
+enum {
+ GG_USERLIST_PUT, /**< Eksport listy kontaktĂłw. */
+ GG_USERLIST_GET, /**< Import listy kontaktĂłw. */
+};
+
+#endif /* DOXYGEN */
+
+struct gg_userlist_request {
+ uint8_t type;
+} GG_PACKED;
+
+#define GG_USERLIST_REPLY 0x0010
+
+#ifndef DOXYGEN
+
+#define GG_USERLIST_PUT_REPLY 0x00
+#define GG_USERLIST_PUT_MORE_REPLY 0x02
+#define GG_USERLIST_GET_REPLY 0x06
+#define GG_USERLIST_GET_MORE_REPLY 0x04
+
+#else
+
+/**
+ * \ingroup importexport
+ *
+ * Rodzaj odpowiedzi.
+ */
+enum {
+ GG_USERLIST_PUT_REPLY, /**< Wyeksportowano listy kontaktĂłw. */
+ GG_USERLIST_GET_REPLY, /**< Zaimportowano listÄ™ kontaktĂłw. */
+};
+
+#endif /* DOXYGEN */
+
+struct gg_userlist_reply {
+ uint8_t type;
+} GG_PACKED;
+
+struct gg_dcc_tiny_packet {
+ uint8_t type; /* rodzaj pakietu */
+} GG_PACKED;
+
+struct gg_dcc_small_packet {
+ uint32_t type; /* rodzaj pakietu */
+} GG_PACKED;
+
+struct gg_dcc_big_packet {
+ uint32_t type; /* rodzaj pakietu */
+ uint32_t dunno1; /* niewiadoma */
+ uint32_t dunno2; /* niewiadoma */
+} GG_PACKED;
+
+/*
+ * póki co, nie znamy dokładnie protokołu. nie wiemy, co czemu odpowiada.
+ * nazwy sÄ… niepowaĹĽne i tymczasowe.
+ */
+#define GG_DCC_WANT_FILE 0x0003 /* peer chce plik */
+#define GG_DCC_HAVE_FILE 0x0001 /* więc mu damy */
+#define GG_DCC_HAVE_FILEINFO 0x0003 /* niech ma informacje o pliku */
+#define GG_DCC_GIMME_FILE 0x0006 /* peer jest pewny */
+#define GG_DCC_CATCH_FILE 0x0002 /* wysyłamy plik */
+
+#define GG_DCC_FILEATTR_READONLY 0x0020
+
+#define GG_DCC_TIMEOUT_SEND 1800 /* 30 minut */
+#define GG_DCC_TIMEOUT_GET 1800 /* 30 minut */
+#define GG_DCC_TIMEOUT_FILE_ACK 300 /* 5 minut */
+#define GG_DCC_TIMEOUT_VOICE_ACK 300 /* 5 minut */
+
+#define GG_DCC7_INFO 0x1f
+
+struct gg_dcc7_info {
+ uint32_t uin; /* numer nadawcy */
+ uint32_t type; /* sposób połączenia */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ char info[GG_DCC7_INFO_LEN]; /* informacje o połączeniu "ip port" */
+ char hash[GG_DCC7_INFO_HASH_LEN];/* skrĂłt "ip" */
+} GG_PACKED;
+
+#define GG_DCC7_NEW 0x20
+
+struct gg_dcc7_new {
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint32_t uin_from; /* numer nadawcy */
+ uint32_t uin_to; /* numer odbiorcy */
+ uint32_t type; /* rodzaj transmisji */
+ unsigned char filename[GG_DCC7_FILENAME_LEN]; /* nazwa pliku */
+ uint32_t size; /* rozmiar pliku */
+ uint32_t size_hi; /* rozmiar pliku (starsze bajty) */
+ unsigned char hash[GG_DCC7_HASH_LEN]; /* hash SHA1 */
+} GG_PACKED;
+
+#define GG_DCC7_ACCEPT 0x21
+
+struct gg_dcc7_accept {
+ uint32_t uin; /* numer przyjmującego połączenie */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint32_t offset; /* offset przy wznawianiu transmisji */
+ uint32_t dunno1; /* 0x00000000 */
+} GG_PACKED;
+
+// XXX API
+#define GG_DCC7_TYPE_P2P 0x00000001 /**< Połączenie bezpośrednie */
+#define GG_DCC7_TYPE_SERVER 0x00000002 /**< Połączenie przez serwer */
+
+#define GG_DCC7_REJECT 0x22
+
+struct gg_dcc7_reject {
+ uint32_t uin; /**< Numer odrzucającego połączenie */
+ gg_dcc7_id_t id; /**< Identyfikator połączenia */
+ uint32_t reason; /**< Powód rozłączenia */
+} GG_PACKED;
+
+// XXX API
+#define GG_DCC7_REJECT_BUSY 0x00000001 /**< Połączenie bezpośrednie już trwa, nie umiem obsłużyć więcej */
+#define GG_DCC7_REJECT_USER 0x00000002 /**< Użytkownik odrzucił połączenie */
+#define GG_DCC7_REJECT_HIDDEN 0x00000003 /* użytkownik ojest ukryty i nie możesz mu wysłać pliku */
+#define GG_DCC7_REJECT_VERSION 0x00000006 /**< Druga strona ma wersję klienta nieobsługującą połączeń bezpośrednich tego typu */
+
+#define GG_DCC7_ID_REQUEST 0x23
+
+struct gg_dcc7_id_request {
+ uint32_t type; /**< Rodzaj tranmisji */
+} GG_PACKED;
+
+// XXX API
+#define GG_DCC7_TYPE_VOICE 0x00000001 /**< Transmisja głosu */
+#define GG_DCC7_TYPE_FILE 0x00000004 /**< transmisja pliku */
+
+#define GG_DCC7_ID_REPLY 0x23
+
+struct gg_dcc7_id_reply {
+ uint32_t type; /** Rodzaj transmisji */
+ gg_dcc7_id_t id; /** Przyznany identyfikator */
+} GG_PACKED;
+
+/*
+#define GG_DCC7_DUNNO1 0x24
+
+struct gg_dcc7_dunno1 {
+ // XXX
+} GG_PACKED;
+*/
+
+#define GG_DCC7_ABORT 0x0025
+
+struct gg_dcc7_abort {
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint32_t uin_from; /* numer nadawcy */
+ uint32_t uin_to; /* numer odbiorcy */
+} GG_PACKED;
+
+struct gg_dcc7_aborted {
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+} GG_PACKED;
+
+#define GG_DCC7_TIMEOUT_CONNECT 10 /* 10 sekund */
+#define GG_DCC7_TIMEOUT_SEND 1800 /* 30 minut */
+#define GG_DCC7_TIMEOUT_GET 1800 /* 30 minut */
+#define GG_DCC7_TIMEOUT_FILE_ACK 300 /* 5 minut */
+#define GG_DCC7_TIMEOUT_VOICE_ACK 300 /* 5 minut */
+
+#ifdef __cplusplus
+}
+#endif
+
+#if defined(__cplusplus) || defined(_WIN32)
+#ifdef _WIN32
+#pragma pack(pop)
+#endif
+#endif
+
+#endif /* __GG_LIBGADU_H */
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/obsolete.c b/protocols/Gadu-Gadu/src/libgadu/obsolete.c
new file mode 100644
index 0000000000..f8fe4dc5de
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/obsolete.c
@@ -0,0 +1,238 @@
+/* coding: UTF-8 */
+/* $Id: obsolete.c 854 2009-10-12 21:06:28Z wojtekka $ */
+
+/*
+ * (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 obsolete.c
+ *
+ * \brief Nieaktualne funkcje
+ *
+ * Plik zawiera definicje funkcji, które są już nieaktualne ze względu
+ * na zmiany w protokole. Programy konsolidowane ze starszych wersjami
+ * bibliotek powinny nadal mieć możliwość działania, mimo ograniczonej
+ * funkcjonalności.
+ */
+
+/** \cond obsolete */
+
+#include <errno.h>
+
+#include "libgadu.h"
+#include "internal.h"
+
+struct gg_http *gg_userlist_get(uin_t uin, const char *passwd, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_userlist_get() is obsolete. use gg_userlist_request() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+int gg_userlist_get_watch_fd(struct gg_http *h)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+void gg_userlist_get_free(struct gg_http *h)
+{
+
+}
+
+struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_userlist_put() is obsolete. use gg_userlist_request() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+int gg_userlist_put_watch_fd(struct gg_http *h)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+void gg_userlist_put_free(struct gg_http *h)
+{
+
+}
+
+struct gg_http *gg_userlist_remove(uin_t uin, const char *passwd, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_userlist_remove() is obsolete. use gg_userlist_request() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+int gg_userlist_remove_watch_fd(struct gg_http *h)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+void gg_userlist_remove_free(struct gg_http *h)
+{
+
+}
+
+struct gg_http *gg_search(const struct gg_search_request *r, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_search() is obsolete. use gg_search50() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+int gg_search_watch_fd(struct gg_http *h)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+void gg_search_free(struct gg_http *h)
+{
+
+}
+
+const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start)
+{
+ return NULL;
+}
+
+const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start)
+{
+ return NULL;
+}
+
+const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start)
+{
+ return NULL;
+}
+
+const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start)
+{
+ return NULL;
+}
+
+void gg_search_request_free(struct gg_search_request *r)
+{
+
+}
+
+struct gg_http *gg_register(const char *email, const char *password, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_register() is obsolete. use gg_register3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_register2() is obsolete. use gg_register3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_unregister() is obsolete. use gg_unregister3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_unregister2() is obsolete. use gg_unregister3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+
+struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_change_passwd() is obsolete. use gg_change_passwd4() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_change_passwd2() is obsolete. use gg_change_passwd4() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_change_passwd3() is obsolete. use gg_change_passwd4() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_remind_passwd(uin_t uin, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd() is obsolete. use gg_remind_passwd3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd2() is obsolete. use gg_remind_passwd3() instead!\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async)
+{
+ gg_debug(GG_DEBUG_MISC, "// gg_change_info() is obsolete. use gg_pubdir50() instead\n");
+ errno = EINVAL;
+ return NULL;
+}
+
+struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city)
+{
+ return NULL;
+}
+
+void gg_change_info_request_free(struct gg_change_info_request *r)
+{
+
+}
+
+int gg_resolve(int *fd, int *pid, const char *hostname)
+{
+ return -1;
+}
+
+void gg_resolve_pthread_cleanup(void *arg, int kill)
+{
+
+}
+
+int gg_resolve_pthread(int *fd, void **resolver, const char *hostname)
+{
+ return -1;
+}
+
+int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length)
+{
+ return -1;
+}
+
+/** \endcond */
diff --git a/protocols/Gadu-Gadu/src/libgadu/protocol.h b/protocols/Gadu-Gadu/src/libgadu/protocol.h
new file mode 100644
index 0000000000..5b4895c260
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/protocol.h
@@ -0,0 +1,277 @@
+/* coding: UTF-8 */
+/* $Id$ */
+
+/*
+ * (C) Copyright 2009-2010 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ * Bartłomiej Zimoń <uzi18@o2.pl>
+ * Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef LIBGADU_PROTOCOL_H
+#define LIBGADU_PROTOCOL_H
+
+#include "libgadu.h"
+
+#ifdef _WIN32
+#pragma pack(push, 1)
+#endif
+
+#define GG_LOGIN80BETA 0x0029
+
+#define GG_LOGIN80 0x0031
+
+#undef GG_FEATURE_STATUS80BETA
+#undef GG_FEATURE_MSG80
+#undef GG_FEATURE_STATUS80
+#define GG_FEATURE_STATUS80BETA 0x01
+#define GG_FEATURE_MSG80 0x02
+#define GG_FEATURE_STATUS80 0x05
+
+#define GG8_LANG "pl"
+#define GG8_VERSION "Gadu-Gadu Client Build "
+
+struct gg_login80 {
+ uint32_t uin; /* mĂłj numerek */
+ uint8_t language[2]; /* język: GG8_LANG */
+ uint8_t hash_type; /* rodzaj hashowania hasła */
+ uint8_t hash[64]; /* hash hasła dopełniony zerami */
+ uint32_t status; /* status na dzień dobry */
+ uint32_t flags; /* flagi (przeznaczenie nieznane) */
+ uint32_t features; /* opcje protokołu (GG8_FEATURES) */
+ uint32_t local_ip; /* mĂłj adres ip */
+ uint16_t local_port; /* port, na którym słucham */
+ uint32_t external_ip; /* zewnętrzny adres ip (???) */
+ uint16_t external_port; /* zewnętrzny port (???) */
+ uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */
+ uint8_t dunno2; /* 0x64 */
+} GG_PACKED;
+
+#define GG_LOGIN_HASH_TYPE_INVALID 0x0016
+
+#define GG_LOGIN80_OK 0x0035
+
+/**
+ * Logowanie powiodło się (pakiet \c GG_LOGIN80_OK)
+ */
+struct gg_login80_ok {
+ uint32_t unknown1; /* 0x00000001 */
+} GG_PACKED;
+
+/**
+ * Logowanie nie powiodło się (pakiet \c GG_LOGIN80_FAILED)
+ */
+#define GG_LOGIN80_FAILED 0x0043
+
+struct gg_login80_failed {
+ uint32_t unknown1; /* 0x00000001 */
+} GG_PACKED;
+
+#define GG_NEW_STATUS80BETA 0x0028
+
+#define GG_NEW_STATUS80 0x0038
+
+/**
+ * Zmiana stanu (pakiet \c GG_NEW_STATUS80)
+ */
+struct gg_new_status80 {
+ uint32_t status; /**< Nowy status */
+ uint32_t flags; /**< flagi (nieznane przeznaczenie) */
+ uint32_t description_size; /**< rozmiar opisu */
+} GG_PACKED;
+
+#define GG_STATUS80BETA 0x002a
+#define GG_NOTIFY_REPLY80BETA 0x002b
+
+#define GG_STATUS80 0x0036
+#define GG_NOTIFY_REPLY80 0x0037
+
+struct gg_notify_reply80 {
+ uint32_t uin; /* numerek plus flagi w najstarszym bajcie */
+ uint32_t status; /* status danej osoby */
+ uint32_t features; /* opcje protokołu */
+ uint32_t remote_ip; /* adres IP bezpośrednich połączeń */
+ uint16_t remote_port; /* port bezpośrednich połączeń */
+ uint8_t image_size; /* maksymalny rozmiar obrazkĂłw w KB */
+ uint8_t unknown1; /* 0x00 */
+ uint32_t flags; /* flagi połączenia */
+ uint32_t descr_len; /* rozmiar opisu */
+} GG_PACKED;
+
+#define GG_SEND_MSG80 0x002d
+
+struct gg_send_msg80 {
+ uint32_t recipient;
+ uint32_t seq;
+ uint32_t msgclass;
+ uint32_t offset_plain;
+ uint32_t offset_attr;
+} GG_PACKED;
+
+#define GG_RECV_MSG80 0x002e
+
+struct gg_recv_msg80 {
+ uint32_t sender;
+ uint32_t seq;
+ uint32_t time;
+ uint32_t msgclass;
+ uint32_t offset_plain;
+ uint32_t offset_attr;
+} GG_PACKED;
+
+#define GG_DISCONNECT_ACK 0x000d
+
+#define GG_RECV_MSG_ACK 0x0046
+
+struct gg_recv_msg_ack {
+ uint32_t seq;
+} GG_PACKED;
+
+#define GG_USER_DATA 0x0044
+
+struct gg_user_data {
+ uint32_t type;
+ uint32_t user_count;
+} GG_PACKED;
+
+struct gg_user_data_user {
+ uint32_t uin;
+ uint32_t attr_count;
+} GG_PACKED;
+
+#define GG_TYPING_NOTIFICATION 0x0059
+
+struct gg_typing_notification {
+ uint16_t length;
+ uint32_t uin;
+} GG_PACKED;
+
+#define GG_XML_ACTION 0x002c
+
+#define GG_RECV_OWN_MSG 0x005a
+
+#define GG_MULTILOGON_INFO 0x005b
+
+struct gg_multilogon_info {
+ uint32_t count;
+} GG_PACKED;
+
+struct gg_multilogon_info_item {
+ uint32_t addr;
+ uint32_t flags;
+ uint32_t features;
+ uint32_t logon_time;
+ gg_multilogon_id_t conn_id;
+ uint32_t unknown1;
+ uint32_t name_size;
+} GG_PACKED;
+
+#define GG_MULTILOGON_DISCONNECT 0x0062
+
+struct gg_multilogon_disconnect {
+ gg_multilogon_id_t conn_id;
+} GG_PACKED;
+
+#define GG_DCC7_VOICE_RETRIES 0x11 /* 17 powtorzen */
+
+#define GG_DCC7_RESERVED1 0xdeadc0de
+#define GG_DCC7_RESERVED2 0xdeadbeaf
+
+struct gg_dcc7_voice_auth {
+ uint8_t type; /* 0x00 -> wysylanie ID
+ 0x01 -> potwierdzenie ID
+ */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint32_t reserved1; /* GG_DCC7_RESERVED1 */
+ uint32_t reserved2; /* GG_DCC7_RESERVED2 */
+} GG_PACKED;
+
+struct gg_dcc7_voice_nodata { /* wyciszony mikrofon, ten pakiet jest wysylany co 1s (jesli chcemy podtrzymac polaczenie) */
+ uint8_t type; /* 0x02 */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint32_t reserved1; /* GG_DCC7_RESERVED1 */
+ uint32_t reserved2; /* GG_DCC7_RESERVED2 */
+} GG_PACKED;
+
+struct gg_dcc7_voice_data {
+ uint8_t type; /* 0x03 */
+ uint32_t did; /* XXX: co ile zwieksza sie u nas id pakietu [uzywac 0x28] */
+ uint32_t len; /* rozmiar strukturki - 1 (sizeof(type)) */
+ uint32_t packet_id; /* numerek pakietu */
+ uint32_t datalen; /* rozmiar danych */
+ /* char data[]; */ /* ramki: albo gsm, albo speex, albo melp, albo inne. */
+} GG_PACKED;
+
+struct gg_dcc7_voice_init {
+ uint8_t type; /* 0x04 */
+ uint32_t id; /* nr kroku [0x1 - 0x5] */
+ uint32_t protocol; /* XXX: wersja protokolu (0x29, 0x2a, 0x2b) */
+ uint32_t len; /* rozmiar sizeof(protocol)+sizeof(len)+sizeof(data) = 0x08 + sizeof(data) */
+ /* char data[]; */ /* reszta danych */
+} GG_PACKED;
+
+struct gg_dcc7_voice_init_confirm {
+ uint8_t type; /* 0x05 */
+ uint32_t id; /* id tego co potwierdzamy [0x1 - 0x5] */
+} GG_PACKED;
+
+#define GG_DCC7_RELAY_TYPE_SERVER 0x01 /* adres serwera, na który spytać o proxy */
+#define GG_DCC7_RELAY_TYPE_PROXY 0x08 /* adresy proxy, na które sie łączyć */
+
+#define GG_DCC7_RELAY_DUNNO1 0x02
+
+#define GG_DCC7_RELAY_REQUEST 0x0a
+
+struct gg_dcc7_relay_req {
+ uint32_t magic; /* 0x0a */
+ uint32_t len; /* długość całego pakietu */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+ uint16_t type; /* typ zapytania */
+ uint16_t dunno1; /* 0x02 */
+} GG_PACKED;
+
+#define GG_DCC7_RELAY_REPLY_RCOUNT 0x02
+
+#define GG_DCC7_RELAY_REPLY 0x0b
+
+struct gg_dcc7_relay_reply {
+ uint32_t magic; /* 0x0b */
+ uint32_t len; /* długość całego pakietu */
+ uint32_t rcount; /* ilość serwerów */
+} GG_PACKED;
+
+struct gg_dcc7_relay_reply_server {
+ uint32_t addr; /* adres ip serwera */
+ uint16_t port; /* port serwera */
+ uint8_t family; /* rodzina adresów (na końcu?!) AF_INET=2 */
+} GG_PACKED;
+
+#define GG_DCC7_WELCOME_SERVER 0xc0debabe
+
+struct gg_dcc7_welcome_server {
+ uint32_t magic; /* 0xc0debabe */
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+} GG_PACKED;
+
+struct gg_dcc7_welcome_p2p {
+ gg_dcc7_id_t id; /* identyfikator połączenia */
+} GG_PACKED;
+
+#ifdef _WIN32
+#pragma pack(pop)
+#endif
+
+#endif /* LIBGADU_PROTOCOL_H */
diff --git a/protocols/Gadu-Gadu/src/libgadu/pthread.c b/protocols/Gadu-Gadu/src/libgadu/pthread.c
new file mode 100644
index 0000000000..9a8988a358
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/pthread.c
@@ -0,0 +1,78 @@
+/*
+AOL Instant Messenger Plugin for Miranda IM
+
+Copyright (c) 2003-2006 Robert Rainwater
+
+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 "pthread.h"
+#include <process.h>
+#include <newpluginapi.h>
+#include <m_system.h>
+
+/****************************************/
+/* Portable pthread code for Miranda IM */
+
+/* create thread */
+int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(__stdcall * thread_start) (void *), void *param)
+{
+ tid->hThread = (HANDLE)mir_forkthreadex((pThreadFuncEx)*(void**)&thread_start, param, (unsigned *)&tid->dwThreadId);
+
+ return 0;
+}
+
+/* detach a thread */
+int pthread_detach(pthread_t *tid)
+{
+ CloseHandle(tid->hThread);
+ return 0;
+}
+
+/* wait for thread termination */
+int pthread_join(pthread_t *tid, void **value_ptr)
+{
+ if (tid->dwThreadId == GetCurrentThreadId())
+ return 35 /*EDEADLK*/;
+
+ WaitForSingleObject(tid->hThread, INFINITE);
+ return 0;
+}
+
+/* get calling thread's ID */
+pthread_t *pthread_self(void)
+{
+ static int poolId = 0;
+ static pthread_t tidPool[32];
+ /* mark & round pool to 32 items */
+ pthread_t *tid = &tidPool[poolId ++];
+ poolId %= 32;
+
+ tid->hThread = GetCurrentThread();
+ tid->dwThreadId = GetCurrentThreadId();
+ return tid;
+}
+
+/* cancel execution of a thread */
+int pthread_cancel(pthread_t *thread)
+{
+ return TerminateThread(thread->hThread, 0) ? 0 : 3 /*ESRCH*/;
+}
+
+/* terminate thread */
+void pthread_exit(void *value_ptr)
+{
+// _endthreadex((unsigned)value_ptr);
+}
diff --git a/protocols/Gadu-Gadu/src/libgadu/pthread.h b/protocols/Gadu-Gadu/src/libgadu/pthread.h
new file mode 100644
index 0000000000..4de1053e0e
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/pthread.h
@@ -0,0 +1,56 @@
+/*
+pthread Portable implementation
+
+Copyright (c) 2003 Robert Rainwater
+Copyright (c) 2003-2005 Adam Strzelecki
+
+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.
+*/
+
+#ifndef PTHREAD_H
+#define PTHREAD_H
+
+#include <windows.h>
+
+// Minipthread code from Miranda IM source
+typedef struct
+{
+ HANDLE hThread;
+ DWORD dwThreadId;
+}
+pthread_t;
+
+typedef int pthread_attr_t;
+typedef CRITICAL_SECTION pthread_mutex_t;
+
+/* create thread */
+int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(__stdcall *thread_start) (void *), void *param);
+/* wait for thread termination */
+int pthread_join(pthread_t *tid, void **value_ptr);
+/* detach a thread */
+int pthread_detach(pthread_t *tid);
+/* get calling thread's ID */
+pthread_t *pthread_self(void);
+/* cancel execution of a thread */
+int pthread_cancel(pthread_t *thread);
+/* terminate thread */
+void pthread_exit(void *value_ptr);
+
+#define pthread_mutex_init(pmutex, pattr) InitializeCriticalSection(pmutex)
+#define pthread_mutex_destroy(pmutex) DeleteCriticalSection(pmutex)
+#define pthread_mutex_lock(pmutex) EnterCriticalSection(pmutex)
+#define pthread_mutex_unlock(pmutex) LeaveCriticalSection(pmutex)
+
+#endif
diff --git a/protocols/Gadu-Gadu/src/libgadu/pubdir.c b/protocols/Gadu-Gadu/src/libgadu/pubdir.c
new file mode 100644
index 0000000000..f9d656dfe3
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/pubdir.c
@@ -0,0 +1,862 @@
+/* coding: UTF-8 */
+/* $Id: pubdir.c 11370 2010-03-13 16:17:54Z dezred $ */
+
+/*
+ * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Dawid Jarosz <dawjar@poczta.onet.pl>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 pubdir.c
+ *
+ * \brief Obsługa katalogu publicznego
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+#include "win32.h"
+#define random() rand()
+#else
+#include <unistd.h>
+#endif
+
+#include "libgadu.h"
+
+/**
+ * Rejestruje nowego uĹĽytkownika.
+ *
+ * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token().
+ *
+ * \param email Adres e-mail
+ * \param password Hasło
+ * \param tokenid Identyfikator tokenu
+ * \param tokenval Zawartość tokenu
+ * \param async Flaga połączenia asynchronicznego
+ *
+ * \return Struktura \c gg_http lub \c NULL w przypadku błędu
+ *
+ * \ingroup register
+ */
+struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async)
+{
+ struct gg_http *h;
+ char *__pwd, *__email, *__tokenid, *__tokenval, *form, *query;
+
+ if (!email || !password || !tokenid || !tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> register, NULL parameter\n");
+ errno = EFAULT;
+ return NULL;
+ }
+
+ __pwd = gg_urlencode(password);
+ __email = gg_urlencode(email);
+ __tokenid = gg_urlencode(tokenid);
+ __tokenval = gg_urlencode(tokenval);
+
+ if (!__pwd || !__email || !__tokenid || !__tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form fields\n");
+ free(__pwd);
+ free(__email);
+ free(__tokenid);
+ free(__tokenval);
+ return NULL;
+ }
+
+ form = gg_saprintf("pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u",
+ __pwd, __email, __tokenid, __tokenval,
+ gg_http_hash("ss", email, password));
+
+ free(__pwd);
+ free(__email);
+ free(__tokenid);
+ free(__tokenval);
+
+ if (!form) {
+ gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form query\n");
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> register, %s\n", form);
+
+ query = gg_saprintf(
+ "Host: " GG_REGISTER_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "User-Agent: " GG_HTTP_USERAGENT "\r\n"
+ "Content-Length: %d\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n"
+ "%s",
+ (int) strlen(form), form);
+
+ free(form);
+
+ if (!query) {
+ gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for query\n");
+ return NULL;
+ }
+
+ if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) {
+ gg_debug(GG_DEBUG_MISC, "=> register, gg_http_connect() failed mysteriously\n");
+ free(query);
+ return NULL;
+ }
+
+ h->type = GG_SESSION_REGISTER;
+
+ free(query);
+
+ h->callback = gg_pubdir_watch_fd;
+ h->destroy = gg_pubdir_free;
+
+ if (!async)
+ gg_pubdir_watch_fd(h);
+
+ return h;
+}
+
+#ifdef DOXYGEN
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do
+ * \c gg_pubdir_watch_fd().
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup register
+ */
+int gg_register_watch_fd(struct gg_httpd *h)
+{
+ return gg_pubdir_watch_fd(h);
+}
+
+/**
+ * Zwalnia zasoby po operacji.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free().
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup register
+ */
+void gg_register_free(struct gg_http *h)
+{
+ return gg_pubdir_free(h);
+}
+
+#endif /* DOXYGEN */
+
+/**
+ * Usuwa uĹĽytkownika.
+ *
+ * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token().
+ *
+ * \param uin Numer Gadu-Gadu
+ * \param password Hasło
+ * \param tokenid Identyfikator tokenu
+ * \param tokenval Zawartość tokenu
+ * \param async Flaga połączenia asynchronicznego
+ *
+ * \return Struktura \c gg_http lub \c NULL w przypadku błędu
+ *
+ * \ingroup unregister
+ */
+struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async)
+{
+ struct gg_http *h;
+ char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query;
+
+ if (!password || !tokenid || !tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n");
+ errno = EFAULT;
+ return NULL;
+ }
+
+ __pwd = gg_saprintf("%ld", random());
+ __fmpwd = gg_urlencode(password);
+ __tokenid = gg_urlencode(tokenid);
+ __tokenval = gg_urlencode(tokenval);
+
+ if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n");
+ free(__pwd);
+ free(__fmpwd);
+ free(__tokenid);
+ free(__tokenval);
+ return NULL;
+ }
+
+ form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd));
+
+ free(__fmpwd);
+ free(__pwd);
+ free(__tokenid);
+ free(__tokenval);
+
+ if (!form) {
+ gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n");
+ return NULL;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form);
+
+ query = gg_saprintf(
+ "Host: " GG_REGISTER_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "User-Agent: " GG_HTTP_USERAGENT "\r\n"
+ "Content-Length: %d\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n"
+ "%s",
+ (int) strlen(form), form);
+
+ free(form);
+
+ if (!query) {
+ gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n");
+ return NULL;
+ }
+
+ if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) {
+ gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n");
+ free(query);
+ return NULL;
+ }
+
+ h->type = GG_SESSION_UNREGISTER;
+
+ free(query);
+
+ h->callback = gg_pubdir_watch_fd;
+ h->destroy = gg_pubdir_free;
+
+ if (!async)
+ gg_pubdir_watch_fd(h);
+
+ return h;
+}
+
+#ifdef DOXYGEN
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do
+ * \c gg_pubdir_watch_fd().
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup unregister
+ */
+int gg_unregister_watch_fd(struct gg_httpd *h)
+{
+ return gg_pubdir_watch_fd(h);
+}
+
+/**
+ * Zwalnia zasoby po operacji.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free().
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup unregister
+ */
+void gg_unregister_free(struct gg_http *h)
+{
+ return gg_pubdir_free(h);
+}
+
+#endif /* DOXYGEN */
+
+/**
+ * Zmienia hasło użytkownika.
+ *
+ * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token().
+ *
+ * \param uin Numer Gadu-Gadu
+ * \param email Adres e-mail
+ * \param passwd Obecne hasło
+ * \param newpasswd Nowe hasło
+ * \param tokenid Identyfikator tokenu
+ * \param tokenval Zawartość tokenu
+ * \param async Flaga połączenia asynchronicznego
+ *
+ * \return Struktura \c gg_http lub \c NULL w przypadku błędu
+ *
+ * \ingroup passwd
+ */
+struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async)
+{
+ struct gg_http *h;
+ char *form, *query, *__email, *__fmpwd, *__pwd, *__tokenid, *__tokenval;
+
+ if (!uin || !email || !passwd || !newpasswd || !tokenid || !tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> change, NULL parameter\n");
+ errno = EFAULT;
+ return NULL;
+ }
+
+ __fmpwd = gg_urlencode(passwd);
+ __pwd = gg_urlencode(newpasswd);
+ __email = gg_urlencode(email);
+ __tokenid = gg_urlencode(tokenid);
+ __tokenval = gg_urlencode(tokenval);
+
+ if (!__fmpwd || !__pwd || !__email || !__tokenid || !__tokenval) {
+ gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n");
+ free(__fmpwd);
+ free(__pwd);
+ free(__email);
+ free(__tokenid);
+ free(__tokenval);
+ return NULL;
+ }
+
+ if (!(form = gg_saprintf("fmnumber=%d&fmpwd=%s&pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __email, __tokenid, __tokenval, gg_http_hash("ss", email, newpasswd)))) {
+ gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n");
+ free(__fmpwd);
+ free(__pwd);
+ free(__email);
+ free(__tokenid);
+ free(__tokenval);
+
+ return NULL;
+ }
+
+ free(__fmpwd);
+ free(__pwd);
+ free(__email);
+ free(__tokenid);
+ free(__tokenval);
+
+ gg_debug(GG_DEBUG_MISC, "=> change, %s\n", form);
+
+ query = gg_saprintf(
+ "Host: " GG_REGISTER_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "User-Agent: " GG_HTTP_USERAGENT "\r\n"
+ "Content-Length: %d\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n"
+ "%s",
+ (int) strlen(form), form);
+
+ free(form);
+
+ if (!query) {
+ gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for query\n");
+ return NULL;
+ }
+
+ if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) {
+ gg_debug(GG_DEBUG_MISC, "=> change, gg_http_connect() failed mysteriously\n");
+ free(query);
+ return NULL;
+ }
+
+ h->type = GG_SESSION_PASSWD;
+
+ free(query);
+
+ h->callback = gg_pubdir_watch_fd;
+ h->destroy = gg_pubdir_free;
+
+ if (!async)
+ gg_pubdir_watch_fd(h);
+
+ return h;
+}
+
+#ifdef DOXYGEN
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do
+ * \c gg_pubdir_watch_fd().
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup passwd
+ */
+int gg_change_passwd_watch_fd(struct gg_httpd *h)
+{
+ return gg_pubdir_watch_fd(h);
+}
+
+/**
+ * Zwalnia zasoby po operacji.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free().
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup passwd
+ */
+void gg_change_passwd_free(struct gg_http *h)
+{
+ return gg_pubdir_free(h);
+}
+
+#endif /* DOXYGEN */
+
+/**
+ * Wysyła hasło użytkownika na e-mail.
+ *
+ * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token().
+ *
+ * \param uin Numer Gadu-Gadu
+ * \param email Adres e-mail (podany przy rejestracji)
+ * \param tokenid Identyfikator tokenu
+ * \param tokenval Zawartość tokenu
+ * \param async Flaga połączenia asynchronicznego
+ *
+ * \return Struktura \c gg_http lub \c NULL w przypadku błędu
+ *
+ * \ingroup remind
+ */
+struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async)
+{
+ struct gg_http *h;
+ char *form, *query, *__tokenid, *__tokenval, *__email;
+
+ if (!tokenid || !tokenval || !email) {
+ gg_debug(GG_DEBUG_MISC, "=> remind, NULL parameter\n");
+ errno = EFAULT;
+ return NULL;
+ }
+
+ __tokenid = gg_urlencode(tokenid);
+ __tokenval = gg_urlencode(tokenval);
+ __email = gg_urlencode(email);
+
+ if (!__tokenid || !__tokenval || !__email) {
+ gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n");
+ free(__tokenid);
+ free(__tokenval);
+ free(__email);
+ return NULL;
+ }
+
+ if (!(form = gg_saprintf("userid=%d&code=%u&tokenid=%s&tokenval=%s&email=%s", uin, gg_http_hash("u", uin), __tokenid, __tokenval, __email))) {
+ gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n");
+ free(__tokenid);
+ free(__tokenval);
+ free(__email);
+ return NULL;
+ }
+
+ free(__tokenid);
+ free(__tokenval);
+ free(__email);
+
+ gg_debug(GG_DEBUG_MISC, "=> remind, %s\n", form);
+
+ query = gg_saprintf(
+ "Host: " GG_REMIND_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "User-Agent: " GG_HTTP_USERAGENT "\r\n"
+ "Content-Length: %d\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n"
+ "%s",
+ (int) strlen(form), form);
+
+ free(form);
+
+ if (!query) {
+ gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for query\n");
+ return NULL;
+ }
+
+ if (!(h = gg_http_connect(GG_REMIND_HOST, GG_REMIND_PORT, async, "POST", "/appsvc/fmsendpwd3.asp", query))) {
+ gg_debug(GG_DEBUG_MISC, "=> remind, gg_http_connect() failed mysteriously\n");
+ free(query);
+ return NULL;
+ }
+
+ h->type = GG_SESSION_REMIND;
+
+ free(query);
+
+ h->callback = gg_pubdir_watch_fd;
+ h->destroy = gg_pubdir_free;
+
+ if (!async)
+ gg_pubdir_watch_fd(h);
+
+ return h;
+}
+
+#ifdef DOXYGEN
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do
+ * \c gg_pubdir_watch_fd().
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup remind
+ */
+int gg_remind_watch_fd(struct gg_httpd *h)
+{
+ return gg_pubdir_watch_fd(h);
+}
+
+/**
+ * Zwalnia zasoby po operacji.
+ *
+ * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free().
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup remind
+ */
+void gg_remind_free(struct gg_http *h)
+{
+ return gg_pubdir_free(h);
+}
+
+#endif /* DOXYGEN */
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_pubdir_watch_fd(struct gg_http *h)
+{
+ struct gg_pubdir *p;
+ char *tmp;
+
+ if (!h) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (h->state == GG_STATE_ERROR) {
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (h->state != GG_STATE_PARSING) {
+ if (gg_http_watch_fd(h) == -1) {
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n");
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ if (h->state != GG_STATE_PARSING)
+ return 0;
+
+ h->state = GG_STATE_DONE;
+
+ if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) {
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n");
+ return -1;
+ }
+
+ p->success = 0;
+ p->uin = 0;
+
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body);
+
+ if ((tmp = strstr(h->body, "Tokens okregisterreply_packet.reg.dwUserId="))) {
+ p->success = 1;
+ p->uin = strtol(tmp + sizeof("Tokens okregisterreply_packet.reg.dwUserId=") - 1, NULL, 0);
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, success (okregisterreply, uin=%d)\n", p->uin);
+ } else if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) {
+ p->success = 1;
+ if (tmp[7] == ':')
+ p->uin = strtol(tmp + 8, NULL, 0);
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin);
+ } else
+ gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n");
+
+ return 0;
+}
+
+/**
+ * Zwalnia zasoby po operacji na katalogu publicznym.
+ *
+ * \param h Struktura połączenia
+ */
+void gg_pubdir_free(struct gg_http *h)
+{
+ if (!h)
+ return;
+
+ free(h->data);
+ gg_http_free(h);
+}
+
+/**
+ * Pobiera token do autoryzacji operacji na katalogu publicznym.
+ *
+ * Token jest niezbędny do tworzenia nowego i usuwania użytkownika,
+ * zmiany hasła itd.
+ *
+ * \param async Flaga połączenia asynchronicznego
+ *
+ * \return Struktura \c gg_http lub \c NULL w przypadku błędu
+ *
+ * \ingroup token
+ */
+struct gg_http *gg_token(int async)
+{
+ struct gg_http *h;
+ const char *query;
+
+ query = "Host: " GG_REGISTER_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "User-Agent: " GG_HTTP_USERAGENT "\r\n"
+ "Content-Length: 0\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n";
+
+ if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/regtoken.asp", query))) {
+ gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n");
+ return NULL;
+ }
+
+ h->type = GG_SESSION_TOKEN;
+
+ h->callback = gg_token_watch_fd;
+ h->destroy = gg_token_free;
+
+ if (!async)
+ gg_token_watch_fd(h);
+
+ return h;
+}
+
+/**
+ * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
+ *
+ * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE.
+ * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu
+ * znajdzie siÄ™ w polu \c error.
+ *
+ * \param h Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup token
+ */
+int gg_token_watch_fd(struct gg_http *h)
+{
+ if (!h) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (h->state == GG_STATE_ERROR) {
+ gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (h->state != GG_STATE_PARSING) {
+ if (gg_http_watch_fd(h) == -1) {
+ gg_debug(GG_DEBUG_MISC, "=> token, http failure\n");
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ if (h->state != GG_STATE_PARSING)
+ return 0;
+
+ /* jeśli h->data jest puste, to ściągaliśmy tokenid i url do niego,
+ * ale jeśli coś tam jest, to znaczy, że mamy drugi etap polegający
+ * na pobieraniu tokenu. */
+ if (!h->data) {
+ int width, height, length;
+ char *url = NULL, *tokenid = NULL, *path, *headers;
+ const char *host;
+ struct gg_http *h2;
+ struct gg_token *t;
+
+ gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body);
+
+ if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) {
+ gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n");
+ free(url);
+ return -1;
+ }
+
+ if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) {
+ gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n");
+ free(url);
+ free(tokenid);
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* dostaliśmy tokenid i wszystkie niezbędne informacje,
+ * więc pobierzmy obrazek z tokenem */
+
+ if (strncmp(url, "http://", 7)) {
+ path = gg_saprintf("%s?tokenid=%s", url, tokenid);
+ host = GG_REGISTER_HOST;
+ } else {
+ char *slash = strchr(url + 7, '/');
+
+ if (slash) {
+ path = gg_saprintf("%s?tokenid=%s", slash, tokenid);
+ *slash = 0;
+ host = url + 7;
+ } else {
+ gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n");
+ free(url);
+ free(tokenid);
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ if (!path) {
+ gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n");
+ free(url);
+ free(tokenid);
+ return -1;
+ }
+
+ if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) {
+ gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n");
+ free(path);
+ free(url);
+ free(tokenid);
+ return -1;
+ }
+
+ if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) {
+ gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n");
+ free(headers);
+ free(url);
+ free(path);
+ free(tokenid);
+ return -1;
+ }
+
+ free(headers);
+ free(path);
+ free(url);
+
+ gg_http_free_fields(h);
+
+ memcpy(h, h2, sizeof(struct gg_http));
+ free(h2);
+
+ h->type = GG_SESSION_TOKEN;
+
+ h->callback = gg_token_watch_fd;
+ h->destroy = gg_token_free;
+
+ if (!h->async)
+ gg_token_watch_fd(h);
+
+ if (!(h->data = t = malloc(sizeof(struct gg_token)))) {
+ gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token data\n");
+ free(tokenid);
+ return -1;
+ }
+
+ t->width = width;
+ t->height = height;
+ t->length = length;
+ t->tokenid = tokenid;
+ } else {
+ /* obrazek mamy w h->body */
+ h->state = GG_STATE_DONE;
+ }
+
+ return 0;
+}
+
+/**
+ * Zwalnia zasoby po operacji pobierania tokenu.
+ *
+ * \param h Struktura połączenia
+ *
+ * \ingroup token
+ */
+void gg_token_free(struct gg_http *h)
+{
+ struct gg_token *t;
+
+ if (!h)
+ return;
+
+ if ((t = h->data))
+ free(t->tokenid);
+
+ free(h->data);
+ gg_http_free(h);
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/pubdir50.c b/protocols/Gadu-Gadu/src/libgadu/pubdir50.c
new file mode 100644
index 0000000000..6914cbd01a
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/pubdir50.c
@@ -0,0 +1,557 @@
+/* coding: UTF-8 */
+/* $Id: pubdir50.c 11370 2010-03-13 16:17:54Z dezred $ */
+
+/*
+ * (C) Copyright 2003 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 pubdir50.c
+ *
+ * \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x
+ */
+
+#ifndef _WIN64
+#define _USE_32BIT_TIME_T
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef _WIN32
+#include "win32.h"
+#undef small
+#endif
+
+#include "libgadu.h"
+#include "internal.h"
+
+/**
+ * Tworzy nowe zapytanie katalogu publicznego.
+ *
+ * \param type Rodzaj zapytania
+ *
+ * \return Zmienna \c gg_pubdir50_t lub \c NULL w przypadku błędu.
+ *
+ * \ingroup pubdir50
+ */
+gg_pubdir50_t gg_pubdir50_new(int type)
+{
+ gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s));
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type);
+
+ if (!res) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n");
+ return NULL;
+ }
+
+ memset(res, 0, sizeof(struct gg_pubdir50_s));
+
+ res->type = type;
+
+ return res;
+}
+
+/**
+ * \internal Dodaje lub zastępuje pole zapytania lub odpowiedzi katalogu
+ * publicznego.
+ *
+ * \param req Zapytanie lub odpowiedĹş
+ * \param num Numer wyniku odpowiedzi (0 dla zapytania)
+ * \param field Nazwa pola
+ * \param value Wartość pola
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value)
+{
+ struct gg_pubdir50_entry *tmp = NULL, *entry;
+ char *dupfield, *dupvalue;
+ int i;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value);
+
+ if (!(dupvalue = strdup(value))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
+ return -1;
+ }
+
+ for (i = 0; i < req->entries_count; i++) {
+ if (req->entries[i].num != num || strcmp(req->entries[i].field, field))
+ continue;
+
+ free(req->entries[i].value);
+ req->entries[i].value = dupvalue;
+
+ return 0;
+ }
+
+ if (!(dupfield = strdup(field))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
+ free(dupvalue);
+ return -1;
+ }
+
+ if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
+ free(dupfield);
+ free(dupvalue);
+ return -1;
+ }
+
+ req->entries = tmp;
+
+ entry = &req->entries[req->entries_count];
+ entry->num = num;
+ entry->field = dupfield;
+ entry->value = dupvalue;
+
+ req->entries_count++;
+
+ return 0;
+}
+
+/**
+ * Dodaje pole zapytania.
+ *
+ * \param req Zapytanie
+ * \param field Nazwa pola
+ * \param value Wartość pola
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value)
+{
+ return gg_pubdir50_add_n(req, 0, field, value);
+}
+
+/**
+ * Ustawia numer sekwencyjny zapytania.
+ *
+ * \param req Zapytanie
+ * \param seq Numer sekwencyjny
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq)
+{
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq);
+
+ if (!req) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ req->seq = seq;
+
+ return 0;
+}
+
+/**
+ * Zwalnia zasoby po zapytaniu lub odpowiedzi katalogu publicznego.
+ *
+ * \param s Zapytanie lub odpowiedĹş
+ *
+ * \ingroup pubdir50
+ */
+void gg_pubdir50_free(gg_pubdir50_t s)
+{
+ int i;
+
+ if (!s)
+ return;
+
+ for (i = 0; i < s->entries_count; i++) {
+ free(s->entries[i].field);
+ free(s->entries[i].value);
+ }
+
+ free(s->entries);
+ free(s);
+}
+
+/**
+ * Wysyła zapytanie katalogu publicznego do serwera.
+ *
+ * \param sess Struktura sesji
+ * \param req Zapytanie
+ *
+ * \return Numer sekwencyjny zapytania lub 0 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req)
+{
+ size_t size = 5;
+ int i;
+ uint32_t res;
+ char *buf, *p;
+ struct gg_pubdir50_request *r;
+
+ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req);
+
+ if (!sess || !req) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n");
+ errno = EFAULT;
+ return 0;
+ }
+
+ if (sess->state != GG_STATE_CONNECTED) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() not connected\n");
+ errno = ENOTCONN;
+ return 0;
+ }
+
+ for (i = 0; i < req->entries_count; i++) {
+ /* wyszukiwanie bierze tylko pierwszy wpis */
+ if (req->entries[i].num)
+ continue;
+
+ if (sess->encoding == GG_ENCODING_CP1250) {
+ size += strlen(req->entries[i].field) + 1;
+ size += strlen(req->entries[i].value) + 1;
+ } else {
+ char *tmp;
+
+ tmp = gg_utf8_to_cp(req->entries[i].field);
+
+ if (tmp == NULL)
+ return -1;
+
+ size += strlen(tmp) + 1;
+
+ free(tmp);
+
+ tmp = gg_utf8_to_cp(req->entries[i].value);
+
+ if (tmp == NULL)
+ return -1;
+
+ size += strlen(tmp) + 1;
+
+ free(tmp);
+ }
+ }
+
+ if (!(buf = malloc(size))) {
+ gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size);
+ return 0;
+ }
+
+ if (!req->seq)
+ req->seq = (uint32_t)time(NULL);
+
+ res = req->seq;
+
+ r = (struct gg_pubdir50_request*) buf;
+ r->type = req->type;
+ r->seq = gg_fix32(req->seq);
+
+ for (i = 0, p = buf + 5; i < req->entries_count; i++) {
+ if (req->entries[i].num)
+ continue;
+
+ if (sess->encoding == GG_ENCODING_CP1250) {
+ strcpy(p, req->entries[i].field);
+ p += strlen(p) + 1;
+
+ strcpy(p, req->entries[i].value);
+ p += strlen(p) + 1;
+ } else {
+ char *tmp;
+
+ tmp = gg_utf8_to_cp(req->entries[i].field);
+
+ if (tmp == NULL) {
+ free(buf);
+ return -1;
+ }
+
+ strcpy(p, tmp);
+ p += strlen(tmp) + 1;
+ free(tmp);
+
+ tmp = gg_utf8_to_cp(req->entries[i].value);
+
+ if (tmp == NULL) {
+ free(buf);
+ return -1;
+ }
+
+ strcpy(p, tmp);
+ p += strlen(tmp) + 1;
+ free(tmp);
+ }
+ }
+
+ if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1)
+ res = 0;
+
+ free(buf);
+
+ return res;
+}
+
+/*
+ * \internal Analizuje przychodzÄ…cy pakiet odpowiedzi i zapisuje wynik
+ * w strukturze \c gg_event.
+ *
+ * \param sess Struktura sesji
+ * \param e Struktura zdarzenia
+ * \param packet Pakiet odpowiedzi
+ * \param length Długość pakietu odpowiedzi
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length)
+{
+ const char *end = packet + length, *p;
+ struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet;
+ gg_pubdir50_t res;
+ int num = 0;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply_sess(%p, %p, %p, %d);\n", sess, e, packet, length);
+
+ if (!sess || !e || !packet) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (length < 5) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!(res = gg_pubdir50_new(r->type))) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n");
+ return -1;
+ }
+
+ e->event.pubdir50 = res;
+
+ res->seq = gg_fix32(r->seq);
+
+ switch (res->type) {
+ case GG_PUBDIR50_READ:
+ e->type = GG_EVENT_PUBDIR50_READ;
+ break;
+
+ case GG_PUBDIR50_WRITE:
+ e->type = GG_EVENT_PUBDIR50_WRITE;
+ break;
+
+ default:
+ e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY;
+ break;
+ }
+
+ /* brak wynikĂłw? */
+ if (length == 5)
+ return 0;
+
+ /* pomiń początek odpowiedzi */
+ p = packet + 5;
+
+ while (p < end) {
+ const char *field, *value;
+
+ field = p;
+
+ /* sprawdź, czy nie mamy podziału na kolejne pole */
+ if (!*field) {
+ num++;
+ field++;
+ }
+
+ value = NULL;
+
+ for (p = field; p < end; p++) {
+ /* jeśli mamy koniec tekstu... */
+ if (!*p) {
+ /* ...i jeszcze nie mieliśmy wartości pola to
+ * wiemy, że po tym zerze jest wartość... */
+ if (!value)
+ value = p + 1;
+ else
+ /* ...w przeciwym wypadku koniec
+ * wartości i możemy wychodzić
+ * grzecznie z pętli */
+ break;
+ }
+ }
+
+ /* sprawdĹşmy, czy pole nie wychodzi poza pakiet, ĹĽeby nie
+ * mieć segfaultów, jeśli serwer przestanie zakańczać pakietów
+ * przez \0 */
+
+ if (p == end) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n");
+ goto failure;
+ }
+
+ p++;
+
+ /* jeśli dostaliśmy namier na następne wyniki, to znaczy że
+ * mamy koniec wynikĂłw i nie jest to kolejna osoba. */
+ if (!strcasecmp(field, "nextstart")) {
+ res->next = atoi(value);
+ num--;
+ } else {
+ if (sess->encoding == GG_ENCODING_CP1250) {
+ if (gg_pubdir50_add_n(res, num, field, value) == -1)
+ goto failure;
+ } else {
+ char *tmp;
+
+ tmp = gg_cp_to_utf8(value);
+
+ if (tmp == NULL)
+ goto failure;
+
+ if (gg_pubdir50_add_n(res, num, field, tmp) == -1) {
+ free(tmp);
+ goto failure;
+ }
+
+ free(tmp);
+ }
+ }
+ }
+
+ res->count = num + 1;
+
+ return 0;
+
+failure:
+ gg_pubdir50_free(res);
+ return -1;
+}
+
+/**
+ * Pobiera pole z odpowiedzi katalogu publicznego.
+ *
+ * \param res OdpowiedĹş
+ * \param num Numer wyniku odpowiedzi
+ * \param field Nazwa pola (wielkość liter nie ma znaczenia)
+ *
+ * \return Wartość pola lub \c NULL jeśli nie znaleziono
+ *
+ * \ingroup pubdir50
+ */
+const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field)
+{
+ char *value = NULL;
+ int i;
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field);
+
+ if (!res || num < 0 || !field) {
+ gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ for (i = 0; i < res->entries_count; i++) {
+ if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) {
+ value = res->entries[i].value;
+ break;
+ }
+ }
+
+ return value;
+}
+
+/**
+ * Zwraca liczbÄ™ wynikĂłw odpowiedzi.
+ *
+ * \param res OdpowiedĹş
+ *
+ * \return Liczba wyników lub -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+int gg_pubdir50_count(gg_pubdir50_t res)
+{
+ return (!res) ? -1 : res->count;
+}
+
+/**
+ * Zwraca rodzaj zapytania lub odpowiedzi.
+ *
+ * \param res Zapytanie lub odpowiedĹş
+ *
+ * \return Rodzaj lub -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+int gg_pubdir50_type(gg_pubdir50_t res)
+{
+ return (!res) ? -1 : res->type;
+}
+
+/**
+ * Zwraca numer, od ktĂłrego naleĹĽy rozpoczÄ…c kolejne wyszukiwanie.
+ *
+ * Dłuższe odpowiedzi katalogu publicznego są wysyłane przez serwer
+ * w mniejszych paczkach. Po otrzymaniu odpowiedzi, jeśli numer kolejnego
+ * wyszukiwania jest większy od zera, dalsze wyniki można otrzymać przez
+ * wywołanie kolejnego zapytania z określonym numerem początkowym.
+ *
+ * \param res OdpowiedĹş
+ *
+ * \return Numer lub -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+uin_t gg_pubdir50_next(gg_pubdir50_t res)
+{
+ return (!res) ? (unsigned) -1 : res->next;
+}
+
+/**
+ * Zwraca numer sekwencyjny zapytania lub odpowiedzi.
+ *
+ * \param res Zapytanie lub odpowiedĹş
+ *
+ * \return Numer sekwencyjny lub -1 w przypadku błędu
+ *
+ * \ingroup pubdir50
+ */
+uint32_t gg_pubdir50_seq(gg_pubdir50_t res)
+{
+ return (!res) ? (unsigned) -1 : res->seq;
+}
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
diff --git a/protocols/Gadu-Gadu/src/libgadu/resolver.c b/protocols/Gadu-Gadu/src/libgadu/resolver.c
new file mode 100644
index 0000000000..1adef3ef9d
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/resolver.c
@@ -0,0 +1,766 @@
+/* coding: UTF-8 */
+/* $Id$ */
+
+/*
+ * (C) Copyright 2001-2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ * Robert J. WoĹşny <speedy@ziew.org>
+ * Arkadiusz Miśkiewicz <arekm@pld-linux.org>
+ * Tomasz Chiliński <chilek@chilan.com>
+ * Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 resolver.c
+ *
+ * \brief Funkcje rozwiÄ…zywania nazw
+ */
+
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif /* _WIN32 */
+
+#ifndef _WIN32
+#include <netdb.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <signal.h>
+
+#include "libgadu.h"
+#include "resolver.h"
+#include "compat.h"
+
+/** SposĂłb rozwiÄ…zywania nazw serwerĂłw */
+static gg_resolver_t gg_global_resolver_type = GG_RESOLVER_DEFAULT;
+
+/** Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */
+static int (*gg_global_resolver_start)(SOCKET *fd, void **private_data, const char *hostname);
+
+/** Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */
+static void (*gg_global_resolver_cleanup)(void **private_data, int force);
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+
+#include <pthread.h>
+
+#ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R
+/**
+ * \internal Funkcja pomocnicza zwalniajÄ…ca zasoby po rozwiÄ…zywaniu nazwy
+ * w wÄ…tku.
+ *
+ * \param data WskaĹşnik na wskaĹşnik bufora zaalokowanego w wÄ…tku
+ */
+static void gg_gethostbyname_cleaner(void *data)
+{
+ char **buf_ptr = (char**) data;
+
+ if (buf_ptr != NULL) {
+ free(*buf_ptr);
+ *buf_ptr = NULL;
+ }
+}
+#endif
+#endif /* GG_CONFIG_HAVE_PTHREAD */
+
+/**
+ * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
+ *
+ * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli
+ * nie, to zwykłej \c gethostbyname.
+ *
+ * \param hostname Nazwa serwera
+ * \param addr WskaĹşnik na rezultat rozwiÄ…zywania nazwy
+ * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread)
+{
+#ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R
+ char *buf = NULL;
+ char *new_buf = NULL;
+ struct hostent he;
+ struct hostent *he_ptr = NULL;
+ size_t buf_len = 1024;
+ int result = -1;
+ int h_errnop;
+ int ret = 0;
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ int old_state;
+#endif
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ pthread_cleanup_push(gg_gethostbyname_cleaner, &buf);
+
+ if (pthread)
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
+#endif
+
+ buf = malloc(buf_len);
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ if (pthread)
+ pthread_setcancelstate(old_state, NULL);
+#endif
+
+ if (buf != NULL) {
+#ifndef sun
+ while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) {
+#else
+ while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) {
+#endif
+ buf_len *= 2;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ if (pthread)
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
+#endif
+
+ new_buf = realloc(buf, buf_len);
+
+ if (new_buf != NULL)
+ buf = new_buf;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ if (pthread)
+ pthread_setcancelstate(old_state, NULL);
+#endif
+
+ if (new_buf == NULL) {
+ ret = ENOMEM;
+ break;
+ }
+ }
+
+ if (ret == 0 && he_ptr != NULL) {
+ memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr));
+ result = 0;
+ }
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ if (pthread)
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
+#endif
+
+ free(buf);
+ buf = NULL;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ if (pthread)
+ pthread_setcancelstate(old_state, NULL);
+#endif
+ }
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ pthread_cleanup_pop(1);
+#endif
+
+ return result;
+#else
+ struct hostent *he;
+
+ he = gethostbyname(hostname);
+
+ if (he == NULL)
+ return -1;
+
+ memcpy(addr, he->h_addr, sizeof(struct in_addr));
+
+ return 0;
+#endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */
+}
+
+/**
+ * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
+ *
+ * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli
+ * nie, to zwykłej \c gethostbyname.
+ *
+ * \param hostname Nazwa serwera
+ *
+ * \return Zaalokowana struktura \c in_addr lub NULL w przypadku błędu.
+ */
+struct in_addr *gg_gethostbyname(const char *hostname)
+{
+ struct in_addr *addr;
+
+ if (!(addr = malloc(sizeof(struct in_addr))))
+ return NULL;
+
+ if (gg_gethostbyname_real(hostname, addr, 0)) {
+ free(addr);
+ return NULL;
+ }
+ return addr;
+}
+
+/**
+ * \internal Struktura przekazywana do wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™.
+ */
+struct gg_resolver_fork_data {
+ int pid; /*< Identyfikator procesu */
+};
+
+/**
+ * \internal RozwiÄ…zuje nazwÄ™ serwera w osobnym procesie.
+ *
+ * Połączenia asynchroniczne nie mogą blokować procesu w trakcie rozwiązywania
+ * nazwy serwera. W tym celu tworzony jest potok, nowy proces i dopiero w nim
+ * przeprowadzane jest rozwiÄ…zywanie nazwy. Deskryptor strony do odczytu
+ * zapisuje siÄ™ w strukturze sieci i czeka na dane w postaci struktury
+ * \c in_addr. Jeśli nie znaleziono nazwy, zwracana jest \c INADDR_NONE.
+ *
+ * \param fd WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor
+ * potoku
+ * \param priv_data WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony wskaĹşnik
+ * do numeru procesu potomnego rozwiÄ…zujÄ…cego nazwÄ™
+ * \param hostname Nazwa serwera do rozwiÄ…zania
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname)
+{
+ struct gg_resolver_fork_data *data = NULL;
+ struct in_addr addr;
+ int new_errno;
+ SOCKET pipes[2];
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname);
+
+ if (fd == NULL || priv_data == NULL || hostname == NULL) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ data = malloc(sizeof(struct gg_resolver_fork_data));
+
+ if (data == NULL) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n");
+ return -1;
+ }
+
+ if (pipe(pipes) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno));
+ free(data);
+ return -1;
+ }
+
+ data->pid = fork();
+
+ if (data->pid == -1) {
+ new_errno = errno;
+ goto cleanup;
+ }
+
+ if (data->pid == 0) {
+ gg_sock_close(pipes[0]);
+
+ if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
+ /* W przypadku błędu gg_gethostbyname_real() zwróci -1
+ * i nie zmieni &addr. Tam jest juĹĽ INADDR_NONE,
+ * więc nie musimy robić nic więcej. */
+ gg_gethostbyname_real(hostname, &addr, 0);
+ }
+
+ if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr))
+ exit(1);
+
+ exit(0);
+ }
+
+ gg_sock_close(pipes[1]);
+
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data);
+
+ *fd = pipes[0];
+ *priv_data = data;
+
+ return 0;
+
+cleanup:
+ free(data);
+ gg_sock_close(pipes[0]);
+ gg_sock_close(pipes[1]);
+
+ errno = new_errno;
+
+ return -1;
+}
+
+/**
+ * \internal Usuwanie zasobĂłw po procesie rozwiÄ…zywaniu nazwy.
+ *
+ * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu
+ * zasobĂłw sesji podczas rozwiÄ…zywania nazwy.
+ *
+ * \param priv_data WskaĹşnik na zmiennÄ… przechowujÄ…cÄ… wskaĹşnik do prywatnych
+ * danych
+ * \param force Flaga usuwania zasobów przed zakończeniem działania
+ */
+static void gg_resolver_fork_cleanup(void **priv_data, int force)
+{
+ struct gg_resolver_fork_data *data;
+
+ if (priv_data == NULL || *priv_data == NULL)
+ return;
+
+ data = (struct gg_resolver_fork_data*) *priv_data;
+ *priv_data = NULL;
+
+ if (force)
+ kill(data->pid, SIGKILL);
+
+ waitpid(data->pid, NULL, WNOHANG);
+
+ free(data);
+}
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+
+/**
+ * \internal Struktura przekazywana do wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™.
+ */
+struct gg_resolver_pthread_data {
+ pthread_t thread; /*< Identyfikator wÄ…tku */
+ char *hostname; /*< Nazwa serwera */
+ SOCKET rfd; /*< Deskryptor do odczytu */
+ SOCKET wfd; /*< Deskryptor do zapisu */
+};
+
+/**
+ * \internal Usuwanie zasobĂłw po wÄ…tku rozwiÄ…zywaniu nazwy.
+ *
+ * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu
+ * zasobĂłw sesji podczas rozwiÄ…zywania nazwy.
+ *
+ * \param priv_data WskaĹşnik na zmiennÄ… przechowujÄ…cÄ… wskaĹşnik do prywatnych
+ * danych
+ * \param force Flaga usuwania zasobów przed zakończeniem działania
+ */
+static void gg_resolver_pthread_cleanup(void **priv_data, int force)
+{
+ struct gg_resolver_pthread_data *data;
+
+ if (priv_data == NULL || *priv_data == NULL)
+ return;
+
+ data = (struct gg_resolver_pthread_data *) *priv_data;
+ *priv_data = NULL;
+
+ if (force) {
+ pthread_cancel(&data->thread);
+ pthread_join(&data->thread, NULL);
+ }
+
+ free(data->hostname);
+ data->hostname = NULL;
+
+ if (data->wfd != -1) {
+ gg_sock_close(data->wfd);
+ data->wfd = -1;
+ }
+
+ free(data);
+}
+
+/**
+ * \internal WÄ…tek rozwiÄ…zujÄ…cy nazwÄ™.
+ *
+ * \param arg WskaĹşnik na strukturÄ™ \c gg_resolver_pthread_data
+ */
+static void *__stdcall gg_resolver_pthread_thread(void *arg)
+{
+ struct gg_resolver_pthread_data *data = arg;
+ struct in_addr addr;
+
+ pthread_detach(pthread_self());
+
+ if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) {
+ /* W przypadku błędu gg_gethostbyname_real() zwróci -1
+ * i nie zmieni &addr. Tam jest juĹĽ INADDR_NONE,
+ * więc nie musimy robić nic więcej. */
+ gg_gethostbyname_real(data->hostname, &addr, 1);
+ }
+
+ if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr))
+ pthread_exit(NULL);
+ else
+ pthread_exit((void*) -1);
+
+ return NULL; /* żeby kompilator nie marudził */
+}
+
+/**
+ * \internal RozwiÄ…zuje nazwÄ™ serwera w osobnym wÄ…tku.
+ *
+ * Funkcja działa analogicznie do \c gg_resolver_fork_start(), z tą różnicą,
+ * że działa na wątkach, nie procesach. Jest dostępna wyłącznie gdy podczas
+ * kompilacji włączono odpowiednią opcję.
+ *
+ * \param fd WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor
+ * potoku
+ * \param priv_data WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony wskaĹşnik
+ * do prywatnych danych wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™
+ * \param hostname Nazwa serwera do rozwiÄ…zania
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname)
+{
+ struct gg_resolver_pthread_data *data = NULL;
+ int new_errno;
+ SOCKET pipes[2];
+
+ gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname);
+
+ if (fd == NULL || priv_data == NULL || hostname == NULL) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n");
+ errno = EFAULT;
+ return -1;
+ }
+
+ data = malloc(sizeof(struct gg_resolver_pthread_data));
+
+ if (data == NULL) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n");
+ return -1;
+ }
+
+ if (pipe(pipes) == -1) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno));
+ free(data);
+ return -1;
+ }
+
+ data->hostname = strdup(hostname);
+
+ if (data->hostname == NULL) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n");
+ new_errno = errno;
+ goto cleanup;
+ }
+
+ data->rfd = pipes[0];
+ data->wfd = pipes[1];
+
+ if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) {
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n");
+ new_errno = errno;
+ goto cleanup;
+ }
+
+ gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data);
+
+ *fd = pipes[0];
+ *priv_data = data;
+
+ return 0;
+
+cleanup:
+ if (data) {
+ free(data->hostname);
+ free(data);
+ }
+
+ gg_sock_close(pipes[0]);
+ gg_sock_close(pipes[1]);
+
+ errno = new_errno;
+
+ return -1;
+}
+
+#endif /* GG_CONFIG_HAVE_PTHREAD */
+
+/**
+ * Ustawia sposĂłb rozwiÄ…zywania nazw w sesji.
+ *
+ * \param gs Struktura sesji
+ * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type)
+{
+ if (gs == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (type == GG_RESOLVER_DEFAULT) {
+ if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) {
+ gs->resolver_type = gg_global_resolver_type;
+ gs->resolver_start = gg_global_resolver_start;
+ gs->resolver_cleanup = gg_global_resolver_cleanup;
+ return 0;
+ }
+
+#if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT)
+ type = GG_RESOLVER_FORK;
+#else
+ type = GG_RESOLVER_PTHREAD;
+#endif
+ }
+
+ switch (type) {
+ case GG_RESOLVER_FORK:
+ gs->resolver_type = type;
+ gs->resolver_start = gg_resolver_fork_start;
+ gs->resolver_cleanup = gg_resolver_fork_cleanup;
+ return 0;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ case GG_RESOLVER_PTHREAD:
+ gs->resolver_type = type;
+ gs->resolver_start = gg_resolver_pthread_start;
+ gs->resolver_cleanup = gg_resolver_pthread_cleanup;
+ return 0;
+#endif
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/**
+ * Zwraca sposĂłb rozwiÄ…zywania nazw w sesji.
+ *
+ * \param gs Struktura sesji
+ *
+ * \return SposĂłb rozwiÄ…zywania nazw
+ */
+gg_resolver_t gg_session_get_resolver(struct gg_session *gs)
+{
+ if (gs == NULL) {
+ errno = EINVAL;
+ return GG_RESOLVER_INVALID;
+ }
+
+ return gs->resolver_type;
+}
+
+/**
+ * Ustawia własny sposób rozwiązywania nazw w sesji.
+ *
+ * \param gs Struktura sesji
+ * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy
+ * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int))
+{
+ if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gs->resolver_type = GG_RESOLVER_CUSTOM;
+ gs->resolver_start = resolver_start;
+ gs->resolver_cleanup = resolver_cleanup;
+
+ return 0;
+}
+
+/**
+ * Ustawia sposób rozwiązywania nazw połączenia HTTP.
+ *
+ * \param gh Struktura połączenia
+ * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type)
+{
+ if (gh == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (type == GG_RESOLVER_DEFAULT) {
+ if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) {
+ gh->resolver_type = gg_global_resolver_type;
+ gh->resolver_start = gg_global_resolver_start;
+ gh->resolver_cleanup = gg_global_resolver_cleanup;
+ return 0;
+ }
+
+#if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT)
+ type = GG_RESOLVER_FORK;
+#else
+ type = GG_RESOLVER_PTHREAD;
+#endif
+ }
+
+ switch (type) {
+ case GG_RESOLVER_FORK:
+ gh->resolver_type = type;
+ gh->resolver_start = gg_resolver_fork_start;
+ gh->resolver_cleanup = gg_resolver_fork_cleanup;
+ return 0;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ case GG_RESOLVER_PTHREAD:
+ gh->resolver_type = type;
+ gh->resolver_start = gg_resolver_pthread_start;
+ gh->resolver_cleanup = gg_resolver_pthread_cleanup;
+ return 0;
+#endif
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/**
+ * Zwraca sposób rozwiązywania nazw połączenia HTTP.
+ *
+ * \param gh Struktura połączenia
+ *
+ * \return SposĂłb rozwiÄ…zywania nazw
+ */
+gg_resolver_t gg_http_get_resolver(struct gg_http *gh)
+{
+ if (gh == NULL) {
+ errno = EINVAL;
+ return GG_RESOLVER_INVALID;
+ }
+
+ return gh->resolver_type;
+}
+
+/**
+ * Ustawia własny sposób rozwiązywania nazw połączenia HTTP.
+ *
+ * \param gh Struktura sesji
+ * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy
+ * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int))
+{
+ if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gh->resolver_type = GG_RESOLVER_CUSTOM;
+ gh->resolver_start = resolver_start;
+ gh->resolver_cleanup = resolver_cleanup;
+
+ return 0;
+}
+
+/**
+ * Ustawia sposĂłb rozwiÄ…zywania nazw globalnie dla biblioteki.
+ *
+ * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_global_set_resolver(gg_resolver_t type)
+{
+ switch (type) {
+ case GG_RESOLVER_DEFAULT:
+ gg_global_resolver_type = type;
+ gg_global_resolver_start = NULL;
+ gg_global_resolver_cleanup = NULL;
+ return 0;
+
+ case GG_RESOLVER_FORK:
+ gg_global_resolver_type = type;
+ gg_global_resolver_start = gg_resolver_fork_start;
+ gg_global_resolver_cleanup = gg_resolver_fork_cleanup;
+ return 0;
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+ case GG_RESOLVER_PTHREAD:
+ gg_global_resolver_type = type;
+ gg_global_resolver_start = gg_resolver_pthread_start;
+ gg_global_resolver_cleanup = gg_resolver_pthread_cleanup;
+ return 0;
+#endif
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/**
+ * Zwraca sposĂłb rozwiÄ…zywania nazw globalnie dla biblioteki.
+ *
+ * \return SposĂłb rozwiÄ…zywania nazw
+ */
+gg_resolver_t gg_global_get_resolver(void)
+{
+ return gg_global_resolver_type;
+}
+
+/**
+ * Ustawia własny sposób rozwiązywania nazw globalnie dla biblioteki.
+ *
+ * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy
+ * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby
+ *
+ * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco:
+ * - \c "SOCKET *fd" &mdash; wskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor potoku
+ * - \c "void **priv_data" &mdash; wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy
+ * - \c "const char *name" &mdash; nazwa serwera do rozwiÄ…zania
+ *
+ * Parametry funkcji zwalniającej zasoby wyglądają następująco:
+ * - \c "void **priv_data" &mdash; wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu
+ * - \c "int force" &mdash; flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji.
+ *
+ * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub
+ * inny deskryptor pozwalajÄ…cy na co najmniej jednostronnÄ… komunikacjÄ™ i
+ * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy,
+ * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do
+ * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać
+ * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby
+ * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed
+ * rozwiÄ…zaniem nazwy, np. za pomocÄ… funkcji \c gg_logoff(), funkcja
+ * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1.
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int))
+{
+ if (resolver_start == NULL || resolver_cleanup == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gg_global_resolver_type = GG_RESOLVER_CUSTOM;
+ gg_global_resolver_start = resolver_start;
+ gg_global_resolver_cleanup = resolver_cleanup;
+
+ return 0;
+}
+
diff --git a/protocols/Gadu-Gadu/src/libgadu/resolver.h b/protocols/Gadu-Gadu/src/libgadu/resolver.h
new file mode 100644
index 0000000000..145c5178a4
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/resolver.h
@@ -0,0 +1,33 @@
+/* coding: UTF-8 */
+/* $Id$ */
+
+/*
+ * (C) Copyright 2008 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef LIBGADU_RESOLVER_H
+#define LIBGADU_RESOLVER_H
+
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <arpa/inet.h>
+#endif /* _WIN32 */
+
+int gg_gethostbyname_real(const char *hostname, struct in_addr *result, int pthread);
+
+#endif /* LIBGADU_RESOLVER_H */
diff --git a/protocols/Gadu-Gadu/src/libgadu/sha1.c b/protocols/Gadu-Gadu/src/libgadu/sha1.c
new file mode 100644
index 0000000000..124a1cffaa
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/sha1.c
@@ -0,0 +1,308 @@
+/* coding: UTF-8 */
+/* $Id: sha1.c,v 1.4 2007-07-20 23:00:50 wojtekka Exp $ */
+
+/*
+ * (C) Copyright 2007 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ * Public domain SHA-1 implementation by Steve Reid <steve@edmweb.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License Version
+ * 2.1 as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 sha1.c
+ *
+ * \brief Funkcje wyznaczania skrĂłtu SHA1
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#include "win32.h"
+#else
+#include <unistd.h>
+#endif
+
+#include "libgadu.h"
+
+/** \cond ignore */
+
+#if defined(GG_CONFIG_HAVE_OPENSSL) && !defined(GG_CONFIG_MIRANDA)
+
+#include <openssl/sha.h>
+
+#else
+
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+
+Modified by Wojtek Kaniewski <wojtekka@toxygen.net> for compatibility
+with libgadu and OpenSSL API.
+
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define LITTLE_ENDIAN * This should be #define'd if true. */
+/* #define SHA1HANDSOFF * Copies data before messing with it. */
+
+#include <string.h>
+
+typedef struct {
+ uint32_t state[5];
+ uint32_t count[2];
+ unsigned char buffer[64];
+} SHA_CTX;
+
+static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64]);
+static void SHA1_Init(SHA_CTX* context);
+static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len);
+static void SHA1_Final(unsigned char digest[20], SHA_CTX* context);
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#ifndef GG_CONFIG_BIGENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64])
+{
+uint32_t a, b, c, d, e;
+typedef union {
+ unsigned char c[64];
+ uint32_t l[16];
+} CHAR64LONG16;
+CHAR64LONG16* block;
+static unsigned char workspace[64];
+ block = (CHAR64LONG16*)workspace;
+ memcpy(block, buffer, 64);
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+}
+
+
+/* SHA1_Init - Initialize new context */
+
+static void SHA1_Init(SHA_CTX* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len)
+{
+unsigned int i, j;
+
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;
+ context->count[1] += (len >> 29);
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1_Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1_Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+
+static void SHA1_Final(unsigned char digest[20], SHA_CTX* context)
+{
+uint32_t i, j;
+unsigned char finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8)) & 255); /* Endian independent */
+ }
+ SHA1_Update(context, (unsigned char *)"\200", 1);
+ while ((context->count[0] & 504) != 448) {
+ SHA1_Update(context, (unsigned char *)"\0", 1);
+ }
+ SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8)) & 255);
+ }
+ /* Wipe variables */
+ i = j = 0;
+ memset(context->buffer, 0, 64);
+ memset(context->state, 0, 20);
+ memset(context->count, 0, 8);
+ memset(&finalcount, 0, 8);
+#ifdef SHA1HANDSOFF /* make SHA1_Transform overwrite it's own static vars */
+ SHA1_Transform(context->state, context->buffer);
+#endif
+}
+
+#endif /* GG_CONFIG_HAVE_OPENSSL */
+
+/** \endcond */
+
+/** \cond internal */
+
+/**
+ * \internal Liczy skrót SHA1 z ziarna i hasła.
+ *
+ * \param password Hasło
+ * \param seed Ziarno
+ * \param result Bufor na wynik funkcji skrĂłtu (20 bajtĂłw)
+ */
+void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result)
+{
+ SHA_CTX ctx;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, (const unsigned char*) password, (unsigned int)strlen(password));
+ seed = gg_fix32(seed);
+ SHA1_Update(&ctx, (uint8_t*) &seed, 4);
+
+ SHA1_Final(result, &ctx);
+}
+
+/**
+ * \internal Liczy skrĂłt SHA1 z pliku.
+ *
+ * \param fd Deskryptor pliku
+ * \param result WskaĹşnik na skrĂłt
+ *
+ * \return 0 lub -1
+ */
+int gg_file_hash_sha1(int fd, uint8_t *result)
+{
+ unsigned char buf[4096];
+ SHA_CTX ctx;
+ off_t pos, len;
+ int res;
+
+ if ((pos = lseek(fd, 0, SEEK_CUR)) == (off_t) -1)
+ return -1;
+
+ if ((len = lseek(fd, 0, SEEK_END)) == (off_t) -1)
+ return -1;
+
+ if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
+ return -1;
+
+ SHA1_Init(&ctx);
+
+ if (len <= 10485760) {
+ while ((res = read(fd, buf, sizeof(buf))) > 0)
+ SHA1_Update(&ctx, buf, res);
+ } else {
+ int i;
+
+ for (i = 0; i < 9; i++) {
+ int j;
+
+ if (lseek(fd, (len - 1048576) / 9 * i, SEEK_SET) == (off_t) - 1)
+ return -1;
+
+ for (j = 0; j < 1048576 / sizeof(buf); j++) {
+ if ((res = read(fd, buf, sizeof(buf))) != sizeof(buf)) {
+ res = -1;
+ break;
+ }
+
+ SHA1_Update(&ctx, buf, res);
+ }
+
+ if (res == -1)
+ break;
+ }
+ }
+
+ if (res == -1)
+ return -1;
+
+ SHA1_Final(result, &ctx);
+
+ if (lseek(fd, pos, SEEK_SET) == (off_t) -1)
+ return -1;
+
+ return 0;
+}
+
+/** \endcond */
diff --git a/protocols/Gadu-Gadu/src/libgadu/win32.c b/protocols/Gadu-Gadu/src/libgadu/win32.c
new file mode 100644
index 0000000000..4f5ad3ff5d
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/win32.c
@@ -0,0 +1,65 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2010 Bartosz Białek
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef _WIN32
+#include "win32.h"
+
+int sockpipe(SOCKET filedes[2])
+{
+ SOCKET sock;
+ struct sockaddr_in sin;
+ unsigned int len = sizeof(sin);
+
+ filedes[0] = filedes[1] = INVALID_SOCKET;
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
+ return -1;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(0);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ if (bind(sock, (SOCKADDR *)&sin, len) == SOCKET_ERROR ||
+ listen(sock, 1) == SOCKET_ERROR ||
+ getsockname(sock, (SOCKADDR *)&sin, &len) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+
+ if ((filedes[1] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET ||
+ connect(filedes[1], (SOCKADDR *)&sin, len) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+
+ if ((filedes[0] = accept(sock, (SOCKADDR *)&sin, &len)) == INVALID_SOCKET) {
+ closesocket(filedes[1]);
+ filedes[1] = INVALID_SOCKET;
+ closesocket(sock);
+ return -1;
+ }
+
+ closesocket(sock);
+ return 0;
+}
+
+#endif /* _WIN32 */
diff --git a/protocols/Gadu-Gadu/src/libgadu/win32.h b/protocols/Gadu-Gadu/src/libgadu/win32.h
new file mode 100644
index 0000000000..d432a359ac
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/libgadu/win32.h
@@ -0,0 +1,75 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2010 Bartosz Białek
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+/* Windows wrappers for missing POSIX functions */
+
+#ifndef __GG_WIN32_H
+#define __GG_WIN32_H
+
+#include <winsock2.h>
+#include <io.h>
+
+/* Some Visual C++ overrides having no problems with MinGW */
+#ifdef _MSC_VER
+#define S_IWUSR 0x0080
+/* Make sure we included errno before that */
+#include <errno.h>
+#endif
+
+#ifdef EINPROGRESS
+# undef EINPROGRESS
+#endif
+#ifdef ENOTCONN
+# undef ENOTCONN
+#endif
+#ifdef EINTR
+# undef EINTR
+#endif
+#ifdef ECONNRESET
+# undef ECONNRESET
+#endif
+#ifdef ETIMEDOUT
+# undef ETIMEDOUT
+#endif
+
+#define EINPROGRESS WSAEINPROGRESS
+#define ENOTCONN WSAENOTCONN
+#define EINTR WSAEINTR
+#define ECONNRESET WSAECONNRESET
+#define ETIMEDOUT WSAETIMEDOUT
+
+#define WNOHANG WHOHANG
+#define SHUT_RDWR 2
+
+/* Defined in gg.c custom error reporting function */
+#ifdef GG_CONFIG_MIRANDA
+char *ws_strerror(int code);
+#define strerror(x) ws_strerror(x)
+#endif
+
+#define fork() (-1)
+int sockpipe(SOCKET filedes[2]);
+#define pipe(filedes) sockpipe(filedes)
+#define wait(x) (-1)
+#define waitpid(x,y,z) (-1)
+#define ioctl(fd,request,val) ioctlsocket(fd,request,(unsigned long *)val)
+
+#endif /* __GG_WIN32_H */
diff --git a/protocols/Gadu-Gadu/src/links.cpp b/protocols/Gadu-Gadu/src/links.cpp
new file mode 100644
index 0000000000..53770f0bcd
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/links.cpp
@@ -0,0 +1,173 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include "m_assocmgr.h"
+
+//////////////////////////////////////////////////////////
+// File Association Manager support
+
+#define GGS_PARSELINK "%s/ParseLink"
+#define GGS_MENUCHOOSE "%s/MenuChoose"
+
+static HANDLE hInstanceMenu;
+static HANDLE hServiceMenuChoose;
+static HANDLE hServiceParseLink;
+
+static INT_PTR gg_menuchoose(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam)
+ *(void**)lParam = (void*)wParam;
+ return 0;
+}
+
+static INT_PTR gg_parselink(WPARAM wParam, LPARAM lParam)
+{
+ char *arg = (char*)lParam;
+ list_t l = g_Instances;
+ GGPROTO *gg = NULL;
+ uin_t uin;
+ CLISTMENUITEM mi = {0};
+ int items = 0;
+
+ if (list_count(l) == 0)
+ return 0;
+
+ if (arg == NULL)
+ return 1;
+
+ arg = strchr(arg, ':');
+
+ if (arg == NULL)
+ return 1;
+
+ for (++arg; *arg == '/'; ++arg);
+ uin = atoi(arg);
+
+ if (!uin)
+ return 1;
+
+ for (mi.cbSize = sizeof(mi); l; l = l->next)
+ {
+ GGPROTO *gginst = (GGPROTO*)l->data;
+
+ mi.flags = CMIM_FLAGS;
+ if (gginst->m_iStatus > ID_STATUS_OFFLINE)
+ {
+ ++items;
+ gg = (GGPROTO*)l->data;
+ mi.flags |= CMIM_ICON;
+ mi.hIcon = LoadSkinnedProtoIcon(gg->m_szModuleName, gg->m_iStatus);
+ }
+ else
+ {
+ mi.flags |= CMIF_HIDDEN;
+ mi.hIcon = NULL;
+ }
+
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)gginst->hInstanceMenuItem, (LPARAM)&mi);
+ if (mi.hIcon)
+ CallService(MS_SKIN2_RELEASEICON, (WPARAM)mi.hIcon, 0);
+ }
+
+ if (items > 1)
+ {
+ ListParam param = {0};
+ HMENU hMenu = CreatePopupMenu();
+ POINT pt;
+ int cmd = 0;
+
+ param.MenuObjectHandle = hInstanceMenu;
+ CallService(MO_BUILDMENU, (WPARAM)hMenu, (LPARAM)&param);
+
+ GetCursorPos(&pt);
+ cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, pcli->hwndContactList, NULL);
+ DestroyMenu(hMenu);
+
+ if (cmd)
+ CallService(MO_PROCESSCOMMANDBYMENUIDENT, cmd, (LPARAM)&gg);
+ }
+
+ if (gg == NULL)
+ return 0;
+
+ if (ServiceExists(MS_MSG_SENDMESSAGE))
+ {
+ HANDLE hContact = gg->getcontact(uin, 1, 0, NULL);
+ if (hContact != NULL)
+ CallService(MS_MSG_SENDMESSAGE, (WPARAM)hContact, 0);
+ }
+
+ return 0;
+}
+
+void gg_links_instancemenu_init()
+{
+ char service[MAXMODULELABELLENGTH];
+ TMenuParam mnu = {0};
+ TMO_MenuItem tmi = {0};
+
+ mir_snprintf(service, sizeof(service), GGS_MENUCHOOSE, GGDEF_PROTO);
+ hServiceMenuChoose = CreateServiceFunction(service, gg_menuchoose);
+ mnu.cbSize = sizeof(mnu);
+ mnu.name = "GGAccountChooser";
+ mnu.ExecService = service;
+ hInstanceMenu = (HANDLE)CallService(MO_CREATENEWMENUOBJECT, 0, (LPARAM)&mnu);
+
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = CMIF_ICONFROMICOLIB;
+ tmi.pszName = "Cancel";
+ tmi.position = 9999999;
+ tmi.hIcolibItem = LoadSkinnedIconHandle(SKINICON_OTHER_DELETE);
+ CallService(MO_ADDNEWMENUITEM, (WPARAM)hInstanceMenu, (LPARAM)&tmi);
+}
+
+void gg_links_init()
+{
+ if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE))
+ {
+ char service[MAXMODULELABELLENGTH];
+
+ mir_snprintf(service, sizeof(service), GGS_PARSELINK, GGDEF_PROTO);
+ hServiceParseLink = CreateServiceFunction(service, gg_parselink);
+ AssocMgr_AddNewUrlType("gg:", Translate("Gadu-Gadu Link Protocol"), hInstance, IDI_GG, service, 0);
+ }
+}
+
+void gg_links_destroy()
+{
+ DestroyServiceFunction(hServiceMenuChoose);
+ if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE))
+ DestroyServiceFunction(hServiceParseLink);
+}
+
+void GGPROTO::links_instance_init()
+{
+ if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE))
+ {
+ TMO_MenuItem tmi = {0};
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = CMIF_TCHAR;
+ tmi.ownerdata = this;
+ tmi.position = list_count(g_Instances);
+ tmi.ptszName = m_tszUserName;
+ hInstanceMenuItem = (HANDLE)CallService(MO_ADDNEWMENUITEM, (WPARAM)hInstanceMenu, (LPARAM)&tmi);
+ }
+}
diff --git a/protocols/Gadu-Gadu/src/oauth.cpp b/protocols/Gadu-Gadu/src/oauth.cpp
new file mode 100644
index 0000000000..43983edb6d
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/oauth.cpp
@@ -0,0 +1,584 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2010 Bartosz Białek
+//
+// 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 "gg.h"
+#include <io.h>
+#include <fcntl.h>
+#include "protocol.h"
+
+//////////////////////////////////////////////////////////
+// OAuth 1.0 implementation
+
+// Service Provider must accept the HTTP Authorization header
+
+// RSA-SHA1 signature method (see RFC 3447 section 8.2
+// and RSASSA-PKCS1-v1_5 algorithm) is unimplemented
+
+typedef struct
+{
+ char *name;
+ char *value;
+} OAUTHPARAMETER;
+
+typedef enum
+{
+ HMACSHA1,
+ RSASHA1,
+ PLAINTEXT
+} OAUTHSIGNMETHOD;
+
+static int paramsortFunc(const OAUTHPARAMETER *p1, const OAUTHPARAMETER *p2)
+{
+ int res = strcmp(p1->name, p2->name);
+ return res != 0 ? res : strcmp(p1->value, p2->value);
+}
+
+// HMAC-SHA1 (see RFC 2104 for details)
+void hmacsha1_hash(mir_sha1_byte_t *text, int text_len, mir_sha1_byte_t *key, int key_len,
+ mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE])
+{
+ mir_sha1_ctx context;
+ mir_sha1_byte_t k_ipad[64];
+ mir_sha1_byte_t k_opad[64];
+ int i;
+
+ if (key_len > 64) {
+ mir_sha1_ctx tctx;
+ mir_sha1_byte_t tk[MIR_SHA1_HASH_SIZE];
+
+ mir_sha1_init(&tctx);
+ mir_sha1_append(&tctx, key, key_len);
+ mir_sha1_finish(&tctx, tk);
+
+ key = tk;
+ key_len = MIR_SHA1_HASH_SIZE;
+ }
+
+ memset(k_ipad, 0x36, 64);
+ memset(k_opad, 0x5c, 64);
+
+ for (i = 0; i < key_len; i++) {
+ k_ipad[i] ^= key[i];
+ k_opad[i] ^= key[i];
+ }
+
+ mir_sha1_init(&context);
+ mir_sha1_append(&context, k_ipad, 64);
+ mir_sha1_append(&context, text, text_len);
+ mir_sha1_finish(&context, digest);
+
+ mir_sha1_init(&context);
+ mir_sha1_append(&context, k_opad, 64);
+ mir_sha1_append(&context, digest, MIR_SHA1_HASH_SIZE);
+ mir_sha1_finish(&context, digest);
+}
+
+// see RFC 3986 for details
+#define isunreserved(c) ( isalnum((unsigned char)c) || c == '-' || c == '.' || c == '_' || c == '~')
+char *oauth_uri_escape(const char *str)
+{
+ char *res;
+ int size, ix = 0;
+
+ if (str == NULL) return mir_strdup("");
+
+ size = (int)strlen(str) + 1;
+ res = (char *)mir_alloc(size);
+
+ while (*str) {
+ if (!isunreserved(*str)) {
+ size += 2;
+ res = (char *)mir_realloc(res, size);
+ mir_snprintf(&res[ix], 4, "%%%X%X", (*str >> 4) & 15, *str & 15);
+ ix += 3;
+ }
+ else
+ res[ix++] = *str;
+ str++;
+ }
+ res[ix] = 0;
+
+ return res;
+}
+
+// generates Signature Base String
+
+char *oauth_generate_signature(LIST<OAUTHPARAMETER> &params, const char *httpmethod, const char *url)
+{
+ char *res, *urlenc, *urlnorm;
+ OAUTHPARAMETER *p;
+ int i, ix = 0, size;
+
+ if (httpmethod == NULL || url == NULL || !params.getCount()) return mir_strdup("");
+
+ urlnorm = (char *)mir_alloc(strlen(url) + 1);
+ while (*url) {
+ if (*url == '?' || *url == '#') break; // see RFC 3986 section 3
+ urlnorm[ix++] = tolower(*url);
+ url++;
+ }
+ urlnorm[ix] = 0;
+ if ((res = strstr(urlnorm, ":80")) != NULL)
+ memmove(res, res + 3, strlen(res) - 2);
+ else if ((res = strstr(urlnorm, ":443")) != NULL)
+ memmove(res, res + 4, strlen(res) - 3);
+
+ urlenc = oauth_uri_escape(urlnorm);
+ mir_free(urlnorm);
+ size = (int)strlen(httpmethod) + (int)strlen(urlenc) + 1 + 2;
+
+ for (i = 0; i < params.getCount(); i++) {
+ p = params[i];
+ if (!strcmp(p->name, "oauth_signature")) continue;
+ if (i > 0) size += 3;
+ size += (int)strlen(p->name) + (int)strlen(p->value) + 3;
+ }
+
+ res = (char *)mir_alloc(size);
+ strcpy(res, httpmethod);
+ strcat(res, "&");
+ strcat(res, urlenc);
+ mir_free(urlenc);
+ strcat(res, "&");
+
+ for (i = 0; i < params.getCount(); i++) {
+ p = params[i];
+ if (!strcmp(p->name, "oauth_signature")) continue;
+ if (i > 0) strcat(res, "%26");
+ strcat(res, p->name);
+ strcat(res, "%3D");
+ strcat(res, p->value);
+ }
+
+ return res;
+}
+
+char *oauth_getparam(LIST<OAUTHPARAMETER> &params, const char *name)
+{
+ OAUTHPARAMETER *p;
+ int i;
+
+ if (name == NULL) return NULL;
+
+ for (i = 0; i < params.getCount(); i++) {
+ p = params[i];
+ if (!strcmp(p->name, name))
+ return p->value;
+ }
+
+ return NULL;
+}
+
+void oauth_setparam(LIST<OAUTHPARAMETER> &params, const char *name, const char *value)
+{
+ OAUTHPARAMETER *p;
+ int i;
+
+ if (name == NULL) return;
+
+ for (i = 0; i < params.getCount(); i++) {
+ p = params[i];
+ if (!strcmp(p->name, name)) {
+ mir_free(p->value);
+ p->value = oauth_uri_escape(value);
+ return;
+ }
+ }
+
+ p = (OAUTHPARAMETER*)mir_alloc(sizeof(OAUTHPARAMETER));
+ p->name = oauth_uri_escape(name);
+ p->value = oauth_uri_escape(value);
+ params.insert(p);
+}
+
+void oauth_freeparams(LIST<OAUTHPARAMETER> &params)
+{
+ OAUTHPARAMETER *p;
+ int i;
+
+ for (i = 0; i < params.getCount(); i++) {
+ p = params[i];
+ mir_free(p->name);
+ mir_free(p->value);
+ }
+}
+
+int oauth_sign_request(LIST<OAUTHPARAMETER> &params, const char *httpmethod, const char *url,
+ const char *consumer_secret, const char *token_secret)
+{
+ char *sign = NULL, *signmethod;
+
+ if (!params.getCount()) return -1;
+
+ signmethod = oauth_getparam(params, "oauth_signature_method");
+ if (signmethod == NULL) return -1;
+
+ if (!strcmp(signmethod, "HMAC-SHA1")) {
+ char *text = oauth_generate_signature(params, httpmethod, url);
+ char *key;
+ char *csenc = oauth_uri_escape(consumer_secret);
+ char *tsenc = oauth_uri_escape(token_secret);
+ mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE];
+ NETLIBBASE64 nlb64 = {0};
+ int signlen;
+
+ key = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2);
+ strcpy(key, csenc);
+ strcat(key, "&");
+ strcat(key, tsenc);
+ mir_free(csenc);
+ mir_free(tsenc);
+
+ hmacsha1_hash((BYTE*)text, (int)strlen(text), (BYTE*)key, (int)strlen(key), digest);
+
+ signlen = Netlib_GetBase64EncodedBufferSize(MIR_SHA1_HASH_SIZE);
+ sign = (char *)mir_alloc(signlen);
+ nlb64.pszEncoded = sign;
+ nlb64.cchEncoded = signlen;
+ nlb64.pbDecoded = digest;
+ nlb64.cbDecoded = MIR_SHA1_HASH_SIZE;
+ CallService(MS_NETLIB_BASE64ENCODE, 0, (LPARAM)&nlb64);
+
+ mir_free(text);
+ mir_free(key);
+ }
+// else if (!strcmp(signmethod, "RSA-SHA1")) { // unimplemented
+// }
+ else { // PLAINTEXT
+ char *csenc = oauth_uri_escape(consumer_secret);
+ char *tsenc = oauth_uri_escape(token_secret);
+
+ sign = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2);
+ strcpy(sign, csenc);
+ strcat(sign, "&");
+ strcat(sign, tsenc);
+ mir_free(csenc);
+ mir_free(tsenc);
+ }
+
+ oauth_setparam(params, "oauth_signature", sign);
+ mir_free(sign);
+
+ return 0;
+}
+
+char *oauth_generate_nonce()
+{
+ mir_md5_byte_t digest[16];
+ char *result, *str, timestamp[22], randnum[16];
+ int i;
+
+ mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL));
+ CallService(MS_UTILS_GETRANDOM, (WPARAM)sizeof(randnum), (LPARAM)randnum);
+
+ str = (char *)mir_alloc(strlen(timestamp) + strlen(randnum) + 1);
+ strcpy(str, timestamp);
+ strcat(str, randnum);
+ mir_md5_hash((BYTE*)str, (int)strlen(str), digest);
+ mir_free(str);
+
+ result = (char *)mir_alloc(32 + 1);
+ for (i = 0; i < 16; i++)
+ sprintf(result + (i<<1), "%02x", digest[i]);
+
+ return result;
+}
+
+char *oauth_auth_header(const char *httpmethod, const char *url, OAUTHSIGNMETHOD signmethod,
+ const char *consumer_key, const char *consumer_secret,
+ const char *token, const char *token_secret)
+{
+ int i, size;
+ char *res, timestamp[22], *nonce;
+
+ if (httpmethod == NULL || url == NULL) return NULL;
+
+ LIST<OAUTHPARAMETER> oauth_parameters(1, paramsortFunc);
+ oauth_setparam(oauth_parameters, "oauth_consumer_key", consumer_key);
+ oauth_setparam(oauth_parameters, "oauth_version", "1.0");
+ switch (signmethod) {
+ case HMACSHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "HMAC-SHA1"); break;
+ case RSASHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "RSA-SHA1"); break;
+ default: oauth_setparam(oauth_parameters, "oauth_signature_method", "PLAINTEXT"); break;
+ };
+ mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL));
+ oauth_setparam(oauth_parameters, "oauth_timestamp", timestamp);
+ nonce = oauth_generate_nonce();
+ oauth_setparam(oauth_parameters, "oauth_nonce", nonce);
+ mir_free(nonce);
+ if (token != NULL && *token)
+ oauth_setparam(oauth_parameters, "oauth_token", token);
+
+ if (oauth_sign_request(oauth_parameters, httpmethod, url, consumer_secret, token_secret)) {
+ oauth_freeparams(oauth_parameters);
+ oauth_parameters.destroy();
+ return NULL;
+ }
+
+ size = 7;
+ for (i = 0; i < oauth_parameters.getCount(); i++) {
+ OAUTHPARAMETER *p = oauth_parameters[i];
+ if (i > 0) size++;
+ size += (int)strlen(p->name) + (int)strlen(p->value) + 3;
+ }
+
+ res = (char *)mir_alloc(size);
+ strcpy(res, "OAuth ");
+
+ for (i = 0; i < oauth_parameters.getCount(); i++) {
+ OAUTHPARAMETER *p = oauth_parameters[i];
+ if (i > 0) strcat(res, ",");
+ strcat(res, p->name);
+ strcat(res, "=\"");
+ strcat(res, p->value);
+ strcat(res, "\"");
+ }
+
+ oauth_freeparams(oauth_parameters);
+ oauth_parameters.destroy();
+ return res;
+}
+
+char* GGPROTO::oauth_header(const char *httpmethod, const char *url)
+{
+ char *res, uin[32], *password = NULL, *token = NULL, *token_secret = NULL;
+ DBVARIANT dbv;
+
+ UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin);
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal);
+ password = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) {
+ token = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal);
+ token_secret = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ res = oauth_auth_header(httpmethod, url, HMACSHA1, uin, password, token, token_secret);
+ mir_free(password);
+ mir_free(token);
+ mir_free(token_secret);
+
+ return res;
+}
+
+int GGPROTO::oauth_receivetoken()
+{
+ NETLIBHTTPHEADER httpHeaders[3];
+ NETLIBHTTPREQUEST req = {0};
+ NETLIBHTTPREQUEST *resp;
+ char szUrl[256], uin[32], *password = NULL, *str, *token = NULL, *token_secret = NULL;
+ DBVARIANT dbv;
+ int res = 0;
+ HANDLE nlc = NULL;
+
+ UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin);
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal);
+ password = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ // 1. Obtaining an Unauthorized Request Token
+ netlog("gg_oauth_receivetoken(): Obtaining an Unauthorized Request Token...");
+ strcpy(szUrl, "http://api.gadu-gadu.pl/request_token");
+ str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, NULL, NULL);
+
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_POST;
+ req.szUrl = szUrl;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT;
+ req.headersCount = 3;
+ req.headers = httpHeaders;
+ httpHeaders[0].szName = "User-Agent";
+ httpHeaders[0].szValue = GG8_VERSION;
+ httpHeaders[1].szName = "Authorization";
+ httpHeaders[1].szValue = str;
+ httpHeaders[2].szName = "Accept";
+ httpHeaders[2].szValue = "*/*";
+
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ nlc = resp->nlc;
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+ HXML hXml;
+ TCHAR *xmlAction;
+ TCHAR *tag;
+
+ xmlAction = mir_a2t(resp->pData);
+ tag = mir_a2t("result");
+ hXml = xi.parseString(xmlAction, 0, tag);
+ if (hXml != NULL) {
+ HXML node;
+
+ mir_free(tag); tag = mir_a2t("oauth_token");
+ node = xi.getChildByPath(hXml, tag, 0);
+ token = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ mir_free(tag); tag = mir_a2t("oauth_token_secret");
+ node = xi.getChildByPath(hXml, tag, 0);
+ token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ xi.destroyNode(hXml);
+ }
+ mir_free(tag);
+ mir_free(xmlAction);
+ }
+ else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request");
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_oauth_receivetoken(): No response from HTTP request");
+
+ // 2. Obtaining User Authorization
+ netlog("gg_oauth_receivetoken(): Obtaining User Authorization...");
+ mir_free(str);
+ str = oauth_uri_escape("http://www.mojageneracja.pl");
+
+ mir_snprintf(szUrl, 256, "callback_url=%s&request_token=%s&uin=%s&password=%s",
+ str, token, uin, password);
+ mir_free(str);
+ str = mir_strdup(szUrl);
+
+ ZeroMemory(&req, sizeof(req));
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_POST;
+ req.szUrl = szUrl;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11;
+ req.headersCount = 3;
+ req.headers = httpHeaders;
+ strcpy(szUrl, "https://login.gadu-gadu.pl/authorize");
+ httpHeaders[1].szName = "Content-Type";
+ httpHeaders[1].szValue = "application/x-www-form-urlencoded";
+ req.pData = str;
+ req.dataLength = (int)strlen(str);
+
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ else netlog("gg_oauth_receivetoken(): No response from HTTP request");
+
+ // 3. Obtaining an Access Token
+ netlog("gg_oauth_receivetoken(): Obtaining an Access Token...");
+ strcpy(szUrl, "http://api.gadu-gadu.pl/access_token");
+ mir_free(str);
+ str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, token, token_secret);
+ mir_free(token);
+ mir_free(token_secret);
+ token = NULL;
+ token_secret = NULL;
+
+ ZeroMemory(&req, sizeof(req));
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_POST;
+ req.szUrl = szUrl;
+ req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT;
+ req.nlc = nlc;
+ req.headersCount = 3;
+ req.headers = httpHeaders;
+ httpHeaders[1].szName = "Authorization";
+ httpHeaders[1].szValue = str;
+
+ resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req);
+ if (resp) {
+ if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
+ HXML hXml;
+ TCHAR *xmlAction;
+ TCHAR *tag;
+
+ xmlAction = mir_a2t(resp->pData);
+ tag = mir_a2t("result");
+ hXml = xi.parseString(xmlAction, 0, tag);
+ if (hXml != NULL) {
+ HXML node;
+
+ mir_free(tag); tag = mir_a2t("oauth_token");
+ node = xi.getChildByPath(hXml, tag, 0);
+ token = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ mir_free(tag); tag = mir_a2t("oauth_token_secret");
+ node = xi.getChildByPath(hXml, tag, 0);
+ token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ xi.destroyNode(hXml);
+ }
+ mir_free(tag);
+ mir_free(xmlAction);
+ }
+ else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request");
+ Netlib_CloseHandle(resp->nlc);
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
+ }
+ else netlog("gg_oauth_receivetoken(): No response from HTTP request");
+
+ mir_free(password);
+ mir_free(str);
+
+ if (token != NULL && token_secret != NULL) {
+ db_set_s(NULL, m_szModuleName, GG_KEY_TOKEN, token);
+ CallService(MS_DB_CRYPT_ENCODESTRING, (WPARAM)(int)strlen(token_secret) + 1, (LPARAM) token_secret);
+ db_set_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, token_secret);
+ netlog("gg_oauth_receivetoken(): Access Token obtained successfully.");
+ res = 1;
+ }
+ else {
+ db_unset(NULL, m_szModuleName, GG_KEY_TOKEN);
+ db_unset(NULL, m_szModuleName, GG_KEY_TOKENSECRET);
+ netlog("gg_oauth_receivetoken(): Failed to obtain Access Token.");
+ }
+ mir_free(token);
+ mir_free(token_secret);
+
+ return res;
+}
+
+int GGPROTO::oauth_checktoken(int force)
+{
+ if (!force) {
+ char *token = NULL, *token_secret = NULL;
+ DBVARIANT dbv;
+ int res = 1;
+
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) {
+ token = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) {
+ CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal);
+ token_secret = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ if (token == NULL || token_secret == NULL) {
+ res = oauth_receivetoken();
+ }
+
+ mir_free(token);
+ mir_free(token_secret);
+
+ return res;
+ }
+
+ return oauth_receivetoken();
+}
diff --git a/protocols/Gadu-Gadu/src/ownerinfo.cpp b/protocols/Gadu-Gadu/src/ownerinfo.cpp
new file mode 100644
index 0000000000..6c37cdb180
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/ownerinfo.cpp
@@ -0,0 +1,78 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+//////////////////////////////////////////////////////////
+// remind password
+
+typedef struct
+{
+ uin_t uin;
+ const char *email;
+} GG_REMIND_PASS;
+
+void __cdecl GGPROTO::remindpasswordthread(void *param)
+{
+ // Connection handle
+ struct gg_http *h;
+ GG_REMIND_PASS *rp = (GG_REMIND_PASS *)param;
+ GGTOKEN token;
+
+#ifdef DEBUGMODE
+ netlog("gg_remindpasswordthread(): Starting.");
+#endif
+ if (!rp || !rp->email || !rp->uin || !strlen(rp->email))
+ {
+ if (rp) free(rp);
+ return;
+ }
+
+ // Get token
+ if (!gettoken(&token)) return;
+
+ if (!(h = gg_remind_passwd3(rp->uin, rp->email, token.id, token.val, 0)))
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Password could not be reminded because of error:\n\t%s"), _tcserror(errno));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ netlog("gg_remindpasswordthread(): Password could not be reminded because of \"%s\".", strerror(errno));
+ }
+ else
+ {
+ gg_pubdir_free(h);
+ netlog("gg_remindpasswordthread(): Password remind successful.");
+ MessageBox(NULL, TranslateT("Password was sent to your e-mail."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+
+#ifdef DEBUGMODE
+ netlog("gg_remindpasswordthread(): End.");
+#endif
+ if (rp) free(rp);
+}
+
+void GGPROTO::remindpassword(uin_t uin, const char *email)
+{
+ GG_REMIND_PASS *rp = (GG_REMIND_PASS*)malloc(sizeof(GG_REMIND_PASS));
+
+ rp->uin = uin;
+ rp->email = email;
+ forkthread(&GGPROTO::remindpasswordthread, rp);
+}
diff --git a/protocols/Gadu-Gadu/src/popups.cpp b/protocols/Gadu-Gadu/src/popups.cpp
new file mode 100644
index 0000000000..69ac1c350d
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/popups.cpp
@@ -0,0 +1,174 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2011-2012 Bartosz Białek
+//
+// 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 "gg.h"
+
+struct PopupData
+{
+ unsigned flags;
+ TCHAR* title;
+ TCHAR* text;
+ GGPROTO* gg;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup plugin window proc
+
+LRESULT CALLBACK PopupWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_COMMAND:
+ {
+ PopupData* puData = (PopupData*)PUGetPluginData(hWnd);
+ if (puData != NULL)
+ {
+ if (puData->flags & GG_POPUP_MULTILOGON)
+ puData->gg->sessions_view(0, 0);
+ }
+ PUDeletePopUp(hWnd);
+ break;
+ }
+
+ case WM_CONTEXTMENU:
+ PUDeletePopUp(hWnd);
+ break;
+
+ case UM_FREEPLUGINDATA:
+ {
+ PopupData* puData = (PopupData*)PUGetPluginData(hWnd);
+ if (puData != NULL && puData != (PopupData*)CALLSERVICE_NOTFOUND)
+ {
+ mir_free(puData->title);
+ mir_free(puData->text);
+ mir_free(puData);
+ }
+ break;
+ }
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup plugin class registration
+
+void GGPROTO::initpopups()
+{
+ TCHAR szDescr[256];
+ char szName[256];
+
+ POPUPCLASS puc = {0};
+ puc.cbSize = sizeof(puc);
+ puc.PluginWindowProc = PopupWindowProc;
+ puc.flags = PCF_TCHAR;
+
+ puc.ptszDescription = szDescr;
+ puc.pszName = szName;
+ puc.colorBack = RGB(173, 206, 247);
+ puc.colorText = GetSysColor(COLOR_WINDOWTEXT);
+ puc.hIcon = CopyIcon(LoadIconEx("main", FALSE));
+ ReleaseIconEx("main", FALSE);
+ puc.iSeconds = 4;
+ mir_sntprintf(szDescr, SIZEOF(szDescr), _T("%s/%s"), m_tszUserName, TranslateT("Notify"));
+ mir_snprintf(szName, SIZEOF(szName), "%s_%s", m_szModuleName, "Notify");
+ CallService(MS_POPUP_REGISTERCLASS, 0, (WPARAM)&puc);
+
+ puc.ptszDescription = szDescr;
+ puc.pszName = szName;
+ puc.colorBack = RGB(191, 0, 0); // Red
+ puc.colorText = RGB(255, 245, 225); // Yellow
+ puc.iSeconds = 60;
+ puc.hIcon = (HICON)LoadImage(NULL, IDI_WARNING, IMAGE_ICON, 0, 0, LR_SHARED);
+ mir_sntprintf(szDescr, SIZEOF(szDescr), _T("%s/%s"), m_tszUserName, TranslateT("Error"));
+ mir_snprintf(szName, SIZEOF(szName), "%s_%s", m_szModuleName, "Error");
+ CallService(MS_POPUP_REGISTERCLASS, 0, (WPARAM)&puc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Show popup - popup plugin support
+
+void CALLBACK sttMainThreadCallback(PVOID dwParam)
+{
+ PopupData* puData = (PopupData*)dwParam;
+ GGPROTO* gg = puData->gg;
+
+ if (ServiceExists(MS_POPUP_ADDPOPUPCLASS))
+ {
+ char szName[256];
+ POPUPDATACLASS ppd = {sizeof(ppd)};
+ ppd.ptszTitle = puData->title;
+ ppd.ptszText = puData->text;
+ ppd.PluginData = puData;
+ ppd.pszClassName = szName;
+
+ if (puData->flags & GG_POPUP_ERROR || puData->flags & GG_POPUP_WARNING)
+ mir_snprintf(szName, SIZEOF(szName), "%s_%s", gg->m_szModuleName, "Error");
+ else
+ mir_snprintf(szName, SIZEOF(szName), "%s_%s", gg->m_szModuleName, "Notify");
+
+ CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&ppd);
+ }
+ else
+ {
+ if (puData->flags & GG_POPUP_ALLOW_MSGBOX)
+ {
+ BOOL bShow = TRUE;
+
+ if (puData->flags & GG_POPUP_ONCE)
+ {
+ HWND hWnd = FindWindow(NULL, gg->m_tszUserName);
+ while (hWnd != NULL)
+ {
+ if (FindWindowEx(hWnd, NULL, NULL, puData->text) != NULL)
+ {
+ bShow = FALSE;
+ break;
+ }
+ hWnd = FindWindowEx(NULL, hWnd, NULL, gg->m_tszUserName);
+ }
+ }
+
+ if (bShow)
+ {
+ UINT uIcon = puData->flags & GG_POPUP_ERROR ? MB_ICONERROR : puData->flags & GG_POPUP_WARNING ? MB_ICONEXCLAMATION : MB_ICONINFORMATION;
+ MessageBox(NULL, puData->text, gg->m_tszUserName, MB_OK | uIcon);
+ }
+ }
+ mir_free(puData->title);
+ mir_free(puData->text);
+ mir_free(puData);
+ }
+}
+
+void GGPROTO::showpopup(const TCHAR* nickname, const TCHAR* msg, int flags)
+{
+ PopupData* puData;
+
+ if (Miranda_Terminated()) return;
+
+ puData = (PopupData*)mir_alloc(sizeof(PopupData));
+ puData->flags = flags;
+ puData->title = mir_tstrdup(nickname);
+ puData->text = mir_tstrdup(msg);
+ puData->gg = this;
+
+ CallFunctionAsync(sttMainThreadCallback, puData);
+}
diff --git a/protocols/Gadu-Gadu/src/resource.h b/protocols/Gadu-Gadu/src/resource.h
new file mode 100644
index 0000000000..86ec31eadb
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/resource.h
@@ -0,0 +1,147 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef IDC_STATIC
+#define IDC_STATIC -1
+#endif
+#ifndef IDOK
+#define IDOK 1
+#endif
+#ifndef IDCANCEL
+#define IDCANCEL 2
+#endif
+
+#define IDI_GG 251
+#define IDI_IMPORT_TEXT 252
+#define IDI_IMPORT_SERVER 253
+#define IDI_EXPORT_TEXT 254
+#define IDI_EXPORT_SERVER 255
+#define IDI_REMOVE_SERVER 256
+#define IDI_SETTINGS 257
+#define IDI_LIST 258
+#define IDI_NEXT 259
+#define IDI_PREV 260
+#define IDI_SCALE 261
+#define IDI_IMAGE 262
+#define IDI_DELETE 263
+#define IDI_SAVE 264
+#define IDI_CONFERENCE 265
+#define IDI_CLEAR_CONFERENCE 266
+#define IDI_SESSIONS 267
+#define IDI_BLOCK 268
+
+#define IDD_INFO_GG 301
+#define IDD_CHPASS 302
+#define IDD_CHINFO_GG 303
+#define IDD_GGADVANCEDSEARCH 304
+#define IDD_CREATEACCOUNT 305
+#define IDD_REMOVEACCOUNT 306
+#define IDD_CHEMAIL 307
+#define IDD_OPT_GG_ADVANCED 308
+#define IDD_TOKEN 309
+#define IDD_CONFERENCE 310
+#define IDD_OPT_GG_GENERAL 311
+#define IDD_OPT_GG_CONFERENCE 312
+#define IDD_IMAGE_RECV 313
+#define IDD_IMAGE_SEND 314
+#define IDD_ACCMGRUI 315
+#define IDD_SESSIONS 316
+
+#define IDC_UIN 401
+#define IDC_PASSWORD 402
+#define IDC_LOSTPASS 403
+#define IDC_FRIENDSONLY 404
+#define IDC_SHOWINVISIBLE 405
+#define IDC_KEEPALIVE 406
+#define IDC_SAFESTATUS 407
+#define IDC_MANUALHOST 408
+#define IDC_HOST 409
+#define IDC_PORT 410
+#define IDC_RELOADREQD 411
+#define IDC_IP 412
+#define IDC_REALIP 413
+#define IDC_FIRSTNAME 420
+#define IDC_LASTNAME 421
+#define IDC_NICKNAME 422
+#define IDC_GENDER 423
+#define IDC_BIRTHYEAR 424
+#define IDC_CITY 425
+#define IDC_FAMILYNAME 426
+#define IDC_CITYORIGIN 427
+#define IDC_STATUSDESCR 428
+#define IDC_EMAIL 429
+#define IDC_CPASSWORD 430
+#define IDC_SHOWCERRORS 431
+#define IDC_ARECONNECT 432
+#define IDC_LEAVESTATUSMSG 433
+#define IDC_LEAVESTATUS 434
+#define IDC_AGEFROM 435
+#define IDC_AGETO 436
+#define IDC_ONLYCONNECTED 437
+#define IDC_WHITERECT 438
+#define IDC_CREATEACCOUNT 439
+#define IDC_REMOVEACCOUNT 440
+#define IDC_CONFIRM 441
+#define IDC_SAVE 442
+#define IDC_CHPASS 443
+#define IDC_CHEMAIL 444
+#define IDC_DIRECTCONNS 445
+#define IDC_DIRECTPORT 446
+#define IDC_FORWARDHOST 447
+#define IDC_FORWARDPORT 448
+#define IDC_FORWARDING 449
+#define IDC_MSGACK 450
+#define IDC_SSLCONN 451
+#define IDC_VERSION 452
+#define IDC_TOKEN 453
+#define IDC_IGNORECONF 454
+#define IDC_SHOWLINKS 455
+#define IDC_IMGRECEIVE 456
+#define IDC_IMGMETHOD 457
+
+#define IDC_IMAGECLOSE 458
+#define IDC_TABCONTROL 459
+#define IDC_IMAGE_SEND 460
+#define IDC_IMAGE_SAVE 461
+
+#define IDC_GC_POLICY_TOTAL 462
+#define IDC_GC_POLICY_UNKNOWN 463
+#define IDC_GC_POLICY_DEFAULT 464
+#define IDC_GC_COUNT_TOTAL 465
+#define IDC_GC_COUNT_UNKNOWN 466
+
+#define IDC_OPTIONSTAB 467
+#define IDC_ENABLEAVATARS 468
+
+#define IDC_HEADERBAR 1001
+#define IDC_SESSIONS 1002
+#define IDC_SIGNOUTALL 1003
+
+#define IDC_IMG_DELETE 1010
+#define IDC_IMG_SEND 1011
+#define IDC_IMG_PREV 1012
+#define IDC_IMG_NEXT 1013
+#define IDC_IMG_SCALE 1014
+#define IDC_IMG_SAVE 1015
+#define IDC_IMG_CANCEL 1016
+#define IDC_IMG_IMAGE 1017
+#define IDC_IMG_NAME 1018
+
+#define IDC_CLIST 1200
diff --git a/protocols/Gadu-Gadu/src/services.cpp b/protocols/Gadu-Gadu/src/services.cpp
new file mode 100644
index 0000000000..a172d10609
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/services.cpp
@@ -0,0 +1,330 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+#include <io.h>
+
+//////////////////////////////////////////////////////////
+// Status mode -> DB
+char *gg_status2db(int status, const char *suffix)
+{
+ char *prefix;
+ static char str[64];
+
+ switch(status) {
+ case ID_STATUS_AWAY: prefix = "Away"; break;
+ case ID_STATUS_NA: prefix = "Na"; break;
+ case ID_STATUS_DND: prefix = "Dnd"; break;
+ case ID_STATUS_OCCUPIED: prefix = "Occupied"; break;
+ case ID_STATUS_FREECHAT: prefix = "FreeChat"; break;
+ case ID_STATUS_ONLINE: prefix = "On"; break;
+ case ID_STATUS_OFFLINE: prefix = "Off"; break;
+ case ID_STATUS_INVISIBLE: prefix = "Inv"; break;
+ case ID_STATUS_ONTHEPHONE: prefix = "Otp"; break;
+ case ID_STATUS_OUTTOLUNCH: prefix = "Otl"; break;
+ default: return NULL;
+ }
+ strncpy(str, prefix, sizeof(str));
+ strncat(str, suffix, sizeof(str) - strlen(str));
+ return str;
+}
+
+//////////////////////////////////////////////////////////
+// gets protocol status
+
+char* GGPROTO::getstatusmsg(int status)
+{
+ switch(status) {
+ case ID_STATUS_ONLINE:
+ return modemsg.online;
+ break;
+ case ID_STATUS_DND:
+ return modemsg.dnd;
+ break;
+ case ID_STATUS_FREECHAT:
+ return modemsg.freechat;
+ break;
+ case ID_STATUS_INVISIBLE:
+ return modemsg.invisible;
+ break;
+ case ID_STATUS_AWAY:
+ default:
+ return modemsg.away;
+ }
+}
+
+//////////////////////////////////////////////////////////
+// sets specified protocol status
+
+int GGPROTO::refreshstatus(int status)
+{
+ if (status == ID_STATUS_OFFLINE)
+ {
+ disconnect();
+ return TRUE;
+ }
+
+ if (!isonline())
+ {
+ DWORD exitCode = 0;
+ GetExitCodeThread(pth_sess.hThread, &exitCode);
+ if (exitCode == STILL_ACTIVE)
+ return TRUE;
+#ifdef DEBUGMODE
+ netlog("gg_refreshstatus(): Going to connect...");
+#endif
+ threadwait(&pth_sess);
+ pth_sess.hThread = forkthreadex(&GGPROTO::mainthread, NULL, &pth_sess.dwThreadId);
+ }
+ else
+ {
+ char *szMsg = NULL;
+ // Select proper msg
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(getstatusmsg(status));
+ LeaveCriticalSection(&modemsg_mutex);
+ if (szMsg)
+ {
+ netlog("gg_refreshstatus(): Setting status and away message.");
+ EnterCriticalSection(&sess_mutex);
+ gg_change_status_descr(sess, status_m2gg(status, szMsg != NULL), szMsg);
+ LeaveCriticalSection(&sess_mutex);
+ }
+ else
+ {
+ netlog("gg_refreshstatus(): Setting just status.");
+ EnterCriticalSection(&sess_mutex);
+ gg_change_status(sess, status_m2gg(status, 0));
+ LeaveCriticalSection(&sess_mutex);
+ }
+ // Change status of the contact with our own UIN (if got yourself added to the contact list)
+ changecontactstatus( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), status_m2gg(status, szMsg != NULL), szMsg, 0, 0, 0, 0);
+ broadcastnewstatus(status);
+ mir_free(szMsg);
+ }
+
+ return TRUE;
+}
+
+//////////////////////////////////////////////////////////
+// normalize gg status
+
+int gg_normalizestatus(int status)
+{
+ switch(status) {
+ case ID_STATUS_ONLINE: return ID_STATUS_ONLINE;
+ case ID_STATUS_DND: return ID_STATUS_DND;
+ case ID_STATUS_FREECHAT: return ID_STATUS_FREECHAT;
+ case ID_STATUS_OFFLINE: return ID_STATUS_OFFLINE;
+ case ID_STATUS_INVISIBLE: return ID_STATUS_INVISIBLE;
+ }
+ return ID_STATUS_AWAY;
+}
+
+//////////////////////////////////////////////////////////
+// gets avatar capabilities
+
+INT_PTR GGPROTO::getavatarcaps(WPARAM wParam, LPARAM lParam)
+{
+ switch (wParam) {
+ case AF_MAXSIZE:
+ ((POINT *)lParam)->x = ((POINT *)lParam)->y = 200;
+ return 0;
+ case AF_FORMATSUPPORTED:
+ return (lParam == PA_FORMAT_JPEG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_PNG);
+ case AF_ENABLED:
+ return db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS);
+ case AF_DONTNEEDDELAYS:
+ return 1;
+ case AF_MAXFILESIZE:
+ return 307200;
+ case AF_FETCHALWAYS:
+ return 1;
+ }
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// gets avatar information
+
+INT_PTR GGPROTO::getavatarinfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATIONT *pai = (PROTO_AVATAR_INFORMATIONT *)lParam;
+ char *AvatarHash = NULL, *AvatarSavedHash = NULL;
+ char *AvatarURL = NULL;
+ INT_PTR result = GAIR_NOAVATAR;
+ DBVARIANT dbv;
+ uin_t uin = (uin_t)db_get_dw(pai->hContact, m_szModuleName, GG_KEY_UIN, 0);
+
+ netlog("gg_getavatarinfo(): Requesting avatar information for %d.", uin);
+
+ pai->filename[0] = 0;
+ pai->format = PA_FORMAT_UNKNOWN;
+
+ if (!uin || !db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS))
+ return GAIR_NOAVATAR;
+
+ if (!db_get_b(pai->hContact, m_szModuleName, GG_KEY_AVATARREQUESTED, GG_KEYDEF_AVATARREQUESTED)) {
+ requestAvatar(pai->hContact, 1);
+ return (wParam & GAIF_FORCE) != 0 ? GAIR_WAITFOR : GAIR_NOAVATAR;
+ }
+ db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARREQUESTED);
+
+ pai->format = db_get_b(pai->hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE);
+
+ if (!db_get_s(pai->hContact, m_szModuleName, GG_KEY_AVATARURL, &dbv, DBVT_ASCIIZ)) {
+ AvatarURL = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ if (AvatarURL != NULL && strlen(AvatarURL) > 0) {
+ char *AvatarName = strrchr(AvatarURL, '/');
+ AvatarName++;
+ AvatarHash = gg_avatarhash(AvatarName);
+ }
+
+ if (!db_get_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, &dbv, DBVT_ASCIIZ)) {
+ AvatarSavedHash = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ if (AvatarHash != NULL && AvatarSavedHash != NULL) {
+ getAvatarFilename(pai->hContact, pai->filename, SIZEOF(pai->filename));
+ if (!strcmp(AvatarHash, AvatarSavedHash) && !_taccess(pai->filename, 0)) {
+ result = GAIR_SUCCESS;
+ }
+ else if ((wParam & GAIF_FORCE) != 0) {
+ netlog("gg_getavatarinfo(): Contact %d changed avatar.", uin);
+ _tremove(pai->filename);
+ db_set_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, AvatarHash);
+ getAvatar(pai->hContact, AvatarURL);
+ result = GAIR_WAITFOR;
+ }
+ }
+ else if ((wParam & GAIF_FORCE) != 0) {
+ if (AvatarHash == NULL && AvatarSavedHash != NULL) {
+ netlog("gg_getavatarinfo(): Contact %d deleted avatar.", uin);
+ getAvatarFilename(pai->hContact, pai->filename, sizeof(pai->filename));
+ _tremove(pai->filename);
+ db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH);
+ db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARURL);
+ db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARTYPE);
+ }
+ else if (AvatarHash != NULL && AvatarSavedHash == NULL) {
+ netlog("gg_getavatarinfo(): Contact %d set avatar.", uin);
+ db_set_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, AvatarHash);
+ getAvatar(pai->hContact, AvatarURL);
+ result = GAIR_WAITFOR;
+ }
+ }
+
+ mir_free(AvatarHash);
+ mir_free(AvatarSavedHash);
+ mir_free(AvatarURL);
+
+ return result;
+}
+
+//////////////////////////////////////////////////////////
+// gets avatar
+
+INT_PTR GGPROTO::getmyavatar(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR *szFilename = (TCHAR*)wParam;
+ int len = (int)lParam;
+
+ netlog("gg_getmyavatar(): Requesting user avatar.");
+
+ if (szFilename == NULL || len <= 0)
+ return -1;
+
+ if (!db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS))
+ return -2;
+
+ getAvatarFilename(NULL, szFilename, len);
+ return _taccess(szFilename, 0);
+}
+
+//////////////////////////////////////////////////////////
+// sets avatar
+
+INT_PTR GGPROTO::setmyavatar(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR *szFilename = (TCHAR*)lParam;
+
+ if (!db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS))
+ return -2;
+
+ if (szFilename == NULL) {
+ MessageBox(NULL,
+ TranslateT("To remove your Gadu-Gadu avatar, you must use the MojaGeneracja.pl website."),
+ m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ return -1;
+ }
+
+ TCHAR szMyFilename[MAX_PATH];
+ getAvatarFilename(NULL, szMyFilename, SIZEOF(szMyFilename));
+ if ( _tcscmp(szFilename, szMyFilename) && !CopyFile(szFilename, szMyFilename, FALSE)) {
+ netlog("gg_setmyavatar(): Failed to set user avatar. File %s could not be created/overwritten.", szMyFilename);
+ return -1;
+ }
+
+ setAvatar(szMyFilename);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////
+// gets protocol status message
+
+INT_PTR GGPROTO::getmyawaymsg(WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR res = 0;
+ char *szMsg;
+
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = getstatusmsg(wParam ? gg_normalizestatus(wParam) : m_iStatus);
+ if (isonline() && szMsg)
+ res = (lParam & SGMA_UNICODE) ? (INT_PTR)mir_a2u(szMsg) : (INT_PTR)mir_strdup(szMsg);
+ LeaveCriticalSection(&modemsg_mutex);
+ return res;
+}
+
+//////////////////////////////////////////////////////////
+// gets account manager GUI
+
+extern INT_PTR CALLBACK gg_acc_mgr_guidlgproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+INT_PTR GGPROTO::get_acc_mgr_gui(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR) CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_ACCMGRUI), (HWND)lParam, gg_acc_mgr_guidlgproc, (LPARAM)this);
+}
+
+//////////////////////////////////////////////////////////
+// leaves (terminates) conference
+
+INT_PTR GGPROTO::leavechat(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE)wParam;
+ if (hContact)
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
+
+ return 0;
+}
diff --git a/protocols/Gadu-Gadu/src/sessions.cpp b/protocols/Gadu-Gadu/src/sessions.cpp
new file mode 100644
index 0000000000..6f047cd783
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/sessions.cpp
@@ -0,0 +1,445 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2009-2012 Bartosz Białek
+//
+// 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 "gg.h"
+
+#define GGS_CONCUR_SESS "%s/ConcurSess"
+
+static void gg_clearsessionslist(HWND hwndDlg)
+{
+ HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS);
+ LV_COLUMN column = {0};
+ RECT rc;
+ int iWidth;
+
+ if (!hList) return;
+
+ ListView_DeleteAllItems(hList);
+ while (ListView_DeleteColumn(hList, 0));
+
+ column.mask = LVCF_TEXT;
+ column.cx = 500;
+ column.pszText = TranslateT("Client Name");
+ ListView_InsertColumn(hList, 1, &column);
+
+ column.pszText=TranslateT("IP Address");
+ ListView_InsertColumn(hList, 2, &column);
+
+ column.pszText = TranslateT("Login Time");
+ ListView_InsertColumn(hList, 3, &column);
+
+ column.pszText = TranslateT("Action");
+ ListView_InsertColumn(hList, 4, &column);
+
+ GetClientRect(hList, &rc);
+ iWidth = rc.right - rc.left;
+ ListView_SetColumnWidth(hList, 0, iWidth * 45 / 100);
+ ListView_SetColumnWidth(hList, 1, iWidth * 20 / 100);
+ ListView_SetColumnWidth(hList, 2, iWidth * 20 / 100);
+ ListView_SetColumnWidth(hList, 3, LVSCW_AUTOSIZE_USEHEADER);
+}
+
+static void ListView_SetItemTextA(HWND hwndLV, int i, int iSubItem, char* pszText)
+{
+ LV_ITEMA _ms_lvi;
+ _ms_lvi.iSubItem = iSubItem;
+ _ms_lvi.pszText = pszText;
+ SendMessageA(hwndLV, LVM_SETITEMTEXTA, i, (LPARAM)&_ms_lvi);
+}
+
+static int gg_insertlistitem(HWND hList, gg_multilogon_id_t* id, const char* clientName, const char* ip, const char* loginTime)
+{
+ LVITEM item = {0};
+ int index;
+
+ item.iItem = ListView_GetItemCount(hList);
+ item.mask = LVIF_PARAM;
+ item.lParam = (LPARAM)id;
+
+ index = ListView_InsertItem(hList, &item);
+ if (index < 0) return index;
+
+ ListView_SetItemTextA(hList, index, 0, (char*)clientName);
+ ListView_SetItemTextA(hList, index, 1, (char*)ip);
+ ListView_SetItemTextA(hList, index, 2, (char*)loginTime);
+ ListView_SetItemText(hList, index, 3, TranslateT("sign out"));
+
+ return index;
+}
+
+static void gg_listsessions(GGPROTO* gg, HWND hwndDlg)
+{
+ HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS);
+ list_t l;
+
+ if (!hList) return;
+
+ EnterCriticalSection(&gg->sessions_mutex);
+ for (l = gg->sessions; l; l = l->next)
+ {
+ struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data;
+ struct in_addr ia;
+ char* ip;
+ char loginTime[20];
+ ia.S_un.S_addr = sess->remote_addr;
+ ip = inet_ntoa(ia);
+ strftime(loginTime, sizeof(loginTime), "%d-%m-%Y %H:%M:%S", localtime(&sess->logon_time));
+ gg_insertlistitem(hList, &sess->id, sess->name, ip, loginTime);
+ }
+ LeaveCriticalSection(&gg->sessions_mutex);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SIGNOUTALL), ListView_GetItemCount(hList) > 0);
+}
+
+static int sttSessionsDlgResizer(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL* urc)
+{
+ switch (urc->wId)
+ {
+ case IDC_HEADERBAR:
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH;
+ case IDC_SESSIONS:
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH;
+ case IDC_SIGNOUTALL:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
+}
+
+static BOOL IsOverAction(HWND hwndDlg)
+{
+ HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS);
+ LVHITTESTINFO hti;
+ RECT rc;
+ HDC hdc;
+ TCHAR szText[256];
+ SIZE textSize;
+ int textPosX;
+
+ GetCursorPos(&hti.pt);
+ ScreenToClient(hList, &hti.pt);
+ GetClientRect(hList, &rc);
+ if (!PtInRect(&rc, hti.pt) || ListView_SubItemHitTest(hList, &hti) == -1
+ || hti.iSubItem != 3 || !(hti.flags & LVHT_ONITEMLABEL))
+ return FALSE;
+
+ ListView_GetSubItemRect(hList, hti.iItem, hti.iSubItem, LVIR_LABEL, &rc);
+ szText[0] = 0;
+ ListView_GetItemText(hList, hti.iItem, hti.iSubItem, szText, SIZEOF(szText));
+ hdc = GetDC(hList);
+ GetTextExtentPoint32(hdc, szText, lstrlen(szText), &textSize);
+ ReleaseDC(hList, hdc);
+ textPosX = rc.left + (((rc.right - rc.left) - textSize.cx) / 2);
+ return (hti.pt.x > textPosX && hti.pt.x < textPosX + textSize.cx);
+}
+
+static HCURSOR hHandCursor = NULL;
+#define WM_MULTILOGONINFO (WM_USER + 10)
+#define HM_PROTOACK (WM_USER + 11)
+
+static INT_PTR CALLBACK gg_sessions_viewdlg(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER);
+ switch (message) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ gg = (GGPROTO*)lParam;
+ gg->hwndSessionsDlg = hwndDlg;
+
+ SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam);
+ {
+ TCHAR oldTitle[256], newTitle[256];
+ HANDLE hProtoAckEvent;
+
+ GetDlgItemText(hwndDlg, IDC_HEADERBAR, oldTitle, SIZEOF(oldTitle));
+ mir_sntprintf(newTitle, SIZEOF(newTitle), oldTitle, gg->m_tszUserName);
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, newTitle);
+ WindowSetIcon(hwndDlg, "sessions");
+
+ if (hHandCursor == NULL)
+ hHandCursor = LoadCursor(NULL, IDC_HAND);
+ hProtoAckEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_PROTOACK);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hProtoAckEvent);
+
+ ListView_SetExtendedListViewStyle(GetDlgItem(hwndDlg, IDC_SESSIONS), LVS_EX_FULLROWSELECT);
+ SendMessage(hwndDlg, WM_MULTILOGONINFO, 0, 0);
+ return TRUE;
+ }
+
+ case HM_PROTOACK:
+ {
+ ACKDATA* ack = (ACKDATA*)lParam;
+ if (!strcmp(ack->szModule, gg->m_szModuleName) && !ack->hContact && ack->type == ACKTYPE_STATUS
+ && ack->result == ACKRESULT_SUCCESS && (ack->lParam == ID_STATUS_OFFLINE
+ || (ack->hProcess == (HANDLE)ID_STATUS_CONNECTING && ack->lParam != ID_STATUS_OFFLINE
+ && !ListView_GetItemCount(GetDlgItem(hwndDlg, IDC_SESSIONS)))))
+ {
+ gg_clearsessionslist(hwndDlg);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SIGNOUTALL), FALSE);
+ }
+ break;
+ }
+
+ case WM_MULTILOGONINFO:
+ gg_clearsessionslist(hwndDlg);
+ gg_listsessions(gg, hwndDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_SIGNOUTALL:
+ {
+ HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS);
+ LVITEM lvi = {0};
+ int iCount = ListView_GetItemCount(hList), i;
+ lvi.mask = LVIF_PARAM;
+ for (i = 0; i < iCount; i++)
+ {
+ lvi.iItem = i;
+ ListView_GetItem(hList, &lvi);
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_multilogon_disconnect(gg->sess, *((gg_multilogon_id_t*)lvi.lParam));
+ LeaveCriticalSection(&gg->sess_mutex);
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->idFrom == IDC_SESSIONS)
+ {
+ switch (((LPNMHDR)lParam)->code)
+ {
+ case NM_CUSTOMDRAW:
+ {
+ LPNMLVCUSTOMDRAW nm = (LPNMLVCUSTOMDRAW)lParam;
+ switch (nm->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (ListView_GetItemCount(nm->nmcd.hdr.hwndFrom) == 0)
+ {
+ const LPCTSTR szText = gg->isonline()
+ ? TranslateT("There are no active concurrent sessions for this account.")
+ : TranslateT("You have to be logged in to view concurrent sessions.");
+ RECT rc;
+ HWND hwndHeader = ListView_GetHeader(nm->nmcd.hdr.hwndFrom);
+ SIZE textSize;
+ int textPosX;
+ GetClientRect(nm->nmcd.hdr.hwndFrom, &rc);
+ if (hwndHeader != NULL)
+ {
+ RECT rcHeader;
+ GetClientRect(hwndHeader, &rcHeader);
+ rc.top += rcHeader.bottom;
+ }
+ GetTextExtentPoint32(nm->nmcd.hdc, szText, lstrlen(szText), &textSize);
+ textPosX = rc.left + (((rc.right - rc.left) - textSize.cx) / 2);
+ ExtTextOut(nm->nmcd.hdc, textPosX, rc.top + textSize.cy, ETO_OPAQUE, &rc, szText, lstrlen(szText), NULL);
+ }
+ // FALL THROUGH
+
+ case CDDS_ITEMPREPAINT:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT , CDRF_NOTIFYSUBITEMDRAW);
+ return TRUE;
+
+ case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
+ {
+ RECT rc;
+ ListView_GetSubItemRect(nm->nmcd.hdr.hwndFrom, nm->nmcd.dwItemSpec, nm->iSubItem, LVIR_LABEL, &rc);
+ if (nm->nmcd.hdr.idFrom == IDC_SESSIONS && nm->iSubItem == 3)
+ {
+ TCHAR szText[256];
+ szText[0] = 0;
+ ListView_GetItemText(nm->nmcd.hdr.hwndFrom, nm->nmcd.dwItemSpec, nm->iSubItem, szText, SIZEOF(szText));
+ FillRect(nm->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOW));
+ SetTextColor(nm->nmcd.hdc, RGB(0, 0, 255));
+ DrawText(nm->nmcd.hdc, szText, -1, &rc, DT_END_ELLIPSIS | DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_TOP);
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT);
+ return TRUE;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case NM_CLICK:
+ if (IsOverAction(hwndDlg))
+ {
+ LPNMITEMACTIVATE nm = (LPNMITEMACTIVATE)lParam;
+ LVITEM lvi = {0};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = nm->iItem;
+ ListView_GetItem(nm->hdr.hwndFrom, &lvi);
+ EnterCriticalSection(&gg->sess_mutex);
+ gg_multilogon_disconnect(gg->sess, *((gg_multilogon_id_t*)lvi.lParam));
+ LeaveCriticalSection(&gg->sess_mutex);
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ {
+ HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS);
+ POINT pt = {(short)LOWORD(lParam), (short)HIWORD(lParam)}, ptDlg = pt;
+ LVHITTESTINFO lvhti = {0};
+
+ ScreenToClient(hwndDlg, &ptDlg);
+ if (ChildWindowFromPoint(hwndDlg, ptDlg) == hList)
+ {
+ HMENU hMenu;
+ int iSelection;
+
+ lvhti.pt = pt;
+ ScreenToClient(hList, &lvhti.pt);
+ if (ListView_HitTest(hList, &lvhti) == -1) break;
+
+ hMenu = CreatePopupMenu();
+ AppendMenu(hMenu, MFT_STRING, 10001, TranslateT("Copy Text"));
+ AppendMenu(hMenu, MFT_STRING, 10002, TranslateT("Whois"));
+ iSelection = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL);
+ switch (iSelection) {
+ case 10001:
+ {
+ TCHAR szText[512], szClientName[256], szIP[64], szLoginTime[64];
+ HGLOBAL hData;
+ if (!OpenClipboard(hwndDlg))
+ break;
+
+ EmptyClipboard();
+ szClientName[0] = szIP[0] = szLoginTime[0] = 0;
+ ListView_GetItemText(hList, lvhti.iItem, 0, szClientName, SIZEOF(szClientName));
+ ListView_GetItemText(hList, lvhti.iItem, 1, szIP, SIZEOF(szIP));
+ ListView_GetItemText(hList, lvhti.iItem, 2, szLoginTime, SIZEOF(szLoginTime));
+ mir_sntprintf(szText, SIZEOF(szText), _T("%s\t%s\t%s"), szClientName, szIP, szLoginTime);
+ if ((hData = GlobalAlloc(GMEM_MOVEABLE, lstrlen(szText) + 1)) != NULL)
+ {
+ lstrcpy((TCHAR*)GlobalLock(hData), szText);
+ GlobalUnlock(hData);
+ SetClipboardData(CF_TEXT, hData);
+ }
+ CloseClipboard();
+ break;
+ }
+
+ case 10002:
+ {
+ TCHAR szUrl[256], szIP[64];
+ szIP[0] = 0;
+ ListView_GetItemText(hList, lvhti.iItem, 1, szIP, SIZEOF(szIP));
+ mir_sntprintf(szUrl, SIZEOF(szUrl), _T("http://whois.domaintools.com/%s"), szIP);
+ CallService(MS_UTILS_OPENURL, OUF_TCHAR, (LPARAM)szUrl);
+ break;
+ }
+ }
+ DestroyMenu(hMenu);
+ }
+ break;
+ }
+
+ case WM_GETMINMAXINFO:
+ ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = 620;
+ ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = 220;
+ return 0;
+
+ case WM_SIZE:
+ {
+ UTILRESIZEDIALOG urd = {0};
+ urd.cbSize = sizeof(urd);
+ urd.hInstance = hInstance;
+ urd.hwndDlg = hwndDlg;
+ urd.lpTemplate = MAKEINTRESOURCEA(IDD_SESSIONS);
+ urd.pfnResizer = sttSessionsDlgResizer;
+ CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd);
+ return 0;
+ }
+
+ case WM_SETCURSOR:
+ if (LOWORD(lParam) == HTCLIENT && IsOverAction(hwndDlg))
+ {
+ SetCursor(hHandCursor);
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ {
+ HANDLE hProtoAckEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (hProtoAckEvent) UnhookEvent(hProtoAckEvent);
+ gg->hwndSessionsDlg = NULL;
+ WindowFreeIcon(hwndDlg);
+ break;
+ }
+ }
+ return FALSE;
+}
+
+INT_PTR GGPROTO::sessions_view(WPARAM wParam, LPARAM lParam)
+{
+ if (hwndSessionsDlg && IsWindow(hwndSessionsDlg)) {
+ ShowWindow(hwndSessionsDlg, SW_SHOWNORMAL);
+ SetForegroundWindow(hwndSessionsDlg);
+ SetFocus(hwndSessionsDlg);
+ }
+ else CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_SESSIONS), NULL, gg_sessions_viewdlg, (LPARAM)this);
+ return 0;
+}
+
+void GGPROTO::sessions_updatedlg()
+{
+ if (hwndSessionsDlg && IsWindow(hwndSessionsDlg))
+ SendMessage(hwndSessionsDlg, WM_MULTILOGONINFO, 0, 0);
+}
+
+BOOL GGPROTO::sessions_closedlg()
+{
+ if (hwndSessionsDlg && IsWindow(hwndSessionsDlg))
+ return PostMessage(hwndSessionsDlg, WM_CLOSE, 0, 0);
+ return FALSE;
+}
+
+void GGPROTO::sessions_menus_init(HGENMENU hRoot)
+{
+ CLISTMENUITEM mi = {0};
+ char service[64];
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE | CMIF_TCHAR;
+ mi.hParentMenu = hRoot;
+
+ mir_snprintf(service, sizeof(service), GGS_CONCUR_SESS, m_szModuleName);
+ createObjService(service, &GGPROTO::sessions_view);
+ if (hMenuRoot)
+ mi.position = 2050000001;
+ else
+ mi.position = 200003;
+ mi.icolibItem = GetIconHandle(IDI_SESSIONS);
+ mi.ptszName = LPGENT("Concurrent &sessions");
+ mi.pszService = service;
+ Menu_AddProtoMenuItem(&mi);
+}
diff --git a/protocols/Gadu-Gadu/src/token.cpp b/protocols/Gadu-Gadu/src/token.cpp
new file mode 100644
index 0000000000..d2946c12ef
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/token.cpp
@@ -0,0 +1,161 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+#define MAX_LOADSTRING 100
+#define HIMETRIC_INCH 2540
+#define MAP_LOGHIM_TO_PIX(x,ppli) ( ((ppli)*(x) + HIMETRIC_INCH/2) / HIMETRIC_INCH )
+
+////////////////////////////////////////////////////////////////////////////////
+// User Util Dlg Page : Data
+
+typedef struct _GGTOKENDLGDATA
+{
+ int width;
+ int height;
+ char id[256];
+ char val[256];
+ HBITMAP hBitmap;
+} GGTOKENDLGDATA;
+
+INT_PTR CALLBACK gg_tokendlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGTOKENDLGDATA *dat = (GGTOKENDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT rc;
+ TranslateDialogDefault(hwndDlg);
+ GetClientRect(GetDlgItem(hwndDlg, IDC_WHITERECT), &rc);
+ InvalidateRect(hwndDlg, &rc, TRUE);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ return TRUE;
+ }
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ GetDlgItemTextA(hwndDlg, IDC_TOKEN, dat->val, sizeof(dat->val));
+ EndDialog(hwndDlg, IDOK);
+ break;
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ PAINTSTRUCT paintStruct;
+ HDC hdc = BeginPaint(hwndDlg, &paintStruct);
+ RECT rc; GetClientRect(GetDlgItem(hwndDlg, IDC_WHITERECT), &rc);
+ FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
+
+ if (dat && dat->hBitmap)
+ {
+ HDC hdcBmp = NULL;
+ int nWidth, nHeight;
+ BITMAP bmp;
+
+ GetObject(dat->hBitmap, sizeof(bmp), &bmp);
+ nWidth = bmp.bmWidth; nHeight = bmp.bmHeight;
+
+ if (hdcBmp = CreateCompatibleDC(hdc))
+ {
+ SelectObject(hdcBmp, dat->hBitmap);
+ SetStretchBltMode(hdc, HALFTONE);
+ BitBlt(hdc,
+ (rc.left + rc.right - nWidth) / 2,
+ (rc.top + rc.bottom - nHeight) / 2,
+ nWidth, nHeight,
+ hdcBmp, 0, 0, SRCCOPY);
+ DeleteDC(hdcBmp);
+ }
+ }
+ EndPaint(hwndDlg, &paintStruct);
+ return 0;
+ }
+ }
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Gets GG token
+int GGPROTO::gettoken(GGTOKEN *token)
+{
+ struct gg_http *h = NULL;
+ struct gg_token *t = NULL;
+ IMGSRVC_MEMIO memio = {0};
+ GGTOKENDLGDATA dat = {0};
+
+ // Zero tokens
+ strcpy(token->id, "");
+ strcpy(token->val, "");
+
+ if (!(h = gg_token(0)) || gg_token_watch_fd(h) || h->state == GG_STATE_ERROR || h->state != GG_STATE_DONE) {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Token retrieval failed because of error:\n\t%S"), http_error_string(h ? h->error : 0));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg_free_pubdir(h);
+ return FALSE;
+ }
+
+ if (!(t = (struct gg_token *)h->data) || (!h->body)) {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Token retrieval failed because of error:\n\t%S"), http_error_string(h ? h->error : 0));
+ MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg_free_pubdir(h);
+ return FALSE;
+ }
+
+ // Return token id
+ strncpy(dat.id, t->tokenid, sizeof(dat.id));
+ dat.width = t->width;
+ dat.height = t->height;
+
+ // Load bitmap
+ memio.iLen = h->body_size;
+ memio.pBuf = (void *)h->body;
+ memio.fif = FIF_UNKNOWN; /* detect */
+ memio.flags = 0;
+ dat.hBitmap = (HBITMAP) CallService(MS_IMG_LOADFROMMEM, (WPARAM) &memio, 0);
+ if (dat.hBitmap == NULL)
+ {
+ MessageBox(NULL, TranslateT("Could not load token image."), m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg_free_pubdir(h);
+ return FALSE;
+ }
+
+ // Load token dialog
+ if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_TOKEN), NULL, gg_tokendlgproc, (LPARAM)&dat) == IDCANCEL)
+ return FALSE;
+
+ // Fillup patterns
+ strncpy(token->id, dat.id, sizeof(token->id));
+ strncpy(token->val, dat.val, sizeof(token->val));
+
+ return TRUE;
+}
diff --git a/protocols/Gadu-Gadu/src/userutils.cpp b/protocols/Gadu-Gadu/src/userutils.cpp
new file mode 100644
index 0000000000..c9681c56ef
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/userutils.cpp
@@ -0,0 +1,329 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2006 Adam Strzelecki <ono+miranda@java.pl>
+//
+// 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 "gg.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Create New Account : Proc
+
+void *gg_doregister(GGPROTO *gg, char *newPass, char *newEmail)
+{
+ // Connection handles
+ struct gg_http *h = NULL;
+ struct gg_pubdir *s = NULL;
+ GGTOKEN token;
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_doregister(): Starting.");
+#endif
+ if (!newPass || !newEmail) return NULL;
+
+ // Load token
+ if (!gg->gettoken(&token)) return NULL;
+
+ if (!(h = gg_register3(newEmail, newPass, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success || !s->uin)
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Cannot register new account because of error:\n\t%S"),
+ (h && !s) ? http_error_string(h ? h->error : 0) :
+ (s ? Translate("Registration rejected") : strerror(errno)));
+ MessageBox(
+ NULL,
+ error,
+ gg->m_tszUserName,
+ MB_OK | MB_ICONSTOP
+ );
+ gg->netlog("gg_doregister(): Cannot register because of \"%s\".", strerror(errno));
+ }
+ else
+ {
+ db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, s->uin);
+ CallService(MS_DB_CRYPT_ENCODESTRING, strlen(newPass) + 1, (LPARAM) newPass);
+ gg->checknewuser(s->uin, newPass);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, newPass);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, newEmail);
+ gg_pubdir_free(h);
+ gg->netlog("gg_doregister(): Account registration succesful.");
+ MessageBox( NULL,
+ TranslateT("You have registered new account.\nPlease fill up your personal details in \"M->View/Change My Details...\""),
+ gg->m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_doregister(): End.");
+#endif
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Remove Account : Proc
+void *gg_dounregister(GGPROTO *gg, uin_t uin, char *password)
+{
+ // Connection handles
+ struct gg_http *h;
+ struct gg_pubdir *s;
+ GGTOKEN token;
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_dounregister(): Starting.");
+#endif
+ if (!uin || !password) return NULL;
+
+ // Load token
+ if (!gg->gettoken(&token)) return NULL;
+
+ if (!(h = gg_unregister3(uin, password, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success || s->uin != uin)
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Your account cannot be removed because of error:\n\t%S"),
+ (h && !s) ? http_error_string(h ? h->error : 0) :
+ (s ? Translate("Bad number or password") : strerror(errno)));
+ MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg->netlog("gg_dounregister(): Cannot remove account because of \"%s\".", strerror(errno));
+ }
+ else
+ {
+ gg_pubdir_free(h);
+ db_unset(NULL, gg->m_szModuleName, GG_KEY_PASSWORD);
+ db_unset(NULL, gg->m_szModuleName, GG_KEY_UIN);
+ gg->netlog("gg_dounregister(): Account %d has been removed.", uin);
+ MessageBox(NULL, TranslateT("Your account has been removed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_dounregister(): End.");
+#endif
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Change Password Page : Proc
+
+void *gg_dochpass(GGPROTO *gg, uin_t uin, char *password, char *newPass)
+{
+ // Readup email
+ char email[255] = "\0"; DBVARIANT dbv_email;
+ // Connection handles
+ struct gg_http *h;
+ struct gg_pubdir *s;
+ GGTOKEN token;
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_dochpass(): Starting.");
+#endif
+ if (!uin || !password || !newPass) return NULL;
+
+ if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv_email, DBVT_ASCIIZ))
+ {
+ strncpy(email, dbv_email.pszVal, sizeof(email));
+ DBFreeVariant(&dbv_email);
+ }
+
+ // Load token
+ if (!gg->gettoken(&token))
+ return NULL;
+
+ if (!(h = gg_change_passwd4(uin, email, password, newPass, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success)
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Your password cannot be changed because of error:\n\t%S"),
+ (h && !s) ? http_error_string(h ? h->error : 0) :
+ (s ? Translate("Invalid data entered") : strerror(errno)));
+ MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg->netlog("gg_dochpass(): Cannot change password because of \"%s\".", strerror(errno));
+ }
+ else
+ {
+ gg_pubdir_free(h);
+ CallService(MS_DB_CRYPT_ENCODESTRING, strlen(newPass) + 1, (LPARAM) newPass);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, newPass);
+ gg->netlog("gg_dochpass(): Password change succesful.");
+ MessageBox(NULL, TranslateT("Your password has been changed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_dochpass(): End.");
+#endif
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Change E-mail Page : Proc
+
+void *gg_dochemail(GGPROTO *gg, uin_t uin, char *password, char *email, char *newEmail)
+{
+ // Connection handles
+ struct gg_http *h;
+ struct gg_pubdir *s;
+ GGTOKEN token;
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_doemail(): Starting.");
+#endif
+ if (!uin || !email || !newEmail) return NULL;
+
+ // Load token
+ if (!gg->gettoken(&token)) return NULL;
+
+ if (!(h = gg_change_passwd4(uin, newEmail, password, password, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success)
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Your e-mail cannot be changed because of error:\n\t%s"),
+ (h && !s) ? http_error_string(h ? h->error : 0) : (s ? Translate("Bad old e-mail or password") : strerror(errno)));
+ MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP);
+ gg->netlog("gg_dochpass(): Cannot change e-mail because of \"%s\".", strerror(errno));
+ }
+ else
+ {
+ gg_pubdir_free(h);
+ db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, newEmail);
+ gg->netlog("gg_doemail(): E-mail change succesful.");
+ MessageBox(NULL, TranslateT("Your e-mail has been changed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+
+#ifdef DEBUGMODE
+ gg->netlog("gg_doemail(): End.");
+#endif
+
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// User Util Dlg Page : Data
+INT_PTR CALLBACK gg_userutildlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ GGUSERUTILDLGDATA *dat = (GGUSERUTILDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ WindowSetIcon(hwndDlg, "settings");
+ dat = (GGUSERUTILDLGDATA *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
+ if (dat) SetDlgItemTextA(hwndDlg, IDC_EMAIL, dat->email); // Readup email
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_PASSWORD:
+ case IDC_CPASSWORD:
+ case IDC_CONFIRM:
+ {
+ char pass[128], cpass[128];
+ BOOL enable;
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass));
+ GetDlgItemTextA(hwndDlg, IDC_CPASSWORD, cpass, sizeof(cpass));
+ enable = strlen(pass) && strlen(cpass) && !strcmp(cpass, pass);
+ if (dat && dat->mode == GG_USERUTIL_REMOVE)
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), IsDlgButtonChecked(hwndDlg, IDC_CONFIRM) ? enable : FALSE);
+ else
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), enable);
+ break;
+ }
+
+ case IDOK:
+ {
+ char pass[128], cpass[128], email[128];
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass));
+ GetDlgItemTextA(hwndDlg, IDC_CPASSWORD, cpass, sizeof(cpass));
+ GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email));
+ EndDialog(hwndDlg, IDOK);
+
+ // Check dialog box mode
+ if (!dat) break;
+ switch (dat->mode)
+ {
+ case GG_USERUTIL_CREATE: gg_doregister(dat->gg, pass, email); break;
+ case GG_USERUTIL_REMOVE: gg_dounregister(dat->gg, dat->uin, pass); break;
+ case GG_USERUTIL_PASS: gg_dochpass(dat->gg, dat->uin, dat->pass, pass); break;
+ case GG_USERUTIL_EMAIL: gg_dochemail(dat->gg, dat->uin, dat->pass, dat->email, email); break;
+ }
+ break;
+ }
+
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ WindowFreeIcon(hwndDlg);
+ break;
+ }
+ return FALSE;
+}
+
+//////////////////////////////////////////////////////////
+// Hooks protocol event
+
+HANDLE GGPROTO::hookProtoEvent(const char* szEvent, GGEventFunc handler)
+{
+ return ::HookEventObj(szEvent, ( MIRANDAHOOKOBJ )*( void** )&handler, this);
+}
+
+//////////////////////////////////////////////////////////
+// Adds a new protocol specific service function
+
+void GGPROTO::createObjService(const char* szService, GGServiceFunc serviceProc)
+{
+ CreateServiceFunctionObj(szService, (MIRANDASERVICEOBJ)*( void** )&serviceProc, this);
+}
+
+void GGPROTO::createProtoService(const char* szService, GGServiceFunc serviceProc)
+{
+ char str[MAXMODULELABELLENGTH];
+ mir_snprintf(str, sizeof(str), "%s%s", m_szModuleName, szService);
+ CreateServiceFunctionObj(str, (MIRANDASERVICEOBJ)*( void** )&serviceProc, this);
+}
+
+//////////////////////////////////////////////////////////
+// Forks a thread
+
+void GGPROTO::forkthread(GGThreadFunc pFunc, void *param)
+{
+ UINT threadId;
+ CloseHandle( mir_forkthreadowner((pThreadFuncOwner)*(void**)&pFunc, this, param, &threadId));
+}
+
+//////////////////////////////////////////////////////////
+// Forks a thread and returns a pseudo handle for it
+
+HANDLE GGPROTO::forkthreadex(GGThreadFunc pFunc, void *param, UINT *threadId)
+{
+ return mir_forkthreadowner((pThreadFuncOwner)*(void**)&pFunc, this, param, threadId);
+}
+
+//////////////////////////////////////////////////////////
+// Wait for thread to stop
+
+void GGPROTO::threadwait(GGTHREAD *thread)
+{
+ if (!thread->hThread) return;
+ while (WaitForSingleObjectEx(thread->hThread, INFINITE, TRUE) != WAIT_OBJECT_0);
+ CloseHandle(thread->hThread);
+ ZeroMemory(thread, sizeof(GGTHREAD));
+}
+
diff --git a/protocols/Gadu-Gadu/src/version.h b/protocols/Gadu-Gadu/src/version.h
new file mode 100644
index 0000000000..ce9e719bd6
--- /dev/null
+++ b/protocols/Gadu-Gadu/src/version.h
@@ -0,0 +1,23 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2010 Bartosz Białek
+//
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+#define __FILEVERSION_STRING 0,11,0,1
+#define __VERSION_STRING "0.11.0.1"
+#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 11, 0, 1)