Mercurial > pidgin
diff libpurple/protocols/msn/soap.c @ 23507:711773577380
Clean up some MSN SOAP stuff.
I think soap2.[ch] have proven themselves since we've been using them
all this time. Removed soap.[ch] and any references to them, since
those were mostly in header files, without any actual use in code.
Also, removed some SOAP templates from group.h which are now taken
care of by contact.[ch].
author | Elliott Sales de Andrade <qulogic@pidgin.im> |
---|---|
date | Sat, 28 Jun 2008 06:01:22 +0000 |
parents | 1ac5faa72c8d |
children | a0e957b7b923 |
line wrap: on
line diff
--- a/libpurple/protocols/msn/soap.c Mon Jun 23 05:38:55 2008 +0000 +++ b/libpurple/protocols/msn/soap.c Sat Jun 28 06:01:22 2008 +0000 @@ -1,8 +1,7 @@ /** * @file soap.c - * SOAP connection related process - * Author - * MaYuan<mayuan2006@gmail.com> + * C file for SOAP connection related process + * * purple * * Purple is the legal property of its developers, whose names are too numerous @@ -23,854 +22,659 @@ * 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 "internal.h" + #include "soap.h" -#define MSN_SOAP_DEBUG -/*local function prototype*/ -void msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step); +#include "session.h" + +#include "debug.h" +#include "xmlnode.h" + +#include <glib.h> +#if !defined(_WIN32) || !defined(_WINERROR_) +#include <error.h> +#endif + +#define SOAP_TIMEOUT (5 * 60) -/*setup the soap process step*/ -void -msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step) -{ -#ifdef MSN_SOAP_DEBUG - const char *MsnSoapStepText[] = - { - "Unconnected", - "Connecting", - "Connected", - "Processing", - "Connected Idle" - }; +typedef struct _MsnSoapRequest { + char *path; + MsnSoapMessage *message; + MsnSoapCallback cb; + gpointer cb_data; +} MsnSoapRequest; - purple_debug_info("MSN SOAP", "Setting SOAP process step to %s\n", MsnSoapStepText[step]); -#endif - soapconn->step = step; -} +typedef struct _MsnSoapConnection { + MsnSession *session; + char *host; -/*new a soap connection*/ -MsnSoapConn * -msn_soap_new(MsnSession *session,gpointer data, gboolean ssl) -{ - MsnSoapConn *soapconn; - - soapconn = g_new0(MsnSoapConn, 1); - soapconn->session = session; - soapconn->parent = data; - soapconn->ssl_conn = ssl; + time_t last_used; + PurpleSslConnection *ssl; + gboolean connected; - soapconn->gsc = NULL; - soapconn->input_handler = 0; - soapconn->output_handler = 0; - - msn_soap_set_process_step(soapconn, MSN_SOAP_UNCONNECTED); - soapconn->soap_queue = g_queue_new(); + guint event_handle; + GString *buf; + gsize handled_len; + gsize body_len; + int response_code; + gboolean headers_done; + gboolean close_when_done; - return soapconn; -} + MsnSoapMessage *message; -/*ssl soap connect callback*/ -void -msn_soap_connect_cb(gpointer data, PurpleSslConnection *gsc, - PurpleInputCondition cond) -{ - MsnSoapConn * soapconn; - MsnSession *session; - gboolean soapconn_is_valid = FALSE; + 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); - purple_debug_misc("MSN SOAP","SOAP server connection established!\n"); - - soapconn = data; - g_return_if_fail(soapconn != NULL); +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); - session = soapconn->session; - g_return_if_fail(session != NULL); - - soapconn->gsc = gsc; +static void msn_soap_message_send_internal(MsnSession *session, + MsnSoapMessage *message, const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data, gboolean first); - msn_soap_set_process_step(soapconn, MSN_SOAP_CONNECTED); +static void msn_soap_request_destroy(MsnSoapRequest *req, gboolean keep_message); +static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect); +static gboolean msn_soap_write_cb_internal(gpointer data, gint fd, PurpleInputCondition cond, gboolean initial); +static void msn_soap_process(MsnSoapConnection *conn); - /*connection callback*/ - if (soapconn->connect_cb != NULL) { - soapconn_is_valid = soapconn->connect_cb(soapconn, gsc); - } +static gboolean +msn_soap_cleanup_each(gpointer key, gpointer value, gpointer data) +{ + MsnSoapConnection *conn = value; + time_t *t = data; - if (!soapconn_is_valid) { - return; + if ((*t - conn->last_used) > SOAP_TIMEOUT * 2) { + purple_debug_info("soap", "cleaning up soap conn %p\n", conn); + return TRUE; } - /*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(soapconn, gsc, error); - } else { - msn_soap_post(soapconn, NULL); - } -} - -/*init the soap connection*/ -void -msn_soap_init(MsnSoapConn *soapconn,char * host, gboolean ssl, - MsnSoapSslConnectCbFunction connect_cb, - MsnSoapSslErrorCbFunction error_cb) -{ - purple_debug_misc("MSN SOAP","Initializing SOAP connection\n"); - g_free(soapconn->login_host); - soapconn->login_host = g_strdup(host); - soapconn->ssl_conn = ssl; - soapconn->connect_cb = connect_cb; - soapconn->error_cb = error_cb; + return FALSE; } -/*connect the soap connection*/ -void -msn_soap_connect(MsnSoapConn *soapconn) +static gboolean +msn_soap_cleanup_for_session(gpointer data) { - 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 { + MsnSession *sess = data; + time_t t = time(NULL); + + purple_debug_info("soap", "session cleanup timeout\n"); + + if (sess->soap_table) { + g_hash_table_foreach_remove(sess->soap_table, msn_soap_cleanup_each, + &t); + + if (g_hash_table_size(sess->soap_table) == 0) { + purple_timeout_remove(sess->soap_cleanup_handle); + sess->soap_cleanup_handle = 0; + } } - msn_soap_set_process_step(soapconn, MSN_SOAP_CONNECTING); -} - - -static void -msn_soap_close_handler(guint *handler) -{ - if (*handler > 0) { - purple_input_remove(*handler); - *handler = 0; - } -#ifdef MSN_SOAP_DEBUG - else { - purple_debug_misc("MSN SOAP", "Handler inactive, not removing\n"); - } -#endif - -} - - -/*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); + return TRUE; } -/*clean the unhandled SOAP request*/ -void -msn_soap_clean_unhandled_requests(MsnSoapConn *soapconn) +static MsnSoapConnection * +msn_soap_get_connection(MsnSession *session, const char *host) { - MsnSoapReq *request; - - g_return_if_fail(soapconn != NULL); - - soapconn->body = NULL; - - while ((request = g_queue_pop_head(soapconn->soap_queue)) != NULL){ - if (soapconn->read_cb) { - soapconn->read_cb(soapconn); - } - msn_soap_request_free(request); - } -} - -/*destroy the soap connection*/ -void -msn_soap_destroy(MsnSoapConn *soapconn) -{ - g_free(soapconn->login_host); - - g_free(soapconn->login_path); + MsnSoapConnection *conn = NULL; - /*remove the write handler*/ - if (soapconn->output_handler > 0){ - purple_input_remove(soapconn->output_handler); - soapconn->output_handler = 0; - } - /*remove the read handler*/ - if (soapconn->input_handler > 0){ - purple_input_remove(soapconn->input_handler); - soapconn->input_handler = 0; - } - 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_requests(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 (session->soap_table) { + conn = g_hash_table_lookup(session->soap_table, host); + } else { + session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, (GDestroyNotify)msn_soap_connection_destroy); } - if ( soapconn->ssl_conn ) { - len = purple_ssl_read(soapconn->gsc, temp_buf, requested_len); - } else { - len = read(soapconn->fd, temp_buf, requested_len); + if (session->soap_cleanup_handle == 0) + session->soap_cleanup_handle = purple_timeout_add(SOAP_TIMEOUT * 1000, + msn_soap_cleanup_for_session, session); + + if (conn == NULL) { + conn = msn_soap_connection_new(session, host); + g_hash_table_insert(session->soap_table, conn->host, conn); } + conn->last_used = time(NULL); - if ( len <= 0 ) { - switch (errno) { + return conn; +} - case 0: - case EBADF: /* we are sometimes getting this in Windows */ - case EAGAIN: return len; +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; +} - default : purple_debug_error("MSN SOAP", "Read error!" - "read len: %" G_GSSIZE_FORMAT ", error = %s\n", - len, g_strerror(errno)); - purple_input_remove(soapconn->input_handler); - //soapconn->input_handler = 0; - 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 %" G_GSIZE_FORMAT " bytes of memory!\n", - soapconn->read_len + len + 1); - exit(EXIT_FAILURE); - } +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 = purple_timeout_add(0, msn_soap_connection_run, conn); +} -#if defined(MSN_SOAP_DEBUG) - if (len > 0) - purple_debug_info("MSN SOAP", - "Read %" G_GSIZE_FORMAT " bytes from SOAP server:\n%s\n", len, - soapconn->read_buf + soapconn->read_len - len); -#endif +static void +msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error, + gpointer data) +{ + MsnSoapConnection *conn = data; - return len; + /* sslconn already frees the connection in case of error */ + conn->ssl = NULL; + + g_hash_table_remove(conn->session->soap_table, conn->host); } -/*read the whole SOAP server response*/ -static void -msn_soap_read_cb(gpointer data, gint source, PurpleInputCondition cond) +static gboolean +msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url) { - 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); + char *host; + char *path; + + if (purple_url_parse(url, &host, NULL, &path, NULL, NULL)) { + msn_soap_message_send_internal(conn->session, + conn->current_request->message, host, path, + conn->current_request->cb, conn->current_request->cb_data, TRUE); - - /*read the request header*/ - len = msn_soap_read(soapconn); + msn_soap_request_destroy(conn->current_request, TRUE); + conn->current_request = NULL; - if ( len < 0 ) - return; + g_free(host); + g_free(path); - if (soapconn->read_buf == NULL) { - return; + return TRUE; } - if ( (strstr(soapconn->read_buf, "HTTP/1.1 302") != NULL) - || ( strstr(soapconn->read_buf, "HTTP/1.1 301") != NULL ) ) - { - /* Redirect. */ - char *location, *c; + return FALSE; +} + +static gboolean +msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response) +{ + xmlnode *body = xmlnode_get_child(response->xml, "Body"); + xmlnode *fault = xmlnode_get_child(response->xml, "Fault"); + + if (fault) { + xmlnode *faultcode = xmlnode_get_child(fault, "faultcode"); + + if (faultcode != NULL) { + char *faultdata = xmlnode_get_data(faultcode); + + if (g_str_equal(faultdata, "psf:Redirect")) { + xmlnode *url = xmlnode_get_child(fault, "redirectUrl"); - purple_debug_info("MSN SOAP", "HTTP Redirect\n"); - location = strstr(soapconn->read_buf, "Location: "); - if (location == NULL) - { - c = (char *) g_strstr_len(soapconn->read_buf, soapconn->read_len,"\r\n\r\n"); - if (c != NULL) { - /* we have read the whole HTTP headers and found no Location: */ - msn_soap_free_read_buf(soapconn); - msn_soap_post(soapconn, NULL); + if (url) { + char *urldata = xmlnode_get_data(url); + msn_soap_handle_redirect(conn, urldata); + g_free(urldata); + } + + g_free(faultdata); + msn_soap_message_destroy(response); + return TRUE; + } else if (g_str_equal(faultdata, "wsse:FailedAuthentication")) { + xmlnode *reason = xmlnode_get_child(fault, "faultstring"); + char *reasondata = xmlnode_get_data(reason); + + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_AUTH, + reasondata); + + g_free(reasondata); + g_free(faultdata); + msn_soap_message_destroy(response); + return FALSE; } - return; - } - location = strchr(location, ' ') + 1; - - if ((c = strchr(location, '\r')) != NULL) - *c = '\0'; - else - return; - - /* 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); - - msn_soap_close_handler( &(soapconn->input_handler) ); - msn_soap_close(soapconn); - - if (purple_ssl_connect(session->account, soapconn->login_host, - PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb, - msn_soap_error_cb, soapconn) == NULL) { - - purple_debug_error("MSN SOAP", "Unable to connect to %s !\n", soapconn->login_host); - // dispatch next request - msn_soap_post(soapconn, NULL); - } - } - /* 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; - - if ( (location = strstr(soapconn->read_buf, "<psf:redirectUrl>") ) == NULL) - return; - - /* Omit the tag preceding the URL */ - location += strlen("<psf:redirectUrl>"); - if (location > soapconn->read_buf + soapconn->read_len) - return; - if ( (location = strstr(location, "://")) == NULL) - return; - - location += strlen("://"); /* Skip http:// or https:// */ - - if ( (c = strstr(location, "</psf:redirectUrl>")) != NULL ) - *c = '\0'; - else - return; - - 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); - - msn_soap_close_handler( &(soapconn->input_handler) ); - msn_soap_close(soapconn); - - if (purple_ssl_connect(session->account, soapconn->login_host, - PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb, - msn_soap_error_cb, soapconn) == NULL) { - - purple_debug_error("MSN SOAP", "Unable to connect to %s !\n", soapconn->login_host); - // dispatch next request - msn_soap_post(soapconn, NULL); + g_free(faultdata); } } - 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) - { - gchar *faultstring; - - faultstring = strstr(soapconn->read_buf, "<faultstring>"); - - if (faultstring != NULL) - { - gchar *c; - faultstring += strlen("<faultstring>"); - if (faultstring < soapconn->read_buf + soapconn->read_len) { - c = strstr(soapconn->read_buf, "</faultstring>"); - if (c != NULL) { - *c = '\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"))) - { - gboolean soapconn_is_valid = FALSE; - - /*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; - - if (body_start > soapconn->read_buf + soapconn->read_len) - return; - - /* we read the content-length*/ - if ( (length_start = g_strstr_len(soapconn->read_buf, soapconn->read_len, "Content-Length: ")) != NULL) - length_start += strlen("Content-Length: "); - - if (length_start > soapconn->read_buf + soapconn->read_len) - return; - if ( (length_end = strstr(length_start, "\r\n")) == NULL ) - return; - - 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: %" G_GSIZE_FORMAT ", 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 + if (fault || body) { + MsnSoapRequest *request = conn->current_request; + conn->current_request = NULL; + request->cb(request->message, response, + request->cb_data); + msn_soap_message_destroy(response); + msn_soap_request_destroy(request, FALSE); + } - /*remove the read handler*/ - msn_soap_close_handler( &(soapconn->input_handler) ); -// purple_input_remove(soapconn->input_handler); -// soapconn->input_handler = 0; - /* - * 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_is_valid = soapconn->read_cb(soapconn); - } - - if (!soapconn_is_valid) { - return; - } - - /* dispatch next request in queue */ - msn_soap_post(soapconn, NULL); - } - 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; + return TRUE; } -void -msn_soap_free_write_buf(MsnSoapConn *soapconn) +static void +msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond) { - g_return_if_fail(soapconn != NULL); - - if (soapconn->write_buf) { - g_free(soapconn->write_buf); - } - soapconn->write_buf = NULL; - soapconn->written_len = 0; -} + MsnSoapConnection *conn = data; + int count = 0, cnt, perrno; + /* This buffer needs to be larger than any packets received from + login.live.com or Adium will fail to receive the packet + (something weird with the login.live.com server). With NSS it works + fine, so I believe it's some bug with OS X */ + char buf[16 * 1024]; -/*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 write buffer is NULL\n"); - // msn_soap_check_conn_errors(soapconn); - // purple_input_remove(soapconn->output_handler); - // soapconn->output_handler = 0; - msn_soap_close_handler( &(soapconn->output_handler) ); - return; + if (conn->message == NULL) { + conn->message = msn_soap_message_new(NULL, NULL); } - 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!*/ -// msn_soap_check_conn_errors(soapconn); - msn_soap_close_handler( &(soapconn->output_handler) ); -// purple_input_remove(soapconn->output_handler); -// soapconn->output_handler = 0; - - msn_soap_close(soapconn); + if (conn->buf == NULL) { + conn->buf = g_string_new_len(buf, 0); + } + + while ((cnt = purple_ssl_read(conn->ssl, buf, sizeof(buf))) > 0) { + purple_debug_info("soap", "read %d bytes\n", cnt); + count += cnt; + g_string_append_len(conn->buf, buf, cnt); + } - /* TODO: notify of the error */ - purple_debug_error("MSN SOAP", "Error writing to SSL connection!\n"); - msn_soap_post(soapconn, NULL); - return; - } - soapconn->written_len += len; - - if (soapconn->written_len < total_len) + /* && count is necessary for Adium, on OS X the last read always + return an error, so we want to proceed anyway. See #5212 for + discussion on this and the above buffer size issues */ + if(cnt < 0 && errno == EAGAIN && count == 0) return; - msn_soap_close_handler( &(soapconn->output_handler) ); -// purple_input_remove(soapconn->output_handler); -// soapconn->output_handler = 0; - - /*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); - } - /*maybe we need to read the input?*/ - if ( soapconn->input_handler == 0 ) { - soapconn->input_handler = purple_input_add(soapconn->gsc->fd, - PURPLE_INPUT_READ, msn_soap_read_cb, soapconn); - } -} - -/*write the buffer to SOAP connection*/ -void -msn_soap_write(MsnSoapConn * soapconn, char *write_buf, MsnSoapWrittenCbFunction written_cb) -{ - if (soapconn == NULL) { - return; - } - - msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING); - - /* Ideally this wouldn't ever be necessary, but i believe that it is leaking the previous value */ - g_free(soapconn->write_buf); - 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, - MsnSoapReadCbFunction read_cb, - MsnSoapWrittenCbFunction written_cb, - MsnSoapConnectInitFunction connect_init) -{ - 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; - request->connect_init = connect_init; - - 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; - request->connect_init = 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 != NULL); - g_return_if_fail(soapconn->soap_queue != NULL); - - if (soapconn->step == MSN_SOAP_CONNECTED || - soapconn->step == MSN_SOAP_CONNECTED_IDLE) { - - purple_debug_info("MSN SOAP", "Posting new request from head of the queue\n"); - - 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); + // msn_soap_process could alter errno + perrno = errno; + msn_soap_process(conn); + + if (cnt < 0 && perrno != EAGAIN) { + purple_debug_info("soap", "read: %s\n", g_strerror(perrno)); + // It's possible msn_soap_process closed the ssl connection + if (conn->ssl) { + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); } } } -/*post the soap request , - * if not connected, Connected first. - */ -void -msn_soap_post(MsnSoapConn *soapconn, MsnSoapReq *request) -{ - MsnSoapReq *head_request; +static void +msn_soap_process(MsnSoapConnection *conn) { + gboolean handled = FALSE; + char *cursor; + char *linebreak; + + purple_debug_info("soap", "current %s\n", conn->buf->str); + + cursor = conn->buf->str + conn->handled_len; + + if (!conn->headers_done) { + 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 */ + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); + handled = TRUE; + break; + } else if (conn->response_code == 503) { + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL); + return; + } + } else if (cursor == linebreak) { + /* blank line */ + conn->headers_done = TRUE; + cursor = conn->buf->str + conn->handled_len; + break; + } else { + char *line = g_strndup(cursor, linebreak - cursor); + char *sep = strstr(line, ": "); + char *key = line; + char *value; - if (soapconn == NULL) - return; + if (sep == NULL) { + purple_debug_info("soap", "ignoring malformed line: %s\n", line); + g_free(line); + goto loop_end; + } + + value = sep + 2; + *sep = '\0'; + msn_soap_message_add_header(conn->message, key, value); + + if ((conn->response_code == 301 || conn->response_code == 300) + && strcmp(key, "Location") == 0) { + + msn_soap_handle_redirect(conn, value); + + handled = TRUE; + g_free(line); + break; + } else if (conn->response_code == 401 && + strcmp(key, "WWW-Authenticate") == 0) { + char *error = strstr(value, "cbtxt="); - if (request != NULL) { -#ifdef MSN_SOAP_DEBUG - purple_debug_misc("MSN SOAP", "Request added to the queue\n"); -#endif - g_queue_push_tail(soapconn->soap_queue, request); + if (error) { + error += strlen("cbtxt="); + } + + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_AUTH, + error ? purple_url_decode(error) : NULL); + + g_free(line); + return; + } else if (strcmp(key, "Content-Length") == 0) { + conn->body_len = atoi(value); + } else if (strcmp(key, "Connection") == 0) { + if (strcmp(value, "close") == 0) { + conn->close_when_done = TRUE; + } + } + g_free(line); + } + + loop_end: + cursor = conn->buf->str + conn->handled_len; + } } - if ( !g_queue_is_empty(soapconn->soap_queue)) { - - /* we may have to reinitialize the soap connection, so avoid - * reusing the connection for now */ + if (!handled && conn->headers_done) { + if (conn->buf->len - conn->handled_len >= + conn->body_len) { + xmlnode *node = xmlnode_from_str(cursor, conn->body_len); - if (soapconn->step == MSN_SOAP_CONNECTED_IDLE) { - purple_debug_misc("MSN SOAP","Already connected to SOAP server, re-initializing\n"); - msn_soap_close_handler( &(soapconn->input_handler) ); - msn_soap_close_handler( &(soapconn->output_handler) ); - msn_soap_close(soapconn); - } + 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_connected(soapconn) && (soapconn->step == MSN_SOAP_UNCONNECTED)) { - - /*not connected?and we have something to process connect it first*/ - purple_debug_misc("MSN SOAP","No connection to SOAP server. Connecting...\n"); - head_request = g_queue_peek_head(soapconn->soap_queue); - - if (head_request == NULL) { - purple_debug_error("MSN SOAP", "Queue is not empty, but failed to peek the head request!\n"); - return; + if (!msn_soap_handle_body(conn, message)) { + return; + } } - if (head_request->connect_init != NULL) { - head_request->connect_init(soapconn); - } - msn_soap_connect(soapconn); - return; + msn_soap_connection_handle_next(conn); } -#ifdef MSN_SOAP_DEBUG - purple_debug_info("MSN SOAP", "Currently processing another SOAP request\n"); - } else { - purple_debug_info("MSN SOAP", "No requests left to dispatch\n"); -#endif + return; + } + + if (handled) { + msn_soap_connection_handle_next(conn); + } +} + +static void +msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond) +{ + msn_soap_write_cb_internal(data, fd, cond, FALSE); +} + +static gboolean +msn_soap_write_cb_internal(gpointer data, gint fd, PurpleInputCondition cond, + gboolean initial) +{ + MsnSoapConnection *conn = data; + int written; + + if (cond != PURPLE_INPUT_WRITE) return TRUE; + + written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len, + conn->buf->len - conn->handled_len); + + if (written < 0 && errno == EAGAIN) + return TRUE; + else if (written <= 0) { + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + if (!initial) msn_soap_connection_handle_next(conn); + return FALSE; } + conn->handled_len += written; + + if (conn->handled_len < conn->buf->len) + return TRUE; + + /* we are done! */ + g_string_free(conn->buf, TRUE); + conn->buf = NULL; + conn->handled_len = 0; + conn->body_len = 0; + conn->response_code = 0; + conn->headers_done = FALSE; + conn->close_when_done = FALSE; + + purple_input_remove(conn->event_handle); + conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ, + msn_soap_read_cb, conn); + return TRUE; +} + +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" + "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 ? req->message->action : "", + 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); + + purple_debug_info("soap", "%s\n", conn->buf->str); + + 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); + if (!msn_soap_write_cb_internal(conn, conn->ssl->fd, PURPLE_INPUT_WRITE, TRUE)) { + /* Not connected => reconnect and retry */ + purple_debug_info("soap", "not connected, reconnecting\n"); + + conn->connected = FALSE; + conn->current_request = NULL; + msn_soap_connection_sanitize(conn, FALSE); + + g_queue_push_head(conn->queue, req); + conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); + } + + g_free(body); + } + } + + 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); } -/*Post the soap request action*/ -void -msn_soap_post_request(MsnSoapConn *soapconn, MsnSoapReq *request) +static void +msn_soap_message_send_internal(MsnSession *session, + MsnSoapMessage *message, const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data, gboolean first) { - 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 + 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_sanitize(MsnSoapConnection *conn, gboolean disconnect) +{ + if (conn->event_handle) { + purple_input_remove(conn->event_handle); + conn->event_handle = 0; + } - msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING); - request_str = g_strdup_printf( - "POST %s HTTP/1.1\r\n" - "SOAPAction: %s\r\n" - "Content-Type:text/xml; charset=utf-8\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" - "%s", - request->login_path, - request->soap_action, - request->login_host, - strlen(request->body), - request->body - ); + if (conn->message) { + msn_soap_message_destroy(conn->message); + conn->message = NULL; + } + + if (conn->buf) { + g_string_free(conn->buf, TRUE); + conn->buf = NULL; + } + + if (conn->ssl && (disconnect || conn->close_when_done)) { + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + } -#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",request_str, formattedstr); - g_free(formattedstr); - xmlnode_free(node); + if (conn->current_request) { + msn_soap_request_destroy(conn->current_request, FALSE); + conn->current_request = NULL; } - else - purple_debug_info("MSN SOAP","Failed to parse SOAP request being sent:\n%s\n", request_str); -#endif +} + +static void +msn_soap_connection_handle_next(MsnSoapConnection *conn) +{ + msn_soap_connection_sanitize(conn, FALSE); - /*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); + conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); + + if (conn->current_request) { + MsnSoapRequest *req = conn->current_request; + conn->current_request = NULL; + msn_soap_connection_destroy_foreach_cb(req, 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, FALSE); +} + +static void +msn_soap_connection_destroy(MsnSoapConnection *conn) +{ + if (conn->current_request) { + MsnSoapRequest *req = conn->current_request; + conn->current_request = NULL; + msn_soap_connection_destroy_foreach_cb(req, conn); + } + + msn_soap_connection_sanitize(conn, TRUE); + g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn); + g_queue_free(conn->queue); + + g_free(conn->host); + 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); + if (message->xml) + xmlnode_free(message->xml); + g_free(message); + } +} + +void +msn_soap_message_add_header(MsnSoapMessage *message, + const char *name, const char *value) +{ + char *header = g_strdup_printf("%s: %s\r\n", name, value); + + message->headers = g_slist_prepend(message->headers, header); +} + +static void +msn_soap_request_destroy(MsnSoapRequest *req, gboolean keep_message) +{ + g_free(req->path); + if (!keep_message) + msn_soap_message_destroy(req->message); + g_free(req); +} +