view libpurple/protocols/msn/soap2.c @ 20527:0034d7e89032

mostly done with soap level parsing, compiles
author Ka-Hing Cheung <khc@hxbc.us>
date Fri, 21 Sep 2007 06:50:40 +0000
parents 915e11fbaeb0
children 06527cc0f79b
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 "debug.h"
#include "xmlnode.h"

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

static void msn_soap_connection2_destroy_foreach_cb(gpointer item, gpointer data);
static void msn_soap_connection2_cleanup(MsnSoapConnection2 *conn);
static gboolean msn_soap_connection2_run(gpointer data);


MsnSoapConnection2 *
msn_soap_connection2_new(MsnSession *session)
{
	MsnSoapConnection2 *conn = g_new0(MsnSoapConnection2, 1);
	conn->session = session;
	conn->queue = g_queue_new();
	return conn;
}

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

	conn->connected = TRUE;

	if (conn->idle_handle == 0)
		conn->idle_handle = g_idle_add(msn_soap_connection2_run, conn);
}

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

	msn_soap_connection2_cleanup(conn);
}

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

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

	if ((c = strchr(url, '/')) != NULL) {
		g_free(conn->request->host);
		g_free(conn->request->path);

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

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

		msn_soap_connection2_post(conn, conn->request,
			conn->request->cb, conn->request->data);

		return TRUE;
	}

	return FALSE;
}

static gboolean
msn_soap_handle_body(MsnSoapConnection2 *conn, MsnSoapResponse *response)
{
	xmlnode *node = response->message->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(fault, "redirectUrl");

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

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

					return TRUE;
				}
			}

			conn->request->cb(conn, conn->request, conn->response,
				conn->request->data);

			return TRUE;
		}
	}

	return FALSE;
}

static void
msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond)
{
    MsnSoapConnection2 *conn = data;
	MsnSoapMessage *message;
	int count;
	char buf[8192];
	char *linebreak;

	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_connection2_cleanup(conn);
		return;
	}

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

	message = conn->response->message;

	if (message->buf == NULL) {
		message->buf = g_memdup(buf, count);
		message->buf_len = count;
	} else {
		message->buf = g_realloc(message->buf, message->buf_len + count);
		memcpy(message->buf + message->buf_len,	buf, count);
		message->buf_len += count;
	}

	if (conn->response->seen_newline) {
		if (message->buf_len - message->buf_count >=
			conn->response->body_len) {
			xmlnode *node = xmlnode_from_str(
				message->buf + message->buf_count, conn->response->body_len);

			if (node == NULL) {
				purple_debug_info("soap", "Malformed SOAP response: %s\n",
					message->buf + message->buf_count);
			} else {
				conn->response->message->xml = node;
				msn_soap_handle_body(conn, conn->response);
			}
		}

		return;
	}

	while ((linebreak = strstr(message->buf + message->buf_count, "\r\n"))
		!= NULL) {
		message->buf_count = linebreak - message->buf + 2;

		if (conn->response->code == -1) {
			if (sscanf(message->buf + message->buf_count, "HTTP/1.1 %d",
					&conn->response->code) != 1) {
				/* something horribly wrong */
				msn_soap_connection2_destroy_foreach_cb(conn->request, conn);
				conn->request = NULL;
			} else if (conn->response->code == 503) {
				msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
			}
		} else if (message->buf + message->buf_count == linebreak) {
			/* blank line */
			conn->response->seen_newline = TRUE;
		} else {
			char *sep = strstr(message->buf + message->buf_count, ": ");
			char *key = message->buf + message->buf_count;
			char *value = sep + 2;

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

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

				if (msn_soap_handle_redirect(conn, value)) {

				} else if (conn->request->cb) {
					conn->request->cb(conn, conn->request, NULL,
						conn->request->data);
				}
			} 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);
			} else if (strcmp(key, "Content-Length") == 0) {
				conn->response->body_len = atoi(value);
			}
		}
	}
}

