view libgaim/protocols/msn/soap.c @ 20392:9ba7dee775e1

The first msn-p13-merge-head.diff.gz from SF Patch #1621854 from Ka-Hing Cheung. "uploaded a diff, this diff is unchanged from the last tarball that I previously uploaded, except that it's against HEAD. This should be a little easier for most people." (This was apparently msn-p13-merge-head.diff, which SourceForge didn't allow to be uploaded.) PLUS "Updated the diff with basically no change, except with simom's icon fix. The previous diff was broken because some of the files were not added"
author Richard Laager <rlaager@wiktel.com>
date Sun, 15 Apr 2007 03:01:41 +0000
parents e354528c4163
children
line wrap: on
line source

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

/*define this Macro to debug soap server action*/
#undef MSN_SOAP_DEBUG

/*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, GaimSslConnection *gsc,
				 GaimInputCondition cond)
{
	MsnSoapConn * soapconn;
	MsnSession *session;

	gaim_debug_info("MaYuan","Soap connection connected!\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(GaimSslConnection *gsc, GaimSslErrorType error, void *data)
{	
	MsnSoapConn * soapconn = data;

	g_return_if_fail(data != NULL);
	gaim_debug_info("MaYuan","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,
				GaimSslInputFunction	connect_cb,
				GaimSslErrorFunction	error_cb)
{
	gaim_debug_info("MaYuan","msn_soap_init...\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){
		gaim_ssl_connect(soapconn->session->account, soapconn->login_host,
				GAIM_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){
			gaim_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){
		gaim_input_remove(soapconn->output_handler);
	}
	/*remove the read handler*/
	if (soapconn->input_handler > 0){
		gaim_input_remove(soapconn->input_handler);
	}
	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];

//	requested_len = (soapconn->need_to_read > 0) ? soapconn->need_to_read : MSN_SOAP_READ_BUFF_SIZE;
	requested_len = MSN_SOAP_READ_BUFF_SIZE;
	if(soapconn->ssl_conn){
		len = gaim_ssl_read(soapconn->gsc, temp_buf,requested_len);
	}else{
		len = read(soapconn->fd, temp_buf,requested_len);
	}
	if(len >0){
		soapconn->read_buf = g_realloc(soapconn->read_buf,
						soapconn->read_len + len + 1);
		memcpy(soapconn->read_buf + soapconn->read_len, temp_buf, len);
		soapconn->read_len += len;
		soapconn->read_buf[soapconn->read_len] = '\0';
	}
#ifdef MSN_SOAP_DEBUG
	gaim_debug_info("MaYuan","++soap ssl read:{%d}\n",len);
	gaim_debug_info("MaYuan","nexus ssl read:{%s}\n",soapconn->read_buf);
#endif
	return len;
}

/*read the whole SOAP server response*/
void 
msn_soap_read_cb(gpointer data, gint source, GaimInputCondition cond)
{
	MsnSoapConn *soapconn = data;
	MsnSession *session;
	int len;
	char * body_start,*body_len;
	char *length_start,*length_end;

//	gaim_debug_misc("MaYuan", "soap read cb\n");
	session = soapconn->session;
	g_return_if_fail(session != NULL);

	if (soapconn->input_handler == -1){
		soapconn->input_handler = gaim_input_add(soapconn->gsc->fd,
			GAIM_INPUT_READ, msn_soap_read_cb, soapconn);
	}

	/*read the request header*/
	len = msn_soap_read(soapconn);
	if (len < 0 && errno == EAGAIN){
		return;
	}else if (len < 0) {
		gaim_debug_error("msn", "read Error!len:%d\n",len);
		gaim_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;
	}

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

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

		gaim_debug_error("MaYuan", "soap 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);

		gaim_ssl_connect(session->account, soapconn->login_host,
			GAIM_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;

		gaim_debug_error("MaYuan", "soap 401\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 = gaim_url_decode(temp);
				g_free(temp);
			}
		}

		msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, error);
	}
	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;

			//	gaim_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);
#ifdef MSN_SOAP_DEBUG
			gaim_debug_misc("MaYuan","SOAP Read length :%d,body len:%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;
			}
			g_free(body_len);

			/*remove the read handler*/
			gaim_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)
{
	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)
{
	if(soapconn->write_buf){
		g_free(soapconn->write_buf);
	}
	soapconn->write_buf = NULL;
	soapconn->written_len = 0;
}

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

	g_return_if_fail(soapconn != NULL);
	if(soapconn->write_buf == NULL){
		gaim_debug_error("MaYuan","soap buffer is NULL\n");
		gaim_input_remove(soapconn->output_handler);
		soapconn->output_handler = -1;
		return;
	}
	total_len = strlen(soapconn->write_buf);

	/* 
	 * write the content to SSL server,
	 */
	len = gaim_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!*/
		gaim_input_remove(soapconn->output_handler);
		soapconn->output_handler = -1;
		/* TODO: notify of the error */
		return;
	}
	soapconn->written_len += len;

	if (soapconn->written_len < total_len)
		return;

	gaim_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?*/
	msn_soap_read_cb(soapconn,source,0);
}

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

	/*clear the read buffer first*/
	/*start the write*/
	soapconn->output_handler = gaim_input_add(soapconn->gsc->fd, GAIM_INPUT_WRITE,
													msn_soap_write_cb, soapconn);
	msn_soap_write_cb(soapconn, soapconn->gsc->fd, GAIM_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,
				GaimInputFunction read_cb,GaimInputFunction 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->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);
	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)
{
	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);
		}
	}
	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*/
		gaim_debug_info("Ma Yuan","soap is not connected!\n");
		msn_soap_init_func(soapconn);
		msn_soap_connect(soapconn);
		return;
	}
	gaim_debug_info("Ma Yuan","soap  connected!\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 0
	if(soapconn->step == MSN_SOAP_CONNECTED_IDLE){
		msn_soap_post_head_request(soapconn);
	}
#endif
}

/*Post the soap request action*/
void
msn_soap_post_request(MsnSoapConn *soapconn,MsnSoapReq *request)
{
	char * soap_head = NULL;
	char * request_str = NULL;

	gaim_debug_info("MaYuan","msn_soap_post_request()...\n");
	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: %d\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);
	g_free(soap_head);

#ifdef MSN_SOAP_DEBUG
	gaim_debug_info("MaYuan","send to  server{%s}\n",request_str);
#endif

	/*free read buffer*/
	msn_soap_free_read_buf(soapconn);
	/*post it to server*/
	msn_soap_write(soapconn,request_str,request->written_cb);
}