/* Jabber Protocol Plugin for Miranda IM Copyright ( C ) 2002-04 Santithorn Bunchua Copyright ( C ) 2005-12 George Hazan Copyright ( C ) 2007 Maxim Mluhov 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_list.h" #include "jabber_iq.h" #include "jabber_caps.h" #include "jabber_privacy.h" #include "m_genmenu.h" #include "m_clistint.h" void CJabberProto::OnIqResultServerDiscoInfo( HXML iqNode ) { if ( !iqNode ) return; const TCHAR *type = xmlGetAttrValue( iqNode, _T("type")); int i; if ( !_tcscmp( type, _T("result"))) { HXML query = xmlGetChildByTag( iqNode, "query", "xmlns", _T(JABBER_FEAT_DISCO_INFO)); if ( !query ) return; HXML identity; for ( i = 1; ( identity = xmlGetNthChild( query, _T("identity"), i )) != NULL; i++ ) { const TCHAR *identityCategory = xmlGetAttrValue( identity, _T("category")); const TCHAR *identityType = xmlGetAttrValue( identity, _T("type")); const TCHAR *identityName = xmlGetAttrValue( identity, _T("name")); if ( identityCategory && identityType && !_tcscmp( identityCategory, _T("pubsub")) && !_tcscmp( identityType, _T("pep"))) { m_bPepSupported = TRUE; EnableMenuItems( TRUE ); RebuildInfoFrame(); } else if ( identityCategory && identityType && identityName && !_tcscmp( identityCategory, _T("server")) && !_tcscmp( identityType, _T("im")) && !_tcscmp( identityName, _T("Google Talk"))) { m_ThreadInfo->jabberServerCaps |= JABBER_CAPS_PING; m_bGoogleTalk = true; // Google Shared Status m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultGoogleSharedStatus, JABBER_IQ_TYPE_GET)) << 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; } } } } } OnProcessLoginRq( m_ThreadInfo, JABBER_LOGIN_SERVERINFO); } } void CJabberProto::OnIqResultNestedRosterGroups( HXML iqNode, CJabberIqInfo* pInfo ) { const TCHAR *szGroupDelimeter = NULL; BOOL bPrivateStorageSupport = FALSE; if ( iqNode && pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) { bPrivateStorageSupport = TRUE; szGroupDelimeter = XPathFmt( iqNode, _T("query[@xmlns='%s']/roster[@xmlns='%s']"), _T(JABBER_FEAT_PRIVATE_STORAGE), _T( JABBER_FEAT_NESTED_ROSTER_GROUPS )); if ( szGroupDelimeter && !szGroupDelimeter[0] ) szGroupDelimeter = NULL; // "" as roster delimeter is not supported :) } // global fuckup if ( !m_ThreadInfo ) return; // is our default delimiter? if (( !szGroupDelimeter && bPrivateStorageSupport ) || ( szGroupDelimeter && _tcscmp( szGroupDelimeter, _T("\\")))) m_ThreadInfo->send( XmlNodeIq( _T("set"), SerialNext()) << XQUERY( _T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILD( _T("roster"), _T("\\")) << XATTR( _T("xmlns"), _T(JABBER_FEAT_NESTED_ROSTER_GROUPS))); // roster request TCHAR *szUserData = mir_tstrdup( szGroupDelimeter ? szGroupDelimeter : _T("\\")); m_ThreadInfo->send( XmlNodeIq( m_iqManager.AddHandler( &CJabberProto::OnIqResultGetRoster, JABBER_IQ_TYPE_GET, NULL, 0, -1, (void *)szUserData )) << XCHILDNS( _T("query"), _T(JABBER_FEAT_IQ_ROSTER))); } void CJabberProto::OnIqResultNotes( HXML iqNode, CJabberIqInfo* pInfo ) { if ( iqNode && pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT ) { HXML hXmlData = XPathFmt( iqNode, _T("query[@xmlns='%s']/storage[@xmlns='%s']"), _T(JABBER_FEAT_PRIVATE_STORAGE), _T(JABBER_FEAT_MIRANDA_NOTES)); if (hXmlData) m_notes.LoadXml(hXmlData); } } void CJabberProto::OnProcessLoginRq( ThreadData* info, DWORD rq ) { if ( info == NULL ) return; 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)) { if ( jabberChatDllPresent && m_options.AutoJoinBookmarks) { LIST ll( 10 ); LISTFOREACH(i, this, LIST_BOOKMARK) { JABBER_LIST_ITEM* item = ListGetItemPtrFromIndex( i ); if ( item != NULL && !lstrcmp( item->type, _T("conference")) && item->bAutoJoin ) ll.insert( item ); } for ( int j=0; j < ll.getCount(); j++ ) { JABBER_LIST_ITEM* item = ll[j]; TCHAR room[256], *server, *p; TCHAR text[128]; _tcsncpy( text, item->jid, SIZEOF( text )); _tcsncpy( room, text, SIZEOF( room )); p = _tcstok( room, _T( "@" )); server = _tcstok( NULL, _T( "@" )); if ( item->nick && item->nick[0] != 0 ) GroupchatJoinRoom( server, p, item->nick, item->password, true ); else { TCHAR* nick = JabberNickFromJID( m_szJabberJID ); GroupchatJoinRoom( server, p, nick, item->password, true ); mir_free( nick ); } } ll.destroy(); } OnProcessLoginRq( info, JABBER_LOGIN_BOOKMARKS_AJ ); } } void CJabberProto::OnLoggedIn() { m_bJabberOnline = TRUE; m_tmJabberLoggedInTime = time(0); m_ThreadInfo->dwLoginRqs = 0; // XEP-0083 support { CJabberIqInfo* pIqInfo = m_iqManager.AddHandler( &CJabberProto::OnIqResultNestedRosterGroups, JABBER_IQ_TYPE_GET ); // ugly hack to prevent hangup during login process pIqInfo->SetTimeout( 30000 ); m_ThreadInfo->send( XmlNodeIq( pIqInfo ) << XQUERY( _T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS( _T("roster"), _T(JABBER_FEAT_NESTED_ROSTER_GROUPS))); } // Server-side notes { m_ThreadInfo->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultNotes, JABBER_IQ_TYPE_GET)) << XQUERY( _T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS( _T("storage"), _T(JABBER_FEAT_MIRANDA_NOTES))); } int iqId = SerialNext(); IqAdd( iqId, IQ_PROC_DISCOBOOKMARKS, &CJabberProto::OnIqResultDiscoBookmarks); m_ThreadInfo->send( XmlNodeIq( _T("get"), iqId) << XQUERY( _T(JABBER_FEAT_PRIVATE_STORAGE)) << XCHILDNS( _T("storage"), _T("storage:bookmarks"))); m_bPepSupported = FALSE; 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))); QueryPrivacyLists( m_ThreadInfo ); char szServerName[ sizeof(m_ThreadInfo->server) ]; if ( JGetStaticString( "LastLoggedServer", NULL, szServerName, sizeof(szServerName))) SendGetVcard( m_szJabberJID ); else if ( strcmp( m_ThreadInfo->server, szServerName )) SendGetVcard( m_szJabberJID ); JSetString( NULL, "LastLoggedServer", m_ThreadInfo->server ); m_pepServices.ResetPublishAll(); } void CJabberProto::OnIqResultGetAuth( HXML iqNode ) { // RECVED: result of the request for authentication method // ACTION: send account authentication information to log in Log( " iqIdGetAuth" ); HXML queryNode; const TCHAR* type; if (( type=xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( queryNode=xmlGetChild( iqNode , "query" )) == NULL ) return; if ( !lstrcmp( type, _T("result"))) { int iqId = SerialNext(); IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultSetAuth ); XmlNodeIq iq( _T("set"), iqId ); HXML query = iq << XQUERY( _T("jabber:iq:auth")); query << XCHILD( _T("username"), m_ThreadInfo->username ); if ( xmlGetChild( queryNode, "digest" ) != NULL && m_szStreamId ) { char* str = mir_utf8encodeT( m_ThreadInfo->password ); char text[200]; mir_snprintf( text, SIZEOF(text), "%s%s", m_szStreamId, str ); mir_free( str ); if (( str=JabberSha1( text )) != NULL ) { query << XCHILD( _T("digest"), _A2T(str)); mir_free( str ); } } else if ( xmlGetChild( queryNode, "password" ) != NULL ) query << XCHILD( _T("password"), m_ThreadInfo->password ); else { Log( "No known authentication mechanism accepted by the server." ); m_ThreadInfo->send( "" ); return; } if ( xmlGetChild( queryNode , "resource" ) != NULL ) query << XCHILD( _T("resource"), m_ThreadInfo->resource ); m_ThreadInfo->send( iq ); } else if ( !lstrcmp( type, _T("error"))) { m_ThreadInfo->send( "" ); TCHAR text[128]; mir_sntprintf( text, SIZEOF( text ), _T("%s %s."), TranslateT( "Authentication failed for" ), m_ThreadInfo->username ); MsgPopup( NULL, text, TranslateT( "Jabber Authentication" )); JSendBroadcast( NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD ); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultSetAuth( HXML iqNode ) { const TCHAR* type; // RECVED: authentication result // ACTION: if successfully logged in, continue by requesting roster list and set my initial status Log( " iqIdSetAuth" ); if (( type=xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if ( !lstrcmp( type, _T("result"))) { DBVARIANT dbv; if ( JGetStringT( NULL, "Nick", &dbv )) JSetStringT( NULL, "Nick", m_ThreadInfo->username ); else JFreeVariant( &dbv ); OnLoggedIn(); } // What to do if password error? etc... else if ( !lstrcmp( type, _T("error"))) { TCHAR text[128]; m_ThreadInfo->send( "" ); mir_sntprintf( text, SIZEOF( text ), _T("%s %s."), TranslateT( "Authentication failed for" ), m_ThreadInfo->username ); MsgPopup( NULL, text, TranslateT( "Jabber Authentication" )); JSendBroadcast( NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD ); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultBind( HXML iqNode, CJabberIqInfo* pInfo ) { if ( !m_ThreadInfo || !iqNode ) return; if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) { LPCTSTR szJid = XPathT( iqNode, "bind[@xmlns='urn:ietf:params:xml:ns:xmpp-bind']/jid" ); if ( szJid ) { if ( !_tcsncmp( m_ThreadInfo->fullJID, szJid, SIZEOF( m_ThreadInfo->fullJID ))) Log( "Result Bind: " TCHAR_STR_PARAM " confirmed ", m_ThreadInfo->fullJID ); else { Log( "Result Bind: " TCHAR_STR_PARAM " changed to " TCHAR_STR_PARAM, m_ThreadInfo->fullJID, szJid); _tcsncpy( m_ThreadInfo->fullJID, szJid, SIZEOF( m_ThreadInfo->fullJID )); } } if ( m_ThreadInfo->bIsSessionAvailable ) m_ThreadInfo->send( XmlNodeIq( m_iqManager.AddHandler( &CJabberProto::OnIqResultSession, JABBER_IQ_TYPE_SET )) << XCHILDNS( _T("session"), _T("urn:ietf:params:xml:ns:xmpp-session" ))); else OnLoggedIn(); } else { //rfc3920 page 39 m_ThreadInfo->send( "" ); m_ThreadInfo = NULL; // To disallow auto reconnect } } void CJabberProto::OnIqResultSession( HXML iqNode, CJabberIqInfo* pInfo ) { if ( pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) OnLoggedIn(); } void CJabberProto::GroupchatJoinByHContact( HANDLE hContact, bool autojoin ) { TCHAR* roomjid = JGetStringT( hContact, "ChatRoomID" ); if ( !roomjid ) return; TCHAR* room = roomjid; TCHAR* server = _tcschr( roomjid, '@' ); if ( !server ) { mir_free( roomjid ); return; } server[0] = 0; server++; TCHAR *nick = JGetStringT( hContact, "MyNick" ); if ( !nick ) { nick = JabberNickFromJID( m_szJabberJID ); if ( !nick ) { mir_free( roomjid ); return; } } TCHAR *password = JGetStringCrypt( hContact, "LoginPassword" ); GroupchatJoinRoom( server, room, nick, password, autojoin ); mir_free( password ); mir_free( nick ); mir_free( roomjid ); } ///////////////////////////////////////////////////////////////////////////////////////// // JabberIqResultGetRoster - populates LIST_ROSTER and creates contact for any new rosters void CJabberProto::OnIqResultGetRoster( HXML iqNode, CJabberIqInfo* pInfo ) { Log( " iqIdGetRoster" ); TCHAR *szGroupDelimeter = (TCHAR *)pInfo->GetUserData(); if ( pInfo->GetIqType() != JABBER_IQ_TYPE_RESULT ) { mir_free( szGroupDelimeter ); return; } HXML queryNode = xmlGetChild( iqNode , "query" ); if ( queryNode == NULL ) { mir_free( szGroupDelimeter ); return; } if ( lstrcmp( xmlGetAttrValue( queryNode, _T("xmlns")), _T(JABBER_FEAT_IQ_ROSTER))) { mir_free( szGroupDelimeter ); return; } if ( !_tcscmp( szGroupDelimeter, _T("\\"))) { mir_free( szGroupDelimeter ); szGroupDelimeter = NULL; } TCHAR* nick; int i; LIST chatRooms(10); OBJLIST *httpavatars = new OBJLIST(20, JABBER_HTTP_AVATARS::compare); for ( i=0; ; i++ ) { BOOL bIsTransport=FALSE; HXML itemNode = xmlGetChild( queryNode ,i); if ( !itemNode ) break; if ( _tcscmp( xmlGetName( itemNode ), _T("item"))) continue; const TCHAR* str = xmlGetAttrValue( itemNode, _T("subscription")), *name; JABBER_SUBSCRIPTION sub; if ( str == NULL ) sub = SUB_NONE; else if ( !_tcscmp( str, _T("both"))) sub = SUB_BOTH; else if ( !_tcscmp( str, _T("to"))) sub = SUB_TO; else if ( !_tcscmp( str, _T("from"))) sub = SUB_FROM; else sub = SUB_NONE; const TCHAR* jid = xmlGetAttrValue( itemNode, _T("jid")); if ( jid == NULL ) continue; if ( _tcschr( jid, '@' ) == NULL ) bIsTransport = TRUE; if (( name = xmlGetAttrValue( itemNode, _T("name"))) != NULL ) nick = mir_tstrdup( name ); else nick = JabberNickFromJID( jid ); if ( nick == NULL ) continue; JABBER_LIST_ITEM* item = ListAdd( LIST_ROSTER, jid ); item->subscription = sub; mir_free( item->nick ); item->nick = nick; HXML groupNode = xmlGetChild( itemNode , "group" ); replaceStrT( item->group, ( groupNode ) ? xmlGetText( groupNode ) : NULL ); // check group delimiters: if ( item->group && szGroupDelimeter ) { TCHAR *szPos = NULL; while ( szPos = _tcsstr( item->group, szGroupDelimeter )) { *szPos = 0; szPos += _tcslen( szGroupDelimeter ); TCHAR *szNewGroup = (TCHAR *)mir_alloc( sizeof(TCHAR) * ( _tcslen( item->group ) + _tcslen( szPos ) + 2)); _tcscpy( szNewGroup, item->group ); _tcscat( szNewGroup, _T("\\")); _tcscat( szNewGroup, szPos ); mir_free( item->group ); item->group = szNewGroup; } } HANDLE hContact = HContactFromJID( jid ); if ( hContact == NULL ) { // Received roster has a new JID. // Add the jid ( with empty resource ) to Miranda contact list. hContact = DBCreateContact( jid, nick, FALSE, FALSE ); } if ( name != NULL ) { DBVARIANT dbNick; if ( !JGetStringT( hContact, "Nick", &dbNick )) { if ( lstrcmp( nick, dbNick.ptszVal ) != 0 ) DBWriteContactSettingTString( hContact, "CList", "MyHandle", nick ); else DBDeleteContactSetting( hContact, "CList", "MyHandle" ); JFreeVariant( &dbNick ); } else DBWriteContactSettingTString( hContact, "CList", "MyHandle", nick ); } else DBDeleteContactSetting( hContact, "CList", "MyHandle" ); if ( JGetByte( hContact, "ChatRoom", 0 )) { GCSESSION gcw = {0}; gcw.cbSize = sizeof(GCSESSION); gcw.iType = GCW_CHATROOM; gcw.pszModule = m_szModuleName; gcw.dwFlags = GC_TCHAR; gcw.ptszID = jid; gcw.ptszName = NEWTSTR_ALLOCA( jid ); TCHAR* p = (TCHAR*)_tcschr( gcw.ptszName, '@' ); if ( p ) *p = 0; CallServiceSync( MS_GC_NEWSESSION, 0, ( LPARAM )&gcw ); DBDeleteContactSetting( hContact, "CList", "Hidden" ); chatRooms.insert( hContact ); } else { UpdateSubscriptionInfo(hContact, item); } if (!m_options.IgnoreRosterGroups) { if ( item->group != NULL ) { JabberContactListCreateGroup( item->group ); // Don't set group again if already correct, or Miranda may show wrong group count in some case DBVARIANT dbv; if ( !DBGetContactSettingTString( hContact, "CList", "Group", &dbv )) { if ( lstrcmp( dbv.ptszVal, item->group )) DBWriteContactSettingTString( hContact, "CList", "Group", item->group ); JFreeVariant( &dbv ); } else DBWriteContactSettingTString( hContact, "CList", "Group", item->group ); } else DBDeleteContactSetting( hContact, "CList", "Group" ); } if ( hContact != NULL ) { if ( bIsTransport) JSetByte( hContact, "IsTransport", TRUE ); else JSetByte( hContact, "IsTransport", FALSE ); } const TCHAR* imagepath = xmlGetAttrValue(itemNode, _T("vz:img")); if (imagepath) httpavatars->insert(new JABBER_HTTP_AVATARS(imagepath, hContact)); } if (httpavatars->getCount()) JForkThread(&CJabberProto::LoadHttpAvatars, httpavatars); else delete httpavatars; // Delete orphaned contacts ( if roster sync is enabled ) if ( m_options.RosterSync == TRUE ) { int listSize = 0, listAllocSize = 0; HANDLE* list = NULL; HANDLE hContact = ( HANDLE ) CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); while ( hContact != NULL ) { char* str = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM ) hContact, 0 ); if ( str != NULL && !strcmp( str, m_szModuleName )) { DBVARIANT dbv; if ( !JGetStringT( hContact, "jid", &dbv )) { if ( !ListExist( LIST_ROSTER, dbv.ptszVal )) { Log( "Syncing roster: preparing to delete " TCHAR_STR_PARAM " ( hContact=0x%x )", dbv.ptszVal, hContact ); if ( listSize >= listAllocSize ) { listAllocSize = listSize + 100; if (( list=( HANDLE * ) mir_realloc( list, listAllocSize * sizeof( HANDLE ))) == NULL ) { listSize = 0; break; } } list[listSize++] = hContact; } JFreeVariant( &dbv ); } } hContact = ( HANDLE ) CallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM ) hContact, 0 ); } for ( i=0; i < listSize; i++ ) { Log( "Syncing roster: deleting 0x%x", list[i] ); CallService( MS_DB_CONTACT_DELETE, ( WPARAM ) list[i], 0 ); } if ( list != NULL ) mir_free( list ); } EnableMenuItems( TRUE ); Log( "Status changed via THREADSTART" ); m_bModeMsgStatusChangePending = FALSE; SetServerStatus( m_iDesiredStatus ); if ( m_options.AutoJoinConferences ) { for ( i=0; i < chatRooms.getCount(); i++ ) GroupchatJoinByHContact(( HANDLE )chatRooms[i], true); } chatRooms.destroy(); //UI_SAFE_NOTIFY(m_pDlgJabberJoinGroupchat, WM_JABBER_CHECK_ONLINE); //UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_CHECK_ONLINE); UI_SAFE_NOTIFY_HWND(m_hwndJabberAddBookmark, WM_JABBER_CHECK_ONLINE); WindowNotify(WM_JABBER_CHECK_ONLINE); UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH); if ( szGroupDelimeter ) mir_free( szGroupDelimeter ); OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_ROSTER); RebuildInfoFrame(); } void CJabberProto::OnIqResultGetRegister( HXML iqNode ) { // RECVED: result of the request for ( agent ) registration mechanism // ACTION: activate ( agent ) registration input dialog Log( " iqIdGetRegister" ); HXML queryNode; const TCHAR *type; if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( queryNode = xmlGetChild( iqNode , "query" )) == NULL ) return; if ( !lstrcmp( type, _T("result"))) { if ( m_hwndAgentRegInput ) SendMessage( m_hwndAgentRegInput, WM_JABBER_REGINPUT_ACTIVATE, 1 /*success*/, ( LPARAM )xi.copyNode( iqNode )); } else if ( !lstrcmp( type, _T("error"))) { if ( m_hwndAgentRegInput ) { HXML errorNode = xmlGetChild( iqNode , "error" ); TCHAR* str = JabberErrorMsg( errorNode ); SendMessage( m_hwndAgentRegInput, WM_JABBER_REGINPUT_ACTIVATE, 0 /*error*/, ( LPARAM )str ); mir_free( str ); } } } void CJabberProto::OnIqResultSetRegister( HXML iqNode ) { // RECVED: result of registration process // ACTION: notify of successful agent registration Log( " iqIdSetRegister" ); const TCHAR *type, *from; if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( from = xmlGetAttrValue( iqNode, _T("from"))) == NULL ) return; if ( !lstrcmp( type, _T("result"))) { HANDLE hContact = HContactFromJID( from ); if ( hContact != NULL ) JSetByte( hContact, "IsTransport", TRUE ); if ( m_hwndRegProgress ) SendMessage( m_hwndRegProgress, WM_JABBER_REGDLG_UPDATE, 100, ( LPARAM )TranslateT( "Registration successful" )); } else if ( !lstrcmp( type, _T("error"))) { if ( m_hwndRegProgress ) { HXML errorNode = xmlGetChild( iqNode , "error" ); TCHAR* str = JabberErrorMsg( errorNode ); SendMessage( m_hwndRegProgress, WM_JABBER_REGDLG_UPDATE, 100, ( LPARAM )str ); mir_free( str ); } } } ///////////////////////////////////////////////////////////////////////////////////////// // JabberIqResultGetVcard - processes the server-side v-card void CJabberProto::OnIqResultGetVcardPhoto( const TCHAR* jid, HXML n, HANDLE hContact, BOOL& hasPhoto ) { Log( "JabberIqResultGetVcardPhoto: %d", hasPhoto ); if ( hasPhoto ) return; HXML o = xmlGetChild( n , "BINVAL" ); if ( o == NULL || xmlGetText( o ) == NULL ) return; int bufferLen; char* buffer = JabberBase64DecodeT( xmlGetText( o ), &bufferLen ); if ( buffer == NULL ) return; const TCHAR* szPicType; HXML m = xmlGetChild( n , "TYPE" ); if ( m == NULL || xmlGetText( m ) == NULL ) { LBL_NoTypeSpecified: switch( JabberGetPictureType( buffer )) { case PA_FORMAT_GIF: szPicType = _T("image/gif"); break; case PA_FORMAT_BMP: szPicType = _T("image/bmp"); break; case PA_FORMAT_PNG: szPicType = _T("image/png"); break; case PA_FORMAT_JPEG: szPicType = _T("image/jpeg"); break; default: LBL_Ret: mir_free( buffer ); return; } } else { const TCHAR* tszType = xmlGetText( m ); if ( !_tcscmp( tszType, _T("image/jpeg")) || !_tcscmp( tszType, _T("image/png")) || !_tcscmp( tszType, _T("image/gif")) || !_tcscmp( tszType, _T("image/bmp"))) szPicType = tszType; else goto LBL_NoTypeSpecified; } TCHAR szAvatarFileName[MAX_PATH]; GetAvatarFileName( hContact, szAvatarFileName, SIZEOF( szAvatarFileName )); Log( "Picture file name set to " TCHAR_STR_PARAM, szAvatarFileName ); HANDLE hFile = CreateFile( szAvatarFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) goto LBL_Ret; Log( "Writing %d bytes", bufferLen ); DWORD nWritten; if ( !WriteFile( hFile, buffer, bufferLen, &nWritten, NULL )) goto LBL_Ret; CloseHandle( hFile ); Log( "%d bytes written", nWritten ); if ( hContact == NULL ) { hasPhoto = TRUE; CallService( MS_AV_SETMYAVATART, ( WPARAM )m_szModuleName, ( LPARAM )szAvatarFileName ); Log( "My picture saved to " TCHAR_STR_PARAM, szAvatarFileName ); } else { DBVARIANT dbv; if ( !JGetStringT( hContact, "jid", &dbv )) { JABBER_LIST_ITEM *item = ListGetItemPtr( LIST_ROSTER, jid ); if ( item == NULL ) { item = ListAdd( LIST_VCARD_TEMP, jid ); // adding to the temp list to store information about photo item->bUseResource = TRUE; } if ( item != NULL ) { hasPhoto = TRUE; if ( item->photoFileName ) DeleteFile( item->photoFileName ); replaceStrT( item->photoFileName, szAvatarFileName ); Log( "Contact's picture saved to " TCHAR_STR_PARAM, szAvatarFileName ); if (JGetWord( hContact, "Status", ID_STATUS_OFFLINE ) == ID_STATUS_OFFLINE) { char szHashValue[ MAX_PATH ]; if ( JGetStaticString( "AvatarHash", hContact, szHashValue, sizeof( szHashValue ))) OnIqResultGotAvatar( hContact, o, xmlGetText( m )); } } JFreeVariant( &dbv ); } } if ( !hasPhoto ) DeleteFile( szAvatarFileName ); goto LBL_Ret; } static TCHAR* sttGetText( HXML node, char* tag ) { HXML n = xmlGetChild( node , tag ); if ( n == NULL ) return NULL; return ( TCHAR* )xmlGetText( n ); } void CJabberProto::OnIqResultGetVcard( HXML iqNode ) { HXML vCardNode, m, n, o; const TCHAR* type, *jid; HANDLE hContact; TCHAR text[128]; DBVARIANT dbv; Log( " iqIdGetVcard" ); if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( jid = xmlGetAttrValue( iqNode, _T("from"))) == NULL ) return; int id = JabberGetPacketID( iqNode ); if ( id == m_nJabberSearchID ) { m_nJabberSearchID = -1; if (( vCardNode = xmlGetChild( iqNode , "vCard" )) != NULL ) { if ( !lstrcmp( type, _T("result"))) { JABBER_SEARCH_RESULT jsr = { 0 }; jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); jsr.hdr.flags = PSR_TCHAR; jsr.hdr.nick = sttGetText( vCardNode, "NICKNAME" ); jsr.hdr.firstName = sttGetText( vCardNode, "FN" ); jsr.hdr.lastName = _T(""); jsr.hdr.email = sttGetText( vCardNode, "EMAIL" ); _tcsncpy( jsr.jid, jid, SIZEOF( jsr.jid )); jsr.jid[ SIZEOF( jsr.jid )-1 ] = '\0'; JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, ( HANDLE )id, ( LPARAM )&jsr ); JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE )id, 0 ); } else if ( !lstrcmp( type, _T("error"))) JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE )id, 0 ); } else JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE )id, 0 ); return; } size_t len = _tcslen( m_szJabberJID ); if ( !_tcsnicmp( jid, m_szJabberJID, len ) && ( jid[len]=='/' || jid[len]=='\0' )) { hContact = NULL; Log( "Vcard for myself" ); } else { if (( hContact = HContactFromJID( jid )) == NULL ) return; Log( "Other user's vcard" ); } if ( !lstrcmp( type, _T("result"))) { BOOL hasFn, hasNick, hasGiven, hasFamily, hasMiddle, hasBday, hasGender; BOOL hasPhone, hasFax, hasCell, hasUrl; BOOL hasHome, hasHomeStreet, hasHomeStreet2, hasHomeLocality, hasHomeRegion, hasHomePcode, hasHomeCtry; BOOL hasWork, hasWorkStreet, hasWorkStreet2, hasWorkLocality, hasWorkRegion, hasWorkPcode, hasWorkCtry; BOOL hasOrgname, hasOrgunit, hasRole, hasTitle; BOOL hasDesc, hasPhoto; int nEmail, nPhone, nYear, nMonth, nDay; hasFn = hasNick = hasGiven = hasFamily = hasMiddle = hasBday = hasGender = FALSE; hasPhone = hasFax = hasCell = hasUrl = FALSE; hasHome = hasHomeStreet = hasHomeStreet2 = hasHomeLocality = hasHomeRegion = hasHomePcode = hasHomeCtry = FALSE; hasWork = hasWorkStreet = hasWorkStreet2 = hasWorkLocality = hasWorkRegion = hasWorkPcode = hasWorkCtry = FALSE; hasOrgname = hasOrgunit = hasRole = hasTitle = FALSE; hasDesc = hasPhoto = FALSE; nEmail = nPhone = 0; if (( vCardNode = xmlGetChild( iqNode , "vCard" )) != NULL ) { for ( int i=0; ; i++ ) { n = xmlGetChild( vCardNode ,i); if ( !n ) break; if ( xmlGetName( n ) == NULL ) continue; if ( !_tcscmp( xmlGetName( n ), _T("FN"))) { if ( xmlGetText( n ) != NULL ) { hasFn = TRUE; JSetStringT( hContact, "FullName", xmlGetText( n )); } } else if ( !lstrcmp( xmlGetName( n ), _T("NICKNAME"))) { if ( xmlGetText( n ) != NULL ) { hasNick = TRUE; JSetStringT( hContact, "Nick", xmlGetText( n )); } } else if ( !lstrcmp( xmlGetName( n ), _T("N"))) { // First/Last name if ( !hasGiven && !hasFamily && !hasMiddle ) { if (( m=xmlGetChild( n , "GIVEN" )) != NULL && xmlGetText( m )!=NULL ) { hasGiven = TRUE; JSetStringT( hContact, "FirstName", xmlGetText( m )); } if (( m=xmlGetChild( n , "FAMILY" )) != NULL && xmlGetText( m )!=NULL ) { hasFamily = TRUE; JSetStringT( hContact, "LastName", xmlGetText( m )); } if (( m=xmlGetChild( n , "MIDDLE" )) != NULL && xmlGetText( m ) != NULL ) { hasMiddle = TRUE; JSetStringT( hContact, "MiddleName", xmlGetText( m )); } } } else if ( !lstrcmp( xmlGetName( n ), _T("EMAIL"))) { // E-mail address( es ) if (( m=xmlGetChild( n , "USERID" )) == NULL ) // Some bad client put e-mail directly in instead of m = n; if ( xmlGetText( m ) != NULL ) { char text[100]; if ( hContact != NULL ) { if ( nEmail == 0 ) strcpy( text, "e-mail" ); else sprintf( text, "e-mail%d", nEmail-1 ); } else sprintf( text, "e-mail%d", nEmail ); JSetStringT( hContact, text, xmlGetText( m )); if ( hContact == NULL ) { sprintf( text, "e-mailFlag%d", nEmail ); int nFlag = 0; if ( xmlGetChild( n , "HOME" ) != NULL ) nFlag |= JABBER_VCEMAIL_HOME; if ( xmlGetChild( n , "WORK" ) != NULL ) nFlag |= JABBER_VCEMAIL_WORK; if ( xmlGetChild( n , "INTERNET" ) != NULL ) nFlag |= JABBER_VCEMAIL_INTERNET; if ( xmlGetChild( n , "X400" ) != NULL ) nFlag |= JABBER_VCEMAIL_X400; JSetWord( NULL, text, nFlag ); } nEmail++; } } else if ( !lstrcmp( xmlGetName( n ), _T("BDAY"))) { // Birthday if ( !hasBday && xmlGetText( n )!=NULL ) { if ( hContact != NULL ) { if ( _stscanf( xmlGetText( n ), _T("%d-%d-%d"), &nYear, &nMonth, &nDay ) == 3 ) { hasBday = TRUE; JSetWord( hContact, "BirthYear", ( WORD )nYear ); JSetByte( hContact, "BirthMonth", ( BYTE ) nMonth ); JSetByte( hContact, "BirthDay", ( BYTE ) nDay ); SYSTEMTIME sToday = {0}; GetLocalTime(&sToday); int nAge = sToday.wYear - nYear; if (sToday.wMonth < nMonth || (sToday.wMonth == nMonth && sToday.wDay < nDay)) nAge--; if (nAge) JSetWord( hContact, "Age", ( WORD )nAge ); } } else { hasBday = TRUE; JSetStringT( NULL, "BirthDate", xmlGetText( n )); } } } else if ( !lstrcmp( xmlGetName( n ), _T("GENDER"))) { // Gender if ( !hasGender && xmlGetText( n )!=NULL ) { if ( hContact != NULL ) { if ( xmlGetText( n )[0] && strchr( "mMfF", xmlGetText( n )[0] )!=NULL ) { hasGender = TRUE; JSetByte( hContact, "Gender", ( BYTE ) toupper( xmlGetText( n )[0] )); } } else { hasGender = TRUE; JSetStringT( NULL, "GenderString", xmlGetText( n )); } } } else if ( !lstrcmp( xmlGetName( n ), _T("ADR"))) { if ( !hasHome && xmlGetChild( n , "HOME" )!=NULL ) { // Home address hasHome = TRUE; if (( m=xmlGetChild( n , "STREET" )) != NULL && xmlGetText( m ) != NULL ) { hasHomeStreet = TRUE; if ( hContact != NULL ) { if (( o=xmlGetChild( n , "EXTADR" )) != NULL && xmlGetText( o ) != NULL ) mir_sntprintf( text, SIZEOF( text ), _T("%s\r\n%s"), xmlGetText( m ), xmlGetText( o )); else if (( o=xmlGetChild( n , "EXTADD" ))!=NULL && xmlGetText( o )!=NULL ) mir_sntprintf( text, SIZEOF( text ), _T("%s\r\n%s"), xmlGetText( m ), xmlGetText( o )); else _tcsncpy( text, xmlGetText( m ), SIZEOF( text )); text[SIZEOF(text)-1] = '\0'; JSetStringT( hContact, "Street", text ); } else { JSetStringT( hContact, "Street", xmlGetText( m )); if (( m=xmlGetChild( n , "EXTADR" )) == NULL ) m = xmlGetChild( n , "EXTADD" ); if ( m!=NULL && xmlGetText( m )!=NULL ) { hasHomeStreet2 = TRUE; JSetStringT( hContact, "Street2", xmlGetText( m )); } } } if (( m=xmlGetChild( n , "LOCALITY" ))!=NULL && xmlGetText( m )!=NULL ) { hasHomeLocality = TRUE; JSetStringT( hContact, "City", xmlGetText( m )); } if (( m=xmlGetChild( n , "REGION" ))!=NULL && xmlGetText( m )!=NULL ) { hasHomeRegion = TRUE; JSetStringT( hContact, "State", xmlGetText( m )); } if (( m=xmlGetChild( n , "PCODE" ))!=NULL && xmlGetText( m )!=NULL ) { hasHomePcode = TRUE; JSetStringT( hContact, "ZIP", xmlGetText( m )); } if (( m=xmlGetChild( n , "CTRY" ))==NULL || xmlGetText( m )==NULL ) // Some bad client use instead of m = xmlGetChild( n , "COUNTRY" ); if ( m!=NULL && xmlGetText( m )!=NULL ) { hasHomeCtry = TRUE; JSetStringT( hContact, "Country", xmlGetText( m )); } } if ( !hasWork && xmlGetChild( n , "WORK" )!=NULL ) { // Work address hasWork = TRUE; if (( m=xmlGetChild( n , "STREET" ))!=NULL && xmlGetText( m )!=NULL ) { hasWorkStreet = TRUE; if ( hContact != NULL ) { if (( o=xmlGetChild( n , "EXTADR" ))!=NULL && xmlGetText( o )!=NULL ) mir_sntprintf( text, SIZEOF( text ), _T("%s\r\n%s"), xmlGetText( m ), xmlGetText( o )); else if (( o=xmlGetChild( n , "EXTADD" ))!=NULL && xmlGetText( o )!=NULL ) mir_sntprintf( text, SIZEOF( text ), _T("%s\r\n%s"), xmlGetText( m ), xmlGetText( o )); else _tcsncpy( text, xmlGetText( m ), SIZEOF( text )); text[SIZEOF( text )-1] = '\0'; JSetStringT( hContact, "CompanyStreet", text ); } else { JSetStringT( hContact, "CompanyStreet", xmlGetText( m )); if (( m=xmlGetChild( n , "EXTADR" )) == NULL ) m = xmlGetChild( n , "EXTADD" ); if ( m!=NULL && xmlGetText( m )!=NULL ) { hasWorkStreet2 = TRUE; JSetStringT( hContact, "CompanyStreet2", xmlGetText( m )); } } } if (( m=xmlGetChild( n , "LOCALITY" ))!=NULL && xmlGetText( m )!=NULL ) { hasWorkLocality = TRUE; JSetStringT( hContact, "CompanyCity", xmlGetText( m )); } if (( m=xmlGetChild( n , "REGION" ))!=NULL && xmlGetText( m )!=NULL ) { hasWorkRegion = TRUE; JSetStringT( hContact, "CompanyState", xmlGetText( m )); } if (( m=xmlGetChild( n , "PCODE" ))!=NULL && xmlGetText( m )!=NULL ) { hasWorkPcode = TRUE; JSetStringT( hContact, "CompanyZIP", xmlGetText( m )); } if (( m=xmlGetChild( n , "CTRY" ))==NULL || xmlGetText( m )==NULL ) // Some bad client use instead of m = xmlGetChild( n , "COUNTRY" ); if ( m!=NULL && xmlGetText( m )!=NULL ) { hasWorkCtry = TRUE; JSetStringT( hContact, "CompanyCountry", xmlGetText( m )); } } } else if ( !lstrcmp( xmlGetName( n ), _T("TEL"))) { // Telephone/Fax/Cellular if (( m=xmlGetChild( n , "NUMBER" ))!=NULL && xmlGetText( m )!=NULL ) { if ( hContact != NULL ) { if ( !hasFax && xmlGetChild( n , "FAX" )!=NULL ) { hasFax = TRUE; JSetStringT( hContact, "Fax", xmlGetText( m )); } else if ( !hasCell && xmlGetChild( n , "CELL" )!=NULL ) { hasCell = TRUE; JSetStringT( hContact, "Cellular", xmlGetText( m )); } else if ( !hasPhone && ( xmlGetChild( n , "HOME" )!=NULL || xmlGetChild( n , "WORK" )!=NULL || xmlGetChild( n , "VOICE" )!=NULL || ( xmlGetChild( n , "FAX" )==NULL && xmlGetChild( n , "PAGER" )==NULL && xmlGetChild( n , "MSG" )==NULL && xmlGetChild( n , "CELL" )==NULL && xmlGetChild( n , "VIDEO" )==NULL && xmlGetChild( n , "BBS" )==NULL && xmlGetChild( n , "MODEM" )==NULL && xmlGetChild( n , "ISDN" )==NULL && xmlGetChild( n , "PCS" )==NULL ))) { hasPhone = TRUE; JSetStringT( hContact, "Phone", xmlGetText( m )); } } else { char text[ 100 ]; sprintf( text, "Phone%d", nPhone ); JSetStringT( NULL, text, xmlGetText( m )); sprintf( text, "PhoneFlag%d", nPhone ); int nFlag = 0; if ( xmlGetChild( n ,"HOME" ) != NULL ) nFlag |= JABBER_VCTEL_HOME; if ( xmlGetChild( n ,"WORK" ) != NULL ) nFlag |= JABBER_VCTEL_WORK; if ( xmlGetChild( n ,"VOICE" ) != NULL ) nFlag |= JABBER_VCTEL_VOICE; if ( xmlGetChild( n ,"FAX" ) != NULL ) nFlag |= JABBER_VCTEL_FAX; if ( xmlGetChild( n ,"PAGER" ) != NULL ) nFlag |= JABBER_VCTEL_PAGER; if ( xmlGetChild( n ,"MSG" ) != NULL ) nFlag |= JABBER_VCTEL_MSG; if ( xmlGetChild( n ,"CELL" ) != NULL ) nFlag |= JABBER_VCTEL_CELL; if ( xmlGetChild( n ,"VIDEO" ) != NULL ) nFlag |= JABBER_VCTEL_VIDEO; if ( xmlGetChild( n ,"BBS" ) != NULL ) nFlag |= JABBER_VCTEL_BBS; if ( xmlGetChild( n ,"MODEM" ) != NULL ) nFlag |= JABBER_VCTEL_MODEM; if ( xmlGetChild( n ,"ISDN" ) != NULL ) nFlag |= JABBER_VCTEL_ISDN; if ( xmlGetChild( n ,"PCS" ) != NULL ) nFlag |= JABBER_VCTEL_PCS; JSetWord( NULL, text, nFlag ); nPhone++; } } } else if ( !lstrcmp( xmlGetName( n ), _T("URL"))) { // Homepage if ( !hasUrl && xmlGetText( n )!=NULL ) { hasUrl = TRUE; JSetStringT( hContact, "Homepage", xmlGetText( n )); } } else if ( !lstrcmp( xmlGetName( n ), _T("ORG"))) { if ( !hasOrgname && !hasOrgunit ) { if (( m=xmlGetChild( n ,"ORGNAME" ))!=NULL && xmlGetText( m )!=NULL ) { hasOrgname = TRUE; JSetStringT( hContact, "Company", xmlGetText( m )); } if (( m=xmlGetChild( n ,"ORGUNIT" ))!=NULL && xmlGetText( m )!=NULL ) { // The real vCard can have multiple but we will only display the first one hasOrgunit = TRUE; JSetStringT( hContact, "CompanyDepartment", xmlGetText( m )); } } } else if ( !lstrcmp( xmlGetName( n ), _T("ROLE"))) { if ( !hasRole && xmlGetText( n )!=NULL ) { hasRole = TRUE; JSetStringT( hContact, "Role", xmlGetText( n )); } } else if ( !lstrcmp( xmlGetName( n ), _T("TITLE"))) { if ( !hasTitle && xmlGetText( n )!=NULL ) { hasTitle = TRUE; JSetStringT( hContact, "CompanyPosition", xmlGetText( n )); } } else if ( !lstrcmp( xmlGetName( n ), _T("DESC"))) { if ( !hasDesc && xmlGetText( n )!=NULL ) { hasDesc = TRUE; TCHAR* szMemo = JabberUnixToDosT( xmlGetText( n )); JSetStringT( hContact, "About", szMemo ); mir_free( szMemo ); } } else if ( !lstrcmp( xmlGetName( n ), _T("PHOTO"))) OnIqResultGetVcardPhoto( jid, n, hContact, hasPhoto ); } } if ( hasFn && !hasNick ) { TCHAR *name = JGetStringT( hContact, "FullName" ); TCHAR *nick = JGetStringT( hContact, "Nick" ); TCHAR *jidNick = JabberNickFromJID(jid); if ( !nick || ( jidNick && !_tcsicmp( nick, jidNick ))) JSetStringT( hContact, "Nick", name ); mir_free( jidNick ); mir_free( nick ); mir_free( name ); } if ( !hasFn ) JDeleteSetting( hContact, "FullName" ); // We are not deleting "Nick" // if ( !hasNick ) // JDeleteSetting( hContact, "Nick" ); if ( !hasGiven ) JDeleteSetting( hContact, "FirstName" ); if ( !hasFamily ) JDeleteSetting( hContact, "LastName" ); if ( !hasMiddle ) JDeleteSetting( hContact, "MiddleName" ); if ( hContact != NULL ) { while ( true ) { if ( nEmail <= 0 ) JDeleteSetting( hContact, "e-mail" ); else { char text[ 100 ]; sprintf( text, "e-mail%d", nEmail-1 ); if ( DBGetContactSettingString( hContact, m_szModuleName, text, &dbv )) break; JFreeVariant( &dbv ); JDeleteSetting( hContact, text ); } nEmail++; } } else { while ( true ) { char text[ 100 ]; sprintf( text, "e-mail%d", nEmail ); if ( DBGetContactSettingString( NULL, m_szModuleName, text, &dbv )) break; JFreeVariant( &dbv ); JDeleteSetting( NULL, text ); sprintf( text, "e-mailFlag%d", nEmail ); JDeleteSetting( NULL, text ); nEmail++; } } if ( !hasBday ) { JDeleteSetting( hContact, "BirthYear" ); JDeleteSetting( hContact, "BirthMonth" ); JDeleteSetting( hContact, "BirthDay" ); JDeleteSetting( hContact, "BirthDate" ); JDeleteSetting( hContact, "Age" ); } if ( !hasGender ) { if ( hContact != NULL ) JDeleteSetting( hContact, "Gender" ); else JDeleteSetting( NULL, "GenderString" ); } if ( hContact != NULL ) { if ( !hasPhone ) JDeleteSetting( hContact, "Phone" ); if ( !hasFax ) JDeleteSetting( hContact, "Fax" ); if ( !hasCell ) JDeleteSetting( hContact, "Cellular" ); } else { while ( true ) { char text[ 100 ]; sprintf( text, "Phone%d", nPhone ); if ( DBGetContactSettingString( NULL, m_szModuleName, text, &dbv )) break; JFreeVariant( &dbv ); JDeleteSetting( NULL, text ); sprintf( text, "PhoneFlag%d", nPhone ); JDeleteSetting( NULL, text ); nPhone++; } } if ( !hasHomeStreet ) JDeleteSetting( hContact, "Street" ); if ( !hasHomeStreet2 && hContact==NULL ) JDeleteSetting( hContact, "Street2" ); if ( !hasHomeLocality ) JDeleteSetting( hContact, "City" ); if ( !hasHomeRegion ) JDeleteSetting( hContact, "State" ); if ( !hasHomePcode ) JDeleteSetting( hContact, "ZIP" ); if ( !hasHomeCtry ) JDeleteSetting( hContact, "Country" ); if ( !hasWorkStreet ) JDeleteSetting( hContact, "CompanyStreet" ); if ( !hasWorkStreet2 && hContact==NULL ) JDeleteSetting( hContact, "CompanyStreet2" ); if ( !hasWorkLocality ) JDeleteSetting( hContact, "CompanyCity" ); if ( !hasWorkRegion ) JDeleteSetting( hContact, "CompanyState" ); if ( !hasWorkPcode ) JDeleteSetting( hContact, "CompanyZIP" ); if ( !hasWorkCtry ) JDeleteSetting( hContact, "CompanyCountry" ); if ( !hasUrl ) JDeleteSetting( hContact, "Homepage" ); if ( !hasOrgname ) JDeleteSetting( hContact, "Company" ); if ( !hasOrgunit ) JDeleteSetting( hContact, "CompanyDepartment" ); if ( !hasRole ) JDeleteSetting( hContact, "Role" ); if ( !hasTitle ) JDeleteSetting( hContact, "CompanyPosition" ); if ( !hasDesc ) JDeleteSetting( hContact, "About" ); if ( id == m_ThreadInfo->resolveID ) { const TCHAR* p = _tcschr( jid, '@' ); ResolveTransportNicks(( p != NULL ) ? p+1 : jid ); } else { if (( hContact = HContactFromJID( jid )) != NULL ) JSendBroadcast( hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, ( HANDLE ) 1, 0 ); WindowNotify(WM_JABBER_REFRESH_VCARD); } } else if ( !lstrcmp( type, _T("error"))) { if (( hContact = HContactFromJID( jid )) != NULL ) JSendBroadcast( hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, ( HANDLE ) 1, 0 ); } } void CJabberProto::OnIqResultSetVcard( HXML iqNode ) { Log( " iqIdSetVcard" ); if ( !xmlGetAttrValue( iqNode, _T("type"))) return; WindowNotify(WM_JABBER_REFRESH_VCARD); } void CJabberProto::OnIqResultSetSearch( HXML iqNode ) { HXML queryNode, n; const TCHAR* type, *jid; int i, id; JABBER_SEARCH_RESULT jsr; Log( " iqIdGetSearch" ); if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( id = JabberGetPacketID( iqNode )) == -1 ) return; if ( !lstrcmp( type, _T("result"))) { if (( queryNode=xmlGetChild( iqNode , "query" )) == NULL ) return; jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); for ( i=0; ; i++ ) { HXML itemNode = xmlGetChild( queryNode ,i); if ( !itemNode ) break; if ( !lstrcmp( xmlGetName( itemNode ), _T("item"))) { if (( jid=xmlGetAttrValue( itemNode, _T("jid"))) != NULL ) { _tcsncpy( jsr.jid, jid, SIZEOF( jsr.jid )); jsr.jid[ SIZEOF( jsr.jid )-1] = '\0'; jsr.hdr.id = (TCHAR*)jid; Log( "Result jid = " TCHAR_STR_PARAM, jid ); if (( n=xmlGetChild( itemNode , "nick" ))!=NULL && xmlGetText( n )!=NULL ) jsr.hdr.nick = ( TCHAR* )xmlGetText( n ); else jsr.hdr.nick = _T( "" ); if (( n=xmlGetChild( itemNode , "first" ))!=NULL && xmlGetText( n )!=NULL ) jsr.hdr.firstName = ( TCHAR* )xmlGetText( n ); else jsr.hdr.firstName = _T( "" ); if (( n=xmlGetChild( itemNode , "last" ))!=NULL && xmlGetText( n )!=NULL ) jsr.hdr.lastName = ( TCHAR* )xmlGetText( n ); else jsr.hdr.lastName = _T( "" ); if (( n=xmlGetChild( itemNode , "email" ))!=NULL && xmlGetText( n )!=NULL ) jsr.hdr.email = ( TCHAR* )xmlGetText( n ); else jsr.hdr.email = _T( "" ); jsr.hdr.flags = PSR_TCHAR; JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, ( HANDLE ) id, ( LPARAM )&jsr ); } } } JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE ) id, 0 ); } else if ( !lstrcmp( type, _T("error"))) JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE ) id, 0 ); } void CJabberProto::OnIqResultExtSearch( HXML iqNode ) { HXML queryNode; const TCHAR* type; int id; Log( " iqIdGetExtSearch" ); if (( type=xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if (( id = JabberGetPacketID( iqNode )) == -1 ) return; if ( !lstrcmp( type, _T("result"))) { if (( queryNode=xmlGetChild( iqNode , "query" )) == NULL ) return; if (( queryNode=xmlGetChild( queryNode , "x" )) == NULL ) return; for ( int i=0; ; i++ ) { HXML itemNode = xmlGetChild( queryNode ,i); if ( !itemNode ) break; if ( lstrcmp( xmlGetName( itemNode ), _T("item"))) continue; JABBER_SEARCH_RESULT jsr = { 0 }; jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); jsr.hdr.flags = PSR_TCHAR; // jsr.hdr.firstName = ""; for ( int j=0; ; j++ ) { HXML fieldNode = xmlGetChild( itemNode ,j); if ( !fieldNode ) break; if ( lstrcmp( xmlGetName( fieldNode ), _T("field"))) continue; const TCHAR* fieldName = xmlGetAttrValue( fieldNode, _T("var")); if ( fieldName == NULL ) continue; HXML n = xmlGetChild( fieldNode , "value" ); if ( n == NULL ) continue; if ( !lstrcmp( fieldName, _T("jid"))) { _tcsncpy( jsr.jid, xmlGetText( n ), SIZEOF( jsr.jid )); jsr.jid[SIZEOF( jsr.jid )-1] = '\0'; Log( "Result jid = " TCHAR_STR_PARAM, jsr.jid ); } else if ( !lstrcmp( fieldName, _T("nickname"))) jsr.hdr.nick = ( xmlGetText( n ) != NULL ) ? ( TCHAR* )xmlGetText( n ) : _T( "" ); else if ( !lstrcmp( fieldName, _T("fn"))) jsr.hdr.firstName = ( xmlGetText( n ) != NULL ) ? ( TCHAR* )xmlGetText( n ) : _T( "" ); else if ( !lstrcmp( fieldName, _T("given"))) jsr.hdr.firstName = ( xmlGetText( n ) != NULL ) ? ( TCHAR* )xmlGetText( n ) : _T( "" ); else if ( !lstrcmp( fieldName, _T("family"))) jsr.hdr.lastName = ( xmlGetText( n ) != NULL ) ? ( TCHAR* )xmlGetText( n ) : _T( "" ); else if ( !lstrcmp( fieldName, _T("email"))) jsr.hdr.email = ( xmlGetText( n ) != NULL ) ? ( TCHAR* )xmlGetText( n ) : _T( "" ); } JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, ( HANDLE ) id, ( LPARAM )&jsr ); } JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE ) id, 0 ); } else if ( !lstrcmp( type, _T("error"))) JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE ) id, 0 ); } void CJabberProto::OnIqResultSetPassword( HXML iqNode ) { Log( " iqIdSetPassword" ); const TCHAR* type = xmlGetAttrValue( iqNode, _T("type")); if ( type == NULL ) return; if ( !lstrcmp( type, _T("result"))) { _tcsncpy( m_ThreadInfo->password, m_ThreadInfo->newPassword, SIZEOF( m_ThreadInfo->password )); MessageBox( NULL, TranslateT( "Password is successfully changed. Don't forget to update your password in the Jabber protocol option." ), TranslateT( "Change Password" ), MB_OK|MB_ICONINFORMATION|MB_SETFOREGROUND ); } else if ( !lstrcmp( type, _T("error"))) MessageBox( NULL, TranslateT( "Password cannot be changed." ), TranslateT( "Change Password" ), MB_OK|MB_ICONSTOP|MB_SETFOREGROUND ); } /* void CJabberProto::OnIqResultDiscoAgentItems( HXML iqNode, void *userdata ) { if ( !m_options.EnableAvatars ) return; } */ void CJabberProto::OnIqResultGetVCardAvatar( HXML iqNode ) { const TCHAR* type; Log( " OnIqResultGetVCardAvatar" ); const TCHAR* from = xmlGetAttrValue( iqNode, _T("from")); if ( from == NULL ) return; HANDLE hContact = HContactFromJID( from ); if ( hContact == NULL ) return; if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if ( _tcscmp( type, _T("result"))) return; HXML vCard = xmlGetChild( iqNode , "vCard" ); if (vCard == NULL) return; vCard = xmlGetChild( vCard , "PHOTO" ); if (vCard == NULL) return; if ( xmlGetChildCount( vCard ) == 0 ) { JDeleteSetting( hContact, "AvatarHash" ); DBVARIANT dbv = {0}; if ( !JGetStringT( hContact, "AvatarSaved", &dbv )) { JFreeVariant( &dbv ); JDeleteSetting( hContact, "AvatarSaved" ); JSendBroadcast( hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, NULL, NULL ); } return; } HXML typeNode = xmlGetChild( vCard , "TYPE" ); const TCHAR* mimeType = NULL; if (typeNode != NULL) mimeType = xmlGetText( typeNode ); HXML n = xmlGetChild( vCard , "BINVAL" ); if ( n == NULL ) return; JSetByte( hContact, "AvatarXVcard", 1 ); OnIqResultGotAvatar( hContact, n, mimeType); } void CJabberProto::OnIqResultGetClientAvatar( HXML iqNode ) { const TCHAR* type; Log( " iqIdResultGetClientAvatar" ); const TCHAR* from = xmlGetAttrValue( iqNode, _T("from")); if ( from == NULL ) return; HANDLE hContact = HContactFromJID( from ); if ( hContact == NULL ) return; HXML n = NULL; if (( type = xmlGetAttrValue( iqNode, _T("type"))) != NULL && !_tcscmp( type, _T("result"))) { HXML queryNode = xmlGetChild( iqNode , "query" ); if ( queryNode != NULL ) { const TCHAR* xmlns = xmlGetAttrValue( queryNode, _T("xmlns")); if ( !lstrcmp( xmlns, _T(JABBER_FEAT_AVATAR))) { n = xmlGetChild( queryNode , "data" ); } } } if ( n == NULL ) { TCHAR szJid[ JABBER_MAX_JID_LEN ]; lstrcpyn(szJid, from, SIZEOF(szJid)); TCHAR *res = _tcschr(szJid, _T('/')); if ( res != NULL ) *res = 0; // Try server stored avatar int iqId = SerialNext(); IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetServerAvatar ); XmlNodeIq iq( _T("get"), iqId, szJid ); iq << XQUERY( _T(JABBER_FEAT_SERVER_AVATAR)); m_ThreadInfo->send( iq ); return; } const TCHAR* mimeType = mimeType = xmlGetAttrValue( n, _T("mimetype")); OnIqResultGotAvatar( hContact, n, mimeType); } void CJabberProto::OnIqResultGetServerAvatar( HXML iqNode ) { const TCHAR* type; Log( " iqIdResultGetServerAvatar" ); const TCHAR* from = xmlGetAttrValue( iqNode, _T("from")); if ( from == NULL ) return; HANDLE hContact = HContactFromJID( from ); if ( hContact == NULL ) return; HXML n = NULL; if (( type = xmlGetAttrValue( iqNode, _T("type"))) != NULL && !_tcscmp( type, _T("result"))) { HXML queryNode = xmlGetChild( iqNode , "query" ); if ( queryNode != NULL ) { const TCHAR* xmlns = xmlGetAttrValue( queryNode, _T("xmlns")); if ( !lstrcmp( xmlns, _T(JABBER_FEAT_SERVER_AVATAR))) { n = xmlGetChild( queryNode , "data" ); } } } if ( n == NULL ) { TCHAR szJid[ JABBER_MAX_JID_LEN ]; lstrcpyn(szJid, from, SIZEOF(szJid)); TCHAR *res = _tcschr(szJid, _T('/')); if ( res != NULL ) *res = 0; // Try VCard photo int iqId = SerialNext(); IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetVCardAvatar ); XmlNodeIq iq( _T("get"), iqId, szJid ); iq << XCHILDNS( _T("vCard"), _T(JABBER_FEAT_VCARD_TEMP)); m_ThreadInfo->send( iq ); return; } const TCHAR* mimeType = xmlGetAttrValue( n, _T("mimetype")); OnIqResultGotAvatar( hContact, n, mimeType); } void CJabberProto::OnIqResultGotAvatar( HANDLE hContact, HXML n, const TCHAR* mimeType ) { int resultLen = 0; char* body = JabberBase64DecodeT( xmlGetText( n ), &resultLen ); int pictureType; if ( mimeType != NULL ) { if ( !lstrcmp( mimeType, _T("image/jpeg"))) pictureType = PA_FORMAT_JPEG; else if ( !lstrcmp( mimeType, _T("image/png"))) pictureType = PA_FORMAT_PNG; else if ( !lstrcmp( mimeType, _T("image/gif"))) pictureType = PA_FORMAT_GIF; else if ( !lstrcmp( mimeType, _T("image/bmp"))) pictureType = PA_FORMAT_BMP; else { LBL_ErrFormat: Log( "Invalid mime type specified for picture: " TCHAR_STR_PARAM, mimeType ); mir_free( body ); return; } } else if (( pictureType = JabberGetPictureType( body )) == PA_FORMAT_UNKNOWN ) goto LBL_ErrFormat; TCHAR tszFileName[ MAX_PATH ]; PROTO_AVATAR_INFORMATIONT AI; AI.cbSize = sizeof AI; AI.format = pictureType; AI.hContact = hContact; if ( JGetByte( hContact, "AvatarType", PA_FORMAT_UNKNOWN ) != (unsigned char)pictureType ) { GetAvatarFileName( hContact, tszFileName, SIZEOF(tszFileName)); DeleteFile( tszFileName ); } JSetByte( hContact, "AvatarType", pictureType ); char buffer[ 41 ]; mir_sha1_byte_t digest[20]; mir_sha1_ctx sha; mir_sha1_init( &sha ); mir_sha1_append( &sha, ( mir_sha1_byte_t* )body, resultLen ); mir_sha1_finish( &sha, digest ); for ( int i=0; i<20; i++ ) sprintf( buffer+( i<<1 ), "%02x", digest[i] ); GetAvatarFileName( hContact, tszFileName, SIZEOF(tszFileName)); _tcsncpy( AI.filename, tszFileName, SIZEOF(AI.filename)); FILE* out = _tfopen( tszFileName, _T("wb")); if ( out != NULL ) { fwrite( body, resultLen, 1, out ); fclose( out ); JSetString( hContact, "AvatarSaved", buffer ); JSendBroadcast( hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE( &AI ), NULL ); Log("Broadcast new avatar: %s",AI.filename); } else JSendBroadcast( hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE( &AI ), NULL ); mir_free( body ); } ///////////////////////////////////////////////////////////////////////////////////////// // Bookmarks void CJabberProto::OnIqResultDiscoBookmarks( HXML iqNode ) { HXML storageNode;//, nickNode, passNode; const TCHAR* type, *jid, *name; // RECVED: list of bookmarks // ACTION: refresh bookmarks dialog Log( " iqIdGetBookmarks" ); if (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL ) return; if ( !lstrcmp( type, _T("result"))) { if ( m_ThreadInfo && !( m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PRIVATE_STORAGE )) { m_ThreadInfo->jabberServerCaps |= JABBER_CAPS_PRIVATE_STORAGE; EnableMenuItems( TRUE ); } if ( storageNode = XPathT( iqNode, "query/storage[@xmlns='storage:bookmarks']" )) { ListRemoveList( LIST_BOOKMARK ); HXML itemNode; for ( int i = 0; itemNode = xmlGetChild( storageNode, i ); i++ ) { if ( name = xmlGetName( itemNode)) { if ( !_tcscmp( name, _T("conference")) && (jid = xmlGetAttrValue( itemNode, _T("jid")))) { JABBER_LIST_ITEM* item = ListAdd( LIST_BOOKMARK, jid ); item->name = mir_tstrdup( xmlGetAttrValue( itemNode, _T("name"))); item->type = mir_tstrdup( _T( "conference" )); item->bUseResource = TRUE; item->nick = mir_tstrdup( XPathT( itemNode, "nick" )); item->password = mir_tstrdup( XPathT( itemNode, "password" )); const TCHAR* autoJ = xmlGetAttrValue( itemNode, _T("autojoin")); if ( autoJ != NULL ) item->bAutoJoin = ( !lstrcmp( autoJ, _T("true")) || !lstrcmp( autoJ, _T("1"))) ? true : false; } else if ( !_tcscmp( name, _T("url")) && (jid = xmlGetAttrValue( itemNode, _T("url") ))) { JABBER_LIST_ITEM* item = ListAdd( LIST_BOOKMARK, jid ); item->bUseResource = TRUE; item->name = mir_tstrdup( xmlGetAttrValue( itemNode, _T("name"))); item->type = mir_tstrdup( _T("url")); } } } UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_REFRESH); m_ThreadInfo->bBookmarksLoaded = TRUE; OnProcessLoginRq(m_ThreadInfo, JABBER_LOGIN_BOOKMARKS); } } else if ( !lstrcmp( type, _T("error"))) { if ( m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PRIVATE_STORAGE ) { m_ThreadInfo->jabberServerCaps &= ~JABBER_CAPS_PRIVATE_STORAGE; EnableMenuItems( TRUE ); UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_ACTIVATE); return; } } } void CJabberProto::SetBookmarkRequest (XmlNodeIq& iq) { HXML query = iq << XQUERY( _T(JABBER_FEAT_PRIVATE_STORAGE)); HXML storage = query << XCHILDNS( _T("storage"), _T("storage:bookmarks")); LISTFOREACH(i, this, LIST_BOOKMARK) { JABBER_LIST_ITEM* item = ListGetItemPtrFromIndex( i ); if ( item == NULL ) continue; if ( item->jid == NULL ) continue; if ( !lstrcmp( item->type, _T("conference"))) { HXML itemNode = storage << XCHILD( _T("conference")) << XATTR( _T("jid"), item->jid ); if ( item->name ) itemNode << XATTR( _T("name"), item->name ); if ( item->bAutoJoin ) itemNode << XATTRI( _T("autojoin"), 1 ); if ( item->nick ) itemNode << XCHILD( _T("nick"), item->nick ); if ( item->password ) itemNode << XCHILD( _T("password"), item->password ); } if ( !lstrcmp( item->type, _T("url"))) { HXML itemNode = storage << XCHILD( _T("url")) << XATTR( _T("url"), item->jid ); if ( item->name ) itemNode << XATTR( _T("name"), item->name ); } } } void CJabberProto::OnIqResultSetBookmarks( HXML iqNode ) { // RECVED: server's response // ACTION: refresh bookmarks list dialog Log( " iqIdSetBookmarks" ); const TCHAR* type = xmlGetAttrValue( iqNode, _T("type")); if ( type == NULL ) return; if ( !lstrcmp( type, _T("result"))) { UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_REFRESH); } else if ( !lstrcmp( type, _T("error"))) { HXML errorNode = xmlGetChild( iqNode , "error" ); TCHAR* str = JabberErrorMsg( errorNode ); MessageBox( NULL, str, TranslateT( "Jabber Bookmarks Error" ), MB_OK|MB_SETFOREGROUND ); mir_free( str ); UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_ACTIVATE); } } // last activity (XEP-0012) support void CJabberProto::OnIqResultLastActivity( HXML iqNode, CJabberIqInfo* pInfo ) { JABBER_RESOURCE_STATUS *r = ResourceInfoFromJID( pInfo->m_szFrom ); if ( !r ) return; time_t lastActivity = -1; if ( pInfo->m_nIqType == JABBER_IQ_TYPE_RESULT ) { LPCTSTR szSeconds = XPathT( iqNode, "query[@xmlns='jabber:iq:last']/@seconds" ); if ( szSeconds ) { int nSeconds = _ttoi( szSeconds ); if ( nSeconds > 0 ) lastActivity = time( 0 ) - nSeconds; } LPCTSTR szLastStatusMessage = XPathT( iqNode, "query[@xmlns='jabber:iq:last']" ); if ( szLastStatusMessage ) // replace only if it exists replaceStrT( r->statusMessage, szLastStatusMessage ); } r->idleStartTime = lastActivity; JabberUserInfoUpdate(pInfo->GetHContact()); } // entity time (XEP-0202) support void CJabberProto::OnIqResultEntityTime( HXML pIqNode, CJabberIqInfo* pInfo ) { if ( !pInfo->m_hContact ) return; if ( pInfo->m_nIqType == JABBER_IQ_TYPE_RESULT ) { LPCTSTR szTzo = XPathFmt( pIqNode, _T("time[@xmlns='%s']/tzo"), _T( JABBER_FEAT_ENTITY_TIME )); if ( szTzo && szTzo[0] ) { LPCTSTR szMin = _tcschr( szTzo, ':' ); int nTz = _ttoi( szTzo ) * -2; nTz += ( nTz < 0 ? -1 : 1 ) * ( szMin ? _ttoi( szMin + 1 ) / 30 : 0 ); TIME_ZONE_INFORMATION tzinfo; if ( GetTimeZoneInformation( &tzinfo ) == TIME_ZONE_ID_DAYLIGHT ) nTz -= tzinfo.DaylightBias / 30; JSetByte( pInfo->m_hContact, "Timezone", (signed char)nTz ); LPCTSTR szTz = XPathFmt( pIqNode, _T("time[@xmlns='%s']/tz"), _T( JABBER_FEAT_ENTITY_TIME )); if (szTz) JSetStringT( pInfo->m_hContact, "TzName", szTz ); else JDeleteSetting( pInfo->m_hContact, "TzName" ); return; } } else if ( pInfo->m_nIqType == JABBER_IQ_TYPE_ERROR ) { if ( JGetWord( pInfo->m_hContact, "Status", ID_STATUS_OFFLINE ) == ID_STATUS_OFFLINE ) return; } JDeleteSetting( pInfo->m_hContact, "Timezone" ); JDeleteSetting( pInfo->m_hContact, "TzName" ); }