view libpurple/protocols/msn/soap.c @ 20490:2d8999540239

mostly fixed offline messaging, still 2 bugs: 1) looks like the first OIM is sent twice 2) looks like after a couple OIM we can't sent them anymore I haven't tried to reproduce these, feel free to try! also fixed a segfault of quiting when there's an ongoing soap request
author Ka-Hing Cheung <khc@hxbc.us>
date Thu, 06 Sep 2007 06:58:29 +0000
parents 321d25932f5e
children f062793410b5
line wrap: on
line source

/**
 * @file soap.c 
 * 	SOAP connection related process
 *	Author
 * 		MaYuan<mayuan2006@gmail.com>
 * 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 "msn.h"
#include "soap.h"


/*local function prototype*/
void msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step);

/*setup the soap process step*/
void
msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step)
{
	soapconn->step = step;
}

//msn_soap_new(MsnSession *session,gpointer data,int sslconn)
/*new a soap connection*/
MsnSoapConn *
msn_soap_new(MsnSession *session,gpointer data,int sslconn)
{
	MsnSoapConn *soapconn;

	soapconn = g_new0(MsnSoapConn, 1);
	soapconn->session = session;
	soapconn->parent = data;
	soapconn->ssl_conn = sslconn;

	soapconn->gsc = NULL;
	soapconn->input_handler = -1;
	soapconn->output_handler = -1;

	msn_soap_set_process_step(soapconn,MSN_SOAP_UNCONNECTED);
	soapconn->soap_queue = g_queue_new();
	return soapconn;
}

/*ssl soap connect callback*/
void
msn_soap_connect_cb(gpointer data, PurpleSslConnection *gsc,
				 PurpleInputCondition cond)
{
	MsnSoapConn * soapconn;
	MsnSession *session;

	purple_debug_misc("MSN SOAP","SOAP server connection established!\n");

	soapconn = data;
	g_return_if_fail(soapconn != NULL);

	session = soapconn->session;
	g_return_if_fail(session != NULL);

	soapconn->gsc = gsc;

	/*connection callback*/
	if(soapconn->connect_cb != NULL){
		soapconn->connect_cb(data,gsc,cond);
	}

	msn_soap_set_process_step(soapconn,MSN_SOAP_CONNECTED);
	/*we do the SOAP request here*/
	msn_soap_post_head_request(soapconn);
}

/*ssl soap error callback*/
static void
msn_soap_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, void *data)
{	
	MsnSoapConn * soapconn = data;

	g_return_if_fail(data != NULL);
	purple_debug_warning("MSN SOAP","Soap connection error!\n");
	msn_soap_set_process_step(soapconn, MSN_SOAP_UNCONNECTED);

	/*error callback*/
	if(soapconn->error_cb != NULL){
		soapconn->error_cb(gsc,error,data);
	}
}

/*init the soap connection*/
void
msn_soap_init(MsnSoapConn *soapconn,char * host,int ssl,
				PurpleSslInputFunction	connect_cb,
				PurpleSslErrorFunction	error_cb)
{
	purple_debug_misc("MSN SOAP","Initializing SOAP connection\n");
	soapconn->login_host = g_strdup(host);
	soapconn->ssl_conn = ssl;
	soapconn->connect_cb = connect_cb;
	soapconn->error_cb = error_cb;
}

/*connect the soap connection*/
void
msn_soap_connect(MsnSoapConn *soapconn)
{
	if(soapconn->ssl_conn){
		purple_ssl_connect(soapconn->session->account, soapconn->login_host,
				PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb, msn_soap_error_cb,
				soapconn);
	}else{
	}
	msn_soap_set_process_step(soapconn,MSN_SOAP_CONNECTING);
}

/*close the soap connection*/
void
msn_soap_close(MsnSoapConn *soapconn)
{
	if(soapconn->ssl_conn){
		if(soapconn->gsc != NULL){
			purple_ssl_close(soapconn->gsc);
			soapconn->gsc = NULL;
		}
	}else{
	}
	msn_soap_set_process_step(soapconn,MSN_SOAP_UNCONNECTED);
}

/*clean the unhandled SOAP request*/
void
msn_soap_clean_unhandled_request(MsnSoapConn *soapconn)
{
	MsnSoapReq *request;

	g_return_if_fail(soapconn != NULL);

	while ((request = g_queue_pop_head(soapconn->soap_queue)) != NULL){
		msn_soap_request_free(request);
	}
}

