Mercurial > pidgin.yaz
view libpurple/protocols/msn/soap.c @ 20477:9a2a4a0c0003
Add the possibility to create an Address Book, useful for newly registered MSN users.
When changing friendly name, send the new one to the SOAP server in the PRP msn command callback, with escaped html entity chars. Fixes #1294 .
Handle EBADF error sometimes received in SOAP server read callback (observed in win32).
Misc cleanups.
author | Carlos Silva <typ0@pidgin.im> |
---|---|
date | Tue, 07 Aug 2007 02:37:58 +0000 |
parents | 530a92d50c5e |
children | c1c4468207fa |
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_info("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_info("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_info("MSN SOAP","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){ 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); } 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: #ifdef MSN_SOAP_DEBUG purple_debug_info("MSN SOAP", "msn_soap_read(): %s, returning len = %d.\n", strerror(errno), len); #endif 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 { #ifdef MSN_SOAP_DEBUG purple_debug_info("MSN SOAP", "Allocating space for more %d bytes from incoming data. Total space now (according to soapconn->read_len = %d\n", len, soapconn->read_len); #endif 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); } } #ifdef MSN_SOAP_DEBUG purple_debug_info("MSN SOAP","Read SOAP bytes: %d\n", len); if (len > -1) { gchar * soapbody = NULL; xmlnode * node = NULL; soapbody = g_strstr_len(soapconn->read_buf, soapconn->read_len, "\r\n\r\n"); if (soapbody != NULL) node = xmlnode_from_str(soapbody+4, -1); if (node != NULL) { gchar *pretty = xmlnode_to_formatted_str(node, NULL); gchar *http_headers, *delimiter; delimiter = g_strstr_len(soapconn->read_buf, soapconn->read_len,"\r\n\r\n"); if (delimiter != NULL) { http_headers = g_strndup(soapconn->read_buf, delimiter + 4 - soapconn->read_buf); purple_debug_info("MSN SOAP","Nexus server read data:\n%s%s\n", http_headers, pretty); g_free(http_headers); } else purple_debug_info("MSN SOAP","Nexus server read data:\n%s\n", soapconn->read_buf); g_free(pretty); xmlnode_free(node); } else purple_debug_info("MSN SOAP","Received data from Nexus server:\n%s\n", soapconn->read_buf); } #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 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 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*/ 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); } msn_soap_free_read_buf(soapconn); } 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, 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, 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->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*/ purple_debug_info("MSN SOAP","No connection to SOAP server. Connecting...\n"); msn_soap_init_func(soapconn); msn_soap_connect(soapconn); return; } purple_debug_info("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 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; #ifdef MSN_SOAP_DEBUG xmlnode * node; purple_debug_info("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); #ifdef MSN_SOAP_DEBUG node = xmlnode_from_str(request->body, -1); if (node != NULL) { char *pretty = xmlnode_to_formatted_str(node, NULL); purple_debug_info("MSN SOAP","Posting request to SOAP server:\n%s%s\n",soap_head, pretty); g_free(pretty); xmlnode_free(node); } else purple_debug_info("MSN SOAP","Failed to parse SOAP request:\n%s\n", request_str); #endif g_free(soap_head); /*free read buffer*/ msn_soap_free_read_buf(soapconn); /*post it to server*/ msn_soap_write(soapconn,request_str,request->written_cb); }