/*
Jabber Protocol Plugin for Miranda IM
Copyright (C) 2002-04  Santithorn Bunchua
Copyright (C) 2005-12  George Hazan
Copyright (C) 2012-13  Miranda NG Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "jabber.h"
#include "jabber_caps.h"
extern CRITICAL_SECTION mutex;
extern int bSecureIM, bMirOTR, bNewGPG, bPlatform;
void CJabberProto::SerialInit(void)
{
	InitializeCriticalSection(&m_csSerial);
	m_nSerial = 0;
}
void CJabberProto::SerialUninit(void)
{
	DeleteCriticalSection(&m_csSerial);
}
int CJabberProto::SerialNext(void)
{
	unsigned int ret;
	EnterCriticalSection(&m_csSerial);
	ret = m_nSerial;
	m_nSerial++;
	LeaveCriticalSection(&m_csSerial);
	return ret;
}
void CJabberProto::Log(const char* fmt, ...)
{
	va_list vararg;
	va_start(vararg, fmt);
	char* str = (char*)alloca(32000);
	mir_vsnprintf(str, 32000, fmt, vararg);
	va_end(vararg);
	CallService(MS_NETLIB_LOG, (WPARAM)m_hNetlibUser, (LPARAM)str);
}
///////////////////////////////////////////////////////////////////////////////
// JabberChatRoomHContactFromJID - looks for the char room HCONTACT with required JID
HANDLE CJabberProto::ChatRoomHContactFromJID(const TCHAR *jid)
{
	if (jid == NULL)
		return NULL;
	for (HANDLE hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
		DBVARIANT dbv;
		int result = getTString(hContact, "ChatRoomID", &dbv);
		if (result)
			result = getTString(hContact, "jid", &dbv);
		if ( !result) {
			int result;
			result = lstrcmpi(jid, dbv.ptszVal);
			db_free(&dbv);
			if ( !result && isChatRoom(hContact))
				return hContact;
	}	}
	return NULL;
}
///////////////////////////////////////////////////////////////////////////////
// JabberHContactFromJID - looks for the HCONTACT with required JID
HANDLE CJabberProto::HContactFromJID(const TCHAR *jid , BOOL bStripResource)
{
	if (jid == NULL)
		return NULL;
	JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_CHATROOM, jid);
	for (HANDLE hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
		bool bIsChat = isChatRoom(hContact);
		DBVARIANT dbv;
		if ( !getTString(hContact, bIsChat ? "ChatRoomID" : "jid", &dbv)) {
			int result;
			if (item != NULL)
				result = lstrcmpi(jid, dbv.ptszVal);
			else {
				if (bStripResource == 3) {
					if (bIsChat)
						result = lstrcmpi(jid, dbv.ptszVal);  // for chat room we have to have full contact matched
					else if (TRUE)
						result = _tcsnicmp(jid, dbv.ptszVal, _tcslen(dbv.ptszVal));
					else
						result = JabberCompareJids(jid, dbv.ptszVal);
				}
				// most probably it should just look full matching contact
				else
					result = lstrcmpi(jid, dbv.ptszVal);
			}
			db_free(&dbv);
			if ( !result)
				return hContact;
		}
	}
	return NULL;
}
TCHAR* __stdcall JabberNickFromJID(const TCHAR *jid)
{
	if ( !jid) return mir_tstrdup(_T(""));
	const TCHAR *p;
	TCHAR *nick;
	if ((p = _tcschr(jid, '@')) == NULL)
		p = _tcschr(jid, '/');
	if (p != NULL) {
		if ((nick=(TCHAR*)mir_alloc(sizeof(TCHAR)*(int(p-jid)+1))) != NULL) {
			_tcsncpy(nick, jid, p-jid);
			nick[p-jid] = '\0';
		}
	}
	else nick = mir_tstrdup(jid);
	return nick;
}
JABBER_RESOURCE_STATUS* CJabberProto::ResourceInfoFromJID(const TCHAR *jid)
{
	if ( !jid)
		return NULL;
	JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_VCARD_TEMP, jid);
	if (item == NULL)
		item = ListGetItemPtr(LIST_ROSTER, jid);
	if (item == NULL)
		return NULL;
	const TCHAR *p = _tcschr(jid, '/');
	if (p == NULL)
		return &item->itemResource;
	return item->findResource(p+1);
}
JABBER_LIST_ITEM* CJabberProto::GetItemFromContact(HANDLE hContact)
{
	DBVARIANT dbv;
	if (getTString(hContact, "jid", &dbv))
		return NULL;
	JABBER_LIST_ITEM *pItem = ListGetItemPtr(LIST_ROSTER, dbv.ptszVal);
	db_free(&dbv);
	return pItem;
}
TCHAR* JabberPrepareJid(LPCTSTR jid)
{
	if ( !jid) return NULL;
	TCHAR *szNewJid = mir_tstrdup(jid);
	if ( !szNewJid) return NULL;
	TCHAR *pDelimiter = _tcschr(szNewJid, _T('/'));
	if (pDelimiter) *pDelimiter = _T('\0');
	CharLower(szNewJid);
	if (pDelimiter) *pDelimiter = _T('/');
	return szNewJid;
}
void strdel(char* parBuffer, int len)
{
	char* p;
	for (p = parBuffer+len; *p != 0; p++)
		p[ -len ] = *p;
	p[ -len ] = '\0';
}
char* __stdcall JabberUrlDecode(char* str)
{
	char* p, *q;
	if (str == NULL)
		return NULL;
	for (p=q=str; *p!='\0'; p++,q++) {
		if (*p == '<') {
			// skip CDATA
			if ( !strncmp(p, "");
				size_t count = tail ? (tail-p) : strlen(p);
				memmove(q, p, count);
				q += count-1;
				p = (tail ? (tail+3) : (p+count)) - 1;
			} else
			{
				*q = *p;
			}
		} else
		if (*p == '&') {
			if ( !strncmp(p, "&", 5)) {	*q = '&'; p += 4; }
			else if ( !strncmp(p, "'", 6)) { *q = '\''; p += 5; }
			else if ( !strncmp(p, ">", 4)) { *q = '>'; p += 3; }
			else if ( !strncmp(p, "<", 4)) { *q = '<'; p += 3; }
			else if ( !strncmp(p, """, 6)) { *q = '"'; p += 5; }
			else { *q = *p;	}
		} else
		{
			*q = *p;
		}
	}
	*q = '\0';
	return str;
}
void __stdcall JabberUrlDecodeW(WCHAR* str)
{
	if (str == NULL)
		return;
	WCHAR* p, *q;
	for (p=q=str; *p!='\0'; p++,q++) {
		if (*p == '&') {
			if ( !wcsncmp(p, L"&", 5)) {	*q = '&'; p += 4; }
			else if ( !wcsncmp(p, L"'", 6)) { *q = '\''; p += 5; }
			else if ( !wcsncmp(p, L">", 4)) { *q = '>'; p += 3; }
			else if ( !wcsncmp(p, L"<", 4)) { *q = '<'; p += 3; }
			else if ( !wcsncmp(p, L""", 6)) { *q = '"'; p += 5; }
			else { *q = *p;	}
		}
		else {
			*q = *p;
		}
	}
	*q = '\0';
}
char* __stdcall JabberUrlEncode(const char* str)
{
	char* s, *p, *q;
	int c;
	if (str == NULL)
		return NULL;
	for (c=0,p=(char*)str; *p!='\0'; p++) {
		switch (*p) {
			case '&': c += 5; break;
			case '\'': c += 6; break;
			case '>': c += 4; break;
			case '<': c += 4; break;
			case '"': c += 6; break;
			default: c++; break;
		}
	}
	if ((s=(char*)mir_alloc(c+1)) != NULL) {
		for (p=(char*)str,q=s; *p!='\0'; p++) {
			switch (*p) {
			case '&': strcpy(q, "&"); q += 5; break;
			case '\'': strcpy(q, "'"); q += 6; break;
			case '>': strcpy(q, ">"); q += 4; break;
			case '<': strcpy(q, "<"); q += 4; break;
			case '"': strcpy(q, """); q += 6; break;
			default:
				if (*p > 0 && *p < 32) {
					switch(*p) {
					case '\r':
					case '\n':
					case '\t':
						*q = *p;
						break;
					default:
						*q = '?';
					}
				}
				else *q = *p;
				q++;
				break;
			}
		}
		*q = '\0';
	}
	return s;
}
void __stdcall JabberUtfToTchar(const char* pszValue, size_t cbLen, LPTSTR& dest)
{
	char* pszCopy = NULL;
	bool  bNeedsFree = false;
	__try
	{
		// this code can cause access violation when a stack overflow occurs
		pszCopy = (char*)alloca(cbLen+1);
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		bNeedsFree = true;
		pszCopy = (char*)malloc(cbLen+1);
	}
	if (pszCopy == NULL)
		return;
	memcpy(pszCopy, pszValue, cbLen);
	pszCopy[ cbLen ] = 0;
	JabberUrlDecode(pszCopy);
	mir_utf8decode(pszCopy, &dest);
	if (bNeedsFree)
		free(pszCopy);
}
char* __stdcall JabberSha1(char* str)
{
	if (str == NULL)
		return NULL;
	BYTE digest[20];
	mir_sha1_ctx sha;
	mir_sha1_init(&sha);
	mir_sha1_append(&sha, (BYTE*)str, (int)strlen(str));
	mir_sha1_finish(&sha, digest);
	char *result = (char*)mir_alloc(41);
	if (result)
		bin2hex(digest, sizeof(digest), result);
	return result;
}
TCHAR* __stdcall JabberStrFixLines(const TCHAR *str)
{
	if ( !str) return NULL;
	const TCHAR *p;
	int add = 0;
	bool prev_r = false;
	bool prev_n = false;
	for (p = str; p && *p; ++p)
		if (*p == _T('\r') || *p == _T('\n'))
			++add;
	TCHAR *buf = (TCHAR *)mir_alloc((lstrlen(str) + add + 1) * sizeof(TCHAR));
	TCHAR *res = buf;
	for (p = str; p && *p; ++p)
	{
		if (*p == _T('\n') && !prev_r)
			*res++ = _T('\r');
		if (*p != _T('\r') && *p != _T('\n') && prev_r)
			*res++ = _T('\n');
		*res++ = *p;
		prev_r = *p == _T('\r');
		prev_n = *p == _T('\n');
	}
	*res = 0;
	return buf;
}
char* __stdcall JabberUnixToDos(const char* str)
{
	char* p, *q, *res;
	int extra;
	if (str == NULL || str[0]=='\0')
		return NULL;
	extra = 0;
	for (p=(char*)str; *p!='\0'; p++) {
		if (*p == '\n')
			extra++;
	}
	if ((res=(char*)mir_alloc(strlen(str)+extra+1)) != NULL) {
		for (p=(char*)str,q=res; *p!='\0'; p++,q++) {
			if (*p == '\n') {
				*q = '\r';
				q++;
			}
			*q = *p;
		}
		*q = '\0';
	}
	return res;
}
WCHAR* __stdcall JabberUnixToDosW(const WCHAR* str)
{
	if (str == NULL || str[0]=='\0')
		return NULL;
	const WCHAR* p;
	WCHAR* q, *res;
	int extra = 0;
	for (p = str; *p!='\0'; p++) {
		if (*p == '\n')
			extra++;
	}
	if ((res = (WCHAR*)mir_alloc(sizeof(WCHAR)*(wcslen(str) + extra + 1))) != NULL) {
		for (p = str,q=res; *p!='\0'; p++,q++) {
			if (*p == '\n') {
				*q = '\r';
				q++;
			}
			*q = *p;
		}
		*q = '\0';
	}
	return res;
}
void __stdcall JabberHttpUrlDecode(TCHAR* str)
{
	TCHAR *p, *q;
	unsigned int code;
	if (str == NULL) return;
	for (p = q = (TCHAR*)str; *p!='\0'; p++,q++) {
		if (*p=='%' && *(p+1)!='\0' && isxdigit(*(p+1)) && *(p+2)!='\0' && isxdigit(*(p+2))) {
			_stscanf((TCHAR*)p+1, _T("%2x"), &code);
			*q = (unsigned char) code;
			p += 2;
		}
		else {
			*q = *p;
	}	}
	*q = '\0';
}
int __stdcall JabberCombineStatus(int status1, int status2)
{
	// Combine according to the following priority (high to low)
	// ID_STATUS_FREECHAT
	// ID_STATUS_ONLINE
	// ID_STATUS_DND
	// ID_STATUS_AWAY
	// ID_STATUS_NA
	// ID_STATUS_INVISIBLE (valid only for TLEN_PLUGIN)
	// ID_STATUS_OFFLINE
	// other ID_STATUS in random order (actually return status1)
	if (status1==ID_STATUS_FREECHAT || status2==ID_STATUS_FREECHAT)
		return ID_STATUS_FREECHAT;
	if (status1==ID_STATUS_ONLINE || status2==ID_STATUS_ONLINE)
		return ID_STATUS_ONLINE;
	if (status1==ID_STATUS_DND || status2==ID_STATUS_DND)
		return ID_STATUS_DND;
	if (status1==ID_STATUS_AWAY || status2==ID_STATUS_AWAY)
		return ID_STATUS_AWAY;
	if (status1==ID_STATUS_NA || status2==ID_STATUS_NA)
		return ID_STATUS_NA;
	if (status1==ID_STATUS_INVISIBLE || status2==ID_STATUS_INVISIBLE)
		return ID_STATUS_INVISIBLE;
	if (status1==ID_STATUS_OFFLINE || status2==ID_STATUS_OFFLINE)
		return ID_STATUS_OFFLINE;
	return status1;
}
struct tagErrorCodeToStr {
	int code;
	TCHAR *str;
}
static JabberErrorCodeToStrMapping[] = {
	{ JABBER_ERROR_REDIRECT,               LPGENT("Redirect") },
	{ JABBER_ERROR_BAD_REQUEST,            LPGENT("Bad request") },
	{ JABBER_ERROR_UNAUTHORIZED,           LPGENT("Unauthorized") },
	{ JABBER_ERROR_PAYMENT_REQUIRED,       LPGENT("Payment required") },
	{ JABBER_ERROR_FORBIDDEN,              LPGENT("Forbidden") },
	{ JABBER_ERROR_NOT_FOUND,              LPGENT("Not found") },
	{ JABBER_ERROR_NOT_ALLOWED,            LPGENT("Not allowed") },
	{ JABBER_ERROR_NOT_ACCEPTABLE,         LPGENT("Not acceptable") },
	{ JABBER_ERROR_REGISTRATION_REQUIRED,  LPGENT("Registration required") },
	{ JABBER_ERROR_REQUEST_TIMEOUT,        LPGENT("Request timeout") },
	{ JABBER_ERROR_CONFLICT,               LPGENT("Conflict") },
	{ JABBER_ERROR_INTERNAL_SERVER_ERROR,  LPGENT("Internal server error") },
	{ JABBER_ERROR_NOT_IMPLEMENTED,        LPGENT("Not implemented") },
	{ JABBER_ERROR_REMOTE_SERVER_ERROR,    LPGENT("Remote server error") },
	{ JABBER_ERROR_SERVICE_UNAVAILABLE,    LPGENT("Service unavailable") },
	{ JABBER_ERROR_REMOTE_SERVER_TIMEOUT,  LPGENT("Remote server timeout") },
	{ -1,                                  LPGENT("Unknown error") }
};
TCHAR* __stdcall JabberErrorStr(int errorCode)
{
	int i;
	for (i=0; JabberErrorCodeToStrMapping[i].code!=-1 && JabberErrorCodeToStrMapping[i].code!=errorCode; i++);
	return JabberErrorCodeToStrMapping[i].str;
}
TCHAR* __stdcall JabberErrorMsg(HXML errorNode, int* pErrorCode)
{
	TCHAR *errorStr = (TCHAR*)mir_alloc(256 * sizeof(TCHAR));
	if (errorNode == NULL) {
		if (pErrorCode)
			*pErrorCode = -1;
		mir_sntprintf(errorStr, 256, _T("%s -1: %s"), TranslateT("Error"), TranslateT("Unknown error message"));
		return errorStr;
	}
	int errorCode = -1;
	const TCHAR *str = xmlGetAttrValue(errorNode, _T("code"));
	if (str != NULL)
		errorCode = _ttoi(str);
	str = xmlGetText(errorNode);
	if (str == NULL)
		str = xmlGetText(xmlGetChild(errorNode, _T("text")));
	if (str == NULL) {
		for (int i = 0; ; i++) {
			HXML c = xmlGetChild(errorNode, i);
			if (c == NULL) break;
			const TCHAR *attr = xmlGetAttrValue(c, _T("xmlns"));
			if (attr && !_tcscmp(attr, _T("urn:ietf:params:xml:ns:xmpp-stanzas"))) {
				str = xmlGetName(c);
				break;
			}
		}
	}
	if (str != NULL)
		mir_sntprintf(errorStr, 256, _T("%s %d: %s\r\n%s"), TranslateT("Error"), errorCode, TranslateTS(JabberErrorStr(errorCode)), str);
	else
		mir_sntprintf(errorStr, 256, _T("%s %d: %s"), TranslateT("Error"), errorCode, TranslateTS(JabberErrorStr(errorCode)));
	if (pErrorCode)
		*pErrorCode = errorCode;
	return errorStr;
}
void CJabberProto::SendVisibleInvisiblePresence(BOOL invisible)
{
	if ( !m_bJabberOnline) return;
	LISTFOREACH(i, this, LIST_ROSTER)
	{
		JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i);
		if (item == NULL)
			continue;
		HANDLE hContact = HContactFromJID(item->jid);
		if (hContact == NULL)
			continue;
		WORD apparentMode = getWord(hContact, "ApparentMode", 0);
		if (invisible==TRUE && apparentMode==ID_STATUS_OFFLINE)
			m_ThreadInfo->send(XmlNode(_T("presence")) << XATTR(_T("to"), item->jid) << XATTR(_T("type"), _T("invisible")));
		else if (invisible==FALSE && apparentMode==ID_STATUS_ONLINE)
			SendPresenceTo(m_iStatus, item->jid, NULL);
}	}
time_t __stdcall JabberIsoToUnixTime(const TCHAR *stamp)
{
	struct tm timestamp;
	TCHAR date[9];
	int i, y;
	time_t t;
	if (stamp == NULL) return (time_t) 0;
	const TCHAR *p = stamp;
	// Get the date part
	for (i=0; *p!='\0' && i<8 && isdigit(*p); p++,i++)
		date[i] = *p;
	// Parse year
	if (i == 6) {
		// 2-digit year (1970-2069)
		y = (date[0]-'0')*10 + (date[1]-'0');
		if (y < 70) y += 100;
	}
	else if (i == 8) {
		// 4-digit year
		y = (date[0]-'0')*1000 + (date[1]-'0')*100 + (date[2]-'0')*10 + date[3]-'0';
		y -= 1900;
	}
	else
		return (time_t) 0;
	timestamp.tm_year = y;
	// Parse month
	timestamp.tm_mon = (date[i-4]-'0')*10 + date[i-3]-'0' - 1;
	// Parse date
	timestamp.tm_mday = (date[i-2]-'0')*10 + date[i-1]-'0';
	// Skip any date/time delimiter
	for (; *p!='\0' && !isdigit(*p); p++);
	// Parse time
	if (_stscanf(p, _T("%d:%d:%d"), ×tamp.tm_hour, ×tamp.tm_min, ×tamp.tm_sec) != 3)
		return (time_t) 0;
	timestamp.tm_isdst = 0;	// DST is already present in _timezone below
	t = mktime(×tamp);
	_tzset();
	t -= _timezone;
	if (t >= 0)
		return t;
	else
		return (time_t) 0;
}
void CJabberProto::SendPresenceTo(int status, TCHAR* to, HXML extra, const TCHAR *msg)
{
	if ( !m_bJabberOnline) return;
	// Send  update for status (we won't handle ID_STATUS_OFFLINE here)
	short iPriority = (short)getWord("Priority", 0);
	UpdatePriorityMenu(iPriority);
	TCHAR szPriority[40];
	_itot(iPriority, szPriority, 10);
	XmlNode p(_T("presence")); p << XCHILD(_T("priority"), szPriority);
	if (to != NULL)
		p << XATTR(_T("to"), to);
	if (extra)
		xmlAddChild(p, extra);
	// XEP-0115:Entity Capabilities
	HXML c = p << XCHILDNS(_T("c"), JABBER_FEAT_ENTITY_CAPS) << XATTR(_T("node"), JABBER_CAPS_MIRANDA_NODE)
		<< XATTR(_T("ver"), szCoreVersion);
	TCHAR szExtCaps[ 512 ] = _T("");
	if (m_bGoogleTalk)
		_tcscat(szExtCaps, _T(JABBER_EXT_GTALK_PMUC));
	if (bSecureIM) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_SECUREIM));
	}
	if (bMirOTR) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_MIROTR));
	}
	if (bNewGPG) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_NEWGPG));
	}
	if (bPlatform) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_PLATFORMX64));
	} else {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_PLATFORMX86));
	}
	if (m_options.EnableRemoteControl) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_COMMANDS));
	}
	if (m_options.EnableUserMood) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_USER_MOOD));
	}
	if (m_options.EnableUserTune) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_USER_TUNE));
	}
	if (m_options.EnableUserActivity) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_USER_ACTIVITY));
	}
	if (m_options.AcceptNotes) {
		if (szExtCaps[0])
			_tcscat(szExtCaps, _T(" "));
		_tcscat(szExtCaps, _T(JABBER_EXT_MIR_NOTES));
	}
	// add features enabled through IJabberNetInterface::AddFeatures()
	for (int i = 0; i < m_lstJabberFeatCapPairsDynamic.getCount(); i++) {
		if (m_uEnabledFeatCapsDynamic & m_lstJabberFeatCapPairsDynamic[i]->jcbCap) {
			if (szExtCaps[0])
				_tcscat(szExtCaps, _T(" "));
			_tcscat(szExtCaps, m_lstJabberFeatCapPairsDynamic[i]->szExt);
		}
	}
	if (szExtCaps[0])
		xmlAddAttr(c, _T("ext"), szExtCaps);
	if (m_options.EnableAvatars) {
		HXML x = p << XCHILDNS(_T("x"), _T("vcard-temp:x:update"));
		ptrA hashValue( getStringA("AvatarHash"));
		if (hashValue != NULL) // XEP-0153: vCard-Based Avatars
			x << XCHILD(_T("photo"), _A2T(hashValue));
		else
			x << XCHILD(_T("photo"));
	}
	EnterCriticalSection(&m_csModeMsgMutex);
	switch (status) {
	case ID_STATUS_ONLINE:
		if ( !msg) msg = m_modeMsgs.szOnline;
		break;
	case ID_STATUS_INVISIBLE:
		if ( !m_bGoogleSharedStatus) p << XATTR(_T("type"), _T("invisible"));
		break;
	case ID_STATUS_AWAY:
	case ID_STATUS_ONTHEPHONE:
	case ID_STATUS_OUTTOLUNCH:
		p << XCHILD(_T("show"), _T("away"));
		if ( !msg) msg = m_modeMsgs.szAway;
		break;
	case ID_STATUS_NA:
		p << XCHILD(_T("show"), _T("xa"));
		if ( !msg) msg = m_modeMsgs.szNa;
		break;
	case ID_STATUS_DND:
	case ID_STATUS_OCCUPIED:
		p << XCHILD(_T("show"), _T("dnd"));
		if ( !msg) msg = m_modeMsgs.szDnd;
		break;
	case ID_STATUS_FREECHAT:
		p << XCHILD(_T("show"), _T("chat"));
		if ( !msg) msg = m_modeMsgs.szFreechat;
		break;
	default:
		// Should not reach here
		break;
	}
	if (msg)
		p << XCHILD(_T("status"), msg);
	if (m_bGoogleSharedStatus && !to)
		SendIqGoogleSharedStatus(status, msg);
	LeaveCriticalSection(&m_csModeMsgMutex);
	m_ThreadInfo->send(p);
}
void CJabberProto::SendPresence(int status, bool bSendToAll)
{
	SendPresenceTo(status, NULL, NULL);
	SendVisibleInvisiblePresence(status == ID_STATUS_INVISIBLE);
	// Also update status in all chatrooms
	if (bSendToAll) {
		LISTFOREACH(i, this, LIST_CHATROOM)
		{
			JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i);
			if (item != NULL) {
				TCHAR text[ 1024 ];
				mir_sntprintf(text, SIZEOF(text), _T("%s/%s"), item->jid, item->nick);
				SendPresenceTo(status == ID_STATUS_INVISIBLE ? ID_STATUS_ONLINE : status, text, NULL);
}	}	}	}
/////////////////////////////////////////////////////////////////////////////////////////
// Google Shared Status
void CJabberProto::OnIqResultGoogleSharedStatus(HXML iqNode, CJabberIqInfo* pInfo) {
	m_bGoogleSharedStatus = (JABBER_IQ_TYPE_RESULT == pInfo->GetIqType());
	m_bGoogleSharedStatusLock = FALSE;
}
BOOL CJabberProto::OnIqSetGoogleSharedStatus(HXML iqNode, CJabberIqInfo* pInfo) {
	if (JABBER_IQ_TYPE_SET != pInfo->GetIqType()) return FALSE;
	if (m_bGoogleSharedStatusLock) return TRUE;
	int status;
	HXML query = xmlGetChild(iqNode, _T("query"));
	HXML node = xmlGetChild(query, _T("invisible"));
	if (0 == _tcsicmp(_T("true"), xmlGetAttrValue(node, _T("value"))))
		status = ID_STATUS_INVISIBLE;
	else {
		LPCTSTR txt = xmlGetText(xmlGetChild(query, _T("show")));
		if (txt && 0 == _tcsicmp(_T("dnd"), txt))
			status = ID_STATUS_DND;
		else if (m_iStatus == ID_STATUS_DND || m_iStatus == ID_STATUS_INVISIBLE)
			status = ID_STATUS_ONLINE;
		else
			status = m_iStatus;
	}
	if (status != m_iStatus) SetStatus(status);
	return TRUE;
}
void CJabberProto::SendIqGoogleSharedStatus(int status, const TCHAR *msg) {
	XmlNodeIq iq(m_iqManager.AddHandler(&CJabberProto::OnIqResultGoogleSharedStatus, JABBER_IQ_TYPE_SET));
	HXML query = iq << XQUERY(JABBER_FEAT_GTALK_SHARED_STATUS) << XATTR(_T("version"), _T("2"));
	query << XCHILD(_T("status"), msg);
	if (status == ID_STATUS_INVISIBLE) {
		query << XCHILD(_T("show"), _T("default"));
		query << XCHILD(_T("invisible")) << XATTR(_T("value"), _T("true"));
	} else {
		if (status == ID_STATUS_DND)
			query << XCHILD(_T("show"), _T("dnd"));
		else
			query << XCHILD(_T("show"), _T("default"));
		query << XCHILD(_T("invisible")) << XATTR(_T("value"), _T("false"));
	}
	m_bGoogleSharedStatusLock = TRUE;
	m_ThreadInfo->send(iq);
}
void __stdcall JabberStringAppend(char* *str, int *sizeAlloced, const char* fmt, ...)
{
	va_list vararg;
	char* p;
	size_t size, len;
	if (str == NULL) return;
	if (*str == NULL || *sizeAlloced<=0) {
		*sizeAlloced = 2048;
        size = 2048;
		*str = (char*)mir_alloc(size);
		len = 0;
	}
	else {
		len = strlen(*str);
		size = *sizeAlloced - strlen(*str);
	}
	p = *str + len;
	va_start(vararg, fmt);
	while (mir_vsnprintf(p, size, fmt, vararg) == -1) {
		size += 2048;
		(*sizeAlloced) += 2048;
		*str = (char*)mir_realloc(*str, *sizeAlloced);
		p = *str + len;
	}
	va_end(vararg);
}
///////////////////////////////////////////////////////////////////////////////
// JabberGetPacketID - converts the xml id attribute into an integer
int __stdcall JabberGetPacketID(HXML n)
{
	int result = -1;
	const TCHAR *str = xmlGetAttrValue(n, _T("id"));
	if (str)
		if ( !_tcsncmp(str, _T(JABBER_IQID), SIZEOF(JABBER_IQID)-1))
			result = _ttoi(str + SIZEOF(JABBER_IQID)-1);
	return result;
}
///////////////////////////////////////////////////////////////////////////////
// JabberGetClientJID - adds a resource postfix to a JID
TCHAR* CJabberProto::GetClientJID(HANDLE hContact, TCHAR *dest, size_t destLen)
{
	if (hContact == NULL)
		return NULL;
	ptrT jid( getTStringA(hContact, "jid"));
	return GetClientJID(jid, dest, destLen);
}
TCHAR* CJabberProto::GetClientJID(const TCHAR *jid, TCHAR *dest, size_t destLen)
{
	if (jid == NULL)
		return NULL;
	_tcsncpy_s(dest, destLen, jid, _TRUNCATE);
	TCHAR *p = _tcschr(dest, '/');
	mir_cslock lck(m_csLists);
	JABBER_LIST_ITEM *LI = ListGetItemPtr(LIST_ROSTER, jid);
	if (LI != NULL) {
		if (LI->arResources.getCount() == 1 && !lstrcmp(LI->arResources[0]->szCapsNode, _T("http://talk.google.com/xmpp/bot/caps"))) {
			if (p) *p = 0;
			return dest;
		}
		if (p == NULL) {
			JABBER_RESOURCE_STATUS *r = LI->getBestResource();
			if (r != NULL)
				mir_sntprintf(dest, destLen, _T("%s/%s"), jid, r->resourceName);
		}
	}
	return dest;
}
///////////////////////////////////////////////////////////////////////////////
// JabberStripJid - strips a resource postfix from a JID
TCHAR* __stdcall JabberStripJid(const TCHAR *jid, TCHAR *dest, size_t destLen)
{
	if (jid == NULL)
		*dest = 0;
	else {
		_tcsncpy_s(dest, destLen, jid, _TRUNCATE);
		TCHAR *p = _tcschr(dest, '/');
		if (p != NULL)
			*p = 0;
	}
	return dest;
}
/////////////////////////////////////////////////////////////////////////////////////////
// JabberGetPictureType - tries to autodetect the picture type from the buffer
LPCTSTR __stdcall JabberGetPictureType(HXML node, const char *picBuf)
{
	if (LPCTSTR ptszType = xmlGetText( xmlGetChild(node , "TYPE")))
		if ( !_tcscmp(ptszType, _T("image/jpeg")) ||
		     !_tcscmp(ptszType, _T("image/png"))  ||
		     !_tcscmp(ptszType, _T("image/gif"))  ||
		     !_tcscmp(ptszType, _T("image/bmp")))
			return ptszType;
	switch( ProtoGetBufferFormat(picBuf)) {
		case PA_FORMAT_GIF:	return _T("image/gif");
		case PA_FORMAT_BMP:  return _T("image/bmp");
		case PA_FORMAT_PNG:  return _T("image/png");
		case PA_FORMAT_JPEG: return _T("image/jpeg");
	}
	return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////////
// TStringPairs class members
TStringPairs::TStringPairs(char* buffer) :
	elems(NULL)
{
   TStringPairsElem tempElem[ 100 ];
	char* token = strtok(buffer, ",");
	for (numElems=0; token != NULL; numElems++) {
		char* p = strchr(token, '='), *p1;
		if (p == NULL)
			break;
		while(isspace(*token))
			token++;
		tempElem[ numElems ].name = rtrim(token);
		*p++ = 0;
		if ((p1 = strchr(p, '\"')) != NULL) {
			*p1 = 0;
			p = p1+1;
		}
		if ((p1 = strrchr(p, '\"')) != NULL)
			*p1 = 0;
		tempElem[ numElems ].value = rtrim(p);
		token = strtok(NULL, ",");
	}
	if (numElems) {
		elems = new TStringPairsElem[ numElems ];
		memcpy(elems, tempElem, sizeof(tempElem[0]) * numElems);
}	}
TStringPairs::~TStringPairs()
{
	delete[] elems;
}
const char* TStringPairs::operator[](const char* key) const
{
	for (int i = 0; i < numElems; i++)
		if ( !strcmp(elems[i].name, key))
			return elems[i].value;
	return "";
}
////////////////////////////////////////////////////////////////////////
// Manage combo boxes with recent item list
void CJabberProto::ComboLoadRecentStrings(HWND hwndDlg, UINT idcCombo, char *param, int recentCount)
{
	for (int i = 0; i < recentCount; i++) {
		DBVARIANT dbv;
		char setting[MAXMODULELABELLENGTH];
		mir_snprintf(setting, sizeof(setting), "%s%d", param, i);
		if ( !getTString(setting, &dbv)) {
			SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, (LPARAM)dbv.ptszVal);
			db_free(&dbv);
	}	}
	if ( !SendDlgItemMessage(hwndDlg, idcCombo, CB_GETCOUNT, 0, 0))
		SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, (LPARAM)_T(""));
}
void CJabberProto::ComboAddRecentString(HWND hwndDlg, UINT idcCombo, char *param, TCHAR *string, int recentCount)
{
	if ( !string || !*string)
		return;
	if (SendDlgItemMessage(hwndDlg, idcCombo, CB_FINDSTRING, (WPARAM)-1, (LPARAM)string) != CB_ERR)
		return;
	int id;
	SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, (LPARAM)string);
	if ((id = SendDlgItemMessage(hwndDlg, idcCombo, CB_FINDSTRING, (WPARAM)-1, (LPARAM)_T(""))) != CB_ERR)
		SendDlgItemMessage(hwndDlg, idcCombo, CB_DELETESTRING, id, 0);
	id = getByte(param, 0);
	char setting[MAXMODULELABELLENGTH];
	mir_snprintf(setting, sizeof(setting), "%s%d", param, id);
	setTString(setting, string);
	setByte(param, (id+1)%recentCount);
}
/////////////////////////////////////////////////////////////////////////////////////////
// jabber frame maintenance code
static VOID CALLBACK sttRebuildInfoFrameApcProc(void* param)
{
	CJabberProto *ppro = (CJabberProto *)param;
	if ( !ppro->m_pInfoFrame)
		return;
	ppro->m_pInfoFrame->LockUpdates();
	if ( !ppro->m_bJabberOnline) {
		ppro->m_pInfoFrame->RemoveInfoItem("$/PEP");
		ppro->m_pInfoFrame->RemoveInfoItem("$/Transports");
		ppro->m_pInfoFrame->UpdateInfoItem("$/JID", LoadSkinnedIconHandle(SKINICON_OTHER_USERDETAILS), TranslateT("Offline"));
	}
	else {
		ppro->m_pInfoFrame->UpdateInfoItem("$/JID", LoadSkinnedIconHandle(SKINICON_OTHER_USERDETAILS), ppro->m_szJabberJID);
		if ( !ppro->m_bPepSupported)
			ppro->m_pInfoFrame->RemoveInfoItem("$/PEP");
		else {
			ppro->m_pInfoFrame->RemoveInfoItem("$/PEP/");
			ppro->m_pInfoFrame->CreateInfoItem("$/PEP", false);
			ppro->m_pInfoFrame->UpdateInfoItem("$/PEP", ppro->GetIconHandle(IDI_PL_LIST_ANY), TranslateT("Advanced Status"));
			ppro->m_pInfoFrame->CreateInfoItem("$/PEP/mood", true);
			ppro->m_pInfoFrame->SetInfoItemCallback("$/PEP/mood", &CJabberProto::InfoFrame_OnUserMood);
			ppro->m_pInfoFrame->UpdateInfoItem("$/PEP/mood", LoadSkinnedIconHandle(SKINICON_OTHER_SMALLDOT), TranslateT("Set mood..."));
			ppro->m_pInfoFrame->CreateInfoItem("$/PEP/activity", true);
			ppro->m_pInfoFrame->SetInfoItemCallback("$/PEP/activity", &CJabberProto::InfoFrame_OnUserActivity);
			ppro->m_pInfoFrame->UpdateInfoItem("$/PEP/activity", LoadSkinnedIconHandle(SKINICON_OTHER_SMALLDOT), TranslateT("Set activity..."));
		}
		ppro->m_pInfoFrame->RemoveInfoItem("$/Transports/");
		ppro->m_pInfoFrame->CreateInfoItem("$/Transports", false);
		ppro->m_pInfoFrame->UpdateInfoItem("$/Transports", ppro->GetIconHandle(IDI_TRANSPORT), TranslateT("Transports"));
		JABBER_LIST_ITEM *item = NULL;
		LISTFOREACH(i, ppro, LIST_ROSTER)
		{
			if ((item=ppro->ListGetItemPtrFromIndex(i)) != NULL) {
				if (_tcschr(item->jid, '@') == NULL && _tcschr(item->jid, '/') == NULL && item->subscription!=SUB_NONE) {
					HANDLE hContact = ppro->HContactFromJID(item->jid);
					if (hContact == NULL) continue;
					char name[128];
					char *jid_copy = mir_t2a(item->jid);
					mir_snprintf(name, SIZEOF(name), "$/Transports/%s", jid_copy);
					ppro->m_pInfoFrame->CreateInfoItem(name, true, (LPARAM)hContact);
					ppro->m_pInfoFrame->UpdateInfoItem(name, ppro->GetIconHandle(IDI_TRANSPORTL), (TCHAR *)item->jid);
					ppro->m_pInfoFrame->SetInfoItemCallback(name, &CJabberProto::InfoFrame_OnTransport);
					mir_free(jid_copy);
			}	}
		}
	}
	ppro->m_pInfoFrame->Update();
}
void CJabberProto::RebuildInfoFrame()
{
	CallFunctionAsync(sttRebuildInfoFrameApcProc, this);
}
////////////////////////////////////////////////////////////////////////
// time2str & str2time
TCHAR* time2str(time_t _time, TCHAR *buf, size_t bufLen)
{
	struct tm* T = localtime(&_time);
	mir_sntprintf(buf, bufLen, _T("%04d-%02d-%02dT%02d:%02d:%02dZ"),
		T->tm_year+1900, T->tm_mon+1, T->tm_mday, T->tm_hour, T->tm_min, T->tm_sec);
	return buf;
}
time_t str2time(const TCHAR *buf)
{
	struct tm T = { 0 };
	if ( _stscanf(buf, _T("%04d-%02d-%02dT%02d:%02d:%02dZ"), &T.tm_year, &T.tm_mon, &T.tm_mday, &T.tm_hour, &T.tm_min, &T.tm_sec) != 6) {
		int boo;
		if ( _stscanf(buf, _T("%04d-%02d-%02dT%02d:%02d:%02d.%dZ"), &T.tm_year, &T.tm_mon, &T.tm_mday, &T.tm_hour, &T.tm_min, &T.tm_sec, &boo) != 7)
			return 0;
	}
	T.tm_year -= 1900;
	T.tm_mon--;
	return mktime(&T);
}
////////////////////////////////////////////////////////////////////////
// case-insensitive _tcsstr
const TCHAR *JabberStrIStr(const TCHAR *str, const TCHAR *substr)
{
	TCHAR *str_up = NEWTSTR_ALLOCA(str);
	TCHAR *substr_up = NEWTSTR_ALLOCA(substr);
	CharUpperBuff(str_up, lstrlen(str_up));
	CharUpperBuff(substr_up, lstrlen(substr_up));
	TCHAR *p = _tcsstr(str_up, substr_up);
	return p ? (str + (p - str_up)) : NULL;
}
////////////////////////////////////////////////////////////////////////
// clipboard processing
void JabberCopyText(HWND hwnd, TCHAR *text)
{
	if ( !hwnd || !text) return;
	OpenClipboard(hwnd);
	EmptyClipboard();
	HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, sizeof(TCHAR)*(lstrlen(text)+1));
	TCHAR *s = (TCHAR *)GlobalLock(hMem);
	lstrcpy(s, text);
	GlobalUnlock(hMem);
	SetClipboardData(CF_UNICODETEXT, hMem);
	CloseClipboard();
}
/////////////////////////////////////////////////////////////////////////////////////////
// One string entry dialog
struct JabberEnterStringParams
{
	CJabberProto *ppro;
	int type;
	TCHAR *caption;
	TCHAR *result;
	size_t resultLen;
	char *windowName;
	int recentCount;
	int timeout;
	int idcControl;
	int height;
};
static int sttEnterStringResizer(HWND, LPARAM, UTILRESIZECONTROL *urc)
{
	switch (urc->wId)
	{
	case IDC_TXT_MULTILINE:
	case IDC_TXT_COMBO:
	case IDC_TXT_RICHEDIT:
		return RD_ANCHORX_LEFT|RD_ANCHORY_TOP|RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT;
	case IDOK:
	case IDCANCEL:
		return RD_ANCHORX_RIGHT|RD_ANCHORY_BOTTOM;
	}
	return RD_ANCHORX_LEFT|RD_ANCHORY_TOP;
}
static INT_PTR CALLBACK sttEnterStringDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	JabberEnterStringParams *params = (JabberEnterStringParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	switch (msg) {
	case WM_INITDIALOG:
	{
		//SetWindowPos(hwndDlg, HWND_TOPMOST ,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
		TranslateDialogDefault(hwndDlg);
		SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadSkinnedIconBig(SKINICON_OTHER_RENAME));
		SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadSkinnedIcon(SKINICON_OTHER_RENAME));
		JabberEnterStringParams *params = (JabberEnterStringParams *)lParam;
		SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)params);
		SetWindowText(hwndDlg, params->caption);
		RECT rc; GetWindowRect(hwndDlg, &rc);
		switch (params->type)
		{
			case JES_PASSWORD:
			{
				params->idcControl = IDC_TXT_PASSWORD;
				params->height = rc.bottom-rc.top;
				break;
			}
			case JES_MULTINE:
			{
				params->idcControl = IDC_TXT_MULTILINE;
				params->height = 0;
				rc.bottom += (rc.bottom-rc.top) * 2;
				SetWindowPos(hwndDlg, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top, SWP_NOMOVE|SWP_NOREPOSITION);
				break;
			}
			case JES_COMBO:
			{
				params->idcControl = IDC_TXT_COMBO;
				params->height = rc.bottom-rc.top;
				if (params->windowName && params->recentCount)
					params->ppro->ComboLoadRecentStrings(hwndDlg, IDC_TXT_COMBO, params->windowName, params->recentCount);
				break;
			}
			case JES_RICHEDIT:
			{
				params->idcControl = IDC_TXT_RICHEDIT;
				SendDlgItemMessage(hwndDlg, IDC_TXT_RICHEDIT, EM_AUTOURLDETECT, TRUE, 0);
				SendDlgItemMessage(hwndDlg, IDC_TXT_RICHEDIT, EM_SETEVENTMASK, 0, ENM_LINK);
				params->height = 0;
				rc.bottom += (rc.bottom-rc.top) * 2;
				SetWindowPos(hwndDlg, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top, SWP_NOMOVE|SWP_NOREPOSITION);
				break;
			}
		}
		ShowWindow(GetDlgItem(hwndDlg, params->idcControl), SW_SHOW);
		SetDlgItemText(hwndDlg, params->idcControl, params->result);
		if (params->windowName)
			Utils_RestoreWindowPosition(hwndDlg, NULL, params->ppro->m_szModuleName, params->windowName);
		SetTimer(hwndDlg, 1000, 50, NULL);
		if (params->timeout > 0)
		{
			SetTimer(hwndDlg, 1001, 1000, NULL);
			TCHAR buf[128];
			mir_sntprintf(buf, SIZEOF(buf), TranslateT("OK (%d)"), params->timeout);
			SetDlgItemText(hwndDlg, IDOK, buf);
		}
		return TRUE;
	}
	case WM_DESTROY:
		WindowFreeIcon(hwndDlg);
		break;
	case WM_TIMER:
	{
		switch (wParam)
		{
			case 1000:
				KillTimer(hwndDlg,1000);
				EnableWindow(GetParent(hwndDlg), TRUE);
				return TRUE;
			case 1001:
			{
				TCHAR buf[128];
				mir_sntprintf(buf, SIZEOF(buf), TranslateT("OK (%d)"), --params->timeout);
				SetDlgItemText(hwndDlg, IDOK, buf);
				if (params->timeout < 0)
				{
					KillTimer(hwndDlg, 1001);
					UIEmulateBtnClick(hwndDlg, IDOK);
				}
				return TRUE;
			}
		}
	}
	case WM_SIZE:
	{
		UTILRESIZEDIALOG urd = {0};
		urd.cbSize = sizeof(urd);
		urd.hInstance = hInst;
		urd.hwndDlg = hwndDlg;
		urd.lpTemplate = MAKEINTRESOURCEA(IDD_GROUPCHAT_INPUT);
		urd.pfnResizer = sttEnterStringResizer;
		CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd);
		break;
	}
	case WM_GETMINMAXINFO:
	{
		LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam;
		if (params && params->height)
			lpmmi->ptMaxSize.y = lpmmi->ptMaxTrackSize.y = params->height;
		break;
	}
	case WM_NOTIFY:
	{
		ENLINK *param = (ENLINK *)lParam;
		if (param->nmhdr.idFrom != IDC_TXT_RICHEDIT) break;
		if (param->nmhdr.code != EN_LINK) break;
		if (param->msg != WM_LBUTTONUP) break;
		CHARRANGE sel;
		SendMessage(param->nmhdr.hwndFrom, EM_EXGETSEL, 0, (LPARAM) & sel);
		if (sel.cpMin != sel.cpMax) break; // allow link selection
		TEXTRANGE tr;
		tr.chrg = param->chrg;
		tr.lpstrText = (TCHAR *)mir_alloc(sizeof(TCHAR)*(tr.chrg.cpMax - tr.chrg.cpMin + 2));
        SendMessage(param->nmhdr.hwndFrom, EM_GETTEXTRANGE, 0, (LPARAM) & tr);
		char *tmp = mir_t2a(tr.lpstrText);
		CallService(MS_UTILS_OPENURL, 1, (LPARAM)tmp);
        mir_free(tmp);
        mir_free(tr.lpstrText);
        return TRUE;
	}
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			GetDlgItemText(hwndDlg, params->idcControl, params->result, (int)params->resultLen);
			params->result[ params->resultLen-1 ] = 0;
			if ((params->type == JES_COMBO) && params->windowName && params->recentCount)
				params->ppro->ComboAddRecentString(hwndDlg, IDC_TXT_COMBO, params->windowName, params->result, params->recentCount);
			if (params->windowName)
				Utils_SaveWindowPosition(hwndDlg, NULL, params->ppro->m_szModuleName, params->windowName);
			EndDialog(hwndDlg, 1);
			break;
		case IDCANCEL:
			if (params->windowName)
				Utils_SaveWindowPosition(hwndDlg, NULL, params->ppro->m_szModuleName, params->windowName);
			EndDialog(hwndDlg, 0);
			break;
		case IDC_TXT_MULTILINE:
		case IDC_TXT_RICHEDIT:
			if ((HIWORD(wParam) != EN_SETFOCUS) && (HIWORD(wParam) != EN_KILLFOCUS))
			{
				SetDlgItemText(hwndDlg, IDOK, TranslateT("OK"));
				KillTimer(hwndDlg, 1001);
			}
			break;
		case IDC_TXT_COMBO:
			if ((HIWORD(wParam) != CBN_SETFOCUS) && (HIWORD(wParam) != CBN_KILLFOCUS))
			{
				SetDlgItemText(hwndDlg, IDOK, TranslateT("OK"));
				KillTimer(hwndDlg, 1001);
			}
			break;
	}	}
	return FALSE;
}
BOOL CJabberProto::EnterString(TCHAR *result, size_t resultLen, TCHAR *caption, int type, char *windowName, int recentCount, int timeout)
{
	bool free_caption = false;
	if ( !caption || (caption==result))
	{
		free_caption = true;
		caption = mir_tstrdup(result);
		result[ 0 ] = _T('\0');
	}
	JabberEnterStringParams params = { this, type, caption, result, resultLen, windowName, recentCount, timeout };
	BOOL bRetVal = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_GROUPCHAT_INPUT), GetForegroundWindow(), sttEnterStringDlgProc, LPARAM(¶ms));
	if (free_caption) mir_free(caption);
	return bRetVal;
}
// XEP-0203 delay support
bool JabberReadXep203delay(HXML node, time_t &msgTime)
{
	HXML n = xmlGetChildByTag(node, "delay", "xmlns", _T("urn:xmpp:delay"));
	if (n == NULL)
		return false;
	const TCHAR *ptszTimeStamp = xmlGetAttrValue(n, _T("stamp"));
	if (ptszTimeStamp == NULL)
		return false;
	// skip '-' chars
	TCHAR *szStamp = NEWTSTR_ALLOCA(ptszTimeStamp);
	int si = 0, sj = 0;
	while (1) {
		if (szStamp[si] == _T('-'))
			si++;
		else
			if ( !(szStamp[sj++] = szStamp[si++]))
				break;
	};
	msgTime = JabberIsoToUnixTime(szStamp);
	return msgTime != 0;
}
////////////////////////////////////////////////////////////////////////
// Premultiply bitmap channels for 32-bit bitmaps
void JabberBitmapPremultiplyChannels(HBITMAP hBitmap)
{
	BITMAP bmp;
	DWORD dwLen;
	BYTE *p;
	int x, y;
	GetObject(hBitmap, sizeof(bmp), &bmp);
	if (bmp.bmBitsPixel != 32)
		return;
	dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
	p = (BYTE *)malloc(dwLen);
	if (p == NULL)
		return;
	memset(p, 0, dwLen);
	GetBitmapBits(hBitmap, dwLen, p);
	for (y = 0; y < bmp.bmHeight; ++y)
	{
		BYTE *px = p + bmp.bmWidth * 4 * y;
		for (x = 0; x < bmp.bmWidth; ++x)
		{
			px[0] = px[0] * px[3] / 255;
			px[1] = px[1] * px[3] / 255;
			px[2] = px[2] * px[3] / 255;
			px += 4;
		}
	}
	SetBitmapBits(hBitmap, dwLen, p);
	free(p);
}
// Last resource map
void CJabberProto::CleanLastResourceMap()
{
	EnterCriticalSection(&m_csLastResourceMap);
	m_dwResourceMapPointer = 0;
	ZeroMemory(m_ulpResourceToDbEventMap, sizeof(m_ulpResourceToDbEventMap));
	while (m_pLastResourceList) {
		void *pNext = ((void **)m_pLastResourceList)[ 0 ];
		mir_free(m_pLastResourceList);
		m_pLastResourceList = pNext;
	}
	LeaveCriticalSection(&m_csLastResourceMap);
}
// lock CS before use
BOOL CJabberProto::IsLastResourceExists(void *pResource)
{
	if ( !pResource)
		return FALSE;
	void *pOurResource = m_pLastResourceList;
	while (pOurResource) {
		if (pOurResource == pResource)
			return TRUE;
		pOurResource = ((void **)pOurResource)[ 0 ];
	}
	return FALSE;
}
// lock CS before use
void* CJabberProto::AddToLastResourceMap(LPCTSTR szFullJid)
{
	// detach resource from full jid
	const TCHAR *szResource = _tcschr(szFullJid, '/');
	if (szResource == NULL)
		return NULL;
	if (*++szResource == '\0')
		return NULL;
	DWORD dwResourceCount = 0;
	void *pNewTailResource = NULL;
	void *pOurResource = m_pLastResourceList;
	while (pOurResource) {
		dwResourceCount++;
		if ( !_tcscmp((TCHAR *)((BYTE *)pOurResource + sizeof(void *)), szResource))
			return pOurResource;
		void *pTmp = ((void **)pOurResource)[ 0 ];
		if (pTmp && !(((void **)pTmp)[ 0 ]))
			pNewTailResource = pOurResource;
		pOurResource = pTmp;
	}
	if (pNewTailResource && (dwResourceCount > (SIZEOF(m_ulpResourceToDbEventMap) / 2))) {
		void *pTmp = ((void **)pNewTailResource)[ 0 ];
		((void **)pNewTailResource)[ 0 ] = NULL;
		mir_free(pTmp);
	}
	void *pNewResource = mir_alloc(sizeof(void *) + sizeof(TCHAR) * (_tcslen(szResource) + 1));
	if ( !pNewResource)
		return NULL;
	((void **)pNewResource)[ 0 ] = m_pLastResourceList;
	_tcscpy((TCHAR *)((BYTE *)pNewResource + sizeof(void *)), szResource);
	m_pLastResourceList = pNewResource;
	return pNewResource;
}
// lock CS before use
TCHAR* CJabberProto::FindLastResourceByDbEvent(HANDLE hDbEvent)
{
	for (int i = 0; i < SIZEOF(m_ulpResourceToDbEventMap); i += 2) {
		if (m_ulpResourceToDbEventMap[ i ] == (ULONG_PTR)hDbEvent) {
			TCHAR *szRetVal = (TCHAR *)(m_ulpResourceToDbEventMap[ i + 1 ] + sizeof(void *));
			m_ulpResourceToDbEventMap[ i ] = 0;
			m_ulpResourceToDbEventMap[ i + 1 ] = 0;
			return szRetVal;
		}
	}
	return NULL;
}
BOOL CJabberProto::IsMyOwnJID(LPCTSTR szJID)
{
	if ( !m_ThreadInfo)
		return FALSE;
	TCHAR *szFrom = JabberPrepareJid(szJID);
	if ( !szFrom)
		return FALSE;
	TCHAR *szTo = JabberPrepareJid(m_ThreadInfo->fullJID);
	if ( !szTo) {
		mir_free(szFrom);
		return FALSE;
	}
	TCHAR *pDelimiter = _tcschr(szFrom, _T('/'));
	if (pDelimiter) *pDelimiter = _T('\0');
	pDelimiter = _tcschr(szTo, _T('/'));
	if (pDelimiter) *pDelimiter = _T('\0');
	BOOL bRetVal = _tcscmp(szFrom, szTo) == 0;
	mir_free(szFrom);
	mir_free(szTo);
	return bRetVal;
}
void __cdecl CJabberProto::LoadHttpAvatars(void* param)
{
	OBJLIST &avs = *(OBJLIST*)param;
	HANDLE hHttpCon = NULL;
	for (int i = 0; i < avs.getCount(); i++) {
		NETLIBHTTPREQUEST nlhr = {0};
		nlhr.cbSize = sizeof(nlhr);
		nlhr.requestType = REQUEST_GET;
		nlhr.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT;
		nlhr.szUrl = avs[i].Url;
		nlhr.nlc = hHttpCon;
		NETLIBHTTPREQUEST * res = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)&nlhr);
		if (res) {
			hHttpCon = res->nlc;
			if (res->resultCode == 200 && res->dataLength) {
				int pictureType = ProtoGetBufferFormat(res->pData);
				if (pictureType != PA_FORMAT_UNKNOWN) {
					PROTO_AVATAR_INFORMATIONT AI;
					AI.cbSize = sizeof(AI);
					AI.format = pictureType;
					AI.hContact = avs[i].hContact;
					if (getByte(AI.hContact, "AvatarType", PA_FORMAT_UNKNOWN) != (unsigned char)pictureType) {
						TCHAR tszFileName[ MAX_PATH ];
						GetAvatarFileName(AI.hContact, tszFileName, SIZEOF(tszFileName));
						DeleteFile(tszFileName);
					}
					setByte(AI.hContact, "AvatarType", pictureType);
					char buffer[ 41 ];
					BYTE digest[20];
					mir_sha1_ctx sha;
					mir_sha1_init(&sha);
					mir_sha1_append(&sha, (BYTE*)res->pData, res->dataLength);
					mir_sha1_finish(&sha, digest);
					bin2hex(digest, sizeof(digest), buffer);
					ptrA cmpsha( getStringA(AI.hContact, "AvatarSaved"));
					if (cmpsha == NULL || strnicmp(cmpsha, buffer, sizeof(buffer))) {
						TCHAR tszFileName[ MAX_PATH ];
						GetAvatarFileName(AI.hContact, tszFileName, SIZEOF(tszFileName));
						_tcsncpy(AI.filename, tszFileName, SIZEOF(AI.filename));
						FILE* out = _tfopen(tszFileName, _T("wb"));
						if (out != NULL) {
							fwrite(res->pData, res->dataLength, 1, out);
							fclose(out);
							setString(AI.hContact, "AvatarSaved", buffer);
							ProtoBroadcastAck(AI.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &AI, 0);
							Log("Broadcast new avatar: %s",AI.filename);
						}
						else ProtoBroadcastAck(AI.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, &AI, 0);
					}
				}
			}
			CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)res);
		}
		else hHttpCon = NULL;
	}
	delete &avs;
	if (hHttpCon)
		Netlib_CloseHandle(hHttpCon);
}