/*destroy the soap connection*/
void
msn_soap_destroy(MsnSoapConn *soapconn)
{
	if(soapconn->login_host)
		g_free(soapconn->login_host);

	if(soapconn->login_path)
		g_free(soapconn->login_path);

	/*remove the write handler*/
	if (soapconn->output_handler > 0){
		purple_input_remove(soapconn->output_handler);
	}
	/*remove the read handler*/
	if (soapconn->input_handler > 0){
		purple_input_remove(soapconn->input_handler);
		soapconn->input_handler = -1;
	}
	msn_soap_free_read_buf(soapconn);
	msn_soap_free_write_buf(soapconn);

	/*close ssl connection*/
	msn_soap_close(soapconn);

	/*process the unhandled soap request*/
	msn_soap_clean_unhandled_request(soapconn);

	g_queue_free(soapconn->soap_queue);
	g_free(soapconn);
}

/*check the soap is connected?
 * if connected return 1
 */
int
msn_soap_connected(MsnSoapConn *soapconn)
{
	if(soapconn->ssl_conn){
		return (soapconn->gsc == NULL? 0 : 1);
	}
	return(soapconn->fd>0? 1 : 0);
}

/*read and append the content to the buffer*/
static gssize
msn_soap_read(MsnSoapConn *soapconn)
{
	gssize len, requested_len;
	char temp_buf[MSN_SOAP_READ_BUFF_SIZE];
	
	if ( soapconn->need_to_read == 0 || soapconn->need_to_read > MSN_SOAP_READ_BUFF_SIZE) {
		requested_len = MSN_SOAP_READ_BUFF_SIZE;
	}
	else {
		requested_len = soapconn->need_to_read;
	}

	if ( soapconn->ssl_conn ) {
		len = purple_ssl_read(soapconn->gsc, temp_buf, requested_len);
	} else {
		len = read(soapconn->fd, temp_buf, requested_len);
	}

	
	if ( len <= 0 ) {
		switch (errno) {

			case 0:
			case EBADF: /* we are sometimes getting this in Windows */
			case EAGAIN: return len;

			default : purple_debug_error("MSN SOAP", "Read error!"
						"read len: %d, error = %s\n",
						len, strerror(errno));
				  purple_input_remove(soapconn->input_handler);
				  soapconn->input_handler = -1;
				  g_free(soapconn->read_buf);
				  soapconn->read_buf = NULL;
				  soapconn->read_len = 0;
				  /* TODO: error handling */
				  return len;
		}
	}
	else {
		soapconn->read_buf = g_realloc(soapconn->read_buf,
						soapconn->read_len + len + 1);
		if ( soapconn->read_buf != NULL ) {
			memcpy(soapconn->read_buf + soapconn->read_len, temp_buf, len);
			soapconn->read_len += len;
			soapconn->read_buf[soapconn->read_len] = '\0';
		}
		else {
			purple_debug_error("MSN SOAP", "Failure re-allocating %d bytes of memory!\n", soapconn->read_len + len + 1);
			exit(EXIT_FAILURE);
		}
			
	}

#if defined(MSN_SOAP_DEBUG)
	if (len > 0)
		purple_debug_info("MSN SOAP","Read %d bytes from SOAP server:\n%s\n", len, soapconn->read_buf + soapconn->read_len - len);
#endif

	return len;
}