static void
msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond)
{
	MsnSoapConnection2 *conn = data;
	MsnSoapMessage *message = conn->request->message;
	int written;

	g_return_if_fail(cond == PURPLE_INPUT_WRITE);

	written = purple_ssl_write(conn->ssl, message->buf + message->buf_count,
		message->buf_len - message->buf_count);

	if (written < 0 && errno == EAGAIN)
		return;
	else if (written <= 0) {
		msn_soap_connection2_cleanup(conn);
		return;
	}

	message->buf_count += written;

	if (message->buf_count < message->buf_len)
		return;

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

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

	if (req) {
		if (conn->ssl) {
			if (strcmp(conn->ssl->host, req->host) != 0 ||
				strcmp(conn->path, req->path) != 0) {
				purple_input_remove(conn->io_handle);
				conn->io_handle = 0;
				purple_ssl_close(conn->ssl);
				conn->ssl = NULL;
				g_free(conn->path);
				conn->path = NULL;
			}
		}

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

			g_queue_pop_head(conn->queue);

			g_string_append_printf(str,
				"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->action,
				conn->session->passport_info.mspauth,
				req->host, len);

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

			g_string_append(str, "\r\n");
			g_string_append(str, body);

			message->buf_len = str->len;
			message->buf = g_string_free(str, FALSE);
			message->buf_count = 0;
			conn->request = req;

			conn->io_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);
		}		
	}

	conn->idle_handle = 0;
	return FALSE;
}

void
msn_soap_connection2_post(MsnSoapConnection2 *conn, MsnSoapRequest *req,
	MsnSoapCallback cb, gpointer data)
{
	req->cb = cb;
	req->data = data;

	g_queue_push_tail(conn->queue, req);

	if (conn->idle_handle == 0)
		conn->idle_handle = g_idle_add(msn_soap_connection2_run, conn);
}

static void
msn_soap_connection2_destroy_foreach_cb(gpointer item, gpointer data)
{
	MsnSoapRequest *req = item;
	MsnSoapConnection2 *conn = data;

	if (req->cb)
		req->cb(conn, req, NULL, req->data);

	msn_soap_request2_destroy(req);
}

static void
msn_soap_connection2_cleanup(MsnSoapConnection2 *conn)
{
	g_queue_foreach(conn->queue, msn_soap_connection2_destroy_foreach_cb, conn);
	if (conn->request) {
		msn_soap_connection2_destroy_foreach_cb(conn->request, conn);
		conn->request = NULL;
	}

	purple_input_remove(conn->io_handle);
	conn->io_handle = 0;
	g_source_remove(conn->idle_handle);
	conn->idle_handle = 0;

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

	while (g_queue_pop_head(conn->queue) != NULL);

	g_free(conn->path);
}

void
msn_soap_connection2_destroy(MsnSoapConnection2 *conn)
{
	msn_soap_connection2_cleanup(conn);
	g_queue_free(conn->queue);
	g_free(conn);
}

MsnSoapMessage *
msn_soap_message_new()
{
	MsnSoapMessage *req = g_new0(MsnSoapMessage, 1);

	return req;
}

void
msn_soap_message_destroy(MsnSoapMessage *message)
{
	g_slist_foreach(message->headers, (GFunc)g_free, NULL);
	g_free(message->buf);
	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);
}

MsnSoapRequest *
msn_soap_request2_new(const char *host, const char *path, const char *action)
{
	MsnSoapRequest *req = g_new0(MsnSoapRequest, 1);

	req->host = g_strdup(host);
	req->path = g_strdup(path);
	req->action = g_strdup(action);

	return req;
}

void
msn_soap_request2_destroy(MsnSoapRequest *req)
{
	g_free(req->action);
	g_free(req->host);
	g_free(req->path);
	msn_soap_message_destroy(req->message);
	g_free(req);
}

MsnSoapResponse *
msn_soap_response_new(void)
{
	MsnSoapResponse *resp = g_new0(MsnSoapResponse, 1);

	return resp;
}

void
msn_soap_response_destroy(MsnSoapResponse *resp)
{
	msn_soap_message_destroy(resp->message);
	resp->code = -1;
	resp->body_len = -1;
	g_free(resp);
}