From 4b283b1cf3004aebbb1f4807456a2cd0fdb5f30c Mon Sep 17 00:00:00 2001 From: George Hazan Date: Sun, 23 Dec 2012 18:52:25 +0000 Subject: - XEP-0136 support (server-side message history); - code cleaning git-svn-id: http://svn.miranda-ng.org/main/trunk@2816 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/JabberG/jabber_10.vcxproj | 1 + protocols/JabberG/jabber_10.vcxproj.filters | 3 + protocols/JabberG/jabber_11.vcxproj | 1 + protocols/JabberG/jabber_11.vcxproj.filters | 3 + protocols/JabberG/src/jabber.h | 3 + protocols/JabberG/src/jabber_adhoc.cpp | 17 +- protocols/JabberG/src/jabber_agent.cpp | 2 +- protocols/JabberG/src/jabber_archive.cpp | 308 +++++++++++++++++++++++++++ protocols/JabberG/src/jabber_bookmarks.cpp | 2 +- protocols/JabberG/src/jabber_byte.cpp | 6 +- protocols/JabberG/src/jabber_caps.cpp | 106 ++++----- protocols/JabberG/src/jabber_caps.h | 12 +- protocols/JabberG/src/jabber_chat.cpp | 8 +- protocols/JabberG/src/jabber_db_utils.h | 2 + protocols/JabberG/src/jabber_disco.cpp | 4 +- protocols/JabberG/src/jabber_events.cpp | 4 +- protocols/JabberG/src/jabber_ft.cpp | 4 +- protocols/JabberG/src/jabber_groupchat.cpp | 8 +- protocols/JabberG/src/jabber_ibb.cpp | 4 +- protocols/JabberG/src/jabber_iq_handlers.cpp | 4 +- protocols/JabberG/src/jabber_iqid.cpp | 30 ++- protocols/JabberG/src/jabber_list.cpp | 4 +- protocols/JabberG/src/jabber_list.h | 1 + protocols/JabberG/src/jabber_menu.cpp | 11 +- protocols/JabberG/src/jabber_opt.cpp | 1 + protocols/JabberG/src/jabber_privacy.cpp | 2 +- protocols/JabberG/src/jabber_proto.cpp | 4 +- protocols/JabberG/src/jabber_proto.h | 9 + protocols/JabberG/src/jabber_search.cpp | 2 +- protocols/JabberG/src/jabber_thread.cpp | 8 +- protocols/JabberG/src/jabber_userinfo.cpp | 4 +- protocols/JabberG/src/jabber_util.cpp | 36 ++++ 32 files changed, 502 insertions(+), 112 deletions(-) create mode 100644 protocols/JabberG/src/jabber_archive.cpp (limited to 'protocols/JabberG') diff --git a/protocols/JabberG/jabber_10.vcxproj b/protocols/JabberG/jabber_10.vcxproj index febb753f0a..e1f795ede5 100644 --- a/protocols/JabberG/jabber_10.vcxproj +++ b/protocols/JabberG/jabber_10.vcxproj @@ -201,6 +201,7 @@ + Create diff --git a/protocols/JabberG/jabber_10.vcxproj.filters b/protocols/JabberG/jabber_10.vcxproj.filters index 9b905f80ff..4f8501af1d 100644 --- a/protocols/JabberG/jabber_10.vcxproj.filters +++ b/protocols/JabberG/jabber_10.vcxproj.filters @@ -177,6 +177,9 @@ Source Files + + Source Files + diff --git a/protocols/JabberG/jabber_11.vcxproj b/protocols/JabberG/jabber_11.vcxproj index 4e75869570..f30cd91381 100644 --- a/protocols/JabberG/jabber_11.vcxproj +++ b/protocols/JabberG/jabber_11.vcxproj @@ -205,6 +205,7 @@ + Create diff --git a/protocols/JabberG/jabber_11.vcxproj.filters b/protocols/JabberG/jabber_11.vcxproj.filters index 9b905f80ff..4f8501af1d 100644 --- a/protocols/JabberG/jabber_11.vcxproj.filters +++ b/protocols/JabberG/jabber_11.vcxproj.filters @@ -177,6 +177,9 @@ Source Files + + Source Files + diff --git a/protocols/JabberG/src/jabber.h b/protocols/JabberG/src/jabber.h index b8e7a1f4d5..9e27954ffd 100644 --- a/protocols/JabberG/src/jabber.h +++ b/protocols/JabberG/src/jabber.h @@ -745,6 +745,9 @@ TCHAR* __stdcall JabberStripJid(const TCHAR *jid, TCHAR* dest, size_t des int __stdcall JabberGetPictureType(const char* buf); int __stdcall JabberGetPacketID(HXML n); +TCHAR* time2str(time_t _time, TCHAR *buf, size_t bufLen); +time_t str2time(const TCHAR*); + #define JabberUnixToDosT JabberUnixToDosW #define JabberBase64DecodeT JabberBase64DecodeW diff --git a/protocols/JabberG/src/jabber_adhoc.cpp b/protocols/JabberG/src/jabber_adhoc.cpp index c19589ee73..95f54ab015 100644 --- a/protocols/JabberG/src/jabber_adhoc.cpp +++ b/protocols/JabberG/src/jabber_adhoc.cpp @@ -115,7 +115,7 @@ int CJabberProto::AdHoc_RequestListOfCommands(TCHAR * szResponder, HWND hwndDlg) { int iqId = (int)hwndDlg; IqAdd(iqId, IQ_PROC_DISCOCOMMANDS, &CJabberProto::OnIqResult_ListOfCommands); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, szResponder) << XQUERY(_T(JABBER_FEAT_DISCO_ITEMS)) + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, szResponder) << XQUERY(_T(JABBER_FEAT_DISCO_ITEMS)) << XATTR(_T("node"), _T(JABBER_FEAT_COMMANDS))); return iqId; } @@ -183,17 +183,16 @@ int CJabberProto::AdHoc_OnJAHMCommandListResult(HWND hwndDlg, HXML iqNode, Jabbe if (queryNode && xmlGetChild(queryNode ,0) && validResponse) { dat->CommandsNode = xi.copyNode(queryNode); - nodeIdx = 1; int ypos = 20; for (nodeIdx = 1; ; nodeIdx++) { HXML itemNode = xmlGetNthChild(queryNode, _T("item"), nodeIdx); - if (itemNode) { - const TCHAR *name = xmlGetAttrValue(itemNode, _T("name")); - if ( !name) name = xmlGetAttrValue(itemNode, _T("node")); - ypos = AdHoc_AddCommandRadio(GetDlgItem(hwndDlg,IDC_FRAME), TranslateTS(name), nodeIdx, ypos, (nodeIdx==1) ? 1 : 0); - dat->CurrentHeight = ypos; - } - else break; + if (!itemNode) + break; + + const TCHAR *name = xmlGetAttrValue(itemNode, _T("name")); + if ( !name) name = xmlGetAttrValue(itemNode, _T("node")); + ypos = AdHoc_AddCommandRadio(GetDlgItem(hwndDlg,IDC_FRAME), TranslateTS(name), nodeIdx, ypos, (nodeIdx==1) ? 1 : 0); + dat->CurrentHeight = ypos; } } if (nodeIdx>1) { diff --git a/protocols/JabberG/src/jabber_agent.cpp b/protocols/JabberG/src/jabber_agent.cpp index 928beab4e0..62343fd63a 100644 --- a/protocols/JabberG/src/jabber_agent.cpp +++ b/protocols/JabberG/src/jabber_agent.cpp @@ -102,7 +102,7 @@ public: int iqId = m_proto->SerialNext(); m_proto->IqAdd(iqId, IQ_PROC_GETREGISTER, &CJabberProto::OnIqResultGetRegister); - m_proto->m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, m_jid) << XQUERY(_T(JABBER_FEAT_REGISTER))); + m_proto->m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, m_jid) << XQUERY(_T(JABBER_FEAT_REGISTER))); // Enable WS_EX_CONTROLPARENT on IDC_FRAME (so tab stop goes through all its children) LONG frameExStyle = GetWindowLongPtr(GetDlgItem(m_hwnd, IDC_FRAME), GWL_EXSTYLE); diff --git a/protocols/JabberG/src/jabber_archive.cpp b/protocols/JabberG/src/jabber_archive.cpp new file mode 100644 index 0000000000..74ef786e00 --- /dev/null +++ b/protocols/JabberG/src/jabber_archive.cpp @@ -0,0 +1,308 @@ +/* + +Jabber Protocol Plugin for Miranda IM +Copyright (C) 2002-04 Santithorn Bunchua +Copyright (C) 2005-12 George Hazan + +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_iq.h" +#include "jabber_caps.h" + +void CJabberProto::EnableArchive(bool bEnable) +{ + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext()) + << XCHILDNS( _T("auto"), _T(JABBER_FEAT_ARCHIVE)) << XATTR(_T("save"), (bEnable) ? _T("true") : _T("false"))); +} + +void CJabberProto::RetrieveMessageArchive(HANDLE hContact, JABBER_LIST_ITEM *pItem) +{ + if (pItem->bHistoryRead) + return; + + pItem->bHistoryRead = TRUE; + + int iqId = SerialNext(); + XmlNodeIq iq(_T("get"), iqId); + HXML list = iq << XCHILDNS( _T("list"), _T(JABBER_FEAT_ARCHIVE)) << XATTR(_T("with"), pItem->jid); + + time_t tmLast = JGetDword(hContact, "LastCollection", 0); + if (tmLast) { + TCHAR buf[40]; + list << XATTR(_T("start"), time2str(tmLast, buf, SIZEOF(buf))); + } + + IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetCollectionList); + m_ThreadInfo->send(iq); +} + +void CJabberProto::OnIqResultGetCollectionList(HXML iqNode) +{ + const TCHAR *to = xmlGetAttrValue(iqNode, _T("to")); + if (to == NULL || lstrcmp( xmlGetAttrValue(iqNode, _T("type")), _T("result"))) + return; + + HXML list = xmlGetChild(iqNode, "list"); + if (!list || lstrcmp( xmlGetAttrValue(list, _T("xmlns")), _T(JABBER_FEAT_ARCHIVE))) + return; + + HANDLE hContact = NULL; + time_t tmLast = 0; + + for (int nodeIdx = 1; ; nodeIdx++) { + HXML itemNode = xmlGetNthChild(list, _T("chat"), nodeIdx); + if (!itemNode) + break; + + const TCHAR* start = xmlGetAttrValue(itemNode, _T("start")); + const TCHAR* with = xmlGetAttrValue(itemNode, _T("with")); + if (!start || !with) + continue; + + if (hContact == NULL) { + if ((hContact = HContactFromJID(with)) == NULL) + continue; + + tmLast = JGetDword(hContact, "LastCollection", 0); + } + + int iqId = SerialNext(); + IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetCollection); + m_ThreadInfo->send( + XmlNodeIq(_T("get"), iqId) + << XCHILDNS( _T("retrieve"), _T(JABBER_FEAT_ARCHIVE)) << XATTR(_T("with"), with) << XATTR(_T("start"), start)); + + time_t tmThis = str2time(start); + if ( tmThis > tmLast) { + tmLast = tmThis; + JSetDword(hContact, "LastCollection", tmLast+1); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static DWORD dwPreviousTimeStamp = -1; +static HANDLE hPreviousContact = INVALID_HANDLE_VALUE; +static HANDLE hPreviousDbEvent = NULL; + +// Returns TRUE if the event already exist in the database +BOOL IsDuplicateEvent(HANDLE hContact, DBEVENTINFO& dbei) +{ + HANDLE hExistingDbEvent; + DWORD dwEventTimeStamp; + DBEVENTINFO dbeiExisting; + + // get last event + if (!(hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDLAST, (WPARAM)hContact, 0))) + return FALSE; + + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting); + dwEventTimeStamp = dbeiExisting.timestamp; + + // compare with last timestamp + if (dbei.timestamp > dwEventTimeStamp) { + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dwEventTimeStamp; + return FALSE; + } + + if (hContact != hPreviousContact) { + hPreviousContact = hContact; + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dwEventTimeStamp; + + // get first event + if (!(hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDFIRST, (WPARAM)hContact, 0))) + return FALSE; + + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting); + dwEventTimeStamp = dbeiExisting.timestamp; + + // compare with first timestamp + if (dbei.timestamp <= dwEventTimeStamp) { + // remember event + dwPreviousTimeStamp = dwEventTimeStamp; + hPreviousDbEvent = hExistingDbEvent; + + if ( dbei.timestamp != dwEventTimeStamp ) + return FALSE; + } + } + + // check for equal timestamps + if (dbei.timestamp == dwPreviousTimeStamp) { + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hPreviousDbEvent, (LPARAM)&dbeiExisting); + + if ((dbei.timestamp == dbeiExisting.timestamp) && + (dbei.eventType == dbeiExisting.eventType) && + (dbei.cbBlob == dbeiExisting.cbBlob) && + ((dbei.flags&DBEF_SENT) == (dbeiExisting.flags&DBEF_SENT))) + return TRUE; + + // find event with another timestamp + hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDNEXT, (WPARAM)hPreviousDbEvent, 0); + while (hExistingDbEvent != NULL) { + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting); + + if (dbeiExisting.timestamp != dwPreviousTimeStamp) { + // use found event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dbeiExisting.timestamp; + break; + } + + hPreviousDbEvent = hExistingDbEvent; + hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDNEXT, (WPARAM)hExistingDbEvent, 0); + } + } + + hExistingDbEvent = hPreviousDbEvent; + + if (dbei.timestamp <= dwPreviousTimeStamp) { + // look back + while (hExistingDbEvent != NULL) { + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting); + + if (dbei.timestamp > dbeiExisting.timestamp) { + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dbeiExisting.timestamp; + return FALSE; + } + + // Compare event with import candidate + if ((dbei.timestamp == dbeiExisting.timestamp) && + (dbei.eventType == dbeiExisting.eventType) && + (dbei.cbBlob == dbeiExisting.cbBlob) && + ((dbei.flags & DBEF_SENT) == (dbeiExisting.flags & DBEF_SENT))) + { + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dbeiExisting.timestamp; + return TRUE; + } + + // Get previous event in chain + hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDPREV, (WPARAM)hExistingDbEvent, 0); + } + } + else { + // look forward + while (hExistingDbEvent != NULL) { + ZeroMemory(&dbeiExisting, sizeof(dbeiExisting)); + dbeiExisting.cbSize = sizeof(dbeiExisting); + CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting); + + if (dbei.timestamp < dbeiExisting.timestamp) { + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dbeiExisting.timestamp; + return FALSE; + } + + // Compare event with import candidate + if ((dbei.timestamp == dbeiExisting.timestamp) && + (dbei.eventType == dbeiExisting.eventType) && + (dbei.cbBlob == dbeiExisting.cbBlob) && + ((dbei.flags&DBEF_SENT) == (dbeiExisting.flags&DBEF_SENT))) + { + // remember event + hPreviousDbEvent = hExistingDbEvent; + dwPreviousTimeStamp = dbeiExisting.timestamp; + return TRUE; + } + + // Get next event in chain + hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDNEXT, (WPARAM)hExistingDbEvent, 0); + } + } + // reset last event + hPreviousContact = INVALID_HANDLE_VALUE; + return FALSE; +} + +void CJabberProto::OnIqResultGetCollection(HXML iqNode) +{ + if ( lstrcmp( xmlGetAttrValue(iqNode, _T("type")), _T("result"))) + return; + + HXML chatNode = xmlGetChild(iqNode, "chat"); + if (!chatNode || lstrcmp( xmlGetAttrValue(chatNode, _T("xmlns")), _T(JABBER_FEAT_ARCHIVE))) + return; + + const TCHAR* start = xmlGetAttrValue(chatNode, _T("start")); + const TCHAR* with = xmlGetAttrValue(chatNode, _T("with")); + if (!start || !with) + return; + + HANDLE hContact = HContactFromJID(with); + time_t tmStart = str2time(start); + if (hContact == 0 || tmStart == 0) + return; + + _tzset(); + + for (int nodeIdx = 0; ; nodeIdx++) { + HXML itemNode = xmlGetChild(chatNode, nodeIdx); + if (!itemNode) + break; + + int from; + const TCHAR *itemName = xmlGetName(itemNode); + if ( !lstrcmp(itemName, _T("to"))) + from = DBEF_SENT; + else if ( !lstrcmp(itemName, _T("from"))) + from = 0; + else + continue; + + HXML body = xmlGetChild(itemNode, "body"); + if (!body) + continue; + + const TCHAR *tszBody = xmlGetText(body); + const TCHAR *tszSecs = xmlGetAttrValue(itemNode, _T("secs")); + if (!tszBody || !tszSecs) + continue; + + mir_ptr szEventText( mir_utf8encodeT(tszBody)); + + DBEVENTINFO dbei = { sizeof(DBEVENTINFO) }; + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.szModule = m_szModuleName; + dbei.cbBlob = (DWORD)strlen(szEventText); + dbei.flags = DBEF_READ + DBEF_UTF + from; + dbei.pBlob = (PBYTE)(char*)szEventText; + dbei.timestamp = tmStart + _ttol(tszSecs) - timezone; + if ( !IsDuplicateEvent(hContact, dbei)) + CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei); + } +} diff --git a/protocols/JabberG/src/jabber_bookmarks.cpp b/protocols/JabberG/src/jabber_bookmarks.cpp index bd8014d42a..79e105b679 100644 --- a/protocols/JabberG/src/jabber_bookmarks.cpp +++ b/protocols/JabberG/src/jabber_bookmarks.cpp @@ -272,7 +272,7 @@ void CJabberDlgBookmarks::UpdateData() int iqId = m_proto->SerialNext(); m_proto->IqAdd(iqId, IQ_PROC_DISCOBOOKMARKS, &CJabberProto::OnIqResultDiscoBookmarks); - m_proto->m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) + m_proto->m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId) << XQUERY(_T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS(_T("storage"), _T("storage:bookmarks"))); } diff --git a/protocols/JabberG/src/jabber_byte.cpp b/protocols/JabberG/src/jabber_byte.cpp index e9c4a443cd..63160e8985 100644 --- a/protocols/JabberG/src/jabber_byte.cpp +++ b/protocols/JabberG/src/jabber_byte.cpp @@ -444,7 +444,7 @@ void CJabberProto::ByteSendViaProxy(JABBER_BYTE_TRANSFER *jbt) if (jbt == NULL) return; if ((buffer=(char*)mir_alloc(JABBER_NETWORK_BUFFER_SIZE)) == NULL) { - m_ThreadInfo->send(XmlNodeIq(_T("error"), jbt->iqId, jbt->srcJID) + m_ThreadInfo->send( XmlNodeIq(_T("error"), jbt->iqId, jbt->srcJID) << XCHILD(_T("error")) << XATTRI(_T("code"), 406) << XATTR(_T("type"), _T("auth")) << XCHILDNS(_T("not-acceptable"), _T("urn:ietf:params:xml:ns:xmpp-stanzas"))); return; @@ -494,7 +494,7 @@ void CJabberProto::ByteSendViaProxy(JABBER_BYTE_TRANSFER *jbt) (this->*jbt->pfnFinal)((jbt->state == JBT_DONE) ? TRUE : FALSE, jbt->ft); jbt->ft = NULL; if ( !validStreamhost) - m_ThreadInfo->send(XmlNodeIq(_T("error"), jbt->iqId, jbt->srcJID) + m_ThreadInfo->send( XmlNodeIq(_T("error"), jbt->iqId, jbt->srcJID) << XCHILD(_T("error")) << XATTRI(_T("code"), 404) << XATTR(_T("type"), _T("cancel")) << XCHILDNS(_T("item-not-found"), _T("urn:ietf:params:xml:ns:xmpp-stanzas"))); } @@ -688,7 +688,7 @@ void __cdecl CJabberProto::ByteReceiveThread(JABBER_BYTE_TRANSFER *jbt) if ( !validStreamhost && szId && from) { Log("bytestream_recv_connection session not completed"); - m_ThreadInfo->send(XmlNodeIq(_T("error"), szId, from) + m_ThreadInfo->send( XmlNodeIq(_T("error"), szId, from) << XCHILD(_T("error")) << XATTRI(_T("code"), 404) << XATTR(_T("type"), _T("cancel")) << XCHILDNS(_T("item-not-found"), _T("urn:ietf:params:xml:ns:xmpp-stanzas"))); } diff --git a/protocols/JabberG/src/jabber_caps.cpp b/protocols/JabberG/src/jabber_caps.cpp index 8c5f3f484b..55911fd311 100644 --- a/protocols/JabberG/src/jabber_caps.cpp +++ b/protocols/JabberG/src/jabber_caps.cpp @@ -27,49 +27,51 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "version.h" const JabberFeatCapPair g_JabberFeatCapPairs[] = { - { _T(JABBER_FEAT_DISCO_INFO), JABBER_CAPS_DISCO_INFO, _T("Supports Service Discovery info"), }, - { _T(JABBER_FEAT_DISCO_ITEMS), JABBER_CAPS_DISCO_ITEMS, _T("Supports Service Discovery items list"), }, - { _T(JABBER_FEAT_ENTITY_CAPS), JABBER_CAPS_ENTITY_CAPS, _T("Can inform about its Jabber capabilities"), }, - { _T(JABBER_FEAT_SI), JABBER_CAPS_SI, _T("Supports stream initiation (for filetransfers for ex.)"), }, - { _T(JABBER_FEAT_SI_FT), JABBER_CAPS_SI_FT, _T("Supports stream initiation for file transfers"), }, - { _T(JABBER_FEAT_BYTESTREAMS), JABBER_CAPS_BYTESTREAMS, _T("Supports file transfers via SOCKS5 Bytestreams"), }, - { _T(JABBER_FEAT_IBB), JABBER_CAPS_IBB, _T("Supports file transfers via In-Band Bytestreams"), }, - { _T(JABBER_FEAT_OOB), JABBER_CAPS_OOB, _T("Supports file transfers via Out-of-Band Bytestreams"), }, - { _T(JABBER_FEAT_OOB2), JABBER_CAPS_OOB, _T("Supports file transfers via Out-of-Band Bytestreams"), }, - { _T(JABBER_FEAT_COMMANDS), JABBER_CAPS_COMMANDS, _T("Supports execution of Ad-Hoc commands"), }, - { _T(JABBER_FEAT_REGISTER), JABBER_CAPS_REGISTER, _T("Supports in-band registration"), }, - { _T(JABBER_FEAT_MUC), JABBER_CAPS_MUC, _T("Supports multi-user chat"), }, - { _T(JABBER_FEAT_CHATSTATES), JABBER_CAPS_CHATSTATES, _T("Can report chat state in a chat session"), }, - { _T(JABBER_FEAT_LAST_ACTIVITY), JABBER_CAPS_LAST_ACTIVITY, _T("Can report information about the last activity of the user"), }, - { _T(JABBER_FEAT_VERSION), JABBER_CAPS_VERSION, _T("Can report own version information"), }, - { _T(JABBER_FEAT_ENTITY_TIME), JABBER_CAPS_ENTITY_TIME, _T("Can report local time of the user"), }, - { _T(JABBER_FEAT_PING), JABBER_CAPS_PING, _T("Can send and receive ping requests"), }, - { _T(JABBER_FEAT_DATA_FORMS), JABBER_CAPS_DATA_FORMS, _T("Supports data forms"), }, - { _T(JABBER_FEAT_MESSAGE_EVENTS), JABBER_CAPS_MESSAGE_EVENTS, _T("Can request and respond to events relating to the delivery, display, and composition of messages"), }, - { _T(JABBER_FEAT_VCARD_TEMP), JABBER_CAPS_VCARD_TEMP, _T("Supports vCard"), }, - { _T(JABBER_FEAT_AVATAR), JABBER_CAPS_AVATAR, _T("Supports iq-based avatars"), }, - { _T(JABBER_FEAT_XHTML), JABBER_CAPS_XHTML, _T("Supports XHTML formatting of chat messages"), }, - { _T(JABBER_FEAT_AGENTS), JABBER_CAPS_AGENTS, _T("Supports Jabber Browsing"), }, - { _T(JABBER_FEAT_BROWSE), JABBER_CAPS_BROWSE, _T("Supports Jabber Browsing"), }, - { _T(JABBER_FEAT_FEATURE_NEG), JABBER_CAPS_FEATURE_NEG, _T("Can negotiate options for specific features"), }, - { _T(JABBER_FEAT_AMP), JABBER_CAPS_AMP, _T("Can request advanced processing of message stanzas"), }, - { _T(JABBER_FEAT_USER_MOOD), JABBER_CAPS_USER_MOOD, _T("Can report information about user moods"), }, - { _T(JABBER_FEAT_USER_MOOD_NOTIFY), JABBER_CAPS_USER_MOOD_NOTIFY, _T("Receives information about user moods"), }, - { _T(JABBER_FEAT_PUBSUB), JABBER_CAPS_PUBSUB, _T("Supports generic publish-subscribe functionality"), }, - { _T(JABBER_FEAT_SECUREIM), JABBER_CAPS_SECUREIM, _T("Supports SecureIM plugin for Miranda NG"), }, - { _T(JABBER_FEAT_PRIVACY_LISTS), JABBER_CAPS_PRIVACY_LISTS, _T("Can block communications from particular other users using Privacy lists"), }, - { _T(JABBER_FEAT_MESSAGE_RECEIPTS), JABBER_CAPS_MESSAGE_RECEIPTS, _T("Supports Message Receipts"), }, - { _T(JABBER_FEAT_USER_TUNE), JABBER_CAPS_USER_TUNE, _T("Can report information about the music to which a user is listening"), }, - { _T(JABBER_FEAT_USER_TUNE_NOTIFY), JABBER_CAPS_USER_TUNE_NOTIFY, _T("Receives information about the music to which a user is listening"), }, - { _T(JABBER_FEAT_PRIVATE_STORAGE), JABBER_CAPS_PRIVATE_STORAGE, _T("Supports private XML Storage (for bookmakrs and other)"), }, - { _T(JABBER_FEAT_ATTENTION), JABBER_CAPS_ATTENTION, _T("Supports attention requests ('nudge')"), }, - { _T(JABBER_FEAT_ATTENTION_0), JABBER_CAPS_ATTENTION_0, _T("Supports attention requests ('nudge')"), }, - { _T(JABBER_FEAT_USER_ACTIVITY), JABBER_CAPS_USER_ACTIVITY, _T("Can report information about user activity"), }, - { _T(JABBER_FEAT_USER_ACTIVITY_NOTIFY), JABBER_CAPS_USER_ACTIVITY_NOTIFY, _T("Receives information about user activity"), }, - { _T(JABBER_FEAT_MIRANDA_NOTES), JABBER_CAPS_MIRANDA_NOTES, _T("Supports Miranda NG notes extension"), }, - { _T(JABBER_FEAT_JINGLE), JABBER_CAPS_JINGLE, _T("Supports Jingle"), }, - { _T(JABBER_FEAT_ROSTER_EXCHANGE), JABBER_CAPS_ROSTER_EXCHANGE, _T("Supports Roster Exchange"), }, - { _T(JABBER_FEAT_GTALK_PMUC), JABBER_CAPS_GTALK_PMUC, _T("Supports GTalk private multi-user chat"), }, + { _T(JABBER_FEAT_DISCO_INFO), JABBER_CAPS_DISCO_INFO, _T("Supports Service Discovery info") }, + { _T(JABBER_FEAT_DISCO_ITEMS), JABBER_CAPS_DISCO_ITEMS, _T("Supports Service Discovery items list") }, + { _T(JABBER_FEAT_ENTITY_CAPS), JABBER_CAPS_ENTITY_CAPS, _T("Can inform about its Jabber capabilities") }, + { _T(JABBER_FEAT_SI), JABBER_CAPS_SI, _T("Supports stream initiation (for filetransfers for ex.)") }, + { _T(JABBER_FEAT_SI_FT), JABBER_CAPS_SI_FT, _T("Supports stream initiation for file transfers") }, + { _T(JABBER_FEAT_BYTESTREAMS), JABBER_CAPS_BYTESTREAMS, _T("Supports file transfers via SOCKS5 Bytestreams") }, + { _T(JABBER_FEAT_IBB), JABBER_CAPS_IBB, _T("Supports file transfers via In-Band Bytestreams") }, + { _T(JABBER_FEAT_OOB), JABBER_CAPS_OOB, _T("Supports file transfers via Out-of-Band Bytestreams") }, + { _T(JABBER_FEAT_OOB2), JABBER_CAPS_OOB, _T("Supports file transfers via Out-of-Band Bytestreams") }, + { _T(JABBER_FEAT_COMMANDS), JABBER_CAPS_COMMANDS, _T("Supports execution of Ad-Hoc commands") }, + { _T(JABBER_FEAT_REGISTER), JABBER_CAPS_REGISTER, _T("Supports in-band registration") }, + { _T(JABBER_FEAT_MUC), JABBER_CAPS_MUC, _T("Supports multi-user chat") }, + { _T(JABBER_FEAT_CHATSTATES), JABBER_CAPS_CHATSTATES, _T("Can report chat state in a chat session") }, + { _T(JABBER_FEAT_LAST_ACTIVITY), JABBER_CAPS_LAST_ACTIVITY, _T("Can report information about the last activity of the user") }, + { _T(JABBER_FEAT_VERSION), JABBER_CAPS_VERSION, _T("Can report own version information") }, + { _T(JABBER_FEAT_ENTITY_TIME), JABBER_CAPS_ENTITY_TIME, _T("Can report local time of the user") }, + { _T(JABBER_FEAT_PING), JABBER_CAPS_PING, _T("Can send and receive ping requests") }, + { _T(JABBER_FEAT_DATA_FORMS), JABBER_CAPS_DATA_FORMS, _T("Supports data forms") }, + { _T(JABBER_FEAT_MESSAGE_EVENTS), JABBER_CAPS_MESSAGE_EVENTS, _T("Can request and respond to events relating to the delivery, display, and composition of messages") }, + { _T(JABBER_FEAT_VCARD_TEMP), JABBER_CAPS_VCARD_TEMP, _T("Supports vCard") }, + { _T(JABBER_FEAT_AVATAR), JABBER_CAPS_AVATAR, _T("Supports iq-based avatars") }, + { _T(JABBER_FEAT_XHTML), JABBER_CAPS_XHTML, _T("Supports XHTML formatting of chat messages") }, + { _T(JABBER_FEAT_AGENTS), JABBER_CAPS_AGENTS, _T("Supports Jabber Browsing") }, + { _T(JABBER_FEAT_BROWSE), JABBER_CAPS_BROWSE, _T("Supports Jabber Browsing") }, + { _T(JABBER_FEAT_FEATURE_NEG), JABBER_CAPS_FEATURE_NEG, _T("Can negotiate options for specific features") }, + { _T(JABBER_FEAT_AMP), JABBER_CAPS_AMP, _T("Can request advanced processing of message stanzas") }, + { _T(JABBER_FEAT_USER_MOOD), JABBER_CAPS_USER_MOOD, _T("Can report information about user moods") }, + { _T(JABBER_FEAT_USER_MOOD_NOTIFY), JABBER_CAPS_USER_MOOD_NOTIFY, _T("Receives information about user moods") }, + { _T(JABBER_FEAT_PUBSUB), JABBER_CAPS_PUBSUB, _T("Supports generic publish-subscribe functionality") }, + { _T(JABBER_FEAT_SECUREIM), JABBER_CAPS_SECUREIM, _T("Supports SecureIM plugin for Miranda NG") }, + { _T(JABBER_FEAT_PRIVACY_LISTS), JABBER_CAPS_PRIVACY_LISTS, _T("Can block communications from particular other users using Privacy lists") }, + { _T(JABBER_FEAT_MESSAGE_RECEIPTS), JABBER_CAPS_MESSAGE_RECEIPTS, _T("Supports Message Receipts") }, + { _T(JABBER_FEAT_USER_TUNE), JABBER_CAPS_USER_TUNE, _T("Can report information about the music to which a user is listening") }, + { _T(JABBER_FEAT_USER_TUNE_NOTIFY), JABBER_CAPS_USER_TUNE_NOTIFY, _T("Receives information about the music to which a user is listening") }, + { _T(JABBER_FEAT_PRIVATE_STORAGE), JABBER_CAPS_PRIVATE_STORAGE, _T("Supports private XML Storage (for bookmakrs and other)") }, + { _T(JABBER_FEAT_ATTENTION), JABBER_CAPS_ATTENTION, _T("Supports attention requests ('nudge')") }, + { _T(JABBER_FEAT_ATTENTION_0), JABBER_CAPS_ATTENTION_0, _T("Supports attention requests ('nudge')") }, + { _T(JABBER_FEAT_ARCHIVE_AUTO), JABBER_CAPS_ARCHIVE_AUTO, _T("Supports chat history retrieving") }, + { _T(JABBER_FEAT_ARCHIVE_MANAGE), JABBER_CAPS_ARCHIVE_MANAGE, _T("Supports chat history management") }, + { _T(JABBER_FEAT_USER_ACTIVITY), JABBER_CAPS_USER_ACTIVITY, _T("Can report information about user activity") }, + { _T(JABBER_FEAT_USER_ACTIVITY_NOTIFY), JABBER_CAPS_USER_ACTIVITY_NOTIFY, _T("Receives information about user activity") }, + { _T(JABBER_FEAT_MIRANDA_NOTES), JABBER_CAPS_MIRANDA_NOTES, _T("Supports Miranda NG notes extension") }, + { _T(JABBER_FEAT_JINGLE), JABBER_CAPS_JINGLE, _T("Supports Jingle") }, + { _T(JABBER_FEAT_ROSTER_EXCHANGE), JABBER_CAPS_ROSTER_EXCHANGE, _T("Supports Roster Exchange") }, + { _T(JABBER_FEAT_GTALK_PMUC), JABBER_CAPS_GTALK_PMUC, _T("Supports GTalk private multi-user chat") }, { NULL, 0, NULL} }; @@ -150,12 +152,15 @@ void CJabberProto::OnIqResultCapsDiscoInfo(HXML, CJabberIqInfo* pInfo) HXML feature; for (int i = 1; (feature = xmlGetNthChild(query, _T("feature"), i)) != NULL; i++) { const TCHAR *featureName = xmlGetAttrValue(feature, _T("var")); - if (featureName) { - for (int j = 0; g_JabberFeatCapPairs[j].szFeature; j++) { - if ( !_tcscmp(g_JabberFeatCapPairs[j].szFeature, featureName)) { - jcbCaps |= g_JabberFeatCapPairs[j].jcbCap; - break; - } } } } + if (!featureName) + continue; + + for (int j = 0; g_JabberFeatCapPairs[j].szFeature; j++) + if ( !_tcscmp(g_JabberFeatCapPairs[j].szFeature, featureName)) { + jcbCaps |= g_JabberFeatCapPairs[j].jcbCap; + break; + } + } // no version info support and no XEP-0115 support? if (r && r->dwVersionRequestTime == -1 && !r->version && !r->software && !r->szCapsNode) { @@ -167,6 +172,7 @@ void CJabberProto::OnIqResultCapsDiscoInfo(HXML, CJabberIqInfo* pInfo) if ( !m_clientCapsManager.SetClientCaps(pInfo->GetIqId(), jcbCaps)) if (r) r->jcbCachedCaps = jcbCaps; + JabberUserInfoUpdate(pInfo->GetHContact()); } else { @@ -244,7 +250,7 @@ JabberCapsBits CJabberProto::GetResourceCapabilites(const TCHAR *jid, BOOL appen TCHAR queryNode[512]; mir_sntprintf(queryNode, SIZEOF(queryNode), _T("%s#%s"), r->szCapsNode, r->szCapsVer); - m_ThreadInfo->send(XmlNodeIq(pInfo) << XQUERY(_T(JABBER_FEAT_DISCO_INFO)) << XATTR(_T("node"), queryNode)); + m_ThreadInfo->send( XmlNodeIq(pInfo) << XQUERY(_T(JABBER_FEAT_DISCO_INFO)) << XATTR(_T("node"), queryNode)); bRequestSent = TRUE; } diff --git a/protocols/JabberG/src/jabber_caps.h b/protocols/JabberG/src/jabber_caps.h index d9597590fe..ac9b1f28ef 100644 --- a/protocols/JabberG/src/jabber_caps.h +++ b/protocols/JabberG/src/jabber_caps.h @@ -114,7 +114,15 @@ typedef unsigned __int64 JabberCapsBits; #define JABBER_CAPS_USER_TUNE_NOTIFY ((JabberCapsBits)1<<32) #define JABBER_FEAT_PRIVATE_STORAGE "jabber:iq:private" #define JABBER_CAPS_PRIVATE_STORAGE ((JabberCapsBits)1<<33) -#define JABBER_FEAT_CAPTCHA "urn:xmpp:captcha" + +#define JABBER_FEAT_ARCHIVE "urn:xmpp:archive" +#define JABBER_FEAT_ARCHIVE_AUTO "urn:xmpp:archive:auto" +#define JABBER_CAPS_ARCHIVE_AUTO ((JabberCapsBits)1<<34) +#define JABBER_FEAT_ARCHIVE_MANAGE "urn:xmpp:archive:manage" +#define JABBER_CAPS_ARCHIVE_MANAGE ((JabberCapsBits)1<<35) + +#define JABBER_FEAT_CAPTCHA "urn:xmpp:captcha" + // deferred #define JABBER_FEAT_ATTENTION "http://www.xmpp.org/extensions/xep-0224.html#ns" #define JABBER_CAPS_ATTENTION ((JabberCapsBits)1<<34) @@ -270,7 +278,7 @@ struct JabberFeatCapPair { const TCHAR *szFeature; JabberCapsBits jcbCap; - const TCHAR *szDescription; + const TCHAR *tszDescription; }; struct JabberFeatCapPairDynamic diff --git a/protocols/JabberG/src/jabber_chat.cpp b/protocols/JabberG/src/jabber_chat.cpp index df395a6ab2..94252d1047 100644 --- a/protocols/JabberG/src/jabber_chat.cpp +++ b/protocols/JabberG/src/jabber_chat.cpp @@ -854,18 +854,18 @@ public: void CJabberProto::AdminSet(const TCHAR *to, const TCHAR *ns, const TCHAR *szItem, const TCHAR *itemVal, const TCHAR *var, const TCHAR *varVal) { - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext(), to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(szItem, itemVal) << XATTR(var, varVal)); + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext(), to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(szItem, itemVal) << XATTR(var, varVal)); } void CJabberProto::AdminSetReason(const TCHAR *to, const TCHAR *ns, const TCHAR *szItem, const TCHAR *itemVal, const TCHAR *var, const TCHAR *varVal , const TCHAR *rsn) -{ m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext(), to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(szItem, itemVal) << XATTR(var, varVal) << XCHILD(_T("reason"), rsn)); +{ m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext(), to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(szItem, itemVal) << XATTR(var, varVal) << XCHILD(_T("reason"), rsn)); } void CJabberProto::AdminGet(const TCHAR *to, const TCHAR *ns, const TCHAR *var, const TCHAR *varVal, JABBER_IQ_PFUNC foo) { int id = SerialNext(); IqAdd(id, IQ_PROC_NONE, foo); - m_ThreadInfo->send(XmlNodeIq(_T("get"), id, to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(var, varVal)); + m_ThreadInfo->send( XmlNodeIq(_T("get"), id, to) << XQUERY(ns) << XCHILD(_T("item")) << XATTR(var, varVal)); } // Member info dialog @@ -1548,7 +1548,7 @@ int CJabberProto::JabberGcEventHook(WPARAM, LPARAM lParam) case GC_USER_CHANMGR: int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetMuc); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, item->jid) << XQUERY(xmlnsOwner)); + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, item->jid) << XQUERY(xmlnsOwner)); break; } diff --git a/protocols/JabberG/src/jabber_db_utils.h b/protocols/JabberG/src/jabber_db_utils.h index 60becf5ad9..349416839a 100644 --- a/protocols/JabberG/src/jabber_db_utils.h +++ b/protocols/JabberG/src/jabber_db_utils.h @@ -213,6 +213,7 @@ struct CJabberOptions CMOption UseTLS; CMOption AcceptNotes; CMOption AutosaveNotes; + CMOption EnableMsgArchive; CMOption RcMarkMessagesAsRead; CMOption ConnectionKeepAliveInterval; CMOption ConnectionKeepAliveTimeout; @@ -238,6 +239,7 @@ struct CJabberOptions DisableFrame(proto, "DisableFrame", TRUE), EnableAvatars(proto, "EnableAvatars", TRUE), EnableRemoteControl(proto, "EnableRemoteControl", FALSE), + EnableMsgArchive(proto, "EnableMsgArchive", FALSE), EnableUserActivity(proto, "EnableUserActivity", TRUE), EnableUserMood(proto, "EnableUserMood", TRUE), EnableUserTune(proto, "EnableUserTune", FALSE), diff --git a/protocols/JabberG/src/jabber_disco.cpp b/protocols/JabberG/src/jabber_disco.cpp index b6322ea0c6..e65d1c512e 100644 --- a/protocols/JabberG/src/jabber_disco.cpp +++ b/protocols/JabberG/src/jabber_disco.cpp @@ -1473,9 +1473,9 @@ void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM break; case SD_ACT_UNREGISTER: - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext(), pNode->GetJid()) << XQUERY(_T(JABBER_FEAT_REGISTER)) << XCHILD(_T("remove"))); + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext(), pNode->GetJid()) << XQUERY(_T(JABBER_FEAT_REGISTER)) << XCHILD(_T("remove"))); - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext()) << XQUERY(_T(JABBER_FEAT_IQ_ROSTER)) + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext()) << XQUERY(_T(JABBER_FEAT_IQ_ROSTER)) << XCHILD(_T("item")) << XATTR(_T("jid"), pNode->GetJid()) << XATTR(_T("subscription"), _T("remove"))); break; diff --git a/protocols/JabberG/src/jabber_events.cpp b/protocols/JabberG/src/jabber_events.cpp index 4af605937f..2562e0c5f0 100644 --- a/protocols/JabberG/src/jabber_events.cpp +++ b/protocols/JabberG/src/jabber_events.cpp @@ -52,11 +52,11 @@ int CJabberProto::OnContactDeleted(WPARAM wParam, LPARAM) JabberStripJid(m_ThreadInfo->fullJID, szStrippedJid, SIZEOF(szStrippedJid)); TCHAR *szDog = _tcschr(szStrippedJid, _T('@')); if (szDog && _tcsicmp(szDog + 1, dbv.ptszVal)) - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext(), dbv.ptszVal) << XQUERY(_T(JABBER_FEAT_REGISTER)) << XCHILD(_T("remove"))); + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext(), dbv.ptszVal) << XQUERY(_T(JABBER_FEAT_REGISTER)) << XCHILD(_T("remove"))); } // Remove from roster, server also handles the presence unsubscription process. - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext()) << XQUERY(_T(JABBER_FEAT_IQ_ROSTER)) + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext()) << XQUERY(_T(JABBER_FEAT_IQ_ROSTER)) << XCHILD(_T("item")) << XATTR(_T("jid"), dbv.ptszVal) << XATTR(_T("subscription"), _T("remove"))); } diff --git a/protocols/JabberG/src/jabber_ft.cpp b/protocols/JabberG/src/jabber_ft.cpp index 240e6d4bef..b9a354713a 100644 --- a/protocols/JabberG/src/jabber_ft.cpp +++ b/protocols/JabberG/src/jabber_ft.cpp @@ -491,7 +491,7 @@ BOOL CJabberProto::FtHandleIbbRequest(HXML iqNode, BOOL bOpen) item->jibb = jibb; JForkThread((JThreadFunc)&CJabberProto::IbbReceiveThread, jibb); - m_ThreadInfo->send(XmlNodeIq(_T("result"), id, from)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), id, from)); return TRUE; } // stream already open @@ -507,7 +507,7 @@ BOOL CJabberProto::FtHandleIbbRequest(HXML iqNode, BOOL bOpen) item->jibb->bStreamClosed = TRUE; SetEvent(item->jibb->hEvent); - m_ThreadInfo->send(XmlNodeIq(_T("result"), id, from)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), id, from)); return TRUE; } diff --git a/protocols/JabberG/src/jabber_groupchat.cpp b/protocols/JabberG/src/jabber_groupchat.cpp index 407fe2f433..e80628ec8f 100644 --- a/protocols/JabberG/src/jabber_groupchat.cpp +++ b/protocols/JabberG/src/jabber_groupchat.cpp @@ -323,11 +323,9 @@ void CJabberProto::GroupchatJoinRoom(const TCHAR *server, const TCHAR *room, con if (lasteventtime > 0) { _tzset(); lasteventtime += _timezone + 1; - struct tm* time = localtime(&lasteventtime); + TCHAR lasteventdate[20 + 1]; - mir_sntprintf(lasteventdate, SIZEOF(lasteventdate), _T("%04d-%02d-%02dT%02d:%02d:%02dZ"), - time->tm_year+1900, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); - x << XCHILD(_T("history")) << XATTR(_T("since"), lasteventdate); + x << XCHILD(_T("history")) << XATTR(_T("since"), time2str(lasteventtime, lasteventdate, SIZEOF(lasteventdate))); } } @@ -1088,7 +1086,7 @@ void CJabberProto::GroupchatProcessPresence(HXML node) // Request room config int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetMuc); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, item->jid) << XQUERY(xmlnsOwner)); + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, item->jid) << XQUERY(xmlnsOwner)); } mir_free(room); diff --git a/protocols/JabberG/src/jabber_ibb.cpp b/protocols/JabberG/src/jabber_ibb.cpp index 53137d50fc..dc66ce2196 100644 --- a/protocols/JabberG/src/jabber_ibb.cpp +++ b/protocols/JabberG/src/jabber_ibb.cpp @@ -56,7 +56,7 @@ BOOL CJabberProto::OnFtHandleIbbIq(HXML iqNode, CJabberIqInfo* pInfo) bOk = OnIbbRecvdData(xmlGetText(pInfo->GetChildNode()), sid, seq); if (bOk) - m_ThreadInfo->send(XmlNodeIq(_T("result"), pInfo)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), pInfo)); else m_ThreadInfo->send( XmlNodeIq(_T("error"), pInfo) @@ -151,7 +151,7 @@ void __cdecl CJabberProto::IbbReceiveThread(JABBER_IBB_TRANSFER *jibb) jibb->hEvent = NULL; if (jibb->state == JIBB_ERROR) - m_ThreadInfo->send(XmlNodeIq(_T("set"), SerialNext(), jibb->dstJID) << XCHILDNS(_T("close"), _T(JABBER_FEAT_IBB)) << XATTR(_T("sid"), jibb->sid)); + m_ThreadInfo->send( XmlNodeIq(_T("set"), SerialNext(), jibb->dstJID) << XCHILDNS(_T("close"), _T(JABBER_FEAT_IBB)) << XATTR(_T("sid"), jibb->sid)); if (jibb->bStreamClosed && jibb->dwTransferredSize == ft->dwExpectedRecvFileSize) jibb->state = JIBB_DONE; diff --git a/protocols/JabberG/src/jabber_iq_handlers.cpp b/protocols/JabberG/src/jabber_iq_handlers.cpp index ecf0064d61..da82862354 100644 --- a/protocols/JabberG/src/jabber_iq_handlers.cpp +++ b/protocols/JabberG/src/jabber_iq_handlers.cpp @@ -391,7 +391,7 @@ BOOL CJabberProto::OnIqRequestLastActivity(HXML, CJabberIqInfo *pInfo) // XEP-0199: XMPP Ping support BOOL CJabberProto::OnIqRequestPing(HXML, CJabberIqInfo *pInfo) { - m_ThreadInfo->send(XmlNodeIq(_T("result"), pInfo) << XATTR(_T("from"), m_ThreadInfo->fullJID)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), pInfo) << XATTR(_T("from"), m_ThreadInfo->fullJID)); return TRUE; } @@ -505,7 +505,7 @@ BOOL CJabberProto::OnIqRequestAvatar(HXML, CJabberIqInfo *pInfo) fclose(in); char* str = JabberBase64Encode(buffer, bytes); - m_ThreadInfo->send(XmlNodeIq(_T("result"), pInfo) << XQUERY(_T(JABBER_FEAT_AVATAR)) << XCHILD(_T("query"), _A2T(str)) << XATTR(_T("mimetype"), szMimeType)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), pInfo) << XQUERY(_T(JABBER_FEAT_AVATAR)) << XCHILD(_T("query"), _A2T(str)) << XATTR(_T("mimetype"), szMimeType)); mir_free(str); mir_free(buffer); return TRUE; diff --git a/protocols/JabberG/src/jabber_iqid.cpp b/protocols/JabberG/src/jabber_iqid.cpp index 664167b73f..74f8333634 100644 --- a/protocols/JabberG/src/jabber_iqid.cpp +++ b/protocols/JabberG/src/jabber_iqid.cpp @@ -67,16 +67,21 @@ void CJabberProto::OnIqResultServerDiscoInfo(HXML iqNode) << XQUERY(_T(JABBER_FEAT_GTALK_SHARED_STATUS)) << XATTR(_T("version"), _T("2"))); } } + if (m_ThreadInfo) { HXML feature; for (i = 1; (feature = xmlGetNthChild(query, _T("feature"), i)) != NULL; i++) { const TCHAR *featureName = xmlGetAttrValue(feature, _T("var")); - if (featureName) { - for (int j = 0; g_JabberFeatCapPairs[j].szFeature; j++) { - if ( !_tcscmp(g_JabberFeatCapPairs[j].szFeature, featureName)) { - m_ThreadInfo->jabberServerCaps |= g_JabberFeatCapPairs[j].jcbCap; - break; - } } } } } + if (!featureName) + continue; + + for (int j = 0; g_JabberFeatCapPairs[j].szFeature; j++) + if ( !_tcscmp(g_JabberFeatCapPairs[j].szFeature, featureName)) { + m_ThreadInfo->jabberServerCaps |= g_JabberFeatCapPairs[j].jcbCap; + break; + } + } + } OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_SERVERINFO); } } @@ -126,9 +131,11 @@ void CJabberProto::OnProcessLoginRq(ThreadData* info, DWORD rq) info->dwLoginRqs |= rq; - if ((info->dwLoginRqs & JABBER_LOGIN_ROSTER) && (info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS) && - (info->dwLoginRqs & JABBER_LOGIN_SERVERINFO) && !(info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS_AJ)) - { + DWORD dwMask = JABBER_LOGIN_ROSTER | JABBER_LOGIN_BOOKMARKS | JABBER_LOGIN_SERVERINFO; + if ((info->dwLoginRqs & dwMask) == dwMask && !(info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS_AJ)) { + if (info->jabberServerCaps & JABBER_CAPS_ARCHIVE_AUTO) + EnableArchive(m_options.EnableMsgArchive != 0); + if (jabberChatDllPresent && m_options.AutoJoinBookmarks) { LIST ll(10); LISTFOREACH(i, this, LIST_BOOKMARK) @@ -153,7 +160,8 @@ void CJabberProto::OnProcessLoginRq(ThreadData* info, DWORD rq) TCHAR* nick = JabberNickFromJID(m_szJabberJID); GroupchatJoinRoom(server, p, nick, item->password, true); mir_free(nick); - } } + } + } ll.destroy(); } @@ -196,7 +204,7 @@ void CJabberProto::OnLoggedIn() m_ThreadInfo->jabberServerCaps = JABBER_RESOURCE_CAPS_NONE; iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultServerDiscoInfo); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, _A2T(m_ThreadInfo->server)) << XQUERY(_T(JABBER_FEAT_DISCO_INFO))); + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, _A2T(m_ThreadInfo->server)) << XQUERY(_T(JABBER_FEAT_DISCO_INFO))); QueryPrivacyLists(m_ThreadInfo); diff --git a/protocols/JabberG/src/jabber_list.cpp b/protocols/JabberG/src/jabber_list.cpp index 28608e5121..25f69bebed 100644 --- a/protocols/JabberG/src/jabber_list.cpp +++ b/protocols/JabberG/src/jabber_list.cpp @@ -151,8 +151,8 @@ JABBER_LIST_ITEM *CJabberProto::ListAdd(JABBER_LIST list, const TCHAR *jid) bUseResource=TRUE; } } - item = (JABBER_LIST_ITEM*)mir_alloc(sizeof(JABBER_LIST_ITEM)); - ZeroMemory(item, sizeof(JABBER_LIST_ITEM)); + + item = (JABBER_LIST_ITEM*)mir_calloc(sizeof(JABBER_LIST_ITEM)); item->list = list; item->jid = s; item->itemResource.status = ID_STATUS_OFFLINE; diff --git a/protocols/JabberG/src/jabber_list.h b/protocols/JabberG/src/jabber_list.h index 24beee26c7..7c7d3d2014 100644 --- a/protocols/JabberG/src/jabber_list.h +++ b/protocols/JabberG/src/jabber_list.h @@ -183,6 +183,7 @@ struct JABBER_LIST_ITEM BOOL bAutoJoin; BOOL bUseResource; + BOOL bHistoryRead; }; struct JABBER_HTTP_AVATARS diff --git a/protocols/JabberG/src/jabber_menu.cpp b/protocols/JabberG/src/jabber_menu.cpp index 95c0ee8b36..0ee785ef41 100644 --- a/protocols/JabberG/src/jabber_menu.cpp +++ b/protocols/JabberG/src/jabber_menu.cpp @@ -1085,6 +1085,10 @@ int CJabberProto::OnProcessSrmmEvent(WPARAM, LPARAM lParam) if ( !hDialogsList) hDialogsList = (HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST, 0, 0); WindowList_Add(hDialogsList, event->hwndWindow, event->hContact); + + JABBER_LIST_ITEM *pItem = GetItemFromContact(event->hContact); + if (pItem && (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_ARCHIVE_AUTO) && m_options.EnableMsgArchive) + RetrieveMessageArchive(event->hContact, pItem); } else if (event->uType == MSG_WINDOW_EVT_CLOSING) { if (hDialogsList) @@ -1110,12 +1114,11 @@ int CJabberProto::OnProcessSrmmEvent(WPARAM, LPARAM lParam) r->bMessageSessionActive = FALSE; JabberCapsBits jcb = GetResourceCapabilites(jid, TRUE); - if (jcb & JABBER_CAPS_CHATSTATES) { - int iqId = SerialNext(); + if (jcb & JABBER_CAPS_CHATSTATES) m_ThreadInfo->send( - XmlNode(_T("message")) << XATTR(_T("to"), jid) << XATTR(_T("type"), _T("chat")) << XATTRID(iqId) + XmlNode(_T("message")) << XATTR(_T("to"), jid) << XATTR(_T("type"), _T("chat")) << XATTRID( SerialNext()) << XCHILDNS(_T("gone"), _T(JABBER_FEAT_CHATSTATES))); - } } } } + } } } return 0; } diff --git a/protocols/JabberG/src/jabber_opt.cpp b/protocols/JabberG/src/jabber_opt.cpp index cdf57f3f91..d86da765e8 100644 --- a/protocols/JabberG/src/jabber_opt.cpp +++ b/protocols/JabberG/src/jabber_opt.cpp @@ -836,6 +836,7 @@ public: m_otvOptions.AddOption(LPGENT("Messaging") _T("/") LPGENT("Enable user activity receiving"), m_proto->m_options.EnableUserActivity); m_otvOptions.AddOption(LPGENT("Messaging") _T("/") LPGENT("Receive notes"), m_proto->m_options.AcceptNotes); m_otvOptions.AddOption(LPGENT("Messaging") _T("/") LPGENT("Automatically save received notes"), m_proto->m_options.AutosaveNotes); + m_otvOptions.AddOption(LPGENT("Messaging") _T("/") LPGENT("Enable server-side history"), m_proto->m_options.EnableMsgArchive); m_otvOptions.AddOption(LPGENT("Server options") _T("/") LPGENT("Disable SASL authentication (for old servers)"), m_proto->m_options.Disable3920auth); m_otvOptions.AddOption(LPGENT("Server options") _T("/") LPGENT("Enable stream compression (if possible)"), m_proto->m_options.EnableZlib); diff --git a/protocols/JabberG/src/jabber_privacy.cpp b/protocols/JabberG/src/jabber_privacy.cpp index c2ec58912e..6865acb1bf 100644 --- a/protocols/JabberG/src/jabber_privacy.cpp +++ b/protocols/JabberG/src/jabber_privacy.cpp @@ -279,7 +279,7 @@ void CJabberProto::OnIqResultPrivacyLists(HXML iqNode, CJabberIqInfo* pInfo) if (m_pDlgPrivacyLists) { int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultPrivacyList); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId) << XQUERY(_T(JABBER_FEAT_PRIVACY_LISTS)) << XCHILD(_T("list")) << XATTR(_T("name"), listName)); + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId) << XQUERY(_T(JABBER_FEAT_PRIVACY_LISTS)) << XCHILD(_T("list")) << XATTR(_T("name"), listName)); } } } const TCHAR *szName = NULL; diff --git a/protocols/JabberG/src/jabber_proto.cpp b/protocols/JabberG/src/jabber_proto.cpp index 68d8d7bd8e..5d110f17e8 100644 --- a/protocols/JabberG/src/jabber_proto.cpp +++ b/protocols/JabberG/src/jabber_proto.cpp @@ -636,7 +636,7 @@ int __cdecl CJabberProto::FileDeny(HANDLE, HANDLE hTransfer, const TCHAR *) switch (ft->type) { case FT_OOB: - m_ThreadInfo->send(XmlNodeIq(_T("error"), ft->iqId, ft->jid) << XCHILD(_T("error"), _T("File transfer refused")) << XATTRI(_T("code"), 406)); + m_ThreadInfo->send( XmlNodeIq(_T("error"), ft->iqId, ft->jid) << XCHILD(_T("error"), _T("File transfer refused")) << XATTRI(_T("code"), 406)); break; case FT_BYTESTREAM: @@ -900,7 +900,7 @@ HANDLE __cdecl CJabberProto::SearchByEmail(const TCHAR *email) int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_GETSEARCH, &CJabberProto::OnIqResultSetSearch); - m_ThreadInfo->send(XmlNodeIq(_T("set"), iqId, _A2T(szServerName)) << XQUERY(_T("jabber:iq:search")) + m_ThreadInfo->send( XmlNodeIq(_T("set"), iqId, _A2T(szServerName)) << XQUERY(_T("jabber:iq:search")) << XCHILD(_T("email"), email)); return (HANDLE)iqId; } diff --git a/protocols/JabberG/src/jabber_proto.h b/protocols/JabberG/src/jabber_proto.h index 96fd2665c4..7b0970ef86 100644 --- a/protocols/JabberG/src/jabber_proto.h +++ b/protocols/JabberG/src/jabber_proto.h @@ -397,6 +397,14 @@ struct CJabberProto : public PROTO_INTERFACE, public MZeroedObject void ContactMenuAdhocCommands(struct CJabberAdhocStartupParams* param); + //---- jabber_archive.c -------------------------------------------------------------- + + void EnableArchive(bool bEnable); + void RetrieveMessageArchive(HANDLE hContact, JABBER_LIST_ITEM *pItem); + + void OnIqResultGetCollection(HXML iqNode); + void OnIqResultGetCollectionList(HXML iqNode); + //---- jabber_bookmarks.c ------------------------------------------------------------ INT_PTR __cdecl OnMenuHandleBookmarks(WPARAM wParam, LPARAM lParam); @@ -893,6 +901,7 @@ struct CJabberProto : public PROTO_INTERFACE, public MZeroedObject //---- jabber_util.c ----------------------------------------------------------------- + JABBER_LIST_ITEM* GetItemFromContact(HANDLE hContact); JABBER_RESOURCE_STATUS* ResourceInfoFromJID(const TCHAR *jid); void SerialInit(void); diff --git a/protocols/JabberG/src/jabber_search.cpp b/protocols/JabberG/src/jabber_search.cpp index 87557a4fef..4e65cda1a2 100644 --- a/protocols/JabberG/src/jabber_search.cpp +++ b/protocols/JabberG/src/jabber_search.cpp @@ -467,7 +467,7 @@ int CJabberProto::SearchRenewFields(HWND hwndDlg, JabberSearchData * dat) int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_GETSEARCHFIELDS, &CJabberProto::OnIqResultGetSearchFields); - m_ThreadInfo->send(XmlNodeIq(_T("get"), iqId, szServerName) << XQUERY(_T("jabber:iq:search"))); + m_ThreadInfo->send( XmlNodeIq(_T("get"), iqId, szServerName) << XQUERY(_T("jabber:iq:search"))); return iqId; } diff --git a/protocols/JabberG/src/jabber_thread.cpp b/protocols/JabberG/src/jabber_thread.cpp index b116a87ca6..213de6f0a6 100644 --- a/protocols/JabberG/src/jabber_thread.cpp +++ b/protocols/JabberG/src/jabber_thread.cpp @@ -478,7 +478,7 @@ LBL_FatalError: if (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PING) { CJabberIqInfo* pInfo = m_iqManager.AddHandler(&CJabberProto::OnPingReply, JABBER_IQ_TYPE_GET, NULL, 0, -1, this); pInfo->SetTimeout(m_options.ConnectionKeepAliveTimeout); - info->send(XmlNodeIq(pInfo) << XATTR(_T("from"), m_ThreadInfo->fullJID) << XCHILDNS(_T("ping"), _T(JABBER_FEAT_PING))); + info->send( XmlNodeIq(pInfo) << XATTR(_T("from"), m_ThreadInfo->fullJID) << XCHILDNS(_T("ping"), _T(JABBER_FEAT_PING))); } else info->send(" \t "); continue; @@ -627,7 +627,7 @@ recvRest: void CJabberProto::PerformRegistration(ThreadData* info) { iqIdRegGetReg = SerialNext(); - info->send(XmlNodeIq(_T("get"), iqIdRegGetReg, NULL) << XQUERY(_T(JABBER_FEAT_REGISTER))); + info->send( XmlNodeIq(_T("get"), iqIdRegGetReg, NULL) << XQUERY(_T(JABBER_FEAT_REGISTER))); SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 50, (LPARAM)TranslateT("Requesting registration instruction...")); } @@ -637,7 +637,7 @@ void CJabberProto::PerformIqAuth(ThreadData* info) if (info->type == JABBER_SESSION_NORMAL) { int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetAuth); - info->send(XmlNodeIq(_T("get"), iqId) << XQUERY(_T("jabber:iq:auth")) << XCHILD(_T("username"), info->username)); + info->send( XmlNodeIq(_T("get"), iqId) << XQUERY(_T("jabber:iq:auth")) << XCHILD(_T("username"), info->username)); } else if (info->type == JABBER_SESSION_REGISTER) PerformRegistration(info); @@ -1886,7 +1886,7 @@ BOOL CJabberProto::OnProcessJingle(HXML node) LPCTSTR from = xmlGetAttrValue(node, _T("from")); if (szAction && !_tcscmp(szAction, _T("session-initiate"))) { // if this is a Jingle 'session-initiate' and noone processed it yet, reply with "unsupported-applications" - m_ThreadInfo->send(XmlNodeIq(_T("result"), idStr, from)); + m_ThreadInfo->send( XmlNodeIq(_T("result"), idStr, from)); XmlNodeIq iq(_T("set"), SerialNext(), from); HXML jingleNode = iq << XCHILDNS(_T("jingle"), _T(JABBER_FEAT_JINGLE)); diff --git a/protocols/JabberG/src/jabber_userinfo.cpp b/protocols/JabberG/src/jabber_userinfo.cpp index 0cad014101..09aa3094b6 100644 --- a/protocols/JabberG/src/jabber_userinfo.cpp +++ b/protocols/JabberG/src/jabber_userinfo.cpp @@ -276,8 +276,8 @@ static void sttFillResourceInfo(CJabberProto* ppro, HWND hwndTree, HTREEITEM hti for (i = 0; g_JabberFeatCapPairs[i].szFeature; i++) if (jcb & g_JabberFeatCapPairs[i].jcbCap) { TCHAR szDescription[ 1024 ]; - if (g_JabberFeatCapPairs[i].szDescription) - mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(g_JabberFeatCapPairs[i].szDescription), g_JabberFeatCapPairs[i].szFeature); + if (g_JabberFeatCapPairs[i].tszDescription) + mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s (%s)"), TranslateTS(g_JabberFeatCapPairs[i].tszDescription), g_JabberFeatCapPairs[i].szFeature); else mir_sntprintf(szDescription, SIZEOF(szDescription), _T("%s"), g_JabberFeatCapPairs[i].szFeature); sttFillInfoLine(hwndTree, htiCaps, NULL, NULL, szDescription, sttInfoLineId(resource, INFOLINE_CAPS, i)); diff --git a/protocols/JabberG/src/jabber_util.cpp b/protocols/JabberG/src/jabber_util.cpp index 3c9dbc4b71..924270fc27 100644 --- a/protocols/JabberG/src/jabber_util.cpp +++ b/protocols/JabberG/src/jabber_util.cpp @@ -197,6 +197,17 @@ JABBER_RESOURCE_STATUS* CJabberProto::ResourceInfoFromJID(const TCHAR *jid) return r; } +JABBER_LIST_ITEM* CJabberProto::GetItemFromContact(HANDLE hContact) +{ + DBVARIANT dbv; + if (JGetStringT(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; @@ -1253,6 +1264,31 @@ 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) -- cgit v1.2.3