/*read the whole SOAP server response*/
void 
msn_soap_read_cb(gpointer data, gint source, PurpleInputCondition cond)
{
	MsnSoapConn *soapconn = data;
	MsnSession *session;
	int len;
	char * body_start,*body_len;
	char *length_start,*length_end;
#ifdef MSN_SOAP_DEBUG
#if !defined(_WIN32)
	gchar * formattedxml = NULL;
	gchar * http_headers = NULL;
	xmlnode * node = NULL;
#endif
	purple_debug_misc("MSN SOAP", "msn_soap_read_cb()\n");
#endif
	session = soapconn->session;
	g_return_if_fail(session != NULL);

	
	/*read the request header*/
	len = msn_soap_read(soapconn);
	
	if ( len < 0 )
		return;

	if (soapconn->read_buf == NULL) {
		return;
	}

	if ( (strstr(soapconn->read_buf, "HTTP/1.1 302") != NULL) 
		|| ( strstr(soapconn->read_buf, "HTTP/1.1 301") != NULL ) )
	{
		/* Redirect. */
		char *location, *c;

		purple_debug_info("MSN SOAP", "HTTP Redirect\n");
		location = strstr(soapconn->read_buf, "Location: ");
		if (location == NULL)
		{
			msn_soap_free_read_buf(soapconn);

			return;
		}
		location = strchr(location, ' ') + 1;

		if ((c = strchr(location, '\r')) != NULL)
			*c = '\0';

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

		if ((c = strchr(location, '/')) != NULL)
		{
			g_free(soapconn->login_path);
			soapconn->login_path = g_strdup(c);

			*c = '\0';
		}

		g_free(soapconn->login_host);
		soapconn->login_host = g_strdup(location);

		purple_ssl_connect(session->account, soapconn->login_host,
			PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb,
			msn_soap_error_cb, soapconn);
	}
	/* Another case of redirection, active on May, 2007
	   See http://msnpiki.msnfanatic.com/index.php/MSNP13:SOAPTweener#Redirect
	 */
	else if (strstr(soapconn->read_buf,
                    "<faultcode>psf:Redirect</faultcode>") != NULL)
	{
		char *location, *c;

		location = strstr(soapconn->read_buf, "<psf:redirectUrl>");
		/* Omit the tag preceding the URL */
		location += strlen("<psf:redirectUrl>");
		location = strstr(location, ":/");
		if (location == NULL)
		{
			msn_soap_free_read_buf(soapconn);
			return;
		}

		location += strlen("://"); /* Skip http:// or https:// */

		if ( (c = strstr(location, "</psf:redirectUrl>")) != NULL )
			*c = '\0';

		if ( (c = strstr(location, "/")) != NULL )
		{
			g_free(soapconn->login_path);
			soapconn->login_path = g_strdup(c);
			*c = '\0';
		}

		g_free(soapconn->login_host);
		soapconn->login_host = g_strdup(location);

		purple_ssl_connect(session->account, soapconn->login_host,
			PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb,
			msn_soap_error_cb, soapconn);
	}
	else if (strstr(soapconn->read_buf, "HTTP/1.1 401 Unauthorized") != NULL)
	{
		const char *error;

		purple_debug_error("MSN SOAP", "Received HTTP error 401 Unauthorized\n");
		if ((error = strstr(soapconn->read_buf, "WWW-Authenticate")) != NULL)
		{
			if ((error = strstr(error, "cbtxt=")) != NULL)
			{
				const char *c;
				char *temp;

				error += strlen("cbtxt=");

				if ((c = strchr(error, '\n')) == NULL)
					c = error + strlen(error);

				temp = g_strndup(error, c - error);
				error = purple_url_decode(temp);
				g_free(temp);
			}
		}

		msn_session_set_error(session, MSN_ERROR_AUTH, error);
	}
	/* Handle Passport 3.0 authentication failures.
	 * Further info: http://msnpiki.msnfanatic.com/index.php/MSNP13:SOAPTweener
	 */
	else if (strstr(soapconn->read_buf,
				"<faultcode>wsse:FailedAuthentication</faultcode>") != NULL)
	{
		char *faultstring;

		faultstring = strstr(soapconn->read_buf, "<faultstring>");

		if (faultstring != NULL)
		{
			faultstring += strlen("<faultstring>");
			*strstr(soapconn->read_buf, "</faultstring>") = '\0';
		}
		
		msn_session_set_error(session, MSN_ERROR_AUTH, faultstring);
	}
	else if (strstr(soapconn->read_buf, "HTTP/1.1 503 Service Unavailable"))
	{
		msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
	}
	else if ((strstr(soapconn->read_buf, "HTTP/1.1 200 OK"))
		||(strstr(soapconn->read_buf, "HTTP/1.1 500")))
	{
			/*OK! process the SOAP body*/
			body_start = (char *)g_strstr_len(soapconn->read_buf, soapconn->read_len,"\r\n\r\n");
			if (!body_start) {
				return;
			}
			body_start += 4;

			//	purple_debug_misc("msn", "Soap Read: {%s}\n", soapconn->read_buf);

			/* we read the content-length*/
			length_start = strstr(soapconn->read_buf, "Content-Length: ");
			length_start += strlen("Content-Length: ");
			length_end = strstr(length_start, "\r\n");
			body_len = g_strndup(length_start, length_end - length_start);

			/*setup the conn body */
			soapconn->body		= body_start;
			soapconn->body_len	= atoi(body_len);
			g_free(body_len);
#ifdef MSN_SOAP_DEBUG
			purple_debug_misc("MSN SOAP","SOAP bytes read so far: %d, Content-Length: %d\n", soapconn->read_len, soapconn->body_len);
#endif
			soapconn->need_to_read = (body_start - soapconn->read_buf + soapconn->body_len) - soapconn->read_len;
			if ( soapconn->need_to_read > 0 ) {
				return;
			}

#if defined(MSN_SOAP_DEBUG) && !defined(_WIN32)

			node = xmlnode_from_str(soapconn->body, soapconn->body_len);
	
			if (node != NULL) {
				formattedxml = xmlnode_to_formatted_str(node, NULL);
				http_headers = g_strndup(soapconn->read_buf, soapconn->body - soapconn->read_buf);
				
				purple_debug_info("MSN SOAP","Data with XML payload received from the SOAP server:\n%s%s\n", http_headers, formattedxml);
				g_free(http_headers);
				g_free(formattedxml);
				xmlnode_free(node);
			}
			else
				purple_debug_info("MSN SOAP","Data received from the SOAP server:\n%s\n", soapconn->read_buf);
#endif

			/*remove the read handler*/
			purple_input_remove(soapconn->input_handler);
			soapconn->input_handler = -1;
			/*
			 * close the soap connection,if more soap request came,
			 * Just reconnect to do it,
			 *
			 * To solve the problem described below:
			 * When I post the soap request in one socket one after the other,
			 * The first read is ok, But the second soap read always got 0 bytes,
			 * Weird!
			 * */
			msn_soap_close(soapconn);

			/*call the read callback*/
			if ( soapconn->read_cb != NULL ) {
				soapconn->read_cb(soapconn, source, 0);
			}
	}
	return;
}

