view libpurple/protocols/msn/soap2.c @ 20528:06527cc0f79b

removes public handle to soap connection, instead a connection is looked up from session
author Ka-Hing Cheung <khc@hxbc.us>
date Mon, 24 Sep 2007 05:29:07 +0000
parents 0034d7e89032
children 1180920ffcec
line wrap: on
line source

/**
 * @file soap2.c
 * 	C file for SOAP connection related process
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "soap2.h"

#include "session.h"

#include "debug.h"
#include "xmlnode.h"

#include <glib.h>
#include <error.h>

static GHashTable *conn_table = NULL;

typedef struct _MsnSoapRequest {
	char *path;
	MsnSoapMessage *message;
	MsnSoapCallback cb;
	gpointer cb_data;
} MsnSoapRequest;

typedef struct _MsnSoapConnection {
	MsnSession *session;
	char *host;

	PurpleSslConnection *ssl;
	gboolean connected;

	guint event_handle;
	GString *buf;
	gsize handled_len;
	gsize body_len;
	int response_code;
	gboolean headers_done;
	MsnSoapMessage *message;

	GQueue *queue;
	MsnSoapRequest *current_request;
} MsnSoapConnection;

static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data);
static gboolean msn_soap_connection_run(gpointer data);

static MsnSoapConnection *msn_soap_connection_new(MsnSession *session,
	const char *host);
static void msn_soap_connection_handle_next(MsnSoapConnection *conn);
static void msn_soap_connection_destroy(MsnSoapConnection *conn);

static void msn_soap_message_send_internal(MsnSession *session,
	MsnSoapMessage *message, const char *host, const char *path,
	MsnSoapCallback cb, gpointer cb_data, gboolean first);

static void msn_soap_request_destroy(MsnSoapRequest *req);


static MsnSoapConnection *
msn_soap_get_connection(MsnSession *session, const char *host)
{
	MsnSoapConnection *conn = NULL;

	if (session->soap_table) {
		conn = g_hash_table_lookup(conn_table, host);
	} else {
		session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal,
			NULL, (GDestroyNotify)msn_soap_connection_destroy);
	}

	if (conn == NULL) {
		conn = msn_soap_connection_new(session, host);
		g_hash_table_insert(session->soap_table, conn->host, conn);
	}

	return conn;
}

static MsnSoapConnection *
msn_soap_connection_new(MsnSession *session, const char *host)
{
	MsnSoapConnection *conn = g_new0(MsnSoapConnection, 1);
	conn->session = session;
	conn->host = g_strdup(host);
	conn->queue = g_queue_new();
	return conn;
}

static void
msn_soap_connected_cb(gpointer data, PurpleSslConnection *ssl,
		PurpleInputCondition cond)
{
	MsnSoapConnection *conn = data;

	conn->connected = TRUE;

	if (conn->event_handle == 0)
		conn->event_handle = g_idle_add(msn_soap_connection_run, conn);
}

static void
msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error,
		gpointer data)
{
	MsnSoapConnection *conn = data;

	g_hash_table_remove(conn->session->soap_table, conn->host);
}

static gboolean
msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url)
{
	char *c;

	/* Skip the http:// */
	if ((c = strchr(url, '/')) != NULL)
		url += 2;

	if ((c = strchr(url, '/')) != NULL) {
		char *host, *path;

		host = g_strndup(url, c - url);
		path = g_strdup(c);

		msn_soap_message_send_internal(conn->session,
			conn->current_request->message,	host, path,
			conn->current_request->cb, conn->current_request->cb_data, TRUE);

		msn_soap_request_destroy(conn->current_request);
		conn->current_request = NULL;

		g_free(host);
		g_free(path);

		return TRUE;
	}

	return FALSE;
}

static gboolean
msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response)
{
	xmlnode *node = response->xml;

	if (strcmp(node->name, "Envelop") == 0 &&
		node->child && strcmp(node->child->name, "Header") == 0 &&
		node->child->next) {
		xmlnode *body = node->child->next;

		if (strcmp(body->name, "Fault")) {
			xmlnode *fault = xmlnode_get_child(body, "faultcode");

			if (fault != NULL) {
				if (strcmp(fault->data, "psf:Redirect") == 0) {
					xmlnode *url = xmlnode_get_child(body, "redirectUrl");

					if (url) {
						msn_soap_handle_redirect(conn, url->data);
					}

					return TRUE;
				} else if (strcmp(fault->data, "wsse:FailedAuthentication")) {
					xmlnode *reason = xmlnode_get_child(body, "faultstring");

					msn_session_set_error(conn->session, MSN_ERROR_AUTH,
						reason ? reason->data : NULL);

					return FALSE;
				}
			}

			conn->current_request->cb(conn->current_request->message, response,
				conn->current_request->cb_data);
			msn_soap_request_destroy(conn->current_request);
			conn->current_request = NULL;
		}
	}

	return TRUE;
}

