/*
Jabber Protocol Plugin for Miranda NG
Copyright (c) 2002-04 Santithorn Bunchua
Copyright (c) 2005-12 George Hazan
Copyright (C) 2012-19 Miranda NG team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdafx.h"
#include "jabber_caps.h"
int CJabberProto::SerialNext(void)
{
return ::InterlockedIncrement(&m_nSerial);
}
///////////////////////////////////////////////////////////////////////////////
// JabberChatRoomHContactFromJID - looks for the char room MCONTACT with required JID
MCONTACT CJabberProto::ChatRoomHContactFromJID(const char *jid)
{
JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_CHATROOM, jid);
if (item != nullptr && item->hContact)
return item->hContact;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// JabberHContactFromJID - looks for the MCONTACT with required JID
MCONTACT CJabberProto::HContactFromJID(const char *jid, bool bStripResource)
{
if (jid == nullptr)
return 0;
JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_ROSTER, jid);
if (item != nullptr && item->hContact)
return item->hContact;
if (bStripResource) {
char szJid[JABBER_MAX_JID_LEN];
JabberStripJid(jid, szJid, _countof(szJid));
item = ListGetItemPtr(LIST_ROSTER, szJid);
if (item != nullptr && item->hContact)
return item->hContact;
}
return 0;
}
CMStringA MakeJid(const char *jid, const char *resource)
{
CMStringA ret(jid);
if (resource != nullptr) {
ret.AppendChar('/');
ret.Append(resource);
}
return ret;
}
char* JabberNickFromJID(const char *jid)
{
if (jid == nullptr)
return mir_strdup("");
const char *p = strchr(jid, '@');
if (p == nullptr)
p = strchr(jid, '/');
return (p != nullptr) ? mir_strndup(jid, p - jid) : mir_strdup(jid);
}
pResourceStatus CJabberProto::ResourceInfoFromJID(const char *jid)
{
if (jid == nullptr)
return nullptr;
const char *p = strchr(jid, '/');
JABBER_LIST_ITEM *item = nullptr;
if (p) {
char szJid[JABBER_MAX_JID_LEN];
JabberStripJid(jid, szJid, _countof(szJid));
item = ListGetItemPtr(LIST_CHATROOM, szJid);
}
if (item == nullptr)
item = ListGetItemPtr(LIST_VCARD_TEMP, jid);
if (item == nullptr)
item = ListGetItemPtr(LIST_ROSTER, jid);
if (item == nullptr)
return nullptr;
if (p == nullptr)
return item->getTemp();
return item->findResource(p + 1);
}
char* JabberPrepareJid(const char *jid)
{
if (jid == nullptr)
return nullptr;
char *szNewJid = mir_strdup(jid);
if (!szNewJid)
return nullptr;
char *pDelimiter = strchr(szNewJid, '/');
if (pDelimiter)
*pDelimiter = 0;
CharLowerA(szNewJid);
if (pDelimiter)
*pDelimiter = '/';
return szNewJid;
}
char* JabberSha1(const char *str, JabberShaStrBuf buf)
{
BYTE digest[MIR_SHA1_HASH_SIZE];
mir_sha1_ctx sha;
mir_sha1_init(&sha);
mir_sha1_append(&sha, (BYTE*)str, (int)mir_strlen(str));
mir_sha1_finish(&sha, digest);
bin2hex(digest, sizeof(digest), buf);
return buf;
}
wchar_t* JabberStrFixLines(const wchar_t *str)
{
if (str == nullptr)
return nullptr;
const wchar_t *p;
int add = 0;
bool prev_r = false;
bool prev_n = false;
for (p = str; p && *p; ++p)
if (*p == '\r' || *p == '\n')
++add;
wchar_t *buf = (wchar_t *)mir_alloc((mir_wstrlen(str) + add + 1) * sizeof(wchar_t));
wchar_t *res = buf;
for (p = str; p && *p; ++p) {
if (*p == '\n' && !prev_r)
*res++ = '\r';
if (*p != '\r' && *p != '\n' && prev_r)
*res++ = '\n';
*res++ = *p;
prev_r = *p == '\r';
prev_n = *p == '\n';
}
*res = 0;
return buf;
}
void JabberHttpUrlDecode(wchar_t *str)
{
wchar_t *p, *q;
unsigned int code;
if (str == nullptr) return;
for (p = q = (wchar_t*)str; *p != '\0'; p++, q++) {
if (*p == '%' && *(p + 1) != '\0' && isxdigit(*(p + 1)) && *(p + 2) != '\0' && isxdigit(*(p + 2))) {
swscanf((wchar_t*)p + 1, L"%2x", &code);
*q = (unsigned char)code;
p += 2;
}
else *q = *p;
}
*q = '\0';
}
int 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;
wchar_t *str;
}
static JabberErrorCodeToStrMapping[] = {
{ JABBER_ERROR_REDIRECT, LPGENW("Redirect") },
{ JABBER_ERROR_BAD_REQUEST, LPGENW("Bad request") },
{ JABBER_ERROR_UNAUTHORIZED, LPGENW("Unauthorized") },
{ JABBER_ERROR_PAYMENT_REQUIRED, LPGENW("Payment required") },
{ JABBER_ERROR_FORBIDDEN, LPGENW("Forbidden") },
{ JABBER_ERROR_NOT_FOUND, LPGENW("Not found") },
{ JABBER_ERROR_NOT_ALLOWED, LPGENW("Not allowed") },
{ JABBER_ERROR_NOT_ACCEPTABLE, LPGENW("Not acceptable") },
{ JABBER_ERROR_REGISTRATION_REQUIRED, LPGENW("Registration required") },
{ JABBER_ERROR_REQUEST_TIMEOUT, LPGENW("Request timeout") },
{ JABBER_ERROR_CONFLICT, LPGENW("Conflict") },
{ JABBER_ERROR_INTERNAL_SERVER_ERROR, LPGENW("Internal server error") },
{ JABBER_ERROR_NOT_IMPLEMENTED, LPGENW("Not implemented") },
{ JABBER_ERROR_REMOTE_SERVER_ERROR, LPGENW("Remote server error") },
{ JABBER_ERROR_SERVICE_UNAVAILABLE, LPGENW("Service unavailable") },
{ JABBER_ERROR_REMOTE_SERVER_TIMEOUT, LPGENW("Remote server timeout") },
{ -1, LPGENW("Unknown error") }
};
wchar_t* JabberErrorStr(int errorCode)
{
int i;
for (i = 0; JabberErrorCodeToStrMapping[i].code != -1 && JabberErrorCodeToStrMapping[i].code != errorCode; i++);
return JabberErrorCodeToStrMapping[i].str;
}
CMStringW JabberErrorMsg(const TiXmlElement *errorNode, int *pErrorCode)
{
CMStringW ret;
if (errorNode == nullptr) {
if (pErrorCode)
*pErrorCode = -1;
ret.Format(L"%s -1: %s", TranslateT("Error"), TranslateT("Unknown error message"));
return ret;
}
if (auto *pChild = XmlFirstChild(errorNode, "error"))
errorNode = pChild;
int errorCode = errorNode->IntAttribute("code");
const char *str = errorNode->GetText();
if (str == nullptr)
str = XmlGetChildText(errorNode, "text");
if (str == nullptr) {
for (auto *c : TiXmlEnum(errorNode)) {
const char *attr = XmlGetAttr(c, "xmlns");
if (attr && !mir_strcmp(attr, "urn:ietf:params:xml:ns:xmpp-stanzas")) {
str = c->Name();
break;
}
}
}
if (str != nullptr)
ret.Format(L"%s %d: %s\r\n%s", TranslateT("Error"), errorCode, TranslateW(JabberErrorStr(errorCode)), Utf2T(str).get());
else
ret.Format(L"%s %d: %s", TranslateT("Error"), errorCode, TranslateW(JabberErrorStr(errorCode)));
if (pErrorCode)
*pErrorCode = errorCode;
return ret;
}
void CJabberProto::SendVisibleInvisiblePresence(bool invisible)
{
if (!m_bJabberOnline) return;
LISTFOREACH(i, this, LIST_ROSTER)
{
JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i);
if (item == nullptr)
continue;
MCONTACT hContact = HContactFromJID(item->jid);
if (hContact == 0)
continue;
WORD apparentMode = getWord(hContact, "ApparentMode", 0);
if (invisible && apparentMode == ID_STATUS_OFFLINE)
m_ThreadInfo->send(XmlNode("presence") << XATTR("to", item->jid) << XATTR("type", "invisible"));
else if (!invisible && apparentMode == ID_STATUS_ONLINE)
SendPresenceTo(m_iStatus, item->jid);
}
}
time_t JabberIsoToUnixTime(const char *stamp)
{
wchar_t date[9];
int i, y;
if (stamp == nullptr)
return 0;
auto *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 0;
struct tm timestamp;
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 (sscanf(p, "%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
time_t t = mktime(×tamp);
_tzset();
t -= _timezone;
return (t >= 0) ? t : 0;
}
void CJabberProto::SendPresenceTo(int status, const char *to, const TiXmlElement *extra, const char *msg)
{
if (!m_bJabberOnline) return;
// Send update for status (we won't handle ID_STATUS_OFFLINE here)
int iPriority = getDword("Priority", 0);
UpdatePriorityMenu(iPriority);
char szPriority[40];
itoa(iPriority, szPriority, 10);
XmlNode p("presence"); p << XCHILD("priority", szPriority);
if (to != nullptr)
p << XATTR("to", to);
if (extra)
p += extra;
// XEP-0115:Entity Capabilities
if (m_bAllowVersionRequests) {
TiXmlElement *c = p << XCHILDNS("c", JABBER_FEAT_ENTITY_CAPS) << XATTR("hash", "sha-1")
<< XATTR("node", JABBER_CAPS_MIRANDA_NODE) << XATTR("ver", m_clientCapsManager.GetFeaturesCrc());
LIST arrExtCaps(5);
if (bSecureIM)
arrExtCaps.insert(JABBER_EXT_SECUREIM);
if (bMirOTR)
arrExtCaps.insert(JABBER_EXT_MIROTR);
if (bNewGPG)
arrExtCaps.insert(JABBER_EXT_NEWGPG);
if(m_bUseOMEMO)
arrExtCaps.insert(JABBER_EXT_OMEMO);
if (bPlatform)
arrExtCaps.insert(JABBER_EXT_PLATFORMX64);
else
arrExtCaps.insert(JABBER_EXT_PLATFORMX86);
if (m_bEnableRemoteControl)
arrExtCaps.insert(JABBER_EXT_COMMANDS);
if (m_bEnableUserMood)
arrExtCaps.insert(JABBER_EXT_USER_MOOD);
if (m_bEnableUserTune)
arrExtCaps.insert(JABBER_EXT_USER_TUNE);
if (m_bEnableUserActivity)
arrExtCaps.insert(JABBER_EXT_USER_ACTIVITY);
if (m_bAcceptNotes)
arrExtCaps.insert(JABBER_EXT_MIR_NOTES);
NotifyFastHook(hExtListInit, (WPARAM)&arrExtCaps, (LPARAM)(IJabberInterface*)this);
// add features enabled through IJabberNetInterface::AddFeatures()
for (auto &it : m_lstJabberFeatCapPairsDynamic)
if (m_uEnabledFeatCapsDynamic & it->jcbCap)
arrExtCaps.insert(it->szExt);
if (arrExtCaps.getCount()) {
CMStringW szExtCaps = arrExtCaps[0];
for (int i = 1; i < arrExtCaps.getCount(); i++) {
szExtCaps.AppendChar(' ');
szExtCaps += arrExtCaps[i];
}
c->SetAttribute("ext", szExtCaps);
}
}
if (m_tmJabberIdleStartTime)
p << XQUERY(JABBER_FEAT_LAST_ACTIVITY) << XATTRI("seconds", time(0) - m_tmJabberIdleStartTime);
if (m_bEnableAvatars) {
TiXmlElement *x = p << XCHILDNS("x", "vcard-temp:x:update");
ptrA vcardHash(getUStringA("VCardHash"));
if (vcardHash != nullptr)
x << XATTR("vcard", vcardHash);
ptrA hashValue(getUStringA("AvatarHash"));
if (hashValue != nullptr) // XEP-0153: vCard-Based Avatars
x << XCHILD("photo", hashValue);
else
x << XCHILD("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("type", "invisible");
break;
case ID_STATUS_AWAY:
case ID_STATUS_ONTHEPHONE:
case ID_STATUS_OUTTOLUNCH:
p << XCHILD("show", "away");
if (!msg) msg = m_modeMsgs.szAway;
break;
case ID_STATUS_NA:
p << XCHILD("show", "xa");
if (!msg) msg = m_modeMsgs.szNa;
break;
case ID_STATUS_DND:
case ID_STATUS_OCCUPIED:
p << XCHILD("show", "dnd");
if (!msg) msg = m_modeMsgs.szDnd;
break;
case ID_STATUS_FREECHAT:
p << XCHILD("show", "chat");
if (!msg) msg = m_modeMsgs.szFreechat;
break;
default: // Should not reach here
break;
}
if (msg)
p << XCHILD("status", msg);
}
m_ThreadInfo->send(p);
}
void CJabberProto::SendPresence(int status, bool bSendToAll)
{
SendPresenceTo(status, nullptr);
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 != nullptr && item->nick != nullptr)
SendPresenceTo(status == ID_STATUS_INVISIBLE ? ID_STATUS_ONLINE : status, MakeJid(item->jid, item->nick));
}
}
}
///////////////////////////////////////////////////////////////////////////////
// JabberGetPacketID - converts the xml id attribute into an integer
int JabberGetPacketID(const TiXmlElement *n)
{
const char *str = XmlGetAttr(n, "id");
if (str)
if (!strncmp(str, JABBER_IQID, _countof(JABBER_IQID) - 1))
return atoi(str + _countof(JABBER_IQID) - 1);
return -1;
}
char* JabberId2string(int id)
{
char text[100];
mir_snprintf(text, JABBER_IQID "%d", id);
return mir_strdup(text);
}
///////////////////////////////////////////////////////////////////////////////
// JabberGetClientJID - adds a resource postfix to a JID
char* CJabberProto::GetClientJID(MCONTACT hContact, char *dest, size_t destLen)
{
if (hContact == 0)
return nullptr;
ptrA jid(getUStringA(hContact, "jid"));
return GetClientJID(jid, dest, destLen);
}
char* CJabberProto::GetClientJID(const char *jid, char *dest, size_t destLen)
{
if (jid == nullptr)
return nullptr;
strncpy_s(dest, destLen, jid, _TRUNCATE);
char *p = strchr(dest, '/');
mir_cslock lck(m_csLists);
JABBER_LIST_ITEM *LI = ListGetItemPtr(LIST_ROSTER, jid);
if (LI != nullptr) {
if (p == nullptr) {
pResourceStatus r(LI->getBestResource());
if (r != nullptr)
strncpy_s(dest, destLen, MakeJid(jid, r->m_szResourceName), _TRUNCATE);
}
}
return dest;
}
///////////////////////////////////////////////////////////////////////////////
// JabberStripJid - strips a resource postfix from a JID
char* JabberStripJid(const char *jid, char *dest, size_t destLen)
{
if (jid == nullptr)
*dest = 0;
else {
strncpy_s(dest, destLen, jid, _TRUNCATE);
char *p = strchr(dest, '/');
if (p != nullptr)
*p = 0;
}
return dest;
}
/////////////////////////////////////////////////////////////////////////////////////////
// TStringPairs class members
TStringPairs::TStringPairs(char* buffer) :
elems(nullptr)
{
TStringPairsElem tempElem[100];
char* token = strtok(buffer, ",");
for (numElems = 0; token != nullptr; numElems++) {
char* p = strchr(token, '='), *p1;
if (p == nullptr)
break;
while (isspace(*token))
token++;
tempElem[numElems].name = rtrim(token);
*p++ = 0;
if ((p1 = strchr(p, '\"')) != nullptr) {
*p1 = 0;
p = p1 + 1;
}
if ((p1 = strrchr(p, '\"')) != nullptr)
*p1 = 0;
tempElem[numElems].value = rtrim(p);
token = strtok(nullptr, ",");
}
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 (!mir_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, "%s%d", param, i);
ptrW tszRecent(getWStringA(setting));
if (tszRecent != nullptr)
SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, tszRecent);
}
if (!SendDlgItemMessage(hwndDlg, idcCombo, CB_GETCOUNT, 0, 0))
SendDlgItemMessage(hwndDlg, idcCombo, CB_ADDSTRING, 0, (LPARAM)L"");
}
void CJabberProto::ComboAddRecentString(HWND hwndDlg, UINT idcCombo, char *param, const wchar_t *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)L"")) != CB_ERR)
SendDlgItemMessage(hwndDlg, idcCombo, CB_DELETESTRING, id, 0);
id = getByte(param, 0);
char setting[MAXMODULELABELLENGTH];
mir_snprintf(setting, "%s%d", param, id);
setWString(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", Skin_GetIconHandle(SKINICON_OTHER_USERDETAILS), TranslateT("Offline"));
}
else {
ppro->m_pInfoFrame->UpdateInfoItem("$/JID", Skin_GetIconHandle(SKINICON_OTHER_USERDETAILS), Utf2T(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", Skin_GetIconHandle(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", Skin_GetIconHandle(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 = nullptr;
LISTFOREACH(i, ppro, LIST_ROSTER)
{
if ((item = ppro->ListGetItemPtrFromIndex(i)) != nullptr) {
if (strchr(item->jid, '@') == nullptr && strchr(item->jid, '/') == nullptr && item->subscription != SUB_NONE) {
MCONTACT hContact = ppro->HContactFromJID(item->jid);
if (hContact == 0)
continue;
char name[128];
mir_snprintf(name, "$/Transports/%s", item->jid);
ppro->m_pInfoFrame->CreateInfoItem(name, true, hContact);
ppro->m_pInfoFrame->UpdateInfoItem(name, ppro->GetIconHandle(IDI_TRANSPORTL), (wchar_t *)item->jid);
ppro->m_pInfoFrame->SetInfoItemCallback(name, &CJabberProto::InfoFrame_OnTransport);
}
}
}
}
ppro->m_pInfoFrame->Update();
}
void CJabberProto::RebuildInfoFrame()
{
if (!m_bShutdown)
CallFunctionAsync(sttRebuildInfoFrameApcProc, this);
}
////////////////////////////////////////////////////////////////////////
// time2str & str2time
char* time2str(time_t _time, char *buf, size_t bufLen)
{
struct tm *T = gmtime(&_time);
mir_snprintf(buf, bufLen, "%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 char *buf)
{
struct tm T = { 0 };
if (sscanf(buf, "%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 (sscanf(buf, "%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 _mkgmtime(&T);
}
////////////////////////////////////////////////////////////////////////
// case-insensitive wcsstr
const wchar_t *JabberStrIStr(const wchar_t *str, const wchar_t *substr)
{
wchar_t *str_up = NEWWSTR_ALLOCA(str);
wchar_t *substr_up = NEWWSTR_ALLOCA(substr);
CharUpperBuff(str_up, (DWORD)mir_wstrlen(str_up));
CharUpperBuff(substr_up, (DWORD)mir_wstrlen(substr_up));
wchar_t *p = wcsstr(str_up, substr_up);
return p ? (str + (p - str_up)) : nullptr;
}
////////////////////////////////////////////////////////////////////////
// clipboard processing
void JabberCopyText(HWND hwnd, const char *pszText)
{
if (!hwnd || !pszText) return;
if (OpenClipboard(hwnd)) {
Utf2T text(pszText);
EmptyClipboard();
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t)*(mir_wstrlen(text) + 1));
wchar_t *s = (wchar_t *)GlobalLock(hMem);
mir_wstrcpy(s, text);
GlobalUnlock(hMem);
SetClipboardData(CF_UNICODETEXT, hMem);
CloseClipboard();
}
}
void JabberCopyText(HWND hwnd, const wchar_t *text)
{
if (!hwnd || !text) return;
if (OpenClipboard(hwnd)) {
EmptyClipboard();
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t)*(mir_wstrlen(text) + 1));
wchar_t *s = (wchar_t *)GlobalLock(hMem);
mir_wstrcpy(s, text);
GlobalUnlock(hMem);
SetClipboardData(CF_UNICODETEXT, hMem);
CloseClipboard();
}
}
BOOL CJabberProto::EnterString(CMStringW &result, const wchar_t *caption, int type, char *windowName, int recentCount, int timeout)
{
if (caption == nullptr) {
caption = NEWWSTR_ALLOCA(result.GetString());
result.Empty();
}
ENTER_STRING param = { sizeof(param) };
param.type = type;
param.caption = caption;
param.szModuleName = m_szModuleName;
param.szDataPrefix = windowName;
param.recentCount = recentCount;
param.timeout = timeout;
param.ptszInitVal = result;
BOOL res = ::EnterString(¶m);
if (res) {
result = param.ptszResult;
mir_free(param.ptszResult);
}
return res;
}
/////////////////////////////////////////////////////////////////////////////////////////
// XEP-0203 delay support
bool JabberReadXep203delay(const TiXmlElement *node, time_t &msgTime)
{
auto *n = XmlGetChildByTag(node, "delay", "xmlns", "urn:xmpp:delay");
if (n == nullptr)
return false;
const char *ptszTimeStamp = XmlGetAttr(n, "stamp");
if (ptszTimeStamp == nullptr)
return false;
// skip '-' chars
char *szStamp = NEWSTR_ALLOCA(ptszTimeStamp);
int si = 0, sj = 0;
while (true) {
if (szStamp[si] == '-')
si++;
else if (!(szStamp[sj++] = szStamp[si++]))
break;
};
msgTime = JabberIsoToUnixTime(szStamp);
return msgTime != 0;
}
bool CJabberProto::IsMyOwnJID(const char *szJID)
{
if (m_ThreadInfo == nullptr)
return false;
ptrA szFrom(JabberPrepareJid(szJID));
if (szFrom == nullptr)
return false;
ptrA szTo(JabberPrepareJid(m_ThreadInfo->fullJID));
if (szTo == nullptr)
return false;
char *pDelimiter = strchr(szFrom, '/');
if (pDelimiter)
*pDelimiter = 0;
pDelimiter = strchr(szTo, '/');
if (pDelimiter)
*pDelimiter = 0;
return mir_strcmp(szFrom, szTo) == 0;
}
void __cdecl CJabberProto::LoadHttpAvatars(void* param)
{
Thread_SetName("Jabber: LoadHttpAvatars");
OBJLIST &avs = *(OBJLIST*)param;
HNETLIBCONN hHttpCon = nullptr;
for (auto &it : avs) {
NETLIBHTTPREQUEST nlhr = { 0 };
nlhr.cbSize = sizeof(nlhr);
nlhr.requestType = REQUEST_GET;
nlhr.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT;
nlhr.szUrl = it->Url;
nlhr.nlc = hHttpCon;
NETLIBHTTPREQUEST *res = Netlib_HttpTransaction(m_hNetlibUser, &nlhr);
if (res) {
hHttpCon = res->nlc;
if (res->resultCode == 200 && res->dataLength) {
int pictureType = ProtoGetBufferFormat(res->pData);
if (pictureType != PA_FORMAT_UNKNOWN) {
PROTO_AVATAR_INFORMATION ai;
ai.format = pictureType;
ai.hContact = it->hContact;
if (getByte(ai.hContact, "AvatarType", PA_FORMAT_UNKNOWN) != (unsigned char)pictureType) {
wchar_t tszFileName[MAX_PATH];
GetAvatarFileName(ai.hContact, tszFileName, _countof(tszFileName));
DeleteFile(tszFileName);
}
setByte(ai.hContact, "AvatarType", pictureType);
char buffer[2 * MIR_SHA1_HASH_SIZE + 1];
BYTE digest[MIR_SHA1_HASH_SIZE];
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 == nullptr || strnicmp(cmpsha, buffer, sizeof(buffer))) {
wchar_t tszFileName[MAX_PATH];
GetAvatarFileName(ai.hContact, tszFileName, _countof(tszFileName));
wcsncpy_s(ai.filename, tszFileName, _TRUNCATE);
FILE* out = _wfopen(tszFileName, L"wb");
if (out != nullptr) {
fwrite(res->pData, res->dataLength, 1, out);
fclose(out);
setString(ai.hContact, "AvatarSaved", buffer);
ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &ai, 0);
debugLogW(L"Broadcast new avatar: %s", ai.filename);
}
else ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, &ai, 0);
}
}
}
Netlib_FreeHttpRequest(res);
}
else hHttpCon = nullptr;
}
delete &avs;
if (hHttpCon)
Netlib_CloseHandle(hHttpCon);
}
/////////////////////////////////////////////////////////////////////////////////////////
// UI utilities
void SetDlgItemTextUtf(HWND hwndDlg, int ctrlId, const char *szValue)
{
if (szValue)
SetDlgItemTextW(hwndDlg, ctrlId, Utf2T(szValue));
}
void SetWindowTextUtf(HWND hwndDlg, const char *szValue)
{
if (szValue)
SetWindowTextW(hwndDlg, Utf2T(szValue));
}
int UIEmulateBtnClick(HWND hwndDlg, UINT idcButton)
{
if (IsWindowEnabled(GetDlgItem(hwndDlg, idcButton)))
PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(idcButton, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, idcButton));
return 0;
}
void UIShowControls(HWND hwndDlg, int *idList, int nCmdShow)
{
for (; *idList; ++idList)
ShowWindow(GetDlgItem(hwndDlg, *idList), nCmdShow);
}