void 
msn_soap_free_read_buf(MsnSoapConn *soapconn)
{
	g_return_if_fail(soapconn != NULL);
	
	if (soapconn->read_buf) {
		g_free(soapconn->read_buf);
	}
	soapconn->read_buf = NULL;
	soapconn->read_len = 0;
	soapconn->need_to_read = 0;
}

void
msn_soap_free_write_buf(MsnSoapConn *soapconn)
{
	g_return_if_fail(soapconn != NULL);

	if (soapconn->write_buf) {
		g_free(soapconn->write_buf);
	}
	soapconn->write_buf = NULL;
	soapconn->written_len = 0;
}

void
msn_soap_free_data_cb(MsnSoapConn *soapconn)
{
	if (soapconn->data_cb) {
		g_free(soapconn->data_cb);
	}
}

/*Soap write process func*/
static void
msn_soap_write_cb(gpointer data, gint source, PurpleInputCondition cond)
{
	MsnSoapConn *soapconn = data;
	int len, total_len;

	g_return_if_fail(soapconn != NULL);
	if ( soapconn->write_buf == NULL ) {
		purple_debug_error("MSN SOAP","SOAP buffer is NULL\n");
		purple_input_remove(soapconn->output_handler);
		soapconn->output_handler = -1;
		return;
	}
	total_len = strlen(soapconn->write_buf);

	/* 
	 * write the content to SSL server,
	 */
	len = purple_ssl_write(soapconn->gsc,
		soapconn->write_buf + soapconn->written_len,
		total_len - soapconn->written_len);

	if (len < 0 && errno == EAGAIN)
		return;
	else if (len <= 0){
		/*SSL write error!*/
		purple_input_remove(soapconn->output_handler);
		soapconn->output_handler = -1;
		/* TODO: notify of the error */
		purple_debug_error("MSN SOAP","Error writing to SSL connection!\n");
		return;
	}
	soapconn->written_len += len;

	if (soapconn->written_len < total_len)
		return;

	purple_input_remove(soapconn->output_handler);
	soapconn->output_handler = -1;

	/*clear the write buff*/
	msn_soap_free_write_buf(soapconn);

	/* Write finish!
	 * callback for write done
	 */
	if(soapconn->written_cb != NULL){
		soapconn->written_cb(soapconn, source, 0);
	}
	/*maybe we need to read the input?*/
	if ( soapconn->input_handler == -1 ) {
		soapconn->input_handler = purple_input_add(soapconn->gsc->fd,
			PURPLE_INPUT_READ, msn_soap_read_cb, soapconn);
	}
//	msn_soap_read_cb(soapconn,source,0);
}

/*write the buffer to SOAP connection*/
void
msn_soap_write(MsnSoapConn * soapconn, char *write_buf, PurpleInputFunction written_cb)
{
	soapconn->write_buf = write_buf;
	soapconn->written_len = 0;
	soapconn->written_cb = written_cb;
	
	msn_soap_free_read_buf(soapconn);

	/*clear the read buffer first*/
	/*start the write*/
	soapconn->output_handler = purple_input_add(soapconn->gsc->fd, PURPLE_INPUT_WRITE,
													msn_soap_write_cb, soapconn);
	msn_soap_write_cb(soapconn, soapconn->gsc->fd, PURPLE_INPUT_WRITE);
}