static void
msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond)
{
    MsnSoapConnection *conn = data;
	int count;
	char buf[8192];
	char *linebreak;
	char *cursor;
	gboolean handled = FALSE;

	g_return_if_fail(cond == PURPLE_INPUT_READ);

	count = purple_ssl_read(conn->ssl, buf, sizeof(buf));
	if (count < 0 && errno == EAGAIN)
		return;
	else if (count <= 0) {
		msn_soap_connection_handle_next(conn);
		purple_ssl_close(conn->ssl);
		conn->ssl = NULL;
		return;
	}

	if (conn->message == NULL) {
		conn->message = msn_soap_message_new(NULL, NULL);
	}

	if (conn->buf == NULL) {
		conn->buf = g_string_new_len(buf, count);
	} else {
		g_string_append_len(conn->buf, buf, count);
	}

	cursor = conn->buf->str + conn->handled_len;

	if (conn->headers_done) {
		if (conn->buf->len - conn->handled_len >= 
			conn->body_len) {
			xmlnode *node = xmlnode_from_str(cursor, conn->body_len);

			if (node == NULL) {
				purple_debug_info("soap", "Malformed SOAP response: %s\n",
					cursor);
			} else {
				MsnSoapMessage *message = conn->message;
				conn->message = NULL;
				message->xml = node;

				if (!msn_soap_handle_body(conn, message))
					return;
			}

			msn_soap_connection_handle_next(conn);
		}

		return;
	}

	while ((linebreak = strstr(cursor, "\r\n"))	!= NULL) {
		conn->handled_len = linebreak - conn->buf->str + 2;

		if (conn->response_code == 0) {
			if (sscanf(cursor, "HTTP/1.1 %d", &conn->response_code) != 1) {
				/* something horribly wrong */
				msn_soap_connection_handle_next(conn);
				purple_ssl_close(conn->ssl);
				conn->ssl = NULL;
				handled = TRUE;
				break;
			} else if (conn->response_code == 503) {
				msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
				return;
			}
		} else if (cursor == linebreak) {
			/* blank line */
			conn->headers_done = TRUE;
		} else {
			char *sep = strstr(cursor, ": ");
			char *key = cursor;
			char *value = sep + 2;

			*sep = '\0';
			*linebreak = '\0';
			msn_soap_message_add_header(conn->message, key, value);

			if ((conn->response_code == 301 || conn->response_code == 300)
				&& strcmp(key, "Location") == 0) {

				if (!msn_soap_handle_redirect(conn, value) &&
					conn->current_request->cb) {
					conn->current_request->cb(conn->current_request->message,
						NULL, conn->current_request->cb_data);
				}

				handled = TRUE;
				break;
			} else if (conn->response_code == 401 &&
				strcmp(key, "WWW-Authenticate") == 0) {
				char *error = strstr(value, "cbtxt=");

				if (error) {
					error += strlen("cbtxt=");
				}

				msn_session_set_error(conn->session, MSN_ERROR_AUTH,
					error ? purple_url_decode(error) : NULL);

				return;
			} else if (strcmp(key, "Content-Length") == 0) {
				conn->body_len = atoi(value);
			}
		}
	}

	if (handled) {
		msn_soap_connection_handle_next(conn);
	}
}

static void
msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond)
{
	MsnSoapConnection *conn = data;
	int written;

	g_return_if_fail(cond == PURPLE_INPUT_WRITE);

	written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len,
		conn->buf->len - conn->handled_len);

	if (written < 0 && errno == EAGAIN)
		return;
	else if (written <= 0) {
		purple_ssl_close(conn->ssl);
		conn->ssl = NULL;
		msn_soap_connection_handle_next(conn);
		return;
	}

	conn->handled_len += written;

	if (conn->handled_len < conn->buf->len)
		return;

	/* we are done! */
	purple_input_remove(conn->event_handle);
	conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ,
		msn_soap_read_cb, conn);
}

