| author | Cesar Negrete (NuttyBunny) <cesar.negrete@gmail.com> | 2011-03-27 15:21:35 (GMT) |
|---|---|---|
| committer | Cesar Negrete (NuttyBunny) <cesar.negrete@gmail.com> | 2011-03-27 15:21:35 (GMT) |
| commit | e92f63e6fbd315c6a26aab354631ee10a1eb3411 (patch) (side-by-side diff) | |
| tree | 067ba8098bae804fbcd8e2c6149368a68eacf96a | |
| parent | 95568461bac2e372f2be9590fa74f5c34b824704 (diff) | |
241 files changed, 91144 insertions, 16546 deletions
diff --git a/3.0/Compiler Source Files/Patches/imaccountvalidator-patches b/3.0/Compiler Source Files/Patches/imaccountvalidator-patches new file mode 100644 index 0000000..1e492ac --- a/dev/null +++ b/3.0/Compiler Source Files/Patches/imaccountvalidator-patches @@ -0,0 +1,33 @@ +diff -rupN imaccountvalidator-1.0//inc/IMAccountValidatorHandler.h imaccountvalidator-1.0-new//inc/IMAccountValidatorHandler.h +--- imaccountvalidator-1.0//inc/IMAccountValidatorHandler.h 2011-03-25 10:35:57.680553000 -0600 ++++ imaccountvalidator-1.0-new//inc/IMAccountValidatorHandler.h 2011-03-27 09:08:42.556552999 -0600 +@@ -40,6 +40,7 @@ class IMAccountValidatorApp; + #define PURPLE_ICQ "prpl-icq" + #define PURPLE_JABBER "prpl-jabber" + #define PURPLE_LIVE "prpl-msn" ++#define PURPLE_WLM "prpl-msn-pecan" + #define PURPLE_MYSPACE "prpl-myspace" + #define PURPLE_QQ "prpl-qq" + #define PURPLE_SAMETIME "prpl-meanwhile" +@@ -55,6 +56,7 @@ class IMAccountValidatorApp; + #define TEMPLATE_ICQ "org.webosinternals.messaging.icq" + #define TEMPLATE_JABBER "org.webosinternals.messaging.jabber" + #define TEMPLATE_LIVE "org.webosinternals.messaging.live" ++#define TEMPLATE_WLM "org.webosinternals.messaging.wlm" + #define TEMPLATE_MYSPACE "org.webosinternals.messaging.myspace" + #define TEMPLATE_QQ "org.webosinternals.messaging.qq" + #define TEMPLATE_SAMETIME "org.webosinternals.messaging.sametime" +diff -rupN imaccountvalidator-1.0//Makefile imaccountvalidator-1.0-new//Makefile +--- imaccountvalidator-1.0//Makefile 2011-03-25 10:35:57.808553000 -0600 ++++ imaccountvalidator-1.0-new//Makefile 2011-03-27 09:08:42.560553000 -0600 +@@ -7,7 +7,7 @@ LOCAL_INCLUDES := -I$(QPEDIR)/include/mo + -I./inc \ + -I$(STAGING_INCDIR) \ + -I. +-LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) ++LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) -march=armv7-a + LOCAL_CPPFLAGS := $(CPPFLAGS) -fno-rtti + +-include Makefile.inc +\ No newline at end of file ++include Makefile.inc diff --git a/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches b/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches new file mode 100644 index 0000000..1911bbe --- a/dev/null +++ b/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches @@ -0,0 +1,5813 @@ +diff -rupN imlibpurpleservice-1.0/inc/PalmImCommon.h imlibpurpleservice-1.0-new//inc/PalmImCommon.h +--- imlibpurpleservice-1.0/inc/PalmImCommon.h 2011-03-25 10:45:40.648552999 -0600 ++++ imlibpurpleservice-1.0-new//inc/PalmImCommon.h 2011-03-27 09:10:06.064552999 -0600 +@@ -40,6 +40,7 @@ + #define SERVICENAME_ICQ "type_icq" + #define SERVICENAME_JABBER "type_jabber" + #define SERVICENAME_LIVE "type_live" ++#define SERVICENAME_WLM "type_wlm" + #define SERVICENAME_MYSPACE "type_myspace" + #define SERVICENAME_QQ "type_qq" + #define SERVICENAME_SAMETIME "type_sametime" +@@ -57,6 +58,7 @@ + #define CAPABILITY_ICQ "org.webosinternals.messaging.icq" + #define CAPABILITY_JABBER "org.webosinternals.messaging.jabber" + #define CAPABILITY_LIVE "org.webosinternals.messaging.live" ++#define CAPABILITY_WLM "org.webosinternals.messaging.wlm" + #define CAPABILITY_MYSPACE "org.webosinternals.messaging.myspace" + #define CAPABILITY_QQ "org.webosinternals.messaging.qq" + #define CAPABILITY_SAMETIME "org.webosinternals.messaging.sametime" +diff -rupN imlibpurpleservice-1.0/Makefile imlibpurpleservice-1.0-new//Makefile +--- imlibpurpleservice-1.0/Makefile 2011-03-25 10:45:40.672552999 -0600 ++++ imlibpurpleservice-1.0-new//Makefile 2011-03-27 09:10:06.108553000 -0600 +@@ -7,7 +7,7 @@ LOCAL_INCLUDES := -I$(QPEDIR)/include/mo + -I./inc \ + -I$(STAGING_INCDIR) \ + -I. +-LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) ++LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) -march=armv7-a + LOCAL_CPPFLAGS := $(CPPFLAGS) -fno-rtti + +-include Makefile.inc +\ No newline at end of file ++include Makefile.inc +diff -rupN imlibpurpleservice-1.0/Makefile.inc imlibpurpleservice-1.0-new//Makefile.inc +--- imlibpurpleservice-1.0/Makefile.inc 2011-03-25 10:45:39.596552999 -0600 ++++ imlibpurpleservice-1.0-new//Makefile.inc 2011-03-27 09:10:04.848552999 -0600 +@@ -1,5 +1,6 @@ ++CXX = g++ + LIBS := -llunaservice -lmojoluna -lmojodb -lmojocore -lpurple -lsanitize +-LOCAL_LDFLAGS := $(LDFLAGS) $(LIBS) -Llib/armv6 -Llib/armv7 -Llib/x86 -Wl,-R/media/cryptofs/apps/usr/palm/applications/org.webosinternals.messaging/sysfiles/usr/lib -Wl,-Rlib/x86 -Wl,-Rlib/armv6 -Wl,-Rlib/armv7 ++LOCAL_LDFLAGS := -Wl,-Rlib/armv7 -Llib/armv7 $(LDFLAGS) $(LIBS) -Wl,-R/media/cryptofs/apps/usr/palm/applications/org.webosinternals.messaging/sysfiles/usr/lib + + IM_SOURCES := \ + BuddyListConsolidator.cpp \ +@@ -27,7 +28,7 @@ IM_OBJECTS := $(IM_SOURCES:%.cpp=$(OBJDI + all: setup $(IM_TARGET) + + $(IM_TARGET): $(IM_OBJECTS) +- $(CXX) -o $(IM_TARGET) $(IM_OBJECTS) $(LOCAL_LDFLAGS) ++ $(CXX) -o $(IM_TARGET) $(IM_OBJECTS) $(LOCAL_LDFLAGS) lib/armv7/libstdc++.so.6 + + $(OBJDIR)/%.o: %.cpp + $(CXX) -MMD $(INCLUDES) $(LOCAL_CFLAGS) $(LOCAL_CPPFLAGS) -c $< -o $@ +diff -rupN imlibpurpleservice-1.0/src/IMLoginState.cpp imlibpurpleservice-1.0-new//src/IMLoginState.cpp +--- imlibpurpleservice-1.0/src/IMLoginState.cpp 2011-03-25 10:45:40.740553000 -0600 ++++ imlibpurpleservice-1.0-new//src/IMLoginState.cpp 2011-03-27 09:10:06.156553000 -0600 +@@ -1413,6 +1413,9 @@ void IMLoginSyncStateHandler::updateSync + else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){ + m_capabilityId.assign(CAPABILITY_LIVE); + } ++ else if (strcmp(serviceName, SERVICENAME_WLM) == 0){ ++ m_capabilityId.assign(CAPABILITY_WLM); ++ } + else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){ + m_capabilityId.assign(CAPABILITY_MYSPACE); + } +diff -rupN imlibpurpleservice-1.0/src/IMLoginState.cpp~ imlibpurpleservice-1.0-new//src/IMLoginState.cpp~ +--- imlibpurpleservice-1.0/src/IMLoginState.cpp~ 1969-12-31 18:00:00.000000000 -0600 ++++ imlibpurpleservice-1.0-new//src/IMLoginState.cpp~ 2011-03-27 09:10:06.124553000 -0600 +@@ -0,0 +1,1538 @@ ++/* ++ * IMLoginState.h ++ * ++ * Copyright 2010 Palm, Inc. All rights reserved. ++ * ++ * This program is free software and licensed under the terms of the GNU ++ * General Public License Version 2 as published by the Free ++ * Software Foundation; ++ * ++ * 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, ++ * Version 2 along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- ++ * 1301, USA ++ * ++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM ++ * Transport service for use on a mobile device. ++ * ++ * IMLoginState class handles changes in user login status ++ */ ++ ++#include "IMLoginState.h" ++#include "db/MojDbQuery.h" ++#include "PalmImCommon.h" ++#include "IMDefines.h" ++#include "IMServiceApp.h" ++#include "ConnectionStateHandler.h" ++#include "DisplayController.h" ++#include "LibpurpleAdapter.h" ++ ++/* ++ * IMLoginState ++ */ ++IMLoginState::IMLoginState(MojService* service) ++: m_loginStateRevision(0), ++ m_signalHandler(NULL), ++ m_service(service) ++{ ++ // Register for login callbacks ++ LibpurpleAdapter::assignIMLoginState(this); ++ // Register for connection changed callbacks. ++ ConnectionState::setLoginStateCallback(this); ++} ++ ++IMLoginState::~IMLoginState() ++{ ++ LibpurpleAdapter::assignIMLoginState(NULL); ++ ConnectionState::setLoginStateCallback(NULL); ++} ++ ++void IMLoginState::handlerDone(IMLoginStateHandlerInterface* handler) ++{ ++ // this gets called from "handler's" destructor ++ if (m_signalHandler != handler) ++ { ++ MojLogError(IMServiceApp::s_log, _T("handlerDone doesn't match. ours=%p, theirs=%p being destroyed"), m_signalHandler, handler); ++ } ++ else ++ { ++ m_signalHandler = NULL; ++ } ++} ++ ++void IMLoginState::loginForTesting(MojServiceMessage* serviceMsg, const MojObject payload) ++{ ++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this); ++ m_signalHandler->loginForTesting(serviceMsg, payload); ++} ++ ++/* ++ * LoginState DB watch was triggered ++ */ ++MojErr IMLoginState::handleLoginStateChange(MojServiceMessage* serviceMsg, const MojObject payload) ++{ ++ // we should not be orphaning an existing handler... ++ if (NULL != m_signalHandler) { ++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange: called when signal handler already existed.")); ++ } ++ ++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this); ++ return m_signalHandler->handleLoginStateChange(serviceMsg, payload); ++} ++ ++/* ++ * Connection Manager subscription watch was triggered ++ */ ++MojErr IMLoginState::handleConnectionChanged(const MojObject& payload) ++{ ++ //TODO: this should be its own ConnectionChangedHandler ++ if (m_signalHandler == NULL) ++ { ++ MojLogWarning(IMServiceApp::s_log, _T("handleConnectionChanged needed to create a signal handler")); ++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this); ++ } ++ return m_signalHandler->handleConnectionChanged(payload); ++} ++ ++/* ++ * Callback from libpurple when the account is signed on or off ++ */ ++void IMLoginState::loginResult(const char* serviceName, const char* username, LoginResult type, bool loggedOut, const char* errCode, bool noRetry) ++{ ++ if (m_signalHandler == NULL) ++ { ++ MojLogWarning(IMServiceApp::s_log, _T("loginResult needed to create a signal handler")); ++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this); ++ } ++ m_signalHandler->loginResult(serviceName, username, type, loggedOut, errCode, noRetry); ++} ++ ++/* ++ * Callback from libpurple when buddy list is synced ++ */ ++void IMLoginState::buddyListResult(const char* serviceName, const char* username, MojObject& buddyList, bool fullList) ++{ ++ // fullList == false means it is just changes to an individual buddy ++ if (fullList == false) ++ { ++ MojLogError(IMServiceApp::s_log, _T("buddyListResult: fullList is false. Doing nothing.")); ++ //TODO: Query com.palm.contact for this buddy to see if it needs to be updated (picture or name changed) ++ //TODO: Merge (with query) com.palm.imbuddystatus to update this buddy's availability and custom message ++ } ++ else ++ { ++ if (m_signalHandler == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("buddyListResult called but m_signalHandler == NULL. Doing nothing.")); ++ } ++ else ++ { ++ m_signalHandler->fullBuddyListResult(serviceName, username, buddyList); ++ } ++ } ++} ++ ++ ++bool IMLoginState::getLoginStateData(const MojString& key, LoginStateData& state) ++{ ++ bool found = (m_loginState.find(key) != m_loginState.end()); ++ if (found) ++ { ++ state = m_loginState[key]; ++ } ++ return found; ++} ++ ++bool IMLoginState::getLoginStateData(const MojString& serviceName, const MojString& username, LoginStateData& state) ++{ ++ std::map<MojString, LoginStateData>::iterator itr = m_loginState.begin(); ++ bool found = false; ++ while (!found && itr != m_loginState.end()) ++ { ++ state = itr->second; ++ found = (state.getServiceName() == serviceName && state.getUsername() == username); ++ if (!found) ++ { ++ itr++; ++ } ++ } ++ ++ return found; ++} ++ ++void IMLoginState::putLoginStateData(const MojString& key, LoginStateData& newState) ++{ ++ m_loginState[key] = newState; ++} ++ ++ ++/* ++ * IMLoginStateHandler -- signal handler ++ */ ++IMLoginStateHandler::IMLoginStateHandler(MojService* service, MojInt64 loginStateRevision, IMLoginState* loginStateController) ++: m_activityAdoptSlot(this, &IMLoginStateHandler::activityAdoptResult), ++ m_activityCompleteSlot(this, &IMLoginStateHandler::activityCompleteResult), ++ m_setWatchSlot(this, &IMLoginStateHandler::setWatchResult), ++ m_loginStateQuerySlot(this, &IMLoginStateHandler::loginStateQueryResult), ++ m_getCredentialsSlot(this, &IMLoginStateHandler::getCredentialsResult), ++ m_updateLoginStateSlot(this, &IMLoginStateHandler::updateLoginStateResult), ++ m_ignoreUpdateLoginStateSlot(this, &IMLoginStateHandler::ignoreUpdateLoginStateResult), ++ m_queryForContactsSlot(this, &IMLoginStateHandler::queryForContactsResult), ++ m_queryForBuddyStatusSlot(this, &IMLoginStateHandler::queryForBuddyStatusResult), ++ m_markBuddiesAsOfflineSlot(this, &IMLoginStateHandler::markBuddiesAsOfflineResult), ++ m_moveMessagesToPendingSlot(this, &IMLoginStateHandler::moveMessagesToPendingResult), ++ m_moveCommandsToPendingSlot(this, &IMLoginStateHandler::moveCommandsToPendingResult), ++ m_service(service), ++ m_dbClient(service, MojDbServiceDefs::ServiceName), ++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName), ++ m_loginStateController(loginStateController), ++ m_activityId(-1), ++ m_workingLoginStateRev(loginStateRevision), ++ m_buddyListConsolidator(NULL) ++{ ++} ++ ++ ++IMLoginStateHandler::~IMLoginStateHandler() ++{ ++ // Tell IMLoginState that this handler is destroyed ++ m_loginStateController->handlerDone(this); ++} ++ ++void IMLoginStateHandler::loginForTesting(MojServiceMessage* serviceMsg, const MojObject payload) ++{ ++ LoginParams loginParams; ++ ++ // your username and password go here! ++ loginParams.username = "xxx@aol.com"; ++ loginParams.password = "xxx"; ++ loginParams.serviceName = "type_aim"; ++ ++ loginParams.availability = PalmAvailability::ONLINE; ++ loginParams.customMessage = ""; ++ loginParams.connectionType = "wan"; ++ loginParams.localIpAddress = NULL; ++ loginParams.accountId = ""; ++ LibpurpleAdapter::login(&loginParams, m_loginStateController); ++ serviceMsg->replySuccess(); ++} ++/* ++ * This is an entry point into the IMLoginStateHandler machine. This is triggered when the db changes. ++ */ ++MojErr IMLoginStateHandler::handleLoginStateChange(MojServiceMessage* serviceMsg, const MojObject payload) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ ++ m_activityId = -1; ++ // get the $activity object ++ MojObject activityObj; ++ bool found = payload.get("$activity", activityObj); ++ if (found) ++ { ++ found = activityObj.get("activityId", m_activityId); ++ } ++ ++ if (!found) ++ { ++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange parameter has no activityId")); ++ //TODO No activity, so query the activity manager to see if we've got one. If not, create/start one. ++ // For now, just do the query ++ queryLoginState(); ++ } ++ else ++ { ++ adoptActivity(); ++ } ++ ++ serviceMsg->replySuccess(); ++ return MojErrNone; ++} ++ ++/* ++ * This is an entry point into the IMLoginStateHandler machine. This is triggered by connection changes ++ */ ++MojErr IMLoginStateHandler::handleConnectionChanged(const MojObject payload) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ MojErr err; ++ // Things to check: ++ // + no connection - ensure all logged out ++ // + specific interface dropped - ensure all accounts on that interface are logged off ++ // + wifi is available - log out any accounts on wan so they switch to wifi ++ ++ // If there's not internet connection, set all records to offline ++ bool wanConnected = ConnectionState::wanConnected(); ++ bool wifiConnected = ConnectionState::wifiConnected(); ++ if (!ConnectionState::hasInternetConnection()) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged no connection. setting all accounts offline")); ++ MojDbQuery query; // intentionally empty query since we want all records changed ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.putString("ipAddress", ""); ++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps); ++ ++ // Mark all buddies so they look offline to us ++ MojString empty; ++ empty.assign(""); ++ markBuddiesAsOffline(empty); ++ ++ // Also tell libpurple to disconnect ++ LibpurpleAdapter::deviceConnectionClosed(true, NULL); ++ } ++ // Two reasons for disconnecting from WAN: it went down or WiFi came up. ++ // WiFi connection is preferred over WAN ++ else if (wanConnected == false || (wanConnected == true && wifiConnected == true)) ++ { ++ //NOTE don't need to mark the buddies as offline since we're just switching to WiFi ++ ++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged wan off, but wifi on. setting all wan accounts offline")); ++ MojDbQuery query; ++ query.where("ipAddress", MojDbQuery::OpEq, ConnectionState::wanIpAddress()); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.putString("ipAddress", ""); ++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps); ++ ++ // Also tell libpurple to disconnect ++ LibpurpleAdapter::deviceConnectionClosed(false, ConnectionState::wanIpAddress()); ++ } ++ else if (wifiConnected == false) ++ { ++ //NOTE don't need to mark the buddies as offline since we're just switching to WAN ++ ++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged wifi off, setting them offline")); ++ MojDbQuery query; ++ query.where("ipAddress", MojDbQuery::OpEq, ConnectionState::wifiIpAddress()); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.putString("ipAddress", ""); ++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps); ++ ++ // Also tell libpurple to disconnect ++ LibpurpleAdapter::deviceConnectionClosed(false, ConnectionState::wifiIpAddress()); ++ } ++ ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::activityAdoptResult(MojObject& payload, MojErr resultErr) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ ++ if (resultErr != MojErrNone) ++ { ++ // adopt failed ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("activity manager adopt FAILED. error %d - %s"), resultErr, error.data()); ++ } ++ else ++ { ++ //IMServiceHandler::logMojObjectJsonString(_T("activityAdoptResult payload: %s"), payload); ++ bool adopted = false; ++ payload.get("adopted", adopted); //TODO: check for "orphan"? ++ ++ // we currently get 2 notifications from the adopt call. ignore the first one ++ if (adopted) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("Activity adopted. Querying Login State")); ++ queryLoginState(); ++ } ++ } ++ ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::activityCompleteResult(MojObject& payload, MojErr resultErr) ++{ ++ //TODO: log if there are errors ++ m_activityAdoptSlot.cancel(); ++ //setWatch(); ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::setWatchResult(MojObject& payload, MojErr resultErr) ++{ ++ //TODO: log if there are errors ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::loginStateQueryResult(MojObject& payload, MojErr resultErr) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ ++ if (resultErr != MojErrNone) ++ { ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("loginStateQuery failed. error %d - %s"), resultErr, error.data()); ++ } ++ else ++ { ++ IMServiceHandler::logMojObjectJsonString(_T("loginStateQuery success: %s"), payload); ++ // result is in the form {results:[{_id, _rev, username, serviceName, state, availability, customMessage}]} ++ MojObject results; ++ payload.get("results", results); ++ ++ processLoginStates(results); ++ } ++ ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::updateLoginStateResult(MojObject& payload, MojErr resultErr) ++{ ++ // DB state is updated so complete the activity and reset the watch so it ++ // can fire again and start the next state transition ++ completeAndResetWatch(); ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::ignoreUpdateLoginStateResult(MojObject& payload, MojErr resultErr) ++{ ++ // This is for intermediate changes to login state (like "logging in") so the result ++ // is not actionable. ++ if (resultErr != MojErrNone) ++ { ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("ERROR Intermediate UpdateLoginState. error %d - %s"), resultErr, error.data()); ++ } ++ return MojErrNone; ++} ++ ++// Set the state & availability to offline and set errorCode ++// dbmerge will then complete and reset the activity ++MojErr IMLoginStateHandler::handleBadCredentials(const MojString& serviceName, const MojString& username, const char* err) ++{ ++ MojDbQuery query; ++ query.where("serviceName", MojDbQuery::OpEq, serviceName); ++ query.where("username", MojDbQuery::OpEq, username); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putInt("availability", PalmAvailability::OFFLINE); ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.putString("ipAddress", ""); ++ if (err != NULL) ++ { ++ mergeProps.putString("errorCode", err); ++ } ++ m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps); ++ ++ // update the syncState record for this account so account dashboard can display errors ++ // first we need to find our account id ++ LoginStateData state; ++ MojString accountId, service, user; ++ service.assign(serviceName); ++ user.assign(username); ++ bool found = m_loginStateController->getLoginStateData(service, user, state); ++ if (found) { ++ accountId = state.getAccountId(); ++ MojRefCountedPtr<IMLoginSyncStateHandler> syncStateHandler(new IMLoginSyncStateHandler(m_service)); ++ syncStateHandler->updateSyncStateRecord(serviceName, accountId, LoginCallbackInterface::LOGIN_FAILED, err); ++ } ++ else { ++ MojLogError(IMServiceApp::s_log, _T("loginResult: could not find account Id in cached login states map. No syncState record created.")); ++ // can we do anything here?? ++ } ++ ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::getCredentialsResult(MojObject& payload, MojErr resultErr) ++{ ++ MojLogError(IMServiceApp::s_log, _T("getCredentialsResult: wanConnected:%d wifiConnected:%d"),ConnectionState::wanConnected(),ConnectionState::wifiConnected()); ++ if (resultErr != MojErrNone) ++ { ++ // Failed to get credentials, so log the issue and set the state to offlnie ++ MojString serviceName = m_workingLoginState.getServiceName(); ++ MojString username = m_workingLoginState.getUsername(); ++ ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("Failed to get credentials on %s. error %d - %s"), serviceName.data(), resultErr, error.data()); ++ ++ handleBadCredentials(serviceName, username, ERROR_BAD_PASSWORD); ++ } ++ // About to login so this is the time to make a final check for internet connection ++ else if (!ConnectionState::hasInternetConnection()) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("No internet connection available!")); ++ // No internet so mark this activity complete and reset the watch ++ // which will fire next time there's a stable connection ++ completeAndResetWatch(); ++ } ++ else ++ { ++ //TODO: get rid of m_workingLoginState: what in the credentials response can tie it back to its corresponding m_loginState? ++ ++ // Now get the login params and request login ++ MojObject credentials; ++ MojErr err = payload.getRequired("credentials", credentials); ++ ++ LoginParams loginParams; ++ MojString password; ++ err = credentials.getRequired("password", password); ++ if (password.empty()) ++ { ++ MojLogError(IMServiceApp::s_log, _T("Password is empty. I think this is not ok.")); ++ } ++ ++ MojString serviceName = m_workingLoginState.getServiceName(); ++ MojString username = m_workingLoginState.getUsername(); ++ ++ MojString connectionType, localIpAddress; ++ ConnectionState::getBestConnection(connectionType, localIpAddress); ++ // We're about to log in, so set the state to "logging in" ++ updateLoginStateNoResponse(serviceName, username, LOGIN_STATE_LOGGING_ON, localIpAddress.data()); ++ ++ loginParams.password = password.data(); ++ loginParams.accountId = m_workingLoginState.getAccountId().data(); ++ loginParams.username = username.data(); ++ loginParams.serviceName = serviceName.data(); ++ loginParams.availability = m_workingLoginState.getAvailability(); ++ loginParams.customMessage = m_workingLoginState.getCustomMessage(); ++ loginParams.connectionType = connectionType.data(); ++ loginParams.localIpAddress = localIpAddress.data(); ++ ++ // Login may be asynchronous with the result callback in loginResult(). ++ // Also deal with immediate results ++ LibpurpleAdapter::LoginResult result; ++ result = LibpurpleAdapter::login(&loginParams, m_loginStateController); ++ if (result == LibpurpleAdapter::FAILED) ++ { ++ handleBadCredentials(serviceName, username, ERROR_GENERIC_ERROR); ++ } ++ if (result == LibpurpleAdapter::INVALID_CREDENTIALS) ++ { ++ handleBadCredentials(serviceName, username, ERROR_AUTHENTICATION_FAILED); ++ } ++ else if (result == LibpurpleAdapter::ALREADY_LOGGED_IN) ++ { ++ MojDbQuery query; ++ query.where("serviceName", MojDbQuery::OpEq, serviceName); ++ query.where("username", MojDbQuery::OpEq, username); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", LOGIN_STATE_ONLINE); ++ mergeProps.putString("ipAddress", localIpAddress); ++ m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps); ++ ++ // update any imcommands that are in the "waiting-for-connection" status ++ moveWaitingCommandsToPending(); ++ } ++ } ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::queryForContactsResult(MojObject& payload, MojErr resultErr) ++{ ++ if (m_buddyListConsolidator == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on contact response")); ++ //TODO what to do here??? ++ } ++ else ++ { ++ MojObject contactArray; ++ payload.getRequired("results", contactArray); ++ m_buddyListConsolidator->setContacts(contactArray); ++ if (m_buddyListConsolidator->isAllDataSet()) ++ { ++ consolidateAllBuddyLists(); ++ } ++ } ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::queryForBuddyStatusResult(MojObject& payload, MojErr resultErr) ++{ ++ if (m_buddyListConsolidator == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on buddystatus response")); ++ //TODO what to do here??? ++ } ++ else ++ { ++ MojObject buddyArray; ++ payload.getRequired("results", buddyArray); ++ m_buddyListConsolidator->setBuddyStatus(buddyArray); ++ if (m_buddyListConsolidator->isAllDataSet()) ++ { ++ consolidateAllBuddyLists(); ++ } ++ } ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::markBuddiesAsOffline(const MojString& serviceName, const MojString& username) ++{ ++ MojErr err = MojErrNotFound; ++ LoginStateData state; ++ bool found = m_loginStateController->getLoginStateData(serviceName, username, state); ++ if (found) ++ { ++ err = markBuddiesAsOffline(state.getAccountId()); ++ } ++ return err; ++} ++ ++ ++MojErr IMLoginStateHandler::markBuddiesAsOffline(const MojString& accountId) ++{ ++ //MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::markBuddiesAsOffline account=%s"), accountId.data()); ++ // All buddies for this account get marked as offline ++ MojDbQuery query; ++ if (accountId.length() > 0) ++ { ++ query.where("accountId", MojDbQuery::OpEq, accountId); ++ } ++ else ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("markBuddiesAsOffline: no accountId - marking all buddies offline")); ++ } ++ ++ query.from(IM_BUDDYSTATUS_KIND); ++ MojObject mergeProps; ++ mergeProps.putInt("availability", PalmAvailability::OFFLINE); ++ mergeProps.putString("status", ""); ++ return m_tempdbClient.merge(m_markBuddiesAsOfflineSlot, query, mergeProps); ++} ++ ++ ++MojErr IMLoginStateHandler::markBuddiesAsOfflineResult(MojObject& payload, MojErr resultErr) ++{ ++ // TODO: anything to do here??? ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::moveWaitingMessagesToPending(const MojString& serviceName, const MojString& username) ++{ ++ //MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::moveWaitingMessagesToPending")); ++ MojDbQuery query; ++ MojString folderName, statusName; ++ folderName.assign(IMMessage::folderStrings[Outbox]); ++ query.where(MOJDB_FOLDER, MojDbQuery::OpEq, folderName); ++ statusName.assign(IMMessage::statusStrings[WaitingForConnection]); ++ query.where(MOJDB_STATUS, MojDbQuery::OpEq, statusName); ++ ++ query.from(IM_IMMESSAGE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString(MOJDB_STATUS, IMMessage::statusStrings[Pending]); ++ return m_dbClient.merge(m_moveMessagesToPendingSlot, query, mergeProps); ++} ++ ++ ++MojErr IMLoginStateHandler::moveMessagesToPendingResult(MojObject& payload, MojErr resultErr) ++{ ++ return MojErrNone; ++} ++ ++/* ++ * Find any imcommands that were left in the "waiting-for-connection" status and make them pending now that we are back online ++ */ ++MojErr IMLoginStateHandler::moveWaitingCommandsToPending() ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::moveWaitingCommandsToPending")); ++ MojDbQuery query; ++ MojString statusName; ++ statusName.assign(IMMessage::statusStrings[WaitingForConnection]); ++ query.where(MOJDB_STATUS, MojDbQuery::OpEq, statusName); ++ ++ query.from(IM_IMCOMMAND_KIND); ++ MojObject mergeProps; ++ mergeProps.putString(MOJDB_STATUS, IMMessage::statusStrings[Pending]); ++ return m_dbClient.merge(m_moveCommandsToPendingSlot, query, mergeProps); ++} ++ ++ ++MojErr IMLoginStateHandler::moveCommandsToPendingResult(MojObject& payload, MojErr resultErr) ++{ ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::queryLoginState() ++{ ++ MojRefCountedPtr<MojServiceRequest> req; ++ MojErr err = m_service->createRequest(req); ++ if (err) ++ { ++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler: create activity manager adopt request failed")); ++ } ++ else ++ { ++ MojDbQuery query; ++ getLoginStateQuery(query); ++ err = m_dbClient.find(m_loginStateQuerySlot, query, false, false); ++ if (err) ++ { ++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange query failed")); ++ } ++ } ++ ++ return err; ++} ++ ++ ++void IMLoginStateHandler::logLoginStates(const LoginStateData& cachedState, const LoginStateData& newState) ++{ ++ MojString stateString; ++ cachedState.toString(stateString); ++ //MojLogInfo(IMServiceApp::s_log, _T("cachedState: %s"), stateString.data()); ++ newState.toString(stateString); ++ //MojLogInfo(IMServiceApp::s_log, _T("newState: %s"), stateString.data()); ++} ++ ++ ++// resultArray should be an array of db objects of the form ++// {_id, _rev, username, serviceName, state, availability, customMessage} ++MojErr IMLoginStateHandler::processLoginStates(MojObject& loginStateArray) ++{ ++ MojErr err = MojErrNone; ++ ++ MojObject loginStateEntry; ++ MojObject::ConstArrayIterator loginStateItr = loginStateArray.arrayBegin(); ++ // This shouldn't happen, but check if there's nothing to do. ++ if (loginStateItr == loginStateArray.arrayEnd()) ++ { ++ MojLogError(IMServiceApp::s_log, _T("processLoginStates had empty result set")); ++ completeAndResetWatch(); ++ } ++ else ++ { ++ // Just grab the first change and deal with it. If other records have changed, we'll ++ // pick that up when the watch is reset. This works because the query is on _rev in ++ // ascending order. ++ loginStateEntry = *loginStateItr; ++ LoginStateData newState; ++ newState.assignFromDbRecord(loginStateEntry); ++ ++ // Update the revision number so this change is removed from the next query. ++ m_loginStateController->setLoginStateRevision(newState.getRevision()); ++ //m_loginStateRevision = newState.getRevision(); ++ ++ LoginStateData cachedState; ++ bool found = m_loginStateController->getLoginStateData(newState.getKey(), cachedState); ++ if (!found) ++ { ++ // Key not found, so assign the new state to the cached state and ++ // let it proceed as normal for login (most likely) or logout ++ cachedState = newState; ++ } ++ ++ logLoginStates(cachedState, newState); ++ m_workingLoginState = newState; ++ if (newState.needsToLogin(cachedState)) ++ { ++ // Get account info & password then start the login process. ++ bool found = false; ++ MojString accountId; ++ err = loginStateEntry.get("accountId", accountId, found); ++ if (err != MojErrNone || !found) ++ { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("ERROR accountId missing from imloginstate. error %d - %s"), err, error.data()); ++ // TODO: fail the record and ... ++ completeAndResetWatch(); ++ } ++ else ++ { ++ MojRefCountedPtr<MojServiceRequest> req; ++ err = m_service->createRequest(req); ++ MojObject params; ++ err = params.put("accountId", accountId); ++ err = params.putString("name", "common"); ++ err = req->send(m_getCredentialsSlot, "com.palm.service.accounts","readCredentials", params, 1); ++ } ++ } ++ else if (newState.needsToLogoff(cachedState)) ++ { ++ // Now log out. The result will come in loginResult() with type=SIGNED_OFF ++ if (LibpurpleAdapter::logout(newState.getServiceName(), newState.getUsername(), m_loginStateController)) ++ { ++ // Update state to show we're in the process of logging off ++ updateLoginStateNoResponse(newState.getServiceName(), newState.getUsername(), LOGIN_STATE_LOGGING_OFF, NULL); ++ } ++ else ++ { ++ // Logout returns false if the account is already logged out. ++ // TODO: update newState to offline to reflect the changed state ++ updateLoginStateNoResponse(newState.getServiceName(), newState.getUsername(), LOGIN_STATE_OFFLINE, NULL); ++ // Make the buddies for this account look offline to us ++ markBuddiesAsOffline(newState.getAccountId()); ++ completeAndResetWatch(); ++ } ++ } ++ else if (newState.needsToGetBuddies(cachedState)) ++ { ++ getBuddyLists(newState.getServiceName(), newState.getUsername(), newState.getAccountId()); ++ } ++ else if (newState.hasAvailabilityChanged(cachedState) || newState.hasCustomMessageChanged(cachedState)) ++ { ++ if (newState.hasAvailabilityChanged(cachedState)) ++ { ++ LibpurpleAdapter::setMyAvailability(newState.getServiceName().data(), newState.getUsername().data(), newState.getAvailability()); ++ } ++ ++ if (newState.hasCustomMessageChanged(cachedState)) ++ { ++ LibpurpleAdapter::setMyCustomMessage(newState.getServiceName().data(), newState.getUsername().data(), newState.getCustomMessage().data()); ++ } ++ ++ completeAndResetWatch(); ++ } ++ else ++ { ++ // Nothing needed to be done so just reset the watch. ++ completeAndResetWatch(); ++ } ++ ++ // Finally, add or update the cached entry to reflect whatever changed ++ m_loginStateController->putLoginStateData(newState.getKey(), newState); ++ } ++ return err; ++} ++ ++ ++// This fires off 3 asynchronous requests with the responses being stored in m_buddyListConsolidator ++// So whichever of the 3 returns last will continue the buddy list processing ++MojErr IMLoginStateHandler::getBuddyLists(const MojString& serviceName, const MojString& username, const MojString& accountId) ++{ ++ m_buddyListConsolidator = new BuddyListConsolidator(m_service, accountId); ++ ++ // Get a full list of buddies. The result is asynchronously returned via the buddyListResult() callback ++ bool ok = LibpurpleAdapter::getFullBuddyList(serviceName, username); ++ if (ok) ++ { ++ //TODO move these two slots into BuddyListConsolidator?? ++ MojErr err; ++ ++ // Get contacts that are buddies of this user ++ MojDbQuery contactsQuery; ++ contactsQuery.from(IM_CONTACT_KIND); ++ contactsQuery.where("accountId", MojDbQuery::OpEq, accountId); ++ err = m_dbClient.find(m_queryForContactsSlot, contactsQuery, false, false); ++ if (err) ++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: query for contacts returned err: %d"),err); ++ ++ // Get buddies of this user ++ MojDbQuery buddiesQuery; ++ buddiesQuery.from(IM_BUDDYSTATUS_KIND); ++ buddiesQuery.where("accountId", MojDbQuery::OpEq, accountId); ++ err = m_tempdbClient.find(m_queryForBuddyStatusSlot, buddiesQuery, false, false); ++ if (err) ++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: query for buddies returned err: %d"),err); ++ } ++ else ++ { ++ //TODO: set the state to offline?? Perhaps loginstate needs a retry count. ++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: getFullBuddyList return false. This is not good")); ++ ++ delete m_buddyListConsolidator; ++ m_buddyListConsolidator = NULL; ++ ++ ++ // Since it failed, we need to reset the watch ourself. ++ completeAndResetWatch(); ++ } ++ ++ return MojErrNone; ++} ++ ++MojErr IMLoginStateHandler::consolidateAllBuddyLists() ++{ ++ if (m_buddyListConsolidator == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null in consolidateBuddyLists(). This shouldn't be possible")); ++ //TODO what to do here??? ++ } ++ else ++ { ++ m_buddyListConsolidator->consolidateContacts(); ++ m_buddyListConsolidator->consolidateBuddyStatus(); ++ // The consolidator cleans itself up. Good kitty. ++ m_buddyListConsolidator = NULL; ++ } ++ ++ // The user is online at this point despite any errors in buddy consolidation ++ ++ // update any imcommands that are in the "waiting-for-connection" status ++ moveWaitingCommandsToPending(); ++ ++ MojString serviceName = m_workingLoginState.getServiceName(); ++ MojString username = m_workingLoginState.getUsername(); ++ MojDbQuery query; ++ query.where("serviceName", MojDbQuery::OpEq, serviceName); ++ query.where("username", MojDbQuery::OpEq, username); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", LOGIN_STATE_ONLINE); ++ return m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps); ++} ++ ++MojErr IMLoginStateHandler::adoptActivity() ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("Adopting activityId: %llu."), m_activityId); ++ MojErr err = MojErrNone; ++ if (m_activityId > 0) ++ { ++ MojRefCountedPtr<MojServiceRequest> req; ++ MojErr err = m_service->createRequest(req); ++ if (err) ++ { ++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler: create activity manager adopt request failed")); ++ } ++ else ++ { ++ MojObject adoptParams; ++ adoptParams.put(_T("activityId"), m_activityId); ++ adoptParams.putBool(_T("subscribe"), true); ++ ++ ++ ++ MojLogInfo(IMServiceApp::s_log, _T("handleLoginStateChange adopting activity id: %llu."), m_activityId); ++ err = req->send(m_activityAdoptSlot, "com.palm.activitymanager", "adopt", adoptParams, MojServiceRequest::Unlimited); ++ if (err) ++ { ++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler::adoptActivity send activity manager adopt request failed")); ++ } ++ } ++ } ++ ++ return err; ++} ++ ++ ++MojErr IMLoginStateHandler::completeAndResetWatch() ++{ ++ //MojLogInfo(IMServiceApp::s_log, _T("Completing activityId: %llu."), m_activityId); ++ MojErr err = MojErrNone; ++ ++ // If there is an active activity, mark it as completed with the "restart" flag ++ if (m_activityId > 0) ++ { ++ MojRefCountedPtr<MojServiceRequest> completeReq; ++ MojErr err = m_service->createRequest(completeReq); ++ MojErrCheck(err); ++ MojObject completeParams; ++ completeParams.put(_T("activityId"), m_activityId); ++ completeParams.put(_T("restart"), true); // Important, we need the activity to restart ++ ++ // Build out the actvity's trigger ++ const char* triggerJson = ++ "{\"key\":\"fired\"," ++ "\"method\":\"palm://com.palm.db/watch\"," ++ "}"; ++ MojObject trigger; ++ trigger.fromJson(triggerJson); ++ MojDbQuery dbQuery; ++ getLoginStateQuery(dbQuery); ++ MojObject queryDetails; ++ dbQuery.toObject(queryDetails); ++ MojObject query; ++ query.put("query", queryDetails); ++ trigger.put("params", query); ++ ++ completeParams.put("trigger", trigger); ++ ++ err = completeReq->send(m_activityCompleteSlot, "com.palm.activitymanager", "complete", completeParams, 1); ++ MojErrCheck(err); ++ ++ m_activityId = -1; // clear this out so we don't keep using it. ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("completeAndResetWatch - no activity id to complete")); ++// setWatch(); ++ } ++ ++ return err; ++} ++ ++/* ++ * NOT USED ++ */ ++//MojErr IMLoginStateHandler::setWatch() ++//{ ++// MojLogError(IMServiceApp::s_log, _T("setWatch 1")); ++// MojLogInfo(IMServiceApp::s_log, _T("Starting new loginstate watch")); ++// MojErr err = MojErrNone; ++// ++// // Reset the activity watch ++// MojRefCountedPtr<MojServiceRequest> watchReq; ++// err = m_service->createRequest(watchReq); ++// MojErrCheck(err); ++// MojLogDebug(IMServiceApp::s_log, _T("com.palm.activitymanager/create")); ++// ++// MojLogError(IMServiceApp::s_log, _T("setWatch 2")); ++// // A lot of the activity object is static data, so fill that in using hardcoded strings, then add the "trigger" ++// // Properties within the "activity" object ++// ++// // Build out the actvity's trigger ++// const char* triggerJson = ++// "{\"key\":\"fired\"," ++// "\"method\":\"palm://com.palm.db/watch\"," ++// "}"; ++// MojObject trigger; ++// trigger.fromJson(triggerJson); ++// MojDbQuery dbQuery; ++// getLoginStateQuery(dbQuery); ++// MojObject queryDetails; ++// dbQuery.toObject(queryDetails); ++// MojObject query; ++// query.put("query", queryDetails); ++// trigger.put("params", query); ++// ++// // Build out the activity ++// const char* activityJson = ++// "{\"name\":\"Libpurple loginstate\"," ++// "\"description\":\"Watch for changes to the imloginstate\"," ++// "\"type\": {\"foreground\": true}," ++// "\"callback\":{\"method\":\"palm://com.palm.imlibpurple/loginStateChanged\"}" ++// "}"; ++// MojObject activity; ++// activity.fromJson(activityJson); ++// activity.put("trigger", trigger); ++// ++// // requirements for internet:true ++// MojObject requirements; ++// err = requirements.put("internet", true); ++// MojErrCheck(err); ++// err = activity.put("requirements", requirements); ++// MojErrCheck(err); ++// ++// MojObject activityCreateParams; ++// activityCreateParams.putBool("start", true); ++// activityCreateParams.put("activity", activity); ++// //TODO should this be Unlimited or just one-off??? ++// err = watchReq->send(m_setWatchSlot, "com.palm.activitymanager", "create", activityCreateParams, MojServiceRequest::Unlimited); ++// MojErrCheck(err); ++// ++// return err; ++//} ++ ++ ++// This is a callback from the LibpurpleAdapter for notification of login events (success, failed, disconnected) ++void IMLoginStateHandler::loginResult(const char* serviceName, const char* username, LoginCallbackInterface::LoginResult type, bool loggedOut, const char* errCode, bool noRetry) ++{ ++ MojErr err = MojErrNone; ++ MojLogInfo(IMServiceApp::s_log, _T("loginResult: user %s, service %s, result=%d, code=%s, loggedOut=%d, noRetry=%d."), username, serviceName, type, errCode, loggedOut, noRetry); ++ ++ MojString serviceNameMoj, usernameMoj, errorCodeMoj; ++ if (errCode == NULL) ++ { ++ // If no error code was specified, assume no error ++ errorCodeMoj.assign(ERROR_NO_ERROR); ++ } ++ else ++ { ++ errorCodeMoj.assign(errCode); ++ } ++ ++ // Want to merge the new state and errorCode values for the given username and serviceName ++ MojDbQuery query; ++ serviceNameMoj.assign(serviceName); ++ usernameMoj.assign(username); ++ query.where("serviceName", MojDbQuery::OpEq, serviceNameMoj); ++ query.where("username", MojDbQuery::OpEq, usernameMoj); ++ query.from(IM_LOGINSTATE_KIND); ++ ++ MojObject mergeProps; ++ if (noRetry) ++ { ++ if (type == LoginCallbackInterface::LOGIN_SUCCESS) ++ { ++ MojLogWarning(IMServiceApp::s_log, _T("loginResult WARNING: ignoring noRetry==true because type==login_success")); ++ } ++ else ++ { ++ mergeProps.putInt("availability", PalmAvailability::OFFLINE); ++ } ++ } ++ ++ // Special case to handle getting logged off because of lost network connection. ++ // There's a race condition where the login fails before the network manager ++ // admits the connection is lost so the following prevents it from immediately ++ // logging off which then triggers the db watch, causing a login to start and ++ // quickly get stopped. ++ if (type == LoginCallbackInterface::LOGIN_FAILED && noRetry == false) ++ { ++ IMLoginFailRetryHandler* handler = new IMLoginFailRetryHandler(m_service); ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.put("errorCode", errorCodeMoj); ++ handler->startTimerActivity(serviceNameMoj, query, mergeProps); ++ } ++ else ++ { ++ switch (type) ++ { ++ case LoginCallbackInterface::LOGIN_SUCCESS: ++ mergeProps.putString("state", LOGIN_STATE_GETTING_BUDDIES); ++ mergeProps.put("errorCode", errorCodeMoj); ++ // Since the login succeeded, move any waiting messages back to pending ++ moveWaitingMessagesToPending(serviceNameMoj, usernameMoj); ++ break; ++ case LoginCallbackInterface::LOGIN_SIGNED_OFF: ++ case LoginCallbackInterface::LOGIN_FAILED: ++ case LoginCallbackInterface::LOGIN_TIMEOUT: ++ mergeProps.putString("state", LOGIN_STATE_OFFLINE); ++ mergeProps.put("errorCode", errorCodeMoj); ++ // Make the buddies for this account look offline to us ++ markBuddiesAsOffline(serviceNameMoj, usernameMoj); ++ break; ++ } ++ err = m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps); ++ } ++ ++ // update the syncState record for this account so account dashboard can display errors ++ // first we need to find our account id ++ LoginStateData state; ++ MojString accountId, service, user; ++ service.assign(serviceName); ++ user.assign(username); ++ bool found = m_loginStateController->getLoginStateData(service, user, state); ++ if (found) { ++ accountId = state.getAccountId(); ++ MojRefCountedPtr<IMLoginSyncStateHandler> syncStateHandler(new IMLoginSyncStateHandler(m_service)); ++ syncStateHandler->updateSyncStateRecord(serviceName, accountId, type, errorCodeMoj); ++ } ++ else { ++ MojLogError(IMServiceApp::s_log, _T("loginResult: could not find account Id in cached login states map. No syncState record created.")); ++ // can we do anything here?? ++ } ++ ++} ++ ++ ++// This is a callback from the LibpurpleAdapter for notification of buddyList changes ++void IMLoginStateHandler::fullBuddyListResult(const char* serviceName, const char* username, MojObject& buddyList) ++{ ++ ++ //TODO: Update com.palm.imbuddystatus (maybe just delete all and add all new) ++ if (m_buddyListConsolidator == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on fullBuddyListResult")); ++ //TODO what to do here??? ++ } ++ else ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("fullBuddyListResult: setting new buddy list")); ++ m_buddyListConsolidator->setNewBuddyList(buddyList); ++ if (m_buddyListConsolidator->isAllDataSet()) ++ { ++ consolidateAllBuddyLists(); ++ } ++ } ++} ++ ++ ++MojErr IMLoginStateHandler::getLoginStateQuery(MojDbQuery& query) ++{ ++ query.from(IM_LOGINSTATE_KIND); ++ query.where("_rev", MojDbQuery::OpGreaterThan, m_workingLoginStateRev); ++ MojObject queryObj; ++ query.toObject(queryObj); ++ IMServiceHandler::logMojObjectJsonString(_T("getLoginStateQuery: %s"), queryObj); ++ return MojErrNone; ++} ++ ++ ++MojErr IMLoginStateHandler::updateLoginStateNoResponse(const MojString& serviceName, const MojString& username, const char* state, const char* ipAddress) ++{ ++ MojDbQuery query; ++ query.where("serviceName", MojDbQuery::OpEq, serviceName); ++ query.where("username", MojDbQuery::OpEq, username); ++ query.from(IM_LOGINSTATE_KIND); ++ MojObject mergeProps; ++ mergeProps.putString("state", state); ++ if (ipAddress != NULL) ++ { ++ mergeProps.putString("ipAddress", ipAddress); ++ } ++ return m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps); ++} ++ ++ ++/* ++ * IMLoginState::LoginStateData ++ */ ++MojErr LoginStateData::assignFromDbRecord(MojObject& record) ++{ ++ MojErr err = MojErrNone; ++ bool found = false; ++ ++ err = record.get("_id", m_recordId, found); ++ if (err != MojErrNone || found == false) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing _id.")); ++ return err; ++ } ++ ++ err = record.get("_rev", m_revision, found); ++ if (err != MojErrNone || found == false) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing _rev. err=%u"), err); ++ return err; ++ } ++ ++ record.get("username", m_username, found); ++ record.get("accountId", m_accountId, found); ++ record.get("serviceName", m_serviceName, found); ++ err = record.get("state", m_state, found); ++ if (err != MojErrNone || found == false) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing state, assuming offline.")); ++ m_state.assign(LOGIN_STATE_OFFLINE); ++ } ++ ++ // This may legitimately be missing ++ record.get("customMessage", m_customMessage, found); ++ ++ err = record.get("availability", m_availability, found); ++ if (err != MojErrNone || found == false) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing availability, assuming offline.")); ++ m_availability = PalmAvailability::OFFLINE; ++ } ++ return err; ++} ++ ++bool LoginStateData::needsToLogin(LoginStateData& oldState) ++{ ++ return (m_state == LOGIN_STATE_OFFLINE && m_availability != PalmAvailability::OFFLINE && m_availability != PalmAvailability::NO_PRESENCE); ++} ++ ++bool LoginStateData::needsToGetBuddies(LoginStateData& oldState) ++{ ++ return (m_state == LOGIN_STATE_GETTING_BUDDIES && m_availability != PalmAvailability::OFFLINE && m_availability != PalmAvailability::NO_PRESENCE && (oldState.m_state == LOGIN_STATE_OFFLINE || oldState.m_state == LOGIN_STATE_LOGGING_ON)); ++} ++ ++bool LoginStateData::needsToLogoff(LoginStateData& oldState) ++{ ++ return (m_state != LOGIN_STATE_OFFLINE && m_availability == PalmAvailability::OFFLINE); ++} ++ ++bool LoginStateData::hasAvailabilityChanged(LoginStateData& oldState) ++{ ++ return (m_availability != oldState.m_availability); ++} ++ ++bool LoginStateData::hasCustomMessageChanged(LoginStateData& newState) ++{ ++ return (m_customMessage != newState.m_customMessage); ++} ++ ++ ++/* ++ * IMLoginFailRetryHandler -- signal handler ++ */ ++IMLoginFailRetryHandler::IMLoginFailRetryHandler(MojService* service) ++: m_activitySubscriptionSlot(this, &IMLoginFailRetryHandler::activitySubscriptionResult), ++ m_dbmergeSlot(this, &IMLoginFailRetryHandler::dbmergeResult), ++ m_activityCompleteSlot(this, &IMLoginFailRetryHandler::activityCompleteResult), ++ m_service(service), ++ m_dbClient(service, MojDbServiceDefs::ServiceName), ++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName), ++ m_activityId(-1) ++{ ++} ++ ++MojErr IMLoginFailRetryHandler::startTimerActivity(const MojString& serviceName, const MojDbQuery& query, const MojObject& mergeProps) ++{ ++ //MojLogError(IMServiceApp::s_log, _T("**********IMLoginFailRetryHandler::startTimerActivity")); ++ MojRefCountedPtr<MojServiceRequest> req; ++ MojErr err = m_service->createRequest(req); ++ if (err != MojErrNone) ++ { ++ MojLogError(IMServiceApp::s_log, _T("IMLoginFailRetryHandler createRequest failed. error %d"), err); ++ } ++ else ++ { ++ m_query = query; ++ m_mergeProps = mergeProps; ++ // This is what the activity looks like ++ // { ++ // "name":"Set IMLoginState Offline " + serviceName, ++ // "description":"Scheduled request to mark the service as offline", ++ // "type":{"foreground":true}, ++ // "schedule":{ ++ // "start":<current date/time> + 2s, ++ // } ++ // } ++ // activity ++ MojObject activity; ++ MojString activityName; ++ activityName.assign(_T("Set IMLoginState Offline ")); ++ activityName.append(serviceName); ++ activity.putString("name", activityName); ++ activity.putString("description", _T("Scheduled request to mark the service as offline")); ++ ++ // activity.type ++ MojObject activityType; ++ activityType.putBool("foreground", true); ++ activity.put("type", activityType); ++ ++ // activity.schedule ++ time_t targetDate; ++ time(&targetDate); ++ targetDate += 2; // schedule for 2 seconds in the future ++ tm* ptm = gmtime(&targetDate); ++ char scheduleTime[50]; ++ sprintf(scheduleTime, "%d-%02d-%02d %02d:%02d:%02dZ", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); ++ MojLogDebug(IMServiceApp::s_log, _T("IMLoginFailRetryHandler: com.palm.activitymanager/create date=%s"), scheduleTime); ++ MojObject scheduleObj; ++ scheduleObj.putString("start", scheduleTime); ++ activity.put("schedule", scheduleObj); ++ ++ MojObject params; ++ params.putBool("start", true); ++ params.putBool("subscribe", true); ++ params.put("activity", activity); ++ ++ err = req->send(m_activitySubscriptionSlot, "com.palm.activitymanager", "create", params, MojServiceRequest::Unlimited); ++ } ++ ++ return err; ++} ++ ++// Expected responses ++// {"activityId":78,"returnValue":true} ++// {"activityId":78,"event":"start","returnValue":true} <-- this occurs when the timeout fires ++// Error in case there is already an activity for the given serviceName ++// {"errorCode":17,"errorText":"Activity with that name already exists","returnValue":false} ++MojErr IMLoginFailRetryHandler::activitySubscriptionResult(MojObject& result, MojErr err) ++{ ++ MojLogDebug(IMServiceApp::s_log, _T("IMLoginFailRetryHandler::activitySubscriptionResult begin err=%d"), err); ++ ++ MojString event; ++ if (err == MojErrNone && result.getRequired(_T("event"), event) == MojErrNone) ++ { ++ if (event == "start") ++ { ++ bool found = result.get("activityId", m_activityId); ++ if (!found) ++ MojLogError(IMServiceApp::s_log, _T("activitySubscriptionResult: parameter has no activityId")); ++ err = m_dbClient.merge(m_dbmergeSlot, m_query, m_mergeProps); ++ //TODO also markBuddiesAsOffline? Shouldn't need to since lost connection should do that later ++ } ++ } ++ return err; ++} ++ ++MojErr IMLoginFailRetryHandler::dbmergeResult(MojObject& result, MojErr err) ++{ ++ if (m_activityId > 0) ++ { ++ MojRefCountedPtr<MojServiceRequest> req; ++ err = m_service->createRequest(req); ++ MojObject completeParams; ++ completeParams.put(_T("activityId"), m_activityId); ++ err = req->send(m_activityCompleteSlot, "com.palm.activitymanager", "complete", completeParams, 1); ++ } ++ return err; ++} ++ ++MojErr IMLoginFailRetryHandler::activityCompleteResult(MojObject& result, MojErr err) ++{ ++ m_activitySubscriptionSlot.cancel(); ++ return err; ++} ++ ++ ++/* ++ * IMLoginSyncStateHandler -- signal handler to update syncState ++ */ ++IMLoginSyncStateHandler::IMLoginSyncStateHandler(MojService* service) ++: m_removeSyncStateSlot(this, &IMLoginSyncStateHandler::removeSyncStateResult), ++ m_saveSyncStateSlot(this, &IMLoginSyncStateHandler::saveSyncStateResult), ++ m_service(service), ++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName) ++{ ++} ++ ++/* ++ * Update the syncState record in temp DB ++ * delete any existing syncState record. Covers the case when login succeeds ++ * if the login failed due to bad password, need to add a syncState record with the correct account error code ++ */ ++void IMLoginSyncStateHandler::updateSyncStateRecord(const char* serviceName, MojString accountId, LoginCallbackInterface::LoginResult type, const char* errCode) ++{ ++ // syncState record: ++ /* ++ { ++ "_kind": "com.palm.account.syncstate:1", ++ "accountId": "++HBI8gkY38GlaVk", LoginStateData.getAccountId() ++ "busAddress": "com.palm.imlibpurple" ++ "capabilityProvider": "com.palm.google.talk" or "com.palm.aol.aim" ++ "errorCode": "401_UNAUTHORIZED", ++ "errorText": "Incorrect Password", ++ "syncState": "ERROR" ++ } ++ */ ++ ++ ++ // get the capabilityProvidor id from the service ++ // TODO - should read this out of template ID... ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0) { ++ m_capabilityId.assign(CAPABILITY_AIM); ++ } ++ else if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0){ ++ m_capabilityId.assign(CAPABILITY_FACEBOOK); ++ } ++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0){ ++ m_capabilityId.assign(CAPABILITY_GTALK); ++ } ++ else if (strcmp(serviceName, SERVICENAME_GADU) == 0){ ++ m_capabilityId.assign(CAPABILITY_GADU); ++ } ++ else if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0){ ++ m_capabilityId.assign(CAPABILITY_GROUPWISE); ++ } ++ else if (strcmp(serviceName, SERVICENAME_ICQ) == 0){ ++ m_capabilityId.assign(CAPABILITY_ICQ); ++ } ++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0){ ++ m_capabilityId.assign(CAPABILITY_JABBER); ++ } ++ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){ ++ m_capabilityId.assign(CAPABILITY_LIVE); ++ } ++ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){ ++ m_capabilityId.assign(CAPABILITY_MYSPACE); ++ } ++ else if (strcmp(serviceName, SERVICENAME_QQ) == 0){ ++ m_capabilityId.assign(CAPABILITY_QQ); ++ } ++ else if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0){ ++ m_capabilityId.assign(CAPABILITY_SAMETIME); ++ } ++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0){ ++ m_capabilityId.assign(CAPABILITY_SIPE); ++ } ++ else if (strcmp(serviceName, SERVICENAME_XFIRE) == 0){ ++ m_capabilityId.assign(CAPABILITY_XFIRE); ++ } ++ else if (strcmp(serviceName, SERVICENAME_YAHOO) == 0){ ++ m_capabilityId.assign(CAPABILITY_YAHOO); ++ } ++ else { ++ MojLogError(IMServiceApp::s_log, _T("updateSyncStateRecord: unknown serviceName %s. No syncState record created."), serviceName); ++ // can we do anything here?? ++ return; ++ } ++ ++ // save the input parameters ++ m_accountId = accountId; ++ ++ // get the error code for the new record if we need it ++ if (LoginCallbackInterface::LOGIN_SUCCESS != type) { ++ ++ // for now, we only show dashboard for authentication errors ++ if ((strcmp(errCode, ERROR_AUTHENTICATION_FAILED) == 0) || ++ (strcmp(errCode, ERROR_BAD_USERNAME) == 0) || ++ (strcmp(errCode, ERROR_BAD_PASSWORD) == 0)) { ++ m_errorCode.assign(ACCOUNT_401_UNAUTHORIZED); ++ } ++ } ++ ++ // delete any existing record for this account ++ // construct our where clause - find by username, accountId and servicename ++ MojDbQuery query; ++ query.where("capabilityProvider", MojDbQuery::OpEq, m_capabilityId); ++ query.where("accountId", MojDbQuery::OpEq, m_accountId); ++ query.from(IM_SYNCSTATE_KIND); ++ ++ // delete the old record. If none exists, that is OK - DB just returns a count of 0 in result ++ MojErr err = m_tempdbClient.del(m_removeSyncStateSlot, query); ++ if (err) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("updateSyncStateRecord: dbClient.del() failed: error %d - %s"), err, error.data()); ++ } ++} ++ ++/* ++ * Remove the old the syncState record result ++ */ ++MojErr IMLoginSyncStateHandler::removeSyncStateResult(MojObject& result, MojErr resultErr) ++{ ++ if (resultErr) { ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("removeSyncStateResult: DB del failed. error %d - %s"), resultErr, error.data()); ++ } ++ else { ++ IMServiceHandler::logMojObjectJsonString(_T("removeSyncStateResult: syncState successfully removed: %s"), result); ++ } ++ ++ // no error means we are done ++ if (m_errorCode.empty()) ++ return MojErrNone; ++ ++ ++ // otherwise add the new record ++ MojObject props; ++ props.putString(_T("errorCode"), m_errorCode); ++ props.putString(_T("_kind"), IM_SYNCSTATE_KIND); ++ props.putString(_T("accountId"), m_accountId); ++ props.putString(_T("capabilityProvider"), m_capabilityId); ++ ++ // bus address ++ MojString busAddr; ++ busAddr.assign(_T("org.webosinternals.imlibpurple")); ++ props.putString(_T("busAddress"), busAddr); ++ ++ // sync state - error ++ MojString syncState; ++ syncState.assign(_T("ERROR")); ++ props.putString(_T("syncState"), syncState); ++ ++ // log it ++ MojString json; ++ props.toJson(json); ++ MojLogInfo(IMServiceApp::s_log, _T("removeSyncStateResult: saving syncState to db: %s"), json.data()); ++ ++ MojErr err = m_tempdbClient.put(m_saveSyncStateSlot, props); ++ if (err) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("removeSyncStateResult: dbClient.put() failed: error %d - %s"), err, error.data()); ++ } ++ ++ return MojErrNone; ++} ++ ++/* ++ * Save the new syncState record result ++ * Just log errors. ++ */ ++MojErr IMLoginSyncStateHandler::saveSyncStateResult(MojObject& result, MojErr resultErr) ++{ ++ if (resultErr) { ++ MojString error; ++ MojErrToString(resultErr, error); ++ MojLogError(IMServiceApp::s_log, _T("saveSyncStateResult: DB put failed. error %d - %s"), resultErr, error.data()); ++ } ++ else { ++ IMServiceHandler::logMojObjectJsonString(_T("saveSyncStateResult: syncState successfully saved: %s"), result); ++ } ++ ++ return MojErrNone; ++} ++ +diff -rupN imlibpurpleservice-1.0/src/IMServiceApp.cpp imlibpurpleservice-1.0-new//src/IMServiceApp.cpp +--- imlibpurpleservice-1.0/src/IMServiceApp.cpp 2011-03-25 10:45:40.744552999 -0600 ++++ imlibpurpleservice-1.0-new//src/IMServiceApp.cpp 2011-03-27 09:10:06.164553000 -0600 +@@ -32,6 +32,7 @@ MojLogger IMServiceApp::s_log(_T("org.we + + int main(int argc, char** argv) + { ++purple_debug_set_enabled(TRUE); + IMServiceApp app; + LibpurpleAdapter::init(); + int mainResult = app.main(argc, argv); +diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp +--- imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp 2011-03-25 10:45:40.760552999 -0600 ++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp 2011-03-27 09:10:06.212553000 -0600 +@@ -378,6 +378,9 @@ static char* getMojoFriendlyTemplateID ( + else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){ + return (char*)CAPABILITY_LIVE; + } ++ else if (strcmp(serviceName, SERVICENAME_WLM) == 0){ ++ return (char*)CAPABILITY_WLM; ++ } + else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){ + return (char*)CAPABILITY_MYSPACE; + } +@@ -554,7 +557,7 @@ static char* getPrplProtocolIdFromServic + } + GString* prplProtocolId = g_string_new("prpl-"); + +- if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0)) ++ if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0) || (strcmp(serviceName, SERVICENAME_WLM) == 0)) + { + if (strcmp(serviceName, SERVICENAME_GTALK) == 0) + { +@@ -566,6 +569,11 @@ static char* getPrplProtocolIdFromServic + // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn" + g_string_append(prplProtocolId, "msn"); + } ++ if (strcmp(serviceName, SERVICENAME_WLM) == 0) ++ { ++ // Special case for live (pecan) where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan" ++ g_string_append(prplProtocolId, "msn-pecan"); ++ } + if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) + { + // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" +@@ -647,6 +655,12 @@ static char* getServiceNameFromPrplProto + g_string_free(serviceName, TRUE); + serviceName = g_string_new("live"); + } ++ if (strcmp(serviceName->str, "msn-pecan") == 0) ++ { ++ // Special case for live where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("wlm"); ++ } + if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0) + { + // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" +diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp~ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp~ +--- imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp~ 1969-12-31 18:00:00.000000000 -0600 ++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp~ 2011-03-27 09:10:06.132553000 -0600 +@@ -0,0 +1,2817 @@ ++/* ++ * LibpurpleAdapter.cpp ++ * ++ * Copyright 2010 Palm, Inc. All rights reserved. ++ * ++ * This program is free software and licensed under the terms of the GNU ++ * General Public License Version 2 as published by the Free ++ * Software Foundation; ++ * ++ * 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, ++ * Version 2 along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- ++ * 1301, USA ++ * ++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM ++ * Transport service for use on a mobile device. ++ * ++ * The LibpurpleAdapter is a simple adapter between libpurple.so and the ++ * IMLibpurpleService transport ++ */ ++ ++/* ++ * This file includes code from pidgin (nullclient.c) ++ * ++ * pidgin ++ * ++ * Pidgin is the legal property of its developers, whose names are too numerous ++ * to list here. Please refer to the COPYRIGHT file distributed with this ++ * source distribution. ++ * ++ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA ++ * ++ */ ++ ++#include "purple.h" ++ ++#include <glib.h> ++ ++#include <signal.h> ++#include <string.h> ++#include <unistd.h> ++#include <stdlib.h> ++ ++#include "LibpurpleAdapter.h" ++#include "LibpurpleAdapterPrefs.h" ++#include "PalmImCommon.h" ++#include "luna/MojLunaService.h" ++#include "IMServiceApp.h" ++#include "db/MojDbQuery.h" ++ ++static const guint PURPLE_GLIB_READ_COND = (G_IO_IN | G_IO_HUP | G_IO_ERR); ++static const guint PURPLE_GLIB_WRITE_COND = (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL); ++//static const guint CONNECT_TIMEOUT_SECONDS = 45; Moved to preferences ++//static const guint BUDDY_LIST_REFRESH = 10; Moved to preferences ++ ++static LoginCallbackInterface* s_loginState = NULL; ++static IMServiceCallbackInterface* s_imServiceHandler = NULL; ++static LibpurplePrefsHandler* s_PrefsHandler = NULL; ++ ++/** ++ * List of accounts that are online ++ */ ++static GHashTable* s_onlineAccountData = NULL; ++/** ++ * List of accounts that are in the process of logging in ++ */ ++static GHashTable* s_pendingAccountData = NULL; ++static GHashTable* s_offlineAccountData = NULL; ++static GHashTable* s_accountLoginTimers = NULL; ++static GHashTable* s_connectionTypeData = NULL; ++static GHashTable* s_AccountIdsData = NULL; ++static GHashTable* s_accountBuddyListTimers = NULL; ++ ++/* ++ * list of pending authorization requests ++ */ ++static GHashTable* s_AuthorizeRequests = NULL; ++ ++static bool s_libpurpleInitialized = FALSE; ++static bool s_registeredForAccountSignals = FALSE; ++static bool s_registeredForPresenceUpdateSignals = FALSE; ++/** ++ * Keeps track of the local IP address that we bound to when logging in to individual accounts ++ * key: accountKey, value: IP address ++ */ ++static GHashTable* s_ipAddressesBoundTo = NULL; ++ ++typedef struct _IOClosure ++{ ++ guint result; ++ gpointer data; ++ PurpleInputFunction function; ++} IOClosure; ++ ++// used for processing buddy invite requests ++typedef struct _auth_and_add ++{ ++ PurpleAccountRequestAuthorizationCb auth_cb; ++ PurpleAccountRequestAuthorizationCb deny_cb; ++ void *data; ++ char *remote_user; ++ char *alias; ++ PurpleAccount *account; ++} AuthRequest; ++ ++static void incoming_message_cb(PurpleConversation *conv, const char *who, const char *alias, const char *message, PurpleMessageFlags flags, time_t mtime); ++static void adapterUIInit(void); ++static GHashTable* getClientInfo(void); ++static gboolean adapterInvokeIO(GIOChannel *source, GIOCondition condition, gpointer data); ++static guint adapterIOAdd(gint fd, PurpleInputCondition condition, PurpleInputFunction function, gpointer data); ++ ++//Prompt for authorization when someone adds this account to their buddy list. ++//To authorize them to see this account's presence, call authorize_cb (user_data); otherwise call deny_cb (user_data); ++//Returns: ++// a UI-specific handle, as passed to close_account_request. ++// void(* _PurpleAccountUiOps::close_account_request)(void *ui_handle) ++// Close a pending request for authorization. ++// ui_handle is a handle as returned by request_authorize. ++//Parameters: ++// account The account that was added ++// remote_user The name of the user that added this account. ++// id The optional ID of the local account. Rarely used. ++// alias The optional alias of the remote user. ++// message The optional message sent by the user wanting to add you. ++// on_list Is the remote user already on the buddy list? ++// auth_cb The callback called when the local user accepts ++// deny_cb The callback called when the local user rejects ++// user_data Data to be passed back to the above callbacks ++static void *request_authorize_cb (PurpleAccount *account, ++ const char *remote_user, ++ const char *id, ++ const char *alias, ++ const char *message, ++ gboolean on_list, ++ PurpleAccountRequestAuthorizationCb authorize_cb, ++ PurpleAccountRequestAuthorizationCb deny_cb, ++ void *user_data); ++ ++void request_add_cb (PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message); ++ ++// AccountUIOps: ++// /** A buddy who is already on this account's buddy list added this account ++//- * to their buddy list. ++// */ ++//- void (*notify_added)(PurpleAccount *account, ++//- const char *remote_user, ++//- const char *id, ++//- const char *alias, ++//- const char *message); ++//- ++//- /** This account's status changed. */ ++//- void (*status_changed)(PurpleAccount *account, ++//- PurpleStatus *status); ++//- ++//- /** Someone we don't have on our list added us; prompt to add them. */ ++//- void (*request_add)(PurpleAccount *account, ++//- const char *remote_user, ++//- const char *id, ++//- const char *alias, ++//- const char *message); ++//- ++//- /** Prompt for authorization when someone adds this account to their buddy ++//- * list. To authorize them to see this account's presence, call \a ++//- * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data); ++//- * @return a UI-specific handle, as passed to #close_account_request. ++//- */ ++//- void *(*request_authorize)(PurpleAccount *account, ++//- const char *remote_user, ++//- const char *id, ++//- const char *alias, ++//- const char *message, ++//- gboolean on_list, ++//- PurpleAccountRequestAuthorizationCb authorize_cb, ++//- PurpleAccountRequestAuthorizationCb deny_cb, ++//- void *user_data); ++//- ++//- /** Close a pending request for authorization. \a ui_handle is a handle ++//- * as returned by #request_authorize. ++//- */ ++//- void (*close_account_request)(void *ui_handle); ++static PurpleAccountUiOps adapterAccountUIOps = ++{ ++ NULL, // notify_added ++ NULL, // status_changed ++ request_add_cb, // request_add ++ request_authorize_cb, // request_authorize ++ NULL, // close_account_request, ++ ++ // padding ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++}; ++ ++static PurpleCoreUiOps adapterCoreUIOps = ++{ ++ NULL, NULL, adapterUIInit, NULL, getClientInfo, NULL, NULL, NULL ++}; ++ ++static PurpleEventLoopUiOps adapterEventLoopUIOps = ++{ ++ g_timeout_add, g_source_remove, adapterIOAdd, g_source_remove, NULL, g_timeout_add_seconds, NULL, NULL, NULL ++}; ++ ++static PurpleConversationUiOps adapterConversationUIOps = ++{ ++ NULL, NULL, NULL, NULL, incoming_message_cb, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ++ NULL, NULL ++}; ++ ++// useful for debugging ++static void authRequest_log_func(gpointer key, gpointer value, gpointer ud) ++{ ++ AuthRequest *aa = (AuthRequest *)value; ++ MojLogInfo(IMServiceApp::s_log, _T(" AuthRequest: key = %s, requester = %s"), (char*)key, aa->remote_user); ++} ++static void logAuthRequestTableValues() ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("Authorize Request Table:")); ++ g_hash_table_foreach(s_AuthorizeRequests, authRequest_log_func, NULL); ++} ++ ++ ++void adapterUIInit(void) ++{ ++ purple_conversations_set_ui_ops(&adapterConversationUIOps); ++ purple_accounts_set_ui_ops(&adapterAccountUIOps); ++} ++ ++void destroyNotify(gpointer dataToFree) ++{ ++ g_free(dataToFree); ++} ++ ++gboolean adapterInvokeIO(GIOChannel* ioChannel, GIOCondition ioCondition, gpointer data) ++{ ++ IOClosure* ioClosure = (IOClosure*)data; ++ int purpleCondition = 0; ++ ++ if (PURPLE_GLIB_READ_COND & ioCondition) ++ { ++ purpleCondition = purpleCondition | PURPLE_INPUT_READ; ++ } ++ ++ if (PURPLE_GLIB_WRITE_COND & ioCondition) ++ { ++ purpleCondition = purpleCondition | PURPLE_INPUT_WRITE; ++ } ++ ++ ioClosure->function(ioClosure->data, g_io_channel_unix_get_fd(ioChannel), (PurpleInputCondition)purpleCondition); ++ ++ return TRUE; ++} ++ ++guint adapterIOAdd(gint fd, PurpleInputCondition purpleCondition, PurpleInputFunction inputFunction, gpointer data) ++{ ++ GIOChannel* ioChannel; ++ unsigned int ioCondition = 0; ++ IOClosure* ioClosure = g_new0(IOClosure, 1); ++ ++ ioClosure->data = data; ++ ioClosure->function = inputFunction; ++ ++ if (PURPLE_INPUT_READ & purpleCondition) ++ { ++ ioCondition = ioCondition | PURPLE_GLIB_READ_COND; ++ } ++ ++ if (PURPLE_INPUT_WRITE & purpleCondition) ++ { ++ ioCondition = ioCondition | PURPLE_GLIB_WRITE_COND; ++ } ++ ++ ioChannel = g_io_channel_unix_new(fd); ++ ioClosure->result = g_io_add_watch_full(ioChannel, G_PRIORITY_DEFAULT, (GIOCondition)ioCondition, adapterInvokeIO, ioClosure, ++ destroyNotify); ++ ++ g_io_channel_unref(ioChannel); ++ return ioClosure->result; ++} ++ ++/* ++ * Helper methods ++ */ ++ ++ /* ++ * The messaging service expects the username to be in the username@domain.com format, whereas the AIM prpl uses the username only ++ * Free the returned string when you're done with it ++ */ ++static char* getMojoFriendlyUsername(const char* username, const char* serviceName) ++{ ++ if (!username || !serviceName) ++ { ++ return strdup(""); ++ } ++ GString* mojoFriendlyUsername = g_string_new(username); ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && strchr(username, '@') == NULL) ++ { ++ g_string_append(mojoFriendlyUsername, "@aol.com"); ++ } ++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0) ++ { ++ char* resource = (char*)memchr(username, '/', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0) ++ { ++ if (strstr(username, "/") != NULL) ++ { ++ //If jabber resource is blank remove / ++ char *resource = (char*)memchr(username, '/', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ } ++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ char *resource = (char*)memchr(username, ',', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str); ++ g_string_free(mojoFriendlyUsername, TRUE); ++ return mojoFriendlyUsernameToReturn; ++} ++ ++static char* getMojoFriendlyTemplateID (char* serviceName) ++{ ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0) { ++ return (char*)CAPABILITY_AIM; ++ } ++ else if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0){ ++ return (char*)CAPABILITY_FACEBOOK; ++ } ++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0){ ++ return (char*)CAPABILITY_GTALK; ++ } ++ else if (strcmp(serviceName, SERVICENAME_GADU) == 0){ ++ return (char*)CAPABILITY_GADU; ++ } ++ else if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0){ ++ return (char*)CAPABILITY_GROUPWISE; ++ } ++ else if (strcmp(serviceName, SERVICENAME_ICQ) == 0){ ++ return (char*)CAPABILITY_ICQ; ++ } ++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0){ ++ return (char*)CAPABILITY_JABBER; ++ } ++ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){ ++ return (char*)CAPABILITY_LIVE; ++ } ++ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){ ++ return (char*)CAPABILITY_MYSPACE; ++ } ++ else if (strcmp(serviceName, SERVICENAME_QQ) == 0){ ++ return (char*)CAPABILITY_QQ; ++ } ++ else if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0){ ++ return (char*)CAPABILITY_SAMETIME; ++ } ++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0){ ++ return (char*)CAPABILITY_SIPE; ++ } ++ else if (strcmp(serviceName, SERVICENAME_XFIRE) == 0){ ++ return (char*)CAPABILITY_XFIRE; ++ } ++ else if (strcmp(serviceName, SERVICENAME_YAHOO) == 0){ ++ return (char*)CAPABILITY_YAHOO; ++ } ++ ++ return (char*)""; ++} ++ ++/* ++ * This method handles special cases where the username passed by the mojo side does not satisfy a particular prpl's requirement ++ * (e.g. for logging into AIM, the mojo service uses "palmpre@aol.com", yet the aim prpl expects "palmpre"; same scenario with yahoo) ++ * Free the returned string when you're done with it ++ */ ++static char* getPrplFriendlyUsername(const char* serviceName, const char* username) ++{ ++ if (!username || !serviceName) ++ { ++ return strdup(""); ++ } ++ ++ char* transportFriendlyUsername = strdup(username); ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0) ++ { ++ // Need to strip off @aol.com, but not @aol.com.mx ++ const char* extension = strstr(username, "@aol.com"); ++ if (extension != NULL && strstr(extension, "aol.com.") == NULL) ++ { ++ strtok(transportFriendlyUsername, "@"); ++ } ++ } ++ ++ const char* SIPEServerLogin; ++ const char* JabberResource; ++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName); ++ ++ //Special Case for Office Communicator when DOMAIN\USER is set. Account name is USERNAME,DOMAIN\USER ++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ if (strstr(username, ",") != NULL) ++ { ++ //A "," exists in the sign in name already ++ //transportFriendlyUsername = malloc(strlen(username) + 1); ++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username); ++ return transportFriendlyUsername; ++ } ++ else ++ { ++ SIPEServerLogin = s_PrefsHandler->GetStringPreference("SIPEServerLogin", templateId, username); ++ if (strcmp(SIPEServerLogin, "") != 0) ++ { ++ //transportFriendlyUsername = malloc(strlen(username) + 1); ++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username); ++ ++ //SIPE Account ++ char *SIPEFullLoginName = NULL; ++ SIPEFullLoginName = (char *)calloc(strlen(transportFriendlyUsername) + strlen(SIPEServerLogin) + 2, sizeof(char)); ++ strcat(SIPEFullLoginName, transportFriendlyUsername); ++ strcat(SIPEFullLoginName, ","); ++ strcat(SIPEFullLoginName, SIPEServerLogin); ++ ++ return SIPEFullLoginName; ++ } ++ } ++ } ++ ++ //Special Case for jabber when resource is set. Account name is USERNAME/RESOURCE ++ if (strcmp(serviceName, SERVICENAME_JABBER) == 0) ++ { ++ if (strstr(username, "/") != NULL) ++ { ++ //A "/" exists in the sign in name already ++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username); ++ ++ return transportFriendlyUsername; ++ } ++ else ++ { ++ JabberResource = s_PrefsHandler->GetStringPreference("JabberResource", templateId, username); ++ ++ if (strcmp(JabberResource, "") != 0) ++ { ++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username); ++ ++ //Jabber Account ++ char *JabberFullLoginName = NULL; ++ JabberFullLoginName = (char *)calloc(strlen(transportFriendlyUsername) + strlen(JabberResource) + 2, sizeof(char)); ++ strcat(JabberFullLoginName, transportFriendlyUsername); ++ strcat(JabberFullLoginName, "/"); ++ strcat(JabberFullLoginName, JabberResource); ++ ++ return JabberFullLoginName; ++ } ++ } ++ } ++ ++ MojLogInfo(IMServiceApp::s_log, _T("getPrplFriendlyUsername: username: %s, transportFriendlyUsername: %s"), username, transportFriendlyUsername); ++ return transportFriendlyUsername; ++} ++ ++static char* stripResourceFromGtalkUsername(const char* username, const char* serviceName) ++{ ++ if (!username) ++ { ++ return strdup(""); ++ } ++ if ((strcmp(serviceName, SERVICENAME_GTALK) != 0) && (strcmp(serviceName, SERVICENAME_JABBER) != 0)) ++ { ++ return strdup(username); ++ } ++ ++ GString* mojoFriendlyUsername = g_string_new(username); ++ char* resource = (char*)memchr(username, '/', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str); ++ g_string_free(mojoFriendlyUsername, TRUE); ++ return mojoFriendlyUsernameToReturn; ++} ++ ++static const char* getMojoFriendlyErrorCode(PurpleConnectionError type) ++{ ++ const char* mojoFriendlyErrorCode; ++ if (type == PURPLE_CONNECTION_ERROR_INVALID_USERNAME) ++ { ++ mojoFriendlyErrorCode = ERROR_BAD_USERNAME; ++ } ++ else if (type == PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED) ++ { ++ mojoFriendlyErrorCode = ERROR_AUTHENTICATION_FAILED; ++ } ++ else if (type == PURPLE_CONNECTION_ERROR_NETWORK_ERROR) ++ { ++ mojoFriendlyErrorCode = ERROR_NETWORK_ERROR; ++ } ++ else if (type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) ++ { ++ mojoFriendlyErrorCode = ERROR_NAME_IN_USE; ++ } ++ else ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("PurpleConnectionError was %i"), type); ++ mojoFriendlyErrorCode = ERROR_GENERIC_ERROR; ++ } ++ return mojoFriendlyErrorCode; ++} ++ ++/* ++ * Given mojo-friendly serviceName, it will return prpl-specific protocol_id (e.g. given "type_aim", it will return "prpl-aim") ++ * Free the returned string when you're done with it ++ */ ++static char* getPrplProtocolIdFromServiceName(const char* serviceName) ++{ ++ if (!serviceName || serviceName[0] == 0) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getPrplProtocolIdFromServiceName called with empty serviceName")); ++ return strdup(""); ++ } ++ GString* prplProtocolId = g_string_new("prpl-"); ++ ++ if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0)) ++ { ++ if (strcmp(serviceName, SERVICENAME_GTALK) == 0) ++ { ++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber" ++ g_string_append(prplProtocolId, "jabber"); ++ } ++ if (strcmp(serviceName, SERVICENAME_LIVE) == 0) ++ { ++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn" ++ g_string_append(prplProtocolId, "msn"); ++ } ++ if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) ++ { ++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" ++ g_string_append(prplProtocolId, "bigbrownchunx-facebookim"); ++ } ++ if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) ++ { ++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile" ++ g_string_append(prplProtocolId, "meanwhile"); ++ } ++ if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) ++ { ++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell" ++ g_string_append(prplProtocolId, "novell"); ++ } ++ if (strcmp(serviceName, SERVICENAME_GADU) == 0) ++ { ++ // Special case for gadu gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg" ++ g_string_append(prplProtocolId, "gg"); ++ } ++ } ++ else ++ { ++ const char* stringChopper = serviceName; ++ stringChopper += strlen("type_"); ++ g_string_append(prplProtocolId, stringChopper); ++ } ++ char* prplProtocolIdToReturn = strdup(prplProtocolId->str); ++ g_string_free(prplProtocolId, TRUE); ++ return prplProtocolIdToReturn; ++} ++ ++/* ++ * Given the prpl-specific protocol_id, it will return mojo-friendly serviceName (e.g. given "prpl-aim", it will return "type_aim") ++ * Free the returned string when you're done with it ++ */ ++static char* getServiceNameFromPrplProtocolId(PurpleAccount *account) ++{ ++ char *prplProtocolId = account->protocol_id; ++ if (!prplProtocolId) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId")); ++ return strdup("type_default"); ++ } ++ char* stringChopper = prplProtocolId; ++ stringChopper += strlen("prpl-"); ++ GString* serviceName = g_string_new(stringChopper); ++ ++ if (strcmp(serviceName->str, "jabber") == 0) ++ { ++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber" ++ g_string_free(serviceName, TRUE); ++ ++ const char *Alias = purple_account_get_alias (account); ++ ++ if (Alias != NULL) ++ { ++ //Check account alias. gtalk for Gtalk, jabber for Jabber ++ if (strcmp(Alias, "gtalk") == 0) ++ { ++ // Special case for gtalk where the java serviceName is "gmail" and the prpl protocol_id is "prpl-jabber" ++ serviceName = g_string_new("gtalk"); ++ } ++ else ++ { ++ // Special case for jabber where the java serviceName is "jabber" and the prpl protocol_id is "prpl-jabber" ++ serviceName = g_string_new("jabber"); ++ } ++ } ++ else ++ { ++ //Account alias is blank for some reason. Guess gtalk ++ serviceName = g_string_new("gtalk"); ++ } ++ } ++ if (strcmp(serviceName->str, "msn") == 0) ++ { ++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("live"); ++ } ++ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0) ++ { ++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("facebook"); ++ } ++ if (strcmp(serviceName->str, "meanwhile") == 0) ++ { ++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("sametime"); ++ } ++ if (strcmp(serviceName->str, "novell") == 0) ++ { ++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("groupwise"); ++ } ++ if (strcmp(serviceName->str, "gg") == 0) ++ { ++ // Special case for gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg" ++ g_string_free(serviceName, TRUE); ++ serviceName = g_string_new("gadu"); ++ } ++ char* serviceNameToReturn = NULL; ++ // asprintf allocates appropriate-sized buffer ++ asprintf(&serviceNameToReturn, "type_%s", serviceName->str); ++ g_string_free(serviceName, TRUE); ++ return serviceNameToReturn; ++} ++ ++static char* getAccountKey(const char* username, const char* serviceName) ++{ ++ if (!username || !serviceName) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getAccountKey called with username=\"%s\" and serviceName=\"%s\""), username, serviceName); ++ return strdup(""); ++ } ++ char *accountKey = NULL; ++ // asprintf allocates appropriate-sized buffer ++ asprintf(&accountKey, "%s_%s", username, serviceName); ++ return accountKey; ++} ++ ++static char* getAuthRequestKey(const char* username, const char* serviceName, const char* remoteUsername) ++{ ++ if (!username || !serviceName || !remoteUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getAuthRequestKey - empty input parameter. username: %s, serviceName: %s, remoteUsername: %s"), username, serviceName, remoteUsername); ++ return strdup(""); ++ } ++ char *authRequestKey = NULL; ++ // asprintf allocates appropriate-sized buffer ++ asprintf(&authRequestKey, "%s_%s_%s", username, serviceName, remoteUsername); ++ MojLogInfo(IMServiceApp::s_log, _T("getAuthRequestKey - username: %s, serviceName: %s, remoteUser: %s key: %s"), username, serviceName, remoteUsername, authRequestKey); ++ return authRequestKey; ++} ++ ++static char* getAccountKeyFromPurpleAccount(PurpleAccount* account) ++{ ++ if (!account) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getAccountKeyFromPurpleAccount called with empty account")); ++ return strdup(""); ++ } ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ char* accountKey = getAccountKey(username, serviceName); ++ ++ free(serviceName); ++ free(username); ++ ++ return accountKey; ++} ++ ++/** ++ * Returns a GString containing the special stanza to enable server-side presence update queue ++ * Clean up after yourself using g_string_free when you're done with the return value ++ */ ++static GString* getEnableQueueStanza(PurpleAccount* account) ++{ ++ GString* stanza = NULL; ++ if (account != NULL) ++ { ++ if (strcmp(account->protocol_id, "prpl-jabber") == 0) ++ { ++ stanza = g_string_new(""); ++ PurpleConnection* pc = purple_account_get_connection(account); ++ if (pc == NULL) ++ { ++ return NULL; ++ } ++ const char* displayName = purple_connection_get_display_name(pc); ++ if (displayName == NULL) ++ { ++ return NULL; ++ } ++ g_string_append(stanza, "<iq from='"); ++ g_string_append(stanza, displayName); ++ g_string_append(stanza, "' type='set'><query xmlns='google:queue'><enable/></query></iq>"); ++ } ++ else if (strcmp(account->protocol_id, "prpl-aim") == 0) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("getEnableQueueStanza for AIM")); ++ stanza = g_string_new("true"); ++ } ++ } ++ return stanza; ++} ++ ++/** ++ * Returns a GString containing the special stanza to disable and flush the server-side presence update queue ++ * Clean up after yourself using g_string_free when you're done with the return value ++ */ ++static GString* getDisableQueueStanza(PurpleAccount* account) ++{ ++ GString* stanza = NULL; ++ if (account != NULL) ++ { ++ if (strcmp(account->protocol_id, "prpl-jabber") == 0) ++ { ++ stanza = g_string_new(""); ++ PurpleConnection* pc = purple_account_get_connection(account); ++ if (pc == NULL) ++ { ++ return NULL; ++ } ++ const char* displayName = purple_connection_get_display_name(pc); ++ if (displayName == NULL) ++ { ++ return NULL; ++ } ++ g_string_append(stanza, "<iq from='"); ++ g_string_append(stanza, displayName); ++ g_string_append(stanza, "' type='set'><query xmlns='google:queue'><disable/><flush/></query></iq>"); ++ } ++ else if (strcmp(account->protocol_id, "prpl-aim") == 0) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("getDisableQueueStanza for AIM")); ++ stanza = g_string_new("false"); ++ } ++ } ++ return stanza; ++} ++ ++static void enableServerQueueForAccount(PurpleAccount* account) ++{ ++ if (!account) ++ { ++ MojLogError(IMServiceApp::s_log, _T("enableServerQueueForAccount called with empty account")); ++ return; ++ } ++ ++ PurplePluginProtocolInfo* prpl_info = NULL; ++ PurpleConnection* gc = purple_account_get_connection(account); ++ PurplePlugin* prpl = NULL; ++ ++ if (gc != NULL) ++ { ++ prpl = purple_connection_get_prpl(gc); ++ } ++ ++ if (prpl != NULL) ++ { ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ } ++ ++ if (prpl_info && prpl_info->send_raw) ++ { ++ GString* enableQueueStanza = getEnableQueueStanza(account); ++ if (enableQueueStanza != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("Enabling server queue for %s"), account->protocol_id); ++ prpl_info->send_raw(gc, enableQueueStanza->str, enableQueueStanza->len); ++ g_string_free(enableQueueStanza, TRUE); ++ } ++ } ++} ++ ++static void disableServerQueueForAccount(PurpleAccount* account) ++{ ++ if (!account) ++ { ++ MojLogError(IMServiceApp::s_log, _T("disableServerQueueForAccount called with empty account")); ++ return; ++ } ++ PurplePluginProtocolInfo* prpl_info = NULL; ++ PurpleConnection* gc = purple_account_get_connection(account); ++ PurplePlugin* prpl = NULL; ++ ++ if (gc != NULL) ++ { ++ prpl = purple_connection_get_prpl(gc); ++ } ++ ++ if (prpl != NULL) ++ { ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ } ++ ++ if (prpl_info && prpl_info->send_raw) ++ { ++ GString* disableQueueStanza = getDisableQueueStanza(account); ++ if (disableQueueStanza != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("Disabling server queue")); ++ prpl_info->send_raw(gc, disableQueueStanza->str, disableQueueStanza->len); ++ g_string_free(disableQueueStanza, TRUE); ++ } ++ } ++} ++ ++/** ++ * Asking the gtalk server to enable/disable queueing of presence updates ++ * This is called when the screen is turned off (enable:true) or turned on (enable:false) ++ */ ++bool LibpurpleAdapter::queuePresenceUpdates(bool enable) ++{ ++ PurpleAccount* account; ++ GList* onlineAccountKeys = NULL; ++ GList* iterator = NULL; ++ char* accountKey = NULL; ++ ++ onlineAccountKeys = g_hash_table_get_keys(s_onlineAccountData); ++ for (iterator = onlineAccountKeys; iterator != NULL; iterator = g_list_next(iterator)) ++ { ++ accountKey = (char*)iterator->data; ++ account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (account) ++ { ++ /* ++ * enabling/disabling server queue is supported by gtalk (jabber) or aim ++ */ ++ if ((strcmp(account->protocol_id, "prpl-jabber") == 0) || ++ (strcmp(account->protocol_id, "prpl-aim") == 0)) ++ { ++ if (enable) ++ { ++ enableServerQueueForAccount(account); ++ } ++ else ++ { ++ disableServerQueueForAccount(account); ++ } ++ } ++ } ++ } ++ return TRUE; ++} ++ ++ ++static int getPalmAvailabilityFromPurpleAvailability(int prplAvailability) ++{ ++ switch (prplAvailability) ++ { ++ case PURPLE_STATUS_UNSET: ++ return PalmAvailability::NO_PRESENCE; ++ case PURPLE_STATUS_OFFLINE: ++ return PalmAvailability::OFFLINE; ++ case PURPLE_STATUS_AVAILABLE: ++ return PalmAvailability::ONLINE; ++ case PURPLE_STATUS_UNAVAILABLE: ++ return PalmAvailability::IDLE; ++ case PURPLE_STATUS_INVISIBLE: ++ return PalmAvailability::INVISIBLE; ++ case PURPLE_STATUS_AWAY: ++ return PalmAvailability::IDLE; ++ case PURPLE_STATUS_EXTENDED_AWAY: ++ return PalmAvailability::IDLE; ++ case PURPLE_STATUS_MOBILE: ++ return PalmAvailability::MOBILE; ++ case PURPLE_STATUS_TUNE: ++ return PalmAvailability::ONLINE; ++ default: ++ return PalmAvailability::OFFLINE; ++ } ++} ++ ++ ++static PurpleStatusPrimitive getPurpleAvailabilityFromPalmAvailability(int palmAvailability) ++{ ++ switch (palmAvailability) ++ { ++ case PalmAvailability::ONLINE: ++ return PURPLE_STATUS_AVAILABLE; ++ case PalmAvailability::MOBILE: ++ return PURPLE_STATUS_MOBILE; ++ case PalmAvailability::IDLE: ++ return PURPLE_STATUS_AWAY; ++ case PalmAvailability::INVISIBLE: ++ return PURPLE_STATUS_INVISIBLE; ++ case PalmAvailability::OFFLINE: ++ return PURPLE_STATUS_OFFLINE; ++ default: ++ return PURPLE_STATUS_OFFLINE; ++ } ++} ++ ++/* ++ * End of helper methods ++ */ ++ ++/* ++ * Callbacks ++ */ ++ ++static void buddy_signed_on_off_cb(PurpleBuddy* buddy, gpointer data) ++{ ++ LSError lserror; ++ LSErrorInit(&lserror); ++ ++ PurpleAccount* account = purple_buddy_get_account(buddy); ++ char* accountKey = getAccountKeyFromPurpleAccount(account); ++ const char* accountId = (const char*)g_hash_table_lookup(s_AccountIdsData, accountKey); ++ if (NULL == accountId) { ++ MojLogError(IMServiceApp::s_log, _T("buddy_signed_on_off_cb: accountId not found in table. accountKey: %s"), accountKey); ++ free(accountKey); ++ return; ++ } ++ ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ PurpleStatus* activeStatus = purple_presence_get_active_status(purple_buddy_get_presence(buddy)); ++ /* ++ * Getting the new availability ++ */ ++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(activeStatus)); ++ int newAvailabilityValue = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive); ++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddy); ++ const char* customMessage = ""; ++ char* buddyAvatarLocation = NULL; ++ ++ char* UserName = getMojoFriendlyUsername(account->username, serviceName); ++ char* templateId = getMojoFriendlyTemplateID(serviceName); ++ ++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, UserName) == true) ++ { ++ if (icon != NULL) ++ { ++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon); ++ } ++ } ++ else ++ { ++ buddyAvatarLocation = (char*)""; ++ } ++ ++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true) ++ { ++ if (buddy->server_alias == NULL) ++ { ++ buddy->server_alias = (char*)""; ++ } ++ } ++ else ++ { ++ if (buddy->alias == NULL) ++ { ++ buddy->alias = (char*)""; ++ } ++ } ++ ++ customMessage = purple_status_get_attr_string(activeStatus, "message"); ++ if (customMessage == NULL) ++ { ++ customMessage = ""; ++ } ++ ++ PurpleGroup* group = purple_buddy_get_group(buddy); ++ const char* groupName = purple_group_get_name(group); ++ if (groupName == NULL) ++ { ++ groupName = ""; ++ } ++ ++ //Special for Office Communicator. (Remove 'sip:' prefix) ++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ if (strstr(buddy->name, "sip:") != NULL) ++ { ++ GString *SIPBuddyAlias = g_string_new(buddy->name); ++ g_string_erase(SIPBuddyAlias, 0, 4); ++ buddy->name = SIPBuddyAlias->str; ++ } ++ } ++ ++ // call into the imlibpurpletransport ++ // buddy->name is stored in the imbuddyStatus DB kind in the libpurple format - ie. for AIM without the "@aol.com" so that is how we need to search for it ++ s_imServiceHandler->updateBuddyStatus(accountId, serviceName, buddy->name, newAvailabilityValue,customMessage, groupName, buddyAvatarLocation); ++ ++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true) ++ { ++ g_message( ++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'", ++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->server_alias, groupName); ++ } ++ else ++ { ++ g_message( ++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'", ++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->alias, groupName); ++ } ++ ++ free(serviceName); ++ free(accountKey); ++ ++ /* if (buddyAvatarLocation) ++ { ++ g_free(buddyAvatarLocation); ++ } */ ++} ++ ++static void buddy_status_changed_cb(PurpleBuddy* buddy, PurpleStatus* old_status, PurpleStatus* new_status, ++ gpointer unused) ++{ ++ /* ++ * Getting the new availability ++ */ ++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(new_status)); ++ int newAvailabilityValue = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive); ++ ++ /* ++ * Getting the new custom message ++ */ ++ const char* customMessage = purple_status_get_attr_string(new_status, "message"); ++ if (customMessage == NULL) ++ { ++ customMessage = ""; ++ } ++ ++ LSError lserror; ++ LSErrorInit(&lserror); ++ ++ PurpleAccount* account = purple_buddy_get_account(buddy); ++ char* accountKey = getAccountKeyFromPurpleAccount(account); ++ const char* accountId = (const char*)g_hash_table_lookup(s_AccountIdsData, accountKey); ++ if (NULL == accountId) { ++ MojLogError(IMServiceApp::s_log, _T("buddy_status_changed_cb: accountId not found in table. accountKey: %s"), accountKey); ++ free(accountKey); ++ return; ++ } ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ ++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddy); ++ char* buddyAvatarLocation = NULL; ++ ++ char* UserName = getMojoFriendlyUsername(account->username, serviceName); ++ char* templateId = getMojoFriendlyTemplateID(serviceName); ++ ++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, UserName) == true) ++ { ++ if (icon != NULL) ++ { ++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon); ++ } ++ } ++ else ++ { ++ buddyAvatarLocation = (char*)""; ++ } ++ ++ PurpleGroup* group = purple_buddy_get_group(buddy); ++ const char* groupName = purple_group_get_name(group); ++ if (groupName == NULL) ++ { ++ groupName = ""; ++ } ++ ++ //Special for Office Communicator. (Remove 'sip:' prefix) ++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ if (strstr(buddy->name, "sip:") != NULL) ++ { ++ GString *SIPBuddyAlias = g_string_new(buddy->name); ++ g_string_erase(SIPBuddyAlias, 0, 4); ++ buddy->name = SIPBuddyAlias->str; ++ } ++ } ++ ++ // call into the imlibpurpletransport ++ // buddy->name is stored in the imbuddyStatus DB kind in the libpurple format - ie. for AIM without the "@aol.com" so that is how we need to search for it ++ s_imServiceHandler->updateBuddyStatus(accountId, serviceName, buddy->name, newAvailabilityValue, customMessage, groupName, buddyAvatarLocation); ++ ++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true) ++ { ++ g_message( ++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'", ++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->server_alias, groupName); ++ } ++ else ++ { ++ g_message( ++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'", ++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->alias, groupName); ++ } ++ ++ if (serviceName) ++ { ++ free(serviceName); ++ } ++ free(accountKey); ++ ++ /* if (buddyAvatarLocation) ++ { ++ g_free(buddyAvatarLocation); ++ } */ ++} ++ ++static void buddy_avatar_changed_cb(PurpleBuddy* buddy) ++{ ++ PurpleStatus* activeStatus = purple_presence_get_active_status(purple_buddy_get_presence(buddy)); ++ MojLogInfo(IMServiceApp::s_log, _T("buddy avatar changed for %s"), buddy->name); ++ buddy_status_changed_cb(buddy, activeStatus, activeStatus, NULL); ++} ++ ++/* ++ * Called after we remove a buddy from out list ++ */ ++static void buddy_removed_cb(PurpleBuddy* buddy) ++{ ++ // nothing to do... ++ MojLogInfo(IMServiceApp::s_log, _T("buddy removed %s"), buddy->name); ++} ++ ++/* ++ * Called both after we add a buddy to our list and when we accept a remote users' invitation to add us to their list ++ * buddy is the new buddy ++ */ ++static void buddy_added_cb(PurpleBuddy* buddy) ++{ ++ // nothing to do... ++ MojLogInfo(IMServiceApp::s_log, _T("buddy added %s"), buddy->name); ++} ++ ++// testing. Never gets called... ++//static void sent_message_cb (PurpleAccount *account, const char *receiver, const char *message) ++//{ ++// MojLogInfo(IMServiceApp::s_log, _T("im message sent: %s, receiver %s"), message, receiver); ++//} ++ ++bool LibpurpleAdapter::getFullBuddyList(const char* serviceName, const char* username) ++{ ++ MojLogInfo(IMServiceApp::s_log, "%s called.", __FUNCTION__); ++ ++ if (!serviceName || !username) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getBuddyList: Invalid parameter. Please double check the passed parameters.")); ++ return FALSE; ++ } ++ ++ if (s_loginState == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getBuddyList: s_loginState still null.")); ++ return FALSE; ++ } ++ ++ MojLogInfo(IMServiceApp::s_log, _T("getFullBuddyList: Parameters: serviceName %s, username %s"), serviceName, username); ++ ++ /* ++ * Send over the full buddy list if the account is already logged in ++ */ ++ bool success; ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (account == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList: ERROR: No account for %s on %s"), username, serviceName); ++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! accountKey: %s"), accountKey); ++ success = FALSE; ++ } ++ else ++ { ++ success = TRUE; ++ GSList* buddyList = purple_find_buddies(account, NULL); ++ MojObject buddyListObj; ++ if (!buddyList) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList: WARNING: the buddy list was NULL, returning empty buddy list. accountKey %s"), accountKey); ++ s_loginState->buddyListResult(serviceName, username, buddyListObj, true); ++ } ++ ++ GSList* buddyIterator = NULL; ++ PurpleBuddy* buddyToBeAdded = NULL; ++ PurpleGroup* group = NULL; ++ char* buddyAvatarLocation = NULL; ++ ++ //printf("\n\n\n\n\n\n ---------------- BUDDY LIST SIZE: %d----------------- \n\n\n\n\n\n", g_slist_length(buddyList)); ++ for (buddyIterator = buddyList; buddyIterator != NULL; buddyIterator = buddyIterator->next) ++ { ++ MojObject buddyObj; ++ buddyObj.clear(MojObject::TypeArray); ++ buddyToBeAdded = (PurpleBuddy*)buddyIterator->data; ++ ++ buddyObj.putString("username", buddyToBeAdded->name); ++ buddyObj.putString("serviceName", serviceName); ++ ++ group = purple_buddy_get_group(buddyToBeAdded); ++ const char* groupName = purple_group_get_name(group); ++ buddyObj.putString("group", groupName); ++ ++ /* ++ * Getting the availability ++ */ ++ PurpleStatus* status = purple_presence_get_active_status(purple_buddy_get_presence(buddyToBeAdded)); ++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(status)); ++ int availability = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive); ++ buddyObj.putInt("availability", availability); ++ ++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName); ++ ++ //Special for Office Communicator. (Remove 'sip:' prefix) ++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ if (strstr(buddyToBeAdded->name, "sip:") != NULL) ++ { ++ GString *SIPBuddyAlias = g_string_new(buddyToBeAdded->name); ++ g_string_erase(SIPBuddyAlias, 0, 4); ++ buddyObj.putString("displayName", SIPBuddyAlias->str); ++ } ++ else ++ { ++ buddyObj.putString("displayName", buddyToBeAdded->name); ++ } ++ } ++ else ++ { ++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, username) == true) ++ { ++ if (buddyToBeAdded->server_alias != NULL) ++ { ++ buddyObj.putString("displayName", buddyToBeAdded->server_alias); ++ } ++ } ++ else ++ { ++ if (buddyToBeAdded->alias != NULL) ++ { ++ buddyObj.putString("displayName", buddyToBeAdded->alias); ++ } ++ } ++ } ++ ++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddyToBeAdded); ++ ++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, username) == true) ++ { ++ if (icon != NULL) ++ { ++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon); ++ buddyObj.putString("avatar", buddyAvatarLocation); ++ } ++ else ++ { ++ buddyObj.putString("avatar", ""); ++ } ++ } ++ else ++ { ++ buddyObj.putString("avatar", ""); ++ } ++ ++ const char* customMessage = purple_status_get_attr_string(status, "message"); ++ if (customMessage != NULL) ++ { ++ buddyObj.putString("status", customMessage); ++ } ++ ++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, username) == true) ++ { ++ g_message("%s says: %s's presence: availability: '%d', custom message: '%s', avatar location: '%s', display name: '%s', group name:'%s'", ++ __FUNCTION__, buddyToBeAdded->name, availability, customMessage, buddyAvatarLocation, buddyToBeAdded->server_alias, groupName); ++ } ++ else ++ { ++ g_message("%s says: %s's presence: availability: '%d', custom message: '%s', avatar location: '%s', display name: '%s', group name:'%s'", ++ __FUNCTION__, buddyToBeAdded->name, availability, customMessage, buddyAvatarLocation, buddyToBeAdded->alias, groupName); ++ } ++ ++ if (buddyAvatarLocation) ++ { ++ g_free(buddyAvatarLocation); ++ buddyAvatarLocation = NULL; ++ } ++ ++ buddyListObj.push(buddyObj); ++ } ++ ++ s_loginState->buddyListResult(serviceName, username, buddyListObj, true); ++ ++ //TODO free the buddyList object??? ++ } ++ ++ if (accountKey) ++ { ++ free(accountKey); ++ } ++ return success; ++} ++ ++typedef struct _LoginDetails ++{ ++ PurpleAccount* account; ++ char* accountKey; ++ char* serviceName; ++ char* username; ++ gpointer loginState; ++} LoginDetails; ++ ++gboolean BuddyListRefreshCallback(gpointer data) ++{ ++ LoginDetails *logindetails = (LoginDetails*)data; ++ ++ void* blist_handle = purple_blist_get_handle(); ++ static int handle; ++ gpointer loginState = logindetails->loginState; ++ ++ char* accountKey = logindetails->accountKey; ++ PurpleAccount* loggedInAccount = (PurpleAccount*)logindetails->account; ++ char* serviceName = logindetails->serviceName; ++ char* username = logindetails->username; ++ ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ /* ++ * If the account is not pending anymore (which means login either already failed or succeeded) ++ * then we shouldn't have gotten to this point since we should have cancelled the timer ++ */ ++ MojLogError(IMServiceApp::s_log, ++ _T("WARNING: we shouldn't have gotten to BuddyListRefreshCallback since the account is not logged in. accountKey %s"), accountKey); ++ free(accountKey); ++ return TRUE; ++ } ++ ++ /* ++ * Remove the timer ++ */ ++ guint BuddyListtimerHandle = (guint)g_hash_table_lookup(s_accountBuddyListTimers, accountKey); ++ purple_timeout_remove(BuddyListtimerHandle); ++ g_hash_table_remove(s_accountBuddyListTimers, accountKey); ++ ++ // Don't free accountKey because s_onlineAccountData has a reference to it. ++ MojLogInfo(IMServiceApp::s_log, _T("account_logged_in_cb: inserting account into onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_insert(s_onlineAccountData, accountKey, loggedInAccount); ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Account connected...")); ++ ++ //reply with login success ++ if (loginState) ++ { ++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_SUCCESS, false, ERROR_NO_ERROR, false); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_logged_in_cb called with loginState=NULL")); ++ } ++ ++ if (s_registeredForPresenceUpdateSignals == FALSE) ++ { ++ purple_signal_connect(blist_handle, "buddy-status-changed", &handle, PURPLE_CALLBACK(buddy_status_changed_cb), ++ loginState); ++ purple_signal_connect(blist_handle, "buddy-signed-on", &handle, PURPLE_CALLBACK(buddy_signed_on_off_cb), ++ GINT_TO_POINTER(TRUE)); ++ purple_signal_connect(blist_handle, "buddy-signed-off", &handle, PURPLE_CALLBACK(buddy_signed_on_off_cb), ++ GINT_TO_POINTER(FALSE)); ++ purple_signal_connect(blist_handle, "buddy-icon-changed", &handle, PURPLE_CALLBACK(buddy_avatar_changed_cb), ++ GINT_TO_POINTER(FALSE)); ++ purple_signal_connect(blist_handle, "buddy-removed", &handle, PURPLE_CALLBACK(buddy_removed_cb), ++ GINT_TO_POINTER(FALSE)); ++ purple_signal_connect(blist_handle, "buddy-added", &handle, PURPLE_CALLBACK(buddy_added_cb), ++ GINT_TO_POINTER(FALSE)); ++ ++ // testing. Doesn't work: error - "Signal data for sent-im-msg not found". Need to figure out the right handle ++// purple_signal_connect(purple_connections_get_handle(), "sent-im-msg", &handle, PURPLE_CALLBACK(sent_message_cb), ++// GINT_TO_POINTER(FALSE)); ++ s_registeredForPresenceUpdateSignals = TRUE; ++ } ++ ++ //Refresh the full buddy list ++ MojLogError(IMServiceApp::s_log, _T("BuddyListRefreshCallback accountKey: %s"), accountKey); ++ ++ return TRUE; ++} ++ ++static void account_logged_in_cb(PurpleConnection* gc, gpointer loginState) ++{ ++ PurpleAccount* loggedInAccount = purple_connection_get_account(gc); ++ g_return_if_fail(loggedInAccount != NULL); ++ ++ char* serviceName = getServiceNameFromPrplProtocolId(loggedInAccount); ++ char* username = getMojoFriendlyUsername(loggedInAccount->username, serviceName); ++ char* accountKey = getAccountKey(username, serviceName); ++ ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ // we were online. why are we getting notified that we're connected again? we were never disconnected. ++ // mark the account online just to be sure? ++ MojLogError(IMServiceApp::s_log, _T("account_logged_in_cb: account already online. why are we getting notified? accountKey %s"), accountKey); ++ free(serviceName); ++ free(username); ++ free(accountKey); ++ return; ++ } ++ ++ /* ++ * cancel the connect timeout for this account ++ */ ++ guint timerHandle = (guint)g_hash_table_lookup(s_accountLoginTimers, accountKey); ++ purple_timeout_remove(timerHandle); ++ g_hash_table_remove(s_accountLoginTimers, accountKey); ++ ++ //Create timer to refresh the buddy list after login ++ LoginDetails* logindetails = g_new0(LoginDetails, 1); ++ ++ logindetails->accountKey = accountKey; ++ logindetails->serviceName = serviceName; ++ logindetails->username = username; ++ logindetails->loginState = loginState; ++ logindetails->account = loggedInAccount; ++ ++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName); ++ char* BuddyListTimeOut = s_PrefsHandler->GetStringPreference("BuddyListTimeOut", templateId, username); ++ static const guint BUDDY_LIST_REFRESH = (guint)atoi(BuddyListTimeOut); ++ guint BuddyListtimerHandle = purple_timeout_add_seconds(BUDDY_LIST_REFRESH, BuddyListRefreshCallback, logindetails); ++ g_hash_table_insert(s_accountBuddyListTimers, accountKey, (gpointer)BuddyListtimerHandle); ++ ++ MojLogError(IMServiceApp::s_log, _T("Login successful. Waiting %s seconds to ensure successful buddy list retrieval."),BuddyListTimeOut); ++} ++ ++static void account_signed_off_cb(PurpleConnection* gc, gpointer loginState) ++{ ++ PurpleAccount* account = purple_connection_get_account(gc); ++ g_return_if_fail(account != NULL); ++ ++ char* accountKey = getAccountKeyFromPurpleAccount(account); ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("account_signed_off_cb: removing account from onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_remove(s_onlineAccountData, accountKey); ++ } ++ else if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ } ++ else ++ { ++ // Already signed off this account (or never signed in) so just return ++ free(accountKey); ++ return; ++ } ++ ++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey); ++ //g_hash_table_remove(connectionTypeData, accountKey); ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Account disconnected...")); ++ ++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL) ++ { ++ /* ++ * Keep the PurpleAccount struct to reuse in future logins ++ */ ++ g_hash_table_insert(s_offlineAccountData, accountKey, account); ++ } ++ ++ // reply with signed off ++ if (loginState) ++ { ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* myMojoFriendlyUsername = getMojoFriendlyUsername(account->username, serviceName); ++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, myMojoFriendlyUsername, LoginCallbackInterface::LOGIN_SIGNED_OFF, false, ERROR_NO_ERROR, true); ++ free(serviceName); ++ free(myMojoFriendlyUsername); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_logged_in_cb called with loginState=NULL")); ++ } ++ ++ free(accountKey); ++} ++ ++/* ++ * This callback is called if a) the login attempt failed, or b) login was successful but the session was closed ++ * (e.g. connection problems, etc). ++ */ ++static void account_login_failed_cb(PurpleConnection* gc, PurpleConnectionError type, const gchar* description, ++ gpointer loginState) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("account_login_failed is called with description %s"), description); ++ ++ PurpleAccount* account = purple_connection_get_account(gc); ++ g_return_if_fail(account != NULL); ++ ++ gboolean loggedOut = FALSE; ++ bool noRetry = true; ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ char* accountKey = getAccountKey(username, serviceName); ++ ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ /* ++ * We were online on this account and are now disconnected because either a) the data connection is dropped, ++ * b) the server is down, or c) the user has logged in from a different location and forced this session to ++ * get closed. ++ */ ++ // Special handling for broken network connection errors (due to bad coverage or flight mode) ++ if (type == 0)//PURPLE_CONNECTION_ERROR_NETWORK_ERROR) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("We had a network error. Reason: %s, prpl error code: %i"), description, type); ++ noRetry = false; ++ } ++ else ++ { ++ loggedOut = TRUE; ++ MojLogInfo(IMServiceApp::s_log, _T("We were logged out. Reason: %s, prpl error code: %i"), description, type); ++ } ++ } ++ else ++ { ++ /* ++ * cancel the connect timeout for this account ++ */ ++ guint timerHandle = (guint)g_hash_table_lookup(s_accountLoginTimers, accountKey); ++ purple_timeout_remove(timerHandle); ++ g_hash_table_remove(s_accountLoginTimers, accountKey); ++ ++ //Cancel the buddy list timer ++ guint BuddyListtimerHandle = (guint)g_hash_table_lookup(s_accountBuddyListTimers, accountKey); ++ purple_timeout_remove(BuddyListtimerHandle); ++ g_hash_table_remove(s_accountBuddyListTimers, accountKey); ++ ++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) == NULL) ++ { ++ /* ++ * This account was in neither of the account data lists (online or pending). We must have logged it out ++ * and not cared about letting mojo know about it (probably because mojo went down and came back up and ++ * thought that the account was logged out anyways) ++ */ ++ MojLogError(IMServiceApp::s_log, _T("account_login_failed_cb: account in neither online or pending list. Why are we getting logged out? accountKey: %s description %s:"), ++ accountKey, description); ++ // don't leak! ++ free(serviceName); ++ free(username); ++ free(accountKey); ++ return; ++ } ++ else ++ { ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ } ++ } ++ ++ const char* mojoFriendlyErrorCode = getMojoFriendlyErrorCode(type); ++ const char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey); ++ const char* connectionType = (char*)g_hash_table_lookup(s_connectionTypeData, accountKey); ++ ++ if (accountBoundToIpAddress == NULL) ++ { ++ accountBoundToIpAddress = ""; ++ } ++ ++ if (connectionType == NULL) ++ { ++ connectionType = ""; ++ } ++ MojLogInfo(IMServiceApp::s_log, _T("account_login_failed_cb: removing account from onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_remove(s_onlineAccountData, accountKey); ++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey); ++ g_hash_table_remove(s_connectionTypeData, accountKey); ++ ++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL) ++ { ++ /* ++ * Keep the PurpleAccount struct to reuse in future logins ++ */ ++ g_hash_table_insert(s_offlineAccountData, accountKey, account); ++ // don't free the accountKey in this case since now s_offlineAccountData points to it ++ accountKey = NULL; ++ } ++ ++ // reply with login failed ++ if (loginState != NULL) ++ { ++ //TODO: determine if there are cases where noRetry should be false ++ //TODO: include "description" parameter because it had useful details? ++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_FAILED, loggedOut, mojoFriendlyErrorCode, noRetry); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_login_failed_cb called with loginState=NULL")); ++ } ++ free(serviceName); ++ free(username); ++ if (NULL != accountKey) ++ free(accountKey); ++} ++ ++static void account_status_changed(PurpleAccount* account, PurpleStatus* oldStatus, PurpleStatus* newStatus, gpointer loginState) ++{ ++ printf("\n\n ACCOUNT STATUS CHANGED \n\n"); ++} ++ ++/* ++ * This gets called then we decline a remote user's buddy invite ++ * TODO - what signal gets emitted when a remote user declines our invitation??? ++ */ ++static void account_auth_deny_cb(PurpleAccount* account, const char* remote_user) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("account_auth_deny_cb called. account: %s, remote_user: %s"), account->username, remote_user); ++ ++ // TODO this needs to happen when remote user declines our invite, not here... ++// char* serviceName = getServiceNameFromPrplProtocolId(account); ++// char* username = getMojoFriendlyUsername(account->username, serviceName); ++// char* usernameFromStripped = stripResourceFromGtalkUsername(remote_user, serviceName); ++// ++// // tell transport to delete the buddy and contacts ++// s_imServiceHandler->buddyInviteDeclined(serviceName, username, usernameFromStripped); ++// ++// // clean up ++// free(serviceName); ++// free(username); ++// free(usernameFromStripped); ++ ++ ++} ++ ++/* ++ * This gets called then we accept a remote user's buddy invite ++ * log: account_auth_accept_cb called. account: palm@gmail.com/Home, buddy: palm3@gmail.com ++ */ ++static void account_auth_accept_cb(PurpleAccount* account, const char* remote_user) ++{ ++ // nothing to do ++ MojLogInfo(IMServiceApp::s_log, _T("account_auth_accept_cb called. account: %s, remote_user: %s"), account->username, remote_user); ++} ++ ++ ++void incoming_message_cb(PurpleConversation* conv, const char* who, const char* alias, const char* message, ++ PurpleMessageFlags flags, time_t mtime) ++{ ++ /* ++ * snippet taken from nullclient ++ */ ++ const char* usernameFrom; ++ if (who &&* who) ++ usernameFrom = who; ++ else if (alias &&* alias) ++ usernameFrom = alias; ++ else ++ usernameFrom = ""; ++ ++ if ((flags & PURPLE_MESSAGE_RECV) != PURPLE_MESSAGE_RECV) ++ { ++ /* this is a sent message. ignore it. */ ++ return; ++ } ++ ++ PurpleAccount* account = purple_conversation_get_account(conv); ++ ++ // these never return null... ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ ++ if (strcmp(username, usernameFrom) == 0) // TODO: should this compare use account->username? ++ { ++ /* We get notified even though we sent the message. Just ignore it */ ++ free(serviceName); ++ free(username); ++ return; ++ } ++ ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && (strcmp(usernameFrom, "aolsystemmsg") == 0 || strcmp(usernameFrom, ++ "AOL System Msg") == 0)) ++ { ++ /* ++ * ignore messages from aolsystemmsg telling us that we're logged in somewhere else ++ */ ++ free(serviceName); ++ free(username); ++ return; ++ } ++ ++ char* usernameFromStripped = stripResourceFromGtalkUsername(usernameFrom, serviceName); ++ ++ //Special for Office Communicator. (Remove 'sip:' prefix) ++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ if (strstr(usernameFromStripped, "sip:") != NULL) ++ { ++ GString *SIPBuddyAlias = g_string_new(usernameFromStripped); ++ g_string_erase(SIPBuddyAlias, 0, 4); ++ usernameFromStripped = (char*)SIPBuddyAlias->str; ++ } ++ } ++ ++ // call the transport service incoming message handler ++ s_imServiceHandler->incomingIM(serviceName, username, usernameFromStripped, message); ++ ++ free(serviceName); ++ free(username); ++ free(usernameFromStripped); ++} ++ ++/* ++ * Called when a remote user requests authorization to be our buddy ++ * ++ * Note: this method will get called every time user logs in if there is a pending invitation. ++ */ ++static void *request_authorize_cb (PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, ++ gboolean on_list, PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, ++ void *user_data) ++{ ++ ++ MojLogInfo(IMServiceApp::s_log, _T("request_authorize_cb called. remote user: %s, id: %s, message: %s"), remote_user, id, message); ++ ++ // these never return null... ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ char* usernameFromStripped = stripResourceFromGtalkUsername(remote_user, serviceName); ++ ++ // Save off the authorize/deny callbacks to use later ++ AuthRequest *aa = g_new0(AuthRequest, 1); ++ aa->auth_cb = authorize_cb; ++ aa->deny_cb = deny_cb; ++ aa->data = user_data; ++ aa->remote_user = g_strdup(remote_user); ++ aa->alias = g_strdup(alias); ++ aa->account = account; ++ ++ char *authRequestKey = getAuthRequestKey(username, serviceName, usernameFromStripped); ++ // if there is already an entry for this, we need to replace it since callback function pointers will change on login ++ // old object gets deleted by our destroy functions specified in the hash table construction ++ g_hash_table_replace(s_AuthorizeRequests, authRequestKey, aa); ++ // log the table ++ logAuthRequestTableValues(); ++ ++ // call back into IMServiceHandler to create a receivedBuddyInvite imCommand. ++ s_imServiceHandler->receivedBuddyInvite(serviceName, username, usernameFromStripped, message); ++ ++ free(serviceName); ++ free(username); ++ free(usernameFromStripped); ++ // don't free the authRequestKey - it is not copied, but held onto for the life of the hash table once inserted ++ ++ return NULL; ++ ++} ++ ++/* ++ * Not used ++ */ ++void request_add_cb(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message) { ++ ++ MojLogInfo(IMServiceApp::s_log, _T("request_add_cb called. remote user: %s"), remote_user); ++} ++ ++gboolean connectTimeoutCallback(gpointer data) ++{ ++ char* accountKey = (char*)data; ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey); ++ if (account == NULL) ++ { ++ /* ++ * If the account is not pending anymore (which means login either already failed or succeeded) ++ * then we shouldn't have gotten to this point since we should have cancelled the timer ++ */ ++ MojLogError(IMServiceApp::s_log, ++ _T("WARNING: we shouldn't have gotten to connectTimeoutCallback since login had already failed/succeeded. accountKey %s"), accountKey); ++ free(accountKey); ++ return FALSE; ++ } ++ ++ /* ++ * abort logging in since our connect timeout has hit before login either failed or succeeded ++ */ ++ g_hash_table_remove(s_accountLoginTimers, accountKey); ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey); ++ g_hash_table_remove(s_accountBuddyListTimers, accountKey); ++ ++ purple_account_disconnect(account); ++ ++ if (s_loginState) ++ { ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ ++ s_loginState->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_TIMEOUT, false, ERROR_NETWORK_ERROR, true); ++ ++ free(serviceName); ++ free(username); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("ERROR: connectTimeoutCallback called with s_loginState=NULL. accountKey %s"), accountKey); ++ } ++ ++ free(accountKey); ++ return FALSE; ++} ++ ++/* ++ * End of callbacks ++ */ ++ ++/* ++ * libpurple initialization methods ++ */ ++ ++static GHashTable* getClientInfo(void) ++{ ++ GHashTable* clientInfo = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ g_hash_table_insert(clientInfo, (void*)"name", (void*)"Palm Messaging"); ++ g_hash_table_insert(clientInfo, (void*)"version", (void*)""); ++ ++ return clientInfo; ++} ++ ++static void initializeLibpurple() ++{ ++ signal(SIGCHLD, SIG_IGN); ++ ++ /* Set a custom user directory (optional) */ ++ purple_util_set_user_dir(CUSTOM_USER_DIRECTORY); ++ ++ /* We do not want any debugging for now to keep the noise to a minimum. */ ++ purple_debug_set_enabled(TRUE); ++ ++ /* Set the core-uiops, which is used to ++ * - initialize the ui specific preferences. ++ * - initialize the debug ui. ++ * - initialize the ui components for all the modules. ++ * - uninitialize the ui components for all the modules when the core terminates. ++ */ ++ purple_core_set_ui_ops(&adapterCoreUIOps); ++ ++ /* Set path to search for plugins. The core (libpurple) takes care of loading the ++ * core-plugins, which includes the protocol-plugins. So it is not essential to add ++ * any path here, but it might be desired, especially for ui-specific plugins. */ ++ purple_plugins_add_search_path(CUSTOM_PLUGIN_PATH); ++ ++ purple_eventloop_set_ui_ops(&adapterEventLoopUIOps); ++ ++ // TODO - there is a memory leak in here...at least on desktop ++ if (!purple_core_init(UI_ID)) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("libpurple initialization failed.")); ++ abort(); ++ } ++ ++ /* Create and load the buddylist. */ ++ purple_set_blist(purple_blist_new()); ++ purple_blist_load(); ++ ++ purple_buddy_icons_set_cache_dir("/var/luna/data/im-avatars"); ++ ++ s_libpurpleInitialized = TRUE; ++ MojLogInfo(IMServiceApp::s_log, _T("libpurple initialized.\n")); ++} ++/* ++ * End of libpurple initialization methods ++ */ ++ ++/* ++ * Service methods ++ */ ++LibpurpleAdapter::LoginResult LibpurpleAdapter::login(LoginParams* params, LoginCallbackInterface* loginState) ++{ ++ LoginResult result = LibpurpleAdapter::OK; ++ ++ PurpleAccount* account; ++ char* prplProtocolId = NULL; ++ char* transportFriendlyUserName = NULL; ++ char* accountKey = NULL; ++ bool accountIsAlreadyOnline = FALSE; ++ bool accountIsAlreadyPending = FALSE; ++ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ PurpleAccount* alreadyActiveAccount = NULL; ++ ++ if (!params || !params->serviceName || params->serviceName[0] == 0 || !params->username || params->username[0] == 0) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login with empty username or serviceName")); ++ result = LibpurpleAdapter::INVALID_CREDENTIALS; ++ goto error; ++ } ++ ++ if (!params->localIpAddress) ++ { ++ params->localIpAddress = ""; ++ } ++ ++ if (!params->connectionType) ++ { ++ params->connectionType = ""; ++ } ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: accountId %s, servicename %s, connectionType %s"), params->accountId, params->serviceName, params->connectionType); ++ ++ if (s_libpurpleInitialized == FALSE) ++ { ++ initializeLibpurple(); ++ } ++ ++ /* libpurple variables */ ++ accountKey = getAccountKey(params->username, params->serviceName); ++ ++ // If this account id isn't yet stored, then keep track of it now. ++ if (g_hash_table_lookup(s_AccountIdsData, accountKey) == NULL) ++ { ++ g_hash_table_insert(s_AccountIdsData, accountKey, strdup(params->accountId)); ++ } ++ ++ /* ++ * Let's check to see if we're already logged in to this account or that we're already in the process of logging in ++ * to this account. This can happen when mojo goes down and comes back up. ++ */ ++ alreadyActiveAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (alreadyActiveAccount != NULL) ++ { ++ accountIsAlreadyOnline = TRUE; ++ } ++ else ++ { ++ alreadyActiveAccount = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey); ++ if (alreadyActiveAccount != NULL) ++ { ++ accountIsAlreadyPending = TRUE; ++ } ++ } ++ ++ if (alreadyActiveAccount != NULL) ++ { ++ /* ++ * We're either already logged in to this account or we're already in the process of logging in to this account ++ * (i.e. it's pending; waiting for server response) ++ */ ++ char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey); ++ if (accountBoundToIpAddress != NULL && strcmp(params->localIpAddress, accountBoundToIpAddress) == 0) ++ { ++ /* ++ * We're using the right interface for this account ++ */ ++ if (accountIsAlreadyPending) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login: We were already in the process of logging in. accountKey %s"), accountKey); ++ return LibpurpleAdapter::OK; ++ } ++ else if (accountIsAlreadyOnline) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login: We were already logged in to the requested account. accountKey %s"), accountKey); ++ return LibpurpleAdapter::ALREADY_LOGGED_IN; ++ } ++ } ++ else ++ { ++ /* ++ * We're not using the right interface. Close the current connection for this account and create a new one ++ */ ++ MojLogError(IMServiceApp::s_log, ++ _T("LibpurpleAdapter::login: We have to logout and login again since the local IP address has changed. Logging out from account. accountKey %s"), accountKey); ++ /* ++ * Once the current connection is closed we don't want to let mojo know that the account was disconnected. ++ * Since mojo went down and came back up it didn't know that the account was connected anyways. ++ * So let's take the account out of the account data hash and then disconnect it. ++ */ ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("LibpurpleAdapter::login: removing account from onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_remove(s_onlineAccountData, accountKey); ++ } ++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ } ++ purple_account_disconnect(alreadyActiveAccount); ++ } ++ } ++ ++ /* ++ * Let's go through our usual login process ++ */ ++ ++ // TODO this currently ignores authentication token, but should check it as well when support for auth token is added ++ if (params->password == NULL || params->password[0] == 0) ++ { ++ MojLogError(IMServiceApp::s_log, _T("Error: null or empty password trying to log in to servicename %s"), params->serviceName); ++ result = LibpurpleAdapter::INVALID_CREDENTIALS; ++ goto error; ++ } ++ else ++ { ++ /* save the local IP address that we need to use */ ++ // TODO - move this to #ifdef. If you are running imlibpurpletransport on desktop, but tethered to device, params->localIpAddress needs to be set to ++ // NULL otherwise login will fail... ++ if (params->localIpAddress != NULL && params->localIpAddress[0] != 0) ++ { ++ purple_prefs_remove("/purple/network/preferred_local_ip_address"); ++ purple_prefs_add_string("/purple/network/preferred_local_ip_address", params->localIpAddress); ++ } ++ else ++ { ++#ifdef DEVICE ++ /* ++ * If we're on device you should not accept an empty ipAddress; it's mandatory to be provided ++ */ ++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login with missing localIpAddress")); ++ result = FAILED; ++ goto error; ++#endif ++ } ++ ++ /* save the local IP address that we need to use */ ++ if (params->connectionType != NULL && params->connectionType[0] != 0) ++ { ++ g_hash_table_insert(s_connectionTypeData, accountKey, strdup(params->connectionType)); ++ } ++ ++ prplProtocolId = getPrplProtocolIdFromServiceName(params->serviceName); ++ /* ++ * If we've already logged in to this account before then re-use the old PurpleAccount struct ++ */ ++ transportFriendlyUserName = getPrplFriendlyUsername(params->serviceName, params->username); ++ account = (PurpleAccount*)g_hash_table_lookup(s_offlineAccountData, accountKey); ++ if (!account) ++ { ++ /* Create the account */ ++ account = purple_account_new(transportFriendlyUserName, prplProtocolId); ++ if (!account) ++ { ++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login failed to create new Purple account")); ++ result = LibpurpleAdapter::FAILED; ++ goto error; ++ } ++ } ++ ++ //Load account preferences ++ char* templateId = getMojoFriendlyTemplateID((char*)params->serviceName); ++ ++ if (strcmp((char*)params->serviceName, SERVICENAME_JABBER) == 0) ++ { ++ //Set Account Alias to jabber ++ purple_account_set_alias (account,"jabber"); ++ } ++ if (strcmp((char*)params->serviceName,SERVICENAME_GTALK) == 0) ++ { ++ //Set Account Alias to gtalk ++ purple_account_set_alias (account,"gtalk"); ++ } ++ ++ MojString m_templateId; ++ m_templateId.assign(templateId); ++ s_PrefsHandler->setaccountprefs(m_templateId,account); ++ //Load account preferences ++ MojLogInfo(IMServiceApp::s_log, _T("Logging in...")); ++ ++ purple_account_set_password(account, params->password); ++ } ++ ++ if (result == LibpurpleAdapter::OK) ++ { ++ /* mark the account as pending */ ++ g_hash_table_insert(s_pendingAccountData, accountKey, account); ++ ++ if (params->localIpAddress != NULL && params->localIpAddress[0] != 0) ++ { ++ /* keep track of the local IP address that we bound to when logging in to this account */ ++ g_hash_table_insert(s_ipAddressesBoundTo, accountKey, strdup(params->localIpAddress)); ++ } ++ ++ /* It's necessary to enable the account first. */ ++ purple_account_set_enabled(account, UI_ID, TRUE); ++ ++ /* Now, to connect the account, create a status and activate it. */ ++ ++ /* ++ * Create a timer for this account's login so it can fail the login after a timeout. ++ */ ++ char* UserName = getMojoFriendlyUsername(params->username, params->serviceName); ++ char* templateId = getMojoFriendlyTemplateID((char*)params->serviceName); ++ static const guint CONNECT_TIMEOUT_SECONDS = (guint)atoi(s_PrefsHandler->GetStringPreference("LoginTimeOut", templateId, UserName)); ++ guint timerHandle = purple_timeout_add_seconds(CONNECT_TIMEOUT_SECONDS, connectTimeoutCallback, accountKey); ++ g_hash_table_insert(s_accountLoginTimers, accountKey, (gpointer)timerHandle); ++ ++ PurpleStatusPrimitive prim = getPurpleAvailabilityFromPalmAvailability(params->availability); ++ PurpleSavedStatus* savedStatus = purple_savedstatus_new(NULL, prim); ++ if (params->customMessage && params->customMessage[0]) ++ { ++ purple_savedstatus_set_message(savedStatus, params->customMessage); ++ } ++ purple_savedstatus_activate_for_account(savedStatus, account); ++ } ++ ++ error: ++ ++ if (prplProtocolId) ++ { ++ free(prplProtocolId); ++ } ++ if (transportFriendlyUserName) ++ { ++ free(transportFriendlyUserName); ++ } ++ ++ return result; ++} ++ ++bool LibpurpleAdapter::logout(const char* serviceName, const char* username, LoginCallbackInterface* loginState) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!username || !serviceName) ++ { ++ MojLogError(IMServiceApp::s_log, _T("Invalid logout parameter. Please double check the passed parameters.")); ++ return FALSE; ++ } ++ ++ bool success = TRUE; ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: servicename %s"), serviceName); ++ ++ char* accountKey = getAccountKey(username, serviceName); ++ ++ // Remove the accountId since a logout could be from the user removing the account ++ g_hash_table_remove(s_AccountIdsData, accountKey); ++ ++ PurpleAccount* accountTologoutFrom = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (accountTologoutFrom == NULL) ++ { ++ accountTologoutFrom = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey); ++ if (accountTologoutFrom == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("Trying to logout from an account that is not logged in. username %s, service name %s, accountKey %s"), username, serviceName, accountKey); ++ success = FALSE; ++ } ++ } ++ ++ if (accountTologoutFrom != NULL) ++ { ++ purple_account_disconnect(accountTologoutFrom); ++ } ++ ++ if (accountKey) ++ { ++ free(accountKey); ++ } ++ return success; ++} ++ ++bool LibpurpleAdapter::setMyAvailability(const char* serviceName, const char* username, int availability) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!serviceName || !username) ++ { ++ MojLogError(IMServiceApp::s_log, _T("setMyAvailability: Invalid parameter. Please double check the passed parameters.")); ++ return FALSE; ++ } ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: serviceName %s, availability %i"), serviceName, availability); ++ ++ bool retVal = FALSE; ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (account == NULL) ++ { ++ //this should never happen based on MessagingService's logic ++ MojLogError(IMServiceApp::s_log, ++ _T("setMyAvailability was called on an account that wasn't logged in. serviceName: %s, availability: %i"), ++ serviceName, availability); ++ retVal = FALSE; ++ } ++ else ++ { ++ retVal = TRUE; ++ /* ++ * Let's get the current custom message and set it as well so that we don't overwrite it with "" ++ */ ++ PurplePresence* presence = purple_account_get_presence(account); ++ const PurpleStatus* status = purple_presence_get_active_status(presence); ++ const PurpleValue* value = purple_status_get_attr_value(status, "message"); ++ const char* customMessage = NULL; ++ if (value != NULL) ++ { ++ customMessage = purple_value_get_string(value); ++ } ++ if (customMessage == NULL) ++ { ++ customMessage = ""; ++ } ++ ++ PurpleStatusPrimitive prim = getPurpleAvailabilityFromPalmAvailability(availability); ++ PurpleStatusType* type = purple_account_get_status_type_with_primitive(account, prim); ++ GList* attrs = NULL; ++ attrs = g_list_append(attrs, (void*)"message"); ++ attrs = g_list_append(attrs, (char*)customMessage); ++ purple_account_set_status_list(account, purple_status_type_get_id(type), TRUE, attrs); ++ } ++ ++ // delete the key since it was just for lookup ++ free(accountKey); ++ return retVal; ++} ++ ++bool LibpurpleAdapter::setMyCustomMessage(const char* serviceName, const char* username, const char* customMessage) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!serviceName || !username || !customMessage) ++ { ++ MojLogError(IMServiceApp::s_log, _T("setMyCustomMessage: Invalid parameter. Please double check the passed parameters.")); ++ return FALSE; ++ } ++ ++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: serviceName %s"), serviceName); ++ ++ bool retVal = FALSE; ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (account == NULL) ++ { ++ //this should never happen based on MessagingService's logic ++ MojLogError(IMServiceApp::s_log, ++ _T("setMyCustomMessage was called on an account that wasn't logged in. serviceName: %s"), ++ serviceName); ++ retVal = FALSE; ++ } ++ else ++ { ++ retVal = TRUE; ++ // get the account's current status type ++ PurpleStatusType* type = purple_status_get_type(purple_account_get_active_status(account)); ++ GList* attrs = NULL; ++ attrs = g_list_append(attrs, (void*)"message"); ++ attrs = g_list_append(attrs, (char*)customMessage); ++ purple_account_set_status_list(account, purple_status_type_get_id(type), TRUE, attrs); ++ } ++ ++ // delete the key since it was just for lookup ++ free(accountKey); ++ return retVal; ++} ++ ++/* ++ * Block this user from sending us messages ++ */ ++LibpurpleAdapter::SendResult LibpurpleAdapter::blockBuddy(const char* serviceName, const char* username, const char* buddyUsername, bool block) ++{ ++ bool success = FALSE; ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!serviceName || !username || !buddyUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("blockBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername); ++ return INVALID_PARAMS; ++ } ++ ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ success = (account != NULL); ++ if (success) ++ { ++ // strip off the "@aol.com" if needed ++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername); ++ if (block) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("blockBuddy: deny %s"), transportFriendlyUserName); ++ purple_privacy_deny(account, transportFriendlyUserName, false, true); ++ } ++ else ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("blockBuddy: allow %s"), transportFriendlyUserName); ++ purple_privacy_allow(account, transportFriendlyUserName, false, true); ++ } ++ free(transportFriendlyUserName); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, "blockBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey); ++ } ++ ++ if (accountKey) ++ { ++ free(accountKey); ++ } ++ ++ if (success) ++ return SENT; ++ else return USER_NOT_LOGGED_IN; ++} ++ ++/* ++ * Remove a buddy from our account ++ */ ++LibpurpleAdapter::SendResult LibpurpleAdapter::removeBuddy(const char* serviceName, const char* username, const char* buddyUsername) ++{ ++ bool success = FALSE; ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!serviceName || !username || !buddyUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("removeBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername); ++ return INVALID_PARAMS; ++ } ++ ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ success = (account != NULL); ++ if (success) ++ { ++ // strip off the "@aol.com" if needed ++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername); ++ MojLogInfo(IMServiceApp::s_log, _T("removeBuddy: %s"), transportFriendlyUserName); ++ ++ PurpleBuddy* buddy = purple_find_buddy(account, transportFriendlyUserName); ++ if (NULL == buddy) { ++ MojLogError(IMServiceApp::s_log, _T("could not find buddy %s in list - cannot remove"), transportFriendlyUserName); ++ success = FALSE; ++ } ++ else { ++ PurpleGroup* group = purple_buddy_get_group(buddy); ++ // remove from server list ++ purple_account_remove_buddy(account, buddy, group); ++ ++ // remove from buddy list - generates a "buddy-removed" signal ++ purple_blist_remove_buddy(buddy); ++ } ++ ++ free(transportFriendlyUserName); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, "removeBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey); ++ } ++ ++ if (accountKey) ++ { ++ free(accountKey); ++ } ++ ++ if (success) ++ return SENT; ++ else return USER_NOT_LOGGED_IN; ++} ++ ++/* ++ * Add a buddy to our buddy list. Some services (GTalk) will require the buddy to authorize us to add them ++ */ ++LibpurpleAdapter::SendResult LibpurpleAdapter::addBuddy(const char* serviceName, const char* username, const char* buddyUsername, const char* groupName) ++{ ++ bool success = FALSE; ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!serviceName || !username || !buddyUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("addBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername); ++ return INVALID_PARAMS; ++ } ++ ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ success = (account != NULL); ++ if (success) ++ { ++ // strip off the "@aol.com" if needed ++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername); ++ MojLogInfo(IMServiceApp::s_log, _T("addBuddy: %s"), transportFriendlyUserName); ++ ++ PurpleBuddy* buddy = purple_buddy_new(account, transportFriendlyUserName, /*alias*/ NULL); ++ ++ // add buddy to list ++ /* ++ void purple_blist_add_buddy ( PurpleBuddy * buddy, ++ PurpleContact * contact, ++ PurpleGroup * group, ++ PurpleBlistNode * node ++ ) ++ ++ Adds a new buddy to the buddy list. ++ The buddy will be inserted right after node or prepended to the group if node is NULL. If both are NULL, the buddy will be added to the "Buddies" group. ++ */ ++ PurpleGroup *group = NULL; ++ if (NULL != groupName && *groupName != '\0') { ++ group = purple_find_group(groupName); ++ if (NULL == group){ ++ // group not there - add it ++ MojLogInfo(IMServiceApp::s_log, _T("addBuddy: adding new group %s"), groupName); ++ group = purple_group_new(groupName); ++ } ++ } ++ purple_blist_add_buddy(buddy, NULL, group, NULL); ++ ++ // add to server - note: this has to be called AFTER the purple_blist_add_buddy() call otherwise it seg faults... ++ purple_account_add_buddy(account, buddy); ++ ++ // note - there seems to be an inconsistency in libpurple where AIM buddies added via purple appear offline until the account is logged off and on... ++ // see http://pidgin.im/pipermail/devel/2007-June/001517.html: ++ /* Such is not the case on AIM, however. The behavior I see here is that the ++ buddies appear in the Pidgin blist but always have an offline status, even ++ though I know at least four of these people are online. Looking at blist.xml ++ after the next shown flush in the debug window shows that nothing has been added ++ to the local list. The alias and buddy notes are not added, either. The status ++ remains incorrect until I restart Pidgin, disable and enable the account, or ++ switch to the offline status and then back to an online status. At this point, ++ the buddies are finally shown in blist.xml and statuses are correct; however, ++ the alias and notes string that were set at import are missing. Behavior is ++ identical on ICQ when importing a list of AIM buddies (which to my limited ++ knowledge does not require authorization). ++ */ ++ ++ free(transportFriendlyUserName); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, "addBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey); ++ } ++ ++ if (accountKey) ++ { ++ free(accountKey); ++ } ++ ++ if (success) ++ return SENT; ++ else return USER_NOT_LOGGED_IN; ++} ++ ++/* ++ * Authorize the remote user to be our buddy ++ */ ++LibpurpleAdapter::SendResult LibpurpleAdapter::authorizeBuddy(const char* serviceName, const char* username, const char* fromUsername) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("authorizeBuddy: username: %s, serviceName: %s, buddyUsername: %s"), username, serviceName, fromUsername); ++ ++ if (!serviceName || !username || !fromUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("authorizeBuddy: null parameter. cannot process command")); ++ return INVALID_PARAMS; ++ } ++ ++ // if we got here, we need to be online... ++ char *accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* onlineAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ ++ if (onlineAccount == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("authorizeBuddy: account not online. accountKey: %s"), accountKey); ++ free (accountKey); ++ return USER_NOT_LOGGED_IN; ++ } ++ // free key since it was only used for lookup ++ free (accountKey); ++ ++ // create the key and find the auth_and_add object in the s_AuthorizeRequests table ++ char *authRequestKey = getAuthRequestKey(username, serviceName, fromUsername); ++ AuthRequest *aa = (AuthRequest *)g_hash_table_lookup(s_AuthorizeRequests, authRequestKey); ++ if (NULL == aa) ++ { ++ MojLogError(IMServiceApp::s_log, "authorizeBuddy: cannot find auth request object - authRequestKey %s", authRequestKey); ++ // log the table ++ logAuthRequestTableValues(); ++ free (authRequestKey); ++ return SEND_FAILED; ++ } ++ ++ // authorize account ++ aa->auth_cb(aa->data); ++ ++ // TODO - do we need to add this user to our buddy list? Libpurple seems to add it automatically - appears at next login. ++ ++ // we are done with this request - remove it from list ++ // object and key held by table get deleted by our destroy functions specified in the hash table construction ++ g_hash_table_remove(s_AuthorizeRequests, authRequestKey); ++ ++ // free key used for look-up ++ free (authRequestKey); ++ ++ return SENT; ++} ++ ++/* ++ * Decline the request from the remote user to be our buddy ++ */ ++LibpurpleAdapter::SendResult LibpurpleAdapter::declineBuddy(const char* serviceName, const char* username, const char* fromUsername) ++{ ++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: username: %s, serviceName: %s, buddyUsername: %s"), username, serviceName, fromUsername); ++ if (!serviceName || !username || !fromUsername) ++ { ++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: null parameter. cannot process command")); ++ return INVALID_PARAMS; ++ } ++ ++ // if we got here, we need to be online... ++ char *accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* onlineAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (onlineAccount == NULL) ++ { ++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: account not online. accountKey: %s"), accountKey); ++ free (accountKey); ++ return USER_NOT_LOGGED_IN; ++ } ++ // free key since it was only used for lookup ++ free (accountKey); ++ ++ // create the key and find the auth_and_add object in the s_AuthorizeRequests table ++ char *authRequestKey = getAuthRequestKey(username, serviceName, fromUsername); ++ AuthRequest *aa = (AuthRequest *)g_hash_table_lookup(s_AuthorizeRequests, authRequestKey); ++ if (NULL == aa) ++ { ++ MojLogError(IMServiceApp::s_log, "declineBuddy: cannot find auth request object - authRequestKey %s", authRequestKey); ++ // log the table ++ logAuthRequestTableValues(); ++ free (authRequestKey); ++ return SEND_FAILED; ++ } ++ ++ aa->deny_cb(aa->data); ++ ++ // we are done with this request - remove it from list ++ // object gets deleted by our destroy functions specified in the hash table construction ++ g_hash_table_remove(s_AuthorizeRequests, authRequestKey); ++ ++ // free key used for look-up ++ free (authRequestKey); ++ return SENT; ++} ++ ++LibpurpleAdapter::SendResult LibpurpleAdapter::sendMessage(const char* serviceName, const char* username, const char* usernameTo, const char* messageText) ++{ ++ if (!serviceName || !username || !usernameTo || !messageText) ++ { ++ MojLogError(IMServiceApp::s_log, _T("sendMessage: Invalid parameter. Please double check the passed parameters.")); ++ return LibpurpleAdapter::INVALID_PARAMS; ++ } ++ ++ LibpurpleAdapter::SendResult retVal = LibpurpleAdapter::SENT; ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ char* accountKey = getAccountKey(username, serviceName); ++ PurpleAccount* accountToSendFrom = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ ++ if (accountToSendFrom == NULL) ++ { ++ retVal = LibpurpleAdapter::USER_NOT_LOGGED_IN; ++ MojLogError(IMServiceApp::s_log, _T("sendMessage: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s"), username, serviceName, accountKey); ++ } ++ else ++ { ++ // strip off the "@aol.com" if needed ++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, usernameTo); ++ PurpleConversation* purpleConversation = purple_conversation_new(PURPLE_CONV_TYPE_IM, accountToSendFrom, transportFriendlyUserName); ++ char* messageTextUnescaped = g_strcompress(messageText); ++ ++ // replace this with the lower level call so we can try to get an error code back... ++ // calls common_send which calls serv_send_im (conversation.c) ++// purple_conv_im_send(purple_conversation_get_im_data(purpleConversation), messageTextUnescaped); ++// common_send(PurpleConversation *conv, const char *message, PurpleMessageFlags msgflags) ++// gc = purple_conversation_get_gc(conv); ++// err = serv_send_im(gc, purple_conversation_get_name(conv), sent, msgflags); ++ // we still don't seem to get an error value back there...returns 1, even for an invalid recipient ++ int err = serv_send_im(purple_conversation_get_gc(purpleConversation), purple_conversation_get_name(purpleConversation), messageTextUnescaped, (PurpleMessageFlags)0); ++ if (err < 0) { ++ retVal = LibpurpleAdapter::SEND_FAILED; ++ MojLogError(IMServiceApp::s_log, _T("sendMessage: serv_send_im returned err %d"), err); ++ } ++ ++ free(messageTextUnescaped); ++ free(transportFriendlyUserName); ++ } ++ if (accountKey) ++ free(accountKey); ++ return retVal; ++} ++ ++ ++// Called by IMLoginState whenever a connection interface goes down. ++// If all==true, then all interfaces went down. ++bool LibpurpleAdapter::deviceConnectionClosed(bool all, const char* ipAddress) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ if (!ipAddress) ++ { ++ return FALSE; ++ } ++ ++ PurpleAccount* account; ++ GSList* accountToLogoutList = NULL; ++ GList* iterator = NULL; ++ ++ GList* onlineAndPendingAccountKeys = g_hash_table_get_keys(s_ipAddressesBoundTo); ++ for (iterator = onlineAndPendingAccountKeys; iterator != NULL; iterator = g_list_next(iterator)) ++ { ++ char* accountKey = (char*)iterator->data; ++ char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey); ++ if (all == true || (accountBoundToIpAddress != NULL && strcmp(ipAddress, accountBoundToIpAddress) == 0)) ++ { ++ bool accountWasLoggedIn = FALSE; ++ ++ account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey); ++ if (account == NULL) ++ { ++ account = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey); ++ if (account == NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("account was not found in the hash")); ++ continue; ++ } ++ MojLogInfo(IMServiceApp::s_log, _T("Abandoning login")); ++ } ++ else ++ { ++ accountWasLoggedIn = TRUE; ++ MojLogInfo(IMServiceApp::s_log, _T("Logging out")); ++ } ++ ++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("deviceConnectionClosed: removing account from onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_remove(s_onlineAccountData, accountKey); ++ } ++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_pendingAccountData, accountKey); ++ } ++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL) ++ { ++ /* ++ * Keep the PurpleAccount struct to reuse in future logins ++ */ ++ g_hash_table_insert(s_offlineAccountData, accountKey, account); ++ } ++ ++ purple_account_disconnect(account); ++ ++ accountToLogoutList = g_slist_append(accountToLogoutList, account); ++ ++ MojLogInfo(IMServiceApp::s_log, _T("deviceConnectionClosed: removing account from onlineAccountData hash table. accountKey %s"), accountKey); ++ g_hash_table_remove(s_onlineAccountData, accountKey); ++ // We can't remove this guy since we're iterating through its keys. We'll remove it after the break ++ //g_hash_table_remove(ipAddressesBoundTo, accountKey); ++ } ++ } ++ ++ if (g_slist_length(accountToLogoutList) == 0) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("No accounts were connected on the requested ip address")); ++ } ++ else ++ { ++ GSList* accountIterator = NULL; ++ for (accountIterator = accountToLogoutList; accountIterator != NULL; accountIterator = accountIterator->next) ++ { ++ account = (PurpleAccount*) accountIterator->data; ++ char* serviceName = getServiceNameFromPrplProtocolId(account); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ char* accountKey = getAccountKey(username, serviceName); ++ ++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey); ++ ++ free(serviceName); ++ free(username); ++ free(accountKey); ++ } ++ } ++ ++ return TRUE; ++} ++ ++void LibpurpleAdapter::assignIMLoginState(LoginCallbackInterface* loginState) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ static int handle = 0x1AD; ++ s_loginState = loginState; ++ ++ if (s_registeredForAccountSignals == TRUE) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("Disconnecting old signals.")); ++ s_registeredForAccountSignals = FALSE; ++ ++ purple_signal_disconnect(purple_connections_get_handle(), "signed-on", &handle, ++ PURPLE_CALLBACK(account_logged_in_cb)); ++ purple_signal_disconnect(purple_connections_get_handle(), "signed-off", &handle, ++ PURPLE_CALLBACK(account_signed_off_cb)); ++ purple_signal_disconnect(purple_connections_get_handle(), "connection-error", &handle, ++ PURPLE_CALLBACK(account_login_failed_cb)); ++ ++ purple_signal_disconnect(purple_accounts_get_handle(), "account-status-changed", &handle, ++ PURPLE_CALLBACK(account_status_changed)); ++ purple_signal_disconnect(purple_accounts_get_handle(), "account-authorization-denied", &handle, ++ PURPLE_CALLBACK(account_auth_deny_cb)); ++ purple_signal_disconnect(purple_accounts_get_handle(), "account-authorization-granted", &handle, ++ PURPLE_CALLBACK(account_auth_accept_cb)); ++ } ++ ++ if (loginState != NULL) ++ { ++ MojLogInfo(IMServiceApp::s_log, _T("Connecting new signals.")); ++ s_registeredForAccountSignals = TRUE; ++ /* ++ * Listen for a number of different signals: ++ */ ++ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle, ++ PURPLE_CALLBACK(account_logged_in_cb), loginState); ++ purple_signal_connect(purple_connections_get_handle(), "signed-off", &handle, ++ PURPLE_CALLBACK(account_signed_off_cb), loginState); ++ purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle, ++ PURPLE_CALLBACK(account_login_failed_cb), loginState); ++ ++ // accounts signals ++ purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", &handle, ++ PURPLE_CALLBACK(account_status_changed), loginState); ++ purple_signal_connect(purple_accounts_get_handle(), "account-authorization-denied", &handle, ++ PURPLE_CALLBACK(account_auth_deny_cb), loginState); ++ purple_signal_connect(purple_accounts_get_handle(), "account-authorization-granted", &handle, ++ PURPLE_CALLBACK(account_auth_accept_cb), loginState); ++ } ++} ++ ++void LibpurpleAdapter::assignIMServiceHandler(IMServiceCallbackInterface* imServiceHandler) ++{ ++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__); ++ ++ s_imServiceHandler = imServiceHandler; ++} ++ ++void LibpurpleAdapter::assignPrefsHandler(LibpurplePrefsHandler* PrefsHandler) ++{ ++ s_PrefsHandler = PrefsHandler; ++} ++ ++/* ++ * Value destroy function for s_AuthorizeRequests ++ */ ++static void deleteAuthRequest(void* obj) ++{ ++ AuthRequest *aa = (AuthRequest *)obj; ++ MojLogInfo(IMServiceApp::s_log, _T("deleteAuthRequest: deleting auth request object. account: %s, remote_user: %s"), aa->account->username, aa->remote_user); ++ g_free(aa->remote_user); ++ g_free(aa->alias); ++ g_free(aa); ++} ++ ++void LibpurpleAdapter::init() ++{ ++ //TODO: replace the NULLs with real functions to prevent memory leaks ++ /* g_hash_table_new_full() Creates a new GHashTable like g_hash_table_new() and allows one to specify functions to free the memory allocated for the key and value that get called when removing the entry from the GHashTable. ++ * Parameters: ++ hash_func: a function to create a hash value from a key. ++ key_equal_func: a function to check two keys for equality. ++ key_destroy_func: a function to free the memory allocated for the key used when removing the entry from the GHashTable or NULL if you don't want to supply such a function. ++ value_destroy_func: a function to free the memory allocated for the value used when removing the entry from the GHashTable or NULL if you don't want to supply such a function. ++ Returns: a new GHashTable. ++ */ ++ s_onlineAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); ++ s_pendingAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_accountLoginTimers = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_ipAddressesBoundTo = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free); ++ s_connectionTypeData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free); ++ s_AccountIdsData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free); ++ s_offlineAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_AuthorizeRequests = g_hash_table_new_full(g_str_hash, g_str_equal, free, deleteAuthRequest); ++ s_accountBuddyListTimers = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ ++ initializeLibpurple(); ++} +diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp +--- imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp 2011-03-25 10:45:40.752552999 -0600 ++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp 2011-03-27 09:10:06.196553000 -0600 +@@ -139,6 +139,9 @@ static char* getMojoFriendlyServiceName + else if (strcmp(templateId, CAPABILITY_LIVE) == 0){ + return (char*)SERVICENAME_LIVE; + } ++ else if (strcmp(templateId, CAPABILITY_WLM) == 0){ ++ return (char*)SERVICENAME_WLM; ++ } + else if (strcmp(templateId, CAPABILITY_MYSPACE) == 0){ + return (char*)SERVICENAME_MYSPACE; + } +@@ -627,6 +630,7 @@ static char* getServiceNameFromPrplProto + MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId")); + return strdup("type_default"); + } ++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId, prplProtocolId = %s"), prplProtocolId); + char* stringChopper = prplProtocolId; + stringChopper += strlen("prpl-"); + GString* serviceName = g_string_new(stringChopper); +@@ -643,6 +647,12 @@ static char* getServiceNameFromPrplProto + g_string_free(serviceName, true); + serviceName = g_string_new("live"); + } ++ if (strcmp(serviceName->str, "msn-pecan") == 0) ++ { ++ // Special case for wlm where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("wlm"); ++ } + if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0) + { + // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" +@@ -667,6 +677,7 @@ static char* getServiceNameFromPrplProto + g_string_free(serviceName, true); + serviceName = g_string_new("gadu"); + } ++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId, serviceName = %s"),serviceName->str); + char* serviceNameToReturn = NULL; + // asprintf allocates appropriate-sized buffer + asprintf(&serviceNameToReturn, "type_%s", serviceName->str); +diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp~ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp~ +--- imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp~ 1969-12-31 18:00:00.000000000 -0600 ++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp~ 2011-03-27 09:10:06.180553000 -0600 +@@ -0,0 +1,821 @@ ++/* ++ * LibpurpleAdapterPrefs.cpp ++ * ++ */ ++ ++#include "LibpurpleAdapterPrefs.h" ++#include "LibpurpleAdapter.h" ++#include "PalmImCommon.h" ++#include "IMServiceApp.h" ++#include "db/MojDbServiceClient.h" ++ ++/* ++ * list of account preferences ++ */ ++static GHashTable* s_AccountAliases = NULL; ++static GHashTable* s_AccountAvatars = NULL; ++static GHashTable* s_AccountBadCert = NULL; ++static GHashTable* s_ServerName = NULL; ++static GHashTable* s_ServerPort = NULL; ++static GHashTable* s_ServerTLS = NULL; ++static GHashTable* s_LoginTimeOut = NULL; ++static GHashTable* s_BuddyListTimeOut = NULL; ++ ++static GHashTable* s_SametimeHideID = NULL; ++static GHashTable* s_SIPEServerProxy = NULL; ++static GHashTable* s_SIPEUserAgent = NULL; ++static GHashTable* s_XFireVersion = NULL; ++static GHashTable* s_SIPEServerLogin = NULL; ++static GHashTable* s_JabberResource = NULL; ++ ++LibpurpleAdapterPrefs::LibpurpleAdapterPrefs(MojService* service) ++: m_findCommandSlot(this, &LibpurpleAdapterPrefs::findCommandResult), ++ m_service(service), ++ m_dbClient(service) ++{ ++ ++} ++ ++LibpurpleAdapterPrefs::~LibpurpleAdapterPrefs() { ++ ++} ++ ++inline const char * const BoolToString(bool b) ++{ ++ return b ? "true" : "false"; ++} ++ ++inline bool StringToBool(char* c) ++{ ++ if(c == NULL) ++ { ++ return false; ++ } ++ if (strcmp(c, "false") == 0) ++ { ++ return false; ++ } ++ if (strcmp(c, "true") == 0) ++ { ++ return true; ++ } ++ return false; ++} ++ ++/* ++ * The messaging service expects the username to be in the username@domain.com format, whereas the AIM prpl uses the username only ++ * Free the returned string when you're done with it ++ */ ++static char* getMojoFriendlyUsername(const char* username, const char* serviceName) ++{ ++ if (!username || !serviceName) ++ { ++ return strdup(""); ++ } ++ GString* mojoFriendlyUsername = g_string_new(username); ++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && strchr(username, '@') == NULL) ++ { ++ g_string_append(mojoFriendlyUsername, "@aol.com"); ++ } ++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0) ++ { ++ char* resource = (char*)memchr(username, '/', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0) ++ { ++ if (strstr(username, "/") != NULL) ++ { ++ //If jabber resource is blank remove / ++ char *resource = (char*)memchr(username, '/', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ } ++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0) ++ { ++ char *resource = (char*)memchr(username, ',', strlen(username)); ++ if (resource != NULL) ++ { ++ int charsToKeep = resource - username; ++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1); ++ } ++ } ++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str); ++ g_string_free(mojoFriendlyUsername, true); ++ return mojoFriendlyUsernameToReturn; ++} ++ ++static char* getMojoFriendlyServiceName (const char* templateId) ++{ ++ if (strcmp(templateId, CAPABILITY_AIM) == 0) { ++ return (char*)SERVICENAME_AIM; ++ } ++ else if (strcmp(templateId, CAPABILITY_FACEBOOK) == 0){ ++ return (char*)SERVICENAME_FACEBOOK; ++ } ++ else if (strcmp(templateId, CAPABILITY_GTALK) == 0){ ++ return (char*)SERVICENAME_GTALK; ++ } ++ else if (strcmp(templateId, CAPABILITY_GADU) == 0){ ++ return (char*)SERVICENAME_GADU; ++ } ++ else if (strcmp(templateId, CAPABILITY_GROUPWISE) == 0){ ++ return (char*)SERVICENAME_GROUPWISE; ++ } ++ else if (strcmp(templateId, CAPABILITY_ICQ) == 0){ ++ return (char*)SERVICENAME_ICQ; ++ } ++ else if (strcmp(templateId, CAPABILITY_JABBER) == 0){ ++ return (char*)SERVICENAME_JABBER; ++ } ++ else if (strcmp(templateId, CAPABILITY_LIVE) == 0){ ++ return (char*)SERVICENAME_LIVE; ++ } ++ else if (strcmp(templateId, CAPABILITY_WLM) == 0){ ++ return (char*)SERVICENAME_WLM; ++ } ++ else if (strcmp(templateId, CAPABILITY_MYSPACE) == 0){ ++ return (char*)SERVICENAME_MYSPACE; ++ } ++ else if (strcmp(templateId, CAPABILITY_QQ) == 0){ ++ return (char*)SERVICENAME_QQ; ++ } ++ else if (strcmp(templateId, CAPABILITY_SAMETIME) == 0){ ++ return (char*)SERVICENAME_SAMETIME; ++ } ++ else if (strcmp(templateId, CAPABILITY_SIPE) == 0){ ++ return (char*)SERVICENAME_SIPE; ++ } ++ else if (strcmp(templateId, CAPABILITY_XFIRE) == 0){ ++ return (char*)SERVICENAME_XFIRE; ++ } ++ else if (strcmp(templateId, CAPABILITY_YAHOO) == 0){ ++ return (char*)SERVICENAME_YAHOO; ++ } ++ ++ return (char*)""; ++} ++ ++//Preferences methods ++MojErr LibpurpleAdapterPrefs::LoadAccountPreferences(const char* templateId, const char* UserName) ++{ ++ MojString m_templateId; ++ MojString m_UserName; ++ m_templateId.assign(templateId); ++ m_UserName.assign(UserName); ++ ++ // Create the query ++ MojDbQuery query; ++ MojErr err; ++ ++ //If the template is empty load everything ++ if (!strcmp(m_templateId, "") == 0) ++ { ++ err = query.where("templateId", MojDbQuery::OpEq, m_templateId); ++ MojErrCheck(err); ++ ++ err = query.where("UserName", MojDbQuery::OpEq, m_UserName); ++ MojErrCheck(err); ++ } ++ ++ // add our kind to the object ++ err = query.from("org.webosinternals.messaging.prefs:1"); ++ MojErrCheck(err); ++ ++ // log the query ++ MojObject queryObject; ++ err = query.toObject(queryObject); ++ MojErrCheck(err); ++ ++ MojString json; ++ queryObject.toJson(json); ++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query: %s"), json.data()); ++ ++ if (err) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query failed: error %d - %s"), err, error.data()); ++ return err; ++ } ++ ++ // query DB ++ err = m_dbClient.find(this->m_findCommandSlot, query, true); ++ ++ if (err) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query find failed: error %d - %s"), err, error.data()); ++ return err; ++ } ++ ++ return MojErrNone; ++} ++ ++/* ++ * Callback for the prefs query ++ */ ++MojErr LibpurpleAdapterPrefs::findCommandResult(MojObject& result, MojErr findErr) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ ++ if (findErr) { ++ MojString error; ++ MojErrToString(findErr, error); ++ MojLogError(IMServiceApp::s_log, _T("findCommandResult failed: error %d - %s"), findErr, error.data()); ++ ++ } else { ++ // get the results ++ MojString mojStringJson; ++ result.toJson(mojStringJson); ++ MojLogError(IMServiceApp::s_log, _T("findCommandResult result: %s"), mojStringJson.data()); ++ ++ MojObject results; ++ ++ // get results array in "results" ++ result.get(_T("results"), results); ++ ++ //Are there results? ++ if (!results.empty()) ++ { ++ bool BadCert = false; ++ bool EnableAlias = false; ++ bool EnableAvatar = false; ++ bool ServerTLS = false; ++ MojString Preferences; ++ MojString ServerName; ++ MojString ServerPort; ++ MojString LoginTimeOut; ++ MojString BuddyListTimeOut; ++ MojString UserName; ++ MojString templateId; ++ MojObject m_ServerName; ++ MojObject m_ServerPort; ++ MojObject m_UserName; ++ MojObject m_templateId; ++ MojObject accountPreferences; ++ ++ bool SametimeHideID = false; ++ bool SIPEServerProxy = false; ++ MojString SIPEUserAgent; ++ MojString XFireVersion; ++ MojString SIPEServerLogin; ++ MojString JabberResource; ++ MojObject m_SIPEUserAgent; ++ MojObject m_XFireVersion; ++ MojObject m_SIPEServerLogin; ++ MojObject m_JabberResource; ++ ++ MojObject::ConstArrayIterator PrefslItr = results.arrayBegin(); ++ while (PrefslItr != results.arrayEnd()) { ++ accountPreferences = *PrefslItr; ++ ++ accountPreferences.toJson(Preferences); ++ MojLogError(IMServiceApp::s_log, _T("findCommandResult 'Preferences' result: %s"), Preferences.data()); ++ ++ //Get the results ++ accountPreferences.get ("BadCert", BadCert); ++ accountPreferences.get ("EnableAlias", EnableAlias); ++ accountPreferences.get ("EnableAvatar", EnableAvatar); ++ accountPreferences.getRequired ("ServerName", ServerName); ++ accountPreferences.getRequired ("ServerPort", ServerPort); ++ accountPreferences.getRequired ("LoginTimeOut", LoginTimeOut); ++ accountPreferences.getRequired ("BuddyListTimeOut", BuddyListTimeOut); ++ accountPreferences.get ("ServerTLS", ServerTLS); ++ accountPreferences.getRequired ("UserName", UserName); ++ accountPreferences.getRequired ("templateId", templateId); ++ ++ //Get non standard prefs ++ if (0 == templateId.compare(CAPABILITY_SAMETIME)) ++ { ++ accountPreferences.get ("SametimeHideID", SametimeHideID); ++ } ++ if (0 == templateId.compare(CAPABILITY_SIPE)) ++ { ++ accountPreferences.get ("SIPEServerProxy", SIPEServerProxy); ++ accountPreferences.getRequired ("SIPEUserAgent", SIPEUserAgent); ++ accountPreferences.getRequired ("SIPEServerLogin", SIPEServerLogin); ++ } ++ if (0 == templateId.compare(CAPABILITY_JABBER)) ++ { ++ accountPreferences.getRequired ("JabberResource", JabberResource); ++ } ++ if (0 == templateId.compare(CAPABILITY_XFIRE)) ++ { ++ accountPreferences.getRequired ("XFireversion", XFireVersion); ++ } ++ ++ //Set AccountID ++ char *accountKey = NULL; ++ const char* ServiceType = getMojoFriendlyServiceName (templateId.data()); ++ const char* mojUserName = getMojoFriendlyUsername ((const char*)UserName.data(),ServiceType); ++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType); ++ ++ //Remove any entry in the hash tables ++ if (g_hash_table_lookup(s_AccountAliases, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_AccountAliases, accountKey); ++ } ++ if (g_hash_table_lookup(s_AccountAvatars, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_AccountAvatars, accountKey); ++ } ++ if (g_hash_table_lookup(s_AccountBadCert, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_AccountBadCert, accountKey); ++ } ++ if (g_hash_table_lookup(s_ServerName, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_ServerName, accountKey); ++ } ++ if (g_hash_table_lookup(s_ServerPort, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_ServerPort, accountKey); ++ } ++ if (g_hash_table_lookup(s_LoginTimeOut, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_LoginTimeOut, accountKey); ++ } ++ if (g_hash_table_lookup(s_BuddyListTimeOut, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_BuddyListTimeOut, accountKey); ++ } ++ if (g_hash_table_lookup(s_ServerTLS, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_ServerTLS, accountKey); ++ } ++ if (g_hash_table_lookup(s_SametimeHideID, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_SametimeHideID, accountKey); ++ } ++ if (g_hash_table_lookup(s_SIPEServerProxy, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_SIPEServerProxy, accountKey); ++ } ++ if (g_hash_table_lookup(s_SIPEUserAgent, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_SIPEUserAgent, accountKey); ++ } ++ if (g_hash_table_lookup(s_SIPEServerLogin, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_SIPEServerLogin, accountKey); ++ } ++ if (g_hash_table_lookup(s_XFireVersion, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_XFireVersion, accountKey); ++ } ++ if (g_hash_table_lookup(s_JabberResource, accountKey) != NULL) ++ { ++ g_hash_table_remove(s_JabberResource, accountKey); ++ } ++ ++ //Show results ++ //findCommandResult result: {"results":[{"BadCert":false,"EnableAlias":true,"EnableAvatar":true,"ServerName":"messenger.hotmail.com","ServerPort":"1863","UserName":"SOMEONE@HOTMAIL.COM","_id":"++HRyXbD3r+pt8M2","_kind":"org.webosinternals.messaging.prefs:1","_rev":41856,"_sync":true,"templateId":"ORG.WEBOSINTERNALS.MESSAGING.LIVE"}],"returnValue":true} ++ if (BadCert) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' BadCert: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' BadCert: false")); ++ } ++ if (EnableAlias) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAlias: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAlias: false")); ++ } ++ if (EnableAvatar) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAvatar: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAvatar: false")); ++ } ++ if (ServerTLS) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerTLS: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerTLS: false")); ++ } ++ if (0 == templateId.compare(CAPABILITY_SAMETIME)) ++ { ++ //Show results ++ if (SametimeHideID) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SametimeHideID: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SametimeHideID: false")); ++ } ++ //Add the results to the hash tables ++ g_hash_table_insert(s_SametimeHideID, accountKey, strdup(BoolToString(SametimeHideID))); ++ } ++ if (0 == templateId.compare(CAPABILITY_SIPE)) ++ { ++ //Show results ++ if (SIPEServerProxy) ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerProxy: true")); ++ } ++ else ++ { ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerProxy: false")); ++ } ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEUserAgent: %s"), SIPEUserAgent.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerLogin: %s"), SIPEServerLogin.data()); ++ ++ //Add the results to the hash tables ++ g_hash_table_insert(s_SIPEServerLogin, accountKey, strdup(SIPEServerLogin.data())); ++ g_hash_table_insert(s_SIPEUserAgent, accountKey, strdup(SIPEUserAgent.data())); ++ g_hash_table_insert(s_SIPEServerProxy, accountKey, strdup(BoolToString(SIPEServerProxy))); ++ } ++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerName: %s"), ServerName.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerPort: %s"), ServerPort.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' LoginTimeOut: %s"), LoginTimeOut.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' BuddyListTimeOut: %s"), BuddyListTimeOut.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' UserName: %s"), UserName.data()); ++ MojLogError(IMServiceApp::s_log, _T("'Preference' templateId: %s"), templateId.data()); ++ ++ if (0 == templateId.compare(CAPABILITY_XFIRE)) ++ { ++ //Show results ++ MojLogError(IMServiceApp::s_log, _T("'Preference' XFireVersion: %s"), XFireVersion.data()); ++ //Add the results to the hash tables ++ g_hash_table_insert(s_XFireVersion, accountKey, strdup(XFireVersion.data())); ++ } ++ if (0 == templateId.compare(CAPABILITY_JABBER)) ++ { ++ //Show results ++ MojLogError(IMServiceApp::s_log, _T("'Preference' JabberResource: %s"), JabberResource.data()); ++ //Add the results to the hash tables ++ g_hash_table_insert(s_JabberResource, accountKey, strdup(JabberResource.data())); ++ } ++ ++ //Add the results to the hash tables ++ g_hash_table_insert(s_AccountAliases, accountKey, strdup(BoolToString(EnableAlias))); ++ g_hash_table_insert(s_AccountAvatars, accountKey, strdup(BoolToString(EnableAvatar))); ++ g_hash_table_insert(s_AccountBadCert, accountKey, strdup(BoolToString(BadCert))); ++ g_hash_table_insert(s_ServerName, accountKey, strdup(ServerName.data())); ++ g_hash_table_insert(s_ServerPort, accountKey, strdup(ServerPort.data())); ++ g_hash_table_insert(s_ServerTLS, accountKey, strdup(BoolToString(ServerTLS))); ++ g_hash_table_insert(s_LoginTimeOut, accountKey, strdup(LoginTimeOut.data())); ++ g_hash_table_insert(s_BuddyListTimeOut, accountKey, strdup(BuddyListTimeOut.data())); ++ ++ //Loop ++ PrefslItr++; ++ } ++ } ++ } ++ ++ return MojErrNone; ++} ++ ++MojErr LibpurpleAdapterPrefs::GetServerPreferences(const char* templateId, const char* UserName, char*& ServerName, char*& ServerPort, bool& ServerTLS, bool& BadCert) ++{ ++ //Set AccountID ++ char *accountKey = NULL; ++ const char* ServiceType = getMojoFriendlyServiceName (templateId); ++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType); ++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType); ++ ++ //Get Server Name ++ ServerName = (char*)g_hash_table_lookup(s_ServerName, accountKey); ++ if (ServerName == NULL) ++ { ++ ServerName = (char*)""; ++ } ++ ++ //Get Server Port ++ ServerPort = (char*)g_hash_table_lookup(s_ServerPort, accountKey); ++ if (ServerPort == NULL) ++ { ++ ServerPort = (char*)"0"; ++ } ++ ++ //Get Server TLS ++ ServerTLS = StringToBool((char*)g_hash_table_lookup(s_ServerTLS, accountKey)); ++ ++ //Get Server Bad Cert Setting ++ BadCert = StringToBool((char*)g_hash_table_lookup(s_AccountBadCert, accountKey)); ++ ++ return MojErrNone; ++} ++ ++char* LibpurpleAdapterPrefs::GetStringPreference(const char* Preference, const char* templateId, const char* UserName) ++{MojLogError(IMServiceApp::s_log, _T("In GetStringPrefs: %s"),Preference); ++ // Set AccountID ++ char *accountKey = NULL; ++ const char* ServiceType = getMojoFriendlyServiceName (templateId); ++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType); ++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType); ++ ++ char* Setting = NULL; ++ ++ if (strcmp(Preference, "SIPEUserAgent") == 0) ++ { ++ //Get SIPEUserAgent Setting ++ Setting = (char*)g_hash_table_lookup(s_SIPEUserAgent, accountKey); ++ } ++ if (strcmp(Preference, "XFireVersion") == 0) ++ { ++ //Get XFireVersion Setting ++ Setting = (char*)g_hash_table_lookup(s_XFireVersion, accountKey); ++ ++ //If Xfire version is blank default to 132 ++ if (Setting == NULL) ++ { ++ Setting = (char*)"132"; ++ } ++ } ++ if (strcmp(Preference, "SIPEServerLogin") == 0) ++ { ++ //Get SIPEServerLogin Setting ++ Setting = (char*)g_hash_table_lookup(s_SIPEServerLogin, accountKey); ++ } ++ if (strcmp(Preference, "JabberResource") == 0) ++ { ++ //Get JabberResource Setting ++ Setting = (char*)g_hash_table_lookup(s_JabberResource, accountKey); ++ } ++ ++ if (strcmp(Preference, "LoginTimeOut") == 0) ++ { ++ //Get Server Timeout ++ Setting = (char*)g_hash_table_lookup(s_LoginTimeOut, accountKey); ++ if (Setting == NULL) ++ { ++ Setting = (char*)"45"; ++ } ++ } ++ ++ if (strcmp(Preference, "BuddyListTimeOut") == 0) ++ { ++ //Get BuddyList Timeout ++ Setting = (char*)g_hash_table_lookup(s_BuddyListTimeOut, accountKey); ++ if (Setting == NULL) ++ { ++ Setting = (char*)"10"; ++ } ++ } ++ ++ if (Setting == NULL) ++ { ++ return (char*)""; ++ } ++ else ++ { ++ return Setting; ++ } ++} ++ ++bool LibpurpleAdapterPrefs::GetBoolPreference(const char* Preference, const char* templateId, const char* UserName) ++{ ++ //Set AccountID ++ char *accountKey = NULL; ++ const char* ServiceType = getMojoFriendlyServiceName (templateId); ++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType); ++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType); ++ ++ bool Setting = false; ++ ++ if (strcmp(Preference, "Avatar") == 0) ++ { ++ //Get Avatar Setting ++ Setting = StringToBool((char*)g_hash_table_lookup(s_AccountAvatars, accountKey)); ++ return Setting; ++ } ++ if (strcmp(Preference, "Alias") == 0) ++ { ++ //Get Alias Setting ++ Setting = StringToBool((char*)g_hash_table_lookup(s_AccountAliases, accountKey)); ++ return Setting; ++ } ++ if (strcmp(Preference, "SametimeHideID") == 0) ++ { ++ //Get SametimeHideID Setting ++ Setting = StringToBool((char*)g_hash_table_lookup(s_SametimeHideID, accountKey)); ++ return Setting; ++ } ++ if (strcmp(Preference, "SIPEServerProxy") == 0) ++ { ++ //Get SIPEServerProxy Setting ++ Setting = StringToBool((char*)g_hash_table_lookup(s_SIPEServerProxy, accountKey)); ++ return Setting; ++ } ++ return false; ++} ++ ++/* ++ * Given the prpl-specific protocol_id, it will return mojo-friendly serviceName (e.g. given "prpl-aim", it will return "type_aim") ++ * Free the returned string when you're done with it ++ */ ++static char* getServiceNameFromPrplProtocolId(char* prplProtocolId) ++{ ++ if (!prplProtocolId) ++ { ++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId")); ++ return strdup("type_default"); ++ } ++ char* stringChopper = prplProtocolId; ++ stringChopper += strlen("prpl-"); ++ GString* serviceName = g_string_new(stringChopper); ++ ++ if (strcmp(serviceName->str, "jabber") == 0) ++ { ++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("gtalk"); ++ } ++ if (strcmp(serviceName->str, "msn") == 0) ++ { ++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("live"); ++ } ++ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0) ++ { ++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("facebook"); ++ } ++ if (strcmp(serviceName->str, "meanwhile") == 0) ++ { ++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("sametime"); ++ } ++ if (strcmp(serviceName->str, "novell") == 0) ++ { ++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("groupwise"); ++ } ++ if (strcmp(serviceName->str, "gg") == 0) ++ { ++ // Special case for gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg" ++ g_string_free(serviceName, true); ++ serviceName = g_string_new("gadu"); ++ } ++ char* serviceNameToReturn = NULL; ++ // asprintf allocates appropriate-sized buffer ++ asprintf(&serviceNameToReturn, "type_%s", serviceName->str); ++ g_string_free(serviceName, true); ++ return serviceNameToReturn; ++} ++ ++void LibpurpleAdapterPrefs::setaccountprefs(MojString templateId, PurpleAccount* account) ++{ ++ //Load the preferences ++ char* ServerName; ++ char* ServerPort; ++ bool ServerTLS = false; ++ bool BadCert = false; ++ char* SIPEUserAgent; ++ char* XFireversion; ++ bool SIPEServerProxy = false; ++ bool SametimehideID = false; ++ ++ //Load account preferences ++ char* serviceName = getServiceNameFromPrplProtocolId(account->protocol_id); ++ char* username = getMojoFriendlyUsername(account->username, serviceName); ++ GetServerPreferences((const char*)templateId.data(), (const char*)username, ServerName, ServerPort, ServerTLS, BadCert); ++ ++ //If no server blank ++ if (strcmp(ServerName, "") == 0) ++ { ++ ServerName = (char*)""; ++ } ++ //If no port quit ++ if (strcmp(ServerPort, "") == 0) ++ { ++ return; ++ } ++ ++ //Bad Cert Accept? ++ purple_prefs_remove("/purple/acceptbadcert"); ++ if (BadCert) ++ { ++ MojLogError(IMServiceApp::s_log, "Accepting Bad Certificates"); ++ purple_prefs_add_string("/purple/acceptbadcert", "true"); ++ } ++ ++ //Set account preferences ++ purple_account_set_string(account, "server", ServerName); ++ purple_account_set_string(account, "connect_server", ServerName); ++ if (0 == templateId.compare(CAPABILITY_GADU)) ++ { ++ //Set connect server ++ purple_account_set_string(account, "gg_server", ServerName); ++ purple_account_set_int(account, "server_port", atoi(ServerPort)); ++ } ++ purple_account_set_int(account, "port", atoi(ServerPort)); ++ purple_account_set_bool(account, "require_tls", ServerTLS); ++ ++ if (ServerTLS) ++ { ++ purple_account_set_string(account, "transport", "tls"); ++ } ++ else ++ { ++ purple_account_set_string(account, "transport", "auto"); ++ } ++ if (0 == templateId.compare(CAPABILITY_QQ)) ++ { ++ purple_account_set_string(account, "client_version", "qq2008"); ++ ++ //Set server in servername:port format for QQ ++ char *qqServer = NULL; ++ qqServer = (char *)calloc(strlen(ServerName) + strlen(ServerPort) + 1, sizeof(char)); ++ strcat(qqServer, ServerName); ++ strcat(qqServer, ":"); ++ strcat(qqServer, ServerPort); ++ purple_account_set_string(account, "server", qqServer); ++ } ++ if (0 == templateId.compare(CAPABILITY_FACEBOOK)) ++ { ++ //Don't load chat history ++ purple_account_set_bool(account, "facebook_show_history", false); ++ } ++ if (0 == templateId.compare(CAPABILITY_XFIRE)) ++ { ++ XFireversion = GetStringPreference("XFireVersion", templateId, username); ++ purple_account_set_int(account, "version", atoi(XFireversion)); ++ } ++ ++ if (0 == templateId.compare(CAPABILITY_SIPE)) ++ { ++ SIPEUserAgent = GetStringPreference("SIPEUserAgent", templateId, username); ++ SIPEServerProxy = GetBoolPreference("SIPEServerProxy", templateId, username); ++ ++ //Set ServerName ++ if(strcmp(ServerName, "") != 0) ++ { ++ char *SIPEFullServerName = NULL; ++ SIPEFullServerName = (char *)calloc(strlen(ServerName) + strlen(ServerPort) + 1, sizeof(char)); ++ strcat(SIPEFullServerName, ServerName); ++ strcat(SIPEFullServerName, ":"); ++ strcat(SIPEFullServerName, ServerPort); ++ purple_account_set_string(account, "server", SIPEFullServerName); ++ ++ if (SIPEFullServerName) ++ { ++ free(SIPEFullServerName); ++ } ++ } ++ ++ //Proxy? ++ if (!SIPEServerProxy) ++ { ++ //Disable Proxy ++ PurpleProxyInfo *info = purple_proxy_info_new(); ++ purple_proxy_info_set_type(info, PURPLE_PROXY_NONE); ++ } ++ ++ //User Agent ++ if(strcmp(SIPEUserAgent, "") != 0) ++ { ++ purple_account_set_string(account, "useragent", SIPEUserAgent); ++ } ++ } ++ if (0 == templateId.compare(CAPABILITY_SAMETIME)) ++ { ++ SametimehideID = GetBoolPreference("SametimeHideID", templateId, username); ++ purple_account_set_bool(account, "fake_client_id", SametimehideID); ++ } ++} ++ ++void LibpurpleAdapterPrefs::init() ++{ ++ //Initialise hash tables ++ s_AccountAliases = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_AccountAvatars = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_AccountBadCert = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_ServerName = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_ServerPort = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_ServerTLS = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_LoginTimeOut = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_BuddyListTimeOut = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ ++ s_SametimeHideID = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_SIPEServerProxy = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_SIPEUserAgent = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_XFireVersion = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_SIPEServerLogin = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ s_JabberResource = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); ++ ++ LibpurpleAdapter::assignPrefsHandler(this); ++} +diff -rupN imlibpurpleservice-1.0/src/OnEnabledHandler.cpp imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp +--- imlibpurpleservice-1.0/src/OnEnabledHandler.cpp 2011-03-25 10:45:40.704552999 -0600 ++++ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp 2011-03-27 09:10:06.136552999 -0600 +@@ -162,6 +162,8 @@ MojErr OnEnabledHandler::getDefaultServi + serviceName.assign(SERVICENAME_JABBER); + else if (templateId == "org.webosinternals.messaging.live") + serviceName.assign(SERVICENAME_LIVE); ++ else if (templateId == "org.webosinternals.messaging.wlm") ++ serviceName.assign(SERVICENAME_WLM); + else if (templateId == "org.webosinternals.messaging.myspace") + serviceName.assign(SERVICENAME_MYSPACE); + else if (templateId == "org.webosinternals.messaging.qq") +@@ -198,6 +200,8 @@ void OnEnabledHandler::getServiceNameFro + serviceName.assign(SERVICENAME_JABBER); + else if (m_capabilityProviderId == CAPABILITY_LIVE) + serviceName.assign(SERVICENAME_LIVE); ++ else if (m_capabilityProviderId == CAPABILITY_WLM) ++ serviceName.assign(SERVICENAME_WLM); + else if (m_capabilityProviderId == CAPABILITY_MYSPACE) + serviceName.assign(SERVICENAME_MYSPACE); + else if (m_capabilityProviderId == CAPABILITY_QQ) +diff -rupN imlibpurpleservice-1.0/src/OnEnabledHandler.cpp~ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp~ +--- imlibpurpleservice-1.0/src/OnEnabledHandler.cpp~ 1969-12-31 18:00:00.000000000 -0600 ++++ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp~ 2011-03-27 09:10:06.148553000 -0600 +@@ -0,0 +1,434 @@ ++/* ++ * OnEnabledHandler.cpp ++ * ++ * Copyright 2010 Palm, Inc. All rights reserved. ++ * ++ * This program is free software and licensed under the terms of the GNU ++ * General Public License Version 2 as published by the Free ++ * Software Foundation; ++ * ++ * 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, ++ * Version 2 along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- ++ * 1301, USA ++ * ++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM ++ * Transport service for use on a mobile device. ++ * ++ * OnEnabledHandler class handles enabling and disabling the IM account ++ */ ++ ++#include "OnEnabledHandler.h" ++#include "db/MojDbQuery.h" ++#include "IMServiceApp.h" ++#include "IMDefines.h" ++ ++OnEnabledHandler::OnEnabledHandler(MojService* service) ++: m_getAccountInfoSlot(this, &OnEnabledHandler::getAccountInfoResult), ++ m_addImLoginStateSlot(this, &OnEnabledHandler::addImLoginStateResult), ++ m_deleteImLoginStateSlot(this, &OnEnabledHandler::deleteImLoginStateResult), ++ m_deleteImMessagesSlot(this, &OnEnabledHandler::deleteImMessagesResult), ++ m_deleteImCommandsSlot(this, &OnEnabledHandler::deleteImCommandsResult), ++ m_deleteContactsSlot(this, &OnEnabledHandler::deleteContactsResult), ++ m_deleteImBuddyStatusSlot(this, &OnEnabledHandler::deleteImBuddyStatusResult), ++ m_service(service), ++ m_dbClient(service, MojDbServiceDefs::ServiceName), ++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName), ++ m_enable(false) ++{ ++} ++ ++OnEnabledHandler::~OnEnabledHandler() ++{ ++} ++ ++/* ++ * 1. get account details from accountservices or db? ++ */ ++MojErr OnEnabledHandler::start(const MojObject& payload) ++{ ++ ++ IMServiceHandler::logMojObjectJsonString(_T("OnEnabledHandler payload: %s"), payload); ++ ++ MojString accountId; ++ MojErr err = payload.getRequired(_T("accountId"), accountId); ++ if (err != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler accountId missing so bailing. error %d"), err); ++ return err; ++ } ++ ++ err = payload.getRequired(_T("enabled"), m_enable); ++ if (err != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler enabled missing, assuming 'false'")); ++ m_enable = false; ++ } ++ ++ err = payload.getRequired(_T("capabilityProviderId"), m_capabilityProviderId); ++ if (err != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler capabilityProviderId missing so bailing. error %d"), err); ++ return err; ++ } ++ ++ MojRefCountedPtr<MojServiceRequest> req; ++ err = m_service->createRequest(req); ++ if (err != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::start createRequest failed. error %d"), err); ++ } else { ++ MojObject params; ++ params.put(_T("accountId"), accountId); ++ err = req->send(m_getAccountInfoSlot, "com.palm.service.accounts", "getAccountInfo", params, 1); ++ if (err) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::start: getAccountInfo id %s failed. error %d"), accountId.data(), err); ++ } ++ } ++ ++ return MojErrNone; ++} ++ ++/* ++ */ ++MojErr OnEnabledHandler::getAccountInfoResult(MojObject& payload, MojErr resultErr) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ IMServiceHandler::logMojObjectJsonString(_T("OnEnabledHandler::getAccountInfoResult payload: %s"), payload); ++ ++ if (resultErr != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler getAccountInfo result error %d"), resultErr); ++ return resultErr; ++ } ++ ++ MojObject result; ++ MojErr err = payload.getRequired("result", result); ++ if (err != MojErrNone || result.empty()) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult result empty or error %d"), err); ++ return err; ++ } ++ ++ MojString accountId; ++ err = result.getRequired("_id", accountId); ++ if (err != MojErrNone || accountId.empty()) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult accountId empty or error %d"), err); ++ return err; ++ } ++ ++ MojString username; ++ err = result.getRequired("username", username); ++ if (err != MojErrNone || username.empty()) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult username empty or error %d"), err); ++ return err; ++ } ++ ++ MojString serviceName; ++ getServiceNameFromCapabilityId(serviceName); ++ if (serviceName.empty()) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult serviceName empty")); ++ return err; ++ } ++ ++ if (m_enable) { ++ err = accountEnabled(accountId, serviceName, username); ++ } else { ++ err = accountDisabled(accountId, serviceName, username); ++ } ++ ++ return err; ++} ++ ++MojErr OnEnabledHandler::getDefaultServiceName(const MojObject& accountResult, MojString& serviceName) ++{ ++ MojString templateId; ++ MojErr err = accountResult.getRequired("templateId", templateId); ++ if (err != MojErrNone) { ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler templateId empty or error %d"), err); ++ } else { ++ if (templateId == "org.webosinternals.messaging.aol.aim") ++ serviceName.assign(SERVICENAME_AIM); ++ else if (templateId == "org.webosinternals.messaging.facebook") ++ serviceName.assign(SERVICENAME_FACEBOOK); ++ else if (templateId == "org.webosinternals.messaging.google.talk") ++ serviceName.assign(SERVICENAME_GTALK); ++ else if (templateId == "org.webosinternals.messaging.gadu") ++ serviceName.assign(SERVICENAME_GADU); ++ else if (templateId == "org.webosinternals.messaging.groupwise") ++ serviceName.assign(SERVICENAME_GROUPWISE); ++ else if (templateId == "org.webosinternals.messaging.icq") ++ serviceName.assign(SERVICENAME_ICQ); ++ else if (templateId == "org.webosinternals.messaging.jabber") ++ serviceName.assign(SERVICENAME_JABBER); ++ else if (templateId == "org.webosinternals.messaging.live") ++ serviceName.assign(SERVICENAME_LIVE); ++ else if (templateId == "org.webosinternals.messaging.myspace") ++ serviceName.assign(SERVICENAME_MYSPACE); ++ else if (templateId == "org.webosinternals.messaging.qq") ++ serviceName.assign(SERVICENAME_QQ); ++ else if (templateId == "org.webosinternals.messaging.sametime") ++ serviceName.assign(SERVICENAME_SAMETIME); ++ else if (templateId == "org.webosinternals.messaging.sipe") ++ serviceName.assign(SERVICENAME_SIPE); ++ else if (templateId == "org.webosinternals.messaging.xfire") ++ serviceName.assign(SERVICENAME_XFIRE); ++ else if (templateId == "org.webosinternals.messaging.yahoo") ++ serviceName.assign(SERVICENAME_YAHOO); ++ else ++ err = MojErrNotImpl; ++ } ++ return err; ++} ++ ++void OnEnabledHandler::getServiceNameFromCapabilityId(MojString& serviceName) ++{ ++ if (m_capabilityProviderId == CAPABILITY_AIM) ++ serviceName.assign(SERVICENAME_AIM); ++ else if (m_capabilityProviderId == CAPABILITY_FACEBOOK) ++ serviceName.assign(SERVICENAME_FACEBOOK); ++ else if (m_capabilityProviderId == CAPABILITY_GTALK) ++ serviceName.assign(SERVICENAME_GTALK); ++ else if (m_capabilityProviderId == CAPABILITY_GADU) ++ serviceName.assign(SERVICENAME_GADU); ++ else if (m_capabilityProviderId == CAPABILITY_GROUPWISE) ++ serviceName.assign(SERVICENAME_GROUPWISE); ++ else if (m_capabilityProviderId == CAPABILITY_ICQ) ++ serviceName.assign(SERVICENAME_ICQ); ++ else if (m_capabilityProviderId == CAPABILITY_JABBER) ++ serviceName.assign(SERVICENAME_JABBER); ++ else if (m_capabilityProviderId == CAPABILITY_LIVE) ++ serviceName.assign(SERVICENAME_LIVE); ++ else if (m_capabilityProviderId == CAPABILITY_MYSPACE) ++ serviceName.assign(SERVICENAME_MYSPACE); ++ else if (m_capabilityProviderId == CAPABILITY_QQ) ++ serviceName.assign(SERVICENAME_QQ); ++ else if (m_capabilityProviderId == CAPABILITY_SAMETIME) ++ serviceName.assign(SERVICENAME_SAMETIME); ++ else if (m_capabilityProviderId == CAPABILITY_SIPE) ++ serviceName.assign(SERVICENAME_SIPE); ++ else if (m_capabilityProviderId == CAPABILITY_XFIRE) ++ serviceName.assign(SERVICENAME_XFIRE); ++ else if (m_capabilityProviderId == CAPABILITY_YAHOO) ++ serviceName.assign(SERVICENAME_YAHOO); ++} ++ ++/* ++ * Example messaging capability provider ++ * "capabilityProviders": [{ ++ "_id": "2+MR", ++ "capability": "MESSAGING", ++ "id": "com.palm.google.talk", ++ "capabilitySubtype": "IM", ++ "loc_name": "Google Talk", ++ "icon": { ++ "loc_32x32": "/usr/palm/public/accounts/com.palm.google/images/gtalk32x32.png", ++ "loc_48x48": "/usr/palm/public/accounts/com.palm.google/images/gtalk48x48.png", ++ "splitter": "/usr/palm/public/accounts/com.palm.google/images/gtalk_transport_splitter.png" ++ }, ++ "implementation": "palm://com.palm.imlibpurple/", ++ "onEnabled": "palm://com.palm.imlibpurple/onEnabled" ++ "serviceName":"type_aim", ++ "dbkinds": { ++ "immessage":"com.palm.immessage.libpurple:1", ++ "imcommand":"com.palm.imcommand.libpurple:1" ++ } ++ }], ++ */ ++MojErr OnEnabledHandler::getMessagingCapabilityObject(const MojObject& capabilityProviders, MojObject& messagingObj) ++{ ++ // iterate thru the capabilities array ++ MojErr err = MojErrRequiredPropNotFound; ++ MojString capability; ++ MojObject::ConstArrayIterator itr = capabilityProviders.arrayBegin(); ++ // This shouldn't happen, but check if there's nothing to do. ++ while (itr != capabilityProviders.arrayEnd()) { ++ messagingObj = *itr; ++ err = messagingObj.getRequired("capability", capability); ++ if (capability == "MESSAGING") { ++ err = MojErrNone; ++ break; ++ } ++ ++itr; ++ } ++ ++ return err; ++} ++ ++/* ++ * Enabling an IM account requires the following ++ * add com.palm.imloginstate.libpurple record ++ */ ++MojErr OnEnabledHandler::accountEnabled(const MojString& accountId, const MojString& serviceName, const MojString& username) ++{ ++ //TODO: first issue a merge in case the account already exists? ++ MojLogTrace(IMServiceApp::s_log); ++ MojLogInfo(IMServiceApp::s_log, _T("accountEnabled id=%s, serviceName=%s"), accountId.data(), serviceName.data()); ++ ++ MojObject imLoginState; ++ imLoginState.putString(_T("_kind"), IM_LOGINSTATE_KIND); ++ imLoginState.put(_T("accountId"), accountId); ++ imLoginState.put(_T("serviceName"), serviceName); ++ imLoginState.put(_T("username"), username); ++ imLoginState.putString(_T("state"), LOGIN_STATE_OFFLINE); ++ imLoginState.putInt(_T("availability"), PalmAvailability::ONLINE); //default to online so we automatically login at first ++ MojErr err = m_dbClient.put(m_addImLoginStateSlot, imLoginState); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.put() failed: error %d - %s"), err, error.data()); ++ } ++ ++ return err; ++} ++ ++/* ++ * Disabling an account means doing the following ++ * delete com.palm.imloginstate.libpurple record ++ * delete com.palm.immessage.libpurple records ++ * delete com.palm.imcommand.libpurple records ++ * delete com.palm.contact.libpurple records ++ * delete com.palm.imbuddystatus.libpurple records ++ * delete com.palm.imgroupchat.libpurple records -- groupchats not currently supported ++ * Note: the ChatThreader service takes care of removing empty chats ++ */ ++MojErr OnEnabledHandler::accountDisabled(const MojString& accountId, const MojString& serviceName, const MojString& username) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ MojLogInfo(IMServiceApp::s_log, _T("accountDisabled id=%s, serviceName=%s"), accountId.data(), serviceName.data()); ++ ++ // delete com.palm.imloginstate.libpurple record ++ MojDbQuery queryLoginState; ++ queryLoginState.from(IM_LOGINSTATE_KIND); ++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName); ++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username); ++ MojErr err = m_dbClient.del(m_deleteImLoginStateSlot, queryLoginState); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imloginstate) failed: error %d - %s"), err, error.data()); ++ } ++ ++ // delete com.palm.immessage.libpurple records ++ //TODO: need to query both from & recipient addresses. Simplify this??? ++ MojDbQuery queryMessage; ++ queryMessage.from(IM_IMMESSAGE_KIND); ++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName); ++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username); ++ err = m_dbClient.del(m_deleteImMessagesSlot, queryMessage); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(immessage) failed: error %d - %s"), err, error.data()); ++ } ++ ++ // delete com.palm.imcommand.libpurple records ++ MojDbQuery queryCommand; ++ queryCommand.from(IM_IMCOMMAND_KIND); ++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName); ++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username); ++ err = m_dbClient.del(m_deleteImMessagesSlot, queryCommand); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imcommand) failed: error %d - %s"), err, error.data()); ++ } ++ ++ // delete com.palm.contact.libpurple record ++ MojDbQuery queryContact; ++ queryContact.from(IM_CONTACT_KIND); ++ queryContact.where(_T("accountId"), MojDbQuery::OpEq, accountId); ++ err = m_dbClient.del(m_deleteContactsSlot, queryContact); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(contact) failed: error %d - %s"), err, error.data()); ++ } ++ ++ // delete com.palm.imbuddystatus.libpurple record ++ MojDbQuery queryBuddyStatus; ++ queryBuddyStatus.from(IM_BUDDYSTATUS_KIND); ++ queryBuddyStatus.where(_T("accountId"), MojDbQuery::OpEq, accountId); ++ err = m_tempdbClient.del(m_deleteImBuddyStatusSlot, queryBuddyStatus); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imbuddystatus) failed: error %d - %s"), err, error.data()); ++ } ++ ++ // now we need to tell libpurple to disconnect the account so we don't get more messages for it ++ // LoginCallbackInterface is null because we don't need to do any processing on the callback - all the DB kinds are already gone. ++ LibpurpleAdapter::logout(serviceName, username, NULL); ++ ++ return MojErrNone; ++} ++ ++MojErr OnEnabledHandler::addImLoginStateResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("addLoginStateResult failed: error %d - %s"), err, error.data()); ++ //TODO retry adding the record. Not worrying about this for now because the add would only fail in ++ // extreme conditions. ++ } ++ return err; ++} ++ ++ ++MojErr OnEnabledHandler::deleteImLoginStateResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("deleteImLoginStateResult failed: error %d - %s"), err, error.data()); ++ } ++ return err; ++} ++ ++MojErr OnEnabledHandler::deleteImMessagesResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("deleteImMessagesResult failed: error %d - %s"), err, error.data()); ++ } ++ return err; ++} ++ ++MojErr OnEnabledHandler::deleteImCommandsResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("deleteImCommandsResult failed: error %d - %s"), err, error.data()); ++ } ++ return err; ++} ++ ++MojErr OnEnabledHandler::deleteContactsResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("deleteContactsResult failed: error %d - %s"), err, error.data()); ++ } ++ return err; ++} ++ ++MojErr OnEnabledHandler::deleteImBuddyStatusResult(MojObject& payload, MojErr err) ++{ ++ MojLogTrace(IMServiceApp::s_log); ++ if (err != MojErrNone) { ++ MojString error; ++ MojErrToString(err, error); ++ MojLogCritical(IMServiceApp::s_log, _T("deleteImBuddyStatusResult failed: error %d - %s"), err, error.data()); ++ } ++ return err; ++} ++ diff --git a/3.0/Compiler Source Files/Patches/msn-pecan-patches b/3.0/Compiler Source Files/Patches/msn-pecan-patches new file mode 100644 index 0000000..78db5f6 --- a/dev/null +++ b/3.0/Compiler Source Files/Patches/msn-pecan-patches @@ -0,0 +1,495 @@ +diff -rupN msn-pecan-0.1.1/Makefile msn-pecan-0.1.1-new//Makefile +--- msn-pecan-0.1.1/Makefile 2010-08-28 10:48:09.000000000 -0500 ++++ msn-pecan-0.1.1-new//Makefile 2011-03-27 09:01:42.480553000 -0600 +@@ -17,9 +17,9 @@ GIO_LIBS := $(shell pkg-config --libs gi + + # default configuration options + CVR := y +-LIBSIREN := y +-LIBMSPACK := y +-PLUS_SOUNDS := y ++LIBSIREN := n ++LIBMSPACK := n ++PLUS_SOUNDS := n + DEBUG := y + DIRECTCONN := y + +diff -rupN msn-pecan-0.1.1/Makefile~ msn-pecan-0.1.1-new//Makefile~ +--- msn-pecan-0.1.1/Makefile~ 1969-12-31 18:00:00.000000000 -0600 ++++ msn-pecan-0.1.1-new//Makefile~ 2011-03-27 09:01:42.480553000 -0600 +@@ -0,0 +1,279 @@ ++CC := $(CROSS_COMPILE)gcc ++WINDRES := $(CROSS_COMPILE)windres ++ ++XGETTEXT := xgettext ++MSGFMT := msgfmt ++ ++PLATFORM := $(shell $(CC) -dumpmachine | cut -f 3 -d -) ++ ++PURPLE_CFLAGS := $(shell pkg-config --cflags purple) ++PURPLE_LIBS := $(shell pkg-config --libs purple) ++PURPLE_LIBDIR := $(shell pkg-config --variable=libdir purple) ++PURPLE_DATADIR := $(shell pkg-config --variable=datadir purple) ++PURPLE_PLUGINDIR := $(PURPLE_LIBDIR)/purple-2 ++ ++GIO_CFLAGS := $(shell pkg-config --cflags gio-2.0) ++GIO_LIBS := $(shell pkg-config --libs gio-2.0) ++ ++# default configuration options ++CVR := n ++LIBSIREN := n ++LIBMSPACK := n ++PLUS_SOUNDS := n ++DEBUG := n ++DIRECTCONN := y ++ ++CFLAGS := -O2 ++ ++EXTRA_WARNINGS := -Wall -Wextra -Wformat-nonliteral -Wcast-align -Wpointer-arith \ ++ -Wbad-function-cast -Wmissing-prototypes -Wstrict-prototypes \ ++ -Wmissing-declarations -Winline -Wundef -Wnested-externs -Wcast-qual \ ++ -Wshadow -Wwrite-strings -Wno-unused-parameter -Wfloat-equal -ansi -std=c99 ++ ++SIMPLE_WARNINGS := -Wextra -ansi -std=c99 -Wno-unused-parameter ++ ++OTHER_WARNINGS := -D_FORTIFY_SOURCE=2 -fstack-protector -g3 -Wdisabled-optimization \ ++ -Wendif-labels -Wformat=2 -Wstack-protector -Wswitch ++ ++CFLAGS += -Wall # $(EXTRA_WARNINGS) ++ ++ifdef DEBUG ++ override CFLAGS += -ggdb ++endif ++ ++ifdef DEVEL ++ override CFLAGS += -DPECAN_DEVEL ++endif ++ ++override CFLAGS += -D_XOPEN_SOURCE ++#override CFLAGS += -I. -DENABLE_NLS -DHAVE_LIBPURPLE -DPURPLE_DEBUG ++override CFLAGS += -I. -DENABLE_NLS -DHAVE_LIBPURPLE ++ ++ifdef CVR ++ override CFLAGS += -DPECAN_CVR ++endif ++ ++ifndef DO_NOT_USE_PSM ++ override CFLAGS += -DPECAN_USE_PSM ++endif ++ ++ifdef LIBSIREN ++ override CFLAGS += -DPECAN_LIBSIREN ++ LIBSIREN_LIBS := -lm ++endif ++ ++ifdef LIBMSPACK ++ override CFLAGS += -DPECAN_LIBMSPACK ++ LIBMSPACK_LIBS := -lm ++endif ++ ++ifdef PLUS_SOUNDS ++ override CFLAGS += -DRECEIVE_PLUS_SOUNDS ++endif ++ ++ifdef GIO ++ override CFLAGS += -DUSE_GIO ++endif ++ ++# extra debugging ++override CFLAGS += -DPECAN_DEBUG_SLP ++ ++LDFLAGS := -Wl,--no-undefined ++ ++objects := msn.o \ ++ nexus.o \ ++ notification.o \ ++ page.o \ ++ session.o \ ++ switchboard.o \ ++ sync.o \ ++ pn_log.o \ ++ pn_printf.o \ ++ pn_util.o \ ++ pn_buffer.o \ ++ pn_error.o \ ++ pn_status.o \ ++ pn_oim.o \ ++ pn_dp_manager.o \ ++ cmd/cmdproc.o \ ++ cmd/command.o \ ++ cmd/msg.o \ ++ cmd/table.o \ ++ cmd/transaction.o \ ++ io/pn_parser.o \ ++ ab/pn_group.o \ ++ ab/pn_contact.o \ ++ ab/pn_contactlist.o \ ++ io/pn_stream.o \ ++ io/pn_node.o \ ++ io/pn_cmd_server.o \ ++ io/pn_http_server.o \ ++ io/pn_ssl_conn.o \ ++ fix_purple.o ++ ++ifdef CVR ++ objects += cvr/pn_peer_call.o \ ++ cvr/pn_peer_link.o \ ++ cvr/pn_peer_msg.o \ ++ cvr/pn_msnobj.o ++ objects += libpurple/xfer.o ++endif ++ ++ifdef DIRECTCONN ++ objects += cvr/pn_direct_conn.o ++ objects += io/pn_dc_conn.o ++ override CFLAGS += -DMSN_DIRECTCONN ++endif ++ ++ifdef LIBSIREN ++ objects += ext/libsiren/common.o \ ++ ext/libsiren/dct4.o \ ++ ext/libsiren/decoder.o \ ++ ext/libsiren/huffman.o \ ++ ext/libsiren/rmlt.o \ ++ pn_siren7.o ++endif ++ ++ifdef LIBMSPACK ++ objects += ext/libmspack/cabd.o \ ++ ext/libmspack/mszipd.o \ ++ ext/libmspack/lzxd.o \ ++ ext/libmspack/qtmd.o \ ++ ext/libmspack/system.o ++endif ++ ++sources := $(objects:.o=.c) ++deps := $(objects:.o=.d) ++ ++CATALOGS := ar da de eo es fi fr tr hu it nb nl pt_BR pt sr sv tr zh_CN zh_TW ++ ++ifeq ($(PLATFORM),darwin) ++ SHLIBEXT := dylib ++else ++ifeq ($(PLATFORM),mingw32) ++ SHLIBEXT := dll ++ LDFLAGS += -Wl,--enable-auto-image-base -L./win32 ++ objects += win32/resource.res ++else ++ SHLIBEXT := so ++endif ++endif ++ ++ifdef STATIC ++ SHLIBEXT := a ++ override CFLAGS += -DSTATIC_PECAN ++else ++ifneq ($(PLATFORM),mingw32) ++ override CFLAGS += -fPIC ++endif ++endif ++ ++plugin := libmsn-pecan.$(SHLIBEXT) ++ ++.PHONY: clean ++ ++all: $(plugin) ++ ++version := $(shell ./get-version) ++ ++# pretty print ++ifndef V ++QUIET_CC = @echo ' CC '$@; ++QUIET_LINK = @echo ' LINK '$@; ++QUIET_CLEAN = @echo ' CLEAN '$@; ++QUIET_MO = @echo ' MSGFMT '$@; ++QUIET_WR = @echo ' WINDRES '$@; ++endif ++ ++D = $(DESTDIR) ++ ++plugin_libs := $(PURPLE_LIBS) $(GIO_LIBS) ++ ++ifdef LIBSIREN ++ plugin_libs += $(LIBSIREN_LIBS) ++endif ++ ++ifdef LIBMSPACK ++ plugin_libs += $(LIBMSPACK_LIBS) ++endif ++ ++$(plugin): $(objects) ++$(plugin): CFLAGS := $(CFLAGS) $(PURPLE_CFLAGS) $(GIO_CFLAGS) $(FALLBACK_CFLAGS) -D VERSION='"$(version)"' ++$(plugin): LIBS := $(plugin_libs) ++ ++messages.pot: $(sources) ++ $(XGETTEXT) -m -c --keyword --keyword=_ --keyword=N_ -o $@ $^ ++ ++%.dylib:: ++ $(QUIET_LINK)$(CC) $(LDFLAGS) -dynamiclib -o $@ $^ $(LIBS) ++ ++%.so %.dll:: ++ $(QUIET_LINK)$(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) ++ ++%.a:: ++ $(QUIET_LINK)$(AR) rcs $@ $^ ++ ++%.o:: %.c ++ $(QUIET_CC)$(CC) $(CFLAGS) -MMD -o $@ -c $< ++ ++%.res:: %.rc ++ $(QUIET_WR)$(WINDRES) $< -O coff -o $@ ++ ++clean: ++ $(QUIET_CLEAN)$(RM) $(plugin) $(objects) $(deps) `find -name '*.mo'` ++ ++%.mo:: %.po ++ $(QUIET_MO)$(MSGFMT) -c -o $@ $< ++ ++dist: base := msn-pecan-$(version) ++dist: ++ git archive --format=tar --prefix=$(base)/ HEAD > /tmp/$(base).tar ++ mkdir -p $(base) ++ git-changelog > $(base)/ChangeLog ++ chmod 664 $(base)/ChangeLog ++ tar --append -f /tmp/$(base).tar --owner root --group root $(base)/ChangeLog ++ echo $(version) > $(base)/.version ++ chmod 664 $(base)/.version ++ tar --append -f /tmp/$(base).tar --owner root --group root $(base)/.version ++ rm -r $(base) ++ bzip2 /tmp/$(base).tar ++ ++install: $(plugin) ++ install -D $(plugin) $(D)/$(PURPLE_PLUGINDIR)/$(plugin) ++ # chcon -t textrel_shlib_t $(D)/$(PURPLE_PLUGINDIR)/$(plugin) # for selinux ++ ++uninstall: ++ rm -f $(D)/$(PURPLE_PLUGINDIR)/$(plugin) ++ for x in $(CATALOGS); do \ ++ rm -f $(D)/$(PURPLE_DATADIR)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \ ++ done ++ ++update_locales: messages.pot ++ for x in $(CATALOGS); do \ ++ msgmerge -N -U --backup=off po/$$x.po $<; \ ++ done ++ ++locales: $(foreach e,$(CATALOGS),po/$(e).mo) ++ ++install_locales: locales ++ for x in $(CATALOGS); do \ ++ install -m 644 -D po/$$x.mo $(D)/$(PURPLE_DATADIR)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \ ++ done ++ ++win32-check: ++ test -f win32/libintl.dll.a ++ ++win32: D := libmsn-pecan ++win32: win32-check $(plugin) locales ++ mkdir -p $(D)/plugins ++ cp $(plugin) libmsn-pecan-$(version)-dbg.dll ++ $(CROSS_COMPILE)strip $(plugin) ++ cp $(plugin) $(D)/plugins/ ++ for x in $(CATALOGS); do \ ++ mkdir -p $(D)/locale/$$x/LC_MESSAGES/; \ ++ cp po/$$x.mo $(D)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \ ++ done ++ cp COPYING $(D) ++ tar -cf /tmp/libmsn-pecan.tar $(D) ++ ++-include $(deps) +diff -rupN msn-pecan-0.1.1/pn_log.c msn-pecan-0.1.1-new//pn_log.c +--- msn-pecan-0.1.1/pn_log.c 2010-08-28 10:48:09.000000000 -0500 ++++ msn-pecan-0.1.1-new//pn_log.c 2011-03-27 09:01:42.448553000 -0600 +@@ -159,7 +159,8 @@ pn_base_log_helper (guint level, + if (level <= PN_LOG_LEVEL_INFO || level == PN_LOG_LEVEL_TEST) { + char *arg_s; + arg_s = g_strdup_printf("%s\n", tmp); +- ops->print(purple_level, "msn-pecan", arg_s); ++// ops->print(purple_level, "msn-pecan", arg_s); ++ + g_free(arg_s); + } + #endif +diff -rupN msn-pecan-0.1.1/pn_log.c~ msn-pecan-0.1.1-new//pn_log.c~ +--- msn-pecan-0.1.1/pn_log.c~ 1969-12-31 18:00:00.000000000 -0600 ++++ msn-pecan-0.1.1-new//pn_log.c~ 2011-03-27 09:01:42.480553000 -0600 +@@ -0,0 +1,179 @@ ++/** ++ * Copyright (C) 2007-2009 Felipe Contreras ++ * ++ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ */ ++ ++#include "pn_log.h" ++#include "pn_printf.h" ++ ++#ifdef PN_DEBUG ++ ++/* #define PURPLE_DEBUG */ ++/* #define PN_DEBUG_FILE */ ++ ++#include <fcntl.h> ++#include <unistd.h> ++ ++#include <glib/gstdio.h> ++ ++#ifdef PURPLE_DEBUG ++/* libpurple stuff. */ ++#include <debug.h> ++#endif /* PURPLE_DEBUG */ ++ ++static inline const gchar * ++log_level_to_string (PecanLogLevel level) ++{ ++ switch (level) ++ { ++ case PN_LOG_LEVEL_NONE: return "NONE"; break; ++ case PN_LOG_LEVEL_ERROR: return "ERROR"; break; ++ case PN_LOG_LEVEL_WARNING: return "WARNING"; break; ++ case PN_LOG_LEVEL_INFO: return "INFO"; break; ++ case PN_LOG_LEVEL_DEBUG: return "DEBUG"; break; ++ case PN_LOG_LEVEL_LOG: return "LOG"; break; ++ case PN_LOG_LEVEL_TEST: return "TEST"; break; ++ default: return "Unknown"; break; ++ } ++} ++ ++#ifdef PN_DUMP_FILE ++void ++pn_dump_file (const gchar *buffer, ++ gsize len) ++{ ++ gint fd; ++ static guint c; ++ gchar *basename; ++ gchar *fullname; ++ ++ basename = pn_strdup_printf ("pecan-%.6u.bin", c++); ++ ++ fullname = g_build_filename (g_get_tmp_dir (), basename, NULL); ++ ++ g_free (basename); ++ ++ fd = g_open (fullname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); ++ ++ if (fd) ++ { ++ write (fd, buffer, len); ++ close (fd); ++ } ++} ++#endif /* PN_DUMP_FILE */ ++ ++void ++pn_base_log_helper (guint level, ++ const gchar *file, ++ const gchar *function, ++ gint line, ++ const gchar *fmt, ++ ...) ++{ ++ gchar *tmp; ++ va_list args; ++ gboolean console_print = FALSE; ++ ++ if (level > PECAN_LOG_LEVEL && level != PN_LOG_LEVEL_TEST) ++ return; ++ ++ va_start (args, fmt); ++ ++#if defined(PECAN_DEVEL) ++ console_print = TRUE; ++#else ++ if (level == PN_LOG_LEVEL_TEST) ++ console_print = TRUE; ++#endif ++ ++#if defined(PURPLE_DEBUG) ++ PurpleDebugLevel purple_level; ++ PurpleDebugUiOps *ops; ++ ++ switch (level) { ++ case PN_LOG_LEVEL_ERROR: ++ purple_level = PURPLE_DEBUG_ERROR; break; ++ case PN_LOG_LEVEL_WARNING: ++ purple_level = PURPLE_DEBUG_WARNING; break; ++ case PN_LOG_LEVEL_INFO: ++ purple_level = PURPLE_DEBUG_INFO; break; ++ case PN_LOG_LEVEL_DEBUG: ++ case PN_LOG_LEVEL_TEST: ++ purple_level = PURPLE_DEBUG_MISC; break; ++ case PN_LOG_LEVEL_LOG: ++ purple_level = PURPLE_DEBUG_MISC; break; ++ default: ++ purple_level = PURPLE_DEBUG_MISC; break; ++ } ++ ++ if (purple_debug_is_enabled()) ++ console_print = TRUE; ++ ++ ops = purple_debug_get_ui_ops(); ++ ++ if (!console_print) { ++ if (!ops || !ops->print || ++ (ops->is_enabled && !ops->is_enabled(purple_level, "msn-pecan"))) ++ { ++ return; ++ } ++ } ++#endif ++ ++ tmp = pn_strdup_vprintf (fmt, args); ++ ++#if defined(PN_DEBUG_FILE) ++ { ++ static FILE *logfile; ++ if (!logfile) ++ { ++ gint fd; ++ fd = g_file_open_tmp ("msn-pecan-XXXXXX", NULL, NULL); ++ if (fd) ++ logfile = fdopen (fd, "w"); ++ } ++ if (logfile) ++ { ++ g_fprintf (logfile, "%s\t%s:%d:%s()\t%s\n", ++ log_level_to_string (level), ++ file, line, function, ++ tmp); ++ } ++ } ++#else ++#if defined(PURPLE_DEBUG) ++ if (level <= PN_LOG_LEVEL_INFO || level == PN_LOG_LEVEL_TEST) { ++ char *arg_s; ++ arg_s = g_strdup_printf("%s\n", tmp); ++ ops->print(purple_level, "msn-pecan", arg_s); ++ g_free(arg_s); ++ } ++#endif ++#endif /* PN_DEBUG_FILE */ ++ ++ if (console_print) ++ g_print ("%s %s:%d:%s() %s\n", ++ log_level_to_string (level), ++ file, line, function, ++ tmp); ++ ++ g_free (tmp); ++ ++ va_end (args); ++} ++ ++#endif /* PN_DEBUG */ diff --git a/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches b/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches new file mode 100644 index 0000000..984f078 --- a/dev/null +++ b/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches @@ -0,0 +1,105 @@ +diff -rupN pidgin-facebookchat//fb_blist.c pidgin-facebookchat-new//fb_blist.c +--- pidgin-facebookchat//fb_blist.c 2010-11-26 04:07:32.000000000 -0600 ++++ pidgin-facebookchat-new//fb_blist.c 2011-03-27 08:55:51.068553000 -0600 +@@ -104,6 +104,10 @@ static GList *get_buddies(FacebookAccoun + fbuddy->thumb_url = g_strdup(buddy_icon_url); + + buddy->proto_data = fbuddy; ++// Fix merging contacts on Pre ++ buddy->server_alias = atoll(uid); ++ buddy->name = g_strdup(name); ++ + } + } + +@@ -407,6 +411,8 @@ static void got_buddy_list_cb(FacebookAc + return; + + JsonParser *parser = fb_get_parser(data, data_len); ++purple_debug_info("facebook", "after json parser\n"); ++ + if (parser == NULL) { + if (fba->bad_buddy_list_count++ == 3) + { +diff -rupN pidgin-facebookchat//fb_messages.c pidgin-facebookchat-new//fb_messages.c +--- pidgin-facebookchat//fb_messages.c 2010-10-09 05:33:13.000000000 -0500 ++++ pidgin-facebookchat-new//fb_messages.c 2011-03-27 08:55:51.068553000 -0600 +@@ -398,9 +398,9 @@ static gboolean fb_send_im_fom(FacebookO + jstime = g_strdup_printf("%ld%ld", msg->time.tv_sec, (msg->time.tv_usec/1000)); + + encoded_message = g_strdup(purple_url_encode(msg->message)); +- postdata = g_strdup_printf("msg_text=%s&msg_id=%d&to=%s&client_time=%s&post_form_id=%s", ++ postdata = g_strdup_printf("msg_text=%s&msg_id=%d&to=%s&client_time=%s&post_form_id=%s&fb_dtsg=%s", + encoded_message, msg->msg_id, msg->who, jstime, +- msg->fba->post_form_id ? msg->fba->post_form_id : "0"); ++ msg->fba->post_form_id ? msg->fba->post_form_id : "0",msg->fba->dtsg?msg->fba->dtsg:"(null)"); + g_free(encoded_message); + g_free(jstime); + +diff -rupN pidgin-facebookchat//Makefile pidgin-facebookchat-new//Makefile +--- pidgin-facebookchat//Makefile 2010-10-08 06:35:45.000000000 -0500 ++++ pidgin-facebookchat-new//Makefile 2011-03-27 08:55:51.072552999 -0600 +@@ -5,19 +5,22 @@ WIN32_COMPILER = /usr/bin/i586-mingw32-g + WIN32_WINDRES = i586-mingw32-windres + WIN32_OBJCOPY = i586-mingw32-objcopy + #LINUX_ARM_COMPILER = arm-pc-linux-gnu-gcc +-LINUX_ARM_COMPILER = arm-none-linux-gnueabi-gcc ++LINUX_ARM_COMPILER = gcc + LINUX_PPC_COMPILER = powerpc-unknown-linux-gnu-gcc + FREEBSD60_COMPILER = i686-pc-freebsd6.0-gcc + MACPORT_COMPILER = i686-apple-darwin10-gcc-4.0.1 + +-LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB +-GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/include/json-glib-1.0 -ljson-glib-1.0 ++LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB -DUSE_JSONC ++#LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS ++GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/local/include/json-glib-1.0 -L/srv/preware/cross-compile/staging/armv7/usr/lib -ljson-glib-1.0 -lglib-2.0 -lz -ljson + WIN32_DEV_DIR = /root/pidgin/win32-dev + WIN32_PIDGIN_DIR = /root/pidgin/pidgin-2.3.0_win32 + WIN32_CFLAGS = -I${WIN32_DEV_DIR}/gtk_2_0/include/glib-2.0 -I${WIN32_PIDGIN_DIR}/libpurple/win32 -I${WIN32_DEV_DIR}/gtk_2_0/include -I${WIN32_DEV_DIR}/gtk_2_0/include/glib-2.0 -I${WIN32_DEV_DIR}/gtk_2_0/lib/glib-2.0/include -I/usr/include/json-glib-1.0 -Wno-format + WIN32_LIBS = -L${WIN32_DEV_DIR}/gtk_2_0/lib -L${WIN32_PIDGIN_DIR}/libpurple -lglib-2.0 -lgobject-2.0 -lintl -lpurple -lws2_32 -L. -ljson-glib-1.0 -lzlib1 + MACPORT_CFLAGS = -I/opt/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include -I/opt/local/include/json-glib-1.0 -arch i386 -arch ppc -dynamiclib -L/opt/local/lib -ljson-glib-1.0 -lpurple -lglib-2.0 -lgobject-2.0 -lintl -lz -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 +-PIDGIN_DIR = /root/pidgin/pidgin-2.7.0 ++PIDGIN_DIR = /srv/preware/cross-compile/packages/x/pidgin/build/src/ ++ ++CC = gcc + + DEB_PACKAGE_DIR = ./debdir + +@@ -54,14 +57,13 @@ FACEBOOK_SOURCES = \ + + all: libfacebook.so libfacebook.dll libfacebook64.so libfacebookarm.so libfacebookppc.so installers sourcepackage + ++linux: libfacebookarm.so ++ + install: +- cp libfacebook.so /usr/lib/purple-2/ +- cp libfacebook64.so /usr/lib64/purple-2/ +- cp libfacebookarm.so /usr/lib/pidgin/ +- cp libfacebookppc.so /usr/lib/purple-2/ +- cp facebook16.png /usr/share/pixmaps/pidgin/protocols/16/facebook.png +- cp facebook22.png /usr/share/pixmaps/pidgin/protocols/22/facebook.png +- cp facebook48.png /usr/share/pixmaps/pidgin/protocols/48/facebook.png ++ cp libfacebookarm.so /usr/local/lib/purple-2/ ++ cp facebook16.png /usr/local/share/pixmaps/pidgin/protocols/16/facebook.png ++ cp facebook22.png /usr/local/share/pixmaps/pidgin/protocols/22/facebook.png ++ cp facebook48.png /usr/local/share/pixmaps/pidgin/protocols/48/facebook.png + + installers: pidgin-facebookchat.exe pidgin-facebookchat.deb pidgin-facebookchat.tar.bz2 + +@@ -73,13 +75,13 @@ libfacebook.macport.so: ${FACEBOOK_SOURC + ${MACPORT_COMPILER} ${MACPORT_CFLAGS} -Wall -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.macport.so -shared + + libfacebook.so: ${FACEBOOK_SOURCES} +- ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.so -shared -fPIC -DPIC ++ ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.so -shared -fPIC -DPIC -static + + libfacebookxmpp.so: libfbxmpp.c ${FACEBOOK_SOURCES} + ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe libfbxmpp.c ${FACEBOOK_SOURCES} -o libfacebookxmpp.so -shared -fPIC -DPIC -I$(PIDGIN_DIR)/libpurple/protocols/jabber/ -lxml2 -I/usr/include/libxml2 + + libfacebookarm.so: ${FACEBOOK_SOURCES} +- ${LINUX_ARM_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebookarm.so -shared -fPIC -DPIC ++ ${LINUX_ARM_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebookarm.so -shared -fPIC -DPIC + + libfacebook64.so: ${FACEBOOK_SOURCES} + ${LINUX64_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -m64 -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook64.so -shared -fPIC -DPIC diff --git a/3.0/Compiler Source Files/Patches/pidgin-patches b/3.0/Compiler Source Files/Patches/pidgin-patches new file mode 100644 index 0000000..484ff22 --- a/dev/null +++ b/3.0/Compiler Source Files/Patches/pidgin-patches @@ -0,0 +1,29796 @@ +diff -rupN pidgin-2.7.7/libpurple/blist.c pidgin-2.7.7-new//libpurple/blist.c +--- pidgin-2.7.7/libpurple/blist.c 2011-03-27 09:05:47.872553000 -0600 ++++ pidgin-2.7.7-new//libpurple/blist.c 2011-03-27 09:15:30.072552999 -0600 +@@ -1059,7 +1059,7 @@ void purple_blist_alias_contact(PurpleCo + + purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", + contact, old_alias); +- g_free(old_alias); ++// g_free(old_alias); + } + + void purple_blist_alias_chat(PurpleChat *chat, const char *alias) +@@ -1137,7 +1137,7 @@ void purple_blist_alias_buddy(PurpleBudd + + purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", + buddy, old_alias); +- g_free(old_alias); ++// g_free(old_alias); + } + + void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias) +@@ -1179,7 +1179,7 @@ void purple_blist_server_alias_buddy(Pur + + purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", + buddy, old_alias); +- g_free(old_alias); ++// g_free(old_alias); + } + + /* +diff -rupN pidgin-2.7.7/libpurple/blist.c~ pidgin-2.7.7-new//libpurple/blist.c~ +--- pidgin-2.7.7/libpurple/blist.c~ 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/blist.c~ 2011-03-27 09:15:30.064552999 -0600 +@@ -0,0 +1,3266 @@ ++/* ++ * purple ++ * ++ * Purple is the legal property of its developers, whose names are too numerous ++ * to list here. Please refer to the COPYRIGHT file distributed with this ++ * source distribution. ++ * ++ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA ++ * ++ */ ++#define _PURPLE_BLIST_C_ ++ ++#include "internal.h" ++#include "blist.h" ++#include "conversation.h" ++#include "dbus-maybe.h" ++#include "debug.h" ++#include "notify.h" ++#include "prefs.h" ++#include "privacy.h" ++#include "prpl.h" ++#include "server.h" ++#include "signals.h" ++#include "util.h" ++#include "value.h" ++#include "xmlnode.h" ++ ++static PurpleBlistUiOps *blist_ui_ops = NULL; ++ ++static PurpleBuddyList *purplebuddylist = NULL; ++ ++/** ++ * A hash table used for efficient lookups of buddies by name. ++ * PurpleAccount* => GHashTable*, with the inner hash table being ++ * struct _purple_hbuddy => PurpleBuddy* ++ */ ++static GHashTable *buddies_cache = NULL; ++ ++/** ++ * A hash table used for efficient lookups of groups by name. ++ * UTF-8 collate-key => PurpleGroup*. ++ */ ++static GHashTable *groups_cache = NULL; ++ ++static guint save_timer = 0; ++static gboolean blist_loaded = FALSE; ++ ++/********************************************************************* ++ * Private utility functions * ++ *********************************************************************/ ++ ++static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node) ++{ ++ PurpleBlistNode *n = node; ++ if (!n) ++ return NULL; ++ while (n->next) ++ n = n->next; ++ return n; ++} ++ ++static PurpleBlistNode *purple_blist_get_last_child(PurpleBlistNode *node) ++{ ++ if (!node) ++ return NULL; ++ return purple_blist_get_last_sibling(node->child); ++} ++ ++struct _list_account_buddies { ++ GSList *list; ++ PurpleAccount *account; ++}; ++ ++struct _purple_hbuddy { ++ char *name; ++ PurpleAccount *account; ++ PurpleBlistNode *group; ++}; ++ ++/* This function must not use purple_normalize */ ++static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb) ++{ ++ return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account); ++} ++ ++/* This function must not use purple_normalize */ ++static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2) ++{ ++ return (hb1->group == hb2->group && ++ hb1->account == hb2->account && ++ g_str_equal(hb1->name, hb2->name)); ++} ++ ++static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb) ++{ ++ g_free(hb->name); ++ g_free(hb); ++} ++ ++static void ++purple_blist_buddies_cache_add_account(PurpleAccount *account) ++{ ++ GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash, ++ (GEqualFunc)_purple_blist_hbuddy_equal, ++ (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL); ++ g_hash_table_insert(buddies_cache, account, account_buddies); ++} ++ ++static void ++purple_blist_buddies_cache_remove_account(const PurpleAccount *account) ++{ ++ g_hash_table_remove(buddies_cache, account); ++} ++ ++ ++/********************************************************************* ++ * Writing to disk * ++ *********************************************************************/ ++ ++static void ++value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data) ++{ ++ const char *name; ++ PurpleValue *value; ++ xmlnode *node, *child; ++ char buf[21]; ++ ++ name = (const char *)key; ++ value = (PurpleValue *)hvalue; ++ node = (xmlnode *)user_data; ++ ++ g_return_if_fail(value != NULL); ++ ++ child = xmlnode_new_child(node, "setting"); ++ xmlnode_set_attrib(child, "name", name); ++ ++ if (purple_value_get_type(value) == PURPLE_TYPE_INT) { ++ xmlnode_set_attrib(child, "type", "int"); ++ g_snprintf(buf, sizeof(buf), "%d", purple_value_get_int(value)); ++ xmlnode_insert_data(child, buf, -1); ++ } ++ else if (purple_value_get_type(value) == PURPLE_TYPE_STRING) { ++ xmlnode_set_attrib(child, "type", "string"); ++ xmlnode_insert_data(child, purple_value_get_string(value), -1); ++ } ++ else if (purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN) { ++ xmlnode_set_attrib(child, "type", "bool"); ++ g_snprintf(buf, sizeof(buf), "%d", purple_value_get_boolean(value)); ++ xmlnode_insert_data(child, buf, -1); ++ } ++} ++ ++static void ++chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data) ++{ ++ const char *name; ++ const char *data; ++ xmlnode *node, *child; ++ ++ name = (const char *)key; ++ data = (const char *)value; ++ node = (xmlnode *)user_data; ++ ++ g_return_if_fail(data != NULL); ++ ++ child = xmlnode_new_child(node, "component"); ++ xmlnode_set_attrib(child, "name", name); ++ xmlnode_insert_data(child, data, -1); ++} ++ ++static xmlnode * ++buddy_to_xmlnode(PurpleBlistNode *bnode) ++{ ++ xmlnode *node, *child; ++ PurpleBuddy *buddy; ++ ++ buddy = (PurpleBuddy *)bnode; ++ ++ node = xmlnode_new("buddy"); ++ xmlnode_set_attrib(node, "account", purple_account_get_username(buddy->account)); ++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(buddy->account)); ++ ++ child = xmlnode_new_child(node, "name"); ++ xmlnode_insert_data(child, buddy->name, -1); ++ ++ if (buddy->alias != NULL) ++ { ++ child = xmlnode_new_child(node, "alias"); ++ xmlnode_insert_data(child, buddy->alias, -1); ++ } ++ ++ /* Write buddy settings */ ++ g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node); ++ ++ return node; ++} ++ ++static xmlnode * ++contact_to_xmlnode(PurpleBlistNode *cnode) ++{ ++ xmlnode *node, *child; ++ PurpleContact *contact; ++ PurpleBlistNode *bnode; ++ ++ contact = (PurpleContact *)cnode; ++ ++ node = xmlnode_new("contact"); ++ ++ if (contact->alias != NULL) ++ { ++ xmlnode_set_attrib(node, "alias", contact->alias); ++ } ++ ++ /* Write buddies */ ++ for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) ++ { ++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(bnode)) ++ continue; ++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) ++ { ++ child = buddy_to_xmlnode(bnode); ++ xmlnode_insert_child(node, child); ++ } ++ } ++ ++ /* Write contact settings */ ++ g_hash_table_foreach(cnode->settings, value_to_xmlnode, node); ++ ++ return node; ++} ++ ++static xmlnode * ++chat_to_xmlnode(PurpleBlistNode *cnode) ++{ ++ xmlnode *node, *child; ++ PurpleChat *chat; ++ ++ chat = (PurpleChat *)cnode; ++ ++ node = xmlnode_new("chat"); ++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(chat->account)); ++ xmlnode_set_attrib(node, "account", purple_account_get_username(chat->account)); ++ ++ if (chat->alias != NULL) ++ { ++ child = xmlnode_new_child(node, "alias"); ++ xmlnode_insert_data(child, chat->alias, -1); ++ } ++ ++ /* Write chat components */ ++ g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node); ++ ++ /* Write chat settings */ ++ g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node); ++ ++ return node; ++} ++ ++static xmlnode * ++group_to_xmlnode(PurpleBlistNode *gnode) ++{ ++ xmlnode *node, *child; ++ PurpleGroup *group; ++ PurpleBlistNode *cnode; ++ ++ group = (PurpleGroup *)gnode; ++ ++ node = xmlnode_new("group"); ++ xmlnode_set_attrib(node, "name", group->name); ++ ++ /* Write settings */ ++ g_hash_table_foreach(group->node.settings, value_to_xmlnode, node); ++ ++ /* Write contacts and chats */ ++ for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) ++ { ++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(cnode)) ++ continue; ++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) ++ { ++ child = contact_to_xmlnode(cnode); ++ xmlnode_insert_child(node, child); ++ } ++ else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) ++ { ++ child = chat_to_xmlnode(cnode); ++ xmlnode_insert_child(node, child); ++ } ++ } ++ ++ return node; ++} ++ ++static xmlnode * ++accountprivacy_to_xmlnode(PurpleAccount *account) ++{ ++ xmlnode *node, *child; ++ GSList *cur; ++ char buf[10]; ++ ++ node = xmlnode_new("account"); ++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account)); ++ xmlnode_set_attrib(node, "name", purple_account_get_username(account)); ++ g_snprintf(buf, sizeof(buf), "%d", account->perm_deny); ++ xmlnode_set_attrib(node, "mode", buf); ++ ++ for (cur = account->permit; cur; cur = cur->next) ++ { ++ child = xmlnode_new_child(node, "permit"); ++ xmlnode_insert_data(child, cur->data, -1); ++ } ++ ++ for (cur = account->deny; cur; cur = cur->next) ++ { ++ child = xmlnode_new_child(node, "block"); ++ xmlnode_insert_data(child, cur->data, -1); ++ } ++ ++ return node; ++} ++ ++static xmlnode * ++blist_to_xmlnode(void) ++{ ++ xmlnode *node, *child, *grandchild; ++ PurpleBlistNode *gnode; ++ GList *cur; ++ ++ node = xmlnode_new("purple"); ++ xmlnode_set_attrib(node, "version", "1.0"); ++ ++ /* Write groups */ ++ child = xmlnode_new_child(node, "blist"); ++ for (gnode = purplebuddylist->root; gnode != NULL; gnode = gnode->next) ++ { ++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(gnode)) ++ continue; ++ if (PURPLE_BLIST_NODE_IS_GROUP(gnode)) ++ { ++ grandchild = group_to_xmlnode(gnode); ++ xmlnode_insert_child(child, grandchild); ++ } ++ } ++ ++ /* Write privacy settings */ ++ child = xmlnode_new_child(node, "privacy"); ++ for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next) ++ { ++ grandchild = accountprivacy_to_xmlnode(cur->data); ++ xmlnode_insert_child(child, grandchild); ++ } ++ ++ return node; ++} ++ ++static void ++purple_blist_sync(void) ++{ ++ xmlnode *node; ++ char *data; ++ ++ if (!blist_loaded) ++ { ++ purple_debug_error("blist", "Attempted to save buddy list before it " ++ "was read!\n"); ++ return; ++ } ++ ++ node = blist_to_xmlnode(); ++ data = xmlnode_to_formatted_str(node, NULL); ++ purple_util_write_data_to_file("blist.xml", data, -1); ++ g_free(data); ++ xmlnode_free(node); ++} ++ ++static gboolean ++save_cb(gpointer data) ++{ ++ purple_blist_sync(); ++ save_timer = 0; ++ return FALSE; ++} ++ ++static void ++_purple_blist_schedule_save() ++{ ++ if (save_timer == 0) ++ save_timer = purple_timeout_add_seconds(5, save_cb, NULL); ++} ++ ++static void ++purple_blist_save_account(PurpleAccount *account) ++{ ++#if 1 ++ _purple_blist_schedule_save(); ++#else ++ if (account != NULL) { ++ /* Save the buddies and privacy data for this account */ ++ } else { ++ /* Save all buddies and privacy data */ ++ } ++#endif ++} ++ ++static void ++purple_blist_save_node(PurpleBlistNode *node) ++{ ++ _purple_blist_schedule_save(); ++} ++ ++void purple_blist_schedule_save() ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ /* Save everything */ ++ if (ops && ops->save_account) ++ ops->save_account(NULL); ++} ++ ++ ++/********************************************************************* ++ * Reading from disk * ++ *********************************************************************/ ++ ++static void ++parse_setting(PurpleBlistNode *node, xmlnode *setting) ++{ ++ const char *name = xmlnode_get_attrib(setting, "name"); ++ const char *type = xmlnode_get_attrib(setting, "type"); ++ char *value = xmlnode_get_data(setting); ++ ++ if (!value) ++ return; ++ ++ if (!type || purple_strequal(type, "string")) ++ purple_blist_node_set_string(node, name, value); ++ else if (purple_strequal(type, "bool")) ++ purple_blist_node_set_bool(node, name, atoi(value)); ++ else if (purple_strequal(type, "int")) ++ purple_blist_node_set_int(node, name, atoi(value)); ++ ++ g_free(value); ++} ++ ++static void ++parse_buddy(PurpleGroup *group, PurpleContact *contact, xmlnode *bnode) ++{ ++ PurpleAccount *account; ++ PurpleBuddy *buddy; ++ char *name = NULL, *alias = NULL; ++ const char *acct_name, *proto, *protocol; ++ xmlnode *x; ++ ++ acct_name = xmlnode_get_attrib(bnode, "account"); ++ protocol = xmlnode_get_attrib(bnode, "protocol"); ++ protocol = _purple_oscar_convert(acct_name, protocol); /* XXX: Remove */ ++ proto = xmlnode_get_attrib(bnode, "proto"); ++ proto = _purple_oscar_convert(acct_name, proto); /* XXX: Remove */ ++ ++ if (!acct_name || (!proto && !protocol)) ++ return; ++ ++ account = purple_accounts_find(acct_name, proto ? proto : protocol); ++ ++ if (!account) ++ return; ++ ++ if ((x = xmlnode_get_child(bnode, "name"))) ++ name = xmlnode_get_data(x); ++ ++ if (!name) ++ return; ++ ++ if ((x = xmlnode_get_child(bnode, "alias"))) ++ alias = xmlnode_get_data(x); ++ ++ buddy = purple_buddy_new(account, name, alias); ++ purple_blist_add_buddy(buddy, contact, group, ++ purple_blist_get_last_child((PurpleBlistNode*)contact)); ++ ++ for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) { ++ parse_setting((PurpleBlistNode*)buddy, x); ++ } ++ ++ g_free(name); ++ g_free(alias); ++} ++ ++static void ++parse_contact(PurpleGroup *group, xmlnode *cnode) ++{ ++ PurpleContact *contact = purple_contact_new(); ++ xmlnode *x; ++ const char *alias; ++ ++ purple_blist_add_contact(contact, group, ++ purple_blist_get_last_child((PurpleBlistNode*)group)); ++ ++ if ((alias = xmlnode_get_attrib(cnode, "alias"))) { ++ purple_blist_alias_contact(contact, alias); ++ } ++ ++ for (x = cnode->child; x; x = x->next) { ++ if (x->type != XMLNODE_TYPE_TAG) ++ continue; ++ if (purple_strequal(x->name, "buddy")) ++ parse_buddy(group, contact, x); ++ else if (purple_strequal(x->name, "setting")) ++ parse_setting((PurpleBlistNode*)contact, x); ++ } ++ ++ /* if the contact is empty, don't keep it around. it causes problems */ ++ if (!((PurpleBlistNode*)contact)->child) ++ purple_blist_remove_contact(contact); ++} ++ ++static void ++parse_chat(PurpleGroup *group, xmlnode *cnode) ++{ ++ PurpleChat *chat; ++ PurpleAccount *account; ++ const char *acct_name, *proto, *protocol; ++ xmlnode *x; ++ char *alias = NULL; ++ GHashTable *components; ++ ++ acct_name = xmlnode_get_attrib(cnode, "account"); ++ protocol = xmlnode_get_attrib(cnode, "protocol"); ++ proto = xmlnode_get_attrib(cnode, "proto"); ++ ++ if (!acct_name || (!proto && !protocol)) ++ return; ++ ++ account = purple_accounts_find(acct_name, proto ? proto : protocol); ++ ++ if (!account) ++ return; ++ ++ if ((x = xmlnode_get_child(cnode, "alias"))) ++ alias = xmlnode_get_data(x); ++ ++ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); ++ ++ for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) { ++ const char *name; ++ char *value; ++ ++ name = xmlnode_get_attrib(x, "name"); ++ value = xmlnode_get_data(x); ++ g_hash_table_replace(components, g_strdup(name), value); ++ } ++ ++ chat = purple_chat_new(account, alias, components); ++ purple_blist_add_chat(chat, group, ++ purple_blist_get_last_child((PurpleBlistNode*)group)); ++ ++ for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) { ++ parse_setting((PurpleBlistNode*)chat, x); ++ } ++ ++ g_free(alias); ++} ++ ++static void ++parse_group(xmlnode *groupnode) ++{ ++ const char *name = xmlnode_get_attrib(groupnode, "name"); ++ PurpleGroup *group; ++ xmlnode *cnode; ++ ++ if (!name) ++ name = _("Buddies"); ++ ++ group = purple_group_new(name); ++ purple_blist_add_group(group, ++ purple_blist_get_last_sibling(purplebuddylist->root)); ++ ++ for (cnode = groupnode->child; cnode; cnode = cnode->next) { ++ if (cnode->type != XMLNODE_TYPE_TAG) ++ continue; ++ if (purple_strequal(cnode->name, "setting")) ++ parse_setting((PurpleBlistNode*)group, cnode); ++ else if (purple_strequal(cnode->name, "contact") || ++ purple_strequal(cnode->name, "person")) ++ parse_contact(group, cnode); ++ else if (purple_strequal(cnode->name, "chat")) ++ parse_chat(group, cnode); ++ } ++} ++ ++/* TODO: Make static and rename to load_blist */ ++void ++purple_blist_load() ++{ ++ xmlnode *purple, *blist, *privacy; ++ ++ blist_loaded = TRUE; ++ ++ purple = purple_util_read_xml_from_file("blist.xml", _("buddy list")); ++ ++ if (purple == NULL) ++ return; ++ ++ blist = xmlnode_get_child(purple, "blist"); ++ if (blist) { ++ xmlnode *groupnode; ++ for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL; ++ groupnode = xmlnode_get_next_twin(groupnode)) { ++ parse_group(groupnode); ++ } ++ } ++ ++ privacy = xmlnode_get_child(purple, "privacy"); ++ if (privacy) { ++ xmlnode *anode; ++ for (anode = privacy->child; anode; anode = anode->next) { ++ xmlnode *x; ++ PurpleAccount *account; ++ int imode; ++ const char *acct_name, *proto, *mode, *protocol; ++ ++ acct_name = xmlnode_get_attrib(anode, "name"); ++ protocol = xmlnode_get_attrib(anode, "protocol"); ++ proto = xmlnode_get_attrib(anode, "proto"); ++ mode = xmlnode_get_attrib(anode, "mode"); ++ ++ if (!acct_name || (!proto && !protocol) || !mode) ++ continue; ++ ++ account = purple_accounts_find(acct_name, proto ? proto : protocol); ++ ++ if (!account) ++ continue; ++ ++ imode = atoi(mode); ++ account->perm_deny = (imode != 0 ? imode : PURPLE_PRIVACY_ALLOW_ALL); ++ ++ for (x = anode->child; x; x = x->next) { ++ char *name; ++ if (x->type != XMLNODE_TYPE_TAG) ++ continue; ++ ++ if (purple_strequal(x->name, "permit")) { ++ name = xmlnode_get_data(x); ++ purple_privacy_permit_add(account, name, TRUE); ++ g_free(name); ++ } else if (purple_strequal(x->name, "block")) { ++ name = xmlnode_get_data(x); ++ purple_privacy_deny_add(account, name, TRUE); ++ g_free(name); ++ } ++ } ++ } ++ } ++ ++ xmlnode_free(purple); ++ ++ /* This tells the buddy icon code to do its thing. */ ++ _purple_buddy_icons_blist_loaded_cb(); ++} ++ ++ ++/********************************************************************* ++ * Stuff * ++ *********************************************************************/ ++ ++static void ++purple_contact_compute_priority_buddy(PurpleContact *contact) ++{ ++ PurpleBlistNode *bnode; ++ PurpleBuddy *new_priority = NULL; ++ ++ g_return_if_fail(contact != NULL); ++ ++ contact->priority = NULL; ++ for (bnode = ((PurpleBlistNode*)contact)->child; ++ bnode != NULL; ++ bnode = bnode->next) ++ { ++ PurpleBuddy *buddy; ++ ++ if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) ++ continue; ++ ++ buddy = (PurpleBuddy*)bnode; ++ if (new_priority == NULL) ++ { ++ new_priority = buddy; ++ continue; ++ } ++ ++ if (purple_account_is_connected(buddy->account)) ++ { ++ int cmp = 1; ++ if (purple_account_is_connected(new_priority->account)) ++ cmp = purple_presence_compare(purple_buddy_get_presence(new_priority), ++ purple_buddy_get_presence(buddy)); ++ ++ if (cmp > 0 || (cmp == 0 && ++ purple_prefs_get_bool("/purple/contact/last_match"))) ++ { ++ new_priority = buddy; ++ } ++ } ++ } ++ ++ contact->priority = new_priority; ++ contact->priority_valid = TRUE; ++} ++ ++ ++/***************************************************************************** ++ * Public API functions * ++ *****************************************************************************/ ++ ++PurpleBuddyList *purple_blist_new() ++{ ++ PurpleBlistUiOps *ui_ops; ++ GList *account; ++ PurpleBuddyList *gbl = g_new0(PurpleBuddyList, 1); ++ PURPLE_DBUS_REGISTER_POINTER(gbl, PurpleBuddyList); ++ ++ ui_ops = purple_blist_get_ui_ops(); ++ ++ gbl->buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash, ++ (GEqualFunc)_purple_blist_hbuddy_equal, ++ (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL); ++ ++ buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal, ++ NULL, (GDestroyNotify)g_hash_table_destroy); ++ ++ groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash, ++ (GEqualFunc)g_str_equal, ++ (GDestroyNotify)g_free, NULL); ++ ++ for (account = purple_accounts_get_all(); account != NULL; account = account->next) ++ { ++ purple_blist_buddies_cache_add_account(account->data); ++ } ++ ++ if (ui_ops != NULL && ui_ops->new_list != NULL) ++ ui_ops->new_list(gbl); ++ ++ return gbl; ++} ++ ++void ++purple_set_blist(PurpleBuddyList *list) ++{ ++ purplebuddylist = list; ++} ++ ++PurpleBuddyList * ++purple_get_blist() ++{ ++ return purplebuddylist; ++} ++ ++PurpleBlistNode * ++purple_blist_get_root() ++{ ++ return purplebuddylist ? purplebuddylist->root : NULL; ++} ++ ++static void ++append_buddy(gpointer key, gpointer value, gpointer user_data) ++{ ++ GSList **list = user_data; ++ *list = g_slist_prepend(*list, value); ++} ++ ++GSList * ++purple_blist_get_buddies() ++{ ++ GSList *buddies = NULL; ++ ++ if (!purplebuddylist) ++ return NULL; ++ ++ g_hash_table_foreach(purplebuddylist->buddies, append_buddy, &buddies); ++ return buddies; ++} ++ ++void * ++purple_blist_get_ui_data() ++{ ++ return purplebuddylist->ui_data; ++} ++ ++void ++purple_blist_set_ui_data(void *ui_data) ++{ ++ purplebuddylist->ui_data = ui_data; ++} ++ ++void purple_blist_show() ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ if (ops && ops->show) ++ ops->show(purplebuddylist); ++} ++ ++void purple_blist_destroy() ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ purple_debug(PURPLE_DEBUG_INFO, "blist", "Destroying\n"); ++ ++ if (ops && ops->destroy) ++ ops->destroy(purplebuddylist); ++} ++ ++void purple_blist_set_visible(gboolean show) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ if (ops && ops->set_visible) ++ ops->set_visible(purplebuddylist, show); ++} ++ ++static PurpleBlistNode *get_next_node(PurpleBlistNode *node, gboolean godeep) ++{ ++ if (node == NULL) ++ return NULL; ++ ++ if (godeep && node->child) ++ return node->child; ++ ++ if (node->next) ++ return node->next; ++ ++ return get_next_node(node->parent, FALSE); ++} ++ ++PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline) ++{ ++ PurpleBlistNode *ret = node; ++ ++ if (offline) ++ return get_next_node(ret, TRUE); ++ do ++ { ++ ret = get_next_node(ret, TRUE); ++ } while (ret && PURPLE_BLIST_NODE_IS_BUDDY(ret) && ++ !purple_account_is_connected(purple_buddy_get_account((PurpleBuddy *)ret))); ++ ++ return ret; ++} ++ ++PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node) ++{ ++ return node ? node->parent : NULL; ++} ++ ++PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node) ++{ ++ return node ? node->child : NULL; ++} ++ ++PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node) ++{ ++ return node? node->next : NULL; ++} ++ ++PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node) ++{ ++ return node? node->prev : NULL; ++} ++ ++void * ++purple_blist_node_get_ui_data(const PurpleBlistNode *node) ++{ ++ g_return_val_if_fail(node, NULL); ++ ++ return node->ui_data; ++} ++ ++void ++purple_blist_node_set_ui_data(PurpleBlistNode *node, void *ui_data) { ++ g_return_if_fail(node); ++ ++ node->ui_data = ui_data; ++} ++ ++void ++purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurplePresence *presence; ++ PurpleStatus *status; ++ PurpleBlistNode *cnode; ++ ++ g_return_if_fail(buddy != NULL); ++ ++ presence = purple_buddy_get_presence(buddy); ++ status = purple_presence_get_active_status(presence); ++ ++ purple_debug_info("blist", "Updating buddy status for %s (%s)\n", ++ buddy->name, purple_account_get_protocol_name(buddy->account)); ++ ++ if (purple_status_is_online(status) && ++ !purple_status_is_online(old_status)) { ++ ++ purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy); ++ ++ cnode = buddy->node.parent; ++ if (++(PURPLE_CONTACT(cnode)->online) == 1) ++ PURPLE_GROUP(cnode->parent)->online++; ++ } else if (!purple_status_is_online(status) && ++ purple_status_is_online(old_status)) { ++ ++ purple_blist_node_set_int(&buddy->node, "last_seen", time(NULL)); ++ purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy); ++ ++ cnode = buddy->node.parent; ++ if (--(PURPLE_CONTACT(cnode)->online) == 0) ++ PURPLE_GROUP(cnode->parent)->online--; ++ } else { ++ purple_signal_emit(purple_blist_get_handle(), ++ "buddy-status-changed", buddy, old_status, ++ status); ++ } ++ ++ /* ++ * This function used to only call the following two functions if one of ++ * the above signals had been triggered, but that's not good, because ++ * if someone's away message changes and they don't go from away to back ++ * to away then no signal is triggered. ++ * ++ * It's a safe assumption that SOMETHING called this function. PROBABLY ++ * because something, somewhere changed. Calling the stuff below ++ * certainly won't hurt anything. Unless you're on a K6-2 300. ++ */ ++ purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy)); ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy); ++} ++ ++void ++purple_blist_update_node_icon(PurpleBlistNode *node) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ g_return_if_fail(node != NULL); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, node); ++} ++ ++void ++purple_blist_update_buddy_icon(PurpleBuddy *buddy) ++{ ++ purple_blist_update_node_icon((PurpleBlistNode *)buddy); ++} ++ ++/* ++ * TODO: Maybe remove the call to this from server.c and call it ++ * from oscar.c and toc.c instead? ++ */ ++void purple_blist_rename_buddy(PurpleBuddy *buddy, const char *name) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ struct _purple_hbuddy *hb, *hb2; ++ GHashTable *account_buddies; ++ ++ g_return_if_fail(buddy != NULL); ++ ++ hb = g_new(struct _purple_hbuddy, 1); ++ hb->name = (gchar *)purple_normalize(buddy->account, buddy->name); ++ hb->account = buddy->account; ++ hb->group = ((PurpleBlistNode *)buddy)->parent->parent; ++ g_hash_table_remove(purplebuddylist->buddies, hb); ++ ++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account); ++ g_hash_table_remove(account_buddies, hb); ++ ++ hb->name = g_strdup(purple_normalize(buddy->account, name)); ++ g_hash_table_replace(purplebuddylist->buddies, hb, buddy); ++ ++ hb2 = g_new(struct _purple_hbuddy, 1); ++ hb2->name = g_strdup(hb->name); ++ hb2->account = buddy->account; ++ hb2->group = ((PurpleBlistNode *)buddy)->parent->parent; ++ ++ g_hash_table_replace(account_buddies, hb2, buddy); ++ ++ g_free(buddy->name); ++ buddy->name = g_strdup(name); ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode *) buddy); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy); ++} ++ ++static gboolean ++purple_strings_are_different(const char *one, const char *two) ++{ ++ return !((one && two && g_utf8_collate(one, two) == 0) || ++ ((one == NULL || *one == '\0') && (two == NULL || *two == '\0'))); ++} ++ ++void purple_blist_alias_contact(PurpleContact *contact, const char *alias) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleConversation *conv; ++ PurpleBlistNode *bnode; ++ char *old_alias; ++ char *new_alias = NULL; ++ ++ g_return_if_fail(contact != NULL); ++ ++ if ((alias != NULL) && (*alias != '\0')) ++ new_alias = purple_utf8_strip_unprintables(alias); ++ ++ if (!purple_strings_are_different(contact->alias, new_alias)) { ++ g_free(new_alias); ++ return; ++ } ++ ++ old_alias = contact->alias; ++ ++ if ((new_alias != NULL) && (*new_alias != '\0')) ++ contact->alias = new_alias; ++ else { ++ contact->alias = NULL; ++ g_free(new_alias); /* could be "\0" */ ++ } ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) contact); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)contact); ++ ++ for(bnode = ((PurpleBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next) ++ { ++ PurpleBuddy *buddy = (PurpleBuddy *)bnode; ++ ++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, ++ buddy->account); ++ if (conv) ++ purple_conversation_autoset_title(conv); ++ } ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", ++ contact, old_alias); ++// g_free(old_alias); ++} ++ ++void purple_blist_alias_chat(PurpleChat *chat, const char *alias) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ char *old_alias; ++ char *new_alias = NULL; ++ ++ g_return_if_fail(chat != NULL); ++ ++ if ((alias != NULL) && (*alias != '\0')) ++ new_alias = purple_utf8_strip_unprintables(alias); ++ ++ if (!purple_strings_are_different(chat->alias, new_alias)) { ++ g_free(new_alias); ++ return; ++ } ++ ++ old_alias = chat->alias; ++ ++ if ((new_alias != NULL) && (*new_alias != '\0')) ++ chat->alias = new_alias; ++ else { ++ chat->alias = NULL; ++ g_free(new_alias); /* could be "\0" */ ++ } ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) chat); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)chat); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", ++ chat, old_alias); ++ g_free(old_alias); ++} ++ ++void purple_blist_alias_buddy(PurpleBuddy *buddy, const char *alias) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleConversation *conv; ++ char *old_alias; ++ char *new_alias = NULL; ++ ++ g_return_if_fail(buddy != NULL); ++ ++ if ((alias != NULL) && (*alias != '\0')) ++ new_alias = purple_utf8_strip_unprintables(alias); ++ ++ if (!purple_strings_are_different(buddy->alias, new_alias)) { ++ g_free(new_alias); ++ return; ++ } ++ ++ old_alias = buddy->alias; ++ ++ if ((new_alias != NULL) && (*new_alias != '\0')) ++ buddy->alias = new_alias; ++ else { ++ buddy->alias = NULL; ++ g_free(new_alias); /* could be "\0" */ ++ } ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) buddy); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy); ++ ++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, ++ buddy->account); ++ if (conv) ++ purple_conversation_autoset_title(conv); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", ++ buddy, old_alias); ++// g_free(old_alias); ++} ++ ++void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleConversation *conv; ++ char *old_alias; ++ char *new_alias = NULL; ++ ++ g_return_if_fail(buddy != NULL); ++ ++ if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL)) ++ new_alias = purple_utf8_strip_unprintables(alias); ++ ++ if (!purple_strings_are_different(buddy->server_alias, new_alias)) { ++ g_free(new_alias); ++ return; ++ } ++ ++ old_alias = buddy->server_alias; ++ ++ if ((new_alias != NULL) && (*new_alias != '\0')) ++ buddy->server_alias = new_alias; ++ else { ++ buddy->server_alias = NULL; ++ g_free(new_alias); /* could be "\0"; */ ++ } ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) buddy); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy); ++ ++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, ++ buddy->account); ++ if (conv) ++ purple_conversation_autoset_title(conv); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased", ++ buddy, old_alias); ++// g_free(old_alias); ++} ++ ++/* ++ * TODO: If merging, prompt the user if they want to merge. ++ */ ++void purple_blist_rename_group(PurpleGroup *source, const char *name) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleGroup *dest; ++ gchar *old_name; ++ gchar *new_name; ++ GList *moved_buddies = NULL; ++ GSList *accts; ++ ++ g_return_if_fail(source != NULL); ++ g_return_if_fail(name != NULL); ++ ++ new_name = purple_utf8_strip_unprintables(name); ++ ++ if (*new_name == '\0' || purple_strequal(new_name, source->name)) { ++ g_free(new_name); ++ return; ++ } ++ ++ dest = purple_find_group(new_name); ++ if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) { ++ /* We're merging two groups */ ++ PurpleBlistNode *prev, *child, *next; ++ ++ prev = purple_blist_get_last_child((PurpleBlistNode*)dest); ++ child = ((PurpleBlistNode*)source)->child; ++ ++ /* ++ * TODO: This seems like a dumb way to do this... why not just ++ * append all children from the old group to the end of the new ++ * one? PRPLs might be expecting to receive an add_buddy() for ++ * each moved buddy... ++ */ ++ while (child) ++ { ++ next = child->next; ++ if (PURPLE_BLIST_NODE_IS_CONTACT(child)) { ++ PurpleBlistNode *bnode; ++ purple_blist_add_contact((PurpleContact *)child, dest, prev); ++ for (bnode = child->child; bnode != NULL; bnode = bnode->next) { ++ purple_blist_add_buddy((PurpleBuddy *)bnode, (PurpleContact *)child, ++ NULL, bnode->prev); ++ moved_buddies = g_list_append(moved_buddies, bnode); ++ } ++ prev = child; ++ } else if (PURPLE_BLIST_NODE_IS_CHAT(child)) { ++ purple_blist_add_chat((PurpleChat *)child, dest, prev); ++ prev = child; ++ } else { ++ purple_debug(PURPLE_DEBUG_ERROR, "blist", ++ "Unknown child type in group %s\n", source->name); ++ } ++ child = next; ++ } ++ ++ /* Make a copy of the old group name and then delete the old group */ ++ old_name = g_strdup(source->name); ++ purple_blist_remove_group(source); ++ source = dest; ++ g_free(new_name); ++ } else { ++ /* A simple rename */ ++ PurpleBlistNode *cnode, *bnode; ++ gchar* key; ++ ++ /* Build a GList of all buddies in this group */ ++ for (cnode = ((PurpleBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) { ++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) ++ for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) ++ moved_buddies = g_list_append(moved_buddies, bnode); ++ } ++ ++ old_name = source->name; ++ source->name = new_name; ++ ++ key = g_utf8_collate_key(old_name, -1); ++ g_hash_table_remove(groups_cache, key); ++ g_free(key); ++ ++ key = g_utf8_collate_key(new_name, -1); ++ g_hash_table_insert(groups_cache, key, source); ++ } ++ ++ /* Save our changes */ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) source); ++ ++ /* Update the UI */ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode*)source); ++ ++ /* Notify all PRPLs */ ++ /* TODO: Is this condition needed? Seems like it would always be TRUE */ ++ if(old_name && !purple_strequal(source->name, old_name)) { ++ for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) { ++ PurpleAccount *account = accts->data; ++ PurpleConnection *gc = NULL; ++ PurplePlugin *prpl = NULL; ++ PurplePluginProtocolInfo *prpl_info = NULL; ++ GList *l = NULL, *buddies = NULL; ++ ++ gc = purple_account_get_connection(account); ++ ++ if(gc) ++ prpl = purple_connection_get_prpl(gc); ++ ++ if(gc && prpl) ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ ++ if(!prpl_info) ++ continue; ++ ++ for(l = moved_buddies; l; l = l->next) { ++ PurpleBuddy *buddy = (PurpleBuddy *)l->data; ++ ++ if(buddy && buddy->account == account) ++ buddies = g_list_append(buddies, (PurpleBlistNode *)buddy); ++ } ++ ++ if(prpl_info->rename_group) { ++ prpl_info->rename_group(gc, old_name, source, buddies); ++ } else { ++ GList *cur, *groups = NULL; ++ ++ /* Make a list of what the groups each buddy is in */ ++ for(cur = buddies; cur; cur = cur->next) { ++ PurpleBlistNode *node = (PurpleBlistNode *)cur->data; ++ groups = g_list_prepend(groups, node->parent->parent); ++ } ++ ++ purple_account_remove_buddies(account, buddies, groups); ++ g_list_free(groups); ++ purple_account_add_buddies(account, buddies); ++ } ++ ++ g_list_free(buddies); ++ } ++ } ++ g_list_free(moved_buddies); ++ g_free(old_name); ++} ++ ++static void purple_blist_node_initialize_settings(PurpleBlistNode *node); ++ ++PurpleChat *purple_chat_new(PurpleAccount *account, const char *alias, GHashTable *components) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleChat *chat; ++ ++ g_return_val_if_fail(account != NULL, NULL); ++ g_return_val_if_fail(components != NULL, NULL); ++ ++ chat = g_new0(PurpleChat, 1); ++ chat->account = account; ++ if ((alias != NULL) && (*alias != '\0')) ++ chat->alias = purple_utf8_strip_unprintables(alias); ++ chat->components = components; ++ purple_blist_node_initialize_settings((PurpleBlistNode *)chat); ++ ((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE; ++ ++ if (ops != NULL && ops->new_node != NULL) ++ ops->new_node((PurpleBlistNode *)chat); ++ ++ PURPLE_DBUS_REGISTER_POINTER(chat, PurpleChat); ++ return chat; ++} ++ ++void ++purple_chat_destroy(PurpleChat *chat) ++{ ++ g_hash_table_destroy(chat->components); ++ g_hash_table_destroy(chat->node.settings); ++ g_free(chat->alias); ++ PURPLE_DBUS_UNREGISTER_POINTER(chat); ++ g_free(chat); ++} ++ ++PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBuddy *buddy; ++ ++ g_return_val_if_fail(account != NULL, NULL); ++ g_return_val_if_fail(name != NULL, NULL); ++ ++ buddy = g_new0(PurpleBuddy, 1); ++ buddy->account = account; ++ buddy->name = purple_utf8_strip_unprintables(name); ++ buddy->alias = purple_utf8_strip_unprintables(alias); ++ buddy->presence = purple_presence_new_for_buddy(buddy); ++ ((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE; ++ ++ purple_presence_set_status_active(buddy->presence, "offline", TRUE); ++ ++ purple_blist_node_initialize_settings((PurpleBlistNode *)buddy); ++ ++ if (ops && ops->new_node) ++ ops->new_node((PurpleBlistNode *)buddy); ++ ++ PURPLE_DBUS_REGISTER_POINTER(buddy, PurpleBuddy); ++ return buddy; ++} ++ ++void ++purple_buddy_destroy(PurpleBuddy *buddy) ++{ ++ PurplePlugin *prpl; ++ PurplePluginProtocolInfo *prpl_info; ++ ++ /* ++ * Tell the owner PRPL that we're about to free the buddy so it ++ * can free proto_data ++ */ ++ prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account)); ++ if (prpl) { ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ if (prpl_info && prpl_info->buddy_free) ++ prpl_info->buddy_free(buddy); ++ } ++ ++ /* Delete the node */ ++ purple_buddy_icon_unref(buddy->icon); ++ g_hash_table_destroy(buddy->node.settings); ++ purple_presence_destroy(buddy->presence); ++ g_free(buddy->name); ++ g_free(buddy->alias); ++ g_free(buddy->server_alias); ++ ++ PURPLE_DBUS_UNREGISTER_POINTER(buddy); ++ g_free(buddy); ++ ++ /* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can ++ * g_object_ref() it when connecting the callback and ++ * g_object_unref() it in the handler. That way, it won't ++ * get freed while the timeout is pending and this line can ++ * be removed. */ ++ while (g_source_remove_by_user_data((gpointer *)buddy)); ++} ++ ++void ++purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon) ++{ ++ g_return_if_fail(buddy != NULL); ++ ++ if (buddy->icon != icon) ++ { ++ purple_buddy_icon_unref(buddy->icon); ++ buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL); ++ } ++ ++ purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy); ++ ++ purple_blist_update_node_icon((PurpleBlistNode*)buddy); ++} ++ ++PurpleAccount * ++purple_buddy_get_account(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ return buddy->account; ++} ++ ++const char * ++purple_buddy_get_name(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ return buddy->name; ++} ++ ++PurpleBuddyIcon * ++purple_buddy_get_icon(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ return buddy->icon; ++} ++ ++gpointer ++purple_buddy_get_protocol_data(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ return buddy->proto_data; ++} ++ ++void ++purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data) ++{ ++ g_return_if_fail(buddy != NULL); ++ ++ buddy->proto_data = data; ++} ++ ++ ++void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node) ++{ ++ PurpleBlistNode *cnode = (PurpleBlistNode*)chat; ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ g_return_if_fail(chat != NULL); ++ g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT((PurpleBlistNode *)chat)); ++ ++ if (node == NULL) { ++ if (group == NULL) ++ group = purple_group_new(_("Chats")); ++ ++ /* Add group to blist if isn't already on it. Fixes #2752. */ ++ if (!purple_find_group(group->name)) { ++ purple_blist_add_group(group, ++ purple_blist_get_last_sibling(purplebuddylist->root)); ++ } ++ } else { ++ group = (PurpleGroup*)node->parent; ++ } ++ ++ /* if we're moving to overtop of ourselves, do nothing */ ++ if (cnode == node) ++ return; ++ ++ if (cnode->parent) { ++ /* This chat was already in the list and is ++ * being moved. ++ */ ++ ((PurpleGroup *)cnode->parent)->totalsize--; ++ if (purple_account_is_connected(chat->account)) { ++ ((PurpleGroup *)cnode->parent)->online--; ++ ((PurpleGroup *)cnode->parent)->currentsize--; ++ } ++ if (cnode->next) ++ cnode->next->prev = cnode->prev; ++ if (cnode->prev) ++ cnode->prev->next = cnode->next; ++ if (cnode->parent->child == cnode) ++ cnode->parent->child = cnode->next; ++ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, cnode); ++ /* ops->remove() cleaned up the cnode's ui_data, so we need to ++ * reinitialize it */ ++ if (ops && ops->new_node) ++ ops->new_node(cnode); ++ } ++ ++ if (node != NULL) { ++ if (node->next) ++ node->next->prev = cnode; ++ cnode->next = node->next; ++ cnode->prev = node; ++ cnode->parent = node->parent; ++ node->next = cnode; ++ ((PurpleGroup *)node->parent)->totalsize++; ++ if (purple_account_is_connected(chat->account)) { ++ ((PurpleGroup *)node->parent)->online++; ++ ((PurpleGroup *)node->parent)->currentsize++; ++ } ++ } else { ++ if (((PurpleBlistNode *)group)->child) ++ ((PurpleBlistNode *)group)->child->prev = cnode; ++ cnode->next = ((PurpleBlistNode *)group)->child; ++ cnode->prev = NULL; ++ ((PurpleBlistNode *)group)->child = cnode; ++ cnode->parent = (PurpleBlistNode *)group; ++ group->totalsize++; ++ if (purple_account_is_connected(chat->account)) { ++ group->online++; ++ group->currentsize++; ++ } ++ } ++ ++ if (ops && ops->save_node) ++ ops->save_node(cnode); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode *)cnode); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added", ++ cnode); ++} ++ ++void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node) ++{ ++ PurpleBlistNode *cnode, *bnode; ++ PurpleGroup *g; ++ PurpleContact *c; ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ struct _purple_hbuddy *hb, *hb2; ++ GHashTable *account_buddies; ++ ++ g_return_if_fail(buddy != NULL); ++ g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy)); ++ ++ bnode = (PurpleBlistNode *)buddy; ++ ++ /* if we're moving to overtop of ourselves, do nothing */ ++ if (bnode == node || (!node && bnode->parent && ++ contact && bnode->parent == (PurpleBlistNode*)contact ++ && bnode == bnode->parent->child)) ++ return; ++ ++ if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) { ++ c = (PurpleContact*)node->parent; ++ g = (PurpleGroup*)node->parent->parent; ++ } else if (contact) { ++ c = contact; ++ g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent); ++ } else { ++ g = group; ++ if (g == NULL) ++ g = purple_group_new(_("Buddies")); ++ /* Add group to blist if isn't already on it. Fixes #2752. */ ++ if (!purple_find_group(g->name)) { ++ purple_blist_add_group(g, ++ purple_blist_get_last_sibling(purplebuddylist->root)); ++ } ++ c = purple_contact_new(); ++ purple_blist_add_contact(c, g, ++ purple_blist_get_last_child((PurpleBlistNode*)g)); ++ } ++ ++ cnode = (PurpleBlistNode *)c; ++ ++ if (bnode->parent) { ++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) { ++ ((PurpleContact*)bnode->parent)->online--; ++ if (((PurpleContact*)bnode->parent)->online == 0) ++ ((PurpleGroup*)bnode->parent->parent)->online--; ++ } ++ if (purple_account_is_connected(buddy->account)) { ++ ((PurpleContact*)bnode->parent)->currentsize--; ++ if (((PurpleContact*)bnode->parent)->currentsize == 0) ++ ((PurpleGroup*)bnode->parent->parent)->currentsize--; ++ } ++ ((PurpleContact*)bnode->parent)->totalsize--; ++ /* the group totalsize will be taken care of by remove_contact below */ ++ ++ if (bnode->parent->parent != (PurpleBlistNode*)g) ++ serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g); ++ ++ if (bnode->next) ++ bnode->next->prev = bnode->prev; ++ if (bnode->prev) ++ bnode->prev->next = bnode->next; ++ if (bnode->parent->child == bnode) ++ bnode->parent->child = bnode->next; ++ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, bnode); ++ ++ if (bnode->parent->parent != (PurpleBlistNode*)g) { ++ struct _purple_hbuddy hb; ++ hb.name = (gchar *)purple_normalize(buddy->account, buddy->name); ++ hb.account = buddy->account; ++ hb.group = bnode->parent->parent; ++ g_hash_table_remove(purplebuddylist->buddies, &hb); ++ ++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account); ++ g_hash_table_remove(account_buddies, &hb); ++ } ++ ++ if (!bnode->parent->child) { ++ purple_blist_remove_contact((PurpleContact*)bnode->parent); ++ } else { ++ purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent); ++ if (ops && ops->update) ++ ops->update(purplebuddylist, bnode->parent); ++ } ++ } ++ ++ if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) { ++ if (node->next) ++ node->next->prev = bnode; ++ bnode->next = node->next; ++ bnode->prev = node; ++ bnode->parent = node->parent; ++ node->next = bnode; ++ } else { ++ if (cnode->child) ++ cnode->child->prev = bnode; ++ bnode->prev = NULL; ++ bnode->next = cnode->child; ++ cnode->child = bnode; ++ bnode->parent = cnode; ++ } ++ ++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) { ++ if (++(PURPLE_CONTACT(bnode->parent)->online) == 1) ++ PURPLE_GROUP(bnode->parent->parent)->online++; ++ } ++ if (purple_account_is_connected(buddy->account)) { ++ if (++(PURPLE_CONTACT(bnode->parent)->currentsize) == 1) ++ PURPLE_GROUP(bnode->parent->parent)->currentsize++; ++ } ++ PURPLE_CONTACT(bnode->parent)->totalsize++; ++ ++ hb = g_new(struct _purple_hbuddy, 1); ++ hb->name = g_strdup(purple_normalize(buddy->account, buddy->name)); ++ hb->account = buddy->account; ++ hb->group = ((PurpleBlistNode*)buddy)->parent->parent; ++ ++ g_hash_table_replace(purplebuddylist->buddies, hb, buddy); ++ ++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account); ++ ++ hb2 = g_new(struct _purple_hbuddy, 1); ++ hb2->name = g_strdup(hb->name); ++ hb2->account = buddy->account; ++ hb2->group = ((PurpleBlistNode*)buddy)->parent->parent; ++ ++ g_hash_table_replace(account_buddies, hb2, buddy); ++ ++ purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy)); ++ ++ if (ops && ops->save_node) ++ ops->save_node((PurpleBlistNode*) buddy); ++ ++ if (ops && ops->update) ++ ops->update(purplebuddylist, (PurpleBlistNode*)buddy); ++ ++ /* Signal that the buddy has been added */ ++ purple_signal_emit(purple_blist_get_handle(), "buddy-added", buddy); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added", ++ PURPLE_BLIST_NODE(buddy)); ++} ++ ++PurpleContact *purple_contact_new() ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ ++ PurpleContact *contact = g_new0(PurpleContact, 1); ++ contact->totalsize = 0; ++ contact->currentsize = 0; ++ contact->online = 0; ++ purple_blist_node_initialize_settings((PurpleBlistNode *)contact); ++ ((PurpleBlistNode *)contact)->type = PURPLE_BLIST_CONTACT_NODE; ++ ++ if (ops && ops->new_node) ++ ops->new_node((PurpleBlistNode *)contact); ++ ++ PURPLE_DBUS_REGISTER_POINTER(contact, PurpleContact); ++ return contact; ++} ++ ++void ++purple_contact_destroy(PurpleContact *contact) ++{ ++ g_hash_table_destroy(contact->node.settings); ++ g_free(contact->alias); ++ PURPLE_DBUS_UNREGISTER_POINTER(contact); ++ g_free(contact); ++} ++ ++PurpleGroup * ++purple_contact_get_group(const PurpleContact *contact) ++{ ++ g_return_val_if_fail(contact, NULL); ++ ++ return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent); ++} ++ ++void purple_contact_set_alias(PurpleContact *contact, const char *alias) ++{ ++ purple_blist_alias_contact(contact,alias); ++} ++ ++const char *purple_contact_get_alias(PurpleContact* contact) ++{ ++ g_return_val_if_fail(contact != NULL, NULL); ++ ++ if (contact->alias) ++ return contact->alias; ++ ++ return purple_buddy_get_alias(purple_contact_get_priority_buddy(contact)); ++} ++ ++gboolean purple_contact_on_account(PurpleContact *c, PurpleAccount *account) ++{ ++ PurpleBlistNode *bnode, *cnode = (PurpleBlistNode *) c; ++ ++ g_return_val_if_fail(c != NULL, FALSE); ++ g_return_val_if_fail(account != NULL, FALSE); ++ ++ for (bnode = cnode->child; bnode; bnode = bnode->next) { ++ PurpleBuddy *buddy; ++ ++ if (! PURPLE_BLIST_NODE_IS_BUDDY(bnode)) ++ continue; ++ ++ buddy = (PurpleBuddy *)bnode; ++ if (buddy->account == account) ++ return TRUE; ++ } ++ return FALSE; ++} ++ ++void purple_contact_invalidate_priority_buddy(PurpleContact *contact) ++{ ++ g_return_if_fail(contact != NULL); ++ ++ contact->priority_valid = FALSE; ++} ++ ++PurpleGroup *purple_group_new(const char *name) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleGroup *group; ++ ++ g_return_val_if_fail(name != NULL, NULL); ++ g_return_val_if_fail(*name != '\0', NULL); ++ ++ group = purple_find_group(name); ++ if (group != NULL) ++ return group; ++ ++ group = g_new0(PurpleGroup, 1); ++ group->name = purple_utf8_strip_unprintables(name); ++ group->totalsize = 0; ++ group->currentsize = 0; ++ group->online = 0; ++ purple_blist_node_initialize_settings((PurpleBlistNode *)group); ++ ((PurpleBlistNode *)group)->type = PURPLE_BLIST_GROUP_NODE; ++ ++ if (ops && ops->new_node) ++ ops->new_node((PurpleBlistNode *)group); ++ ++ PURPLE_DBUS_REGISTER_POINTER(group, PurpleGroup); ++ return group; ++} ++ ++void ++purple_group_destroy(PurpleGroup *group) ++{ ++ g_hash_table_destroy(group->node.settings); ++ g_free(group->name); ++ PURPLE_DBUS_UNREGISTER_POINTER(group); ++ g_free(group); ++} ++ ++void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleGroup *g; ++ PurpleBlistNode *gnode, *cnode, *bnode; ++ ++ g_return_if_fail(contact != NULL); ++ g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT((PurpleBlistNode*)contact)); ++ ++ if (PURPLE_BLIST_NODE(contact) == node) ++ return; ++ ++ if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) || ++ PURPLE_BLIST_NODE_IS_CHAT(node))) ++ g = (PurpleGroup*)node->parent; ++ else if (group) ++ g = group; ++ else { ++ g = purple_find_group(_("Buddies")); ++ if (g == NULL) { ++ g = purple_group_new(_("Buddies")); ++ purple_blist_add_group(g, ++ purple_blist_get_last_sibling(purplebuddylist->root)); ++ } ++ } ++ ++ gnode = (PurpleBlistNode*)g; ++ cnode = (PurpleBlistNode*)contact; ++ ++ if (cnode->parent) { ++ if (cnode->parent->child == cnode) ++ cnode->parent->child = cnode->next; ++ if (cnode->prev) ++ cnode->prev->next = cnode->next; ++ if (cnode->next) ++ cnode->next->prev = cnode->prev; ++ ++ if (cnode->parent != gnode) { ++ bnode = cnode->child; ++ while (bnode) { ++ PurpleBlistNode *next_bnode = bnode->next; ++ PurpleBuddy *b = (PurpleBuddy*)bnode; ++ GHashTable *account_buddies; ++ ++ struct _purple_hbuddy *hb, *hb2; ++ ++ hb = g_new(struct _purple_hbuddy, 1); ++ hb->name = g_strdup(purple_normalize(b->account, b->name)); ++ hb->account = b->account; ++ hb->group = cnode->parent; ++ ++ g_hash_table_remove(purplebuddylist->buddies, hb); ++ ++ account_buddies = g_hash_table_lookup(buddies_cache, b->account); ++ g_hash_table_remove(account_buddies, hb); ++ ++ if (!purple_find_buddy_in_group(b->account, b->name, g)) { ++ hb->group = gnode; ++ g_hash_table_replace(purplebuddylist->buddies, hb, b); ++ ++ hb2 = g_new(struct _purple_hbuddy, 1); ++ hb2->name = g_strdup(hb->name); ++ hb2->account = b->account; ++ hb2->group = gnode; ++ ++ g_hash_table_replace(account_buddies, hb2, b); ++ ++ if (purple_account_get_connection(b->account)) ++ serv_move_buddy(b, (PurpleGroup *)cnode->parent, g); ++ } else { ++ gboolean empty_contact = FALSE; ++ ++ /* this buddy already exists in the group, so we're ++ * gonna delete it instead */ ++ g_free(hb->name); ++ g_free(hb); ++ if (purple_account_get_connection(b->account)) ++ purple_account_remove_buddy(b->account, b, (PurpleGroup *)cnode->parent); ++ ++ if (!cnode->child->next) ++ empty_contact = TRUE; ++ purple_blist_remove_buddy(b); ++ ++ /** in purple_blist_remove_buddy(), if the last buddy in a ++ * contact is removed, the contact is cleaned up and ++ * g_free'd, so we mustn't try to reference bnode->next */ ++ if (empty_contact) ++ return; ++ } ++ bnode = next_bnode; ++ } ++ } ++ ++ if (contact->online > 0) ++ ((PurpleGroup*)cnode->parent)->online--; ++ if (contact->currentsize > 0) ++ ((PurpleGroup*)cnode->parent)->currentsize--; ++ ((PurpleGroup*)cnode->parent)->totalsize--; ++ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, cnode); ++ ++ if (ops && ops->remove_node) ++ ops->remove_node(cnode); ++ } ++ ++ if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) || ++ PURPLE_BLIST_NODE_IS_CHAT(node))) { ++ if (node->next) ++ node->next->prev = cnode; ++ cnode->next = node->next; ++ cnode->prev = node; ++ cnode->parent = node->parent; ++ node->next = cnode; ++ } else { ++ if (gnode->child) ++ gnode->child->prev = cnode; ++ cnode->prev = NULL; ++ cnode->next = gnode->child; ++ gnode->child = cnode; ++ cnode->parent = gnode; ++ } ++ ++ if (contact->online > 0) ++ g->online++; ++ if (contact->currentsize > 0) ++ g->currentsize++; ++ g->totalsize++; ++ ++ if (ops && ops->save_node) ++ { ++ if (cnode->child) ++ ops->save_node(cnode); ++ for (bnode = cnode->child; bnode; bnode = bnode->next) ++ ops->save_node(bnode); ++ } ++ ++ if (ops && ops->update) ++ { ++ if (cnode->child) ++ ops->update(purplebuddylist, cnode); ++ ++ for (bnode = cnode->child; bnode; bnode = bnode->next) ++ ops->update(purplebuddylist, bnode); ++ } ++} ++ ++void purple_blist_merge_contact(PurpleContact *source, PurpleBlistNode *node) ++{ ++ PurpleBlistNode *sourcenode = (PurpleBlistNode*)source; ++ PurpleBlistNode *prev, *cur, *next; ++ PurpleContact *target; ++ ++ g_return_if_fail(source != NULL); ++ g_return_if_fail(node != NULL); ++ ++ if (PURPLE_BLIST_NODE_IS_CONTACT(node)) { ++ target = (PurpleContact *)node; ++ prev = purple_blist_get_last_child(node); ++ } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { ++ target = (PurpleContact *)node->parent; ++ prev = node; ++ } else { ++ return; ++ } ++ ++ if (source == target || !target) ++ return; ++ ++ next = sourcenode->child; ++ ++ while (next) { ++ cur = next; ++ next = cur->next; ++ if (PURPLE_BLIST_NODE_IS_BUDDY(cur)) { ++ purple_blist_add_buddy((PurpleBuddy *)cur, target, NULL, prev); ++ prev = cur; ++ } ++ } ++} ++ ++void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node) ++{ ++ PurpleBlistUiOps *ops; ++ PurpleBlistNode *gnode = (PurpleBlistNode*)group; ++ gchar* key; ++ ++ g_return_if_fail(group != NULL); ++ g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP((PurpleBlistNode *)group)); ++ ++ ops = purple_blist_get_ui_ops(); ++ ++ /* if we're moving to overtop of ourselves, do nothing */ ++ if (gnode == node) { ++ if (!purplebuddylist->root) ++ node = NULL; ++ else ++ return; ++ } ++ ++ if (purple_find_group(group->name)) { ++ /* This is just being moved */ ++ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, (PurpleBlistNode *)group); ++ ++ if (gnode == purplebuddylist->root) ++ purplebuddylist->root = gnode->next; ++ if (gnode->prev) ++ gnode->prev->next = gnode->next; ++ if (gnode->next) ++ gnode->next->prev = gnode->prev; ++ } else { ++ key = g_utf8_collate_key(group->name, -1); ++ g_hash_table_insert(groups_cache, key, group); ++ } ++ ++ if (node && PURPLE_BLIST_NODE_IS_GROUP(node)) { ++ gnode->next = node->next; ++ gnode->prev = node; ++ if (node->next) ++ node->next->prev = gnode; ++ node->next = gnode; ++ } else { ++ if (purplebuddylist->root) ++ purplebuddylist->root->prev = gnode; ++ gnode->next = purplebuddylist->root; ++ gnode->prev = NULL; ++ purplebuddylist->root = gnode; ++ } ++ ++ if (ops && ops->save_node) { ++ ops->save_node(gnode); ++ for (node = gnode->child; node; node = node->next) ++ ops->save_node(node); ++ } ++ ++ if (ops && ops->update) { ++ ops->update(purplebuddylist, gnode); ++ for (node = gnode->child; node; node = node->next) ++ ops->update(purplebuddylist, node); ++ } ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added", ++ gnode); ++} ++ ++void purple_blist_remove_contact(PurpleContact *contact) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *node, *gnode; ++ ++ g_return_if_fail(contact != NULL); ++ ++ node = (PurpleBlistNode *)contact; ++ gnode = node->parent; ++ ++ if (node->child) { ++ /* ++ * If this contact has children then remove them. When the last ++ * buddy is removed from the contact, the contact is automatically ++ * deleted. ++ */ ++ while (node->child->next) { ++ purple_blist_remove_buddy((PurpleBuddy*)node->child); ++ } ++ /* ++ * Remove the last buddy and trigger the deletion of the contact. ++ * It would probably be cleaner if contact-deletion was done after ++ * a timeout? Or if it had to be done manually, like below? ++ */ ++ purple_blist_remove_buddy((PurpleBuddy*)node->child); ++ } else { ++ /* Remove the node from its parent */ ++ if (gnode->child == node) ++ gnode->child = node->next; ++ if (node->prev) ++ node->prev->next = node->next; ++ if (node->next) ++ node->next->prev = node->prev; ++ ++ /* Update the UI */ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, node); ++ ++ if (ops && ops->remove_node) ++ ops->remove_node(node); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed", ++ PURPLE_BLIST_NODE(contact)); ++ ++ /* Delete the node */ ++ purple_contact_destroy(contact); ++ } ++} ++ ++void purple_blist_remove_buddy(PurpleBuddy *buddy) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *node, *cnode, *gnode; ++ PurpleContact *contact; ++ PurpleGroup *group; ++ struct _purple_hbuddy hb; ++ GHashTable *account_buddies; ++ ++ g_return_if_fail(buddy != NULL); ++ ++ node = (PurpleBlistNode *)buddy; ++ cnode = node->parent; ++ gnode = (cnode != NULL) ? cnode->parent : NULL; ++ contact = (PurpleContact *)cnode; ++ group = (PurpleGroup *)gnode; ++ ++ /* Remove the node from its parent */ ++ if (node->prev) ++ node->prev->next = node->next; ++ if (node->next) ++ node->next->prev = node->prev; ++ if ((cnode != NULL) && (cnode->child == node)) ++ cnode->child = node->next; ++ ++ /* Adjust size counts */ ++ if (contact != NULL) { ++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) { ++ contact->online--; ++ if (contact->online == 0) ++ group->online--; ++ } ++ if (purple_account_is_connected(buddy->account)) { ++ contact->currentsize--; ++ if (contact->currentsize == 0) ++ group->currentsize--; ++ } ++ contact->totalsize--; ++ ++ /* Re-sort the contact */ ++ if (cnode->child && contact->priority == buddy) { ++ purple_contact_invalidate_priority_buddy(contact); ++ if (ops && ops->update) ++ ops->update(purplebuddylist, cnode); ++ } ++ } ++ ++ /* Remove this buddy from the buddies hash table */ ++ hb.name = (gchar *)purple_normalize(buddy->account, buddy->name); ++ hb.account = buddy->account; ++ hb.group = gnode; ++ g_hash_table_remove(purplebuddylist->buddies, &hb); ++ ++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account); ++ g_hash_table_remove(account_buddies, &hb); ++ ++ /* Update the UI */ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, node); ++ ++ if (ops && ops->remove_node) ++ ops->remove_node(node); ++ ++ /* Signal that the buddy has been removed before freeing the memory for it */ ++ purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed", ++ PURPLE_BLIST_NODE(buddy)); ++ ++ purple_buddy_destroy(buddy); ++ ++ /* If the contact is empty then remove it */ ++ if ((contact != NULL) && !cnode->child) ++ purple_blist_remove_contact(contact); ++} ++ ++void purple_blist_remove_chat(PurpleChat *chat) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *node, *gnode; ++ PurpleGroup *group; ++ ++ g_return_if_fail(chat != NULL); ++ ++ node = (PurpleBlistNode *)chat; ++ gnode = node->parent; ++ group = (PurpleGroup *)gnode; ++ ++ if (gnode != NULL) ++ { ++ /* Remove the node from its parent */ ++ if (gnode->child == node) ++ gnode->child = node->next; ++ if (node->prev) ++ node->prev->next = node->next; ++ if (node->next) ++ node->next->prev = node->prev; ++ ++ /* Adjust size counts */ ++ if (purple_account_is_connected(chat->account)) { ++ group->online--; ++ group->currentsize--; ++ } ++ group->totalsize--; ++ ++ } ++ ++ /* Update the UI */ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, node); ++ ++ if (ops && ops->remove_node) ++ ops->remove_node(node); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed", ++ PURPLE_BLIST_NODE(chat)); ++ ++ /* Delete the node */ ++ purple_chat_destroy(chat); ++} ++ ++void purple_blist_remove_group(PurpleGroup *group) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *node; ++ GList *l; ++ gchar* key; ++ ++ g_return_if_fail(group != NULL); ++ ++ node = (PurpleBlistNode *)group; ++ ++ /* Make sure the group is empty */ ++ if (node->child) ++ return; ++ ++ /* Remove the node from its parent */ ++ if (purplebuddylist->root == node) ++ purplebuddylist->root = node->next; ++ if (node->prev) ++ node->prev->next = node->next; ++ if (node->next) ++ node->next->prev = node->prev; ++ ++ key = g_utf8_collate_key(group->name, -1); ++ g_hash_table_remove(groups_cache, key); ++ g_free(key); ++ ++ /* Update the UI */ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, node); ++ ++ if (ops && ops->remove_node) ++ ops->remove_node(node); ++ ++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed", ++ PURPLE_BLIST_NODE(group)); ++ ++ /* Remove the group from all accounts that are online */ ++ for (l = purple_connections_get_all(); l != NULL; l = l->next) ++ { ++ PurpleConnection *gc = (PurpleConnection *)l->data; ++ ++ if (purple_connection_get_state(gc) == PURPLE_CONNECTED) ++ purple_account_remove_group(purple_connection_get_account(gc), group); ++ } ++ ++ /* Delete the node */ ++ purple_group_destroy(group); ++} ++ ++PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact) ++{ ++ g_return_val_if_fail(contact != NULL, NULL); ++ ++ if (!contact->priority_valid) ++ purple_contact_compute_priority_buddy(contact); ++ ++ return contact->priority; ++} ++ ++const char *purple_buddy_get_alias_only(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ if ((buddy->alias != NULL) && (*buddy->alias != '\0')) { ++ return buddy->alias; ++ } else if ((buddy->server_alias != NULL) && ++ (*buddy->server_alias != '\0')) { ++ ++ return buddy->server_alias; ++ } ++ ++ return NULL; ++} ++ ++ ++const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy) ++{ ++ PurpleContact *c; ++ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ /* Search for an alias for the buddy. In order of precedence: */ ++ /* The buddy alias */ ++ if (buddy->alias != NULL) ++ return buddy->alias; ++ ++ /* The contact alias */ ++ c = purple_buddy_get_contact(buddy); ++ if ((c != NULL) && (c->alias != NULL)) ++ return c->alias; ++ ++ /* The server alias */ ++ if ((buddy->server_alias) && (*buddy->server_alias)) ++ return buddy->server_alias; ++ ++ /* The buddy's user name (i.e. no alias) */ ++ return buddy->name; ++} ++ ++ ++const char *purple_buddy_get_alias(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ /* Search for an alias for the buddy. In order of precedence: */ ++ /* The buddy alias */ ++ if (buddy->alias != NULL) ++ return buddy->alias; ++ ++ /* The server alias */ ++ if ((buddy->server_alias) && (*buddy->server_alias)) ++ return buddy->server_alias; ++ ++ /* The buddy's user name (i.e. no alias) */ ++ return buddy->name; ++} ++ ++const char *purple_buddy_get_local_buddy_alias(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy, NULL); ++ return buddy->alias; ++} ++ ++const char *purple_buddy_get_server_alias(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ if ((buddy->server_alias) && (*buddy->server_alias)) ++ return buddy->server_alias; ++ ++ return NULL; ++} ++ ++const char *purple_buddy_get_local_alias(PurpleBuddy *buddy) ++{ ++ PurpleContact *c; ++ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ /* Search for an alias for the buddy. In order of precedence: */ ++ /* The buddy alias */ ++ if (buddy->alias != NULL) ++ return buddy->alias; ++ ++ /* The contact alias */ ++ c = purple_buddy_get_contact(buddy); ++ if ((c != NULL) && (c->alias != NULL)) ++ return c->alias; ++ ++ /* The buddy's user name (i.e. no alias) */ ++ return buddy->name; ++} ++ ++const char *purple_chat_get_name(PurpleChat *chat) ++{ ++ char *ret = NULL; ++ PurplePlugin *prpl; ++ PurplePluginProtocolInfo *prpl_info = NULL; ++ ++ g_return_val_if_fail(chat != NULL, NULL); ++ ++ if ((chat->alias != NULL) && (*chat->alias != '\0')) ++ return chat->alias; ++ ++ prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account)); ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ ++ if (prpl_info->chat_info) { ++ struct proto_chat_entry *pce; ++ GList *parts = prpl_info->chat_info(purple_account_get_connection(chat->account)); ++ pce = parts->data; ++ ret = g_hash_table_lookup(chat->components, pce->identifier); ++ g_list_foreach(parts, (GFunc)g_free, NULL); ++ g_list_free(parts); ++ } ++ ++ return ret; ++} ++ ++PurpleBuddy *purple_find_buddy(PurpleAccount *account, const char *name) ++{ ++ PurpleBuddy *buddy; ++ struct _purple_hbuddy hb; ++ PurpleBlistNode *group; ++ ++ g_return_val_if_fail(purplebuddylist != NULL, NULL); ++ g_return_val_if_fail(account != NULL, NULL); ++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); ++ ++ hb.account = account; ++ hb.name = (gchar *)purple_normalize(account, name); ++ ++ for (group = purplebuddylist->root; group; group = group->next) { ++ if (!group->child) ++ continue; ++ ++ hb.group = group; ++ if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb))) { ++ return buddy; ++ } ++ } ++ ++ return NULL; ++} ++ ++PurpleBuddy *purple_find_buddy_in_group(PurpleAccount *account, const char *name, ++ PurpleGroup *group) ++{ ++ struct _purple_hbuddy hb; ++ ++ g_return_val_if_fail(purplebuddylist != NULL, NULL); ++ g_return_val_if_fail(account != NULL, NULL); ++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); ++ ++ hb.name = (gchar *)purple_normalize(account, name); ++ hb.account = account; ++ hb.group = (PurpleBlistNode*)group; ++ ++ return g_hash_table_lookup(purplebuddylist->buddies, &hb); ++} ++ ++static void find_acct_buddies(gpointer key, gpointer value, gpointer data) ++{ ++ PurpleBuddy *buddy = value; ++ GSList **list = data; ++ ++ *list = g_slist_prepend(*list, buddy); ++} ++ ++GSList *purple_find_buddies(PurpleAccount *account, const char *name) ++{ ++ PurpleBuddy *buddy; ++ PurpleBlistNode *node; ++ GSList *ret = NULL; ++ ++ g_return_val_if_fail(purplebuddylist != NULL, NULL); ++ g_return_val_if_fail(account != NULL, NULL); ++ ++ if ((name != NULL) && (*name != '\0')) { ++ struct _purple_hbuddy hb; ++ ++ hb.name = (gchar *)purple_normalize(account, name); ++ hb.account = account; ++ ++ for (node = purplebuddylist->root; node != NULL; node = node->next) { ++ if (!node->child) ++ continue; ++ ++ hb.group = node; ++ if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb)) != NULL) ++ ret = g_slist_prepend(ret, buddy); ++ } ++ } else { ++ GSList *list = NULL; ++ GHashTable *buddies = g_hash_table_lookup(buddies_cache, account); ++ g_hash_table_foreach(buddies, find_acct_buddies, &list); ++ ret = list; ++ } ++ ++ return ret; ++} ++ ++PurpleGroup *purple_find_group(const char *name) ++{ ++ gchar* key; ++ PurpleGroup *group; ++ ++ g_return_val_if_fail(purplebuddylist != NULL, NULL); ++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); ++ ++ key = g_utf8_collate_key(name, -1); ++ group = g_hash_table_lookup(groups_cache, key); ++ g_free(key); ++ ++ return group; ++} ++ ++PurpleChat * ++purple_blist_find_chat(PurpleAccount *account, const char *name) ++{ ++ char *chat_name; ++ PurpleChat *chat; ++ PurplePlugin *prpl; ++ PurplePluginProtocolInfo *prpl_info = NULL; ++ struct proto_chat_entry *pce; ++ PurpleBlistNode *node, *group; ++ GList *parts; ++ char *normname; ++ ++ g_return_val_if_fail(purplebuddylist != NULL, NULL); ++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); ++ ++ if (!purple_account_is_connected(account)) ++ return NULL; ++ ++ prpl = purple_find_prpl(purple_account_get_protocol_id(account)); ++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); ++ ++ if (prpl_info->find_blist_chat != NULL) ++ return prpl_info->find_blist_chat(account, name); ++ ++ normname = g_strdup(purple_normalize(account, name)); ++ for (group = purplebuddylist->root; group != NULL; group = group->next) { ++ for (node = group->child; node != NULL; node = node->next) { ++ if (PURPLE_BLIST_NODE_IS_CHAT(node)) { ++ ++ chat = (PurpleChat*)node; ++ ++ if (account != chat->account) ++ continue; ++ ++ parts = prpl_info->chat_info( ++ purple_account_get_connection(chat->account)); ++ ++ pce = parts->data; ++ chat_name = g_hash_table_lookup(chat->components, ++ pce->identifier); ++ g_list_foreach(parts, (GFunc)g_free, NULL); ++ g_list_free(parts); ++ ++ if (chat->account == account && chat_name != NULL && ++ normname != NULL && !strcmp(purple_normalize(account, chat_name), normname)) { ++ g_free(normname); ++ return chat; ++ } ++ } ++ } ++ } ++ ++ g_free(normname); ++ return NULL; ++} ++ ++PurpleGroup * ++purple_chat_get_group(PurpleChat *chat) ++{ ++ g_return_val_if_fail(chat != NULL, NULL); ++ ++ return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent); ++} ++ ++PurpleAccount * ++purple_chat_get_account(PurpleChat *chat) ++{ ++ g_return_val_if_fail(chat != NULL, NULL); ++ ++ return chat->account; ++} ++ ++GHashTable * ++purple_chat_get_components(PurpleChat *chat) ++{ ++ g_return_val_if_fail(chat != NULL, NULL); ++ ++ return chat->components; ++} ++ ++PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent); ++} ++ ++PurplePresence *purple_buddy_get_presence(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ return buddy->presence; ++} ++ ++PurpleMediaCaps purple_buddy_get_media_caps(const PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, 0); ++ return buddy->media_caps; ++} ++ ++void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps) ++{ ++ g_return_if_fail(buddy != NULL); ++ buddy->media_caps = media_caps; ++} ++ ++PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy) ++{ ++ g_return_val_if_fail(buddy != NULL, NULL); ++ ++ if (((PurpleBlistNode *)buddy)->parent == NULL) ++ return NULL; ++ ++ return (PurpleGroup *)(((PurpleBlistNode*)buddy)->parent->parent); ++} ++ ++GSList *purple_group_get_accounts(PurpleGroup *group) ++{ ++ GSList *l = NULL; ++ PurpleBlistNode *gnode, *cnode, *bnode; ++ ++ gnode = (PurpleBlistNode *)group; ++ ++ for (cnode = gnode->child; cnode; cnode = cnode->next) { ++ if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) { ++ if (!g_slist_find(l, ((PurpleChat *)cnode)->account)) ++ l = g_slist_append(l, ((PurpleChat *)cnode)->account); ++ } else if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) { ++ for (bnode = cnode->child; bnode; bnode = bnode->next) { ++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) { ++ if (!g_slist_find(l, ((PurpleBuddy *)bnode)->account)) ++ l = g_slist_append(l, ((PurpleBuddy *)bnode)->account); ++ } ++ } ++ } ++ } ++ ++ return l; ++} ++ ++void purple_blist_add_account(PurpleAccount *account) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *gnode, *cnode, *bnode; ++ ++ g_return_if_fail(purplebuddylist != NULL); ++ ++ if (!ops || !ops->update) ++ return; ++ ++ for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) { ++ if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) ++ continue; ++ for (cnode = gnode->child; cnode; cnode = cnode->next) { ++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) { ++ gboolean recompute = FALSE; ++ for (bnode = cnode->child; bnode; bnode = bnode->next) { ++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode) && ++ ((PurpleBuddy*)bnode)->account == account) { ++ recompute = TRUE; ++ ((PurpleContact*)cnode)->currentsize++; ++ if (((PurpleContact*)cnode)->currentsize == 1) ++ ((PurpleGroup*)gnode)->currentsize++; ++ ops->update(purplebuddylist, bnode); ++ } ++ } ++ if (recompute || ++ purple_blist_node_get_bool(cnode, "show_offline")) { ++ purple_contact_invalidate_priority_buddy((PurpleContact*)cnode); ++ ops->update(purplebuddylist, cnode); ++ } ++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode) && ++ ((PurpleChat*)cnode)->account == account) { ++ ((PurpleGroup *)gnode)->online++; ++ ((PurpleGroup *)gnode)->currentsize++; ++ ops->update(purplebuddylist, cnode); ++ } ++ } ++ ops->update(purplebuddylist, gnode); ++ } ++} ++ ++void purple_blist_remove_account(PurpleAccount *account) ++{ ++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); ++ PurpleBlistNode *gnode, *cnode, *bnode; ++ PurpleBuddy *buddy; ++ PurpleChat *chat; ++ PurpleContact *contact; ++ PurpleGroup *group; ++ GList *list = NULL, *iter = NULL; ++ ++ g_return_if_fail(purplebuddylist != NULL); ++ ++ for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) { ++ if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) ++ continue; ++ ++ group = (PurpleGroup *)gnode; ++ ++ for (cnode = gnode->child; cnode; cnode = cnode->next) { ++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) { ++ gboolean recompute = FALSE; ++ contact = (PurpleContact *)cnode; ++ ++ for (bnode = cnode->child; bnode; bnode = bnode->next) { ++ if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) ++ continue; ++ ++ buddy = (PurpleBuddy *)bnode; ++ if (account == buddy->account) { ++ PurplePresence *presence; ++ ++ presence = purple_buddy_get_presence(buddy); ++ ++ if(purple_presence_is_online(presence)) { ++ contact->online--; ++ if (contact->online == 0) ++ group->online--; ++ ++ purple_blist_node_set_int(&buddy->node, ++ "last_seen", time(NULL)); ++ } ++ ++ contact->currentsize--; ++ if (contact->currentsize == 0) ++ group->currentsize--; ++ ++ if (!g_list_find(list, presence)) ++ list = g_list_prepend(list, presence); ++ ++ if (contact->priority == buddy) ++ purple_contact_invalidate_priority_buddy(contact); ++ else ++ recompute = TRUE; ++ ++ if (ops && ops->remove) { ++ ops->remove(purplebuddylist, bnode); ++ } ++ } ++ } ++ if (recompute) { ++ purple_contact_invalidate_priority_buddy(contact); ++ if (ops && ops->update) ++ ops->update(purplebuddylist, cnode); ++ } ++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) { ++ chat = (PurpleChat *)cnode; ++ ++ if(chat->account == account) { ++ group->currentsize--; ++ group->online--; ++ ++ if (ops && ops->remove) ++ ops->remove(purplebuddylist, cnode); ++ } ++ } ++ } ++ } ++ ++ for (iter = list; iter; iter = iter->next) ++ { ++ purple_presence_set_status_active(iter->data, "offline", TRUE); ++ } ++ g_list_free(list); ++} ++ ++gboolean purple_group_on_account(PurpleGroup *g, PurpleAccount *account) ++{ ++ PurpleBlistNode *cnode; ++ for (cnode = ((PurpleBlistNode *)g)->child; cnode; cnode = cnode->next) { ++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) { ++ if(purple_contact_on_account((PurpleContact *) cnode, account)) ++ return TRUE; ++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) { ++ PurpleChat *chat = (PurpleChat *)cnode; ++ if ((!account && purple_account_is_connected(chat->account)) ++ || chat->account == account) ++ return TRUE; ++ } ++ } ++ return FALSE; ++} ++ ++const char *purple_group_get_name(PurpleGroup *group) ++{ ++ g_return_val_if_fail(group != NULL, NULL); ++ ++ return group->name; ++} ++ ++void ++purple_blist_request_add_buddy(PurpleAccount *account, const char *username, ++ const char *group, const char *alias) ++{ ++ PurpleBlistUiOps *ui_ops; ++ ++ ui_ops = purple_blist_get_ui_ops(); ++ ++ if (ui_ops != NULL && ui_ops->request_add_buddy != NULL) ++ ui_ops->request_add_buddy(account, username, group, alias); ++} ++ ++void ++purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group, ++ const char *alias, const char *name) ++{ ++ PurpleBlistUiOps *ui_ops; ++ ++ ui_ops = purple_blist_get_ui_ops(); ++ ++ if (ui_ops != NULL && ui_ops->request_add_chat != NULL) ++ ui_ops->request_add_chat(account, group, alias, name); ++} ++ ++void ++purple_blist_request_add_group(void) ++{ ++ PurpleBlistUiOps *ui_ops; ++ ++ ui_ops = purple_blist_get_ui_ops(); ++ ++ if (ui_ops != NULL && ui_ops->request_add_group != NULL) ++ ui_ops->request_add_group(); ++} ++ ++static void ++purple_blist_node_destroy(PurpleBlistNode *node) ++{ ++ PurpleBlistUiOps *ui_ops; ++ PurpleBlistNode *child, *next_child; ++ ++ ui_ops = purple_blist_get_ui_ops(); ++ child = node->child; ++ while (child) { ++ next_child = child->next; ++ purple_blist_node_destroy(child); ++ child = next_child; ++ } ++ ++ /* Allow the UI to free data */ ++ node->parent = NULL; ++ node->child = NULL; ++ node->next = NULL; ++ node->prev = NULL; ++ if (ui_ops && ui_ops->remove) ++ ui_ops->remove(purplebuddylist, node); ++ ++ if (PURPLE_BLIST_NODE_IS_BUDDY(node)) ++ purple_buddy_destroy((PurpleBuddy*)node); ++ else if (PURPLE_BLIST_NODE_IS_CHAT(node)) ++ purple_chat_destroy((PurpleChat*)node); ++ else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) ++ purple_contact_destroy((PurpleContact*)node); ++ else if (PURPLE_BLIST_NODE_IS_GROUP(node)) ++ purple_group_destroy((PurpleGroup*)node); ++} ++ ++static void ++purple_blist_node_setting_free(gpointer data) ++{ ++ PurpleValue *value; ++ ++ value = (PurpleValue *)data; ++ ++ purple_value_destroy(value); ++} ++ ++static void purple_blist_node_initialize_settings(PurpleBlistNode *node) ++{ ++ if (node->settings) ++ return; ++ ++ node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, ++ (GDestroyNotify)purple_blist_node_setting_free); ++} ++ ++void purple_blist_node_remove_setting(PurpleBlistNode *node, const char *key) ++{ ++ PurpleBlistUiOps *ops; ++ g_return_if_fail(node != NULL); ++ g_return_if_fail(node->settings != NULL); ++ g_return_if_fail(key != NULL); ++ ++ g_hash_table_remove(node->settings, key); ++ ++ ops = purple_blist_get_ui_ops(); ++ if (ops && ops->save_node) ++ ops->save_node(node); ++} ++ ++void ++purple_blist_node_set_flags(PurpleBlistNode *node, PurpleBlistNodeFlags flags) ++{ ++ g_return_if_fail(node != NULL); ++ ++ node->flags = flags; ++} ++ ++PurpleBlistNodeFlags ++purple_blist_node_get_flags(PurpleBlistNode *node) ++{ ++ g_return_val_if_fail(node != NULL, 0); ++ ++ return node->flags; ++} ++ ++PurpleBlistNodeType ++purple_blist_node_get_type(PurpleBlistNode *node) ++{ ++ g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE); ++ return node->type; ++} ++ ++void ++purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data) ++{ ++ PurpleValue *value; ++ PurpleBlistUiOps *ops; ++ ++ g_return_if_fail(node != NULL); ++ g_return_if_fail(node->settings != NULL); ++ g_return_if_fail(key != NULL); ++ ++ value = purple_value_new(PURPLE_TYPE_BOOLEAN); ++ purple_value_set_boolean(value, data); ++ ++ g_hash_table_replace(node->settings, g_strdup(key), value); ++ ++ ops = purple_blist_get_ui_ops(); ++ if (ops && ops->save_node) ++ ops->save_node(node); ++} ++ ++gboolean ++purple_blist_node_get_bool(PurpleBlistNode* node, const char *key) ++{ ++ PurpleValue *value; ++ ++ g_return_val_if_fail(node != NULL, FALSE); ++ g_return_val_if_fail(node->settings != NULL, FALSE); ++ g_return_val_if_fail(key != NULL, FALSE); ++ ++ value = g_hash_table_lookup(node->settings, key); ++ ++ if (value == NULL) ++ return FALSE; ++ ++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN, FALSE); ++ ++ return purple_value_get_boolean(value); ++} ++ ++void ++purple_blist_node_set_int(PurpleBlistNode* node, const char *key, int data) ++{ ++ PurpleValue *value; ++ PurpleBlistUiOps *ops; ++ ++ g_return_if_fail(node != NULL); ++ g_return_if_fail(node->settings != NULL); ++ g_return_if_fail(key != NULL); ++ ++ value = purple_value_new(PURPLE_TYPE_INT); ++ purple_value_set_int(value, data); ++ ++ g_hash_table_replace(node->settings, g_strdup(key), value); ++ ++ ops = purple_blist_get_ui_ops(); ++ if (ops && ops->save_node) ++ ops->save_node(node); ++} ++ ++int ++purple_blist_node_get_int(PurpleBlistNode* node, const char *key) ++{ ++ PurpleValue *value; ++ ++ g_return_val_if_fail(node != NULL, 0); ++ g_return_val_if_fail(node->settings != NULL, 0); ++ g_return_val_if_fail(key != NULL, 0); ++ ++ value = g_hash_table_lookup(node->settings, key); ++ ++ if (value == NULL) ++ return 0; ++ ++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_INT, 0); ++ ++ return purple_value_get_int(value); ++} ++ ++void ++purple_blist_node_set_string(PurpleBlistNode* node, const char *key, const char *data) ++{ ++ PurpleValue *value; ++ PurpleBlistUiOps *ops; ++ ++ g_return_if_fail(node != NULL); ++ g_return_if_fail(node->settings != NULL); ++ g_return_if_fail(key != NULL); ++ ++ value = purple_value_new(PURPLE_TYPE_STRING); ++ purple_value_set_string(value, data); ++ ++ g_hash_table_replace(node->settings, g_strdup(key), value); ++ ++ ops = purple_blist_get_ui_ops(); ++ if (ops && ops->save_node) ++ ops->save_node(node); ++} ++ ++const char * ++purple_blist_node_get_string(PurpleBlistNode* node, const char *key) ++{ ++ PurpleValue *value; ++ ++ g_return_val_if_fail(node != NULL, NULL); ++ g_return_val_if_fail(node->settings != NULL, NULL); ++ g_return_val_if_fail(key != NULL, NULL); ++ ++ value = g_hash_table_lookup(node->settings, key); ++ ++ if (value == NULL) ++ return NULL; ++ ++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_STRING, NULL); ++ ++ return purple_value_get_string(value); ++} ++ ++GList * ++purple_blist_node_get_extended_menu(PurpleBlistNode *n) ++{ ++ GList *menu = NULL; ++ ++ g_return_val_if_fail(n != NULL, NULL); ++ ++ purple_signal_emit(purple_blist_get_handle(), ++ "blist-node-extended-menu", ++ n, &menu); ++ return menu; ++} ++ ++int purple_blist_get_group_size(PurpleGroup *group, gboolean offline) ++{ ++ if (!group) ++ return 0; ++ ++ return offline ? group->totalsize : group->currentsize; ++} ++ ++int purple_blist_get_group_online_count(PurpleGroup *group) ++{ ++ if (!group) ++ return 0; ++ ++ return group->online; ++} ++ ++void ++purple_blist_set_ui_ops(PurpleBlistUiOps *ops) ++{ ++ gboolean overrode = FALSE; ++ blist_ui_ops = ops; ++ ++ if (!ops) ++ return; ++ ++ if (!ops->save_node) { ++ ops->save_node = purple_blist_save_node; ++ overrode = TRUE; ++ } ++ if (!ops->remove_node) { ++ ops->remove_node = purple_blist_save_node; ++ overrode = TRUE; ++ } ++ if (!ops->save_account) { ++ ops->save_account = purple_blist_save_account; ++ overrode = TRUE; ++ } ++ ++ if (overrode && (ops->save_node != purple_blist_save_node || ++ ops->remove_node != purple_blist_save_node || ++ ops->save_account != purple_blist_save_account)) { ++ purple_debug_warning("blist", "Only some of the blist saving UI ops " ++ "were overridden. This probably is not what you want!\n"); ++ } ++} ++ ++PurpleBlistUiOps * ++purple_blist_get_ui_ops(void) ++{ ++ return blist_ui_ops; ++} ++ ++ ++void * ++purple_blist_get_handle(void) ++{ ++ static int handle; ++ ++ return &handle; ++} ++ ++void ++purple_blist_init(void) ++{ ++ void *handle = purple_blist_get_handle(); ++ ++ purple_signal_register(handle, "buddy-status-changed", ++ purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, ++ 3, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY), ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_STATUS), ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_STATUS)); ++ purple_signal_register(handle, "buddy-privacy-changed", ++ purple_marshal_VOID__POINTER, NULL, ++ 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "buddy-idle-changed", ++ purple_marshal_VOID__POINTER_INT_INT, NULL, ++ 3, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY), ++ purple_value_new(PURPLE_TYPE_INT), ++ purple_value_new(PURPLE_TYPE_INT)); ++ ++ ++ purple_signal_register(handle, "buddy-signed-on", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "buddy-signed-off", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "buddy-got-login-time", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "blist-node-added", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_NODE)); ++ ++ purple_signal_register(handle, "blist-node-removed", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_NODE)); ++ ++ purple_signal_register(handle, "buddy-added", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "buddy-removed", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "buddy-icon-changed", ++ purple_marshal_VOID__POINTER, NULL, 1, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY)); ++ ++ purple_signal_register(handle, "update-idle", purple_marshal_VOID, NULL, 0); ++ ++ purple_signal_register(handle, "blist-node-extended-menu", ++ purple_marshal_VOID__POINTER_POINTER, NULL, 2, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_NODE), ++ purple_value_new(PURPLE_TYPE_BOXED, "GList **")); ++ ++ purple_signal_register(handle, "blist-node-aliased", ++ purple_marshal_VOID__POINTER_POINTER, NULL, 2, ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_NODE), ++ purple_value_new(PURPLE_TYPE_STRING)); ++ ++ purple_signal_register(handle, "buddy-caps-changed", ++ purple_marshal_VOID__POINTER_INT_INT, NULL, ++ 3, purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_BLIST_BUDDY), ++ purple_value_new(PURPLE_TYPE_INT), ++ purple_value_new(PURPLE_TYPE_INT)); ++ ++ purple_signal_connect(purple_accounts_get_handle(), "account-created", ++ handle, ++ PURPLE_CALLBACK(purple_blist_buddies_cache_add_account), ++ NULL); ++ ++ purple_signal_connect(purple_accounts_get_handle(), "account-destroying", ++ handle, ++ PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account), ++ NULL); ++} ++ ++void ++purple_blist_uninit(void) ++{ ++ PurpleBlistNode *node, *next_node; ++ ++ /* This happens if we quit before purple_set_blist is called. */ ++ if (purplebuddylist == NULL) ++ return; ++ ++ if (save_timer != 0) { ++ purple_timeout_remove(save_timer); ++ save_timer = 0; ++ purple_blist_sync(); ++ } ++ ++ purple_blist_destroy(); ++ ++ node = purple_blist_get_root(); ++ while (node) { ++ next_node = node->next; ++ purple_blist_node_destroy(node); ++ node = next_node; ++ } ++ purplebuddylist->root = NULL; ++ ++ g_hash_table_destroy(purplebuddylist->buddies); ++ g_hash_table_destroy(buddies_cache); ++ g_hash_table_destroy(groups_cache); ++ ++ buddies_cache = NULL; ++ groups_cache = NULL; ++ ++ PURPLE_DBUS_UNREGISTER_POINTER(purplebuddylist); ++ g_free(purplebuddylist); ++ purplebuddylist = NULL; ++ ++ purple_signals_disconnect_by_handle(purple_blist_get_handle()); ++ purple_signals_unregister_by_instance(purple_blist_get_handle()); ++} +diff -rupN pidgin-2.7.7/libpurple/certificate.c~ pidgin-2.7.7-new//libpurple/certificate.c~ +--- pidgin-2.7.7/libpurple/certificate.c~ 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/certificate.c~ 2011-03-27 09:15:31.012553000 -0600 +@@ -0,0 +1,2235 @@ ++/** ++ * @file certificate.c Public-Key Certificate API ++ * @ingroup core ++ */ ++ ++/* ++ * ++ * purple ++ * ++ * Purple is the legal property of its developers, whose names are too numerous ++ * to list here. Please refer to the COPYRIGHT file distributed with this ++ * source distribution. ++ * ++ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA ++ */ ++ ++#include "internal.h" ++#include "certificate.h" ++#include "dbus-maybe.h" ++#include "debug.h" ++#include "request.h" ++#include "signals.h" ++#include "util.h" ++ ++/** List holding pointers to all registered certificate schemes */ ++static GList *cert_schemes = NULL; ++/** List of registered Verifiers */ ++static GList *cert_verifiers = NULL; ++/** List of registered Pools */ ++static GList *cert_pools = NULL; ++ ++/* ++ * TODO: Merge this with PurpleCertificateVerificationStatus for 3.0.0 */ ++typedef enum { ++ PURPLE_CERTIFICATE_UNKNOWN_ERROR = -1, ++ ++ /* Not an error */ ++ PURPLE_CERTIFICATE_NO_PROBLEMS = 0, ++ ++ /* Non-fatal */ ++ PURPLE_CERTIFICATE_NON_FATALS_MASK = 0x0000FFFF, ++ ++ /* The certificate is self-signed. */ ++ PURPLE_CERTIFICATE_SELF_SIGNED = 0x01, ++ ++ /* The CA is not in libpurple's pool of certificates. */ ++ PURPLE_CERTIFICATE_CA_UNKNOWN = 0x02, ++ ++ /* The current time is before the certificate's specified ++ * activation time. ++ */ ++ PURPLE_CERTIFICATE_NOT_ACTIVATED = 0x04, ++ ++ /* The current time is after the certificate's specified expiration time */ ++ PURPLE_CERTIFICATE_EXPIRED = 0x08, ++ ++ /* The certificate's subject name doesn't match the expected */ ++ PURPLE_CERTIFICATE_NAME_MISMATCH = 0x10, ++ ++ /* No CA pool was found. This shouldn't happen... */ ++ PURPLE_CERTIFICATE_NO_CA_POOL = 0x20, ++ ++ /* Fatal */ ++ PURPLE_CERTIFICATE_FATALS_MASK = 0xFFFF0000, ++ ++ /* The signature chain could not be validated. Due to limitations in the ++ * the current API, this also indicates one of the CA certificates in the ++ * chain is expired (or not yet activated). FIXME 3.0.0 */ ++ PURPLE_CERTIFICATE_INVALID_CHAIN = 0x10000, ++ ++ /* The signature has been revoked. */ ++ PURPLE_CERTIFICATE_REVOKED = 0x20000, ++ ++ PURPLE_CERTIFICATE_LAST = 0x40000, ++} PurpleCertificateInvalidityFlags; ++ ++static const gchar * ++invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag) ++{ ++ switch (flag) { ++ case PURPLE_CERTIFICATE_SELF_SIGNED: ++ return _("The certificate is self-signed and cannot be " ++ "automatically checked."); ++ break; ++ case PURPLE_CERTIFICATE_CA_UNKNOWN: ++ return _("The certificate is not trusted because no certificate " ++ "that can verify it is currently trusted."); ++ break; ++ case PURPLE_CERTIFICATE_NOT_ACTIVATED: ++ return _("The certificate is not valid yet. Check that your " ++ "computer's date and time are accurate."); ++ break; ++ case PURPLE_CERTIFICATE_EXPIRED: ++ return _("The certificate has expired and should not be " ++ "considered valid. Check that your computer's date " ++ "and time are accurate."); ++ break; ++ case PURPLE_CERTIFICATE_NAME_MISMATCH: ++ /* Translators: "domain" refers to a DNS domain (e.g. talk.google.com) */ ++ return _("The certificate presented is not issued to this domain."); ++ break; ++ case PURPLE_CERTIFICATE_NO_CA_POOL: ++ return _("You have no database of root certificates, so " ++ "this certificate cannot be validated."); ++ break; ++ case PURPLE_CERTIFICATE_INVALID_CHAIN: ++ return _("The certificate chain presented is invalid."); ++ break; ++ case PURPLE_CERTIFICATE_REVOKED: ++ return _("The certificate has been revoked."); ++ break; ++ case PURPLE_CERTIFICATE_UNKNOWN_ERROR: ++ default: ++ return _("An unknown certificate error occurred."); ++ break; ++ } ++} ++ ++void ++purple_certificate_verify (PurpleCertificateVerifier *verifier, ++ const gchar *subject_name, GList *cert_chain, ++ PurpleCertificateVerifiedCallback cb, ++ gpointer cb_data) ++{ ++ PurpleCertificateVerificationRequest *vrq; ++ PurpleCertificateScheme *scheme; ++ ++ g_return_if_fail(subject_name != NULL); ++ /* If you don't have a cert to check, why are you requesting that it ++ be verified? */ ++ g_return_if_fail(cert_chain != NULL); ++ g_return_if_fail(cb != NULL); ++ ++ /* Look up the CertificateScheme */ ++ scheme = purple_certificate_find_scheme(verifier->scheme_name); ++ g_return_if_fail(scheme); ++ ++ /* Check that at least the first cert in the chain matches the ++ Verifier scheme */ ++ g_return_if_fail(scheme == ++ ((PurpleCertificate *) (cert_chain->data))->scheme); ++ ++ /* Construct and fill in the request fields */ ++ vrq = g_new0(PurpleCertificateVerificationRequest, 1); ++ vrq->verifier = verifier; ++ vrq->scheme = scheme; ++ vrq->subject_name = g_strdup(subject_name); ++ vrq->cert_chain = purple_certificate_copy_list(cert_chain); ++ vrq->cb = cb; ++ vrq->cb_data = cb_data; ++ ++ /* Initiate verification */ ++ (verifier->start_verification)(vrq); ++} ++ ++void ++purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateVerificationStatus st) ++{ ++ PurpleCertificateVerifier *vr; ++ ++ g_return_if_fail(vrq); ++ ++ const char *acceptbadcert; ++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert"); ++ ++ if (acceptbadcert != NULL) ++ { ++ purple_debug_warning("certificate", "Certificate Error. Accepting as requested!\n"); ++ purple_prefs_remove("/purple/acceptbadcert"); ++ ++ ++ const char *acceptbadcert; ++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert"); ++ ++ if (acceptbadcert != NULL) ++ { ++ purple_debug_warning("certificate", "Certificate Error. Accepting as requested!\n"); ++ purple_prefs_remove("/purple/acceptbadcert"); ++ ++ //Dodgy Cert Acceptance ++ st = PURPLE_CERTIFICATE_VALID; ++ } ++ //Dodgy Cert Acceptance ++ st = PURPLE_CERTIFICATE_VALID; ++ } ++ ++ if (st == PURPLE_CERTIFICATE_VALID) { ++ purple_debug_info("certificate", ++ "Successfully verified certificate for %s\n", ++ vrq->subject_name); ++ } else { ++ purple_debug_error("certificate", ++ "Failed to verify certificate for %s\n", ++ vrq->subject_name); ++ } ++ ++ /* Pass the results on to the request's callback */ ++ (vrq->cb)(st, vrq->cb_data); ++ ++ /* And now to eliminate the request */ ++ /* Fetch the Verifier responsible... */ ++ vr = vrq->verifier; ++ /* ...and order it to KILL */ ++ (vr->destroy_request)(vrq); ++ ++ /* Now the internals have been cleaned up, so clean up the libpurple- ++ created elements */ ++ g_free(vrq->subject_name); ++ purple_certificate_destroy_list(vrq->cert_chain); ++ ++ /* A structure born ++ * to much ado ++ * and with so much within. ++ * It reaches now ++ * its quiet end. */ ++ g_free(vrq); ++} ++ ++ ++PurpleCertificate * ++purple_certificate_copy(PurpleCertificate *crt) ++{ ++ g_return_val_if_fail(crt, NULL); ++ g_return_val_if_fail(crt->scheme, NULL); ++ g_return_val_if_fail(crt->scheme->copy_certificate, NULL); ++ ++ return (crt->scheme->copy_certificate)(crt); ++} ++ ++GList * ++purple_certificate_copy_list(GList *crt_list) ++{ ++ GList *new_l, *l; ++ ++ /* First, make a shallow copy of the list */ ++ new_l = g_list_copy(crt_list); ++ ++ /* Now go through and actually duplicate each certificate */ ++ for (l = new_l; l; l = l->next) { ++ l->data = purple_certificate_copy(l->data); ++ } ++ ++ return new_l; ++} ++ ++void ++purple_certificate_destroy (PurpleCertificate *crt) ++{ ++ PurpleCertificateScheme *scheme; ++ ++ if (NULL == crt) return; ++ ++ scheme = crt->scheme; ++ ++ (scheme->destroy_certificate)(crt); ++} ++ ++void ++purple_certificate_destroy_list (GList * crt_list) ++{ ++ PurpleCertificate *crt; ++ GList *l; ++ ++ for (l=crt_list; l; l = l->next) { ++ crt = (PurpleCertificate *) l->data; ++ purple_certificate_destroy(crt); ++ } ++ ++ g_list_free(crt_list); ++} ++ ++gboolean ++purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer) ++{ ++ PurpleCertificateScheme *scheme; ++ ++ g_return_val_if_fail(crt, FALSE); ++ g_return_val_if_fail(issuer, FALSE); ++ ++ scheme = crt->scheme; ++ g_return_val_if_fail(scheme, FALSE); ++ /* We can't compare two certs of unrelated schemes, obviously */ ++ g_return_val_if_fail(issuer->scheme == scheme, FALSE); ++ ++ return (scheme->signed_by)(crt, issuer); ++} ++ ++gboolean ++purple_certificate_check_signature_chain_with_failing(GList *chain, ++ PurpleCertificate **failing) ++{ ++ GList *cur; ++ PurpleCertificate *crt, *issuer; ++ gchar *uid; ++ time_t now, activation, expiration; ++ gboolean ret; ++ ++ g_return_val_if_fail(chain, FALSE); ++ ++ if (failing) ++ *failing = NULL; ++ ++ uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data); ++ purple_debug_info("certificate", ++ "Checking signature chain for uid=%s\n", ++ uid); ++ g_free(uid); ++ ++ /* If this is a single-certificate chain, say that it is valid */ ++ if (chain->next == NULL) { ++ purple_debug_info("certificate", ++ "...Singleton. We'll say it's valid.\n"); ++ return TRUE; ++ } ++ ++ now = time(NULL); ++ ++ /* Load crt with the first certificate */ ++ crt = (PurpleCertificate *)(chain->data); ++ /* And start with the second certificate in the chain */ ++ for ( cur = chain->next; cur; cur = cur->next ) { ++ ++ issuer = (PurpleCertificate *)(cur->data); ++ ++ uid = purple_certificate_get_unique_id(issuer); ++ ++ ret = purple_certificate_get_times(issuer, &activation, &expiration); ++ if (!ret || now < activation || now > expiration) { ++ if (!ret) ++ purple_debug_error("certificate", ++ "...Failed to get validity times for certificate %s\n" ++ "Chain is INVALID\n", uid); ++ else if (now > expiration) ++ purple_debug_error("certificate", ++ "...Issuer %s expired at %s\nChain is INVALID\n", ++ uid, ctime(&expiration)); ++ else ++ purple_debug_error("certificate", ++ "...Not-yet-activated issuer %s will be valid at %s\n" ++ "Chain is INVALID\n", uid, ctime(&activation)); ++ ++ if (failing) ++ *failing = crt; ++ ++ g_free(uid); ++ return FALSE; ++ } ++ ++ /* Check the signature for this link */ ++ if (! purple_certificate_signed_by(crt, issuer) ) { ++ purple_debug_error("certificate", ++ "...Bad or missing signature by %s\nChain is INVALID\n", ++ uid); ++ g_free(uid); ++ ++ if (failing) ++ *failing = crt; ++ ++ return FALSE; ++ } ++ ++ purple_debug_info("certificate", ++ "...Good signature by %s\n", ++ uid); ++ g_free(uid); ++ ++ /* The issuer is now the next crt whose signature is to be ++ checked */ ++ crt = issuer; ++ } ++ ++ /* If control reaches this point, the chain is valid */ ++ purple_debug_info("certificate", "Chain is VALID\n"); ++ return TRUE; ++} ++ ++gboolean ++purple_certificate_check_signature_chain(GList *chain) ++{ ++ return purple_certificate_check_signature_chain_with_failing(chain, NULL); ++} ++ ++PurpleCertificate * ++purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename) ++{ ++ g_return_val_if_fail(scheme, NULL); ++ g_return_val_if_fail(scheme->import_certificate, NULL); ++ g_return_val_if_fail(filename, NULL); ++ ++ return (scheme->import_certificate)(filename); ++} ++ ++GSList * ++purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename) ++{ ++ g_return_val_if_fail(scheme, NULL); ++ g_return_val_if_fail(scheme->import_certificates, NULL); ++ g_return_val_if_fail(filename, NULL); ++ ++ return (scheme->import_certificates)(filename); ++} ++ ++gboolean ++purple_certificate_export(const gchar *filename, PurpleCertificate *crt) ++{ ++ PurpleCertificateScheme *scheme; ++ ++ g_return_val_if_fail(filename, FALSE); ++ g_return_val_if_fail(crt, FALSE); ++ g_return_val_if_fail(crt->scheme, FALSE); ++ ++ scheme = crt->scheme; ++ g_return_val_if_fail(scheme->export_certificate, FALSE); ++ ++ return (scheme->export_certificate)(filename, crt); ++} ++ ++static gboolean ++byte_arrays_equal(const GByteArray *array1, const GByteArray *array2) ++{ ++ g_return_val_if_fail(array1 != NULL, FALSE); ++ g_return_val_if_fail(array2 != NULL, FALSE); ++ ++ return (array1->len == array2->len) && ++ (0 == memcmp(array1->data, array2->data, array1->len)); ++} ++ ++GByteArray * ++purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt) ++{ ++ PurpleCertificateScheme *scheme; ++ GByteArray *fpr; ++ ++ g_return_val_if_fail(crt, NULL); ++ g_return_val_if_fail(crt->scheme, NULL); ++ ++ scheme = crt->scheme; ++ ++ g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL); ++ ++ fpr = (scheme->get_fingerprint_sha1)(crt); ++ ++ return fpr; ++} ++ ++gchar * ++purple_certificate_get_unique_id(PurpleCertificate *crt) ++{ ++ g_return_val_if_fail(crt, NULL); ++ g_return_val_if_fail(crt->scheme, NULL); ++ g_return_val_if_fail(crt->scheme->get_unique_id, NULL); ++ ++ return (crt->scheme->get_unique_id)(crt); ++} ++ ++gchar * ++purple_certificate_get_issuer_unique_id(PurpleCertificate *crt) ++{ ++ g_return_val_if_fail(crt, NULL); ++ g_return_val_if_fail(crt->scheme, NULL); ++ g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL); ++ ++ return (crt->scheme->get_issuer_unique_id)(crt); ++} ++ ++gchar * ++purple_certificate_get_subject_name(PurpleCertificate *crt) ++{ ++ PurpleCertificateScheme *scheme; ++ gchar *subject_name; ++ ++ g_return_val_if_fail(crt, NULL); ++ g_return_val_if_fail(crt->scheme, NULL); ++ ++ scheme = crt->scheme; ++ ++ g_return_val_if_fail(scheme->get_subject_name, NULL); ++ ++ subject_name = (scheme->get_subject_name)(crt); ++ ++ return subject_name; ++} ++ ++gboolean ++purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name) ++{ ++ PurpleCertificateScheme *scheme; ++ ++ g_return_val_if_fail(crt, FALSE); ++ g_return_val_if_fail(crt->scheme, FALSE); ++ g_return_val_if_fail(name, FALSE); ++ ++ scheme = crt->scheme; ++ ++ g_return_val_if_fail(scheme->check_subject_name, FALSE); ++ ++ return (scheme->check_subject_name)(crt, name); ++} ++ ++gboolean ++purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration) ++{ ++ PurpleCertificateScheme *scheme; ++ ++ g_return_val_if_fail(crt, FALSE); ++ ++ scheme = crt->scheme; ++ ++ g_return_val_if_fail(scheme, FALSE); ++ ++ /* If both provided references are NULL, what are you doing calling ++ this? */ ++ g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE); ++ ++ /* Throw the request on down to the certscheme */ ++ return (scheme->get_times)(crt, activation, expiration); ++} ++ ++gchar * ++purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id) ++{ ++ gchar *path; ++ gchar *esc_scheme_name, *esc_name, *esc_id; ++ ++ g_return_val_if_fail(pool, NULL); ++ g_return_val_if_fail(pool->scheme_name, NULL); ++ g_return_val_if_fail(pool->name, NULL); ++ ++ /* Escape all the elements for filesystem-friendliness */ ++ esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL; ++ esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL; ++ esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL; ++ ++ path = g_build_filename(purple_user_dir(), ++ "certificates", /* TODO: constantize this? */ ++ esc_scheme_name, ++ esc_name, ++ esc_id, ++ NULL); ++ ++ g_free(esc_scheme_name); ++ g_free(esc_name); ++ g_free(esc_id); ++ return path; ++} ++ ++gboolean ++purple_certificate_pool_usable(PurpleCertificatePool *pool) ++{ ++ g_return_val_if_fail(pool, FALSE); ++ g_return_val_if_fail(pool->scheme_name, FALSE); ++ ++ /* Check that the pool's scheme is loaded */ ++ if (purple_certificate_find_scheme(pool->scheme_name) == NULL) { ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++PurpleCertificateScheme * ++purple_certificate_pool_get_scheme(PurpleCertificatePool *pool) ++{ ++ g_return_val_if_fail(pool, NULL); ++ g_return_val_if_fail(pool->scheme_name, NULL); ++ ++ return purple_certificate_find_scheme(pool->scheme_name); ++} ++ ++gboolean ++purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id) ++{ ++ g_return_val_if_fail(pool, FALSE); ++ g_return_val_if_fail(id, FALSE); ++ g_return_val_if_fail(pool->cert_in_pool, FALSE); ++ ++ return (pool->cert_in_pool)(id); ++} ++ ++PurpleCertificate * ++purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id) ++{ ++ g_return_val_if_fail(pool, NULL); ++ g_return_val_if_fail(id, NULL); ++ g_return_val_if_fail(pool->get_cert, NULL); ++ ++ return (pool->get_cert)(id); ++} ++ ++gboolean ++purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt) ++{ ++ gboolean ret = FALSE; ++ ++ g_return_val_if_fail(pool, FALSE); ++ g_return_val_if_fail(id, FALSE); ++ g_return_val_if_fail(pool->put_cert, FALSE); ++ ++ /* Whether crt->scheme matches find_scheme(pool->scheme_name) is not ++ relevant... I think... */ ++ g_return_val_if_fail( ++ g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0, ++ FALSE); ++ ++ ret = (pool->put_cert)(id, crt); ++ ++ /* Signal that the certificate was stored if success*/ ++ if (ret) { ++ purple_signal_emit(pool, "certificate-stored", ++ pool, id); ++ } ++ ++ return ret; ++} ++ ++gboolean ++purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id) ++{ ++ gboolean ret = FALSE; ++ ++ g_return_val_if_fail(pool, FALSE); ++ g_return_val_if_fail(id, FALSE); ++ g_return_val_if_fail(pool->delete_cert, FALSE); ++ ++ ret = (pool->delete_cert)(id); ++ ++ /* Signal that the certificate was deleted if success */ ++ if (ret) { ++ purple_signal_emit(pool, "certificate-deleted", ++ pool, id); ++ } ++ ++ return ret; ++} ++ ++GList * ++purple_certificate_pool_get_idlist(PurpleCertificatePool *pool) ++{ ++ g_return_val_if_fail(pool, NULL); ++ g_return_val_if_fail(pool->get_idlist, NULL); ++ ++ return (pool->get_idlist)(); ++} ++ ++void ++purple_certificate_pool_destroy_idlist(GList *idlist) ++{ ++ GList *l; ++ ++ /* Iterate through and free them strings */ ++ for ( l = idlist; l; l = l->next ) { ++ g_free(l->data); ++ } ++ ++ g_list_free(idlist); ++} ++ ++ ++/****************************************************************************/ ++/* Builtin Verifiers, Pools, etc. */ ++/****************************************************************************/ ++ ++static void ++x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id) ++{ ++ g_return_if_fail(vrq); ++ ++ purple_debug_info("certificate/x509_singleuse", ++ "VRQ on cert from %s gave %d\n", ++ vrq->subject_name, id); ++ ++ /* Signal what happened back to the caller */ ++ if (1 == id) { ++ /* Accepted! */ ++ purple_certificate_verify_complete(vrq, ++ PURPLE_CERTIFICATE_VALID); ++ } else { ++ /* Not accepted */ ++ purple_certificate_verify_complete(vrq, ++ PURPLE_CERTIFICATE_INVALID); ++ ++ } ++} ++ ++static void ++x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq) ++{ ++ gchar *sha_asc; ++ GByteArray *sha_bin; ++ gchar *cn; ++ const gchar *cn_match; ++ gchar *primary, *secondary; ++ PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data; ++ ++ /* Pull out the SHA1 checksum */ ++ sha_bin = purple_certificate_get_fingerprint_sha1(crt); ++ /* Now decode it for display */ ++ sha_asc = purple_base16_encode_chunked(sha_bin->data, ++ sha_bin->len); ++ ++ /* Get the cert Common Name */ ++ cn = purple_certificate_get_subject_name(crt); ++ ++ /* Determine whether the name matches */ ++ if (purple_certificate_check_subject_name(crt, vrq->subject_name)) { ++ cn_match = ""; ++ } else { ++ cn_match = _("(DOES NOT MATCH)"); ++ } ++ ++ /* Make messages */ ++ primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name); ++ secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc); ++ ++ /* Make a semi-pretty display */ ++ purple_request_accept_cancel( ++ vrq->cb_data, /* TODO: Find what the handle ought to be */ ++ _("Single-use Certificate Verification"), ++ primary, ++ secondary, ++ 0, /* Accept by default */ ++ NULL, /* No account */ ++ NULL, /* No other user */ ++ NULL, /* No associated conversation */ ++ vrq, ++ x509_singleuse_verify_cb, ++ x509_singleuse_verify_cb ); ++ ++ /* Cleanup */ ++ g_free(cn); ++ g_free(primary); ++ g_free(secondary); ++ g_free(sha_asc); ++ g_byte_array_free(sha_bin, TRUE); ++} ++ ++static void ++x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq) ++{ ++ /* I don't do anything! */ ++} ++ ++static PurpleCertificateVerifier x509_singleuse = { ++ "x509", /* Scheme name */ ++ "singleuse", /* Verifier name */ ++ x509_singleuse_start_verify, /* start_verification function */ ++ x509_singleuse_destroy_request, /* Request cleanup operation */ ++ ++ NULL, ++ NULL, ++ NULL, ++ NULL ++}; ++ ++ ++ ++/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/ ++/* This is implemented in what may be the most inefficient and bugprone way ++ possible; however, future optimizations should not be difficult. */ ++ ++static PurpleCertificatePool x509_ca; ++ ++/** Holds a key-value pair for quickish certificate lookup */ ++typedef struct { ++ gchar *dn; ++ PurpleCertificate *crt; ++} x509_ca_element; ++ ++static void ++x509_ca_element_free(x509_ca_element *el) ++{ ++ if (NULL == el) return; ++ ++ g_free(el->dn); ++ purple_certificate_destroy(el->crt); ++ g_free(el); ++} ++ ++/** System directory to probe for CA certificates */ ++/* This is set in the lazy_init function */ ++static GList *x509_ca_paths = NULL; ++ ++/** A list of loaded CAs, populated from the above path whenever the lazy_init ++ happens. Contains pointers to x509_ca_elements */ ++static GList *x509_ca_certs = NULL; ++ ++/** Used for lazy initialization purposes. */ ++static gboolean x509_ca_initialized = FALSE; ++ ++/** Adds a certificate to the in-memory cache, doing nothing else */ ++static gboolean ++x509_ca_quiet_put_cert(PurpleCertificate *crt) ++{ ++ x509_ca_element *el; ++ ++ /* lazy_init calls this function, so calling lazy_init here is a ++ Bad Thing */ ++ ++ g_return_val_if_fail(crt, FALSE); ++ g_return_val_if_fail(crt->scheme, FALSE); ++ /* Make sure that this is some kind of X.509 certificate */ ++ /* TODO: Perhaps just check crt->scheme->name instead? */ ++ g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE); ++ ++ el = g_new0(x509_ca_element, 1); ++ el->dn = purple_certificate_get_unique_id(crt); ++ el->crt = purple_certificate_copy(crt); ++ x509_ca_certs = g_list_prepend(x509_ca_certs, el); ++ ++ return TRUE; ++} ++ ++/* Since the libpurple CertificatePools get registered before plugins are ++ loaded, an X.509 Scheme is generally not available when x509_ca_init is ++ called, but x509_ca requires X.509 operations in order to properly load. ++ ++ To solve this, I present the lazy_init function. It attempts to finish ++ initialization of the Pool, but it usually fails when it is called from ++ x509_ca_init. However, this is OK; initialization is then simply deferred ++ until someone tries to use functions from the pool. */ ++static gboolean ++x509_ca_lazy_init(void) ++{ ++ PurpleCertificateScheme *x509; ++ GDir *certdir; ++ const gchar *entry; ++ GPatternSpec *pempat, *crtpat; ++ GList *iter = NULL; ++ GSList *crts = NULL; ++ ++ if (x509_ca_initialized) return TRUE; ++ ++ /* Check that X.509 is registered */ ++ x509 = purple_certificate_find_scheme(x509_ca.scheme_name); ++ if ( !x509 ) { ++ purple_debug_warning("certificate/x509/ca", ++ "Lazy init failed because an X.509 Scheme " ++ "is not yet registered. Maybe it will be " ++ "better later.\n"); ++ return FALSE; ++ } ++ ++ /* Use a glob to only read .pem files */ ++ pempat = g_pattern_spec_new("*.pem"); ++ crtpat = g_pattern_spec_new("*.crt"); ++ ++ /* Populate the certificates pool from the search path(s) */ ++ for (iter = x509_ca_paths; iter; iter = iter->next) { ++ certdir = g_dir_open(iter->data, 0, NULL); ++ if (!certdir) { ++ purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", (const char *)iter->data); ++ continue; ++ } ++ ++ while ( (entry = g_dir_read_name(certdir)) ) { ++ gchar *fullpath; ++ PurpleCertificate *crt; ++ ++ if (!g_pattern_match_string(pempat, entry) && !g_pattern_match_string(crtpat, entry)) { ++ continue; ++ } ++ ++ fullpath = g_build_filename(iter->data, entry, NULL); ++ ++ /* TODO: Respond to a failure in the following? */ ++ crts = purple_certificates_import(x509, fullpath); ++ ++ while (crts && crts->data) { ++ crt = crts->data; ++ if (x509_ca_quiet_put_cert(crt)) { ++ gchar *name; ++ name = purple_certificate_get_subject_name(crt); ++ purple_debug_info("certificate/x509/ca", ++ "Loaded %s from %s\n", ++ name ? name : "(unknown)", fullpath); ++ g_free(name); ++ } else { ++ purple_debug_error("certificate/x509/ca", ++ "Failed to load certificate from %s\n", ++ fullpath); ++ } ++ purple_certificate_destroy(crt); ++ crts = g_slist_delete_link(crts, crts); ++ } ++ ++ g_free(fullpath); ++ } ++ g_dir_close(certdir); ++ } ++ ++ g_pattern_spec_free(pempat); ++ g_pattern_spec_free(crtpat); ++ ++ purple_debug_info("certificate/x509/ca", ++ "Lazy init completed.\n"); ++ x509_ca_initialized = TRUE; ++ return TRUE; ++} ++ ++static gboolean ++x509_ca_init(void) ++{ ++ /* Attempt to point at the appropriate system path */ ++ if (NULL == x509_ca_paths) { ++#ifdef _WIN32 ++ x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR, ++ "ca-certs", NULL)); ++#else ++# ifdef SSL_CERTIFICATES_DIR ++ x509_ca_paths = g_list_append(NULL, g_strdup(SSL_CERTIFICATES_DIR)); ++# endif ++ x509_ca_paths = g_list_append(x509_ca_paths, ++ g_build_filename("/media/internal", "Messaging_Certificates", NULL)); ++#endif ++ } ++ ++ /* Attempt to initialize now, but if it doesn't work, that's OK; ++ it will get done later */ ++ if ( ! x509_ca_lazy_init()) { ++ purple_debug_info("certificate/x509/ca", ++ "Init failed, probably because a " ++ "dependency is not yet registered. " ++ "It has been deferred to later.\n"); ++ } ++ ++ return TRUE; ++} ++ ++static void ++x509_ca_uninit(void) ++{ ++ GList *l; ++ ++ for (l = x509_ca_certs; l; l = l->next) { ++ x509_ca_element *el = l->data; ++ x509_ca_element_free(el); ++ } ++ g_list_free(x509_ca_certs); ++ x509_ca_certs = NULL; ++ x509_ca_initialized = FALSE; ++ g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL); ++ g_list_free(x509_ca_paths); ++ x509_ca_paths = NULL; ++} ++ ++/** Look up a ca_element by dn */ ++static x509_ca_element * ++x509_ca_locate_cert(GList *lst, const gchar *dn) ++{ ++ GList *cur; ++ ++ for (cur = lst; cur; cur = cur->next) { ++ x509_ca_element *el = cur->data; ++ if (purple_strequal(dn, el->dn)) { ++ return el; ++ } ++ } ++ return NULL; ++} ++ ++static GSList * ++x509_ca_locate_certs(GList *lst, const gchar *dn) ++{ ++ GList *cur; ++ GSList *crts = NULL; ++ ++ for (cur = lst; cur; cur = cur->next) { ++ x509_ca_element *el = cur->data; ++ if (purple_strequal(dn, el->dn)) { ++ crts = g_slist_prepend(crts, el); ++ } ++ } ++ return crts; ++} ++ ++ ++static gboolean ++x509_ca_cert_in_pool(const gchar *id) ++{ ++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE); ++ g_return_val_if_fail(id, FALSE); ++ ++ if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) { ++ return TRUE; ++ } else { ++ return FALSE; ++ } ++ ++ return FALSE; ++} ++ ++static PurpleCertificate * ++x509_ca_get_cert(const gchar *id) ++{ ++ PurpleCertificate *crt = NULL; ++ x509_ca_element *el; ++ ++ g_return_val_if_fail(x509_ca_lazy_init(), NULL); ++ g_return_val_if_fail(id, NULL); ++ ++ /* Search the memory-cached pool */ ++ el = x509_ca_locate_cert(x509_ca_certs, id); ++ ++ if (el != NULL) { ++ /* Make a copy of the memcached one for the function caller ++ to play with */ ++ crt = purple_certificate_copy(el->crt); ++ } else { ++ crt = NULL; ++ } ++ ++ return crt; ++} ++ ++static GSList * ++x509_ca_get_certs(const gchar *id) ++{ ++ GSList *crts = NULL, *els = NULL; ++ ++ g_return_val_if_fail(x509_ca_lazy_init(), NULL); ++ g_return_val_if_fail(id, NULL); ++ ++ /* Search the memory-cached pool */ ++ els = x509_ca_locate_certs(x509_ca_certs, id); ++ ++ if (els != NULL) { ++ GSList *cur; ++ /* Make a copy of the memcached ones for the function caller ++ to play with */ ++ for (cur = els; cur; cur = cur->next) { ++ x509_ca_element *el = cur->data; ++ crts = g_slist_prepend(crts, purple_certificate_copy(el->crt)); ++ } ++ g_slist_free(els); ++ } ++ ++ return crts; ++} ++ ++static gboolean ++x509_ca_put_cert(const gchar *id, PurpleCertificate *crt) ++{ ++ gboolean ret = FALSE; ++ ++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE); ++ ++ /* TODO: This is a quick way of doing this. At some point the change ++ ought to be flushed to disk somehow. */ ++ ret = x509_ca_quiet_put_cert(crt); ++ ++ return ret; ++} ++ ++static gboolean ++x509_ca_delete_cert(const gchar *id) ++{ ++ x509_ca_element *el; ++ ++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE); ++ g_return_val_if_fail(id, FALSE); ++ ++ /* Is the id even in the pool? */ ++ el = x509_ca_locate_cert(x509_ca_certs, id); ++ if ( el == NULL ) { ++ purple_debug_warning("certificate/x509/ca", ++ "Id %s wasn't in the pool\n", ++ id); ++ return FALSE; ++ } ++ ++ /* Unlink it from the memory cache and destroy it */ ++ x509_ca_certs = g_list_remove(x509_ca_certs, el); ++ x509_ca_element_free(el); ++ ++ return TRUE; ++} ++ ++static GList * ++x509_ca_get_idlist(void) ++{ ++ GList *l, *idlist; ++ ++ g_return_val_if_fail(x509_ca_lazy_init(), NULL); ++ ++ idlist = NULL; ++ for (l = x509_ca_certs; l; l = l->next) { ++ x509_ca_element *el = l->data; ++ idlist = g_list_prepend(idlist, g_strdup(el->dn)); ++ } ++ ++ return idlist; ++} ++ ++ ++static PurpleCertificatePool x509_ca = { ++ "x509", /* Scheme name */ ++ "ca", /* Pool name */ ++ N_("Certificate Authorities"),/* User-friendly name */ ++ NULL, /* Internal data */ ++ x509_ca_init, /* init */ ++ x509_ca_uninit, /* uninit */ ++ x509_ca_cert_in_pool, /* Certificate exists? */ ++ x509_ca_get_cert, /* Cert retriever */ ++ x509_ca_put_cert, /* Cert writer */ ++ x509_ca_delete_cert, /* Cert remover */ ++ x509_ca_get_idlist, /* idlist retriever */ ++ ++ NULL, ++ NULL, ++ NULL, ++ NULL ++ ++}; ++ ++ ++ ++/***** Cache of certificates given by TLS/SSL peers *****/ ++static PurpleCertificatePool x509_tls_peers; ++ ++static gboolean ++x509_tls_peers_init(void) ++{ ++ gchar *poolpath; ++ int ret; ++ ++ /* Set up key cache here if it isn't already done */ ++ poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL); ++ ret = purple_build_dir(poolpath, 0700); /* Make it this user only */ ++ ++ if (ret != 0) ++ purple_debug_info("certificate/tls_peers", ++ "Could not create %s. Certificates will not be cached.\n", ++ poolpath); ++ ++ g_free(poolpath); ++ ++ return TRUE; ++} ++ ++static gboolean ++x509_tls_peers_cert_in_pool(const gchar *id) ++{ ++ gchar *keypath; ++ gboolean ret = FALSE; ++ ++ g_return_val_if_fail(id, FALSE); ++ ++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); ++ ++ ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR); ++ ++ g_free(keypath); ++ return ret; ++} ++ ++static PurpleCertificate * ++x509_tls_peers_get_cert(const gchar *id) ++{ ++ PurpleCertificateScheme *x509; ++ PurpleCertificate *crt; ++ gchar *keypath; ++ ++ g_return_val_if_fail(id, NULL); ++ ++ /* Is it in the pool? */ ++ if ( !x509_tls_peers_cert_in_pool(id) ) { ++ return NULL; ++ } ++ ++ /* Look up the X.509 scheme */ ++ x509 = purple_certificate_find_scheme("x509"); ++ g_return_val_if_fail(x509, NULL); ++ ++ /* Okay, now find and load that key */ ++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); ++ crt = purple_certificate_import(x509, keypath); ++ ++ g_free(keypath); ++ ++ return crt; ++} ++ ++static gboolean ++x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt) ++{ ++ gboolean ret = FALSE; ++ gchar *keypath; ++ ++ g_return_val_if_fail(crt, FALSE); ++ g_return_val_if_fail(crt->scheme, FALSE); ++ /* Make sure that this is some kind of X.509 certificate */ ++ /* TODO: Perhaps just check crt->scheme->name instead? */ ++ g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE); ++ ++ /* Work out the filename and export */ ++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); ++ ret = purple_certificate_export(keypath, crt); ++ ++ g_free(keypath); ++ return ret; ++} ++ ++static gboolean ++x509_tls_peers_delete_cert(const gchar *id) ++{ ++ gboolean ret = FALSE; ++ gchar *keypath; ++ ++ g_return_val_if_fail(id, FALSE); ++ ++ /* Is the id even in the pool? */ ++ if (!x509_tls_peers_cert_in_pool(id)) { ++ purple_debug_warning("certificate/tls_peers", ++ "Id %s wasn't in the pool\n", ++ id); ++ return FALSE; ++ } ++ ++ /* OK, so work out the keypath and delete the thing */ ++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); ++ if ( unlink(keypath) != 0 ) { ++ purple_debug_error("certificate/tls_peers", ++ "Unlink of %s failed!\n", ++ keypath); ++ ret = FALSE; ++ } else { ++ ret = TRUE; ++ } ++ ++ g_free(keypath); ++ return ret; ++} ++ ++static GList * ++x509_tls_peers_get_idlist(void) ++{ ++ GList *idlist = NULL; ++ GDir *dir; ++ const gchar *entry; ++ gchar *poolpath; ++ ++ /* Get a handle on the pool directory */ ++ poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL); ++ dir = g_dir_open(poolpath, ++ 0, /* No flags */ ++ NULL); /* Not interested in what the error is */ ++ g_free(poolpath); ++ ++ g_return_val_if_fail(dir, NULL); ++ ++ /* Traverse the directory listing and create an idlist */ ++ while ( (entry = g_dir_read_name(dir)) != NULL ) { ++ /* Unescape the filename */ ++ const char *unescaped = purple_unescape_filename(entry); ++ ++ /* Copy the entry name into our list (GLib owns the original ++ string) */ ++ idlist = g_list_prepend(idlist, g_strdup(unescaped)); ++ } ++ ++ /* Release the directory */ ++ g_dir_close(dir); ++ ++ return idlist; ++} ++ ++static PurpleCertificatePool x509_tls_peers = { ++ "x509", /* Scheme name */ ++ "tls_peers", /* Pool name */ ++ N_("SSL Peers Cache"), /* User-friendly name */ ++ NULL, /* Internal data */ ++ x509_tls_peers_init, /* init */ ++ NULL, /* uninit not required */ ++ x509_tls_peers_cert_in_pool, /* Certificate exists? */ ++ x509_tls_peers_get_cert, /* Cert retriever */ ++ x509_tls_peers_put_cert, /* Cert writer */ ++ x509_tls_peers_delete_cert, /* Cert remover */ ++ x509_tls_peers_get_idlist, /* idlist retriever */ ++ ++ NULL, ++ NULL, ++ NULL, ++ NULL ++}; ++ ++ ++/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/ ++static PurpleCertificateVerifier x509_tls_cached; ++ ++ ++/* The following is several hacks piled together and needs to be fixed. ++ * It exists because show_cert (see its comments) needs the original reason ++ * given to user_auth in order to rebuild the dialog. ++ */ ++/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get ++ closed by handle or otherwise abnormally. */ ++typedef struct { ++ PurpleCertificateVerificationRequest *vrq; ++ gchar *reason; ++} x509_tls_cached_ua_ctx; ++ ++static x509_tls_cached_ua_ctx * ++x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq, ++ const gchar *reason) ++{ ++ x509_tls_cached_ua_ctx *c; ++ ++ c = g_new0(x509_tls_cached_ua_ctx, 1); ++ c->vrq = vrq; ++ c->reason = g_strdup(reason); ++ ++ return c; ++} ++ ++ ++static void ++x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c) ++{ ++ g_return_if_fail(c); ++ g_free(c->reason); ++ g_free(c); ++} ++ ++static void ++x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq, ++ const gchar *reason); ++ ++static void ++x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id) ++{ ++ PurpleCertificate *disp_crt = c->vrq->cert_chain->data; ++ ++ /* Since clicking a button closes the request, show it again */ ++ x509_tls_cached_user_auth(c->vrq, c->reason); ++ ++ /* Show the certificate AFTER re-opening the dialog so that this ++ appears above the other */ ++ purple_certificate_display_x509(disp_crt); ++ ++ x509_tls_cached_ua_ctx_free(c); ++} ++ ++static void ++x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id) ++{ ++ PurpleCertificateVerificationRequest *vrq; ++ PurpleCertificatePool *tls_peers; ++ ++ g_return_if_fail(c); ++ g_return_if_fail(c->vrq); ++ ++ vrq = c->vrq; ++ ++ x509_tls_cached_ua_ctx_free(c); ++ ++ tls_peers = purple_certificate_find_pool("x509","tls_peers"); ++ ++ if (2 == id) { ++ gchar *cache_id = vrq->subject_name; ++ purple_debug_info("certificate/x509/tls_cached", ++ "User ACCEPTED cert\nCaching first in chain for future use as %s...\n", ++ cache_id); ++ ++ purple_certificate_pool_store(tls_peers, cache_id, ++ vrq->cert_chain->data); ++ ++ purple_certificate_verify_complete(vrq, ++ PURPLE_CERTIFICATE_VALID); ++ } else { ++ purple_debug_warning("certificate/x509/tls_cached", ++ "User REJECTED cert\n"); ++ purple_certificate_verify_complete(vrq, ++ PURPLE_CERTIFICATE_INVALID); ++ } ++} ++ ++static void ++x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore) ++{ ++ x509_tls_cached_user_auth_cb(c, 2); ++} ++ ++static void ++x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore) ++{ ++ x509_tls_cached_user_auth_cb(c, 1); ++} ++ ++/** Validates a certificate by asking the user ++ * @param reason String to explain why the user needs to accept/refuse the ++ * certificate. ++ * @todo Needs a handle argument ++ */ ++static void ++x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq, ++ const gchar *reason) ++{ ++ const char *acceptbadcert; ++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert"); ++ ++ if (acceptbadcert != NULL) ++ { ++ purple_debug_warning("certificate/x509/tls_cached", "Certificate Error. Accepting as requested!\n"); ++ purple_prefs_remove("/purple/acceptbadcert"); ++ ++ //Dodgy Cert Acceptance ++ x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx_new(vrq, reason),2); ++ } ++ else ++ { ++ purple_debug_warning("certificate/x509/tls_cached", "Certificate Error. Rejecting as requested!\n"); ++ ++ //Reject Bad Certificate ++ x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx_new(vrq, reason),1); ++ } ++} ++ ++static void ++x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateInvalidityFlags flags); ++ ++static void ++x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateInvalidityFlags flags) ++{ ++ PurpleCertificatePool *tls_peers; ++ PurpleCertificate *peer_crt = vrq->cert_chain->data; ++ ++ if (flags & PURPLE_CERTIFICATE_FATALS_MASK) { ++ /* TODO: Also print any other warnings? */ ++ const gchar *error; ++ gchar *tmp, *secondary; ++ ++ if (flags & PURPLE_CERTIFICATE_INVALID_CHAIN) ++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_INVALID_CHAIN); ++ else if (flags & PURPLE_CERTIFICATE_REVOKED) ++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_REVOKED); ++ else ++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_UNKNOWN_ERROR); ++ ++ tmp = g_strdup_printf(_("The certificate for %s could not be validated."), ++ vrq->subject_name); ++ secondary = g_strconcat(tmp, " ", error, NULL); ++ g_free(tmp); ++ ++ purple_notify_error(NULL, /* TODO: Probably wrong. */ ++ _("SSL Certificate Error"), ++ _("Unable to validate certificate"), ++ secondary); ++ g_free(secondary); ++ ++ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID); ++ return; ++ } else if (flags & PURPLE_CERTIFICATE_NON_FATALS_MASK) { ++ /* Non-fatal error. Prompt the user. */ ++ gchar *tmp; ++ GString *errors; ++ guint32 i = 1; ++ ++ tmp = g_strdup_printf(_("The certificate for %s could not be validated."), ++ vrq->subject_name); ++ errors = g_string_new(tmp); ++ g_free(tmp); ++ ++ errors = g_string_append_c(errors, '\n'); ++ ++ /* Special case a name mismatch because we want to display the two names... */ ++ if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) { ++ gchar *sn = purple_certificate_get_subject_name(peer_crt); ++ ++ if (sn) { ++ g_string_append_printf(errors, _("The certificate claims to be " ++ "from \"%s\" instead. This could mean that you are " ++ "not connecting to the service you believe you are."), ++ sn); ++ g_free(sn); ++ ++ flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH; ++ } ++ } ++ ++ while (i != PURPLE_CERTIFICATE_LAST) { ++ if (flags & i) { ++ errors = g_string_append_c(errors, '\n'); ++ g_string_append(errors, invalidity_reason_to_string(i)); ++ } ++ ++ i <<= 1; ++ } ++ ++ x509_tls_cached_user_auth(vrq, errors->str); ++ g_string_free(errors, TRUE); ++ return; ++ } ++ ++ /* If we reach this point, the certificate is good. */ ++ ++ /* Look up the local cache and store it there for future use */ ++ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, ++ "tls_peers"); ++ if (tls_peers) { ++ if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, ++ peer_crt)) { ++ purple_debug_error("certificate/x509/tls_cached", ++ "FAILED to cache peer certificate\n"); ++ } ++ } else { ++ purple_debug_error("certificate/x509/tls_cached", ++ "Unable to locate tls_peers certificate cache.\n"); ++ } ++ ++ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); ++} ++ ++static void ++x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateInvalidityFlags flags) ++{ ++ /* TODO: Looking this up by name over and over is expensive. ++ Fix, please! */ ++ PurpleCertificatePool *tls_peers = ++ purple_certificate_find_pool(x509_tls_cached.scheme_name, ++ "tls_peers"); ++ ++ /* The peer's certificate should be the first in the list */ ++ PurpleCertificate *peer_crt = ++ (PurpleCertificate *) vrq->cert_chain->data; ++ ++ PurpleCertificate *cached_crt; ++ GByteArray *peer_fpr, *cached_fpr; ++ ++ /* Load up the cached certificate */ ++ cached_crt = purple_certificate_pool_retrieve( ++ tls_peers, vrq->subject_name); ++ if ( !cached_crt ) { ++ purple_debug_warning("certificate/x509/tls_cached", ++ "Lookup failed on cached certificate!\n" ++ "Falling back to full verification.\n"); ++ /* vrq now becomes the problem of unknown_peer */ ++ x509_tls_cached_unknown_peer(vrq, flags); ++ return; ++ } ++ ++ /* Now get SHA1 sums for both and compare them */ ++ /* TODO: This is not an elegant way to compare certs */ ++ peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt); ++ cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt); ++ if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) { ++ purple_debug_info("certificate/x509/tls_cached", ++ "Peer cert matched cached\n"); ++ x509_tls_cached_complete(vrq, flags); ++ } else { ++ purple_debug_error("certificate/x509/tls_cached", ++ "Peer cert did NOT match cached\n"); ++ /* vrq now becomes the problem of the user */ ++ x509_tls_cached_unknown_peer(vrq, flags); ++ } ++ ++ purple_certificate_destroy(cached_crt); ++ g_byte_array_free(peer_fpr, TRUE); ++ g_byte_array_free(cached_fpr, TRUE); ++} ++ ++/* ++ * This is called from two points in x509_tls_cached_unknown_peer below ++ * once we've verified the signature chain is valid. Now we need to verify ++ * the subject name of the certificate. ++ */ ++static void ++x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateInvalidityFlags flags) ++{ ++ PurpleCertificate *peer_crt; ++ GList *chain = vrq->cert_chain; ++ ++ peer_crt = (PurpleCertificate *) chain->data; ++ ++ /* Last, check that the hostname matches */ ++ if ( ! purple_certificate_check_subject_name(peer_crt, ++ vrq->subject_name) ) { ++ gchar *sn = purple_certificate_get_subject_name(peer_crt); ++ ++ flags |= PURPLE_CERTIFICATE_NAME_MISMATCH; ++ purple_debug_error("certificate/x509/tls_cached", ++ "Name mismatch: Certificate given for %s " ++ "has a name of %s\n", ++ vrq->subject_name, sn); ++ g_free(sn); ++ } ++ ++ x509_tls_cached_complete(vrq, flags); ++} ++ ++/* For when we've never communicated with this party before */ ++/* TODO: Need ways to specify possibly multiple problems with a cert, or at ++ least reprioritize them. ++ */ ++static void ++x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq, ++ PurpleCertificateInvalidityFlags flags) ++{ ++ PurpleCertificatePool *ca; ++ PurpleCertificate *peer_crt; ++ PurpleCertificate *ca_crt, *end_crt; ++ PurpleCertificate *failing_crt; ++ GList *chain = vrq->cert_chain; ++ GSList *ca_crts, *cur; ++ GByteArray *last_fpr, *ca_fpr; ++ gboolean valid = FALSE; ++ gchar *ca_id; ++ ++ peer_crt = (PurpleCertificate *) chain->data; ++ ++ /* TODO: Figure out a way to check for a bad signature, as opposed to ++ "not self-signed" */ ++ if ( purple_certificate_signed_by(peer_crt, peer_crt) ) { ++ flags |= PURPLE_CERTIFICATE_SELF_SIGNED; ++ ++ purple_debug_info("certificate/x509/tls_cached", ++ "Certificate for %s is self-signed.\n", ++ vrq->subject_name); ++ ++ x509_tls_cached_check_subject_name(vrq, flags); ++ return; ++ } /* if (self signed) */ ++ ++ /* Next, attempt to verify the last certificate against a CA */ ++ ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca"); ++ ++ /* Next, check that the certificate chain is valid */ ++ if (!purple_certificate_check_signature_chain_with_failing(chain, ++ &failing_crt)) ++ { ++ gboolean chain_validated = FALSE; ++ /* ++ * Check if the failing certificate is in the CA store. If it is, then ++ * consider this fully validated. This works around issues with some ++ * prominent intermediate CAs whose signature is md5WithRSAEncryption. ++ * I'm looking at CACert Class 3 here. See #4458 for details. ++ */ ++ if (ca) { ++ gchar *uid = purple_certificate_get_unique_id(failing_crt); ++ PurpleCertificate *ca_crt = purple_certificate_pool_retrieve(ca, uid); ++ if (ca_crt != NULL) { ++ GByteArray *failing_fpr; ++ GByteArray *ca_fpr; ++ failing_fpr = purple_certificate_get_fingerprint_sha1(failing_crt); ++ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); ++ if (byte_arrays_equal(failing_fpr, ca_fpr)) { ++ purple_debug_info("certificate/x509/tls_cached", ++ "Full chain verification failed (probably a bad " ++ "signature algorithm), but found the last " ++ "certificate %s in the CA pool.\n", uid); ++ chain_validated = TRUE; ++ } ++ ++ g_byte_array_free(failing_fpr, TRUE); ++ g_byte_array_free(ca_fpr, TRUE); ++ } ++ ++ purple_certificate_destroy(ca_crt); ++ g_free(uid); ++ } ++ ++ /* ++ * If we get here, either the cert matched the stuff right above ++ * or it didn't, in which case we give up and complain to the user. ++ */ ++ if (!chain_validated) ++ /* TODO: Tell the user where the chain broke? */ ++ flags |= PURPLE_CERTIFICATE_INVALID_CHAIN; ++ ++ x509_tls_cached_check_subject_name(vrq, flags); ++ return; ++ } /* if (signature chain not good) */ ++ ++ /* If, for whatever reason, there is no Certificate Authority pool ++ loaded, we'll verify the subject name and then warn about thsi. */ ++ if ( !ca ) { ++ purple_debug_error("certificate/x509/tls_cached", ++ "No X.509 Certificate Authority pool " ++ "could be found!\n"); ++ ++ flags |= PURPLE_CERTIFICATE_NO_CA_POOL; ++ ++ x509_tls_cached_check_subject_name(vrq, flags); ++ return; ++ } ++ ++ end_crt = g_list_last(chain)->data; ++ ++ /* Attempt to look up the last certificate's issuer */ ++ ca_id = purple_certificate_get_issuer_unique_id(end_crt); ++ purple_debug_info("certificate/x509/tls_cached", ++ "Checking for a CA with DN=%s\n", ++ ca_id); ++ ca_crts = x509_ca_get_certs(ca_id); ++ if ( NULL == ca_crts ) { ++ flags |= PURPLE_CERTIFICATE_CA_UNKNOWN; ++ ++ purple_debug_warning("certificate/x509/tls_cached", ++ "Certificate Authority with DN='%s' not " ++ "found. I'll prompt the user, I guess.\n", ++ ca_id); ++ g_free(ca_id); ++ ++ x509_tls_cached_check_subject_name(vrq, flags); ++ return; ++ } ++ ++ g_free(ca_id); ++ ++ /* ++ * Check the fingerprints; if they match, then this certificate *is* one ++ * of the designated "trusted roots", and we don't need to verify the ++ * signature. This is good because some of the older roots are self-signed ++ * with bad hash algorithms that we don't want to allow in any other ++ * circumstances (one of Verisign's root CAs is self-signed with MD2). ++ * ++ * If the fingerprints don't match, we'll fall back to checking the ++ * signature. ++ * ++ * GnuTLS doesn't seem to include the final root in the verification ++ * list, so this check will never succeed. NSS *does* include it in ++ * the list, so here we are. ++ */ ++ last_fpr = purple_certificate_get_fingerprint_sha1(end_crt); ++ for (cur = ca_crts; cur; cur = cur->next) { ++ ca_crt = cur->data; ++ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); ++ ++ if ( byte_arrays_equal(last_fpr, ca_fpr) || ++ purple_certificate_signed_by(end_crt, ca_crt) ) ++ { ++ /* TODO: If signed_by ever returns a reason, maybe mention ++ that, too. */ ++ /* TODO: Also mention the CA involved. While I could do this ++ now, a full DN is a little much with which to assault the ++ user's poor, leaky eyes. */ ++ valid = TRUE; ++ g_byte_array_free(ca_fpr, TRUE); ++ break; ++ } ++ ++ g_byte_array_free(ca_fpr, TRUE); ++ } ++ ++ if (valid == FALSE) ++ flags |= PURPLE_CERTIFICATE_INVALID_CHAIN; ++ ++ g_slist_foreach(ca_crts, (GFunc)purple_certificate_destroy, NULL); ++ g_slist_free(ca_crts); ++ g_byte_array_free(last_fpr, TRUE); ++ ++ x509_tls_cached_check_subject_name(vrq, flags); ++} ++ ++static void ++x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq) ++{ ++ const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */ ++ PurpleCertificatePool *tls_peers; ++ time_t now, activation, expiration; ++ PurpleCertificateInvalidityFlags flags = PURPLE_CERTIFICATE_NO_PROBLEMS; ++ gboolean ret; ++ ++ g_return_if_fail(vrq); ++ ++ purple_debug_info("certificate/x509/tls_cached", ++ "Starting verify for %s\n", ++ vrq->subject_name); ++ ++ /* ++ * Verify the first certificate (the main one) has been activated and ++ * isn't expired, i.e. activation < now < expiration. ++ */ ++ now = time(NULL); ++ ret = purple_certificate_get_times(vrq->cert_chain->data, &activation, ++ &expiration); ++ if (!ret) { ++ flags |= PURPLE_CERTIFICATE_EXPIRED | PURPLE_CERTIFICATE_NOT_ACTIVATED; ++ purple_debug_error("certificate/x509/tls_cached", ++ "Failed to get validity times for certificate %s\n", ++ vrq->subject_name); ++ } else if (now > expiration) { ++ flags |= PURPLE_CERTIFICATE_EXPIRED; ++ purple_debug_error("certificate/x509/tls_cached", ++ "Certificate %s expired at %s\n", ++ vrq->subject_name, ctime(&expiration)); ++ } else if (now < activation) { ++ flags |= PURPLE_CERTIFICATE_NOT_ACTIVATED; ++ purple_debug_error("certificate/x509/tls_cached", ++ "Certificate %s is not yet valid, will be at %s\n", ++ vrq->subject_name, ctime(&activation)); ++ } ++ ++ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name); ++ ++ if (!tls_peers) { ++ purple_debug_error("certificate/x509/tls_cached", ++ "Couldn't find local peers cache %s\n", ++ tls_peers_name); ++ ++ /* vrq now becomes the problem of unknown_peer */ ++ x509_tls_cached_unknown_peer(vrq, flags); ++ return; ++ } ++ ++ /* Check if the peer has a certificate cached already */ ++ purple_debug_info("certificate/x509/tls_cached", ++ "Checking for cached cert...\n"); ++ if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) { ++ purple_debug_info("certificate/x509/tls_cached", ++ "...Found cached cert\n"); ++ /* vrq is now the responsibility of cert_in_cache */ ++ x509_tls_cached_cert_in_cache(vrq, flags); ++ } else { ++ purple_debug_warning("certificate/x509/tls_cached", ++ "...Not in cache\n"); ++ /* vrq now becomes the problem of unknown_peer */ ++ x509_tls_cached_unknown_peer(vrq, flags); ++ } ++} ++ ++static void ++x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq) ++{ ++ g_return_if_fail(vrq); ++} ++ ++static PurpleCertificateVerifier x509_tls_cached = { ++ "x509", /* Scheme name */ ++ "tls_cached", /* Verifier name */ ++ x509_tls_cached_start_verify, /* Verification begin */ ++ x509_tls_cached_destroy_request,/* Request cleanup */ ++ ++ NULL, ++ NULL, ++ NULL, ++ NULL ++ ++}; ++ ++/****************************************************************************/ ++/* Subsystem */ ++/****************************************************************************/ ++void ++purple_certificate_init(void) ++{ ++ /* Register builtins */ ++ purple_certificate_register_verifier(&x509_singleuse); ++ purple_certificate_register_pool(&x509_ca); ++ purple_certificate_register_pool(&x509_tls_peers); ++ purple_certificate_register_verifier(&x509_tls_cached); ++} ++ ++void ++purple_certificate_uninit(void) ++{ ++ /* Unregister all Verifiers */ ++ g_list_foreach(cert_verifiers, (GFunc)purple_certificate_unregister_verifier, NULL); ++ ++ /* Unregister all Pools */ ++ g_list_foreach(cert_pools, (GFunc)purple_certificate_unregister_pool, NULL); ++} ++ ++gpointer ++purple_certificate_get_handle(void) ++{ ++ static gint handle; ++ return &handle; ++} ++ ++PurpleCertificateScheme * ++purple_certificate_find_scheme(const gchar *name) ++{ ++ PurpleCertificateScheme *scheme = NULL; ++ GList *l; ++ ++ g_return_val_if_fail(name, NULL); ++ ++ /* Traverse the list of registered schemes and locate the ++ one whose name matches */ ++ for(l = cert_schemes; l; l = l->next) { ++ scheme = (PurpleCertificateScheme *)(l->data); ++ ++ /* Name matches? that's our man */ ++ if(!g_ascii_strcasecmp(scheme->name, name)) ++ return scheme; ++ } ++ ++ purple_debug_warning("certificate", ++ "CertificateScheme %s requested but not found.\n", ++ name); ++ ++ /* TODO: Signalling and such? */ ++ ++ return NULL; ++} ++ ++GList * ++purple_certificate_get_schemes(void) ++{ ++ return cert_schemes; ++} ++ ++gboolean ++purple_certificate_register_scheme(PurpleCertificateScheme *scheme) ++{ ++ g_return_val_if_fail(scheme != NULL, FALSE); ++ ++ /* Make sure no scheme is registered with the same name */ ++ if (purple_certificate_find_scheme(scheme->name) != NULL) { ++ return FALSE; ++ } ++ ++ /* Okay, we're golden. Register it. */ ++ cert_schemes = g_list_prepend(cert_schemes, scheme); ++ ++ /* TODO: Signalling and such? */ ++ ++ purple_debug_info("certificate", ++ "CertificateScheme %s registered\n", ++ scheme->name); ++ ++ return TRUE; ++} ++ ++gboolean ++purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme) ++{ ++ if (NULL == scheme) { ++ purple_debug_warning("certificate", ++ "Attempting to unregister NULL scheme\n"); ++ return FALSE; ++ } ++ ++ /* TODO: signalling? */ ++ ++ /* TODO: unregister all CertificateVerifiers for this scheme?*/ ++ /* TODO: unregister all CertificatePools for this scheme? */ ++ /* Neither of the above should be necessary, though */ ++ cert_schemes = g_list_remove(cert_schemes, scheme); ++ ++ purple_debug_info("certificate", ++ "CertificateScheme %s unregistered\n", ++ scheme->name); ++ ++ ++ return TRUE; ++} ++ ++PurpleCertificateVerifier * ++purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name) ++{ ++ PurpleCertificateVerifier *vr = NULL; ++ GList *l; ++ ++ g_return_val_if_fail(scheme_name, NULL); ++ g_return_val_if_fail(ver_name, NULL); ++ ++ /* Traverse the list of registered verifiers and locate the ++ one whose name matches */ ++ for(l = cert_verifiers; l; l = l->next) { ++ vr = (PurpleCertificateVerifier *)(l->data); ++ ++ /* Scheme and name match? */ ++ if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) && ++ !g_ascii_strcasecmp(vr->name, ver_name)) ++ return vr; ++ } ++ ++ purple_debug_warning("certificate", ++ "CertificateVerifier %s, %s requested but not found.\n", ++ scheme_name, ver_name); ++ ++ /* TODO: Signalling and such? */ ++ ++ return NULL; ++} ++ ++ ++GList * ++purple_certificate_get_verifiers(void) ++{ ++ return cert_verifiers; ++} ++ ++gboolean ++purple_certificate_register_verifier(PurpleCertificateVerifier *vr) ++{ ++ g_return_val_if_fail(vr != NULL, FALSE); ++ ++ /* Make sure no verifier is registered with the same scheme/name */ ++ if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) { ++ return FALSE; ++ } ++ ++ /* Okay, we're golden. Register it. */ ++ cert_verifiers = g_list_prepend(cert_verifiers, vr); ++ ++ /* TODO: Signalling and such? */ ++ ++ purple_debug_info("certificate", ++ "CertificateVerifier %s registered\n", ++ vr->name); ++ return TRUE; ++} ++ ++gboolean ++purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr) ++{ ++ if (NULL == vr) { ++ purple_debug_warning("certificate", ++ "Attempting to unregister NULL verifier\n"); ++ return FALSE; ++ } ++ ++ /* TODO: signalling? */ ++ ++ cert_verifiers = g_list_remove(cert_verifiers, vr); ++ ++ ++ purple_debug_info("certificate", ++ "CertificateVerifier %s unregistered\n", ++ vr->name); ++ ++ return TRUE; ++} ++ ++PurpleCertificatePool * ++purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name) ++{ ++ PurpleCertificatePool *pool = NULL; ++ GList *l; ++ ++ g_return_val_if_fail(scheme_name, NULL); ++ g_return_val_if_fail(pool_name, NULL); ++ ++ /* Traverse the list of registered pools and locate the ++ one whose name matches */ ++ for(l = cert_pools; l; l = l->next) { ++ pool = (PurpleCertificatePool *)(l->data); ++ ++ /* Scheme and name match? */ ++ if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) && ++ !g_ascii_strcasecmp(pool->name, pool_name)) ++ return pool; ++ } ++ ++ purple_debug_warning("certificate", ++ "CertificatePool %s, %s requested but not found.\n", ++ scheme_name, pool_name); ++ ++ /* TODO: Signalling and such? */ ++ ++ return NULL; ++ ++} ++ ++GList * ++purple_certificate_get_pools(void) ++{ ++ return cert_pools; ++} ++ ++gboolean ++purple_certificate_register_pool(PurpleCertificatePool *pool) ++{ ++ g_return_val_if_fail(pool, FALSE); ++ g_return_val_if_fail(pool->scheme_name, FALSE); ++ g_return_val_if_fail(pool->name, FALSE); ++ g_return_val_if_fail(pool->fullname, FALSE); ++ ++ /* Make sure no pools are registered under this name */ ++ if (purple_certificate_find_pool(pool->scheme_name, pool->name)) { ++ return FALSE; ++ } ++ ++ /* Initialize the pool if needed */ ++ if (pool->init) { ++ gboolean success; ++ ++ success = pool->init(); ++ if (!success) ++ return FALSE; ++ } ++ ++ /* Register the Pool */ ++ cert_pools = g_list_prepend(cert_pools, pool); ++ ++ /* TODO: Emit a signal that the pool got registered */ ++ ++ PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool); ++ purple_signal_register(pool, /* Signals emitted from pool */ ++ "certificate-stored", ++ purple_marshal_VOID__POINTER_POINTER, ++ NULL, /* No callback return value */ ++ 2, /* Two non-data arguments */ ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_CERTIFICATEPOOL), ++ purple_value_new(PURPLE_TYPE_STRING)); ++ ++ purple_signal_register(pool, /* Signals emitted from pool */ ++ "certificate-deleted", ++ purple_marshal_VOID__POINTER_POINTER, ++ NULL, /* No callback return value */ ++ 2, /* Two non-data arguments */ ++ purple_value_new(PURPLE_TYPE_SUBTYPE, ++ PURPLE_SUBTYPE_CERTIFICATEPOOL), ++ purple_value_new(PURPLE_TYPE_STRING)); ++ ++ purple_debug_info("certificate", ++ "CertificatePool %s registered\n", ++ pool->name); ++ ++ return TRUE; ++} ++ ++gboolean ++purple_certificate_unregister_pool(PurpleCertificatePool *pool) ++{ ++ if (NULL == pool) { ++ purple_debug_warning("certificate", ++ "Attempting to unregister NULL pool\n"); ++ return FALSE; ++ } ++ ++ /* Check that the pool is registered */ ++ if (!g_list_find(cert_pools, pool)) { ++ purple_debug_warning("certificate", ++ "Pool to unregister isn't registered!\n"); ++ ++ return FALSE; ++ } ++ ++ /* Uninit the pool if needed */ ++ PURPLE_DBUS_UNREGISTER_POINTER(pool); ++ if (pool->uninit) { ++ pool->uninit(); ++ } ++ ++ cert_pools = g_list_remove(cert_pools, pool); ++ ++ /* TODO: Signalling? */ ++ purple_signal_unregister(pool, "certificate-stored"); ++ purple_signal_unregister(pool, "certificate-deleted"); ++ ++ purple_debug_info("certificate", ++ "CertificatePool %s unregistered\n", ++ pool->name); ++ return TRUE; ++} ++ ++/****************************************************************************/ ++/* Scheme-specific functions */ ++/****************************************************************************/ ++ ++void ++purple_certificate_display_x509(PurpleCertificate *crt) ++{ ++ gchar *sha_asc; ++ GByteArray *sha_bin; ++ gchar *cn; ++ time_t activation, expiration; ++ gchar *activ_str, *expir_str; ++ gchar *secondary; ++ ++ /* Pull out the SHA1 checksum */ ++ sha_bin = purple_certificate_get_fingerprint_sha1(crt); ++ /* Now decode it for display */ ++ sha_asc = purple_base16_encode_chunked(sha_bin->data, ++ sha_bin->len); ++ ++ /* Get the cert Common Name */ ++ /* TODO: Will break on CA certs */ ++ cn = purple_certificate_get_subject_name(crt); ++ ++ /* Get the certificate times */ ++ /* TODO: Check the times against localtime */ ++ /* TODO: errorcheck? */ ++ if (!purple_certificate_get_times(crt, &activation, &expiration)) { ++ purple_debug_error("certificate", ++ "Failed to get certificate times!\n"); ++ activation = expiration = 0; ++ } ++ activ_str = g_strdup(ctime(&activation)); ++ expir_str = g_strdup(ctime(&expiration)); ++ ++ /* Make messages */ ++ secondary = g_strdup_printf(_("Common name: %s\n\n" ++ "Fingerprint (SHA1): %s\n\n" ++ "Activation date: %s\n" ++ "Expiration date: %s\n"), ++ cn ? cn : "(null)", ++ sha_asc ? sha_asc : "(null)", ++ activ_str ? activ_str : "(null)", ++ expir_str ? expir_str : "(null)"); ++ ++ /* Make a semi-pretty display */ ++ purple_notify_info( ++ NULL, /* TODO: Find what the handle ought to be */ ++ _("Certificate Information"), ++ "", ++ secondary); ++ ++ /* Cleanup */ ++ g_free(cn); ++ g_free(secondary); ++ g_free(sha_asc); ++ g_free(activ_str); ++ g_free(expir_str); ++ g_byte_array_free(sha_bin, TRUE); ++} ++ ++void purple_certificate_add_ca_search_path(const char *path) ++{ ++ if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp)) ++ return; ++ x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path)); ++} ++ +diff -rupN pidgin-2.7.7/libpurple/plugins/ssl/ssl-gnutls.c pidgin-2.7.7-new//libpurple/plugins/ssl/ssl-gnutls.c +--- pidgin-2.7.7/libpurple/plugins/ssl/ssl-gnutls.c 2011-03-27 09:05:47.652552999 -0600 ++++ pidgin-2.7.7-new//libpurple/plugins/ssl/ssl-gnutls.c 2011-03-27 09:15:29.968552999 -0600 +@@ -856,7 +856,10 @@ x509_destroy_certificate(PurpleCertifica + static gboolean + x509_certificate_signed_by(PurpleCertificate * crt, + PurpleCertificate * issuer) ++ + { ++// Don't care if cert is signed with MD2 hash ++ gnutls_certificate_set_verify_flags(issuer, GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2); + gnutls_x509_crt crt_dat; + gnutls_x509_crt issuer_dat; + unsigned int verify; /* used to store result from GnuTLS verifier */ +Binary files pidgin-2.7.7/libpurple/protocols/facebook/arm/libjson-glib-1.0.so and pidgin-2.7.7-new//libpurple/protocols/facebook/arm/libjson-glib-1.0.so differ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/COPYING pidgin-2.7.7-new//libpurple/protocols/facebook/COPYING +--- pidgin-2.7.7/libpurple/protocols/facebook/COPYING 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/COPYING 2011-03-27 09:15:30.752552999 -0600 +@@ -0,0 +1,674 @@ ++ GNU GENERAL PUBLIC LICENSE ++ Version 3, 29 June 2007 ++ ++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ Preamble ++ ++ The GNU General Public License is a free, copyleft license for ++software and other kinds of works. ++ ++ The licenses for most software and other practical works are designed ++to take away your freedom to share and change the works. By contrast, ++the GNU General Public License is intended to guarantee your freedom to ++share and change all versions of a program--to make sure it remains free ++software for all its users. We, the Free Software Foundation, use the ++GNU General Public License for most of our software; it applies also to ++any other work released this way by its authors. You can apply it to ++your programs, too. ++ ++ When we speak of free software, we are referring to freedom, not ++price. Our General Public Licenses are designed to make sure that you ++have the freedom to distribute copies of free software (and charge for ++them if you wish), that you receive source code or can get it if you ++want it, that you can change the software or use pieces of it in new ++free programs, and that you know you can do these things. ++ ++ To protect your rights, we need to prevent others from denying you ++these rights or asking you to surrender the rights. Therefore, you have ++certain responsibilities if you distribute copies of the software, or if ++you modify it: responsibilities to respect the freedom of others. ++ ++ For example, if you distribute copies of such a program, whether ++gratis or for a fee, you must pass on to the recipients the same ++freedoms that you received. You must make sure that they, too, receive ++or can get the source code. And you must show them these terms so they ++know their rights. ++ ++ Developers that use the GNU GPL protect your rights with two steps: ++(1) assert copyright on the software, and (2) offer you this License ++giving you legal permission to copy, distribute and/or modify it. ++ ++ For the developers' and authors' protection, the GPL clearly explains ++that there is no warranty for this free software. For both users' and ++authors' sake, the GPL requires that modified versions be marked as ++changed, so that their problems will not be attributed erroneously to ++authors of previous versions. ++ ++ Some devices are designed to deny users access to install or run ++modified versions of the software inside them, although the manufacturer ++can do so. This is fundamentally incompatible with the aim of ++protecting users' freedom to change the software. The systematic ++pattern of such abuse occurs in the area of products for individuals to ++use, which is precisely where it is most unacceptable. Therefore, we ++have designed this version of the GPL to prohibit the practice for those ++products. If such problems arise substantially in other domains, we ++stand ready to extend this provision to those domains in future versions ++of the GPL, as needed to protect the freedom of users. ++ ++ Finally, every program is threatened constantly by software patents. ++States should not allow patents to restrict development and use of ++software on general-purpose computers, but in those that do, we wish to ++avoid the special danger that patents applied to a free program could ++make it effectively proprietary. To prevent this, the GPL assures that ++patents cannot be used to render the program non-free. ++ ++ The precise terms and conditions for copying, distribution and ++modification follow. ++ ++ TERMS AND CONDITIONS ++ ++ 0. Definitions. ++ ++ "This License" refers to version 3 of the GNU General Public License. ++ ++ "Copyright" also means copyright-like laws that apply to other kinds of ++works, such as semiconductor masks. ++ ++ "The Program" refers to any copyrightable work licensed under this ++License. Each licensee is addressed as "you". "Licensees" and ++"recipients" may be individuals or organizations. ++ ++ To "modify" a work means to copy from or adapt all or part of the work ++in a fashion requiring copyright permission, other than the making of an ++exact copy. The resulting work is called a "modified version" of the ++earlier work or a work "based on" the earlier work. ++ ++ A "covered work" means either the unmodified Program or a work based ++on the Program. ++ ++ To "propagate" a work means to do anything with it that, without ++permission, would make you directly or secondarily liable for ++infringement under applicable copyright law, except executing it on a ++computer or modifying a private copy. Propagation includes copying, ++distribution (with or without modification), making available to the ++public, and in some countries other activities as well. ++ ++ To "convey" a work means any kind of propagation that enables other ++parties to make or receive copies. Mere interaction with a user through ++a computer network, with no transfer of a copy, is not conveying. ++ ++ An interactive user interface displays "Appropriate Legal Notices" ++to the extent that it includes a convenient and prominently visible ++feature that (1) displays an appropriate copyright notice, and (2) ++tells the user that there is no warranty for the work (except to the ++extent that warranties are provided), that licensees may convey the ++work under this License, and how to view a copy of this License. If ++the interface presents a list of user commands or options, such as a ++menu, a prominent item in the list meets this criterion. ++ ++ 1. Source Code. ++ ++ The "source code" for a work means the preferred form of the work ++for making modifications to it. "Object code" means any non-source ++form of a work. ++ ++ A "Standard Interface" means an interface that either is an official ++standard defined by a recognized standards body, or, in the case of ++interfaces specified for a particular programming language, one that ++is widely used among developers working in that language. ++ ++ The "System Libraries" of an executable work include anything, other ++than the work as a whole, that (a) is included in the normal form of ++packaging a Major Component, but which is not part of that Major ++Component, and (b) serves only to enable use of the work with that ++Major Component, or to implement a Standard Interface for which an ++implementation is available to the public in source code form. A ++"Major Component", in this context, means a major essential component ++(kernel, window system, and so on) of the specific operating system ++(if any) on which the executable work runs, or a compiler used to ++produce the work, or an object code interpreter used to run it. ++ ++ The "Corresponding Source" for a work in object code form means all ++the source code needed to generate, install, and (for an executable ++work) run the object code and to modify the work, including scripts to ++control those activities. However, it does not include the work's ++System Libraries, or general-purpose tools or generally available free ++programs which are used unmodified in performing those activities but ++which are not part of the work. For example, Corresponding Source ++includes interface definition files associated with source files for ++the work, and the source code for shared libraries and dynamically ++linked subprograms that the work is specifically designed to require, ++such as by intimate data communication or control flow between those ++subprograms and other parts of the work. ++ ++ The Corresponding Source need not include anything that users ++can regenerate automatically from other parts of the Corresponding ++Source. ++ ++ The Corresponding Source for a work in source code form is that ++same work. ++ ++ 2. Basic Permissions. ++ ++ All rights granted under this License are granted for the term of ++copyright on the Program, and are irrevocable provided the stated ++conditions are met. This License explicitly affirms your unlimited ++permission to run the unmodified Program. The output from running a ++covered work is covered by this License only if the output, given its ++content, constitutes a covered work. This License acknowledges your ++rights of fair use or other equivalent, as provided by copyright law. ++ ++ You may make, run and propagate covered works that you do not ++convey, without conditions so long as your license otherwise remains ++in force. You may convey covered works to others for the sole purpose ++of having them make modifications exclusively for you, or provide you ++with facilities for running those works, provided that you comply with ++the terms of this License in conveying all material for which you do ++not control copyright. Those thus making or running the covered works ++for you must do so exclusively on your behalf, under your direction ++and control, on terms that prohibit them from making any copies of ++your copyrighted material outside their relationship with you. ++ ++ Conveying under any other circumstances is permitted solely under ++the conditions stated below. Sublicensing is not allowed; section 10 ++makes it unnecessary. ++ ++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law. ++ ++ No covered work shall be deemed part of an effective technological ++measure under any applicable law fulfilling obligations under article ++11 of the WIPO copyright treaty adopted on 20 December 1996, or ++similar laws prohibiting or restricting circumvention of such ++measures. ++ ++ When you convey a covered work, you waive any legal power to forbid ++circumvention of technological measures to the extent such circumvention ++is effected by exercising rights under this License with respect to ++the covered work, and you disclaim any intention to limit operation or ++modification of the work as a means of enforcing, against the work's ++users, your or third parties' legal rights to forbid circumvention of ++technological measures. ++ ++ 4. Conveying Verbatim Copies. ++ ++ You may convey verbatim copies of the Program's source code as you ++receive it, in any medium, provided that you conspicuously and ++appropriately publish on each copy an appropriate copyright notice; ++keep intact all notices stating that this License and any ++non-permissive terms added in accord with section 7 apply to the code; ++keep intact all notices of the absence of any warranty; and give all ++recipients a copy of this License along with the Program. ++ ++ You may charge any price or no price for each copy that you convey, ++and you may offer support or warranty protection for a fee. ++ ++ 5. Conveying Modified Source Versions. ++ ++ You may convey a work based on the Program, or the modifications to ++produce it from the Program, in the form of source code under the ++terms of section 4, provided that you also meet all of these conditions: ++ ++ a) The work must carry prominent notices stating that you modified ++ it, and giving a relevant date. ++ ++ b) The work must carry prominent notices stating that it is ++ released under this License and any conditions added under section ++ 7. This requirement modifies the requirement in section 4 to ++ "keep intact all notices". ++ ++ c) You must license the entire work, as a whole, under this ++ License to anyone who comes into possession of a copy. This ++ License will therefore apply, along with any applicable section 7 ++ additional terms, to the whole of the work, and all its parts, ++ regardless of how they are packaged. This License gives no ++ permission to license the work in any other way, but it does not ++ invalidate such permission if you have separately received it. ++ ++ d) If the work has interactive user interfaces, each must display ++ Appropriate Legal Notices; however, if the Program has interactive ++ interfaces that do not display Appropriate Legal Notices, your ++ work need not make them do so. ++ ++ A compilation of a covered work with other separate and independent ++works, which are not by their nature extensions of the covered work, ++and which are not combined with it such as to form a larger program, ++in or on a volume of a storage or distribution medium, is called an ++"aggregate" if the compilation and its resulting copyright are not ++used to limit the access or legal rights of the compilation's users ++beyond what the individual works permit. Inclusion of a covered work ++in an aggregate does not cause this License to apply to the other ++parts of the aggregate. ++ ++ 6. Conveying Non-Source Forms. ++ ++ You may convey a covered work in object code form under the terms ++of sections 4 and 5, provided that you also convey the ++machine-readable Corresponding Source under the terms of this License, ++in one of these ways: ++ ++ a) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by the ++ Corresponding Source fixed on a durable physical medium ++ customarily used for software interchange. ++ ++ b) Convey the object code in, or embodied in, a physical product ++ (including a physical distribution medium), accompanied by a ++ written offer, valid for at least three years and valid for as ++ long as you offer spare parts or customer support for that product ++ model, to give anyone who possesses the object code either (1) a ++ copy of the Corresponding Source for all the software in the ++ product that is covered by this License, on a durable physical ++ medium customarily used for software interchange, for a price no ++ more than your reasonable cost of physically performing this ++ conveying of source, or (2) access to copy the ++ Corresponding Source from a network server at no charge. ++ ++ c) Convey individual copies of the object code with a copy of the ++ written offer to provide the Corresponding Source. This ++ alternative is allowed only occasionally and noncommercially, and ++ only if you received the object code with such an offer, in accord ++ with subsection 6b. ++ ++ d) Convey the object code by offering access from a designated ++ place (gratis or for a charge), and offer equivalent access to the ++ Corresponding Source in the same way through the same place at no ++ further charge. You need not require recipients to copy the ++ Corresponding Source along with the object code. If the place to ++ copy the object code is a network server, the Corresponding Source ++ may be on a different server (operated by you or a third party) ++ that supports equivalent copying facilities, provided you maintain ++ clear directions next to the object code saying where to find the ++ Corresponding Source. Regardless of what server hosts the ++ Corresponding Source, you remain obligated to ensure that it is ++ available for as long as needed to satisfy these requirements. ++ ++ e) Convey the object code using peer-to-peer transmission, provided ++ you inform other peers where the object code and Corresponding ++ Source of the work are being offered to the general public at no ++ charge under subsection 6d. ++ ++ A separable portion of the object code, whose source code is excluded ++from the Corresponding Source as a System Library, need not be ++included in conveying the object code work. ++ ++ A "User Product" is either (1) a "consumer product", which means any ++tangible personal property which is normally used for personal, family, ++or household purposes, or (2) anything designed or sold for incorporation ++into a dwelling. In determining whether a product is a consumer product, ++doubtful cases shall be resolved in favor of coverage. For a particular ++product received by a particular user, "normally used" refers to a ++typical or common use of that class of product, regardless of the status ++of the particular user or of the way in which the particular user ++actually uses, or expects or is expected to use, the product. A product ++is a consumer product regardless of whether the product has substantial ++commercial, industrial or non-consumer uses, unless such uses represent ++the only significant mode of use of the product. ++ ++ "Installation Information" for a User Product means any methods, ++procedures, authorization keys, or other information required to install ++and execute modified versions of a covered work in that User Product from ++a modified version of its Corresponding Source. The information must ++suffice to ensure that the continued functioning of the modified object ++code is in no case prevented or interfered with solely because ++modification has been made. ++ ++ If you convey an object code work under this section in, or with, or ++specifically for use in, a User Product, and the conveying occurs as ++part of a transaction in which the right of possession and use of the ++User Product is transferred to the recipient in perpetuity or for a ++fixed term (regardless of how the transaction is characterized), the ++Corresponding Source conveyed under this section must be accompanied ++by the Installation Information. But this requirement does not apply ++if neither you nor any third party retains the ability to install ++modified object code on the User Product (for example, the work has ++been installed in ROM). ++ ++ The requirement to provide Installation Information does not include a ++requirement to continue to provide support service, warranty, or updates ++for a work that has been modified or installed by the recipient, or for ++the User Product in which it has been modified or installed. Access to a ++network may be denied when the modification itself materially and ++adversely affects the operation of the network or violates the rules and ++protocols for communication across the network. ++ ++ Corresponding Source conveyed, and Installation Information provided, ++in accord with this section must be in a format that is publicly ++documented (and with an implementation available to the public in ++source code form), and must require no special password or key for ++unpacking, reading or copying. ++ ++ 7. Additional Terms. ++ ++ "Additional permissions" are terms that supplement the terms of this ++License by making exceptions from one or more of its conditions. ++Additional permissions that are applicable to the entire Program shall ++be treated as though they were included in this License, to the extent ++that they are valid under applicable law. If additional permissions ++apply only to part of the Program, that part may be used separately ++under those permissions, but the entire Program remains governed by ++this License without regard to the additional permissions. ++ ++ When you convey a copy of a covered work, you may at your option ++remove any additional permissions from that copy, or from any part of ++it. (Additional permissions may be written to require their own ++removal in certain cases when you modify the work.) You may place ++additional permissions on material, added by you to a covered work, ++for which you have or can give appropriate copyright permission. ++ ++ Notwithstanding any other provision of this License, for material you ++add to a covered work, you may (if authorized by the copyright holders of ++that material) supplement the terms of this License with terms: ++ ++ a) Disclaiming warranty or limiting liability differently from the ++ terms of sections 15 and 16 of this License; or ++ ++ b) Requiring preservation of specified reasonable legal notices or ++ author attributions in that material or in the Appropriate Legal ++ Notices displayed by works containing it; or ++ ++ c) Prohibiting misrepresentation of the origin of that material, or ++ requiring that modified versions of such material be marked in ++ reasonable ways as different from the original version; or ++ ++ d) Limiting the use for publicity purposes of names of licensors or ++ authors of the material; or ++ ++ e) Declining to grant rights under trademark law for use of some ++ trade names, trademarks, or service marks; or ++ ++ f) Requiring indemnification of licensors and authors of that ++ material by anyone who conveys the material (or modified versions of ++ it) with contractual assumptions of liability to the recipient, for ++ any liability that these contractual assumptions directly impose on ++ those licensors and authors. ++ ++ All other non-permissive additional terms are considered "further ++restrictions" within the meaning of section 10. If the Program as you ++received it, or any part of it, contains a notice stating that it is ++governed by this License along with a term that is a further ++restriction, you may remove that term. If a license document contains ++a further restriction but permits relicensing or conveying under this ++License, you may add to a covered work material governed by the terms ++of that license document, provided that the further restriction does ++not survive such relicensing or conveying. ++ ++ If you add terms to a covered work in accord with this section, you ++must place, in the relevant source files, a statement of the ++additional terms that apply to those files, or a notice indicating ++where to find the applicable terms. ++ ++ Additional terms, permissive or non-permissive, may be stated in the ++form of a separately written license, or stated as exceptions; ++the above requirements apply either way. ++ ++ 8. Termination. ++ ++ You may not propagate or modify a covered work except as expressly ++provided under this License. Any attempt otherwise to propagate or ++modify it is void, and will automatically terminate your rights under ++this License (including any patent licenses granted under the third ++paragraph of section 11). ++ ++ However, if you cease all violation of this License, then your ++license from a particular copyright holder is reinstated (a) ++provisionally, unless and until the copyright holder explicitly and ++finally terminates your license, and (b) permanently, if the copyright ++holder fails to notify you of the violation by some reasonable means ++prior to 60 days after the cessation. ++ ++ Moreover, your license from a particular copyright holder is ++reinstated permanently if the copyright holder notifies you of the ++violation by some reasonable means, this is the first time you have ++received notice of violation of this License (for any work) from that ++copyright holder, and you cure the violation prior to 30 days after ++your receipt of the notice. ++ ++ Termination of your rights under this section does not terminate the ++licenses of parties who have received copies or rights from you under ++this License. If your rights have been terminated and not permanently ++reinstated, you do not qualify to receive new licenses for the same ++material under section 10. ++ ++ 9. Acceptance Not Required for Having Copies. ++ ++ You are not required to accept this License in order to receive or ++run a copy of the Program. Ancillary propagation of a covered work ++occurring solely as a consequence of using peer-to-peer transmission ++to receive a copy likewise does not require acceptance. However, ++nothing other than this License grants you permission to propagate or ++modify any covered work. These actions infringe copyright if you do ++not accept this License. Therefore, by modifying or propagating a ++covered work, you indicate your acceptance of this License to do so. ++ ++ 10. Automatic Licensing of Downstream Recipients. ++ ++ Each time you convey a covered work, the recipient automatically ++receives a license from the original licensors, to run, modify and ++propagate that work, subject to this License. You are not responsible ++for enforcing compliance by third parties with this License. ++ ++ An "entity transaction" is a transaction transferring control of an ++organization, or substantially all assets of one, or subdividing an ++organization, or merging organizations. If propagation of a covered ++work results from an entity transaction, each party to that ++transaction who receives a copy of the work also receives whatever ++licenses to the work the party's predecessor in interest had or could ++give under the previous paragraph, plus a right to possession of the ++Corresponding Source of the work from the predecessor in interest, if ++the predecessor has it or can get it with reasonable efforts. ++ ++ You may not impose any further restrictions on the exercise of the ++rights granted or affirmed under this License. For example, you may ++not impose a license fee, royalty, or other charge for exercise of ++rights granted under this License, and you may not initiate litigation ++(including a cross-claim or counterclaim in a lawsuit) alleging that ++any patent claim is infringed by making, using, selling, offering for ++sale, or importing the Program or any portion of it. ++ ++ 11. Patents. ++ ++ A "contributor" is a copyright holder who authorizes use under this ++License of the Program or a work on which the Program is based. The ++work thus licensed is called the contributor's "contributor version". ++ ++ A contributor's "essential patent claims" are all patent claims ++owned or controlled by the contributor, whether already acquired or ++hereafter acquired, that would be infringed by some manner, permitted ++by this License, of making, using, or selling its contributor version, ++but do not include claims that would be infringed only as a ++consequence of further modification of the contributor version. For ++purposes of this definition, "control" includes the right to grant ++patent sublicenses in a manner consistent with the requirements of ++this License. ++ ++ Each contributor grants you a non-exclusive, worldwide, royalty-free ++patent license under the contributor's essential patent claims, to ++make, use, sell, offer for sale, import and otherwise run, modify and ++propagate the contents of its contributor version. ++ ++ In the following three paragraphs, a "patent license" is any express ++agreement or commitment, however denominated, not to enforce a patent ++(such as an express permission to practice a patent or covenant not to ++sue for patent infringement). To "grant" such a patent license to a ++party means to make such an agreement or commitment not to enforce a ++patent against the party. ++ ++ If you convey a covered work, knowingly relying on a patent license, ++and the Corresponding Source of the work is not available for anyone ++to copy, free of charge and under the terms of this License, through a ++publicly available network server or other readily accessible means, ++then you must either (1) cause the Corresponding Source to be so ++available, or (2) arrange to deprive yourself of the benefit of the ++patent license for this particular work, or (3) arrange, in a manner ++consistent with the requirements of this License, to extend the patent ++license to downstream recipients. "Knowingly relying" means you have ++actual knowledge that, but for the patent license, your conveying the ++covered work in a country, or your recipient's use of the covered work ++in a country, would infringe one or more identifiable patents in that ++country that you have reason to believe are valid. ++ ++ If, pursuant to or in connection with a single transaction or ++arrangement, you convey, or propagate by procuring conveyance of, a ++covered work, and grant a patent license to some of the parties ++receiving the covered work authorizing them to use, propagate, modify ++or convey a specific copy of the covered work, then the patent license ++you grant is automatically extended to all recipients of the covered ++work and works based on it. ++ ++ A patent license is "discriminatory" if it does not include within ++the scope of its coverage, prohibits the exercise of, or is ++conditioned on the non-exercise of one or more of the rights that are ++specifically granted under this License. You may not convey a covered ++work if you are a party to an arrangement with a third party that is ++in the business of distributing software, under which you make payment ++to the third party based on the extent of your activity of conveying ++the work, and under which the third party grants, to any of the ++parties who would receive the covered work from you, a discriminatory ++patent license (a) in connection with copies of the covered work ++conveyed by you (or copies made from those copies), or (b) primarily ++for and in connection with specific products or compilations that ++contain the covered work, unless you entered into that arrangement, ++or that patent license was granted, prior to 28 March 2007. ++ ++ Nothing in this License shall be construed as excluding or limiting ++any implied license or other defenses to infringement that may ++otherwise be available to you under applicable patent law. ++ ++ 12. No Surrender of Others' Freedom. ++ ++ If conditions are imposed on you (whether by court order, agreement or ++otherwise) that contradict the conditions of this License, they do not ++excuse you from the conditions of this License. If you cannot convey a ++covered work so as to satisfy simultaneously your obligations under this ++License and any other pertinent obligations, then as a consequence you may ++not convey it at all. For example, if you agree to terms that obligate you ++to collect a royalty for further conveying from those to whom you convey ++the Program, the only way you could satisfy both those terms and this ++License would be to refrain entirely from conveying the Program. ++ ++ 13. Use with the GNU Affero General Public License. ++ ++ Notwithstanding any other provision of this License, you have ++permission to link or combine any covered work with a work licensed ++under version 3 of the GNU Affero General Public License into a single ++combined work, and to convey the resulting work. The terms of this ++License will continue to apply to the part which is the covered work, ++but the special requirements of the GNU Affero General Public License, ++section 13, concerning interaction through a network will apply to the ++combination as such. ++ ++ 14. Revised Versions of this License. ++ ++ The Free Software Foundation may publish revised and/or new versions of ++the GNU General Public License from time to time. Such new versions will ++be similar in spirit to the present version, but may differ in detail to ++address new problems or concerns. ++ ++ Each version is given a distinguishing version number. If the ++Program specifies that a certain numbered version of the GNU General ++Public License "or any later version" applies to it, you have the ++option of following the terms and conditions either of that numbered ++version or of any later version published by the Free Software ++Foundation. If the Program does not specify a version number of the ++GNU General Public License, you may choose any version ever published ++by the Free Software Foundation. ++ ++ If the Program specifies that a proxy can decide which future ++versions of the GNU General Public License can be used, that proxy's ++public statement of acceptance of a version permanently authorizes you ++to choose that version for the Program. ++ ++ Later license versions may give you additional or different ++permissions. However, no additional obligations are imposed on any ++author or copyright holder as a result of your choosing to follow a ++later version. ++ ++ 15. Disclaimer of Warranty. ++ ++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY ++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT ++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY ++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM ++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ++ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ++ ++ 16. Limitation of Liability. ++ ++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING ++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS ++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY ++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE ++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF ++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD ++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), ++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGES. ++ ++ 17. Interpretation of Sections 15 and 16. ++ ++ If the disclaimer of warranty and limitation of liability provided ++above cannot be given local legal effect according to their terms, ++reviewing courts shall apply local law that most closely approximates ++an absolute waiver of all civil liability in connection with the ++Program, unless a warranty or assumption of liability accompanies a ++copy of the Program in return for a fee. ++ ++ END OF TERMS AND CONDITIONS ++ ++ How to Apply These Terms to Your New Programs ++ ++ If you develop a new program, and you want it to be of the greatest ++possible use to the public, the best way to achieve this is to make it ++free software which everyone can redistribute and change under these terms. ++ ++ To do so, attach the following notices to the program. It is safest ++to attach them to the start of each source file to most effectively ++state the exclusion of warranty; and each file should have at least ++the "copyright" line and a pointer to where the full notice is found. ++ ++ <one line to give the program's name and a brief idea of what it does.> ++ Copyright (C) <year> <name of author> ++ ++ 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 3 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, see <http://www.gnu.org/licenses/>. ++ ++Also add information on how to contact you by electronic and paper mail. ++ ++ If the program does terminal interaction, make it output a short ++notice like this when it starts in an interactive mode: ++ ++ <program> Copyright (C) <year> <name of author> ++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. ++ This is free software, and you are welcome to redistribute it ++ under certain conditions; type `show c' for details. ++ ++The hypothetical commands `show w' and `show c' should show the appropriate ++parts of the General Public License. Of course, your program's commands ++might be different; for a GUI interface, you would use an "about box". ++ ++ You should also get your employer (if you work as a programmer) or school, ++if any, to sign a "copyright disclaimer" for the program, if necessary. ++For more information on this, and how to apply and follow the GNU GPL, see ++<http://www.gnu.org/licenses/>. ++ ++ The GNU General Public License does not permit incorporating your program ++into proprietary programs. If your program is a subroutine library, you ++may consider it more useful to permit linking proprietary applications with ++the library. If this is what you want to do, use the GNU Lesser General ++Public License instead of this License. But first, please read ++<http://www.gnu.org/philosophy/why-not-lgpl.html>. +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/COPYRIGHT pidgin-2.7.7-new//libpurple/protocols/facebook/COPYRIGHT +--- pidgin-2.7.7/libpurple/protocols/facebook/COPYRIGHT 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/COPYRIGHT 2011-03-27 09:15:30.848552999 -0600 +@@ -0,0 +1,5 @@ ++This protocol plugin is copyright (C) 2008 by the following: ++ ++Mark Doliner ++Casey Ho ++Eion Robb +Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook16.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook16.png differ +Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook22.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook22.png differ +Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook48.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook48.png differ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/facebook.nsi pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.nsi +--- pidgin-2.7.7/libpurple/protocols/facebook/facebook.nsi 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.nsi 2011-03-27 09:15:30.820553000 -0600 +@@ -0,0 +1,99 @@ ++; Script based on the Skype4Pidgin and Off-the-Record Messaging NSI files ++ ++ ++SetCompress off ++ ++; todo: SetBrandingImage ++; HM NIS Edit Wizard helper defines ++!define PRODUCT_NAME "pidgin-facebookchat" ++!define PRODUCT_VERSION "1.69" ++!define PRODUCT_PUBLISHER "Eion Robb" ++!define PRODUCT_WEB_SITE "http://pidgin-facebookchat.googlecode.com/" ++!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" ++!define PRODUCT_UNINST_ROOT_KEY "HKLM" ++ ++; MUI 1.67 compatible ------ ++!include "MUI.nsh" ++ ++; MUI Settings ++!define MUI_ABORTWARNING ++!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" ++!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" ++ ++; Welcome page ++!insertmacro MUI_PAGE_WELCOME ++; License page ++!insertmacro MUI_PAGE_LICENSE "COPYING" ++; Instfiles page ++!insertmacro MUI_PAGE_INSTFILES ++!define MUI_FINISHPAGE_RUN ++!define MUI_FINISHPAGE_RUN_TEXT "Run Pidgin" ++!define MUI_FINISHPAGE_RUN_FUNCTION "RunPidgin" ++!insertmacro MUI_PAGE_FINISH ++ ++; Uninstaller pages ++;!insertmacro MUI_UNPAGE_INSTFILES ++ ++; Language files ++!insertmacro MUI_LANGUAGE "English" ++ ++; MUI end ------ ++ ++Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" ++OutFile "${PRODUCT_NAME}.exe" ++ ++Var "PidginDir" ++ ++ShowInstDetails show ++ShowUnInstDetails show ++ ++Section "MainSection" SEC01 ++ ;Check for pidgin installation ++ Call GetPidginInstPath ++ ++ SetOverwrite try ++ ++ SetOutPath "$PidginDir\pixmaps\pidgin" ++ File "/oname=protocols\16\facebook.png" "facebook16.png" ++ File "/oname=protocols\22\facebook.png" "facebook22.png" ++ File "/oname=protocols\48\facebook.png" "facebook48.png" ++ ++ SetOutPath "$PidginDir\ca-certs" ++ File "login.facebook.com.pem" ++ ++ SetOverwrite try ++ copy: ++ ClearErrors ++ Delete "$PidginDir\plugins\libfacebook.dll" ++ IfErrors dllbusy ++ SetOutPath "$PidginDir\plugins" ++ File "libfacebook.dll" ++ Goto after_copy ++ dllbusy: ++ MessageBox MB_RETRYCANCEL "libfacebook.dll is busy. Please close Pidgin (including tray icon) and try again" IDCANCEL cancel ++ Goto copy ++ cancel: ++ Abort "Installation of pidgin-facebookchat aborted" ++ after_copy: ++ ++ SetOutPath "$PidginDir" ++ File "libjson-glib-1.0.dll" ++ ++SectionEnd ++ ++Function GetPidginInstPath ++ Push $0 ++ ReadRegStr $0 HKLM "Software\pidgin" "" ++ IfFileExists "$0\pidgin.exe" cont ++ ReadRegStr $0 HKCU "Software\pidgin" "" ++ IfFileExists "$0\pidgin.exe" cont ++ MessageBox MB_OK|MB_ICONINFORMATION "Failed to find Pidgin installation." ++ Abort "Failed to find Pidgin installation. Please install Pidgin first." ++ cont: ++ StrCpy $PidginDir $0 ++FunctionEnd ++ ++Function RunPidgin ++ ExecShell "" "$PidginDir\pidgin.exe" ++FunctionEnd ++ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/facebook.svg pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.svg +--- pidgin-2.7.7/libpurple/protocols/facebook/facebook.svg 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.svg 2011-03-27 09:15:30.744552999 -0600 +@@ -0,0 +1,108 @@ ++<?xml version="1.0" encoding="UTF-8" standalone="no"?> ++<!-- Created with Inkscape (http://www.inkscape.org/) --> ++ ++<svg ++ xmlns:svg="http://www.w3.org/2000/svg" ++ xmlns="http://www.w3.org/2000/svg" ++ xmlns:xlink="http://www.w3.org/1999/xlink" ++ version="1.1" ++ width="48" ++ height="48" ++ id="svg2"> ++ <defs ++ id="defs6"> ++ <linearGradient ++ id="linearGradient3650"> ++ <stop ++ id="stop3652" ++ style="stop-color:#6a6a6a;stop-opacity:1" ++ offset="0" /> ++ <stop ++ id="stop3658" ++ style="stop-color:#779cf4;stop-opacity:1" ++ offset="0.88979578" /> ++ <stop ++ id="stop3654" ++ style="stop-color:#545eff;stop-opacity:1" ++ offset="1" /> ++ </linearGradient> ++ <linearGradient ++ id="linearGradient3634"> ++ <stop ++ id="stop3636" ++ style="stop-color:#4072e6;stop-opacity:1" ++ offset="0" /> ++ <stop ++ id="stop3638" ++ style="stop-color:#334e89;stop-opacity:1" ++ offset="1" /> ++ </linearGradient> ++ <radialGradient ++ cx="13.327877" ++ cy="6.1316919" ++ r="17.96468" ++ fx="13.327877" ++ fy="6.1316919" ++ id="radialGradient3640" ++ xlink:href="#linearGradient3634" ++ gradientUnits="userSpaceOnUse" ++ gradientTransform="matrix(1.8070156,1.4532131,-1.0519575,1.3080694,-2.6667852,-23.653083)" /> ++ <radialGradient ++ cx="24.023664" ++ cy="21.943848" ++ r="19.95002" ++ fx="24.023664" ++ fy="21.943848" ++ id="radialGradient3656" ++ xlink:href="#linearGradient3650" ++ gradientUnits="userSpaceOnUse" ++ gradientTransform="matrix(1.3070175,1.2268147e-7,-1.2100323e-7,1.289138,-7.3756861,-6.3448031)" /> ++ <filter ++ x="-0.070605978" ++ y="-0.54366601" ++ width="1.1412119" ++ height="2.087332" ++ color-interpolation-filters="sRGB" ++ id="filter3678"> ++ <feGaussianBlur ++ id="feGaussianBlur3680" ++ stdDeviation="1.2013434" /> ++ </filter> ++ </defs> ++ <rect ++ width="40.835415" ++ height="5.3033009" ++ ry="1.7350562" ++ x="3.5355339" ++ y="38.277283" ++ transform="matrix(1,0,0,0.94255278,0.61871843,3.785993)" ++ id="rect3660" ++ style="opacity:0.53252037;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3678)" /> ++ <g ++ id="g3618"> ++ <rect ++ width="38.910927" ++ height="38.947548" ++ ry="3.0047133" ++ x="4.5682015" ++ y="2.4700744" ++ id="rect2818" ++ style="fill:url(#radialGradient3656);fill-opacity:1;fill-rule:nonzero;stroke:#24469d;stroke-width:0.98911101;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> ++ <rect ++ width="35.929359" ++ height="36.256153" ++ ry="1.7350562" ++ x="6.1269403" ++ y="3.735842" ++ id="rect3592" ++ style="fill:url(#radialGradient3640);fill-opacity:1;fill-rule:nonzero;stroke:none" /> ++ <path ++ d="m 7,31.978036 -7.938e-4,5.031486 C 6.9992062,38.10436 7.9191305,39 8.9741214,39 L 38.95642,39 c 1.054991,0 1.971131,-0.904795 1.971131,-1.999633 l 0.0092,-4.994865 L 7,31.978036 z" ++ id="rect3598" ++ style="opacity:0.46341463;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /> ++ </g> ++ <path ++ d="m 31.5,6.96875 c -3.441955,0 -6.239955,2.6781558 -6.46875,6.0625 L 25,16 l -3,0 0,5 3,0 0,18 7,0 0,-18 5.5,0 0,-5 -5.46875,0 0,-1.875 c 0,-1.178295 0.915762,-2.125 2.09375,-2.125 1.828085,-0.153263 3.458457,-0.138113 4.885084,0.321843 l 0,-4.9999996 C 36.391262,6.8079395 33.910076,6.9137911 31.5,6.96875 z" ++ id="rect3609" ++ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /> ++</svg> +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.c 2011-03-27 09:15:30.752552999 -0600 +@@ -0,0 +1,719 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "libfacebook.h" ++#include "fb_connection.h" ++#include "fb_blist.h" ++#include "fb_util.h" ++#include "fb_friendlist.h" ++#include "blist.h" ++ ++static void set_buddies_offline(PurpleBuddy *buddy, ++ GHashTable *online_buddies_list) ++{ ++ if (PURPLE_BUDDY_IS_ONLINE(buddy) && ++ g_hash_table_lookup(online_buddies_list, buddy->name) == NULL) ++ { ++ purple_prpl_got_user_status(buddy->account, buddy->name, ++ purple_primitive_get_id_from_type( ++ PURPLE_STATUS_OFFLINE), ++ NULL); ++ } ++} ++ ++static void buddy_icon_cb(FacebookAccount *fba, const gchar *data, gsize data_len, ++ gpointer user_data) ++{ ++ gchar *buddyname; ++ PurpleBuddy *buddy; ++ FacebookBuddy *fbuddy; ++ gpointer buddy_icon_data; ++ ++ buddyname = user_data; ++ ++ purple_debug_info("facebook", ++ "buddy icon for buddy %s %" G_GSIZE_FORMAT "\n", ++ buddyname, data_len); ++ ++ buddy = purple_find_buddy(fba->account, buddyname); ++ g_free(buddyname); ++ ++ g_return_if_fail(buddy != NULL); ++ ++ fbuddy = buddy->proto_data; ++ ++ g_return_if_fail(fbuddy != NULL); ++ ++ buddy_icon_data = g_memdup(data, data_len); ++ ++ purple_buddy_icons_set_for_user(fba->account, buddy->name, ++ buddy_icon_data, data_len, fbuddy->thumb_url); ++} ++ ++/** ++ * Find buddy names ++ */ ++static GList *get_buddies(FacebookAccount *fba, const gchar *uid, ++ const gchar *name, JsonArray *friend_list_ids) ++{ ++ GList *buddies; ++ GList *cur; ++ ++ buddies = fb_get_buddies_friend_list(fba, uid, friend_list_ids); ++ ++ // Initialize proto data for each buddy. ++ for (cur = buddies; cur != NULL; cur = cur->next) ++ { ++ PurpleBuddy *buddy; ++ ++ buddy = (PurpleBuddy *) cur->data; ++ ++ /* Set the FacebookBuddy structure */ ++ if (buddy->proto_data == NULL) ++ { ++ FacebookBuddy *fbuddy; ++ gchar *buddy_icon_url; ++ ++ fbuddy = g_new0(FacebookBuddy, 1); ++ fbuddy->buddy = buddy; ++ fbuddy->fba = fba; ++ fbuddy->uid = atoll(uid); ++ fbuddy->name = g_strdup(name); ++ ++ // load the old buddy icon url from the icon 'checksum' ++ buddy_icon_url = (char *) ++ purple_buddy_icons_get_checksum_for_user(buddy); ++ if (buddy_icon_url != NULL) ++ fbuddy->thumb_url = g_strdup(buddy_icon_url); ++ ++ buddy->proto_data = fbuddy; ++ } ++ } ++ ++ return buddies; ++} ++ ++static void process_buddy_icon(FacebookAccount *fba, FacebookBuddy *fbuddy, ++ const gchar *buddy_icon_url) ++{ ++ PurpleBuddy *buddy; ++ gchar *icon_host; ++ gchar *icon_path, *real_path; ++ gchar *search_tmp; ++ ++ buddy = fbuddy->buddy; ++ ++ /* Seperate the URL into pieces */ ++ purple_url_parse(buddy_icon_url, &icon_host, NULL, &icon_path, NULL, NULL); ++ ++ if (icon_path != NULL && icon_path[0] != '/') ++ { ++ /* Slap a / at the front of that badboy */ ++ real_path = g_strconcat("/", icon_path, NULL); ++ g_free(icon_path); ++ icon_path = real_path; ++ } ++ ++ if (fbuddy->thumb_url == NULL || ++ !g_str_equal(fbuddy->thumb_url, icon_path)) ++ { ++ g_free(fbuddy->thumb_url); ++ if (g_str_equal(icon_path, "/pics/q_silhouette.gif")) ++ { ++ fbuddy->thumb_url = NULL; ++ /* User has no icon */ ++ purple_buddy_icons_set_for_user(fba->account, ++ purple_buddy_get_name(buddy), NULL, 0, NULL); ++ } ++ else ++ { ++ fbuddy->thumb_url = g_strdup(icon_path); ++ ++ /* small icon at /profile6/1845/74/q800753867_2878.jpg */ ++ /* bigger icon at /profile6/1845/74/n800753867_2878.jpg */ ++ search_tmp = strstr(icon_path, "/q"); ++ if (search_tmp) ++ *(search_tmp + 1) = 'n'; ++ else ++ { ++ search_tmp = strstr(icon_path, "_q.jpg"); ++ if (search_tmp) ++ *(search_tmp + 1) = 'n'; ++ } ++ purple_debug_info("facebook", "buddy %s has a new buddy icon at http://%s%s\n", buddy->name, icon_host, icon_path); ++ /* Fetch their icon */ ++ fb_post_or_get(fba, FB_METHOD_GET, icon_host, ++ icon_path, NULL, ++ buddy_icon_cb, g_strdup(purple_buddy_get_name(buddy)), FALSE); ++ } ++ } ++ g_free(icon_host); ++ g_free(icon_path); ++} ++ ++static void process_buddies(FacebookAccount *fba, GHashTable *online_buddies_list, ++ JsonObject *nowAvailableList, gchar *uid, JsonObject *userInfo) ++{ ++ const gchar *name; ++ gboolean idle; ++ GList *buddies, *cur; ++ gboolean current_buddy_online; ++ ++ JsonArray *friend_list_ids; ++ ++ friend_list_ids = NULL; ++ name = json_node_get_string(json_object_get_member(userInfo, "name")); ++ ++ /* look for "uid":{"i":_____} */ ++ if (json_object_has_member(nowAvailableList, uid)) ++ { ++ JsonObject *userBlistInfo; ++ userBlistInfo = json_node_get_object( ++ json_object_get_member(nowAvailableList, uid)); ++ idle = json_node_get_boolean( ++ json_object_get_member(userBlistInfo, "i")); ++ if (json_object_has_member(userBlistInfo, "fl")) { ++ friend_list_ids = json_node_get_array( ++ json_object_get_member(userBlistInfo, "fl")); ++ } ++ ++ current_buddy_online = TRUE; ++ } else { ++ /* if we're here, the buddy's info has been sent, ++ * but they're not actually online */ ++ current_buddy_online = FALSE; ++ idle = FALSE; ++ } ++ ++ /* is this us? */ ++ if (atoll(uid) == fba->uid) ++ { ++ purple_connection_set_display_name(fba->pc, name); ++ ++ /* check that we don't want to show ourselves */ ++ current_buddy_online = !purple_account_get_bool( ++ fba->account, "facebook_hide_self", TRUE); ++ } ++ ++ buddies = get_buddies(fba, uid, name, friend_list_ids); ++ for (cur = buddies; cur != NULL; cur = cur->next) ++ { ++ PurpleBuddy *buddy; ++ FacebookBuddy *fbuddy; ++ ++ buddy = (PurpleBuddy *)cur->data; ++ fbuddy = buddy->proto_data; ++ ++ process_buddy_icon(fba, fbuddy, json_node_get_string( ++ json_object_get_member(userInfo, "thumbSrc"))); ++ ++ purple_presence_set_idle(purple_buddy_get_presence(buddy), ++ idle, 0); ++ ++ if (current_buddy_online) ++ { ++ /* Add buddy to the list of online buddies */ ++ g_hash_table_insert(online_buddies_list, buddy->name, buddy); ++ ++ // Set buddy as online in buddy list. We check for several ++ // conditions before doing this, because if we set it always ++ // Pidgin has a bug where the logs go nuts with "x is online". ++ if (!PURPLE_BUDDY_IS_ONLINE(buddy) || ++ idle != purple_presence_is_idle( ++ purple_buddy_get_presence(buddy))) ++ { ++ purple_prpl_got_user_status(fba->account, buddy->name, ++ purple_primitive_get_id_from_type( ++ idle ? PURPLE_STATUS_AWAY : ++ PURPLE_STATUS_AVAILABLE), NULL); ++ } ++ } ++ } ++ ++ /* update the blist if we have no previous alias */ ++ fb_blist_set_alias(fba, uid, name); ++} ++ ++static void process_notifications(FacebookAccount *fba, ++ JsonObject *notifications) ++{ ++ if (notifications != NULL && ++ purple_account_get_check_mail(fba->account)) ++ { ++ JsonNode *inboxCount_node = json_object_get_member( ++ notifications, "inboxCount"); ++ if (inboxCount_node) { ++ gint inbox_count = json_node_get_int(inboxCount_node); ++ if (inbox_count && ++ inbox_count != fba->last_inbox_count) { ++ fba->last_inbox_count = inbox_count; ++ gchar *url = g_strdup("http://www.facebook.com/inbox/"); ++ purple_notify_emails( ++ fba->pc, inbox_count, ++ FALSE, NULL, NULL, ++ (const char**) &(fba->account->username), ++ (const char**) &(url), NULL, NULL); ++ g_free(url); ++ } ++ } ++ } ++} ++ ++static void got_status_stream_cb(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer userdata) ++{ ++ gchar *error = NULL; ++ JsonParser *parser; ++ JsonObject *objnode; ++ gint new_latest; ++ const gchar *html; ++ gchar **messages; ++ gchar *message; ++ gint i; ++ gchar *uid_string; ++ gchar *message_string; ++ gsize uid_length; ++ FacebookBuddy *fbuddy; ++ PurpleBuddy *buddy; ++ GHashTable *processed_buddies; ++ ++ purple_debug_info("facebook", "parsing status message stream\n"); ++ ++ if (fba == NULL) ++ return; ++ ++ parser = fb_get_parser(data, data_len); ++ if (parser == NULL) { ++ purple_debug_info("facebook", "could not parse\n"); ++ return; ++ } ++ ++ //purple_debug_misc("facebook", "status message stream\n%s\n", data); ++ ++ objnode = fb_get_json_object(parser, &error); ++ ++ if (error || !json_object_has_member(objnode, "payload")) { ++ purple_debug_info("facebook", "no payload\n"); ++ json_parser_free(parser); ++ return; ++ } ++ ++ objnode = json_node_get_object(json_object_get_member( ++ objnode, "payload")); ++ ++ html = json_node_get_string(json_object_get_member( ++ objnode, "html")); ++ //purple_debug_misc("facebook", "html data\n%s\n", html); ++ ++ processed_buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); ++ ++ messages = g_strsplit(html, "/h6>", -1); ++ for(i = 0; messages[i]; i++) ++ { ++ message = messages[i]; ++ uid_length = 0; ++ ++ //find uid: ++ //start with aid_ ... " ++ uid_string = strstr(message, "aid_"); ++ if (!uid_string) ++ continue; ++ uid_string += 4; ++ while (uid_string[uid_length] >= '0' && ++ uid_string[uid_length] <= '9') ++ { ++ uid_length++; ++ } ++ uid_string = g_strndup(uid_string, uid_length); ++ purple_debug_info("facebook", "uid: %s\n", uid_string); ++ ++ //find message: ++ // last index of ++ // /a> ... < ++ message_string = g_strrstr(message, "/a>"); ++ if (!message_string) ++ { ++ g_free(uid_string); ++ continue; ++ } ++ message_string = strchr(message_string, '>'); ++ if (!message_string) ++ { ++ g_free(uid_string); ++ continue; ++ } ++ message_string += 1; ++ message_string = g_strndup(message_string, g_strrstr(message_string, "<")-message_string); ++ purple_debug_info("facebook", "message: %s\n", message_string); ++ ++ if (g_hash_table_lookup(processed_buddies, uid_string)) ++ { ++ // Already processed a status message for this buddy ++ g_free(uid_string); ++ g_free(message_string); ++ continue; ++ } ++ g_hash_table_insert(processed_buddies, uid_string, uid_string); ++ ++ buddy = purple_find_buddy(fba->account, uid_string); ++ if (buddy && buddy->proto_data) ++ { ++ fbuddy = buddy->proto_data; ++ g_free(fbuddy->status); ++ ++ fbuddy->status = purple_strreplace(message_string, "♥", "♥"); ++ g_free(message_string); message_string = fbuddy->status; ++ fbuddy->status = purple_markup_strip_html(message_string); ++ ++ purple_prpl_got_user_status(fba->account, buddy->name, ++ purple_status_get_id(purple_presence_get_active_status( ++ purple_buddy_get_presence(buddy))), "message", fbuddy->status, NULL); ++ } ++ ++ g_free(message_string); ++ } ++ g_strfreev(messages); ++ g_hash_table_destroy(processed_buddies); ++ ++ new_latest = json_node_get_int(json_object_get_member( ++ objnode, "newestStoryTime")); ++ if (!new_latest) ++ { ++ purple_debug_info("facebook", "no newestStoryTime\n"); ++ } else { ++ fba->last_status_timestamp = new_latest; ++ } ++ ++ json_parser_free(parser); ++} ++ ++static void got_buddy_list_cb(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer userdata) ++{ ++ GSList *buddies_list; ++ GHashTable *online_buddies_list = g_hash_table_new( ++ g_str_hash, g_str_equal); ++ gchar *uid; ++ ++ purple_debug_info("facebook", "parsing buddy list\n"); ++ ++ if (fba == NULL) ++ return; ++ ++ JsonParser *parser = fb_get_parser(data, data_len); ++ if (parser == NULL) { ++ if (fba->bad_buddy_list_count++ == 3) ++ { ++ purple_connection_error_reason(fba->pc, ++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR, ++ _("Could not retrieve buddy list")); ++ } ++ return; ++ } ++ ++ purple_debug_misc("facebook", "buddy list\n%s\n", data); ++ ++ gchar *error = NULL; ++ JsonObject *objnode = fb_get_json_object(parser, &error); ++ if (error) { ++ purple_debug_info("facebook", "eion test\n"); ++ if (json_node_get_int(json_object_get_member(objnode, "error")) == 1356007) ++ { ++ //Chat is down for maintenence ++ purple_connection_error_reason(fba->pc, ++ PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, ++ error); ++ fba->pc->wants_to_die = TRUE; ++ } ++ if (fba->bad_buddy_list_count++ == 3) ++ { ++ purple_connection_error_reason( ++ fba->pc, ++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR, ++ error); ++ } ++ g_free(error); ++ json_parser_free(parser); ++ ++ return; ++ } ++ ++ /* look for "userInfos":{ ... }, */ ++ if (!json_object_has_member(objnode, "payload")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ objnode = json_node_get_object(json_object_get_member( ++ objnode, "payload")); ++ if (!json_object_has_member(objnode, "buddy_list")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ JsonObject *buddy_list = json_node_get_object(json_object_get_member( ++ objnode, "buddy_list")); ++ if (!json_object_has_member(buddy_list, "userInfos")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ ++ //Reset invalid buddy list counter ++ fba->bad_buddy_list_count = 0; ++ ++ if (purple_account_get_bool(fba->account, "facebook_use_groups", TRUE)) ++ { ++ //Only process if we have the setting ++ fb_process_friend_lists(fba, buddy_list); ++ } ++ ++ // Iterate through the list of buddy infos sent to us. ++ JsonObject *userInfos; ++ JsonObject *nowAvailableList; ++ userInfos = json_node_get_object(json_object_get_member( ++ buddy_list, "userInfos")); ++ nowAvailableList = json_node_get_object(json_object_get_member( ++ buddy_list, "nowAvailableList")); ++ GList *userIds; ++ userIds = json_object_get_members(userInfos); ++ GList *currentUserNode; ++ for( currentUserNode = userIds; ++ currentUserNode; ++ currentUserNode = g_list_next(currentUserNode)) ++ { ++ uid = currentUserNode->data; ++ ++ JsonObject *userInfo; ++ userInfo = json_node_get_object(json_object_get_member( ++ userInfos, uid)); ++ // Process the user, which generally consists of updating ++ // state info such as name, idle item, status message,etc. ++ process_buddies(fba, online_buddies_list, nowAvailableList, ++ uid, userInfo); ++ } ++ g_list_free(userIds); ++ ++ // Set users offline. We do this in a seperate function because FB ++ // only sends us a list of users who are online. We find the users ++ // that are not in the union of of buddy list users + online, and ++ // mark them as offline. ++ buddies_list = purple_find_buddies(fba->account, NULL); ++ if (buddies_list != NULL) ++ { ++ g_slist_foreach( ++ buddies_list, ++ (GFunc)set_buddies_offline, online_buddies_list); ++ g_slist_free(buddies_list); ++ } ++ g_hash_table_destroy(online_buddies_list); ++ ++ // The buddy list also contains notifications data. Process and ++ // display is appropriate. ++ process_notifications(fba, json_node_get_object( ++ json_object_get_member(objnode, "notifications"))); ++ ++ json_parser_free(parser); ++} ++ ++gboolean fb_get_buddy_list(gpointer data) ++{ ++ FacebookAccount *fba; ++ gchar *postdata; ++ ++ fba = data; ++ ++ postdata = g_strdup_printf( ++ "user=%" G_GINT64_FORMAT "&popped_out=true&force_render=true&buddy_list=1&__a=1&post_form_id_source=AsyncRequest&post_form_id=%s&fb_dtsg=%s¬ifications=1", ++ fba->uid, fba->post_form_id?fba->post_form_id:"(null)", fba->dtsg?fba->dtsg:"(null)"); ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/chat/buddy_list.php", ++ postdata, got_buddy_list_cb, NULL, FALSE); ++ g_free(postdata); ++ ++ postdata = g_strdup_printf("/ajax/intent.php?filter=app_2915120374&request_type=1&__a=1&newest=%d&ignore_self=true", ++ fba->last_status_timestamp); ++ fb_post_or_get(fba, FB_METHOD_GET, NULL, postdata, ++ NULL, got_status_stream_cb, NULL, FALSE); ++ g_free(postdata); ++ ++ return TRUE; ++} ++ ++static void got_full_buddy_list(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer userdata) ++{ ++ int i; ++ PurpleGroup *fb_group; ++ JsonParser *parser; ++ gchar *error = NULL; ++ JsonObject *objnode, *node; ++ JsonArray *entries; ++ ++ purple_debug_info("facebook", "parsing full buddy list\n"); ++ ++ if (fba == NULL) ++ return; ++ ++ parser = fb_get_parser(data, data_len); ++ if (parser == NULL) ++ return; ++ ++ purple_debug_misc("facebook", "full buddy list\n%s\n", data); ++ ++ objnode = fb_get_json_object(parser, &error); ++ if (!json_object_has_member(objnode, "payload")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ objnode = json_node_get_object(json_object_get_member( ++ objnode, "payload")); ++ if (!json_object_has_member(objnode, "entries")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ entries = json_node_get_array(json_object_get_member( ++ objnode, "entries")); ++ ++ fb_group = purple_find_group(DEFAULT_GROUP_NAME); ++ if (fb_group == NULL) ++ { ++ fb_group = purple_group_new(DEFAULT_GROUP_NAME); ++ purple_blist_add_group(fb_group, NULL); ++ } ++ for(i = 0; i < json_array_get_length(entries); i++) ++ { ++ node = json_node_get_object(json_array_get_element(entries, i)); ++ const gchar *type = json_node_get_string(json_object_get_member(node, "ty")); ++ if (type[0] != 'u' && type[0] != 'g') ++ continue; ++ const gchar *uid = json_node_get_string(json_object_get_member(node, "i")); ++ const gchar *name = json_node_get_string(json_object_get_member(node, "t")); ++ ++ if (type[0] == 'g') ++ { ++ PurpleChat *chat = purple_blist_find_chat(fba->account, uid); ++ purple_blist_alias_chat(chat, name); ++ continue; ++ } ++ ++ if (purple_find_buddy(fba->account, uid)) ++ continue; ++ ++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, name); ++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL); ++ FacebookBuddy *fbuddy = g_new0(FacebookBuddy, 1); ++ fbuddy->buddy = buddy; ++ fbuddy->fba = fba; ++ fbuddy->uid = atoll(uid); ++ fbuddy->name = g_strdup(name); ++ buddy->proto_data = fbuddy; ++ ++ const gchar *thumb_url = json_node_get_string(json_object_get_member(node, "it")); ++ process_buddy_icon(fba, fbuddy, thumb_url); ++ } ++ ++ json_parser_free(parser); ++} ++ ++void fb_get_full_buddy_list(FacebookAccount *fba) ++{ ++ gchar *url; ++ ++ url = g_strdup_printf("/ajax/typeahead_search.php?u=%" G_GINT64_FORMAT "&__a=1", fba->uid); ++ fb_post_or_get(fba, FB_METHOD_GET, NULL, url, NULL, got_full_buddy_list, NULL, FALSE); ++ g_free(url); ++} ++ ++void fb_blist_poke_buddy(PurpleBlistNode *node, gpointer data) ++{ ++ PurpleBuddy *buddy; ++ FacebookBuddy *fbuddy; ++ FacebookAccount *fba; ++ gchar *postdata; ++ ++ if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) ++ return; ++ buddy = (PurpleBuddy *) node; ++ if (!buddy) ++ return; ++ fbuddy = buddy->proto_data; ++ if (!fbuddy) ++ return; ++ fba = fbuddy->fba; ++ if (!fba) ++ return; ++ ++ postdata = g_strdup_printf("uid=%" G_GINT64_FORMAT "&pokeback=0&post_form_id=%s", fbuddy->uid, fba->post_form_id); ++ ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/poke.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_free(postdata); ++} ++ ++void fb_blist_set_alias(FacebookAccount *fba, const gchar *id, ++ const gchar *name) ++{ ++ const char *current_alias; ++ PurpleBuddy *buddy; ++ ++ buddy = purple_find_buddy(fba->account, id); ++ if (!buddy) { ++ return; ++ } ++ ++ /* Set an alias if no user-defined alias is set yet. This provides ++ * a basic name alias for each user which is more useful than a ++ * number. A small corner case bug here- aliases will not change ++ * in accordance with people changing their names on Facebook. ++ */ ++ current_alias = purple_buddy_get_alias_only(buddy); ++ if (!current_alias) { ++ purple_debug_info("facebook", "aliasing %s to %s\n", id, name); ++ purple_blist_alias_buddy(buddy, name); ++ } ++ ++ /* In case user removes an alias, we have the server as fallback */ ++ serv_got_alias(fba->pc, id, name); ++} ++ ++void fb_blist_init(FacebookAccount *fba) ++{ ++ fb_friendlist_init(fba); ++ ++ fb_get_buddy_list(fba); ++ ++ /* periodically check for updates to your buddy list */ ++ fba->buddy_list_timer = purple_timeout_add_seconds(60, ++ fb_get_buddy_list, fba); ++ ++ fb_get_full_buddy_list(fba); ++} ++ ++void fb_blist_destroy(FacebookAccount *fba) ++{ ++ if (fba->buddy_list_timer) { ++ purple_timeout_remove(fba->buddy_list_timer); ++ } ++ ++ fb_friendlist_destroy(fba); ++} +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.h 2011-03-27 09:15:30.736552999 -0600 +@@ -0,0 +1,37 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_BLIST_H ++#define FACEBOOK_BLIST_H ++ ++#include "libfacebook.h" ++ ++gboolean fb_get_buddy_list(gpointer data); ++void fb_blist_poke_buddy(PurpleBlistNode *node, gpointer data); ++ ++void fb_blist_set_alias(FacebookAccount *fba, const char *id, ++ const char *name); ++ ++void fb_blist_init(FacebookAccount *fba); ++void fb_blist_destroy(FacebookAccount *fba); ++ ++void fb_get_full_buddy_list(FacebookAccount *fba); ++ ++#endif /* FACEBOOK_BLIST_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.c 2011-03-27 09:15:30.888552999 -0600 +@@ -0,0 +1,273 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_chat.h" ++#include "fb_blist.h" ++#include "fb_util.h" ++#include "fb_connection.h" ++#include "fb_friendlist.h" ++#include "fb_messages.h" ++#include "fb_conversation.h" ++ ++#include "conversation.h" ++ ++void ++fb_got_facepile(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data) ++{ ++ gchar *group = user_data; ++ JsonParser *parser; ++ JsonObject *object, *payload, *user_obj; ++ JsonArray *facepile; ++ PurpleConversation *conv; ++ PurpleConvChat *chat; ++ gchar *uid; ++ guint i; ++ PurpleGroup *pgroup; ++ ++ purple_debug_info("facebook", "got facepile %s\n", data?data:"(null)"); ++ ++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account); ++ chat = PURPLE_CONV_CHAT(conv); ++ ++ parser = fb_get_parser(data, data_len); ++ ++ if (!parser) ++ { ++ purple_debug_warning("facebook", ++ "could not fetch facepile for group %s\n", group); ++ g_free(group); ++ return; ++ } ++ ++ object = fb_get_json_object(parser, NULL); ++ payload = json_node_get_object( ++ json_object_get_member(object, "payload")); ++ facepile = json_node_get_array( ++ json_object_get_member(payload, "facepile_click_info")); ++ ++ pgroup = purple_find_group(DEFAULT_GROUP_NAME); ++ if (!pgroup) ++ { ++ pgroup = purple_group_new(DEFAULT_GROUP_NAME); ++ purple_blist_add_group(pgroup, NULL); ++ } ++ ++ purple_conv_chat_clear_users(chat); ++ uid = g_strdup_printf("%" G_GINT64_FORMAT, fba->uid); ++ purple_conv_chat_add_user(chat, uid, NULL, PURPLE_CBFLAGS_NONE, FALSE); ++ if (!purple_find_buddy(fba->account, uid)) ++ { ++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, "You"); ++ purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); ++ purple_blist_add_buddy(buddy, NULL, pgroup, NULL); ++ } ++ g_free(uid); ++ ++ for (i = 0; i < json_array_get_length(facepile); i++) ++ { ++ user_obj = json_node_get_object( ++ json_array_get_element(facepile, i)); ++ uid = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int(json_object_get_member(user_obj, "uid"))); ++ ++ purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv), uid, NULL, PURPLE_CBFLAGS_NONE, FALSE); ++ ++ if (!purple_find_buddy(fba->account, uid)) ++ { ++ const char *alias = json_node_get_string(json_object_get_member(user_obj, "name")); ++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, alias); ++ purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); ++ purple_blist_add_buddy(buddy, NULL, pgroup, NULL); ++ } ++ ++ g_free(uid); ++ } ++ ++ g_free(group); ++} ++ ++PurpleConversation * ++fb_find_chat(FacebookAccount *fba, const gchar *group) ++{ ++ PurpleConversation *conv; ++ gchar *postdata; ++ ++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account); ++ ++ if (conv == NULL) ++ { ++ conv = serv_got_joined_chat(fba->pc, atoi(group), group); ++ ++ postdata = g_strdup_printf("gid=%s&post_form_id=%s&fb_dtsg=%s&lsd=", group, ++ fba->post_form_id, fba->dtsg); ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/groups/chat/update_facepiles.php?__a=1", ++ postdata, fb_got_facepile, g_strdup(group), FALSE); ++ g_free(postdata); ++ } ++ ++ return conv; ++} ++ ++void ++fb_got_groups(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data) ++{ ++ // look for /home.php?sk=group_ ... ++ gchar **splits; ++ gint i; ++ PurpleGroup *group; ++ ++ splits = g_strsplit(data, "<a href=\\\"\\/home.php?sk=group_", 0); ++ ++ if (!splits || !splits[0]) ++ { ++ g_strfreev(splits); ++ return; ++ } ++ ++ group = purple_find_group(DEFAULT_GROUP_NAME); ++ if (!group) ++ { ++ group = purple_group_new(DEFAULT_GROUP_NAME); ++ purple_blist_add_group(group, NULL); ++ } ++ ++ for(i = 1; splits[i]; i++) ++ { ++ gchar *eos; ++ eos = strchr(splits[i], '\\'); ++ if (eos != NULL) ++ { ++ *eos = '\0'; ++ purple_debug_info("facebook", "searching for %s\n", splits[i]); ++ if (!purple_blist_find_chat(fba->account, splits[i])) ++ { ++ gchar *alias = NULL; ++ if (eos[1] == '"' && eos[2] == '>') ++ { ++ purple_debug_info("facebook", "searching for alias\n"); ++ gchar *eoa = strchr(&eos[3], '<'); ++ if (eoa) ++ { ++ *eoa = '\0'; ++ alias = &eos[3]; ++ purple_debug_info("facebook", "found chat alias %s\n", alias); ++ } ++ } ++ ++ purple_debug_info("facebook", "adding chat %s to buddy list...\n", splits[i]); ++ // Add the group chat to the buddy list ++ GHashTable *components = fb_chat_info_defaults(fba->pc, splits[i]); ++ PurpleChat *chat = purple_chat_new(fba->account, alias, components); ++ purple_blist_add_chat(chat, group, NULL); ++ purple_debug_info("facebook", "done\n"); ++ } ++ } ++ } ++ ++ g_strfreev(splits); ++} ++ ++void ++fb_get_groups(FacebookAccount *fba) ++{ ++ fb_post_or_get(fba, FB_METHOD_GET, NULL, "/ajax/home/groups.php?__a=1", NULL, fb_got_groups, NULL, FALSE); ++} ++ ++int ++fb_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags) ++{ ++ PurpleConversation *conv; ++ const char *group; ++ ++ conv = purple_find_chat(pc, id); ++ if (conv != NULL) ++ { ++ group = purple_conversation_get_name(conv); ++ ++ return fb_send_im(pc, group, message, flags); ++ } ++ ++ return -1; ++} ++ ++void ++fb_chat_fake_leave(PurpleConnection *pc, int id) ++{ ++ PurpleConversation *conv; ++ const char *group; ++ ++ conv = purple_find_chat(pc, id); ++ if (conv != NULL) ++ { ++ group = purple_conversation_get_name(conv); ++ fb_conversation_closed(pc, group); ++ } ++} ++ ++GHashTable * ++fb_chat_info_defaults(PurpleConnection *pc, const char *chat_name) ++{ ++ GHashTable *table; ++ ++ table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); ++ ++ if (chat_name != NULL) ++ { ++ g_hash_table_insert(table, "group", g_strdup(chat_name)); ++ } ++ ++ return table; ++} ++ ++gchar * ++fb_get_chat_name(GHashTable *components) ++{ ++ gchar *group; ++ ++ group = (gchar *) g_hash_table_lookup(components, "group"); ++ ++ return g_strdup(group); ++} ++ ++GList * ++fb_chat_info(PurpleConnection *pc) ++{ ++ GList *m = NULL; ++ struct proto_chat_entry *pce; ++ ++ pce = g_new0(struct proto_chat_entry, 1); ++ pce->label = _("Group ID"); ++ pce->identifier = "group"; ++ pce->required = TRUE; ++ m = g_list_append(m, pce); ++ ++ return m; ++} ++ ++void ++fb_fake_join_chat(PurpleConnection *pc, GHashTable *components) ++{ ++ FacebookAccount *fba = pc->proto_data; ++ gchar *group = (gchar *) g_hash_table_lookup(components, "group"); ++ ++ if (group != NULL) ++ { ++ fb_find_chat(fba, group); ++ } ++} +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.h 2011-03-27 09:15:30.876553000 -0600 +@@ -0,0 +1,35 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_CHAT_H ++#define FACEBOOK_CHAT_H ++ ++#include "libfacebook.h" ++ ++PurpleConversation *fb_find_chat(FacebookAccount *fba, const gchar *group); ++void fb_get_groups(FacebookAccount *fba); ++int fb_chat_send(PurpleConnection *, int id, const char *message, PurpleMessageFlags flags); ++void fb_chat_fake_leave(PurpleConnection *, int id); ++GHashTable *fb_chat_info_defaults(PurpleConnection *, const char *chat_name); ++gchar *fb_get_chat_name(GHashTable *components); ++GList *fb_chat_info(PurpleConnection *); ++void fb_fake_join_chat(PurpleConnection *, GHashTable *components); ++ ++#endif /* FACEBOOK_CHAT_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.c 2011-03-27 09:15:30.744552999 -0600 +@@ -0,0 +1,685 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_connection.h" ++ ++#if !GLIB_CHECK_VERSION (2, 22, 0) ++#define g_hostname_is_ip_address(hostname) (g_ascii_isdigit(hostname[0]) && g_strstr_len(hostname, 4, ".")) ++#endif ++ ++static void fb_attempt_connection(FacebookConnection *); ++static void fb_next_connection(FacebookAccount *fba); ++ ++#ifdef HAVE_ZLIB ++#include <zlib.h> ++ ++static gchar *fb_gunzip(const guchar *gzip_data, ssize_t *len_ptr) ++{ ++ gsize gzip_data_len = *len_ptr; ++ z_stream zstr; ++ int gzip_err = 0; ++ gchar *data_buffer; ++ gulong gzip_len = G_MAXUINT16; ++ GString *output_string = NULL; ++ ++ data_buffer = g_new0(gchar, gzip_len); ++ ++ zstr.next_in = NULL; ++ zstr.avail_in = 0; ++ zstr.zalloc = Z_NULL; ++ zstr.zfree = Z_NULL; ++ zstr.opaque = 0; ++ gzip_err = inflateInit2(&zstr, MAX_WBITS+32); ++ if (gzip_err != Z_OK) ++ { ++ g_free(data_buffer); ++ purple_debug_error("facebook", "no built-in gzip support in zlib\n"); ++ return NULL; ++ } ++ ++ zstr.next_in = (Bytef *)gzip_data; ++ zstr.avail_in = gzip_data_len; ++ ++ zstr.next_out = (Bytef *)data_buffer; ++ zstr.avail_out = gzip_len; ++ ++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH); ++ ++ if (gzip_err == Z_DATA_ERROR) ++ { ++ inflateEnd(&zstr); ++ inflateInit2(&zstr, -MAX_WBITS); ++ if (gzip_err != Z_OK) ++ { ++ g_free(data_buffer); ++ purple_debug_error("facebook", "Cannot decode gzip header\n"); ++ return NULL; ++ } ++ zstr.next_in = (Bytef *)gzip_data; ++ zstr.avail_in = gzip_data_len; ++ zstr.next_out = (Bytef *)data_buffer; ++ zstr.avail_out = gzip_len; ++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH); ++ } ++ output_string = g_string_new(""); ++ while (gzip_err == Z_OK) ++ { ++ //append data to buffer ++ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); ++ //reset buffer pointer ++ zstr.next_out = (Bytef *)data_buffer; ++ zstr.avail_out = gzip_len; ++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH); ++ } ++ if (gzip_err == Z_STREAM_END) ++ { ++ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); ++ } else { ++ purple_debug_error("facebook", "gzip inflate error\n"); ++ } ++ inflateEnd(&zstr); ++ ++ g_free(data_buffer); ++ ++ if (len_ptr) ++ *len_ptr = output_string->len; ++ ++ return g_string_free(output_string, FALSE); ++} ++#else /* !HAVE_ZLIB */ ++#warning You really want to compile with -DHAVE_ZLIB ++#endif ++ ++void fb_connection_destroy(FacebookConnection *fbconn) ++{ ++ fbconn->fba->conns = g_slist_remove(fbconn->fba->conns, fbconn); ++ ++ if (fbconn->request != NULL) ++ g_string_free(fbconn->request, TRUE); ++ ++ g_free(fbconn->rx_buf); ++ ++ if (fbconn->connect_data != NULL) ++ purple_proxy_connect_cancel(fbconn->connect_data); ++ ++ if (fbconn->ssl_conn != NULL) ++ purple_ssl_close(fbconn->ssl_conn); ++ ++ if (fbconn->fd >= 0) { ++ close(fbconn->fd); ++ } ++ ++ if (fbconn->input_watcher > 0) ++ purple_input_remove(fbconn->input_watcher); ++ ++ g_free(fbconn->url); ++ g_free(fbconn->hostname); ++ g_free(fbconn); ++} ++ ++static void fb_update_cookies(FacebookAccount *fba, const gchar *headers) ++{ ++ const gchar *cookie_start; ++ const gchar *cookie_end; ++ gchar *cookie_name; ++ gchar *cookie_value; ++ int header_len; ++ ++ g_return_if_fail(headers != NULL); ++ ++ header_len = strlen(headers); ++ ++ /* look for the next "Set-Cookie: " */ ++ /* grab the data up until ';' */ ++ cookie_start = headers; ++ while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && ++ (cookie_start - headers) < header_len) ++ { ++ cookie_start += 14; ++ cookie_end = strchr(cookie_start, '='); ++ cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); ++ cookie_start = cookie_end + 1; ++ cookie_end = strchr(cookie_start, ';'); ++ cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); ++ cookie_start = cookie_end; ++ ++ g_hash_table_replace(fba->cookie_table, cookie_name, ++ cookie_value); ++ } ++} ++ ++static void fb_connection_process_data(FacebookConnection *fbconn) ++{ ++ ssize_t len; ++ gchar *tmp; ++ ++ len = fbconn->rx_len; ++ tmp = g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n"); ++ if (tmp == NULL) { ++ /* This is a corner case that occurs when the connection is ++ * prematurely closed either on the client or the server. ++ * This can either be no data at all or a partial set of ++ * headers. We pass along the data to be good, but don't ++ * do any fancy massaging. In all likelihood the result will ++ * be tossed by the connection callback func anyways ++ */ ++ tmp = g_strndup(fbconn->rx_buf, len); ++ } else { ++ tmp += 4; ++ len -= g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n") - ++ fbconn->rx_buf + 4; ++ tmp = g_memdup(tmp, len + 1); ++ tmp[len] = '\0'; ++ fbconn->rx_buf[fbconn->rx_len - len] = '\0'; ++ fb_update_cookies(fbconn->fba, fbconn->rx_buf); ++ ++#ifdef HAVE_ZLIB ++ if (strstr(fbconn->rx_buf, "Content-Encoding: gzip")) ++ { ++ /* we've received compressed gzip data, decompress */ ++ gchar *gunzipped; ++ gunzipped = fb_gunzip((const guchar *)tmp, &len); ++ g_free(tmp); ++ tmp = gunzipped; ++ } ++#endif ++ } ++ ++ g_free(fbconn->rx_buf); ++ fbconn->rx_buf = NULL; ++ ++ if (fbconn->callback != NULL) { ++ purple_debug_info("facebook", "executing callback for %s\n", fbconn->url); ++ fbconn->callback(fbconn->fba, tmp, len, fbconn->user_data); ++ } ++ ++ g_free(tmp); ++} ++ ++static void fb_fatal_connection_cb(FacebookConnection *fbconn) ++{ ++ PurpleConnection *pc = fbconn->fba->pc; ++ ++ purple_debug_error("facebook", "fatal connection error\n"); ++ ++ fb_connection_destroy(fbconn); ++ ++ /* We died. Do not pass Go. Do not collect $200 */ ++ /* In all seriousness, don't attempt to call the normal callback here. ++ * That may lead to the wrong error message being displayed */ ++ purple_connection_error_reason(pc, ++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR, ++ _("Server closed the connection.")); ++ ++} ++ ++static void fb_post_or_get_readdata_cb(gpointer data, gint source, ++ PurpleInputCondition cond) ++{ ++ FacebookConnection *fbconn; ++ FacebookAccount *fba; ++ gchar buf[4096]; ++ ssize_t len; ++ ++ fbconn = data; ++ fba = fbconn->fba; ++ ++ if (fbconn->method & FB_METHOD_SSL) { ++ len = purple_ssl_read(fbconn->ssl_conn, ++ buf, sizeof(buf) - 1); ++ } else { ++ len = recv(fbconn->fd, buf, sizeof(buf) - 1, 0); ++ } ++ ++ if (len < 0) ++ { ++ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { ++ /* Try again later */ ++ return; ++ } ++ ++ if (fbconn->method & FB_METHOD_SSL && fbconn->rx_len > 0) { ++ /* ++ * This is a slightly hacky workaround for a bug in either ++ * GNU TLS or in the SSL implementation on Facebook's web ++ * servers. The sequence of events is: ++ * 1. We attempt to read the first time and successfully read ++ * the server's response. ++ * 2. We attempt to read a second time and libpurple's call ++ * to gnutls_record_recv() returns the error ++ * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or ++ * "A TLS packet with unexpected length was received." ++ * ++ * Normally the server would have closed the connection ++ * cleanly and this second read() request would have returned ++ * 0. Or maybe it's normal for SSL connections to be severed ++ * in this manner? In any case, this differs from the behavior ++ * of the standard recv() system call. ++ */ ++ purple_debug_warning("facebook", ++ "ssl error, but data received. attempting to continue\n"); ++ } else { ++ /* TODO: Is this a regular occurrence? If so then maybe resend the request? */ ++ fb_fatal_connection_cb(fbconn); ++ return; ++ } ++ } ++ ++ if (len > 0) ++ { ++ buf[len] = '\0'; ++ ++ fbconn->rx_buf = g_realloc(fbconn->rx_buf, ++ fbconn->rx_len + len + 1); ++ memcpy(fbconn->rx_buf + fbconn->rx_len, buf, len + 1); ++ fbconn->rx_len += len; ++ ++ /* Wait for more data before processing */ ++ return; ++ } ++ ++ /* The server closed the connection, let's parse the data */ ++ fb_connection_process_data(fbconn); ++ ++ fb_connection_destroy(fbconn); ++ ++ fb_next_connection(fba); ++} ++ ++static void fb_post_or_get_ssl_readdata_cb (gpointer data, ++ PurpleSslConnection *ssl, PurpleInputCondition cond) ++{ ++ fb_post_or_get_readdata_cb(data, -1, cond); ++} ++ ++static void fb_post_or_get_connect_cb(gpointer data, gint source, ++ const gchar *error_message) ++{ ++ FacebookConnection *fbconn; ++ ssize_t len; ++ ++ fbconn = data; ++ fbconn->connect_data = NULL; ++ ++ if (error_message) ++ { ++ purple_debug_error("facebook", "post_or_get_connect failure to %s\n", fbconn->url); ++ purple_debug_error("facebook", "post_or_get_connect_cb %s\n", ++ error_message); ++ fb_fatal_connection_cb(fbconn); ++ return; ++ } ++ ++ fbconn->fd = source; ++ ++ /* TODO: Check the return value of write() */ ++ len = write(fbconn->fd, fbconn->request->str, ++ fbconn->request->len); ++ fbconn->input_watcher = purple_input_add(fbconn->fd, ++ PURPLE_INPUT_READ, ++ fb_post_or_get_readdata_cb, fbconn); ++} ++ ++static void fb_post_or_get_ssl_connect_cb(gpointer data, ++ PurpleSslConnection *ssl, PurpleInputCondition cond) ++{ ++ FacebookConnection *fbconn; ++ ssize_t len; ++ ++ fbconn = data; ++ ++ purple_debug_info("facebook", "post_or_get_ssl_connect_cb\n"); ++ ++ /* TODO: Check the return value of write() */ ++ len = purple_ssl_write(fbconn->ssl_conn, ++ fbconn->request->str, fbconn->request->len); ++ purple_ssl_input_add(fbconn->ssl_conn, ++ fb_post_or_get_ssl_readdata_cb, fbconn); ++} ++ ++static void fb_host_lookup_cb(GSList *hosts, gpointer data, ++ const char *error_message) ++{ ++ GSList *host_lookup_list; ++ struct sockaddr_in *addr; ++ gchar *hostname; ++ gchar *ip_address; ++ FacebookAccount *fba; ++ PurpleDnsQueryData *query; ++ ++ /* Extract variables */ ++ host_lookup_list = data; ++ ++ fba = host_lookup_list->data; ++ host_lookup_list = ++ g_slist_delete_link(host_lookup_list, host_lookup_list); ++ hostname = host_lookup_list->data; ++ host_lookup_list = ++ g_slist_delete_link(host_lookup_list, host_lookup_list); ++ query = host_lookup_list->data; ++ host_lookup_list = ++ g_slist_delete_link(host_lookup_list, host_lookup_list); ++ ++ /* The callback has executed, so we no longer need to keep track of ++ * the original query. This always needs to run when the cb is ++ * executed. */ ++ fba->dns_queries = g_slist_remove(fba->dns_queries, query); ++ ++ /* Any problems, capt'n? */ ++ if (error_message != NULL) ++ { ++ purple_debug_warning("facebook", ++ "Error doing host lookup: %s\n", error_message); ++ return; ++ } ++ ++ if (hosts == NULL) ++ { ++ purple_debug_warning("facebook", ++ "Could not resolve host name\n"); ++ return; ++ } ++ ++ /* Discard the length... */ ++ hosts = g_slist_delete_link(hosts, hosts); ++ /* Copy the address then free it... */ ++ addr = hosts->data; ++ ip_address = g_strdup(inet_ntoa(addr->sin_addr)); ++ g_free(addr); ++ hosts = g_slist_delete_link(hosts, hosts); ++ ++ /* ++ * DNS lookups can return a list of IP addresses, but we only cache ++ * the first one. So free the rest. ++ */ ++ while (hosts != NULL) ++ { ++ /* Discard the length... */ ++ hosts = g_slist_delete_link(hosts, hosts); ++ /* Free the address... */ ++ g_free(hosts->data); ++ hosts = g_slist_delete_link(hosts, hosts); ++ } ++ ++ g_hash_table_insert(fba->hostname_ip_cache, hostname, ip_address); ++} ++ ++static void fb_cookie_foreach_cb(gchar *cookie_name, ++ gchar *cookie_value, GString *str) ++{ ++ /* TODO: Need to escape name and value? */ ++ g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); ++} ++ ++/** ++ * Serialize the fba->cookie_table hash table to a string. ++ */ ++gchar *fb_cookies_to_string(FacebookAccount *fba) ++{ ++ GString *str; ++ ++ str = g_string_new(NULL); ++ ++ g_hash_table_foreach(fba->cookie_table, ++ (GHFunc)fb_cookie_foreach_cb, str); ++ ++ return g_string_free(str, FALSE); ++} ++ ++static void fb_ssl_connection_error(PurpleSslConnection *ssl, ++ PurpleSslErrorType errortype, gpointer data) ++{ ++ FacebookConnection *fbconn = data; ++ PurpleConnection *pc = fbconn->fba->pc; ++ ++ fbconn->ssl_conn = NULL; ++ fb_connection_destroy(fbconn); ++ purple_connection_ssl_error(pc, errortype); ++} ++ ++void fb_post_or_get(FacebookAccount *fba, FacebookMethod method, ++ const gchar *host, const gchar *url, const gchar *postdata, ++ FacebookProxyCallbackFunc callback_func, gpointer user_data, ++ gboolean keepalive) ++{ ++ GString *request; ++ gchar *cookies; ++ FacebookConnection *fbconn; ++ gchar *real_url; ++ gboolean is_proxy = FALSE; ++ const gchar *user_agent; ++ const gchar* const *languages; ++ gchar *language_names; ++ PurpleProxyInfo *proxy_info = NULL; ++ gchar *proxy_auth; ++ gchar *proxy_auth_base64; ++ ++ /* TODO: Fix keepalive and use it as much as possible */ ++ keepalive = FALSE; ++ ++ if (host == NULL) ++ host = "www.facebook.com"; ++ ++ if (fba && fba->account) ++ { ++ if (purple_account_get_bool(fba->account, "use-https", FALSE)) ++ method |= FB_METHOD_SSL; ++ } ++ ++ if (fba && fba->account && !(method & FB_METHOD_SSL)) ++ { ++ proxy_info = purple_proxy_get_setup(fba->account); ++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) ++ proxy_info = purple_global_proxy_get_info(); ++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) ++ { ++ is_proxy = TRUE; ++ } ++ } ++ if (is_proxy == TRUE) ++ { ++ real_url = g_strdup_printf("http://%s%s", host, url); ++ } else { ++ real_url = g_strdup(url); ++ } ++ ++ cookies = fb_cookies_to_string(fba); ++ user_agent = purple_account_get_string(fba->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)"); ++ ++ if (method & FB_METHOD_POST && !postdata) ++ postdata = ""; ++ ++ /* Build the request */ ++ request = g_string_new(NULL); ++ g_string_append_printf(request, "%s %s HTTP/1.0\r\n", ++ (method & FB_METHOD_POST) ? "POST" : "GET", ++ real_url); ++ if (is_proxy == FALSE) ++ g_string_append_printf(request, "Host: %s\r\n", host); ++ g_string_append_printf(request, "Connection: %s\r\n", ++ (keepalive ? "Keep-Alive" : "close")); ++ g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); ++ if (method & FB_METHOD_POST) { ++ g_string_append_printf(request, ++ "Content-Type: application/x-www-form-urlencoded\r\n"); ++ g_string_append_printf(request, ++ "Content-length: %zu\r\n", strlen(postdata)); ++ } ++ g_string_append_printf(request, "Accept: */*\r\n"); ++ g_string_append_printf(request, "Cookie: isfbe=false;%s\r\n", cookies); ++#ifdef HAVE_ZLIB ++ g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); ++#endif ++ if (is_proxy == TRUE) ++ { ++ if (purple_proxy_info_get_username(proxy_info) && ++ purple_proxy_info_get_password(proxy_info)) ++ { ++ proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); ++ proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); ++ g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); ++ g_free(proxy_auth_base64); ++ g_free(proxy_auth); ++ } ++ } ++ ++ /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ ++ languages = g_get_language_names(); ++ language_names = g_strjoinv(", ", (gchar **)languages); ++ purple_util_chrreplace(language_names, '_', '-'); ++ g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); ++ g_free(language_names); ++ ++ purple_debug_info("facebook", "getting url %s\n", url); ++ ++ g_string_append_printf(request, "\r\n"); ++ if (method & FB_METHOD_POST) ++ g_string_append_printf(request, "%s", postdata); ++ ++ /* If it needs to go over a SSL connection, we probably shouldn't print ++ * it in the debug log. Without this condition a user's password is ++ * printed in the debug log */ ++ if (method == FB_METHOD_POST) ++ purple_debug_info("facebook", "sending request data:\n%s\n", ++ postdata); ++ ++ g_free(cookies); ++ ++ fbconn = g_new0(FacebookConnection, 1); ++ fbconn->fba = fba; ++ fbconn->url = real_url; ++ fbconn->method = method; ++ fbconn->hostname = g_strdup(host); ++ fbconn->request = request; ++ fbconn->callback = callback_func; ++ fbconn->user_data = user_data; ++ fbconn->fd = -1; ++ fbconn->connection_keepalive = keepalive; ++ fbconn->request_time = time(NULL); ++ ++ g_queue_push_head(fba->waiting_conns, fbconn); ++ fb_next_connection(fba); ++} ++ ++static void fb_next_connection(FacebookAccount *fba) ++{ ++ FacebookConnection *fbconn; ++ ++ g_return_if_fail(fba != NULL); ++ ++ if (!g_queue_is_empty(fba->waiting_conns)) ++ { ++ if(g_slist_length(fba->conns) < FB_MAX_CONNECTIONS) ++ { ++ fbconn = g_queue_pop_tail(fba->waiting_conns); ++ fb_attempt_connection(fbconn); ++ } ++ } ++} ++ ++static void fb_attempt_connection(FacebookConnection *fbconn) ++{ ++ gboolean is_proxy = FALSE; ++ FacebookAccount *fba = fbconn->fba; ++ PurpleProxyInfo *proxy_info = NULL; ++ ++ if (fba && fba->account && !(fbconn->method & FB_METHOD_SSL)) ++ { ++ proxy_info = purple_proxy_get_setup(fba->account); ++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) ++ proxy_info = purple_global_proxy_get_info(); ++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) ++ { ++ is_proxy = TRUE; ++ } ++ } ++ ++#if 0 ++ /* Connection to attempt retries. This code doesn't work perfectly, but ++ * remains here for future reference if needed */ ++ if (time(NULL) - fbconn->request_time > 5) { ++ /* We've continuously tried to remake this connection for a ++ * bit now. It isn't happening, sadly. Time to die. */ ++ purple_debug_error("facebook", "could not connect after retries\n"); ++ fb_fatal_connection_cb(fbconn); ++ return; ++ } ++ ++ purple_debug_info("facebook", "making connection attempt\n"); ++ ++ /* TODO: If we're retrying the connection, consider clearing the cached ++ * DNS value. This will require some juggling with the hostname param */ ++ /* TODO/FIXME: This retries almost instantenously, which in some cases ++ * runs at blinding speed. Slow it down. */ ++ /* TODO/FIXME: this doesn't retry properly on non-ssl connections */ ++#endif ++ ++ fba->conns = g_slist_prepend(fba->conns, fbconn); ++ ++ /* ++ * Do a separate DNS lookup for the given host name and cache it ++ * for next time. ++ * ++ * TODO: It would be better if we did this before we call ++ * purple_proxy_connect(), so we could re-use the result. ++ * Or even better: Use persistent HTTP connections for servers ++ * that we access continually. ++ * ++ * TODO: This cache of the hostname<-->IP address does not respect ++ * the TTL returned by the DNS server. We should expire things ++ * from the cache after some amount of time. ++ */ ++ if (!is_proxy && !(fbconn->method & FB_METHOD_SSL) && !g_hostname_is_ip_address(fbconn->hostname)) ++ { ++ /* Don't do this for proxy connections, since proxies do the DNS lookup */ ++ gchar *host_ip; ++ ++ host_ip = g_hash_table_lookup(fba->hostname_ip_cache, fbconn->hostname); ++ if (host_ip != NULL) { ++ g_free(fbconn->hostname); ++ fbconn->hostname = g_strdup(host_ip); ++ } else if (fba->account && !fba->account->disconnecting) { ++ GSList *host_lookup_list = NULL; ++ PurpleDnsQueryData *query; ++ ++ host_lookup_list = g_slist_prepend( ++ host_lookup_list, g_strdup(fbconn->hostname)); ++ host_lookup_list = g_slist_prepend( ++ host_lookup_list, fba); ++ ++ query = purple_dnsquery_a(fbconn->hostname, 80, ++ fb_host_lookup_cb, host_lookup_list); ++ fba->dns_queries = g_slist_prepend(fba->dns_queries, query); ++ host_lookup_list = g_slist_append(host_lookup_list, query); ++ } ++ } ++ ++ if (fbconn->method & FB_METHOD_SSL) { ++ fbconn->ssl_conn = purple_ssl_connect(fba->account, fbconn->hostname, ++ 443, fb_post_or_get_ssl_connect_cb, ++ fb_ssl_connection_error, fbconn); ++ } else { ++ fbconn->connect_data = purple_proxy_connect(NULL, fba->account, ++ fbconn->hostname, 80, fb_post_or_get_connect_cb, fbconn); ++ } ++ ++ return; ++} ++ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.h 2011-03-27 09:15:30.712552999 -0600 +@@ -0,0 +1,62 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_CONNECTION_H ++#define FACEBOOK_CONNECTION_H ++ ++#include "libfacebook.h" ++ ++/* ++ * This is a bitmask. ++ */ ++typedef enum ++{ ++ FB_METHOD_GET = 0x0001, ++ FB_METHOD_POST = 0x0002, ++ FB_METHOD_SSL = 0x0004 ++} FacebookMethod; ++ ++typedef struct _FacebookConnection FacebookConnection; ++struct _FacebookConnection { ++ FacebookAccount *fba; ++ FacebookMethod method; ++ gchar *hostname; ++ gchar *url; ++ GString *request; ++ FacebookProxyCallbackFunc callback; ++ gpointer user_data; ++ char *rx_buf; ++ size_t rx_len; ++ PurpleProxyConnectData *connect_data; ++ PurpleSslConnection *ssl_conn; ++ int fd; ++ guint input_watcher; ++ gboolean connection_keepalive; ++ time_t request_time; ++}; ++ ++void fb_connection_destroy(FacebookConnection *fbconn); ++void fb_post_or_get(FacebookAccount *fba, FacebookMethod method, ++ const gchar *host, const gchar *url, const gchar *postdata, ++ FacebookProxyCallbackFunc callback_func, gpointer user_data, ++ gboolean keepalive); ++gchar *fb_cookies_to_string(FacebookAccount *fba); ++ ++#endif /* FACEBOOK_CONNECTION_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.c 2011-03-27 09:15:30.860553000 -0600 +@@ -0,0 +1,307 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_conversation.h" ++#include "fb_connection.h" ++#include "fb_util.h" ++#include "fb_json.h" ++ ++#include "conversation.h" ++#include "signals.h" ++ ++/***************************************************************************** ++ * MESSAGE PROCESSING * ++ *****************************************************************************/ ++void fb_conversation_handle_message(FacebookAccount *fba, const char *from, ++ const char *to, gint64 message_time, const gchar *message_orig, ++ gboolean log) ++{ ++ gchar *tmp, *message_text; ++ ++ if (!log) { ++ purple_debug_info("facebook", "message with no logging\n"); ++ } ++ ++ // Process message. ++ message_text = fb_strdup_withhtml(message_orig); ++ tmp = message_text; ++ message_text = fb_replace_styled_text(message_text); ++ g_free(tmp); ++ ++ if (fba->uid != atoll(from) || fba->uid == atoll(to)) { ++ purple_debug_info("facebook", ++ "displaying received message %lld: %s\n", ++ (long long int) message_time, message_text); ++ // TODO/FIXME: cheat here by changing formatting colors. ++ // Or add an option to just disable history on conv open. TBD. ++ serv_got_im(fba->pc, from, message_text, ++ log? ++ PURPLE_MESSAGE_RECV : ++ PURPLE_MESSAGE_RECV, ++ message_time / 1000); ++ if (message_time > fba->last_message_time) { ++ fba->last_message_time = message_time; ++ } else { ++ purple_debug_warning("facebook", ++ "displaying message out of sync\n"); ++ } ++ } else if (!g_hash_table_remove( ++ fba->sent_messages_hash, message_orig)) ++ { ++ purple_debug_info("facebook", ++ "displaying sent message %lld: %s\n", ++ (long long int) message_time, message_text); ++ ++ serv_got_im(fba->pc, to, message_text, ++ log? ++ PURPLE_MESSAGE_SEND : ++ PURPLE_MESSAGE_SEND, ++ message_time / 1000); ++ if (message_time > fba->last_message_time) { ++ fba->last_message_time = message_time; ++ } else { ++ purple_debug_warning("facebook", ++ "displaying message out of sync\n"); ++ } ++ } ++ ++ // Cleanup. ++ g_free(message_text); ++} ++ ++void fb_conversation_handle_chat(FacebookAccount *fba, const char *from, ++ const char *group, gint64 message_time, const gchar *message_orig, ++ gboolean log) ++{ ++ gchar *tmp, *message_text; ++ ++ if (!log) { ++ purple_debug_info("facebook", "message with no logging\n"); ++ } ++ ++ // Process message. ++ message_text = fb_strdup_withhtml(message_orig); ++ tmp = message_text; ++ message_text = fb_replace_styled_text(message_text); ++ g_free(tmp); ++ ++ purple_debug_info("facebook", ++ "displaying group message %lld: %s\n", ++ (long long int) message_time, message_text); ++ ++ if (!purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account)) ++ serv_got_joined_chat(fba->pc, atoi(group), group); ++ ++ serv_got_chat_in(fba->pc, atoi(group), from, PURPLE_MESSAGE_RECV, message_text, message_time / 1000); ++ ++ if (message_time > fba->last_message_time) ++ { ++ fba->last_message_time = message_time; ++ } else { ++ purple_debug_warning("facebook", ++ "displaying message out of sync\n"); ++ } ++ ++ // Cleanup. ++ g_free(message_text); ++} ++ ++ ++/***************************************************************************** ++ * HISTORY CODE * ++ *****************************************************************************/ ++ ++static void fb_history_fetch_cb(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer userdata) ++{ ++ JsonParser *parser; ++ JsonObject *object, *payload; ++ JsonArray *history; ++ guint i; ++ gint64 min_time; ++ ++ parser = fb_get_parser(data, data_len); ++ ++ if (!parser) { ++ // We didn't get data, but this isn't necessarily fatal. ++ purple_debug_warning("facebook", ++ "bad data while fetching history\n"); ++ return; ++ } ++ ++ min_time = atoll((char *) userdata); ++ g_free(userdata); ++ purple_debug_info("facebook", "history fetch with min time of %lld\n", ++ (long long int) min_time); ++ ++ object = fb_get_json_object(parser, NULL); ++ payload = json_node_get_object( ++ json_object_get_member(object, "payload")); ++ history = json_node_get_array( ++ json_object_get_member(payload, "history")); ++ ++ purple_debug_info("facebook", ++ "found %d history items to possibly render\n", ++ json_array_get_length(history)); ++ ++ for (i = 0; i < json_array_get_length(history); i++) { ++ const gchar *type; ++ JsonObject *message_obj; ++ ++ message_obj = json_node_get_object( ++ json_array_get_element(history, i)); ++ type = json_node_get_string(json_object_get_member( ++ message_obj, "type")); ++ ++ if (g_str_equal(type, "msg")) { ++ gint64 message_time; ++ const gchar *message; ++ gchar *from; ++ gchar *to; ++ JsonObject *text_obj; ++ ++ from = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int( ++ json_object_get_member(message_obj, "from"))); ++ to = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int( ++ json_object_get_member(message_obj, "to"))); ++ ++ text_obj = json_node_get_object( ++ json_object_get_member(message_obj, "msg")); ++ message = json_node_get_string( ++ json_object_get_member(text_obj, "text")); ++ ++ message_time = fb_time_kludge(json_node_get_int( ++ json_object_get_member(message_obj, "time"))); ++ ++ if (message_time > min_time) { ++ purple_debug_info("facebook", ++ "displaying history message %lld\n", ++ (long long int) message_time); ++ ++ //check to see that this isn't for a multi-user chat ++ if (purple_blist_find_chat(fba->account, to) || ++ purple_find_conversation_with_account( ++ PURPLE_CONV_TYPE_CHAT, to, fba->account)) ++ { ++ fb_conversation_handle_chat( ++ fba, from, to, message_time, message, ++ min_time != 0); ++ } else { ++ fb_conversation_handle_message( ++ fba, from, to, message_time, message, ++ min_time != 0); ++ } ++ } ++ ++ g_free(from); ++ g_free(to); ++ } ++ } ++ ++ json_parser_free(parser); ++} ++ ++void fb_history_fetch(FacebookAccount *fba, const char *who, ++ gboolean display_all) ++{ ++ g_return_if_fail(fba != NULL); ++ ++ purple_debug_info("facebook", "fetching history with %s\n", who); ++ ++ gint64 min_time = fba->last_message_time; ++ if (display_all) { ++ min_time = 0; ++ } ++ ++ gchar *url = g_strdup_printf("/ajax/chat/history.php?id=%s&__a=1", who); ++ fb_post_or_get( ++ fba, FB_METHOD_GET, NULL, url, NULL, fb_history_fetch_cb, ++ g_strdup_printf("%lld", (long long int) min_time), FALSE); ++ g_free(url); ++} ++ ++/***************************************************************************** ++ * GENERAL EVENTS CODE * ++ *****************************************************************************/ ++ ++void fb_conversation_closed(PurpleConnection *gc, const char *who) ++{ ++ FacebookAccount *fba = gc->proto_data; ++ gchar *postdata; ++ ++ g_return_if_fail(fba->post_form_id != NULL); ++ ++ /* notify server that we closed the chat window */ ++ /* close_chat=589039771&window_id=3168919846& ++ * post_form_id=c258fe42460c7e8b61e242a37ef05afc */ ++ postdata = g_strdup_printf("close_chat=%s&post_form_id=%s&fb_dtsg=%s&" ++ "post_form_id_source=AsyncRequest&__a=1", who, ++ fba->post_form_id, fba->dtsg); ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/chat/settings.php", ++ postdata, NULL, NULL, FALSE); ++ g_free(postdata); ++} ++ ++static void fb_conversation_created(PurpleConversation *conv) ++{ ++ PurpleAccount *account = purple_conversation_get_account(conv); ++ ++ if (!fb_conversation_is_fb(conv)) { ++ return; ++ } ++ ++ purple_debug_info("facebook", "conversation created with %s\n", ++ conv->name); ++ ++ if (purple_account_get_bool(account, "facebook_show_history", TRUE)) ++ { ++ fb_history_fetch(account->gc->proto_data, conv->name, TRUE); ++ } ++} ++ ++gboolean fb_conversation_is_fb(PurpleConversation *conv) ++{ ++ PurpleAccount *account = purple_conversation_get_account(conv); ++ const gchar *prpl = purple_account_get_protocol_id(account); ++ return g_str_equal(prpl, FACEBOOK_PLUGIN_ID); ++} ++ ++void fb_conversation_init(FacebookAccount *fba) ++{ ++ fba->last_message_time = 0; ++ ++ purple_signal_connect( ++ purple_conversations_get_handle(), ++ "conversation-created", ++ fba, ++ PURPLE_CALLBACK(fb_conversation_created), ++ NULL); ++} ++ ++void fb_conversation_destroy(FacebookAccount *fba) ++{ ++ purple_signal_disconnect( ++ purple_conversations_get_handle(), ++ "conversation-created", ++ fba, ++ PURPLE_CALLBACK(fb_conversation_created)); ++} ++ ++ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.h 2011-03-27 09:15:30.852553000 -0600 +@@ -0,0 +1,41 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_CONVERSATION_H ++#define FACEBOOK_CONVERSATION_H ++ ++#include "libfacebook.h" ++ ++void fb_conversation_init(FacebookAccount *fba); ++void fb_conversation_destroy(FacebookAccount *fba); ++ ++void fb_conversation_closed(PurpleConnection *gc, const char *who); ++gboolean fb_conversation_is_fb(PurpleConversation *conv); ++ ++void fb_history_fetch(FacebookAccount *fba, const char *who, ++ gboolean display_all); ++void fb_conversation_handle_message(FacebookAccount *fba, const char *from, ++ const char *to, gint64 message_time, const gchar *message_orig, ++ gboolean log); ++void fb_conversation_handle_chat(FacebookAccount *fba, const char *from, ++ const char *group, gint64 message_time, const gchar *message_orig, ++ gboolean log); ++ ++#endif /* FACEBOOK_CONVERSATION_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.c 2011-03-27 09:15:30.856552999 -0600 +@@ -0,0 +1,520 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_friendlist.h" ++#include "fb_connection.h" ++#include "libfacebook.h" ++#include "fb_util.h" ++ ++typedef struct _MoveRequest MoveRequest; ++struct _MoveRequest { ++ char *old_group; ++ char *new_group; ++ char *who; ++}; ++ ++/******************************************************************************/ ++/* Friend list modification methods */ ++/******************************************************************************/ ++static void handle_move_request(FacebookAccount *fba, MoveRequest *request) ++{ ++ const gchar *old_list_id; ++ const gchar *new_list_id; ++ gchar *postdata; ++ gboolean remove_flist, no_original_list; ++ const gchar *command; ++ ++ purple_debug_info("facebook", ++ "handling movement of %s from %s to %s\n", ++ request->who, request->old_group, request->new_group); ++ ++ old_list_id = fb_get_list_id(fba, request->old_group); ++ new_list_id = fb_get_list_id(fba, request->new_group); ++ ++ remove_flist = !new_list_id || g_str_equal(new_list_id, "-1"); ++ no_original_list = !old_list_id || g_str_equal(old_list_id, "-1"); ++ ++ if (remove_flist) { ++ command = "&remove_fl=true"; ++ } else if (no_original_list) { ++ command = "&add_fl=true"; ++ } else { ++ command = "&move_fl=true"; ++ } ++ ++ postdata = g_strdup_printf( ++ "post_form_id=%s&drag_uid=%s&user=%" G_GINT64_FORMAT ++ "&new_flid=%s&old_flid=%s%s", ++ fba->post_form_id, ++ request->who, ++ fba->uid, ++ remove_flist ? "" : new_list_id, ++ no_original_list ? "" : old_list_id, ++ command); ++ ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, ++ "/ajax/chat/buddy_list_settings.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_free(postdata); ++ ++ g_free(request->who); ++ g_free(request->old_group); ++ g_free(request->new_group); ++ g_free(request); ++} ++ ++static void create_list_cb(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer userdata) ++{ ++ // NOTE: this method can also be used for movements between ++ // friend lists if necessary. ++ ++ JsonParser *parser; ++ JsonObject *objnode; ++ MoveRequest *request; ++ ++ // Parse out old data. ++ parser = fb_get_parser(data, data_len); ++ if (parser == NULL) { ++ return; ++ } ++ ++ objnode = fb_get_json_object(parser, NULL); ++ if (!objnode || ++ !json_object_has_member(objnode, "payload")) ++ { ++ json_parser_free(parser); ++ return; ++ } ++ ++ objnode = json_node_get_object(json_object_get_member( ++ objnode, "payload")); ++ fb_process_friend_lists(fba, objnode); ++ ++ json_parser_free(parser); ++ ++ // Move Friend ++ request = (MoveRequest *) userdata; ++ if (request) { ++ handle_move_request(fba, request); ++ } ++} ++ ++static void create_friend_list(FacebookAccount *fba, const gchar *new_group, ++ MoveRequest *request) ++{ ++ gchar *postdata; ++ gchar *new_group_escaped; ++ ++ purple_debug_info("facebook", "creating friend list %s\n", new_group); ++ ++ new_group_escaped = fb_strdup_withhtml(new_group); ++ ++ postdata = g_strdup_printf( ++ "post_form_id=%s&create=%s&user=%" G_GINT64_FORMAT, ++ fba->post_form_id, ++ new_group_escaped, ++ fba->uid); ++ ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, ++ "/ajax/chat/buddy_list_settings.php", ++ postdata, create_list_cb, request, FALSE); ++ ++ g_free(postdata); ++ g_free(new_group_escaped); ++} ++ ++void fb_group_buddy_move(PurpleConnection *pc, const char *who, ++ const char *old_group, const char *new_group) ++{ ++ FacebookAccount *fba; ++ MoveRequest *request; ++ const gchar *new_list_id; ++ ++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE)) ++ { ++ //Dont do anything if we're ignoring groups ++ return; ++ } ++ ++ fba = pc->proto_data; ++ ++ purple_debug_info("facebook", "handling move of %s from %s to %s\n", ++ who, old_group, new_group); ++ ++ // Don't do anything if groups are not actually changing. ++ if (!purple_utf8_strcasecmp(old_group, new_group)) { ++ purple_debug_info("facebook", "groups are same, not moving\n"); ++ return; ++ } ++ ++ // Facebook doesn't support moving yourself because you can't be in a ++ // friend list. Let buddy list be updated as appropriate. ++ if (atoll(who) == fba->uid) { ++ purple_debug_info("facebook", ++ "moving self, do not update server\n"); ++ return; ++ } ++ ++ request = g_new0(MoveRequest, 1); ++ request->old_group = g_utf8_strdown(old_group, -1); ++ request->new_group = g_utf8_strdown(new_group, -1); ++ request->who = g_strdup(who); ++ ++ new_list_id = fb_get_list_id(fba, request->new_group); ++ if (new_list_id) { ++ handle_move_request(fba, request); ++ } else { ++ create_friend_list(fba, new_group, request); ++ } ++} ++ ++void fb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, ++ PurpleGroup *group) ++{ ++ // This method should only remove a buddy from a friend list. ++ // Nothing more. It should not defriend a user ever. See issue ++ // #185 for a good explaination of why this is a bad idea. ++ // ++ // Moreover, defriending is such a rare operation that we should ++ // never make it easy. Facebook intentionally hides such a action ++ // behind multiple layers of links and dialogs. ++ // ++ // If the plugin is ever to perform an actual defriending, it needs ++ // to provide a dialog and user prompt at the absolute bare minimum. ++ FacebookAccount *fba; ++ ++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE)) ++ { ++ //Dont do anything if we're ignoring groups ++ return; ++ } ++ ++ purple_debug_info("facebook", "handing removal of buddy %s\n", ++ buddy->name); ++ ++ fba = pc->proto_data; ++ ++ fb_group_buddy_move(pc, buddy->name, purple_group_get_name(group), ++ DEFAULT_GROUP_NAME); ++} ++ ++void fb_group_rename(PurpleConnection *pc, const char *old_name, ++ PurpleGroup *group, GList *moved_buddies) ++{ ++ ++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE)) ++ { ++ //Dont do anything if we're ignoring groups ++ return; ++ } ++ ++ purple_debug_info("facebook", ++ "handling group rename of %s to %s\n", ++ old_name, purple_group_get_name(group)); ++ ++ // We don't do anything here. Facebook's AJAX API for renaming groups ++ // is horribly, horribly overcomplicated. There is no simple rename ++ // call, instead you must also pass in all the current data about the ++ // friend list and port it over. While it is possible to implement ++ // this, it is risky and could potentially destroy a friend list if ++ // the API changes. That's a Bad Thing(tm). Given the risk involved ++ // with this operation and how rare it is, it's not worth it. ++ // ++ // The problem is compounded by the fact that renaming groups triggers ++ // all sorts of weird behaviors in Pidgin. Renaming to a new name is ++ // simple. Renaming to an existing group name (hence a merge) ++ // triggers completely different behavior with calls to group_buddy ++ // before the call to rename. This completely defeats the purpose of ++ // having a rename function because group_buddy is called instead. ++ // ++ // Thus, the final decision is to use the buddy_move call. ++ ++ // TODO: warn users that renaming has no effect here. ++} ++ ++void fb_group_remove(PurpleConnection *pc, PurpleGroup *group) ++{ ++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE)) ++ { ++ //Dont do anything if we're ignoring groups ++ return; ++ } ++ ++ purple_debug_info("facebook", "got group removal of %s\n", ++ purple_group_get_name(group)); ++ ++ // We don't do anything here. This is because a group rename also ++ // fires a group removal event. This assumes that the new group is ++ // equivalent to the old group, but Facebook friend lists are much more ++ // than simple groups- they are privacy control lists too. There is ++ // no easy way to port the settings between groups. Better off not ++ // deleting, and the user can do the cleanup with their browser. ++} ++ ++/******************************************************************************/ ++/* Friend list fetch methods */ ++/******************************************************************************/ ++ ++const gchar *fb_get_list_id(FacebookAccount *fba, const gchar *list_name) ++{ ++ if (!purple_utf8_strcasecmp(list_name, DEFAULT_GROUP_NAME)) { ++ return "-1"; ++ } ++ ++ return g_hash_table_lookup(fba->friend_lists_reverse, purple_normalize_nocase(NULL, list_name)); ++} ++ ++gboolean fb_process_friend_lists(FacebookAccount *fba, ++ JsonObject *buddy_list) ++{ ++ JsonObject *fl_obj; ++ GList *friend_list_ids, *cur; ++ ++ purple_debug_info("facebook", "processing friend list data\n"); ++ ++ if (!json_object_has_member(buddy_list, "flData")) ++ { ++ purple_debug_info("facebook", "no friend list data\n"); ++ return FALSE; ++ } ++ ++ ++ fl_obj = json_node_get_object(json_object_get_member( ++ buddy_list, "flData")); ++ friend_list_ids = json_object_get_members(fl_obj); ++ for (cur = friend_list_ids; cur != NULL; cur = cur->next) ++ { ++ gchar *id; ++ const gchar *name; ++ JsonObject *data; ++ ++ id = (gchar *) cur->data; ++ data = json_node_get_object(json_object_get_member( ++ fl_obj, id)); ++ name = json_node_get_string(json_object_get_member( ++ data, "n")); ++ if (name) { ++ // Either -1 isnt a valid JSON string or JSON-glib does ++ // this wrong. I'm too tired to tell the difference. ++ if (g_str_equal(id, "_1")) { ++ id = "-1"; ++ } ++ purple_debug_info("facebook", ++ "got friend list %s with id %s\n", ++ name, id); ++ g_hash_table_insert(fba->friend_lists, ++ g_strdup(id), g_strdup(name)); ++ g_hash_table_insert(fba->friend_lists_reverse, ++ g_utf8_strdown(name, -1), g_strdup(id)); ++ } ++ } ++ ++ g_list_free(friend_list_ids); ++ ++ return TRUE; ++} ++ ++static void destroy_buddy(gpointer key, gpointer value, gpointer data) ++{ ++ PurpleBuddy *buddy; ++ gchar *group_name; ++ FacebookAccount *fba; ++ ++ buddy = (PurpleBuddy *) value; ++ group_name = (gchar *) key; ++ fba = (FacebookAccount *) data; ++ ++ purple_debug_info("facebook", "removing %s from group %s\n", ++ buddy->name, group_name); ++ if (atoll(buddy->name) == fba->uid) { ++ purple_debug_info("facebook", "not removing self from %s\n", ++ group_name); ++ return; ++ } ++ ++ purple_blist_remove_buddy(buddy); ++} ++ ++static PurpleBuddy *add_buddy(FacebookAccount *fba, ++ const gchar *friend_list_id, const gchar *uid, GHashTable *cur_groups) ++{ ++ const gchar *group_name; ++ PurpleGroup *fb_group; ++ PurpleBuddy *buddy; ++ ++ group_name = g_hash_table_lookup(fba->friend_lists, purple_normalize_nocase(NULL, friend_list_id)); ++ if (!group_name || group_name[0] == '\0') { ++ purple_debug_info("facebook", ++ "did not find name of list %s\n", ++ friend_list_id); ++ group_name = DEFAULT_GROUP_NAME; ++ } ++ ++ // Initialize group as necessary. ++ fb_group = purple_find_group(group_name); ++ if (fb_group == NULL) ++ { ++ purple_debug_info("facebook", "adding friend list %s\n", ++ group_name); ++ fb_group = purple_group_new(group_name); ++ purple_blist_add_group(fb_group, NULL); ++ } ++ ++ buddy = (PurpleBuddy *)g_hash_table_lookup(cur_groups, purple_normalize_nocase(NULL, group_name)); ++ if (!buddy) { ++ purple_debug_info("facebook", "adding %s to %s\n", ++ uid, group_name); ++ buddy = purple_buddy_new(fba->account, uid, NULL); ++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL); ++ g_hash_table_remove(cur_groups, purple_normalize_nocase(NULL, group_name)); ++ } ++ ++ return buddy; ++} ++ ++ ++GList *fb_get_buddies_friend_list (FacebookAccount *fba, ++ const gchar *uid, JsonArray *friend_list_ids) ++{ ++ GSList *buddies; ++ GSList *cur; ++ GHashTable *cur_groups; ++ int i; ++ GList *final_buddies, *cur_buddy; ++ PurpleGroup *fb_group; ++ PurpleBuddy *buddy; ++ ++ final_buddies = NULL; ++ buddies = purple_find_buddies(fba->account, uid); ++ ++ // If we're already in the buddy list, stop. Ignore FB info because ++ // it will be incorrect. ++ if (atoll(uid) == fba->uid && buddies != NULL) { ++ purple_debug_info("facebook", ++ "already have buddies for self, not adding\n"); ++ for (cur = buddies; cur != NULL; cur = cur->next) ++ { ++ final_buddies = g_list_append( ++ final_buddies, cur->data); ++ } ++ g_slist_free(buddies); ++ return final_buddies; ++ } ++ ++ //Do we want to ignore groups? ++ if (!purple_account_get_bool(fba->account, "facebook_use_groups", TRUE)) ++ { ++ if (buddies != NULL) { ++ //Copy the slist into the list ++ for (cur = buddies; cur != NULL; cur = cur->next) ++ { ++ final_buddies = g_list_append( ++ final_buddies, cur->data); ++ } ++ g_slist_free(buddies); ++ return final_buddies; ++ } else { ++ buddy = purple_buddy_new(fba->account, uid, NULL); ++ fb_group = purple_find_group(DEFAULT_GROUP_NAME); ++ if (fb_group == NULL) ++ { ++ fb_group = purple_group_new(DEFAULT_GROUP_NAME); ++ purple_blist_add_group(fb_group, NULL); ++ } ++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL); ++ final_buddies = g_list_append(final_buddies, buddy); ++ return final_buddies; ++ } ++ } ++ ++ // Determine what buddies exist and what groups they are in. ++ cur_groups = g_hash_table_new_full(g_str_hash, g_str_equal, ++ g_free, NULL); ++ for (cur = buddies; cur != NULL; cur = cur->next) ++ { ++ const gchar *group_name; ++ ++ group_name = purple_group_get_name(purple_buddy_get_group( ++ (PurpleBuddy *)cur->data)); ++ group_name = purple_normalize_nocase(NULL, group_name); ++ ++ g_hash_table_insert(cur_groups, g_strdup(group_name), cur->data); ++ } ++ g_slist_free(buddies); ++ ++ // Create/insert necessary buddies ++ if (friend_list_ids) { ++ for (i = 0; i < json_array_get_length(friend_list_ids); i++) ++ { ++ const gchar *friend_list_id; ++ ++ friend_list_id = json_node_get_string( ++ json_array_get_element(friend_list_ids, i)); ++ ++ buddy = add_buddy(fba, friend_list_id, uid, cur_groups); ++ ++ final_buddies = g_list_append(final_buddies, buddy); ++ } ++ } else { ++ // No friend list data, so we use the default group. ++ final_buddies = g_list_append(final_buddies, ++ add_buddy(fba, "-1", uid, cur_groups)); ++ } ++ ++ // Figure out which groups/buddies are not represented. ++ for (cur_buddy = final_buddies; cur_buddy != NULL; ++ cur_buddy = cur_buddy->next) ++ { ++ const gchar *group_name = purple_group_get_name(purple_buddy_get_group( ++ (PurpleBuddy *)cur_buddy->data)); ++ g_hash_table_remove(cur_groups, purple_normalize_nocase(NULL, group_name)); ++ } ++ ++ // Delete remaining buddies to maintain sync state with server. ++ g_hash_table_foreach(cur_groups, destroy_buddy, fba); ++ ++ // Cleanup! ++ g_hash_table_destroy(cur_groups); ++ ++ return final_buddies; ++} ++ ++void fb_friendlist_init(FacebookAccount *fba) ++{ ++ /* data structure mapping friend list id to name. libpurple only ++ * recognizes name, does not have group aliases */ ++ fba->friend_lists = g_hash_table_new_full(g_str_hash, g_str_equal, ++ g_free, g_free); ++ /* structure mapping names to list id for speed. */ ++ fba->friend_lists_reverse = g_hash_table_new_full(g_str_hash, ++ g_str_equal, g_free, g_free); ++} ++ ++void fb_friendlist_destroy(FacebookAccount *fba) ++{ ++ if (fba->friend_lists) { ++ g_hash_table_destroy(fba->friend_lists); ++ } ++ if (fba->friend_lists_reverse) { ++ g_hash_table_destroy(fba->friend_lists_reverse); ++ } ++} +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.h 2011-03-27 09:15:30.688552999 -0600 +@@ -0,0 +1,47 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_FRIENDLIST_H ++#define FACEBOOK_FRIENDLIST_H ++ ++#include "libfacebook.h" ++#include "fb_json.h" ++ ++#define DEFAULT_GROUP_NAME "Facebook" ++ ++/* Friend list modification methods */ ++void fb_group_buddy_move(PurpleConnection *pc, const char *who, ++ const char *old_group, const char *new_group); ++void fb_group_rename(PurpleConnection *pc, const char *old_name, ++ PurpleGroup *group, GList *moved_buddies); ++void fb_group_remove(PurpleConnection *pc, PurpleGroup *group); ++void fb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, ++ PurpleGroup *group); ++ ++/* Friend list fetch methods */ ++const gchar *fb_get_list_id(FacebookAccount *fba, const gchar *list_name); ++gboolean fb_process_friend_lists(FacebookAccount *fba, JsonObject *buddy_list); ++GList *fb_get_buddies_friend_list (FacebookAccount *fba, ++ const gchar *uid, JsonArray *friend_list_ids); ++ ++void fb_friendlist_init(FacebookAccount *fba); ++void fb_friendlist_destroy(FacebookAccount *fba); ++ ++#endif /* FACEBOOK_FRIENDLIST_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_info.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_info.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.c 2011-03-27 09:15:30.912552999 -0600 +@@ -0,0 +1,237 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_connection.h" ++#include "fb_info.h" ++#include "fb_blist.h" ++ ++/* ++ * TODO: Do we really want to do this? Maybe we could just set a ++ * flag that says that this protocol supports HTML? ++ */ ++static gchar *fb_remove_useless_stripped_links(const gchar *input) ++{ ++ /* removes stripped links like "(/s.php? ... )" from user info */ ++ /* as an artifact of purple_markup_strip_html */ ++ ++ gchar *output = g_strdup(input); ++ gchar *i = output; ++ gchar *end; ++ ++ while ((i = strstr(i, " (/"))) ++ { ++ end = strchr(i, ')'); ++ if (end) ++ { ++ end += 1; ++ /* overwrite everything after the brackets to before it */ ++ g_stpcpy(i, end); ++ } ++ } ++ ++ return output; ++} ++ ++static void fb_get_info_cb(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data) ++{ ++ PurpleNotifyUserInfo *user_info; ++ PurpleBuddyIcon *buddy_icon; ++ size_t image_size; ++ gconstpointer image_pointer; ++ int icon_id = -1; ++ gchar *uid = user_data; ++ gchar *label_tmp; ++ gchar *value_tmp; ++ gchar *value_tmp2; ++ gchar *search_start; ++ gchar *search_end; ++ PurpleBuddy *buddy = NULL; ++ FacebookBuddy *fbuddy = NULL; ++ ++ purple_debug_info("facebook", "get_info_cb\n"); ++ purple_debug_misc("facebook", "%s\n", data); ++ ++ buddy = purple_find_buddy(fba->account, uid); ++ if (buddy) ++ { ++ fbuddy = buddy->proto_data; ++ } ++ ++ /* look from <div id="info_tab" class="info_tab"> */ ++ /* until </div></div></div></div> */ ++ search_start = g_strstr_len(data, data_len, "<div id=\"info_tab\" class=\"info_tab\">"); ++ if (search_start == NULL) ++ { ++ search_start = g_strstr_len(data, data_len, "window.location.replace(\"http:\\/\\/www.facebook.com\\"); ++ if (search_start) ++ { ++ search_start += strlen("window.location.replace(\"http:\\/\\/www.facebook.com\\"); ++ search_end = strchr(search_start, '"'); ++ value_tmp = g_strndup(search_start, search_end - search_start); ++ if (value_tmp) { ++ purple_debug_info("facebook", "info url: %s\n", value_tmp); ++ fb_post_or_get(fba, FB_METHOD_GET, NULL, value_tmp, NULL, fb_get_info_cb, uid, FALSE); ++ g_free(value_tmp); ++ return; ++ } ++ } ++ purple_debug_warning("facebook", ++ "could not find user info, showing default"); ++ user_info = purple_notify_user_info_new(); ++ value_tmp = g_strdup_printf("<a href=\"http://www.facebook.com/profile.php?id=%s\">%s</a>", ++ uid, _("View web profile")); ++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp); ++ purple_notify_user_info_add_section_break(user_info); ++ g_free(value_tmp); ++ purple_notify_userinfo(fba->pc, uid, user_info, NULL, NULL); ++ purple_notify_user_info_destroy(user_info); ++ g_free(uid); ++ return; ++ } ++ search_end = strstr(search_start, "</div></div></div></div>"); ++ ++ user_info = purple_notify_user_info_new(); ++ ++ /* Insert link to profile at top */ ++ value_tmp = g_strdup_printf("<a href=\"http://www.facebook.com/profile.php?id=%s\">%s</a>", ++ uid, _("View web profile")); ++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp); ++ purple_notify_user_info_add_section_break(user_info); ++ g_free(value_tmp); ++ ++ value_tmp = g_strstr_len(data, data_len, "<title>Facebook | "); ++ if (value_tmp) ++ { ++ value_tmp = strchr(value_tmp, '|')+2; ++ value_tmp2 = g_strndup(value_tmp, strstr(value_tmp, "</title>")-value_tmp); ++ value_tmp = g_strchomp(purple_markup_strip_html(value_tmp2)); ++ purple_notify_user_info_add_pair(user_info, _("Name"), value_tmp); ++ fb_blist_set_alias(fba, uid, value_tmp); ++ g_free(value_tmp); ++ g_free(value_tmp2); ++ } ++ ++ value_tmp = g_strstr_len(data, data_len, "<span id=\"profile_status\""); ++ if (value_tmp) ++ { ++ value_tmp2 = strstr(value_tmp, "</span>"); ++ if (value_tmp2) ++ { ++ value_tmp = strchr(value_tmp, '>')+1; ++ value_tmp2 = g_strndup(value_tmp, strchr(value_tmp, '<')-value_tmp); ++ purple_debug_info("facebook", "status: %s\n", value_tmp2); ++ value_tmp = g_strchomp(purple_markup_strip_html(value_tmp2)); ++ if (*value_tmp == '\0') ++ { ++ //For some reason their status message disappeared ++ //Try using their status message from the buddy list ++ if (fbuddy) ++ { ++ g_free(value_tmp); ++ value_tmp = g_strdup(fbuddy->status); ++ } ++ } ++ purple_notify_user_info_add_pair(user_info, _("Status"), value_tmp); ++ g_free(value_tmp); ++ g_free(value_tmp2); ++ } ++ } ++ ++ buddy_icon = purple_buddy_icons_find(fba->account, uid); ++ if (buddy_icon) ++ { ++ image_pointer = purple_buddy_icon_get_data(buddy_icon, &image_size); ++ icon_id = purple_imgstore_add_with_id(g_memdup(image_pointer, image_size), image_size, NULL); ++ value_tmp = g_strdup_printf("<img id='%d'>", icon_id); ++ purple_debug_info("facebook", "user info pic: '%s'\n", value_tmp); ++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp); ++ g_free(value_tmp); ++ } ++ ++ while ((search_start = strstr(search_start, "<dt>")) && search_start < search_end) ++ { ++ search_start += 4; ++ if (search_start[0] == '<' && search_start[1] == '/' && search_start[2] == 'd' && search_start[3] == 't') ++ { ++ /* the tag closes as soon as it opens (bad xhtml) */ ++ continue; ++ } ++ ++ label_tmp = g_strndup(search_start, strchr(search_start, ':')-search_start); ++ if (!*label_tmp) ++ { ++ g_free(label_tmp); ++ continue; ++ } ++ ++ search_start = strstr(search_start, "<dd>"); ++ if (!search_start) ++ { ++ g_free(label_tmp); ++ break; ++ } ++ ++ search_start += 4; ++ value_tmp = g_strndup(search_start, strstr(search_start, "</dd>")-search_start); ++ if (!*value_tmp) ++ { ++ g_free(label_tmp); ++ g_free(value_tmp); ++ continue; ++ } ++ ++ /* turn html to plaintext */ ++ if (strcmp(label_tmp, "AIM")) { ++ value_tmp2 = g_strchomp(purple_markup_strip_html(value_tmp)); ++ g_free(value_tmp); ++ value_tmp = value_tmp2; ++ ++ /* remove the silly links */ ++ value_tmp2 = fb_remove_useless_stripped_links(value_tmp); ++ g_free(value_tmp); ++ value_tmp = value_tmp2; ++ } ++ ++ purple_debug_info("facebook", "label: %s\n", label_tmp); ++ purple_debug_info("facebook", "value: %s\n", value_tmp); ++ purple_notify_user_info_add_pair(user_info, label_tmp, value_tmp); ++ g_free(label_tmp); ++ g_free(value_tmp); ++ } ++ ++ purple_notify_userinfo(fba->pc, uid, user_info, NULL, NULL); ++ purple_notify_user_info_destroy(user_info); ++ ++ if (icon_id >= 0) ++ purple_imgstore_unref_by_id(icon_id); ++ ++ g_free(uid); ++} ++ ++void fb_get_info(PurpleConnection *pc, const gchar *uid) ++{ ++ gchar *profile_url; ++ ++ profile_url = g_strdup_printf("/profile.php?id=%s&v=info", uid); ++ ++ fb_post_or_get(pc->proto_data, FB_METHOD_GET, NULL, profile_url, NULL, fb_get_info_cb, g_strdup(uid), FALSE); ++ ++ g_free(profile_url); ++} +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_info.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_info.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.h 2011-03-27 09:15:30.876553000 -0600 +@@ -0,0 +1,28 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_INFO_H ++#define FACEBOOK_INFO_H ++ ++#include "libfacebook.h" ++ ++void fb_get_info(PurpleConnection *pc, const gchar *uid); ++ ++#endif /* FACEBOOK_INFO_H */ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_json.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_json.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.c 2011-03-27 09:15:30.892553000 -0600 +@@ -0,0 +1,87 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_json.h" ++ ++#ifdef USE_JSONC ++ ++JsonParser * ++json_parser_new(void) ++{ ++ JsonParser *parser; ++ ++ parser = g_new0(JsonParser, 1); ++ parser->tok = json_tokener_new(); ++ ++ return parser; ++} ++ ++gboolean ++json_parser_load_from_data(JsonParser *parser, const gchar *data, ++ gssize length, GError **error) ++{ ++ if (parser->tok == NULL) ++ return FALSE; ++ ++ parser->root = json_tokener_parse_ex(parser->tok, (char*)data, (int)length); ++ ++ if (parser->tok->err != json_tokener_success) ++ { ++ json_object_put(parser->root); ++ parser->root = NULL; ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++void ++json_parser_free(JsonParser *parser) ++{ ++ json_tokener_free(parser->tok); ++ json_object_put(parser->root); ++ g_free(parser); ++} ++ ++JsonNode * ++json_parser_get_root(JsonParser *parser) ++{ ++ return parser->root; ++} ++ ++GList * ++json_object_get_members(JsonObject *obj) ++{ ++ GList *keys = NULL; ++ struct lh_entry *entry; ++ ++ for (entry = json_object_get_object(obj)->head; ++ entry; ++ entry = entry->next) ++ { ++ keys = g_list_prepend(keys, entry->k); ++ } ++ ++ keys = g_list_reverse(keys); ++ ++ return keys; ++} ++ ++#endif +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_json.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_json.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.h 2011-03-27 09:15:30.744552999 -0600 +@@ -0,0 +1,64 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_JSON_H ++#define FACEBOOK_JSON_H ++ ++#ifndef USE_JSONC ++# include <json-glib/json-glib.h> ++#define json_parser_free(parser) g_object_unref(parser) ++#else /* USE_JSONC */ ++# include <glib.h> ++# include <json/json.h> ++typedef struct json_object JsonNode; ++typedef struct json_object JsonObject; ++typedef struct json_object JsonArray; ++typedef struct { ++ struct json_tokener *tok; ++ struct json_object *root; ++} JsonParser; ++ ++gboolean json_parser_load_from_data(JsonParser *parser, ++ const gchar *data, ++ gssize length, ++ GError **error); ++ ++JsonNode* json_parser_get_root(JsonParser *parser); ++JsonParser* json_parser_new(void); ++void json_parser_free(JsonParser *parser); ++ ++#define json_object_has_member(obj, key) ((gboolean)json_object_object_get(obj, key)) ++#define json_object_get_member(obj, key) json_object_object_get(obj, key) ++GList* json_object_get_members(JsonObject *object); ++ ++#define json_node_get_array(node) (node) ++#define json_node_get_object(node) (node) ++#define json_node_get_boolean(node) json_object_get_boolean(node) ++#define json_node_get_double(node) json_object_get_double(node) ++#define json_node_get_int(node) json_object_get_int(node) ++#define json_node_get_string(node) json_object_get_string(node) ++ ++#define json_array_get_element(array, index) json_object_array_get_idx(array, index) ++#define json_array_get_length(array) json_object_array_length(array) ++ ++#endif /* USE_JSONC */ ++ ++#endif /* FACEBOOK_JSON_H */ ++ +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.c +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.c 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.c 2011-03-27 09:15:30.868553000 -0600 +@@ -0,0 +1,233 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#include "fb_managefriends.h" ++#include "fb_connection.h" ++ ++static void fb_auth_accept_cb(gpointer data) ++{ ++ FacebookBuddy *fbuddy = data; ++ FacebookAccount *fba = fbuddy->fba; ++ gchar *buddy_uid; ++ gchar *postdata; ++ ++ g_return_if_fail(fba != NULL); ++ g_return_if_fail(fba->post_form_id != NULL); ++ g_return_if_fail(fbuddy->uid != 0); ++ ++ buddy_uid = g_strdup_printf("%" G_GINT64_FORMAT, fbuddy->uid); ++ ++ postdata = g_strdup_printf( ++ "type=friend_connect&id=%s&actions[accept]=Confirm&" ++ "post_form_id=%s&fb_dtsg=%s&confirm=%s&" ++ "post_form_id_source=AsyncRequest&__a=1", ++ buddy_uid, fba->post_form_id, fba->dtsg, buddy_uid); ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/reqs.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_hash_table_remove(fba->auth_buddies, buddy_uid); ++ ++ g_free(postdata); ++ g_free(fbuddy); ++ g_free(buddy_uid); ++} ++ ++static void fb_auth_reject_cb(gpointer data) ++{ ++ FacebookBuddy *fbuddy = data; ++ FacebookAccount *fba = fbuddy->fba; ++ gchar *buddy_uid; ++ gchar *postdata; ++ ++ g_return_if_fail(fba != NULL); ++ g_return_if_fail(fba->post_form_id != NULL); ++ g_return_if_fail(fbuddy->uid != 0); ++ ++ buddy_uid = g_strdup_printf("%" G_GINT64_FORMAT, fbuddy->uid); ++ ++ postdata = g_strdup_printf( ++ "type=friend_connect&id=%s&action=reject&" ++ "post_form_id=%s&fb_dtsg=%s&" ++ "post_form_id_source=AsyncRequest&__a=1", ++ buddy_uid, fba->post_form_id, fba->dtsg); ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/reqs.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_hash_table_remove(fba->auth_buddies, buddy_uid); ++ ++ g_free(postdata); ++ g_free(fbuddy); ++ g_free(buddy_uid); ++} ++ ++static void fb_check_friend_request_cb(FacebookAccount *fba, const gchar *data, ++ gsize data_len, gpointer user_data) ++{ ++ const char *uid_pre_text = "class=\"confirm\" id=\"friend_connect_"; ++ const char *name_pre_text = "<td class=\"info\"><a "; ++ const char *msg_pre_text = "<div class=\"personal_msg\"><span>"; ++ gchar *uid; ++ gint64 uid_int; ++ gchar *name; ++ gchar *msg; ++ gchar *msg_plain; ++ FacebookBuddy *buddy; ++ const gchar *search_start = data; ++ ++ g_return_if_fail(data_len > 0); ++ g_return_if_fail(data != NULL); ++ ++ /* loop through the data and look for confirm_friend_add_([0-9]*)" */ ++ while ((search_start = strstr(search_start, uid_pre_text))) ++ { ++ search_start += strlen(uid_pre_text); ++ uid = g_strndup(search_start, ++ strchr(search_start, '"') - search_start); ++ purple_debug_info("facebook", "uid: %s\n", uid); ++ ++ uid_int = atoll(uid); ++ ++ if (g_hash_table_lookup_extended(fba->auth_buddies, ++ uid, NULL, NULL)) ++ { ++ /* we've already notified the user of this friend request */ ++ g_free(uid); ++ continue; ++ } ++ ++ name = strstr(search_start, name_pre_text); ++ if (name != NULL) ++ { ++ name += strlen(name_pre_text); ++ name = strchr(name, '>') + 1; ++ name = g_strndup(name, strchr(name, '<') - name); ++ purple_debug_info("facebook", "name: %s\n", name); ++ } else { ++ name = NULL; ++ } ++ ++ msg = strstr(search_start, msg_pre_text); ++ if (msg != NULL) ++ { ++ msg += strlen(msg_pre_text); ++ msg = g_strndup(msg, strstr(msg, "</span></div>") - msg); ++ msg_plain = purple_markup_strip_html(msg); ++ g_free(msg); ++ purple_debug_info("facebook", "msg: %s\n", msg_plain); ++ } else { ++ msg_plain = NULL; ++ } ++ ++ buddy = g_new0(FacebookBuddy, 1); ++ buddy->fba = fba; ++ buddy->uid = uid_int; ++ purple_account_request_authorization( ++ fba->account, uid, NULL, ++ name, msg_plain, TRUE, ++ fb_auth_accept_cb, fb_auth_reject_cb, buddy); ++ ++ /* Don't display an auth request for this buddy again */ ++ g_hash_table_insert(fba->auth_buddies, uid, NULL); ++ ++ g_free(name); ++ g_free(msg_plain); ++ } ++} ++ ++gboolean fb_check_friend_requests(gpointer data) ++{ ++ FacebookAccount *fba; ++ ++ fba = data; ++ ++ if (purple_account_get_bool( ++ fba->account, "facebook_manage_friends", FALSE)) { ++ fb_post_or_get(fba, FB_METHOD_GET, NULL, "/reqs.php", NULL, ++ fb_check_friend_request_cb, NULL, FALSE); ++ } ++ ++ return TRUE; ++} ++ ++void fb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) ++{ ++ gchar *postdata; ++ FacebookAccount *fba = pc->proto_data; ++ gchar *buddy_tmp; ++ ++ if (!purple_account_get_bool( ++ fba->account, "facebook_manage_friends", FALSE)) { ++ /* ++ * We used to pop up dialogs here but if a user renamed a group, ++ * this would spawn message for each person in the buddy list. Bad! ++ purple_notify_info(fba->pc, _("Friend not added"), ++ _("Adding Facebook friends via Pidgin is disabled"), ++ _("Either add a friend via Facebook.com or edit your account preferences")); ++ */ ++ purple_debug_warning("facebook", "attempted to add %s but was blocked\n", buddy->name); ++ return; ++ } ++ ++ if (atoll(buddy->name) == fba->uid) ++ { ++ purple_account_set_bool(fba->account, ++ "facebook_hide_self", FALSE); ++ return; ++ } ++ ++ buddy_tmp = g_strdup(purple_url_encode(buddy->name)); ++ postdata = g_strdup_printf( ++ "user=%" G_GINT64_FORMAT "&profile_id=%s&message=&" ++ "source=&submit=1&post_form_id=%s&fb_dtsg=%s&" ++ "post_form_id_source=AsyncRequest&__a=1", ++ fba->uid, buddy_tmp, fba->post_form_id, fba->dtsg); ++ g_free(buddy_tmp); ++ ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/profile/connect.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_free(postdata); ++} ++ ++void fb_buddy_delete(PurpleConnection *pc, PurpleBuddy *buddy, ++ PurpleGroup *group) ++{ ++ FacebookAccount *fba = pc->proto_data; ++ gchar *buddy_tmp, *postdata; ++ ++ //This function removes a buddy from our friends list on facebook ++ //and shouldn't really be used ++ if (!purple_account_get_bool(fba->account, "facebook_manage_friends", FALSE)) { ++ purple_debug_warning("facebook", "attempted to add %s but was blocked\n", buddy->name); ++ return; ++ } ++ ++ buddy_tmp = g_strdup(purple_url_encode(buddy->name)); ++ postdata = g_strdup_printf( ++ "uid=%s&post_form_id=%s&fb_dtsg=%s&" ++ "post_form_id_source=AsyncRequest&__a=1", ++ buddy_tmp, fba->post_form_id, fba->dtsg); ++ g_free(buddy_tmp); ++ ++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/profile/removefriend.php", ++ postdata, NULL, NULL, FALSE); ++ ++ g_free(postdata); ++} +diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.h +--- pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.h 1969-12-31 18:00:00.000000000 -0600 ++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.h 2011-03-27 09:15:30.868553000 -0600 +@@ -0,0 +1,30 @@ ++/* ++ * libfacebook ++ * ++ * libfacebook is the property of its developers. See the COPYRIGHT file ++ * for more details. ++ * ++ * 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 3 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, see <http://www.gnu.org/licenses/>. ++ */ ++ ++#ifndef FACEBOOK_MANAGEFRIENDS_H ++#define FACEBOOK_MANAGEFRIENDS_H ++ + |