/* New a soap request*/
MsnSoapReq *
msn_soap_request_new(const char *host,const char *post_url,const char *soap_action,
				const char *body, const gpointer data_cb,
				PurpleInputFunction read_cb,PurpleInputFunction written_cb)
{
	MsnSoapReq *request;

	request = g_new0(MsnSoapReq, 1);
	request->id = 0;

	request->login_host = g_strdup(host);
	request->login_path = g_strdup(post_url);
	request->soap_action		= g_strdup(soap_action);
	request->body		= g_strdup(body);
	request->data_cb 	= data_cb;
	request->read_cb	= read_cb;
	request->written_cb	= written_cb;

	return request;
}

/*free a soap request*/
void
msn_soap_request_free(MsnSoapReq *request)
{
	g_return_if_fail(request != NULL);

	g_free(request->login_host);
	g_free(request->login_path);
	g_free(request->soap_action);
	g_free(request->body);
	g_free(request->data_cb);
	request->read_cb	= NULL;
	request->written_cb	= NULL;

	g_free(request);
}

/*post the soap request queue's head request*/
void
msn_soap_post_head_request(MsnSoapConn *soapconn)
{
	purple_debug_info("MSN SOAP", "Posting new request from head of the queue\n");
	
	g_return_if_fail(soapconn->soap_queue != NULL);

	if(!g_queue_is_empty(soapconn->soap_queue)){
		MsnSoapReq *request;
		if((request = g_queue_pop_head(soapconn->soap_queue)) != NULL){
			msn_soap_post_request(soapconn,request);
		}
	} else {
		purple_debug_info("MSN SOAP", "No requests to process found.\n");
		msn_soap_set_process_step(soapconn,MSN_SOAP_CONNECTED_IDLE);
	}
}

/*post the soap request ,
 * if not connected, Connected first.
 */
void
msn_soap_post(MsnSoapConn *soapconn,MsnSoapReq *request,
				MsnSoapConnectInitFunction msn_soap_init_func)
{
	if (request != NULL) {
		g_queue_push_tail(soapconn->soap_queue, request);
	}
	if (!msn_soap_connected(soapconn) && (soapconn->step == MSN_SOAP_UNCONNECTED)
					&&(!g_queue_is_empty(soapconn->soap_queue))) {
		/*not connected?and we have something to process connect it first*/
		purple_debug_misc("MSN SOAP","No connection to SOAP server. Connecting...\n");
		msn_soap_init_func(soapconn);
		msn_soap_connect(soapconn);
		return;
	}
	purple_debug_misc("MSN SOAP","Connected to SOAP server\n");

	/*if connected, what we only needed to do is to queue the request, 
	 * when SOAP request in the queue processed done, will do this command.
	 * we just waiting...
	 * If we send the request this time,error may occure
	 */
	if (soapconn->step == MSN_SOAP_CONNECTED_IDLE){
		msn_soap_post_head_request(soapconn);
	}
}

/*Post the soap request action*/
void
msn_soap_post_request(MsnSoapConn *soapconn,MsnSoapReq *request)
{
	char * soap_head = NULL;
	char * request_str = NULL;
#ifdef MSN_SOAP_DEBUG
#if !defined(_WIN32)
	xmlnode * node;
#endif
	purple_debug_misc("MSN SOAP","msn_soap_post_request()\n");
#endif

	msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING);
	soap_head = g_strdup_printf(
					"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: %" G_GSIZE_FORMAT "\r\n"
					"Connection: Keep-Alive\r\n"
					"Cache-Control: no-cache\r\n\r\n",
					request->login_path,
					request->soap_action,
					soapconn->session->passport_info.mspauth,
					request->login_host,
					strlen(request->body)
					);
	request_str = g_strdup_printf("%s%s", soap_head, request->body);

#if defined(MSN_SOAP_DEBUG) && !defined(_WIN32)
	node = xmlnode_from_str(request->body, -1);
	if (node != NULL) {
		char *formattedstr = xmlnode_to_formatted_str(node, NULL);
		purple_debug_info("MSN SOAP","Posting request to SOAP server:\n%s%s\n",soap_head, formattedstr);
		g_free(formattedstr);
		xmlnode_free(node);
	}
	else
		purple_debug_info("MSN SOAP","Failed to parse SOAP request being sent:\n%s\n", request_str);
#endif
	
	g_free(soap_head);
	/*free read buffer*/
	// msn_soap_free_read_buf(soapconn);
	/*post it to server*/
	soapconn->data_cb = request->data_cb;
	msn_soap_write(soapconn, request_str, request->written_cb);
}