static gboolean
msn_soap_connection_run(gpointer data)
{
	MsnSoapConnection *conn = data;
	MsnSoapRequest *req = g_queue_peek_head(conn->queue);

	conn->event_handle = 0;

	if (req) {
		if (conn->ssl == NULL) {
			conn->ssl = purple_ssl_connect(conn->session->account, conn->host,
				443, msn_soap_connected_cb, msn_soap_error_cb, conn);
		} else if (conn->connected) {
			int len = -1;
			char *body = xmlnode_to_str(req->message->xml, &len);
			GSList *iter;

			g_queue_pop_head(conn->queue);

			conn->buf = g_string_new("");

			g_string_append_printf(conn->buf,
				"POST %s HTTP/1.1\r\n"
				"SOAPAction: %s\r\n"
				"Content-Type:text/xml; charset=utf-8\r\n"
				"Cookie: MSPAuth=%s\r\n"
				"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"
				"Accept: */*\r\n"
				"Host: %s\r\n"
				"Content-Length: %d\r\n"
				"Connection: Keep-Alive\r\n"
				"Cache-Control: no-cache\r\n",
				req->path, req->message->action,
				conn->session->passport_info.mspauth,
				conn->host, len);

			for (iter = req->message->headers; iter; iter = iter->next) {
				g_string_append(conn->buf, (char *)iter->data);
				g_string_append(conn->buf, "\r\n");
			}

			g_string_append(conn->buf, "\r\n");
			g_string_append(conn->buf, body);

			conn->handled_len = 0;
			conn->current_request = req;

			conn->event_handle = purple_input_add(conn->ssl->fd,
				PURPLE_INPUT_WRITE, msn_soap_write_cb, conn);
			msn_soap_write_cb(conn, conn->ssl->fd, PURPLE_INPUT_WRITE);
		}		
	}

	return FALSE;
}

void
msn_soap_message_send(MsnSession *session, MsnSoapMessage *message,
	const char *host, const char *path,
	MsnSoapCallback cb, gpointer cb_data)
{
	msn_soap_message_send_internal(session, message, host, path, cb, cb_data,
		FALSE);
}

static void
msn_soap_message_send_internal(MsnSession *session,
	MsnSoapMessage *message, const char *host, const char *path,
	MsnSoapCallback cb, gpointer cb_data, gboolean first)
{
	MsnSoapConnection *conn = msn_soap_get_connection(session, host);
	MsnSoapRequest *req = g_new0(MsnSoapRequest, 1);

	req->path = g_strdup(path);
	req->message = message;
	req->cb = cb;
	req->cb_data = cb_data;

	if (first) {
		g_queue_push_head(conn->queue, req);
	} else {
		g_queue_push_tail(conn->queue, req);
	}

	if (conn->event_handle == 0)
		conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,
			conn);
}

static void
msn_soap_connection_handle_next(MsnSoapConnection *conn)
{
	if (conn->current_request) {
		msn_soap_connection_destroy_foreach_cb(conn->current_request, conn);
		conn->current_request = NULL;
	}

	purple_input_remove(conn->event_handle);
	conn->event_handle = 0;

	msn_soap_message_destroy(conn->message);
	g_string_free(conn->buf, TRUE);

	conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,	conn);
}

static void
msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data)
{
	MsnSoapRequest *req = item;

	if (req->cb)
		req->cb(req->message, NULL, req->cb_data);

	msn_soap_request_destroy(req);
}

static void
msn_soap_connection_destroy(MsnSoapConnection *conn)
{
	if (conn->current_request) {
		msn_soap_connection_destroy_foreach_cb(conn->current_request, conn);
		conn->current_request = NULL;
	}

	g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn);

	purple_input_remove(conn->event_handle);
	conn->event_handle = 0;

	if (conn->ssl) {
		purple_ssl_close(conn->ssl);
		conn->ssl = NULL;
	}

	msn_soap_message_destroy(conn->message);
	g_string_free(conn->buf, TRUE);
	g_free(conn->host);
	g_queue_free(conn->queue);
	g_free(conn);
}

MsnSoapMessage *
msn_soap_message_new(const char *action, xmlnode *xml)
{
	MsnSoapMessage *message = g_new0(MsnSoapMessage, 1);

	message->action = g_strdup(action);
	message->xml = xml;

	return message;
}

void
msn_soap_message_destroy(MsnSoapMessage *message)
{
	if (message) {
		g_slist_foreach(message->headers, (GFunc)g_free, NULL);
		g_slist_free(message->headers);
		g_free(message->action);
		xmlnode_free(message->xml);
		g_free(message);
	}
}

void
msn_soap_message_add_header(MsnSoapMessage *req,
		const char *name, const char *value)
{
	char *header = g_strdup_printf("%s: %s\r\n", name, value);

	req->headers = g_slist_prepend(req->headers, header);
}

static void
msn_soap_request_destroy(MsnSoapRequest *req)
{
	g_free(req->path);
	msn_soap_message_destroy(req->message);
	g_free(req);
}