pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
2020-02-03, Gary Kramlich
2f836435c33c
closing merged branch
/*
* nmevent.c
*
* Copyright (c) 2004 Novell, Inc. All Rights Reserved.
*
* 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; version 2 of the License.
*
* 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 <glib.h>
#include <string.h>
#include <time.h>
#include "nmevent.h"
#include "nmfield.h"
#include "nmconn.h"
#include "nmuserrecord.h"
#include "nmrtf.h"
#define MAX_UINT32 0xFFFFFFFF
struct _NMEvent
{
/* Event type */
int type;
/* The DN of the event source */
char *source;
/* Timestamp of the event */
guint32 gmt;
/* Conference to associate with the event */
NMConference *conference;
/* User record to associate with the event */
NMUserRecord *user_record;
/* Text associated with the event */
char *text;
/* Reference count for event structure */
int ref_count;
};
/* Handle getdetails response and set the new user record into the event */
static void
_got_user_for_event(NMUser * user, NMERR_T ret_val,
gpointer resp_data, gpointer user_data)
{
NMUserRecord *user_record;
NMEvent *event;
nm_event_cb cb;
if (user == NULL)
return;
user_record = resp_data;
event = user_data;
if (ret_val == NM_OK) {
if (event && user_record) {
/* Add the user record to the event structure
* and make the callback.
*/
nm_event_set_user_record(event, user_record);
if ((cb = nm_user_get_event_callback(user))) {
cb(user, event);
}
}
} else {
/* Cleanup resp_data */
}
/* Clean up */
if (event)
nm_release_event(event);
}
/* Handle getdetails response, set the new user record into the event
* and add the user record as a participant in the conference
*/
static void
_got_user_for_conference(NMUser * user, NMERR_T ret_val,
gpointer resp_data, gpointer user_data)
{
NMUserRecord *user_record = resp_data;
NMEvent *event = user_data;
NMConference *conference;
nm_event_cb cb;
if (user == NULL)
return;
if (event && user_record) {
conference = nm_event_get_conference(event);
if (conference) {
/* Add source of event as recip of the conference */
nm_conference_add_participant(conference, user_record);
/* Add the user record to the event structure
* and make the callback.
*/
nm_event_set_user_record(event, user_record);
if ((cb = nm_user_get_event_callback(user))) {
cb(user, event);
}
}
}
if (event)
nm_release_event(event);
}
/* Read the receive message event, set up the event object, and
* get details for the event source if we don't have them yet.
*/
static NMERR_T
handle_receive_message(NMUser * user, NMEvent * event, gboolean autoreply)
{
NMConference *conference;
NMUserRecord *user_record;
NMConn *conn;
NMERR_T rc = NM_OK;
guint32 size = 0, flags = 0;
char *msg = NULL;
char *nortf = NULL;
char *guid = NULL;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
/* Read the conference flags */
if (error == NULL) {
flags = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
}
/* Read the message text */
if (error == NULL) {
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 100000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
msg = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), msg, size,
NULL, user->cancellable, &error);
purple_debug(PURPLE_DEBUG_INFO, "novell", "Message is %s\n", msg);
/* Auto replies are not in RTF format! */
if (!autoreply) {
NMRtfContext *ctx;
ctx = nm_rtf_init();
nortf = nm_rtf_strip_formatting(ctx, msg);
nm_rtf_deinit(ctx);
purple_debug(PURPLE_DEBUG_INFO, "novell",
"Message without RTF is %s\n", nortf);
/* Store the event data */
nm_event_set_text(event, nortf);
} else {
/* Store the event data */
nm_event_set_text(event, msg);
}
}
}
if (error != NULL) {
if (error->code == G_IO_ERROR_CANCELLED) {
rc = NM_OK;
} else {
rc = NMERR_TCP_READ;
}
g_error_free(error);
return rc;
}
/* Check to see if we already know about the conference */
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_conference_set_flags(conference, flags);
nm_event_set_conference(event, conference);
/* Add a reference to the user record in our event object */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
nm_event_set_user_record(event, user_record);
}
} else {
/* This is a new conference, so create one and add it to our list */
conference = nm_create_conference(guid);
nm_conference_set_flags(conference, flags);
/* Add a reference to the conference in the event */
nm_event_set_conference(event, conference);
/* Add new conference to the conference list */
nm_conference_list_add(user, conference);
/* Check to see if we have details for the event source yet */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
/* We do so add the user record as a recipient of the conference */
nm_conference_add_participant(conference, user_record);
/* Add a reference to the user record in our event object */
nm_event_set_user_record(event, user_record);
} else {
/* Need to go to the server to get details for the user */
rc = nm_send_get_details(user, nm_event_get_source(event),
_got_user_for_conference, event);
if (rc == NM_OK)
rc = -1; /* Not done processing the event yet! */
}
nm_release_conference(conference);
}
g_free(msg);
g_free(nortf);
g_free(guid);
return rc;
}
/* Read the invite event, set up the event object, and
* get details for the event source if we don't have them yet.
*/
static NMERR_T
handle_conference_invite(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
char *msg = NULL;
NMConn *conn;
NMUserRecord *user_record;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
/* Read the the message */
if (error == NULL) {
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 100000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
msg = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), msg, size,
NULL, user->cancellable, &error);
}
}
/* Store the event data */
if (error == NULL) {
NMConference *conference;
nm_event_set_text(event, msg);
conference = nm_conference_list_find(user, guid);
if (conference == NULL) {
conference = nm_create_conference(guid);
/* Add new conference to the list and the event */
nm_conference_list_add(user, conference);
nm_event_set_conference(event, conference);
/* Check to see if we have details for the event source yet */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
/* Add a reference to the user record in our event object */
nm_event_set_user_record(event, user_record);
} else {
/* Need to go to the server to get details for the user */
rc = nm_send_get_details(user, nm_event_get_source(event),
_got_user_for_event, event);
if (rc == NM_OK)
rc = -1; /* Not done processing the event yet! */
}
nm_release_conference(conference);
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(msg);
g_free(guid);
return rc;
}
/* Read the invite notify event, set up the event object, and
* get details for the event source if we don't have them yet.
*/
static NMERR_T
handle_conference_invite_notify(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
NMConn *conn;
NMConference *conference;
NMUserRecord *user_record;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
if (error != NULL) {
if (error->code == G_IO_ERROR_CANCELLED) {
rc = NM_OK;
} else {
rc = NMERR_TCP_READ;
}
g_error_free(error);
return rc;
}
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_event_set_conference(event, conference);
/* Check to see if we have details for the event source yet */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
/* Add a reference to the user record in our event object */
nm_event_set_user_record(event, user_record);
} else {
/* Need to go to the server to get details for the user */
rc = nm_send_get_details(user, nm_event_get_source(event),
_got_user_for_event, event);
if (rc == NM_OK)
rc = -1; /* Not done processing the event yet! */
}
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
g_free(guid);
return rc;
}
/* Read the conference reject event and set up the event object */
static NMERR_T
handle_conference_reject(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
NMConn *conn;
NMConference *conference;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
if (error == NULL) {
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_event_set_conference(event, conference);
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/* Read the conference left event, set up the event object, and
* remove the conference from the list if there are no more
* participants
*/
static NMERR_T
handle_conference_left(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0, flags = 0;
char *guid = NULL;
NMConference *conference;
NMConn *conn;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
/* Read the conference flags */
if (error == NULL) {
flags = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
}
if (error == NULL) {
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_event_set_conference(event, conference);
nm_conference_set_flags(conference, flags);
nm_conference_remove_participant(conference, nm_event_get_source(event));
if (nm_conference_get_participant_count(conference) == 0) {
nm_conference_list_remove(user, conference);
}
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/* Read the conference closed, set up the event object, and
* remove the conference from the list
*/
static NMERR_T
handle_conference_closed(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
NMConference *conference;
NMConn *conn;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
if (error == NULL) {
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_event_set_conference(event, conference);
nm_conference_list_remove(user, conference);
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/* Read the conference joined event, set up the event object, and
* get details for the event source if we don't have them yet.
*/
static NMERR_T
handle_conference_joined(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0, flags = 0;
char *guid = NULL;
NMConn *conn;
NMConference *conference;
NMUserRecord *user_record;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
/* Read the conference flags */
if (error == NULL) {
flags = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
}
if (error == NULL) {
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_conference_set_flags(conference, flags);
nm_event_set_conference(event, conference);
/* Add the new user to the participants list */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
nm_conference_remove_participant(conference,
nm_user_record_get_dn(user_record));
nm_conference_add_participant(conference, user_record);
} else {
/* Need to go to the server to get details for the user */
rc = nm_send_get_details(user, nm_event_get_source(event),
_got_user_for_conference, event);
if (rc == NM_OK)
rc = -1; /* Not done processing the event yet! */
}
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/* Read the typing event and set up the event object */
static NMERR_T
handle_typing(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
NMConference *conference;
NMConn *conn;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size, NULL,
user->cancellable, &error);
}
if (error == NULL) {
conference = nm_conference_list_find(user, guid);
if (conference) {
nm_event_set_conference(event, conference);
} else {
rc = NMERR_CONFERENCE_NOT_FOUND;
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/* Read the event, set up the event object, and update
* the status in the user record (for the event source)
*/
static NMERR_T
handle_status_change(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint16 status;
guint32 size;
char *text = NULL;
NMUserRecord *user_record;
NMConn *conn;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read new status */
status = g_data_input_stream_read_uint16(conn->input, user->cancellable,
&error);
if (error == NULL) {
/* Read the status text */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 10000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
text = g_new0(char, size + 1);
g_input_stream_read_all(G_INPUT_STREAM(conn->input), text, size,
NULL, user->cancellable, &error);
}
}
if (error == NULL) {
nm_event_set_text(event, text);
/* Get a reference to the user record and store the new status */
user_record = nm_find_user_record(user, nm_event_get_source(event));
if (user_record) {
nm_event_set_user_record(event, user_record);
nm_user_record_set_status(user_record, status, text);
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(text);
return rc;
}
/* Read the undeliverable event */
static NMERR_T
handle_undeliverable_status(NMUser * user, NMEvent * event)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
char *guid = NULL;
NMConn *conn;
GError *error = NULL;
conn = nm_user_get_conn(user);
/* Read the conference guid */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (size > 1000) {
g_clear_error(&error);
return NMERR_PROTOCOL;
}
if (error == NULL) {
guid = g_new0(char, size + 1);
rc = g_input_stream_read_all(G_INPUT_STREAM(conn->input), guid, size,
NULL, user->cancellable, &error);
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
g_free(guid);
return rc;
}
/*******************************************************************************
* Event API -- see header file for comments
******************************************************************************/
NMEvent *
nm_create_event(int type, const char *source, guint32 gmt)
{
NMEvent *event = g_new0(NMEvent, 1);
event->type = type;
event->gmt = gmt;
if (source)
event->source = g_strdup(source);
event->ref_count = 1;
return event;
}
void
nm_release_event(NMEvent * event)
{
if (event == NULL) {
return;
}
if (--(event->ref_count) == 0) {
g_free(event->source);
if (event->conference)
nm_release_conference(event->conference);
if (event->user_record)
nm_release_user_record(event->user_record);
g_free(event->text);
g_free(event);
}
}
NMConference *
nm_event_get_conference(NMEvent * event)
{
if (event)
return event->conference;
else
return NULL;
}
void
nm_event_set_conference(NMEvent * event, NMConference * conference)
{
if (event && conference) {
nm_conference_add_ref(conference);
event->conference = conference;
}
}
NMUserRecord *
nm_event_get_user_record(NMEvent * event)
{
if (event)
return event->user_record;
else
return NULL;
}
void
nm_event_set_user_record(NMEvent * event, NMUserRecord * user_record)
{
if (event && user_record) {
nm_user_record_add_ref(user_record);
event->user_record = user_record;
}
}
const char *
nm_event_get_text(NMEvent * event)
{
if (event)
return event->text;
else
return NULL;
}
void
nm_event_set_text(NMEvent * event, const char *text)
{
if (event) {
if (text)
event->text = g_strdup(text);
else
event->text = NULL;
}
}
const char *
nm_event_get_source(NMEvent * event)
{
if (event)
return event->source;
else
return NULL;
}
int
nm_event_get_type(NMEvent * event)
{
if (event)
return event->type;
else
return -1;
}
time_t
nm_event_get_gmt(NMEvent * event)
{
if (event)
return event->gmt;
else
return (time_t)-1;
}
NMERR_T
nm_process_event(NMUser * user, int type)
{
NMERR_T rc = NM_OK;
guint32 size = 0;
NMEvent *event = NULL;
char *source = NULL;
nm_event_cb cb;
NMConn *conn;
GError *error = NULL;
if (user == NULL)
return NMERR_BAD_PARM;
if (type < NMEVT_START || type > NMEVT_STOP)
return NMERR_PROTOCOL;
conn = nm_user_get_conn(user);
/* Read the event source */
size = g_data_input_stream_read_uint32(conn->input, user->cancellable,
&error);
if (error == NULL) {
if (size > 1000000) {
/* Size is larger than our 1MB sanity check. Ignore it. */
rc = NMERR_PROTOCOL;
} else {
source = g_new0(char, size);
rc = g_input_stream_read_all(G_INPUT_STREAM(conn->input), source,
size, NULL, user->cancellable, &error);
}
}
/* Read the event data */
if (error == NULL) {
event = nm_create_event(type, source, time(0));
if (event) {
switch (type) {
case NMEVT_STATUS_CHANGE:
rc = handle_status_change(user, event);
break;
case NMEVT_RECEIVE_MESSAGE:
rc = handle_receive_message(user, event, FALSE);
break;
case NMEVT_RECEIVE_AUTOREPLY:
rc = handle_receive_message(user, event, TRUE);
break;
case NMEVT_USER_TYPING:
case NMEVT_USER_NOT_TYPING:
rc = handle_typing(user, event);
break;
case NMEVT_CONFERENCE_LEFT:
rc = handle_conference_left(user, event);
break;
case NMEVT_CONFERENCE_CLOSED:
rc = handle_conference_closed(user, event);
break;
case NMEVT_CONFERENCE_JOINED:
rc = handle_conference_joined(user, event);
break;
case NMEVT_CONFERENCE_INVITE:
rc = handle_conference_invite(user, event);
break;
case NMEVT_CONFERENCE_REJECT:
rc = handle_conference_reject(user, event);
break;
case NMEVT_CONFERENCE_INVITE_NOTIFY:
rc = handle_conference_invite_notify(user, event);
break;
case NMEVT_UNDELIVERABLE_STATUS:
rc = handle_undeliverable_status(user, event);
break;
case NMEVT_INVALID_RECIPIENT:
/* Nothing else to read, just callback */
break;
case NMEVT_USER_DISCONNECT:
/* Nothing else to read, just callback */
break;
case NMEVT_SERVER_DISCONNECT:
/* Nothing else to read, just callback */
break;
case NMEVT_RECEIVE_FILE:
case NMEVT_CONTACT_ADD:
/* Safely ignored for now */
break;
default:
purple_debug(PURPLE_DEBUG_INFO, "novell",
"Unknown event %d received.\n", type);
rc = NMERR_PROTOCOL;
break;
}
}
} else {
if (error->code != G_IO_ERROR_CANCELLED) {
rc = NMERR_TCP_READ;
}
g_error_free(error);
}
if (rc == (NMERR_T)-1) {
/* -1 means that we are not ready to callback yet. */
rc = NM_OK;
} else if (rc == NM_OK && (cb = nm_user_get_event_callback(user))) {
cb(user, event);
}
if (event) {
nm_release_event(event);
}
/* Cleanup */
g_free(source);
return rc;
}