diff options
author | Eion Robb <eion@robbmob.com> | 2014-11-29 03:19:59 +0300 |
---|---|---|
committer | Eion Robb <eion@robbmob.com> | 2014-11-29 03:19:59 +0300 |
commit | 770ceae1dd2666151ae02a63432224349ed90905 (patch) | |
tree | 886a9e12f3893dee612223133ec0c535bf9cb432 /skypeweb/skypeweb_connection.c | |
parent | 9aec0db6affbb94f27dc4f1e62a52e7420f7a547 (diff) |
Initial check-in of SkypeWeb plugin
Diffstat (limited to 'skypeweb/skypeweb_connection.c')
-rw-r--r-- | skypeweb/skypeweb_connection.c | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/skypeweb/skypeweb_connection.c b/skypeweb/skypeweb_connection.c new file mode 100644 index 0000000..0a68adb --- /dev/null +++ b/skypeweb/skypeweb_connection.c @@ -0,0 +1,772 @@ + + +#include "skypeweb_connection.h" + +#if !PURPLE_VERSION_CHECK(3, 0, 0) + #define purple_connection_error purple_connection_error_reason +#endif + +#if !GLIB_CHECK_VERSION (2, 22, 0) +#define g_hostname_is_ip_address(hostname) (g_ascii_isdigit(hostname[0]) && g_strstr_len(hostname, 4, ".")) +#endif + +static void skypeweb_attempt_connection(SkypeWebConnection *); +static void skypeweb_next_connection(SkypeWebAccount *sa); + +#include <zlib.h> + +static gchar *skypeweb_gunzip(const guchar *gzip_data, gssize *len_ptr) +{ + gsize gzip_data_len = *len_ptr; + z_stream zstr; + int gzip_err = 0; + gchar *data_buffer; + gulong gzip_len = G_MAXUINT16; + GString *output_string = NULL; + + data_buffer = g_new0(gchar, gzip_len); + + zstr.next_in = NULL; + zstr.avail_in = 0; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = 0; + gzip_err = inflateInit2(&zstr, MAX_WBITS+32); + if (gzip_err != Z_OK) + { + g_free(data_buffer); + purple_debug_error("skypeweb", "no built-in gzip support in zlib\n"); + return NULL; + } + + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + + if (gzip_err == Z_DATA_ERROR) + { + inflateEnd(&zstr); + inflateInit2(&zstr, -MAX_WBITS); + if (gzip_err != Z_OK) + { + g_free(data_buffer); + purple_debug_error("skypeweb", "Cannot decode gzip header\n"); + return NULL; + } + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + } + output_string = g_string_new(""); + while (gzip_err == Z_OK) + { + //append data to buffer + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + //reset buffer pointer + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + } + if (gzip_err == Z_STREAM_END) + { + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + } else { + purple_debug_error("skypeweb", "gzip inflate error\n"); + } + inflateEnd(&zstr); + + g_free(data_buffer); + + if (len_ptr) + *len_ptr = output_string->len; + + return g_string_free(output_string, FALSE); +} + +void +skypeweb_connection_close(SkypeWebConnection *skypewebcon) +{ + skypewebcon->sa->conns = g_slist_remove(skypewebcon->sa->conns, skypewebcon); + + if (skypewebcon->connect_data != NULL) { + purple_proxy_connect_cancel(skypewebcon->connect_data); + skypewebcon->connect_data = NULL; + } + + if (skypewebcon->ssl_conn != NULL) { + purple_ssl_close(skypewebcon->ssl_conn); + skypewebcon->ssl_conn = NULL; + } + + if (skypewebcon->fd >= 0) { + close(skypewebcon->fd); + skypewebcon->fd = -1; + } + + if (skypewebcon->input_watcher > 0) { + purple_input_remove(skypewebcon->input_watcher); + skypewebcon->input_watcher = 0; + } + + purple_timeout_remove(skypewebcon->timeout_watcher); + + g_free(skypewebcon->rx_buf); + skypewebcon->rx_buf = NULL; + skypewebcon->rx_len = 0; +} + +void skypeweb_connection_destroy(SkypeWebConnection *skypewebcon) +{ + skypeweb_connection_close(skypewebcon); + + if (skypewebcon->request != NULL) + g_string_free(skypewebcon->request, TRUE); + + g_free(skypewebcon->url); + g_free(skypewebcon->hostname); + g_free(skypewebcon); +} + +static void skypeweb_update_cookies(SkypeWebAccount *sa, const gchar *headers) +{ + const gchar *cookie_start; + const gchar *cookie_end; + gchar *cookie_name; + gchar *cookie_value; + int header_len; + + g_return_if_fail(headers != NULL); + + header_len = strlen(headers); + + /* look for the next "Set-Cookie: " */ + /* grab the data up until ';' */ + cookie_start = headers; + while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && + (cookie_start - headers) < header_len) + { + cookie_start += 14; + cookie_end = strchr(cookie_start, '='); + cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end + 1; + cookie_end = strchr(cookie_start, ';'); + cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end; + + g_hash_table_replace(sa->cookie_table, cookie_name, + cookie_value); + } +} + +static void skypeweb_connection_process_data(SkypeWebConnection *skypewebcon) +{ + gssize len; + gchar *tmp; + + len = skypewebcon->rx_len; + tmp = g_strstr_len(skypewebcon->rx_buf, len, "\r\n\r\n"); + if (tmp == NULL) { + /* This is a corner case that occurs when the connection is + * prematurely closed either on the client or the server. + * This can either be no data at all or a partial set of + * headers. We pass along the data to be good, but don't + * do any fancy massaging. In all likelihood the result will + * be tossed by the connection callback func anyways + */ + tmp = g_strndup(skypewebcon->rx_buf, len); + } else { + tmp += 4; + len -= g_strstr_len(skypewebcon->rx_buf, len, "\r\n\r\n") - + skypewebcon->rx_buf + 4; + tmp = g_memdup(tmp, len + 1); + tmp[len] = '\0'; + skypewebcon->rx_buf[skypewebcon->rx_len - len] = '\0'; + skypeweb_update_cookies(skypewebcon->sa, skypewebcon->rx_buf); + + if (strstr(skypewebcon->rx_buf, "Content-Encoding: gzip")) + { + /* we've received compressed gzip data, decompress */ + gchar *gunzipped; + gunzipped = skypeweb_gunzip((const guchar *)tmp, &len); + g_free(tmp); + tmp = gunzipped; + } + } + + g_free(skypewebcon->rx_buf); + skypewebcon->rx_buf = NULL; + + if (skypewebcon->callback != NULL) { + if (!len) + { + purple_debug_error("skypeweb", "No data in response\n"); + //skypewebcon->callback(skypewebcon->sa, NULL, skypewebcon->user_data); + } else { + JsonParser *parser = json_parser_new(); + if (!json_parser_load_from_data(parser, tmp, len, NULL)) + { + if (skypewebcon->error_callback != NULL) { + skypewebcon->error_callback(skypewebcon->sa, tmp, len, skypewebcon->user_data); + } else { + purple_debug_error("skypeweb", "Error parsing response: %s\n", tmp); + } + } else { + JsonNode *root = json_parser_get_root(parser); + + //TODO + purple_debug_info("skypeweb", "Got response: %s\n", tmp); + purple_debug_info("skypeweb", "executing callback for %s\n", skypewebcon->url); + skypewebcon->callback(skypewebcon->sa, root, skypewebcon->user_data); + } + g_object_unref(parser); + } + } + + g_free(tmp); +} + +static void skypeweb_fatal_connection_cb(SkypeWebConnection *skypewebcon) +{ + PurpleConnection *pc = skypewebcon->sa->pc; + + purple_debug_error("skypeweb", "fatal connection error\n"); + + skypeweb_connection_destroy(skypewebcon); + + /* We died. Do not pass Go. Do not collect $200 */ + /* In all seriousness, don't attempt to call the normal callback here. + * That may lead to the wrong error message being displayed */ + purple_connection_error(pc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); + +} + +static void skypeweb_post_or_get_readdata_cb(gpointer data, gint source, + PurpleInputCondition cond) +{ + SkypeWebConnection *skypewebcon; + SkypeWebAccount *sa; + gchar buf[4096]; + gssize len; + + skypewebcon = data; + sa = skypewebcon->sa; + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL) { + len = purple_ssl_read(skypewebcon->ssl_conn, + buf, sizeof(buf) - 1); + } else { + len = recv(skypewebcon->fd, buf, sizeof(buf) - 1, 0); + } + + if (len < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + /* Try again later */ + return; + } + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL && skypewebcon->rx_len > 0) { + /* + * This is a slightly hacky workaround for a bug in either + * GNU TLS or in the SSL implementation on skypeweb's web + * servers. The sequence of events is: + * 1. We attempt to read the first time and successfully read + * the server's response. + * 2. We attempt to read a second time and libpurple's call + * to gnutls_record_recv() returns the error + * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or + * "A TLS packet with unexpected length was received." + * + * Normally the server would have closed the connection + * cleanly and this second read() request would have returned + * 0. Or maybe it's normal for SSL connections to be severed + * in this manner? In any case, this differs from the behavior + * of the standard recv() system call. + */ + purple_debug_warning("skypeweb", + "ssl error, but data received. attempting to continue\n"); + } else { + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_fatal_connection_cb(skypewebcon); + } + return; + } + } + + if (len > 0) + { + buf[len] = '\0'; + + skypewebcon->rx_buf = g_realloc(skypewebcon->rx_buf, + skypewebcon->rx_len + len + 1); + memcpy(skypewebcon->rx_buf + skypewebcon->rx_len, buf, len + 1); + skypewebcon->rx_len += len; + + /* Wait for more data before processing */ + return; + } + + /* The server closed the connection, let's parse the data */ + skypeweb_connection_process_data(skypewebcon); + + skypeweb_connection_destroy(skypewebcon); + + skypeweb_next_connection(sa); +} + +static void skypeweb_post_or_get_ssl_readdata_cb (gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) +{ + skypeweb_post_or_get_readdata_cb(data, -1, cond); +} + +static void skypeweb_post_or_get_connect_cb(gpointer data, gint source, + const gchar *error_message) +{ + SkypeWebConnection *skypewebcon; + gssize len; + + skypewebcon = data; + skypewebcon->connect_data = NULL; + + if (error_message) + { + purple_debug_error("skypeweb", "post_or_get_connect failure to %s\n", skypewebcon->url); + purple_debug_error("skypeweb", "post_or_get_connect_cb %s\n", + error_message); + skypeweb_fatal_connection_cb(skypewebcon); + return; + } + + skypewebcon->fd = source; + + /* TODO: Check the return value of write() */ + len = write(skypewebcon->fd, skypewebcon->request->str, + skypewebcon->request->len); + skypewebcon->input_watcher = purple_input_add(skypewebcon->fd, + PURPLE_INPUT_READ, + skypeweb_post_or_get_readdata_cb, skypewebcon); +} + +static void skypeweb_post_or_get_ssl_connect_cb(gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) +{ + SkypeWebConnection *skypewebcon; + gssize len; + + skypewebcon = data; + + purple_debug_info("skypeweb", "post_or_get_ssl_connect_cb\n"); + + /* TODO: Check the return value of write() */ + len = purple_ssl_write(skypewebcon->ssl_conn, + skypewebcon->request->str, skypewebcon->request->len); + purple_ssl_input_add(skypewebcon->ssl_conn, + skypeweb_post_or_get_ssl_readdata_cb, skypewebcon); +} + +static void skypeweb_host_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + GSList *host_lookup_list; + struct sockaddr_in *addr; + gchar *hostname; + gchar *ip_address; + SkypeWebAccount *sa; + PurpleDnsQueryData *query; + + /* Extract variables */ + host_lookup_list = data; + + sa = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + hostname = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + query = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + + /* The callback has executed, so we no longer need to keep track of + * the original query. This always needs to run when the cb is + * executed. */ + sa->dns_queries = g_slist_remove(sa->dns_queries, query); + + /* Any problems, capt'n? */ + if (error_message != NULL) + { + purple_debug_warning("skypeweb", + "Error doing host lookup: %s\n", error_message); + return; + } + + if (hosts == NULL) + { + purple_debug_warning("skypeweb", + "Could not resolve host name\n"); + return; + } + + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Copy the address then free it... */ + addr = hosts->data; + ip_address = g_strdup(inet_ntoa(addr->sin_addr)); + g_free(addr); + hosts = g_slist_delete_link(hosts, hosts); + + /* + * DNS lookups can return a list of IP addresses, but we only cache + * the first one. So free the rest. + */ + while (hosts != NULL) + { + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address... */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } + + g_hash_table_insert(sa->hostname_ip_cache, hostname, ip_address); +} + +static void skypeweb_cookie_foreach_cb(gchar *cookie_name, + gchar *cookie_value, GString *str) +{ + /* TODO: Need to escape name and value? */ + g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); +} + +/** + * Serialize the sa->cookie_table hash table to a string. + */ +gchar *skypeweb_cookies_to_string(SkypeWebAccount *sa) +{ + GString *str; + + str = g_string_new(NULL); + + g_hash_table_foreach(sa->cookie_table, + (GHFunc)skypeweb_cookie_foreach_cb, str); + + return g_string_free(str, FALSE); +} + +static void skypeweb_ssl_connection_error(PurpleSslConnection *ssl, + PurpleSslErrorType errortype, gpointer data) +{ + SkypeWebConnection *skypewebcon = data; + SkypeWebAccount *sa = skypewebcon->sa; + PurpleConnection *pc = sa->pc; + + skypewebcon->ssl_conn = NULL; + + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_connection_destroy(skypewebcon); + purple_connection_ssl_error(pc, errortype); + } +} + +SkypeWebConnection * +skypeweb_post_or_get(SkypeWebAccount *sa, SkypeWebMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + SkypeWebProxyCallbackFunc callback_func, gpointer user_data, + gboolean keepalive) +{ + GString *request; + gchar *cookies; + SkypeWebConnection *skypewebcon; + gchar *real_url; + gboolean is_proxy = FALSE; + const gchar *user_agent; + const gchar* const *languages; + gchar *language_names; + PurpleProxyInfo *proxy_info = NULL; + gchar *proxy_auth; + gchar *proxy_auth_base64; + + /* TODO: Fix keepalive and use it as much as possible */ + keepalive = FALSE; + + if (host == NULL) + host = "api.skype.com"; + + if (sa && sa->account) + { + if (purple_account_get_bool(sa->account, "use-https", TRUE)) + method |= SKYPEWEB_METHOD_SSL; + } + + if (sa && sa->account && !(method & SKYPEWEB_METHOD_SSL)) + { + proxy_info = purple_proxy_get_setup(sa->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + { + is_proxy = TRUE; + } + } + if (is_proxy == TRUE) + { + real_url = g_strdup_printf("http://%s%s", host, url); + } else { + real_url = g_strdup(url); + } + + cookies = skypeweb_cookies_to_string(sa); + //user_agent = purple_account_get_string(sa->account, "user-agent", "SkypeWeb 1.2.0 / iPhone"); + + if (method & SKYPEWEB_METHOD_POST && !postdata) + postdata = ""; + + /* Build the request */ + request = g_string_new(NULL); + g_string_append_printf(request, "%s %s HTTP/1.0\r\n", + ((method & SKYPEWEB_METHOD_POST) ? "POST" : ((method & SKYPEWEB_METHOD_PUT) ? "PUT" : ((method & SKYPEWEB_METHOD_DELETE) ? "DELETE" : "GET"))), + real_url); + if (is_proxy == FALSE) + g_string_append_printf(request, "Host: %s\r\n", host); + g_string_append_printf(request, "Connection: %s\r\n", + (keepalive ? "Keep-Alive" : "close")); + //g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); + if (method & SKYPEWEB_METHOD_POST) { + + if (postdata && postdata[0] == '[' || postdata[0] == '{') { + g_string_append(request, "Content-Type: application/json\r\n"); // hax + } else { + g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded\r\n"); + } + g_string_append_printf(request, "Content-length: %zu\r\n", strlen(postdata)); + } + g_string_append_printf(request, "Accept: */*\r\n"); + + if (g_str_equal(host, SKYPEWEB_CONTACTS_HOST)) { + g_string_append_printf(request, "X-Skypetoken: %s\r\n", sa->skype_token); + g_string_append(request, "X-Stratus-Caller: swx-skype.com\r\n"); + g_string_append(request, "X-Stratus-Request: abcd1234\r\n"); + g_string_append(request, "Origin: https://web.skype.com\r\n"); + g_string_append(request, "Referer: https://web.skype.com/main\r\n"); + g_string_append(request, "Accept: application/json; ver=1.0;\r\n"); + } else if (g_str_equal(host, SKYPEWEB_MESSAGES_HOST)) { + g_string_append_printf(request, "RegistrationToken: %s\r\n", sa->registration_token); + g_string_append(request, "Referer: https://web.skype.com/main\r\n"); + g_string_append(request, "Accept: application/json; ver=1.0;\r\n"); + g_string_append(request, "ClientInfo: os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=swx-skype.com; clientVer=908/1.0.0.20\r\n"); + } + + //g_string_append_printf(request, "Cookie: %s\r\n", cookies); + g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); + if (is_proxy == TRUE) + { + if (purple_proxy_info_get_username(proxy_info) && + purple_proxy_info_get_password(proxy_info)) + { + proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); + proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); + g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); + g_free(proxy_auth_base64); + g_free(proxy_auth); + } + } + + /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ + languages = g_get_language_names(); + language_names = g_strjoinv(", ", (gchar **)languages); + purple_util_chrreplace(language_names, '_', '-'); + g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); + g_free(language_names); + + purple_debug_info("skypeweb", "getting url %s\n", url); + + g_string_append_printf(request, "\r\n"); + if (method & SKYPEWEB_METHOD_POST) + g_string_append_printf(request, "%s", postdata); + + /* If it needs to go over a SSL connection, we probably shouldn't print + * it in the debug log. Without this condition a user's password is + * printed in the debug log */ + if (method == SKYPEWEB_METHOD_POST) + purple_debug_info("skypeweb", "sending request data:\n%s\n", postdata); + + purple_debug_misc("skypeweb", "sending headers:\n%s\n", request->str); + + g_free(cookies); + + skypewebcon = g_new0(SkypeWebConnection, 1); + skypewebcon->sa = sa; + skypewebcon->url = real_url; + skypewebcon->method = method; + skypewebcon->hostname = g_strdup(host); + skypewebcon->request = request; + skypewebcon->callback = callback_func; + skypewebcon->user_data = user_data; + skypewebcon->fd = -1; + skypewebcon->connection_keepalive = keepalive; + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + + return skypewebcon; +} + +static void skypeweb_next_connection(SkypeWebAccount *sa) +{ + SkypeWebConnection *skypewebcon; + + g_return_if_fail(sa != NULL); + + if (!g_queue_is_empty(sa->waiting_conns)) + { + if(g_slist_length(sa->conns) < SKYPEWEB_MAX_CONNECTIONS) + { + skypewebcon = g_queue_pop_tail(sa->waiting_conns); + skypeweb_attempt_connection(skypewebcon); + } + } +} + + +static gboolean +skypeweb_connection_timedout(gpointer userdata) +{ + SkypeWebConnection *skypewebcon = userdata; + SkypeWebAccount *sa = skypewebcon->sa; + + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_fatal_connection_cb(skypewebcon); + } + + return FALSE; +} + +static void skypeweb_attempt_connection(SkypeWebConnection *skypewebcon) +{ + gboolean is_proxy = FALSE; + SkypeWebAccount *sa = skypewebcon->sa; + PurpleProxyInfo *proxy_info = NULL; + + if (sa && sa->account && !(skypewebcon->method & SKYPEWEB_METHOD_SSL)) + { + proxy_info = purple_proxy_get_setup(sa->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + { + is_proxy = TRUE; + } + } + +#if 0 + /* Connection to attempt retries. This code doesn't work perfectly, but + * remains here for future reference if needed */ + if (time(NULL) - skypewebcon->request_time > 5) { + /* We've continuously tried to remake this connection for a + * bit now. It isn't happening, sadly. Time to die. */ + purple_debug_error("skypeweb", "could not connect after retries\n"); + skypeweb_fatal_connection_cb(skypewebcon); + return; + } + + purple_debug_info("skypeweb", "making connection attempt\n"); + + /* TODO: If we're retrying the connection, consider clearing the cached + * DNS value. This will require some juggling with the hostname param */ + /* TODO/FIXME: This retries almost instantenously, which in some cases + * runs at blinding speed. Slow it down. */ + /* TODO/FIXME: this doesn't retry properly on non-ssl connections */ +#endif + + sa->conns = g_slist_prepend(sa->conns, skypewebcon); + + /* + * Do a separate DNS lookup for the given host name and cache it + * for next time. + * + * TODO: It would be better if we did this before we call + * purple_proxy_connect(), so we could re-use the result. + * Or even better: Use persistent HTTP connections for servers + * that we access continually. + * + * TODO: This cache of the hostname<-->IP address does not respect + * the TTL returned by the DNS server. We should expire things + * from the cache after some amount of time. + */ + if (!is_proxy && !(skypewebcon->method & SKYPEWEB_METHOD_SSL) && !g_hostname_is_ip_address(skypewebcon->hostname)) + { + /* Don't do this for proxy connections, since proxies do the DNS lookup */ + gchar *host_ip; + + host_ip = g_hash_table_lookup(sa->hostname_ip_cache, skypewebcon->hostname); + if (host_ip != NULL) { + g_free(skypewebcon->hostname); + skypewebcon->hostname = g_strdup(host_ip); + } else if (sa->account && !sa->account->disconnecting) { + GSList *host_lookup_list = NULL; + PurpleDnsQueryData *query; + + host_lookup_list = g_slist_prepend( + host_lookup_list, g_strdup(skypewebcon->hostname)); + host_lookup_list = g_slist_prepend( + host_lookup_list, sa); + + query = purple_dnsquery_a( +#if PURPLE_VERSION_CHECK(3, 0, 0) + skypewebcon->sa->account, +#endif + skypewebcon->hostname, 80, + skypeweb_host_lookup_cb, host_lookup_list); + sa->dns_queries = g_slist_prepend(sa->dns_queries, query); + host_lookup_list = g_slist_append(host_lookup_list, query); + } + } + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL) { + skypewebcon->ssl_conn = purple_ssl_connect(sa->account, skypewebcon->hostname, + 443, skypeweb_post_or_get_ssl_connect_cb, + skypeweb_ssl_connection_error, skypewebcon); + } else { + skypewebcon->connect_data = purple_proxy_connect(NULL, sa->account, + skypewebcon->hostname, 80, skypeweb_post_or_get_connect_cb, skypewebcon); + } + + skypewebcon->timeout_watcher = purple_timeout_add_seconds(120, skypeweb_connection_timedout, skypewebcon); + + return; +} + |