/*
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"
int CJabberProto::SerialNext(void)
{
return ::InterlockedIncrement(&m_nSerial);
}
///////////////////////////////////////////////////////////////////////////////
// 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)) {
ptrT dbJid( getTStringA(hContact, "ChatRoomID"));
if (dbJid == NULL)
if ((dbJid = getTStringA(hContact, "jid")) == NULL)
continue;
if (!lstrcmpi(jid, dbJid) && 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);
ptrT dbJid( getTStringA(hContact, bIsChat ? "ChatRoomID" : "jid"));
if (dbJid != NULL) {
int result;
if (item != NULL)
result = lstrcmpi(jid, dbJid);
else {
if (bStripResource == 3) {
if (bIsChat)
result = lstrcmpi(jid, dbJid); // for chat room we have to have full contact matched
else if (TRUE)
result = _tcsnicmp(jid, dbJid, _tcslen(dbJid));
else
result = JabberCompareJids(jid, dbJid);
}
// most probably it should just look full matching contact
else
result = lstrcmpi(jid, dbJid);
}
if (result == 0)
return hContact;
}
}
return NULL;
}
TCHAR* __stdcall JabberNickFromJID(const TCHAR *jid)
{
if (jid == NULL)
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;
}
pResourceStatus CJabberProto::ResourceInfoFromJID(const TCHAR *jid)
{
if (jid == NULL)
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->getTemp();
return item->findResource(p+1);
}
TCHAR* JabberPrepareJid(LPCTSTR jid)
{
if (jid == NULL) 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 == NULL)
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);
LIST arrExtCaps(5);
if (bSecureIM)
arrExtCaps.insert( _T(JABBER_EXT_SECUREIM));
if (bMirOTR)
arrExtCaps.insert( _T(JABBER_EXT_MIROTR));
if (bNewGPG)
arrExtCaps.insert( _T(JABBER_EXT_NEWGPG));
if (bPlatform)
arrExtCaps.insert( _T(JABBER_EXT_PLATFORMX64));
else
arrExtCaps.insert( _T(JABBER_EXT_PLATFORMX86));
if (m_options.EnableRemoteControl)
arrExtCaps.insert( _T(JABBER_EXT_COMMANDS));
if (m_options.EnableUserMood)
arrExtCaps.insert( _T(JABBER_EXT_USER_MOOD));
if (m_options.EnableUserTune)
arrExtCaps.insert( _T(JABBER_EXT_USER_TUNE));
if (m_options.EnableUserActivity)
arrExtCaps.insert( _T(JABBER_EXT_USER_ACTIVITY));
if (m_options.AcceptNotes)
arrExtCaps.insert( _T(JABBER_EXT_MIR_NOTES));
NotifyFastHook(hExtListInit, (WPARAM)&arrExtCaps, (LPARAM)(IJabberInterface*)this);
// add features enabled through IJabberNetInterface::AddFeatures()
for (int i=0; i < m_lstJabberFeatCapPairsDynamic.getCount(); i++)
if (m_uEnabledFeatCapsDynamic & m_lstJabberFeatCapPairsDynamic[i]->jcbCap)
arrExtCaps.insert(m_lstJabberFeatCapPairsDynamic[i]->szExt);
if (arrExtCaps.getCount()) {
CMString szExtCaps = arrExtCaps[0];
for (int i=1; i < arrExtCaps.getCount(); i++) {
szExtCaps.AppendChar(' ');
szExtCaps += arrExtCaps[i];
}
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"));
}
{
mir_cslock lck(m_csModeMsgMutex);
switch (status) {
case ID_STATUS_ONLINE:
if (!msg) msg = m_modeMsgs.szOnline;
break;
case ID_STATUS_INVISIBLE:
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);
}
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);
} } } }
///////////////////////////////////////////////////////////////////////////////
// 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]->m_tszCapsNode, _T("http://talk.google.com/xmpp/bot/caps"))) {
if (p) *p = 0;
return dest;
}
if (p == NULL) {
pResourceStatus r( LI->getBestResource());
if (r != NULL)
mir_sntprintf(dest, destLen, _T("%s/%s"), jid, r->m_tszResourceName);
}
}
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++) {
char setting[MAXMODULELABELLENGTH];
mir_snprintf(setting, sizeof(setting), "%s%d", param, i);
ptrT tszRecent( getTStringA(setting));
if (tszRecent != NULL)
SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, tszRecent);
}
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:
TranslateDialogDefault(hwndDlg);
SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadSkinnedIconBig(SKINICON_OTHER_RENAME));
SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadSkinnedIcon(SKINICON_OTHER_RENAME));
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);
break;
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;
}
BOOL CJabberProto::IsMyOwnJID(LPCTSTR szJID)
{
if (m_ThreadInfo == NULL)
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);
debugLogA("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);
}