libpurple/purple-socket.c

Fri, 04 Apr 2014 20:30:07 +0200

author
Tomasz Wasilczyk <twasilczyk@pidgin.im>
date
Fri, 04 Apr 2014 20:30:07 +0200
changeset 35753
222e69060b4a
parent 35454
cf2a24d01503
child 36067
df9f5de00ea2
permissions
-rw-r--r--

Remote smileys: display them even on the first time

/* 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 "purple-socket.h"

#include "internal.h"

#include "debug.h"
#include "proxy.h"
#include "sslconn.h"

typedef enum {
	PURPLE_SOCKET_STATE_DISCONNECTED = 0,
	PURPLE_SOCKET_STATE_CONNECTING,
	PURPLE_SOCKET_STATE_CONNECTED,
	PURPLE_SOCKET_STATE_ERROR
} PurpleSocketState;

struct _PurpleSocket
{
	PurpleConnection *gc;
	gchar *host;
	int port;
	gboolean is_tls;
	GHashTable *data;

	PurpleSocketState state;

	PurpleSslConnection *tls_connection;
	PurpleProxyConnectData *raw_connection;
	int fd;
	guint inpa;

	PurpleSocketConnectCb cb;
	gpointer cb_data;
};

PurpleSocket *
purple_socket_new(PurpleConnection *gc)
{
	PurpleSocket *ps = g_new0(PurpleSocket, 1);

	ps->gc = gc;
	ps->fd = -1;
	ps->port = -1;
	ps->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);

	return ps;
}

PurpleConnection *
purple_socket_get_connection(PurpleSocket *ps)
{
	g_return_val_if_fail(ps != NULL, NULL);

	return ps->gc;
}

static gboolean
purple_socket_check_state(PurpleSocket *ps, PurpleSocketState wanted_state)
{
	g_return_val_if_fail(ps != NULL, FALSE);

	if (ps->state == wanted_state)
		return TRUE;

	purple_debug_error("socket", "invalid state: %d (should be: %d)",
		ps->state, wanted_state);
	ps->state = PURPLE_SOCKET_STATE_ERROR;
	return FALSE;
}

void
purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls)
{
	g_return_if_fail(ps != NULL);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
		return;

	ps->is_tls = is_tls;
}

void
purple_socket_set_host(PurpleSocket *ps, const gchar *host)
{
	g_return_if_fail(ps != NULL);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
		return;

	g_free(ps->host);
	ps->host = g_strdup(host);
}

void
purple_socket_set_port(PurpleSocket *ps, int port)
{
	g_return_if_fail(ps != NULL);
	g_return_if_fail(port >= 0);
	g_return_if_fail(port <= 65535);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
		return;

	ps->port = port;
}

static void
_purple_socket_connected_raw(gpointer _ps, gint fd, const gchar *error_message)
{
	PurpleSocket *ps = _ps;

	ps->raw_connection = NULL;

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) {
		if (fd > 0)
			close(fd);
		ps->cb(ps, _("Invalid socket state"), ps->cb_data);
		return;
	}

	if (fd <= 0 || error_message != NULL) {
		if (error_message == NULL)
			error_message = _("Unknown error");
		ps->fd = -1;
		ps->state = PURPLE_SOCKET_STATE_ERROR;
		ps->cb(ps, error_message, ps->cb_data);
		return;
	}

	ps->state = PURPLE_SOCKET_STATE_CONNECTED;
	ps->fd = fd;
	ps->cb(ps, NULL, ps->cb_data);
}

static void
_purple_socket_connected_tls(gpointer _ps, PurpleSslConnection *tls_connection,
	PurpleInputCondition cond)
{
	PurpleSocket *ps = _ps;

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) {
		purple_ssl_close(tls_connection);
		ps->tls_connection = NULL;
		ps->cb(ps, _("Invalid socket state"), ps->cb_data);
		return;
	}

	if (ps->tls_connection->fd <= 0) {
		ps->state = PURPLE_SOCKET_STATE_ERROR;
		purple_ssl_close(tls_connection);
		ps->tls_connection = NULL;
		ps->cb(ps, _("Invalid file descriptor"), ps->cb_data);
		return;
	}

	ps->state = PURPLE_SOCKET_STATE_CONNECTED;
	ps->fd = ps->tls_connection->fd;
	ps->cb(ps, NULL, ps->cb_data);
}

static void
_purple_socket_connected_tls_error(PurpleSslConnection *ssl_connection,
	PurpleSslErrorType error, gpointer _ps)
{
	PurpleSocket *ps = _ps;

	ps->state = PURPLE_SOCKET_STATE_ERROR;
	ps->tls_connection = NULL;
	ps->cb(ps, purple_ssl_strerror(error), ps->cb_data);
}

gboolean
purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb,
	gpointer user_data)
{
	PurpleAccount *account = NULL;

	g_return_val_if_fail(ps != NULL, FALSE);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
		return FALSE;
	ps->state = PURPLE_SOCKET_STATE_CONNECTING;

	if (ps->host == NULL || ps->port < 0) {
		purple_debug_error("socket", "Host or port is not specified");
		ps->state = PURPLE_SOCKET_STATE_ERROR;
		return FALSE;
	}

	if (ps->gc != NULL)
		account = purple_connection_get_account(ps->gc);

	ps->cb = cb;
	ps->cb_data = user_data;

	if (ps->is_tls) {
		if (!purple_ssl_is_supported()) {
			purple_debug_error("socket", "TLS is not supported");
			ps->state = PURPLE_SOCKET_STATE_ERROR;
			return FALSE;
		}

		ps->tls_connection = purple_ssl_connect(account, ps->host,
			ps->port, _purple_socket_connected_tls,
			_purple_socket_connected_tls_error, ps);
	} else {
		ps->raw_connection = purple_proxy_connect(ps->gc, account,
			ps->host, ps->port, _purple_socket_connected_raw, ps);
	}

	if (ps->tls_connection == NULL &&
		ps->raw_connection == NULL)
	{
		ps->state = PURPLE_SOCKET_STATE_ERROR;
		return FALSE;
	}

	return TRUE;
}

gssize
purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len)
{
	g_return_val_if_fail(ps != NULL, -1);
	g_return_val_if_fail(buf != NULL, -1);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
		return -1;

	if (ps->is_tls)
		return purple_ssl_read(ps->tls_connection, buf, len);
	else
		return read(ps->fd, buf, len);
}

gssize
purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len)
{
	g_return_val_if_fail(ps != NULL, -1);
	g_return_val_if_fail(buf != NULL, -1);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
		return -1;

	if (ps->is_tls)
		return purple_ssl_write(ps->tls_connection, buf, len);
	else
		return write(ps->fd, buf, len);
}

void
purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond,
	PurpleInputFunction func, gpointer user_data)
{
	g_return_if_fail(ps != NULL);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
		return;

	if (ps->inpa > 0)
		purple_input_remove(ps->inpa);
	ps->inpa = 0;

	g_return_if_fail(ps->fd > 0);

	if (func != NULL)
		ps->inpa = purple_input_add(ps->fd, cond, func, user_data);
}

int
purple_socket_get_fd(PurpleSocket *ps)
{
	g_return_val_if_fail(ps != NULL, -1);

	if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
		return -1;

	g_return_val_if_fail(ps->fd > 0, -1);

	return ps->fd;
}

void
purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data)
{
	g_return_if_fail(ps != NULL);
	g_return_if_fail(key != NULL);

	if (data == NULL)
		g_hash_table_remove(ps->data, key);
	else
		g_hash_table_insert(ps->data, g_strdup(key), data);
}

gpointer
purple_socket_get_data(PurpleSocket *ps, const gchar *key)
{
	g_return_val_if_fail(ps != NULL, NULL);
	g_return_val_if_fail(key != NULL, NULL);

	return g_hash_table_lookup(ps->data, key);
}

void
purple_socket_destroy(PurpleSocket *ps)
{
	if (ps == NULL)
		return;

	g_free(ps->host);

	if (ps->inpa > 0)
		purple_input_remove(ps->inpa);
	ps->inpa = 0;

	if (ps->tls_connection != NULL) {
		purple_ssl_close(ps->tls_connection);
		ps->fd = -1;
	}

	if (ps->raw_connection != NULL)
		purple_proxy_connect_cancel(ps->raw_connection);

	if (ps->fd > 0)
		close(ps->fd);

	g_hash_table_destroy(ps->data);
	g_free(ps);
}

mercurial