pidgin/purple-plugin-pack

90b11f4ed1c0
Parents 853ff768efd0
Children 0260dc984adc
Add Eion's ning, okcupid, and omegle prpls.
--- a/common/Makefile.am Sun Dec 12 12:43:31 2010 -0500
+++ b/common/Makefile.am Sun Dec 12 18:10:38 2010 -0500
@@ -1,5 +1,6 @@
EXTRA_DIST = \
glib_compat.h \
gtk_template.c \
+ libjson-glib-1.0.dll \
purple_template.c \
pp_internal.h
Binary file common/libjson-glib-1.0.dll has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/libning.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,555 @@
+/*
+ * libning
+ *
+ * libning is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libning.h"
+#include "ning_connection.h"
+#include "ning_chat.h"
+
+JsonObject *ning_json_parse(const gchar *data, gssize data_len)
+{
+ JsonParser *parser;
+ JsonNode *root_node;
+ JsonObject *root_obj;
+
+ parser = json_parser_new();
+ json_parser_load_from_data(parser, data, (gssize) data_len, NULL);
+
+ root_node = json_parser_get_root(parser);
+ root_obj = json_node_dup_object(root_node);
+
+ g_object_unref(parser);
+
+ return root_obj;
+}
+
+gchar *
+build_user_json(NingAccount *na)
+{
+ gchar *user_json;
+ gchar *escaped_name;
+ gchar *escaped_icon;
+ gchar *escaped_id;
+
+ if (na && na->name)
+ {
+ escaped_name = g_strescape(na->name, "");
+ } else {
+ escaped_name = g_strdup("");
+ }
+ if (na && na->icon_url)
+ {
+ escaped_icon = g_strescape(na->icon_url, "");
+ } else {
+ escaped_icon = g_strdup("");
+ }
+ if (na && na->ning_id)
+ {
+ escaped_id = g_strescape(na->ning_id, "");
+ } else {
+ escaped_id = g_strdup("");
+ }
+
+ user_json = g_strdup_printf("{\"name\":\"%s\",\"iconUrl\":\"%s\",\"isAdmin\":\"0\",\"ningId\":\"%s\",\"isNC\":\"0\"}",
+ escaped_name, escaped_icon, escaped_id);
+
+ g_free(escaped_name);
+ g_free(escaped_icon);
+ g_free(escaped_id);
+
+ return user_json;
+}
+
+
+
+/******************************************************************************/
+/* PRPL functions */
+/******************************************************************************/
+
+static const char *ning_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
+{
+ return "ning";
+}
+
+
+static GList *ning_statuses(PurpleAccount *account)
+{
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ purple_debug_info("ning", "statuses\n");
+
+ /* Ning people are either online or offline */
+
+ status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ purple_debug_info("ning", "statuses return\n");
+
+ return types;
+}
+
+void ning_chat_login_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
+{
+ JsonObject *obj;
+ const gchar *result;
+ const gchar *roomId;
+
+ obj = ning_json_parse(data, data_len);
+
+ purple_debug_info("ning", "chat_login_cb: %s\n", data?data:"(null)");
+
+ if (data == NULL || data_len == 0)
+ return;
+
+ //{"command": "login","result": "ok","roomId": "thoughtleaders.thoughtleaders",
+ // "count": 2,"token": "37lfxean70eqh_122d86d5cf7_6f95cb8e_122d49f8e48"}
+
+ result = json_node_get_string(json_object_get_member(obj, "result"));
+ if (!result || !g_str_equal(result, "ok"))
+ {
+ purple_connection_error(na->pc, _("Could not log on"));
+ return;
+ }
+ purple_connection_update_progress(na->pc, _("Joining public chat"), 5, 5);
+ purple_connection_set_state(na->pc, PURPLE_CONNECTED);
+
+ g_free(na->chat_token);
+ na->chat_token = g_strdup(json_node_get_string(json_object_get_member(obj, "token")));
+
+ roomId = json_node_get_string(json_object_get_member(obj, "roomId"));
+
+ ning_join_chat_by_name(na, roomId);
+
+ json_object_unref(obj);
+}
+
+void ning_chat_redir_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
+{
+ JsonObject *obj;
+ gchar *postdata, *encoded_app, *encoded_id;
+ gchar *user_json, *user_encoded;
+
+ purple_debug_info("ning", "ning_chat_redir_cb: %s\n", data?data:"(null)");
+
+ //We get a randomly generated chat domain to use
+ // eg {"domain": "3841.chat07.ningim.com"}
+ obj = ning_json_parse(data, data_len);
+
+ g_free(na->chat_domain);
+ na->chat_domain = g_strdup(json_node_get_string(json_object_get_member(obj, "domain")));
+
+ json_object_unref(obj);
+
+ //Use our new domain to log into the chat servers
+ purple_connection_update_progress(na->pc, _("Logging into chat"), 4, 5);
+
+ encoded_app = g_strdup(purple_url_encode(na->ning_app));
+ encoded_id = g_strdup(purple_url_encode(na->ning_id));
+
+ user_json = build_user_json(na);
+ user_encoded = g_strdup(purple_url_encode(user_json));
+
+ postdata = g_strdup_printf("a=%s&t=%s%s&i=%s&user=%s", encoded_app, encoded_app, encoded_id, encoded_id, user_encoded);
+ ning_post_or_get(na, NING_METHOD_POST, na->chat_domain,
+ "/xn/presence/login", postdata, ning_chat_login_cb, NULL, FALSE);
+
+ g_free(postdata);
+ g_free(encoded_app);
+ g_free(encoded_id);
+ g_free(user_encoded);
+ g_free(user_json);
+}
+
+void ning_login_home_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
+{
+ //We need to look for
+ //<script>window.bzplcm.add({"app":"thoughtleaders","user":"37lfxean70eqh"
+ //and
+ //xg.token = 'b1a7f3ce1719481334cdcc5fe8eabcaa';
+ const gchar *start_string = "\nning = ";
+ const gchar *mid_string = "}};\n";
+ const gchar *xgtoken_start = "xg.token = '";
+ gchar *tmp, *ning_json_string, *xg_token;
+ gchar *url;
+ JsonObject *obj, *profile;
+
+ tmp = g_strstr_len(data, data_len, start_string);
+ if (tmp == NULL)
+ {
+ purple_connection_error(na->pc, _("NingID not found"));
+ return;
+ }
+ tmp += strlen(start_string);
+ ning_json_string = g_strndup(tmp, strstr(tmp, mid_string) - tmp + 2);
+ purple_debug_info("ning", "ning_json_string: %s\n", ning_json_string);
+
+ obj = ning_json_parse(ning_json_string, strlen(ning_json_string));
+ profile = json_node_get_object(json_object_get_member(obj, "CurrentProfile"));
+ g_free(na->ning_id);
+ na->ning_id = g_strdup(json_node_get_string(json_object_get_member(profile, "id")));
+ g_free(na->name);
+ na->name = g_strdup(json_node_get_string(json_object_get_member(profile, "fullName")));
+ g_free(na->icon_url);
+ na->icon_url = g_strdup_printf("%s&width=16&height=16", json_node_get_string(json_object_get_member(profile, "photoUrl")));
+
+ tmp = g_strstr_len(data, data_len, xgtoken_start);
+ if (tmp == NULL)
+ {
+ purple_connection_error(na->pc, _("xgToken not found"));
+ return;
+ }
+ tmp += strlen(xgtoken_start);
+ xg_token = g_strndup(tmp, strchr(tmp, '\'') - tmp);
+ g_free(na->xg_token);
+ na->xg_token = xg_token;
+
+ //Now we should have everything we need to sign into chat
+ purple_connection_update_progress(na->pc, _("Fetching chat server"), 3, 5);
+
+ url = g_strdup_printf("/xn/redirector/redirect?a=%s", purple_url_encode(na->ning_app));
+ ning_post_or_get(na, NING_METHOD_GET, "chat01.ningim.com",
+ url, NULL, ning_chat_redir_cb, NULL, FALSE);
+ g_free(url);
+}
+
+void ning_scan_cookies_for_id(gchar *key, gchar *value, NingAccount *na)
+{
+ if (g_str_has_prefix(key, "xn_id_"))
+ {
+ g_free(na->ning_app);
+ na->ning_app = g_strdup(&key[6]);
+ }
+}
+
+static void ning_login_cb(NingAccount *na, gchar *response, gsize len,
+ gpointer userdata)
+{
+ purple_connection_update_progress(na->pc, _("Fetching token"), 2, 4);
+
+ // ok, we're logged into the host website now
+
+ // Pull the host's Ning account id from the cookie
+ g_hash_table_foreach(na->cookie_table, (GHFunc)ning_scan_cookies_for_id, na);
+
+ //Load the homepage to grab the interesting info
+ ning_post_or_get(na, NING_METHOD_GET, purple_account_get_string(na->account, "host", NULL),
+ "/", NULL, ning_login_home_cb, NULL, FALSE);
+}
+
+static void ning_login(PurpleAccount *account)
+{
+ NingAccount *na;
+ gchar *postdata, *encoded_username, *encoded_password;
+ gchar *encoded_host, *url;
+ const gchar *host;
+
+ purple_debug_info("ning", "login\n");
+
+ /* Create account and initialize state */
+ na = g_new0(NingAccount, 1);
+ na->account = account;
+ na->pc = purple_account_get_connection(account);
+
+ na->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */
+
+ na->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ na->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ g_hash_table_replace(na->cookie_table, g_strdup("xg_cookie_check"),
+ g_strdup("1"));
+
+ account->gc->proto_data = na;
+
+ purple_connection_set_state(na->pc, PURPLE_CONNECTING);
+ purple_connection_update_progress(na->pc, _("Logging in"), 1, 4);
+
+ encoded_username = g_strdup(purple_url_encode(purple_account_get_username(account)));
+ encoded_password = g_strdup(purple_url_encode(purple_account_get_password(account)));
+
+ postdata = g_strdup_printf("xg_token=&emailAddress=%s&password=%s",
+ encoded_username, encoded_password);
+ g_free(encoded_username);
+ g_free(encoded_password);
+
+ host = purple_account_get_string(account, "host", NULL);
+ if (host == NULL || host[0] == '\0')
+ {
+ purple_connection_error(na->pc, _("Host not set"));
+ return;
+ }
+
+ encoded_host = g_strdup(purple_url_encode(host));
+ url = g_strdup_printf("/main/authorization/doSignIn?target=http%%3A%%2F%%2F%s", host);
+
+ ning_post_or_get(na, NING_METHOD_POST | NING_METHOD_SSL, host,
+ url, postdata, ning_login_cb, NULL, FALSE);
+ g_free(postdata);
+}
+
+static void ning_close(PurpleConnection *pc)
+{
+ NingAccount *na;
+ gchar *postdata;
+ gchar *host_encoded;
+ gchar *xg_token_encoded;
+ PurpleDnsQueryData *dns_query;
+
+ purple_debug_info("ning", "disconnecting account\n");
+
+ na = pc->proto_data;
+
+ host_encoded = g_strdup(purple_url_encode(purple_account_get_string(na->account, "host", "")));
+ xg_token_encoded = g_strdup(purple_url_encode(na->xg_token));
+
+ postdata = g_strdup_printf("target=%s&xg_token=%s", host_encoded, xg_token_encoded);
+
+ ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL),
+ "/main/authorization/signOut", postdata, NULL, NULL, FALSE);
+
+ g_free(host_encoded);
+ g_free(xg_token_encoded);
+ g_free(postdata);
+
+ purple_debug_info("ning", "destroying %d incomplete connections\n",
+ g_slist_length(na->conns));
+
+ while (na->conns != NULL)
+ ning_connection_destroy(na->conns->data);
+
+ while (na->dns_queries != NULL) {
+ dns_query = na->dns_queries->data;
+ purple_debug_info("ning", "canceling dns query for %s\n",
+ purple_dnsquery_get_host(dns_query));
+ na->dns_queries = g_slist_remove(na->dns_queries, dns_query);
+ purple_dnsquery_destroy(dns_query);
+ }
+
+ g_hash_table_destroy(na->cookie_table);
+ g_hash_table_destroy(na->hostname_ip_cache);
+
+ while (na->chats != NULL)
+ {
+ NingChat *chat = na->chats->data;
+ na->chats = g_list_remove(na->chats, chat);
+ purple_timeout_remove(chat->userlist_timer);
+ purple_timeout_remove(chat->message_poll_timer);
+ purple_conv_chat_left(PURPLE_CONV_CHAT(purple_find_chat(pc, chat->purple_id)));
+ g_free(chat->roomId);
+ g_free(chat->ning_hash);
+ g_free(chat);
+ }
+
+ g_free(na->ning_id);
+ g_free(na->name);
+ g_free(na->icon_url);
+ g_free(na->xg_token);
+ g_free(na->ning_app);
+
+ g_free(na);
+}
+
+#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
+static GHashTable *ning_get_account_text_table(PurpleAccount *account)
+{
+ GHashTable *table;
+
+ table = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_hash_table_insert(table, "login_label", (gpointer)_("Email Address..."));
+
+ return table;
+}
+#endif
+
+void
+ning_change_passwd(PurpleConnection *pc, const char *old_pass, const char *new_pass)
+{
+ NingAccount *na;
+ PurpleAccount *account;
+ gchar *encoded_username;
+ gchar *encoded_password;
+ gchar *encoded_token;
+ gchar *postdata;
+
+ if (pc == NULL)
+ return;
+ na = pc->proto_data;
+ if (na == NULL || na->xg_token == NULL)
+ return;
+ account = pc->account;
+ if (account == NULL)
+ return;
+
+ encoded_username = g_strdup(purple_url_encode(purple_account_get_username(account)));
+ encoded_password = g_strdup(purple_url_encode(new_pass));
+ encoded_token = g_strdup(purple_url_encode(na->xg_token));
+
+ postdata = g_strdup_printf("emailAddress=%s&password=%s&xg_token=%s",
+ encoded_username, encoded_password, encoded_token);
+
+ ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL),
+ "/profiles/settings/updateEmailAddress", postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+ g_free(encoded_token);
+ g_free(encoded_password);
+ g_free(encoded_username);
+}
+
+
+/******************************************************************************/
+/* Plugin functions */
+/******************************************************************************/
+
+static gboolean plugin_load(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean plugin_unload(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static void plugin_init(PurplePlugin *plugin)
+{
+ PurpleAccountOption *option;
+ PurplePluginInfo *info = plugin->info;
+ PurplePluginProtocolInfo *prpl_info = info->extra_info;
+
+ option = purple_account_option_string_new("Host", "host", "");
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+}
+
+static PurplePluginProtocolInfo prpl_info = {
+ /* options */
+ 0,
+
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ NO_BUDDY_ICONS /* icon_spec */
+ /*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */
+ ning_list_icon, /* list_icon */
+ NULL, /* list_emblems */
+ NULL, /* status_text */
+ NULL, /* tooltip_text */
+ ning_statuses, /* status_types */
+ NULL, /* blist_node_menu */
+ NULL, /* chat_info */
+ NULL, /* chat_info_defaults */
+ ning_login, /* login */
+ ning_close, /* close */
+ ning_send_im, /* send_im */
+ NULL, /* set_info */
+ NULL, /* send_typing */
+ NULL, /* get_info */
+ NULL, /* set_status */
+ NULL, /* set_idle */
+ ning_change_passwd, /* change_passwd */
+ NULL, /* add_buddy */
+ NULL, /* add_buddies */
+ NULL, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ ning_join_chat, /* join_chat */
+ NULL, /* reject chat invite */
+ NULL, /* get_chat_name */
+ NULL, /* chat_invite */
+ NULL, /* chat_leave */
+ ning_chat_whisper, /* chat_whisper */
+ ning_chat_send, /* chat_send */
+ NULL, /* keepalive */
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ NULL, /* alias_buddy */
+ NULL, /* group_buddy */
+ NULL, /* rename_group */
+ NULL, /* buddy_free */
+ NULL, /* convo_closed */
+ purple_normalize_nocase,/* normalize */
+ NULL, /* set_buddy_icon */
+ NULL, /* remove_group */
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ NULL, /* send_file */
+ NULL, /* new_xfer */
+ NULL, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
+ ning_get_account_text_table, /* get_account_text_table */
+#else
+ (gpointer) sizeof(PurplePluginProtocolInfo)
+#endif
+};
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC,
+ 2, /* major_version */
+ 3, /* minor version */
+ PURPLE_PLUGIN_PROTOCOL, /* type */
+ NULL, /* ui_requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ "prpl-bigbrownchunx-ning", /* id */
+ "Ning", /* name */
+ NING_PLUGIN_VERSION, /* version */
+ N_("Ning Protocol Plugin"), /* summary */
+ N_("Ning Protocol Plugin"), /* description */
+ "Eion Robb <eionrobb@gmail.com>", /* author */
+ "", /* homepage */
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ &prpl_info, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PURPLE_INIT_PLUGIN(ning, plugin_init, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/libning.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,100 @@
+/*
+ * libning
+ *
+ * libning is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBNING_H
+#define LIBNING_H
+
+#define NING_PLUGIN_VERSION "0.01"
+#define NING_TEMP_GROUP_NAME "Ning Temp"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <json-glib/json-glib.h>
+
+#ifndef G_GNUC_NULL_TERMINATED
+# if __GNUC__ >= 4
+# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+# else
+# define G_GNUC_NULL_TERMINATED
+# endif /* __GNUC__ >= 4 */
+#endif /* G_GNUC_NULL_TERMINATED */
+
+#ifdef _WIN32
+# include "win32dep.h"
+# define dlopen(a,b) LoadLibrary(a)
+# define RTLD_LAZY
+# define dlsym(a,b) GetProcAddress(a,b)
+# define dlclose(a) FreeLibrary(a)
+#else
+# include <arpa/inet.h>
+# include <dlfcn.h>
+# include <netinet/in.h>
+# include <sys/socket.h>
+#endif
+
+#ifndef PURPLE_PLUGINS
+# define PURPLE_PLUGINS
+#endif
+
+#include "accountopt.h"
+#include "connection.h"
+#include "debug.h"
+#include "dnsquery.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "sslconn.h"
+#include "version.h"
+
+typedef struct _NingAccount NingAccount;
+
+struct _NingAccount {
+ PurpleAccount *account;
+ PurpleConnection *pc;
+ GHashTable *hostname_ip_cache;
+ GSList *conns; /**< A list of all active connections */
+ GSList *dns_queries;
+ GList *chats;
+ GHashTable *cookie_table;
+
+ time_t last_messages_download_time;
+
+ gchar *xg_token;
+
+ gchar *ning_app;
+ gchar *ning_id;
+ gchar *name;
+ gchar *icon_url;
+
+ gchar *chat_domain;
+ gchar *chat_token;
+};
+
+JsonObject *ning_json_parse(const gchar *data, gssize data_len);
+gchar *build_user_json(NingAccount *na);
+
+
+#endif /* LIBNING_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_chat.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,454 @@
+/*
+ * ning_chat.c
+ * pidgin-ning
+ *
+ * Created by MyMacSpace on 6/08/09.
+ * Copyright 2009 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "ning_chat.h"
+
+//list buddies
+//send message to chat
+//send whisper
+//poll for messages
+//fetch chat history
+//join chat
+
+/*
+void (*chat_leave)(PurpleConnection *, int id);
+void (*chat_whisper)(PurpleConnection *, int id, const char *who, const char *message);
+int (*chat_send)(PurpleConnection *, int id, const char *message, PurpleMessageFlags flags);
+void (*join_chat)(PurpleConnection *, GHashTable *components);
+PurpleConversation* purple_find_chat(const PurpleConnection *gc, int id);
+ */
+
+
+gint64 ning_time_kludge(gint initial_time)
+{
+ if (sizeof(gint) >= sizeof(gint64))
+ return initial_time;
+
+ gint64 now_millis = (gint64) time(NULL);
+ now_millis *= 1000;
+ now_millis &= 0xFFFFFFFF00000000LL;
+ gint64 final_time = now_millis | ((guint)initial_time);
+
+ return final_time;
+}
+
+
+void
+ning_chat_messages_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata)
+{
+ NingChat *chat;
+ JsonObject *obj;
+ JsonArray *array;
+ JsonObject *msgobj;
+ const gchar *type;
+ const gchar *body;
+ const gchar *roomId;
+ const gchar *targetId;
+ gint date;
+ JsonObject *sender;
+ const gchar *senderId;
+ PurpleBuddy *buddy;
+ PurpleGroup *group;
+ guint i;
+ guint64 time_kludge;
+
+ chat = userdata;
+
+ purple_debug_info("ning", "ning_chat_messages_cb: %s\n", response?response:"(null)");
+ obj = ning_json_parse(response, len);
+
+ if (json_object_has_member(obj, "hash"))
+ {
+ g_free(chat->ning_hash);
+ chat->ning_hash = g_strdup(json_node_get_string(json_object_get_member(obj, "hash")));
+ }
+
+ array = json_node_get_array(json_object_get_member(obj, "messages"));
+ for(i = 0; i < json_array_get_length(array); i++)
+ {
+ msgobj = json_node_get_object(json_array_get_element(array, i));
+ //type (message or private)
+ //body, date, roomId, targetId, sender(obj)
+ type = json_node_get_string(json_object_get_member(msgobj, "type"));
+ body = json_node_get_string(json_object_get_member(msgobj, "body"));
+ date = json_node_get_int(json_object_get_member(msgobj, "date"));
+ roomId = json_node_get_string(json_object_get_member(msgobj, "roomId"));
+ targetId = json_node_get_string(json_object_get_member(msgobj, "targetId"));
+ sender = json_node_get_object(json_object_get_member(msgobj, "sender"));
+ senderId = json_node_get_string(json_object_get_member(sender, "ningId"));
+
+ //Check that they're on the buddy list
+ buddy = purple_find_buddy(na->account, senderId);
+ if (!buddy)
+ {
+ //They aren't, so lets fake it
+ buddy = purple_buddy_new(na->account, senderId,
+ json_node_get_string(json_object_get_member(sender, "name")));
+ group = purple_find_group(NING_TEMP_GROUP_NAME);
+ if (!group)
+ {
+ group = purple_group_new(NING_TEMP_GROUP_NAME);
+ purple_blist_add_group(group, NULL);
+ purple_blist_node_set_flags(&group->node, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
+ }
+ purple_blist_add_buddy(buddy, NULL, group, NULL);
+ purple_blist_node_set_flags(&buddy->node, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
+ }
+
+ time_kludge = ning_time_kludge(date);
+
+ if (g_str_equal(type, "message"))
+ {
+ serv_got_chat_in(na->pc, chat->purple_id, senderId, PURPLE_MESSAGE_RECV, body, time_kludge);
+ } else if (g_str_equal(type, "private"))
+ {
+ serv_got_chat_in(na->pc, chat->purple_id, senderId,
+ PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_WHISPER, body, time_kludge);
+ } else {
+ purple_debug_info("ning", "unknown message type: %s\n", type);
+ }
+ }
+
+ json_object_unref(obj);
+}
+
+gboolean
+ning_chat_get_history(NingChat *chat)
+{
+ NingAccount *na;
+ gchar *url;
+ gchar *encoded_hash;
+ gchar *encoded_app;
+ gchar *encoded_id;
+ gchar *encoded_token;
+ gchar *encoded_room;
+
+ na = chat->na;
+
+ encoded_hash = g_strdup(purple_url_encode(chat->ning_hash));
+ encoded_app = g_strdup(purple_url_encode(na->ning_app));
+ encoded_id = g_strdup(purple_url_encode(na->ning_id));
+ encoded_token = g_strdup(purple_url_encode(na->chat_token));
+ encoded_room = g_strdup(purple_url_encode(chat->roomId));
+
+ url = g_strdup_printf("/xn/groupchat/list?h=%s&a=%s&i=%s&t=%s&r=%s", encoded_hash, encoded_app,
+ encoded_id, encoded_token, encoded_room);
+
+ ning_post_or_get(na, NING_METHOD_GET, na->chat_domain,
+ url, NULL,
+ ning_chat_messages_cb, chat, FALSE);
+
+ g_free(url);
+ g_free(encoded_room);
+ g_free(encoded_token);
+ g_free(encoded_id);
+ g_free(encoded_app);
+ g_free(encoded_hash);
+
+ return FALSE;
+}
+
+void
+ning_chat_get_users_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata)
+{
+ NingChat *chat;
+ JsonObject *obj;
+ JsonArray *array;
+ JsonObject *userobj;
+ int i;
+ const gchar *ningId;
+ const gchar *name;
+ //const gchar *iconUrl;
+ gboolean isAdmin;
+ PurpleConversation *conv;
+// PurpleBuddy *buddy;
+ PurpleConvChatBuddy *cbuddy;
+ PurpleConversationUiOps *uiops;
+
+ purple_debug_info("ning", "chat users: %s\n", response?response:"(null)");
+
+ chat = userdata;
+ obj = ning_json_parse(response, len);
+
+ if (json_object_has_member(obj, "hash"))
+ {
+ g_free(chat->ning_hash);
+ chat->ning_hash = g_strdup(json_node_get_string(json_object_get_member(obj, "hash")));
+ }
+
+ conv = purple_find_chat(na->pc, chat->purple_id);
+ uiops = purple_conversation_get_ui_ops(conv);
+
+ array = json_node_get_array(json_object_get_member(obj, "expired"));
+ for(i = 0; i < json_array_get_length(array); i++)
+ {
+ ningId = json_node_get_string(json_array_get_element(array, i));
+ purple_conv_chat_remove_user(PURPLE_CONV_CHAT(conv), ningId, NULL);
+ }
+
+ array = json_node_get_array(json_object_get_member(obj, "users"));
+ if (array && json_array_get_length(array) > 0)
+ {
+ purple_conv_chat_clear_users(PURPLE_CONV_CHAT(conv));
+ }
+ for(i = 0; i < json_array_get_length(array); i++)
+ {
+ userobj = json_node_get_object(json_array_get_element(array, i));
+ //isNC, iconUrl, name, ningId, isAdmin
+ ningId = json_node_get_string(json_object_get_member(userobj, "ningId"));
+ name = json_node_get_string(json_object_get_member(userobj, "name"));
+ isAdmin = json_node_get_boolean(json_object_get_member(userobj, "isAdmin"));
+
+ if (!purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), ningId))
+ {
+ purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv), ningId,
+ NULL, isAdmin?PURPLE_CBFLAGS_OP:PURPLE_CBFLAGS_NONE,
+ FALSE);
+ }
+ cbuddy = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), ningId);
+ if (cbuddy)
+ {
+ g_free(cbuddy->alias);
+ cbuddy->alias = g_strdup(name);
+ }
+ //Refresh the buddy
+ if (uiops && uiops->chat_rename_user)
+ {
+ purple_debug_info("ning", "try rename user %s to %s\n", ningId, name);
+ uiops->chat_rename_user(conv, ningId, ningId, name);
+ }
+ else if (uiops && uiops->chat_update_user)
+ {
+ purple_debug_info("ning", "try update user %s\n", ningId);
+ uiops->chat_update_user(conv, ningId);
+ }
+ }
+
+ json_object_unref(obj);
+}
+
+gboolean
+ning_chat_get_users(NingChat *chat)
+{
+ NingAccount *na;
+ gchar *url;
+ gchar *encoded_hash;
+ gchar *encoded_app;
+ gchar *encoded_id;
+ gchar *encoded_token;
+ gchar *encoded_room;
+
+ na = chat->na;
+
+ encoded_hash = g_strdup(purple_url_encode(chat->ning_hash));
+ encoded_app = g_strdup(purple_url_encode(na->ning_app));
+ encoded_id = g_strdup(purple_url_encode(na->ning_id));
+ encoded_token = g_strdup(purple_url_encode(na->chat_token));
+ encoded_room = g_strdup(purple_url_encode(chat->roomId));
+
+ url = g_strdup_printf("/xn/presence/list?h=%s&a=%s&i=%s&t=%s&r=%s", encoded_hash, encoded_app,
+ encoded_id, encoded_token, encoded_room);
+
+ ning_post_or_get(na, NING_METHOD_GET, na->chat_domain,
+ url, NULL,
+ ning_chat_get_users_cb, chat, FALSE);
+
+ g_free(url);
+ g_free(encoded_room);
+ g_free(encoded_token);
+ g_free(encoded_id);
+ g_free(encoded_app);
+ g_free(encoded_hash);
+
+ return TRUE;
+}
+
+gboolean
+ning_chat_poll_messages(NingChat *chat)
+{
+ NingAccount *na;
+ gchar *url;
+ gchar *encoded_app;
+ gchar *encoded_id;
+ gchar *encoded_token;
+ gchar *encoded_room;
+
+ na = chat->na;
+
+ encoded_app = g_strdup(purple_url_encode(na->ning_app));
+ encoded_id = g_strdup(purple_url_encode(na->ning_id));
+ encoded_token = g_strdup(purple_url_encode(na->chat_token));
+ encoded_room = g_strdup(purple_url_encode(chat->roomId));
+
+ url = g_strdup_printf("/xn/groupchat/poll?a=%s&i=%s&t=%s&r=%s", encoded_app,
+ encoded_id, encoded_token, encoded_room);
+
+ ning_post_or_get(na, NING_METHOD_GET, na->chat_domain,
+ url, NULL,
+ ning_chat_messages_cb, chat, FALSE);
+
+ g_free(url);
+ g_free(encoded_room);
+ g_free(encoded_token);
+ g_free(encoded_id);
+ g_free(encoded_app);
+
+ return TRUE;
+}
+
+void
+ning_chat_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata)
+{
+ PurpleConversation *conv;
+
+ conv = userdata;
+
+ purple_debug_info("ning", "ning_chat_cb: %s\n", response?response:"(null)");
+}
+
+void
+ning_chat_whisper(PurpleConnection *pc, int id, const char *who, const char *message)
+{
+ NingAccount *na;
+ gchar *stripped;
+ PurpleConversation *conv;
+ gchar *postdata;
+ gchar *message_json;
+ gchar *sender;
+
+ gchar *message_escaped;
+ gchar *ning_id_escaped;
+ gchar *token_escaped;
+ gchar *room_escaped;
+ gchar *app_escaped;
+
+ purple_debug_info("ning", "chat whisper %s %s\n", who, message);
+
+ na = pc->proto_data;
+ conv = purple_find_chat(pc, id);
+
+ app_escaped = g_strdup(purple_url_encode(na->ning_app));
+ token_escaped = g_strdup(purple_url_encode(na->xg_token));
+ room_escaped = g_strdup(purple_url_encode(conv->name));
+ ning_id_escaped = g_strdup(purple_url_encode(na->ning_id));
+
+ sender = build_user_json(na);
+
+ stripped = purple_markup_strip_html(message);
+ if (who != NULL)
+ {
+ message_json = g_strdup_printf("{ \"roomId\":\"%s\", \"type\":\"private\", \"targetId\":\"%s\", \"body\":\"%s\", \"sender\":%s }",
+ conv->name, who, stripped,
+ sender);
+ } else {
+ message_json = g_strdup_printf("{ \"roomId\":\"%s\", \"type\":\"message\", \"targetId\":null, \"body\":\"%s\", \"sender\":%s }",
+ conv->name, stripped,
+ sender);
+ }
+ message_escaped = g_strdup(purple_url_encode(message_json));
+
+ postdata = g_strdup_printf("a=%s&i=%s&t=%s&r=%s&message=%s",
+ app_escaped, ning_id_escaped,
+ token_escaped, room_escaped,
+ message_escaped);
+
+ ning_post_or_get(na, NING_METHOD_POST, na->chat_domain,
+ "/xn/groupchat/publish", postdata,
+ ning_chat_cb, conv, FALSE);
+
+ g_free(sender);
+ g_free(postdata);
+ g_free(message_escaped);
+ g_free(message_json);
+ g_free(stripped);
+ g_free(app_escaped);
+ g_free(ning_id_escaped);
+ g_free(token_escaped);
+ g_free(room_escaped);
+}
+
+int
+ning_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags)
+{
+ PurpleConversation *conv;
+ GList *chats;
+ PurpleConvChat *chat;
+ gint chat_id;
+
+ if (flags != PURPLE_MESSAGE_SEND)
+ return -1;
+
+ chats = purple_get_chats();
+ for (;chats;chats = chats->next)
+ {
+ conv = chats->data;
+ chat = PURPLE_CONV_CHAT(conv);
+ if (purple_conv_chat_find_user(chat, who))
+ {
+ chat_id = purple_conv_chat_get_id(chat);
+ ning_chat_whisper(pc, chat_id, who, message);
+ return 1;
+ }
+ }
+
+ return -1;
+}
+
+
+int
+ning_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags)
+{
+ if (flags != PURPLE_MESSAGE_SEND)
+ return -1;
+
+ ning_chat_whisper(pc, id, NULL, message);
+ return 1;
+}
+
+void
+ning_join_chat_by_name(NingAccount *na, const gchar *roomId)
+{
+ NingChat *chat;
+
+ if (na == NULL || roomId == NULL)
+ return;
+
+ chat = g_new0(NingChat, 1);
+ chat->na = na;
+ chat->roomId = g_strdup(roomId);
+ chat->purple_id = g_str_hash(roomId);
+ chat->ning_hash = g_strdup("null");
+
+ serv_got_joined_chat(na->pc, g_str_hash(roomId), roomId);
+
+ //get history
+ ning_chat_get_history(chat);
+
+ //get user list
+ ning_chat_get_users(chat);
+ chat->userlist_timer = purple_timeout_add_seconds(60, (GSourceFunc) ning_chat_get_users, chat);
+
+ //start message poll
+ ning_chat_poll_messages(chat);
+ chat->message_poll_timer = purple_timeout_add_seconds(180, (GSourceFunc) ning_chat_poll_messages, chat);
+
+ na->chats = g_list_append(na->chats, chat);
+}
+
+void
+ning_join_chat(PurpleConnection *pc, GHashTable *components)
+{
+ NingAccount *na;
+
+ if (pc == NULL || pc->proto_data == NULL || components == NULL)
+ return;
+
+ na = pc->proto_data;
+ ning_join_chat_by_name(na, g_hash_table_lookup(components, "name"));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_chat.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,34 @@
+/*
+ * ning_chat.h
+ * pidgin-ning
+ *
+ * Created by MyMacSpace on 6/08/09.
+ * Copyright 2009 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+
+#ifndef NING_CHAT_H
+#define NING_CHAT_H
+
+#include "libning.h"
+#include "ning_connection.h"
+
+typedef struct _NingChat {
+ NingAccount *na;
+ gchar *roomId;
+ gint purple_id;
+ gchar *ning_hash;
+
+ gchar *name;
+ guint userlist_timer;
+ guint message_poll_timer;
+} NingChat;
+
+void ning_chat_whisper(PurpleConnection *pc, int id, const char *who, const char *message);
+int ning_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags);
+void ning_join_chat_by_name(NingAccount *na, const gchar *roomId);
+void ning_join_chat(PurpleConnection *pc, GHashTable *components);
+int ning_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags);
+
+#endif /* NING_CHAT_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,624 @@
+/*
+ * libning
+ *
+ * libning is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ning_connection.h"
+#include <zlib.h>
+
+static void ning_attempt_connection(NingConnection *);
+
+static gchar *ning_gunzip(const guchar *gzip_data, ssize_t *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("ning", "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("ning", "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("ning", "gzip inflate error\n");
+ }
+ inflateEnd(&zstr);
+
+ g_free(data_buffer);
+
+ gchar *output_data = g_strdup(output_string->str);
+ *len_ptr = output_string->len;
+
+ g_string_free(output_string, TRUE);
+
+ return output_data;
+}
+
+void ning_connection_destroy(NingConnection *conn)
+{
+ conn->na->conns = g_slist_remove(conn->na->conns, conn);
+
+ if (conn->request != NULL)
+ g_string_free(conn->request, TRUE);
+
+ g_free(conn->rx_buf);
+
+ if (conn->connect_data != NULL)
+ purple_proxy_connect_cancel(conn->connect_data);
+
+ if (conn->ssl_conn != NULL)
+ purple_ssl_close(conn->ssl_conn);
+
+ if (conn->fd >= 0) {
+ close(conn->fd);
+ }
+
+ if (conn->input_watcher > 0)
+ purple_input_remove(conn->input_watcher);
+
+ g_free(conn->hostname);
+ g_free(conn);
+}
+
+static void ning_update_cookies(NingAccount *na, 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: ")) &&
+ (headers-cookie_start) < 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;
+
+ purple_debug_info("ning", "got cookie %s=%s\n",
+ cookie_name, cookie_value);
+
+ g_hash_table_replace(na->cookie_table, cookie_name,
+ cookie_value);
+ }
+}
+
+static void ning_connection_process_data(NingConnection *conn)
+{
+ ssize_t len;
+ gchar *tmp;
+
+ len = conn->rx_len;
+ tmp = g_strstr_len(conn->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(conn->rx_buf, len);
+ } else {
+ tmp += 4;
+ len -= g_strstr_len(conn->rx_buf, len, "\r\n\r\n") -
+ conn->rx_buf + 4;
+ tmp = g_memdup(tmp, len + 1);
+ tmp[len] = '\0';
+ conn->rx_buf[conn->rx_len - len] = '\0';
+ purple_debug_misc("ning", "response headers\n%s\n",
+ conn->rx_buf);
+ ning_update_cookies(conn->na, conn->rx_buf);
+
+ if (strstr(conn->rx_buf, "Content-Encoding: gzip"))
+ {
+ /* we've received compressed gzip data, decompress */
+ gchar *gunzipped;
+ gunzipped = ning_gunzip((const guchar *)tmp, &len);
+ g_free(tmp);
+ tmp = gunzipped;
+ }
+ }
+
+ g_free(conn->rx_buf);
+ conn->rx_buf = NULL;
+
+ if (conn->callback != NULL)
+ conn->callback(conn->na, tmp, len, conn->user_data);
+
+ g_free(tmp);
+}
+
+static void ning_fatal_connection_cb(NingConnection *conn)
+{
+ PurpleConnection *pc = conn->na->pc;
+
+ purple_debug_error("ning", "fatal connection error\n");
+
+ ning_connection_destroy(conn);
+
+ /* 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_reason(pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Server closed the connection."));
+
+}
+
+static void ning_post_or_get_readdata_cb(gpointer data, gint source,
+ PurpleInputCondition cond)
+{
+ NingConnection *conn;
+ gchar buf[4096];
+ ssize_t len;
+
+ conn = data;
+
+ if (conn->method & NING_METHOD_SSL) {
+ len = purple_ssl_read(conn->ssl_conn,
+ buf, sizeof(buf) - 1);
+ } else {
+ len = recv(conn->fd, buf, sizeof(buf) - 1, 0);
+ }
+
+ if (len < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ /* Try again later */
+ return;
+ }
+
+ if (conn->method & NING_METHOD_SSL && conn->rx_len > 0) {
+ /*
+ * This is a slightly hacky workaround for a bug in either
+ * GNU TLS or in the SSL implementation on Facebook'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("ning",
+ "ssl error, but data received. attempting to continue\n");
+ } else {
+ /* TODO: Is this a regular occurrence? If so then maybe resend the request? */
+ ning_fatal_connection_cb(conn);
+ return;
+ }
+ }
+
+ if (len > 0)
+ {
+ buf[len] = '\0';
+
+ conn->rx_buf = g_realloc(conn->rx_buf,
+ conn->rx_len + len + 1);
+ memcpy(conn->rx_buf + conn->rx_len, buf, len + 1);
+ conn->rx_len += len;
+
+ /* Wait for more data before processing */
+ return;
+ }
+
+ /* The server closed the connection, let's parse the data */
+ ning_connection_process_data(conn);
+
+ ning_connection_destroy(conn);
+}
+
+static void ning_post_or_get_ssl_readdata_cb (gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ ning_post_or_get_readdata_cb(data, -1, cond);
+}
+
+static void ning_post_or_get_connect_cb(gpointer data, gint source,
+ const gchar *error_message)
+{
+ NingConnection *conn;
+ ssize_t len;
+
+ conn = data;
+ conn->connect_data = NULL;
+
+ if (error_message)
+ {
+ purple_debug_error("ning", "post_or_get_connect_cb %s\n",
+ error_message);
+ ning_fatal_connection_cb(conn);
+ return;
+ }
+
+ purple_debug_info("ning", "post_or_get_connect_cb\n");
+ conn->fd = source;
+
+ /* TODO: Check the return value of write() */
+ len = write(conn->fd, conn->request->str,
+ conn->request->len);
+ conn->input_watcher = purple_input_add(conn->fd,
+ PURPLE_INPUT_READ,
+ ning_post_or_get_readdata_cb, conn);
+}
+
+static void ning_post_or_get_ssl_connect_cb(gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ NingConnection *conn;
+ ssize_t len;
+
+ conn = data;
+
+ purple_debug_info("ning", "post_or_get_ssl_connect_cb\n");
+
+ /* TODO: Check the return value of write() */
+ len = purple_ssl_write(conn->ssl_conn,
+ conn->request->str, conn->request->len);
+ purple_ssl_input_add(conn->ssl_conn,
+ ning_post_or_get_ssl_readdata_cb, conn);
+}
+
+static void ning_host_lookup_cb(GSList *hosts, gpointer data,
+ const char *error_message)
+{
+ GSList *host_lookup_list;
+ struct sockaddr_in *addr;
+ gchar *hostname;
+ gchar *ip_address;
+ NingAccount *na;
+ PurpleDnsQueryData *query;
+
+ purple_debug_info("ning", "updating cache of dns addresses\n");
+
+ /* Extract variables */
+ host_lookup_list = data;
+
+ na = 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. */
+ na->dns_queries = g_slist_remove(na->dns_queries, query);
+
+ /* Any problems, capt'n? */
+ if (error_message != NULL)
+ {
+ purple_debug_warning("ning",
+ "Error doing host lookup: %s\n", error_message);
+ return;
+ }
+
+ if (hosts == NULL)
+ {
+ purple_debug_warning("ning",
+ "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);
+ }
+
+ purple_debug_info("ning", "Host %s has IP %s\n",
+ hostname, ip_address);
+
+ g_hash_table_insert(na->hostname_ip_cache, hostname, ip_address);
+}
+
+static void ning_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);
+}
+
+static gchar *ning_cookies_to_string(NingAccount *na)
+{
+ GString *str;
+
+ str = g_string_new(NULL);
+
+ g_hash_table_foreach(na->cookie_table,
+ (GHFunc)ning_cookie_foreach_cb, str);
+
+ return g_string_free(str, FALSE);
+}
+
+static void ning_ssl_connection_error(PurpleSslConnection *ssl,
+ PurpleSslErrorType errortype, gpointer data)
+{
+ NingConnection *conn = data;
+ PurpleConnection *pc = conn->na->pc;
+
+ conn->ssl_conn = NULL;
+ ning_connection_destroy(conn);
+ purple_connection_ssl_error(pc, errortype);
+}
+
+void ning_post_or_get(NingAccount *na, NingMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ NingProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive)
+{
+ GString *request;
+ gchar *cookies;
+ NingConnection *conn;
+ 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;
+
+ purple_debug_info("ning", "post_or_get\n");
+
+ /* TODO: Fix keepalive and use it as much as possible */
+ keepalive = FALSE;
+
+ if (host == NULL)
+ {
+ purple_debug_error("ning", "no host specified\n");
+ return;
+ }
+
+ if (na && na->account && !(method & NING_METHOD_SSL))
+ {
+ proxy_info = purple_proxy_get_setup(na->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 = ning_cookies_to_string(na);
+ user_agent = purple_account_get_string(na->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)");
+
+ if (method & NING_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 & NING_METHOD_POST) ? "POST" : "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 & NING_METHOD_POST) {
+ 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");
+ 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_misc("ning", "sending request headers:\n%s\n",
+ request->str);
+
+ g_string_append_printf(request, "\r\n");
+ if (method & NING_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 == NING_METHOD_POST)
+ purple_debug_misc("ning", "sending request data:\n%s\n",
+ postdata);
+
+ g_free(cookies);
+ g_free(real_url);
+ /*
+ * 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)
+ {
+ /* Don't do this for proxy connections, since proxies do the DNS lookup */
+ gchar *host_ip;
+
+ host_ip = g_hash_table_lookup(na->hostname_ip_cache, host);
+ if (host_ip != NULL) {
+ purple_debug_info("ning",
+ "swapping original host %s with cached value of %s\n",
+ host, host_ip);
+ host = host_ip;
+ } else if (na->account && !na->account->disconnecting) {
+ GSList *host_lookup_list = NULL;
+ PurpleDnsQueryData *query;
+
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, g_strdup(host));
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, na);
+
+ query = purple_dnsquery_a(host, 80,
+ ning_host_lookup_cb, host_lookup_list);
+ na->dns_queries = g_slist_prepend(na->dns_queries, query);
+ host_lookup_list = g_slist_append(host_lookup_list, query);
+ }
+ }
+
+ conn = g_new0(NingConnection, 1);
+ conn->na = na;
+ conn->method = method;
+ conn->hostname = g_strdup(host);
+ conn->request = request;
+ conn->callback = callback_func;
+ conn->user_data = user_data;
+ conn->fd = -1;
+ conn->connection_keepalive = keepalive;
+ conn->request_time = time(NULL);
+ na->conns = g_slist_prepend(na->conns, conn);
+
+ ning_attempt_connection(conn);
+}
+
+static void ning_attempt_connection(NingConnection *conn)
+{
+ NingAccount *na = conn->na;
+
+ if (conn->method & NING_METHOD_SSL) {
+ conn->ssl_conn = purple_ssl_connect(na->account, conn->hostname,
+ 443, ning_post_or_get_ssl_connect_cb,
+ ning_ssl_connection_error, conn);
+ } else {
+ conn->connect_data = purple_proxy_connect(NULL, na->account,
+ conn->hostname, 80, ning_post_or_get_connect_cb, conn);
+ }
+
+ return;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,63 @@
+/*
+ * libning
+ *
+ * libning is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NING_CONNECTION_H
+#define NING_CONNECTION_H
+
+#include "libning.h"
+
+typedef void (*NingProxyCallbackFunc)
+ (NingAccount *na, gchar *data, gsize data_len, gpointer user_data);
+
+/*
+ * This is a bitmask.
+ */
+typedef enum
+{
+ NING_METHOD_GET = 0x0001,
+ NING_METHOD_POST = 0x0002,
+ NING_METHOD_SSL = 0x0004
+} NingMethod;
+
+typedef struct _NingConnection NingConnection;
+struct _NingConnection {
+ NingAccount *na;
+ NingMethod method;
+ gchar *hostname;
+ GString *request;
+ NingProxyCallbackFunc callback;
+ gpointer user_data;
+ char *rx_buf;
+ size_t rx_len;
+ PurpleProxyConnectData *connect_data;
+ PurpleSslConnection *ssl_conn;
+ int fd;
+ guint input_watcher;
+ gboolean connection_keepalive;
+ time_t request_time;
+};
+
+void ning_connection_destroy(NingConnection *okcconn);
+void ning_post_or_get(NingAccount *oca, NingMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ NingProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive);
+
+#endif /* NING_CONNECTION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/libokcupid.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,433 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libokcupid.h"
+#include "okc_connection.h"
+#include "okc_messages.h"
+#include "okc_blist.h"
+
+/******************************************************************************/
+/* Utility functions */
+/******************************************************************************/
+
+gchar *okc_convert_unicode(const gchar *input)
+{
+ gunichar unicode_char;
+ gchar unicode_char_str[6];
+ gint unicode_char_len;
+ gchar *next_pos;
+ gchar *input_string;
+ gchar *output_string;
+
+ if (input == NULL)
+ return NULL;
+
+ next_pos = input_string = g_strdup(input);
+
+ while ((next_pos = strstr(next_pos, "\\u")))
+ {
+ /* grab the unicode */
+ sscanf(next_pos, "\\u%4x", &unicode_char);
+ /* turn it to a char* */
+ unicode_char_len = g_unichar_to_utf8(unicode_char, unicode_char_str);
+ /* shove it back into the string */
+ g_memmove(next_pos, unicode_char_str, unicode_char_len);
+ /* move all the data after the \u0000 along */
+ g_stpcpy(next_pos + unicode_char_len, next_pos + 6);
+ }
+
+ output_string = g_strcompress(input_string);
+ g_free(input_string);
+
+ return output_string;
+}
+
+/* Like purple_strdup_withhtml, but escapes htmlentities too */
+gchar *okc_strdup_withhtml(const gchar *src)
+{
+ gulong destsize, i, j;
+ gchar *dest;
+
+ g_return_val_if_fail(src != NULL, NULL);
+
+ /* New length is (length of src) + (number of \n's * 3) + (number of &'s * 5) +
+ (number of <'s * 4) + (number of >'s *4) + (number of "'s * 6) -
+ (number of \r's) + 1 */
+ destsize = 1;
+ for (i = 0; src[i] != '\0'; i++)
+ {
+ if (src[i] == '\n' || src[i] == '<' || src[i] == '>')
+ destsize += 4;
+ else if (src[i] == '&')
+ destsize += 5;
+ else if (src[i] == '"')
+ destsize += 6;
+ else if (src[i] != '\r')
+ destsize++;
+ }
+
+ dest = g_malloc(destsize);
+
+ /* Copy stuff, ignoring \r's, because they are dumb */
+ for (i = 0, j = 0; src[i] != '\0'; i++) {
+ if (src[i] == '\n') {
+ strcpy(&dest[j], "<BR>");
+ j += 4;
+ } else if (src[i] == '<') {
+ strcpy(&dest[j], "&lt;");
+ j += 4;
+ } else if (src[i] == '>') {
+ strcpy(&dest[j], "&gt;");
+ j += 4;
+ } else if (src[i] == '&') {
+ strcpy(&dest[j], "&amp;");
+ j += 5;
+ } else if (src[i] == '"') {
+ strcpy(&dest[j], "&quot;");
+ j += 6;
+ } else if (src[i] != '\r')
+ dest[j++] = src[i];
+ }
+
+ dest[destsize-1] = '\0';
+
+ return dest;
+}
+
+/******************************************************************************/
+/* PRPL functions */
+/******************************************************************************/
+
+static const char *okc_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
+{
+ return "okcupid";
+}
+
+static GList *okc_statuses(PurpleAccount *account)
+{
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ purple_debug_info("okcupid", "statuses\n");
+
+ /* OkCupid people are either online, online with no IM, offline */
+
+ status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ purple_debug_info("okcupid", "statuses return\n");
+
+ return types;
+}
+
+static gboolean okc_get_messages_failsafe(OkCupidAccount *oca)
+{
+ if (oca->last_messages_download_time < (time(NULL) - (60*5))) {
+ /* Messages haven't been downloaded in a while-
+ * something is probably wrong */
+ purple_debug_warning("okcupid",
+ "executing message check failsafe\n");
+ okc_get_new_messages(oca);
+ }
+
+ return TRUE;
+}
+
+static void okc_login_cb(OkCupidAccount *oca, gchar *response, gsize len,
+ gpointer userdata)
+{
+ JsonParser *parser;
+ JsonNode *root;
+ JsonObject *message;
+ gint status;
+
+ purple_connection_update_progress(oca->pc, _("Authenticating"), 2, 3);
+
+ if (!response)
+ {
+ purple_connection_error(oca->pc, "No login response");
+ return;
+ }
+ parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, response, len, NULL))
+ {
+ purple_connection_error(oca->pc, "Error parsing login response");
+ return;
+ }
+ root = json_parser_get_root(parser);
+ message = json_node_get_object(root);
+ status = json_node_get_int(json_object_get_member(message, "status"));
+ if (status >= 100)
+ {
+ purple_connection_error(oca->pc, "Bad username or password");
+ return;
+ }
+
+ /* ok, we're logged in now! */
+ purple_connection_set_state(oca->pc, PURPLE_CONNECTED);
+
+ /* This will kick off our long-poll message retrieval loop */
+ okc_get_new_messages_now(oca);
+
+ oca->perpetual_messages_timer = purple_timeout_add_seconds(15,
+ (GSourceFunc)okc_get_messages_failsafe, oca);
+}
+
+static void okc_login(PurpleAccount *account)
+{
+ OkCupidAccount *oca;
+ gchar *postdata, *encoded_username, *encoded_password;
+
+ purple_debug_info("okcupid", "login\n");
+
+ /* Create account and initialize state */
+ oca = g_new0(OkCupidAccount, 1);
+ oca->account = account;
+ oca->pc = purple_account_get_connection(account);
+
+ oca->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */
+ oca->server_seqid = purple_account_get_int(oca->account, "server_seqid", 0);
+ oca->server_gmt = purple_account_get_int(oca->account, "server_gmt", 0);
+
+ oca->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ oca->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ oca->waiting_conns = g_queue_new();
+
+ account->gc->proto_data = oca;
+
+ purple_connection_set_state(oca->pc, PURPLE_CONNECTING);
+ purple_connection_update_progress(oca->pc, _("Connecting"), 1, 3);
+
+ encoded_username = g_strdup(purple_url_encode(
+ purple_account_get_username(account)));
+ encoded_password = g_strdup(purple_url_encode(
+ purple_account_get_password(account)));
+
+ postdata = g_strdup_printf("username=%s&password=%s",
+ encoded_username, encoded_password);
+ g_free(encoded_username);
+ g_free(encoded_password);
+
+ okc_post_or_get(oca, OKC_METHOD_POST | OKC_METHOD_SSL, "www.okcupid.com",
+ "/login", postdata, okc_login_cb, NULL, FALSE);
+ g_free(postdata);
+}
+
+static void okc_close(PurpleConnection *pc)
+{
+ OkCupidAccount *oca;
+
+ purple_debug_info("okcupid", "disconnecting account\n");
+
+ oca = pc->proto_data;
+
+ okc_post_or_get(oca, OKC_METHOD_POST, NULL, "/logout",
+ "ajax=1", NULL, NULL, FALSE);
+
+ if (oca->new_messages_check_timer)
+ purple_timeout_remove(oca->new_messages_check_timer);
+ if (oca->buddy_presence_timer)
+ purple_timeout_remove(oca->buddy_presence_timer);
+ if (oca->perpetual_messages_timer)
+ purple_timeout_remove(oca->perpetual_messages_timer);
+
+ purple_debug_info("okcupid", "destroying %d waiting connections\n",
+ g_queue_get_length(oca->waiting_conns));
+
+ while (!g_queue_is_empty(oca->waiting_conns))
+ okc_connection_destroy(g_queue_pop_tail(oca->waiting_conns));
+ g_queue_free(oca->waiting_conns);
+
+ purple_debug_info("okcupid", "destroying %d incomplete connections\n",
+ g_slist_length(oca->conns));
+
+ while (oca->conns != NULL)
+ okc_connection_destroy(oca->conns->data);
+
+ while (oca->dns_queries != NULL) {
+ PurpleDnsQueryData *dns_query = oca->dns_queries->data;
+ purple_debug_info("okcupid", "canceling dns query for %s\n",
+ purple_dnsquery_get_host(dns_query));
+ oca->dns_queries = g_slist_remove(oca->dns_queries, dns_query);
+ purple_dnsquery_destroy(dns_query);
+ }
+
+ g_hash_table_destroy(oca->cookie_table);
+ g_hash_table_destroy(oca->hostname_ip_cache);
+
+ //Store server_seqid and server_gmt so that we dont download all the messages on startup
+ purple_account_set_int(oca->account, "server_seqid", oca->server_seqid);
+ purple_account_set_int(oca->account, "server_gmt", oca->server_gmt);
+
+ g_free(oca);
+}
+
+static void okc_buddy_free(PurpleBuddy *buddy)
+{
+ OkCupidBuddy *obuddy = buddy->proto_data;
+ if (obuddy != NULL)
+ {
+ buddy->proto_data = NULL;
+
+ g_free(obuddy->thumb_url);
+ g_free(obuddy);
+ }
+}
+
+/******************************************************************************/
+/* Plugin functions */
+/******************************************************************************/
+
+static gboolean plugin_load(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean plugin_unload(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static void plugin_init(PurplePlugin *plugin)
+{
+ PurpleAccountOption *option;
+ PurplePluginInfo *info = plugin->info;
+ PurplePluginProtocolInfo *prpl_info = info->extra_info;
+
+ option = purple_account_option_string_new("Host", "host", "api.okcupid.com");
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+ option = purple_account_option_bool_new("Show messages sent from website", "show_sent_messages", FALSE);
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+ option = purple_account_option_bool_new("Show people who visit your profile", "show_stalkers", TRUE);
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+}
+
+static PurplePluginProtocolInfo prpl_info = {
+ /* options */
+ OPT_PROTO_UNIQUE_CHATNAME,
+
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ NO_BUDDY_ICONS /* icon_spec */
+ /*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */
+ okc_list_icon, /* list_icon */
+ NULL, /* list_emblems */
+ NULL, /* status_text */
+ NULL, /* tooltip_text */
+ okc_statuses, /* status_types */
+ okc_blist_node_menu, /* blist_node_menu */
+ NULL, /* chat_info */
+ NULL, /* chat_info_defaults */
+ okc_login, /* login */
+ okc_close, /* close */
+ okc_send_im, /* send_im */
+ NULL, /* set_info */
+ NULL, /* send_typing */
+ okc_get_info, /* get_info */
+ NULL, /* set_status */
+ NULL, /* set_idle */
+ NULL, /* change_passwd */
+ okc_add_buddy, /* add_buddy */
+ NULL, /* add_buddies */
+ okc_remove_buddy, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ okc_block_buddy, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ NULL, /* join_chat */
+ NULL, /* reject chat invite */
+ NULL, /* get_chat_name */
+ NULL, /* chat_invite */
+ NULL, /* chat_leave */
+ NULL, /* chat_whisper */
+ NULL, /* chat_send */
+ NULL, /* keepalive */
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ NULL, /* alias_buddy */
+ NULL, /* group_buddy */
+ NULL, /* rename_group */
+ okc_buddy_free, /* buddy_free */
+ NULL, /* convo_closed */
+ purple_normalize_nocase,/* normalize */
+ NULL, /* set_buddy_icon */
+ NULL, /* remove_group */
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ NULL, /* send_file */
+ NULL, /* new_xfer */
+ NULL, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
+ NULL, /* get_account_text_table */
+};
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC,
+ 2, /* major_version */
+ 3, /* minor version */
+ PURPLE_PLUGIN_PROTOCOL, /* type */
+ NULL, /* ui_requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ "prpl-bigbrownchunx-okcupid", /* id */
+ "OkCupid", /* name */
+ OKCUPID_PLUGIN_VERSION, /* version */
+ N_("OkCupid Protocol Plugin"), /* summary */
+ N_("OkCupid Protocol Plugin"), /* description */
+ "Eion Robb <eionrobb@gmail.com>", /* author */
+ "", /* homepage */
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ &prpl_info, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PURPLE_INIT_PLUGIN(okcupid, plugin_init, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/libokcupid.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,103 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBOKCUPID_H
+#define LIBOKCUPID_H
+
+#define OKCUPID_PLUGIN_VERSION "2.03"
+#define OKC_MAX_CONNECTIONS 16
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <json-glib/json-glib.h>
+
+#ifndef G_GNUC_NULL_TERMINATED
+# if __GNUC__ >= 4
+# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+# else
+# define G_GNUC_NULL_TERMINATED
+# endif /* __GNUC__ >= 4 */
+#endif /* G_GNUC_NULL_TERMINATED */
+
+#ifdef _WIN32
+# include "win32dep.h"
+# define dlopen(a,b) LoadLibrary(a)
+# define RTLD_LAZY
+# define dlsym(a,b) GetProcAddress(a,b)
+# define dlclose(a) FreeLibrary(a)
+#else
+# include <arpa/inet.h>
+# include <dlfcn.h>
+# include <netinet/in.h>
+# include <sys/socket.h>
+#endif
+
+#ifndef PURPLE_PLUGINS
+# define PURPLE_PLUGINS
+#endif
+
+#include "accountopt.h"
+#include "connection.h"
+#include "debug.h"
+#include "dnsquery.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "sslconn.h"
+#include "version.h"
+
+typedef struct _OkCupidAccount OkCupidAccount;
+typedef struct _OkCupidBuddy OkCupidBuddy;
+
+typedef void (*OkCupidProxyCallbackFunc)(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data);
+
+struct _OkCupidAccount {
+ PurpleAccount *account;
+ PurpleConnection *pc;
+ GHashTable *hostname_ip_cache;
+ GSList *conns; /**< A list of all active OkCupidConnections */
+ GQueue *waiting_conns; /**< A list of all OkCupidConnections waiting to process */
+ GSList *dns_queries;
+ GHashTable *cookie_table;
+ time_t last_messages_download_time;
+ guint new_messages_check_timer;
+ guint perpetual_messages_timer;
+ guint buddy_presence_timer;
+ guint server_seqid;
+ guint server_gmt;
+ guint last_message_count;
+};
+
+struct _OkCupidBuddy {
+ OkCupidAccount *oca;
+ PurpleBuddy *buddy;
+ gchar *thumb_url;
+};
+
+gchar *okc_strdup_withhtml(const gchar *src);
+gchar *okc_convert_unicode(const gchar *input);
+
+#endif /* LIBOKCUPID_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_blist.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,252 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libokcupid.h"
+#include "okc_connection.h"
+#include "okc_blist.h"
+#include "okc_messages.h"
+
+void okc_blist_wink_buddy(PurpleBlistNode *node, gpointer data)
+{
+ PurpleBuddy *buddy;
+ OkCupidAccount *oca;
+ gchar *postdata;
+ PurpleConnection *pc;
+
+ if(!PURPLE_BLIST_NODE_IS_BUDDY(node))
+ return;
+ buddy = (PurpleBuddy *) node;
+ if (!buddy || !buddy->account)
+ return;
+
+ pc = purple_account_get_connection(buddy->account);
+ if (!pc || !pc->proto_data)
+ return;
+
+ oca = pc->proto_data;
+
+ postdata = g_strdup_printf("woo=1&u=%s&ajax=1", purple_url_encode(buddy->name));
+
+ okc_post_or_get(oca, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+}
+
+void okc_got_info(OkCupidAccount *oca, gchar *data,
+ gsize data_len, gpointer userdata)
+{
+ gchar *username = userdata;
+ JsonParser *parser;
+ JsonNode *root;
+ PurpleNotifyUserInfo *user_info;
+ gchar *value_tmp;
+ GError *error = NULL;
+ JsonObject *info;
+
+ if (!data || !data_len)
+ {
+ g_free(username);
+ return;
+ }
+
+ purple_debug_info("okcupid", "okc_got_info: %s\n", data);
+
+ user_info = purple_notify_user_info_new();
+ /* Insert link to profile at top */
+ value_tmp = g_strdup_printf("<a href=\"http://www.okcupid.com/profile/%s\">%s</a>",
+ username, _("View web profile"));
+ purple_notify_user_info_add_pair(user_info, NULL, value_tmp);
+ purple_notify_user_info_add_section_break(user_info);
+ g_free(value_tmp);
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, data, data_len, &error))
+ {
+ purple_debug_error("okcupid", "got_info error: %s\n", error->message);
+ purple_notify_userinfo(oca->pc, username, user_info, NULL, NULL);
+ purple_notify_user_info_destroy(user_info);
+ g_free(username);
+ return;
+ }
+ root = json_parser_get_root(parser);
+ info = json_node_get_object(root);
+
+ value_tmp = g_strdup_printf("%" G_GINT64_FORMAT " years", json_node_get_int(json_object_get_member(info, "age")));
+ purple_notify_user_info_add_pair(user_info, _("Age"), value_tmp);
+ g_free(value_tmp);
+ purple_notify_user_info_add_pair(user_info, _("Gender"), json_node_get_string(json_object_get_member(info, "sex")));
+ purple_notify_user_info_add_pair(user_info, _("Sexual Preference"), json_node_get_string(json_object_get_member(info, "orientation")));
+ purple_notify_user_info_add_pair(user_info, _("Relationship Status"), json_node_get_string(json_object_get_member(info, "status")));
+ purple_notify_user_info_add_pair(user_info, _("Location"), json_node_get_string(json_object_get_member(info, "location")));
+ value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "matchpercentage")));
+ purple_notify_user_info_add_pair(user_info, _("Match"), value_tmp);
+ g_free(value_tmp);
+ value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "friendpercentage")));
+ purple_notify_user_info_add_pair(user_info, _("Friend"), value_tmp);
+ g_free(value_tmp);
+ value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "enemypercentage")));
+ purple_notify_user_info_add_pair(user_info, _("Enemy"), value_tmp);
+ g_free(value_tmp);
+
+ const gchar *buddy_icon = json_node_get_string(json_object_get_member(info, "thumbnail"));
+ PurpleBuddy *buddy = purple_find_buddy(oca->account, username);
+ OkCupidBuddy *obuddy = buddy->proto_data;
+ if (obuddy == NULL)
+ {
+ obuddy = g_new0(OkCupidBuddy, 1);
+ obuddy->buddy = buddy;
+ obuddy->oca = oca;
+
+ // load the old buddy icon url from the icon 'checksum'
+ const gchar *buddy_icon_url = purple_buddy_icons_get_checksum_for_user(buddy);
+ if (buddy_icon_url != NULL)
+ obuddy->thumb_url = g_strdup(buddy_icon_url);
+
+ buddy->proto_data = obuddy;
+ }
+ if (!obuddy->thumb_url || !g_str_equal(obuddy->thumb_url, buddy_icon))
+ {
+ gchar *host, *path, *path2;
+
+ g_free(obuddy->thumb_url);
+ obuddy->thumb_url = purple_strreplace(buddy_icon, "/60x60/", "/256x256/");
+
+ purple_url_parse(obuddy->thumb_url, &host, NULL, &path, NULL, NULL);
+ if (path[0] != '/')
+ path2 = g_strdup_printf("/%s", path);
+ else
+ path2 = g_strdup(path);
+ okc_post_or_get(oca, OKC_METHOD_GET, host, path2, NULL, okc_buddy_icon_cb, g_strdup(username), FALSE);
+
+ g_free(host);
+ g_free(path);
+ g_free(path2);
+ }
+
+ purple_notify_user_info_add_section_break(user_info);
+ purple_notify_user_info_add_section_header(user_info, _("The Skinny"));
+
+ info = json_node_get_object(json_object_get_member(info, "skinny"));
+ purple_notify_user_info_add_pair(user_info, _("Last Online"), json_node_get_string(json_object_get_member(info, "last_online")));
+ purple_notify_user_info_add_pair(user_info, _("Join Date"), json_node_get_string(json_object_get_member(info, "join_date")));
+ purple_notify_user_info_add_pair(user_info, _("Ethnicity"), json_node_get_string(json_object_get_member(info, "ethnicities")));
+ purple_notify_user_info_add_pair(user_info, _("Height"), json_node_get_string(json_object_get_member(info, "height")));
+ purple_notify_user_info_add_pair(user_info, _("Body Type"), json_node_get_string(json_object_get_member(info, "bodytype")));
+ purple_notify_user_info_add_pair(user_info, _("Looking For"), json_node_get_string(json_object_get_member(info, "lookingfor")));
+ purple_notify_user_info_add_pair(user_info, _("Smokes"), json_node_get_string(json_object_get_member(info, "smoker")));
+ purple_notify_user_info_add_pair(user_info, _("Drinks"), json_node_get_string(json_object_get_member(info, "drinker")));
+ purple_notify_user_info_add_pair(user_info, _("Drugs"), json_node_get_string(json_object_get_member(info, "drugs")));
+ if (json_object_has_member(info, "religion"))
+ {
+ value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "religion")),
+ json_node_get_string(json_object_get_member(info, "religionserious")));
+ purple_notify_user_info_add_pair(user_info, _("Religion"), value_tmp);
+ g_free(value_tmp);
+ }
+ value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "sign")),
+ json_node_get_string(json_object_get_member(info, "sign_status")));
+ purple_notify_user_info_add_pair(user_info, _("Star sign"), value_tmp);
+ g_free(value_tmp);
+ value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "education_status")),
+ json_node_get_string(json_object_get_member(info, "education")));
+ purple_notify_user_info_add_pair(user_info, _("Education"), value_tmp);
+ g_free(value_tmp);
+ purple_notify_user_info_add_pair(user_info, _("Job"), json_node_get_string(json_object_get_member(info, "job")));
+ purple_notify_user_info_add_pair(user_info, _("Income"), json_node_get_string(json_object_get_member(info, "income")));
+ purple_notify_user_info_add_pair(user_info, _("Kids"), json_node_get_string(json_object_get_member(info, "children")));
+ value_tmp = g_strdup_printf("%s and %s", json_node_get_string(json_object_get_member(info, "dogs")),
+ json_node_get_string(json_object_get_member(info, "cats")));
+ purple_notify_user_info_add_pair(user_info, _("Pets"), value_tmp);
+ g_free(value_tmp);
+ purple_notify_user_info_add_pair(user_info, _("Languages"), json_node_get_string(json_object_get_member(info, "languagestr")));
+
+
+ purple_notify_userinfo(oca->pc, username, user_info, NULL, NULL);
+ purple_notify_user_info_destroy(user_info);
+
+ g_object_unref(parser);
+ g_free(username);
+}
+
+void okc_get_info(PurpleConnection *pc, const gchar *uid)
+{
+ gchar *profile_url;
+
+ profile_url = g_strdup_printf("/profile/%s?json=2", purple_url_encode(uid));
+
+ okc_post_or_get(pc->proto_data, OKC_METHOD_GET, NULL, profile_url, NULL, okc_got_info, g_strdup(uid), FALSE);
+
+ g_free(profile_url);
+}
+
+void okc_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ gchar *postdata;
+
+ postdata = g_strdup_printf("addbuddy=1&u=%s&ajax=1", purple_url_encode(buddy->name));
+
+ okc_post_or_get(pc->proto_data, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+}
+
+void okc_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ gchar *postdata;
+
+ postdata = g_strdup_printf("removebuddy=1&u=%s&ajax=1", purple_url_encode(buddy->name));
+
+ okc_post_or_get(pc->proto_data, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+}
+
+void okc_block_buddy(PurpleConnection *pc, const char *name)
+{
+ gchar *block_url;
+
+ block_url = g_strdup_printf("/instantevents?im_block=1&target_screenname=%s", purple_url_encode(name));
+
+ okc_post_or_get(pc->proto_data, OKC_METHOD_GET, NULL, block_url, NULL, NULL, NULL, FALSE);
+
+ g_free(block_url);
+}
+
+
+GList *okc_blist_node_menu(PurpleBlistNode *node)
+{
+ GList *m = NULL;
+ PurpleMenuAction *act;
+
+ if(PURPLE_BLIST_NODE_IS_BUDDY(node))
+ {
+ act = purple_menu_action_new(_("_Wink"),
+ PURPLE_CALLBACK(okc_blist_wink_buddy),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ } else if (PURPLE_BLIST_NODE_IS_CHAT(node))
+ {
+
+ } else if (PURPLE_BLIST_NODE_IS_GROUP(node))
+ {
+
+ }
+ return m;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_blist.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,33 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OKCUPID_BLIST_H
+#define OKCUPID_BLIST_H
+
+#include "libokcupid.h"
+
+void okc_blist_wink_buddy(PurpleBlistNode *node, gpointer data);
+void okc_get_info(PurpleConnection *pc, const gchar *uid);
+void okc_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group);
+void okc_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group);
+void okc_block_buddy(PurpleConnection *, const char *name);
+GList *okc_blist_node_menu(PurpleBlistNode *node);
+
+#endif /* OKCUPID_BLIST_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,647 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "okc_connection.h"
+
+#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 okc_attempt_connection(OkCupidConnection *);
+static void okc_next_connection(OkCupidAccount *oca);
+
+static gchar *okc_gunzip(const guchar *gzip_data, ssize_t *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("okcupid", "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("okcupid", "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("okcupid", "gzip inflate error\n");
+ }
+ inflateEnd(&zstr);
+
+ g_free(data_buffer);
+
+ gchar *output_data = g_strdup(output_string->str);
+ *len_ptr = output_string->len;
+
+ g_string_free(output_string, TRUE);
+
+ return output_data;
+}
+
+void okc_connection_destroy(OkCupidConnection *okconn)
+{
+ okconn->oca->conns = g_slist_remove(okconn->oca->conns, okconn);
+
+ if (okconn->request != NULL)
+ g_string_free(okconn->request, TRUE);
+
+ g_free(okconn->rx_buf);
+
+ if (okconn->connect_data != NULL)
+ purple_proxy_connect_cancel(okconn->connect_data);
+
+ if (okconn->ssl_conn != NULL)
+ purple_ssl_close(okconn->ssl_conn);
+
+ if (okconn->fd >= 0) {
+ close(okconn->fd);
+ }
+
+ if (okconn->input_watcher > 0)
+ purple_input_remove(okconn->input_watcher);
+
+ g_free(okconn->hostname);
+ g_free(okconn);
+}
+
+static void okc_update_cookies(OkCupidAccount *oca, 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: ")) &&
+ (headers-cookie_start) < 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;
+
+ purple_debug_info("okcupid", "got cookie %s=%s\n",
+ cookie_name, cookie_value);
+
+ g_hash_table_replace(oca->cookie_table, cookie_name,
+ cookie_value);
+ }
+}
+
+static void okc_connection_process_data(OkCupidConnection *okconn)
+{
+ ssize_t len;
+ gchar *tmp;
+
+ len = okconn->rx_len;
+ tmp = g_strstr_len(okconn->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(okconn->rx_buf, len);
+ } else {
+ tmp += 4;
+ len -= g_strstr_len(okconn->rx_buf, len, "\r\n\r\n") -
+ okconn->rx_buf + 4;
+ tmp = g_memdup(tmp, len + 1);
+ tmp[len] = '\0';
+ okconn->rx_buf[okconn->rx_len - len] = '\0';
+ //purple_debug_misc("okcupid", "response headers\n%s\n",
+ // okconn->rx_buf);
+ okc_update_cookies(okconn->oca, okconn->rx_buf);
+
+ if (strstr(okconn->rx_buf, "Content-Encoding: gzip"))
+ {
+ /* we've received compressed gzip data, decompress */
+ gchar *gunzipped;
+ gunzipped = okc_gunzip((const guchar *)tmp, &len);
+ g_free(tmp);
+ tmp = gunzipped;
+ }
+ }
+
+ g_free(okconn->rx_buf);
+ okconn->rx_buf = NULL;
+
+ if (okconn->callback != NULL)
+ okconn->callback(okconn->oca, tmp, len, okconn->user_data);
+
+ g_free(tmp);
+}
+
+static void okc_fatal_connection_cb(OkCupidConnection *okconn)
+{
+ PurpleConnection *pc = okconn->oca->pc;
+
+ purple_debug_error("okcupid", "fatal connection error\n");
+
+ okc_connection_destroy(okconn);
+
+ /* 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_reason(pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Server closed the connection."));
+
+}
+
+static void okc_post_or_get_readdata_cb(gpointer data, gint source,
+ PurpleInputCondition cond)
+{
+ OkCupidAccount *oca;
+ OkCupidConnection *okconn;
+ gchar buf[4096];
+ ssize_t len;
+
+ okconn = data;
+ oca = okconn->oca;
+
+ if (okconn->method & OKC_METHOD_SSL) {
+ len = purple_ssl_read(okconn->ssl_conn,
+ buf, sizeof(buf) - 1);
+ } else {
+ len = recv(okconn->fd, buf, sizeof(buf) - 1, 0);
+ }
+
+ if (len < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ /* Try again later */
+ return;
+ }
+
+ if (okconn->method & OKC_METHOD_SSL && okconn->rx_len > 0) {
+ /*
+ * This is a slightly hacky workaround for a bug in either
+ * GNU TLS or in the SSL implementation on Facebook'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("okcupid",
+ "ssl error, but data received. attempting to continue\n");
+ } else {
+ /* TODO: Is this a regular occurrence? If so then maybe resend the request? */
+ okc_fatal_connection_cb(okconn);
+ return;
+ }
+ }
+
+ if (len > 0)
+ {
+ buf[len] = '\0';
+
+ okconn->rx_buf = g_realloc(okconn->rx_buf,
+ okconn->rx_len + len + 1);
+ memcpy(okconn->rx_buf + okconn->rx_len, buf, len + 1);
+ okconn->rx_len += len;
+
+ /* Wait for more data before processing */
+ return;
+ }
+
+ /* The server closed the connection, let's parse the data */
+ okc_connection_process_data(okconn);
+
+ okc_connection_destroy(okconn);
+
+ okc_next_connection(oca);
+}
+
+static void okc_post_or_get_ssl_readdata_cb (gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ okc_post_or_get_readdata_cb(data, -1, cond);
+}
+
+static void okc_post_or_get_connect_cb(gpointer data, gint source,
+ const gchar *error_message)
+{
+ OkCupidConnection *okconn;
+ ssize_t len;
+
+ okconn = data;
+ okconn->connect_data = NULL;
+
+ if (error_message)
+ {
+ purple_debug_error("okcupid", "post_or_get_connect_cb %s\n",
+ error_message);
+ okc_fatal_connection_cb(okconn);
+ return;
+ }
+
+ purple_debug_info("okcupid", "post_or_get_connect_cb\n");
+ okconn->fd = source;
+
+ /* TODO: Check the return value of write() */
+ len = write(okconn->fd, okconn->request->str,
+ okconn->request->len);
+ okconn->input_watcher = purple_input_add(okconn->fd,
+ PURPLE_INPUT_READ,
+ okc_post_or_get_readdata_cb, okconn);
+}
+
+static void okc_post_or_get_ssl_connect_cb(gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ OkCupidConnection *okconn;
+ ssize_t len;
+
+ okconn = data;
+
+ purple_debug_info("okcupid", "post_or_get_ssl_connect_cb\n");
+
+ /* TODO: Check the return value of write() */
+ len = purple_ssl_write(okconn->ssl_conn,
+ okconn->request->str, okconn->request->len);
+ purple_ssl_input_add(okconn->ssl_conn,
+ okc_post_or_get_ssl_readdata_cb, okconn);
+}
+
+static void okc_host_lookup_cb(GSList *hosts, gpointer data,
+ const char *error_message)
+{
+ GSList *host_lookup_list;
+ struct sockaddr_in *addr;
+ gchar *hostname;
+ gchar *ip_address;
+ OkCupidAccount *oca;
+ PurpleDnsQueryData *query;
+
+ purple_debug_info("okcupid", "updating cache of dns addresses\n");
+
+ /* Extract variables */
+ host_lookup_list = data;
+
+ oca = 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. */
+ oca->dns_queries = g_slist_remove(oca->dns_queries, query);
+
+ /* Any problems, capt'n? */
+ if (error_message != NULL)
+ {
+ purple_debug_warning("okcupid",
+ "Error doing host lookup: %s\n", error_message);
+ return;
+ }
+
+ if (hosts == NULL)
+ {
+ purple_debug_warning("okcupid",
+ "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);
+ }
+
+ purple_debug_info("okcupid", "Host %s has IP %s\n",
+ hostname, ip_address);
+
+ g_hash_table_insert(oca->hostname_ip_cache, hostname, ip_address);
+}
+
+static void okc_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);
+}
+
+static gchar *okc_cookies_to_string(OkCupidAccount *oca)
+{
+ GString *str;
+
+ str = g_string_new(NULL);
+
+ g_hash_table_foreach(oca->cookie_table,
+ (GHFunc)okc_cookie_foreach_cb, str);
+
+ return g_string_free(str, FALSE);
+}
+
+static void okc_ssl_connection_error(PurpleSslConnection *ssl,
+ PurpleSslErrorType errortype, gpointer data)
+{
+ OkCupidConnection *okconn = data;
+ PurpleConnection *pc = okconn->oca->pc;
+
+ okconn->ssl_conn = NULL;
+ okc_connection_destroy(okconn);
+ purple_connection_ssl_error(pc, errortype);
+}
+
+void okc_post_or_get(OkCupidAccount *oca, OkCupidMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ OkCupidProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive)
+{
+ GString *request;
+ gchar *cookies;
+ OkCupidConnection *okconn;
+ gchar *real_url;
+ gboolean is_proxy = FALSE;
+ 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 (method & OKC_METHOD_SSL)
+ host = "www.okcupid.com";
+ if (host == NULL && oca && oca->account)
+ host = purple_account_get_string(oca->account, "host", "api.okcupid.com");
+ if (host == NULL)
+ host = "api.okcupid.com";
+
+ if (oca && oca->account && !(method & OKC_METHOD_SSL))
+ {
+ proxy_info = purple_proxy_get_setup(oca->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 = okc_cookies_to_string(oca);
+
+ if (method & OKC_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 & OKC_METHOD_POST) ? "POST" : "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 (libpurple %s)\r\n", purple_core_get_ui(), purple_core_get_version());
+ if (method & OKC_METHOD_POST) {
+ 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");
+ g_string_append_printf(request, "Cookie: %s\r\n", cookies);
+ g_string_append_printf(request, "Accept-Encoding: gzip\r\n");
+ g_string_append_printf(request, "X-OkCupid-Api-Version: 1\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_misc("okcupid", "requesting url %s\n", url);
+
+ g_string_append_printf(request, "\r\n");
+ if (method & OKC_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 == OKC_METHOD_POST)
+ purple_debug_misc("okcupid", "sending request data:\n%s\n",
+ postdata);
+
+ g_free(cookies);
+ g_free(real_url);
+ /*
+ * 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 && !g_hostname_is_ip_address(host))
+ {
+ /* Don't do this for proxy connections, since proxies do the DNS lookup */
+ gchar *host_ip;
+
+ host_ip = g_hash_table_lookup(oca->hostname_ip_cache, host);
+ if (host_ip != NULL) {
+ purple_debug_info("okcupid",
+ "swapping original host %s with cached value of %s\n",
+ host, host_ip);
+ host = host_ip;
+ } else if (oca->account && !oca->account->disconnecting) {
+ GSList *host_lookup_list = NULL;
+ PurpleDnsQueryData *query;
+
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, g_strdup(host));
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, oca);
+
+ query = purple_dnsquery_a(host, 80,
+ okc_host_lookup_cb, host_lookup_list);
+ oca->dns_queries = g_slist_prepend(oca->dns_queries, query);
+ host_lookup_list = g_slist_append(host_lookup_list, query);
+ }
+ }
+
+ okconn = g_new0(OkCupidConnection, 1);
+ okconn->oca = oca;
+ okconn->method = method;
+ okconn->hostname = g_strdup(host);
+ okconn->request = request;
+ okconn->callback = callback_func;
+ okconn->user_data = user_data;
+ okconn->fd = -1;
+ okconn->connection_keepalive = keepalive;
+ okconn->request_time = time(NULL);
+
+ g_queue_push_head(oca->waiting_conns, okconn);
+ okc_next_connection(oca);
+}
+
+static void okc_next_connection(OkCupidAccount *oca)
+{
+ OkCupidConnection *okconn;
+
+ g_return_if_fail(oca != NULL);
+
+ if (!g_queue_is_empty(oca->waiting_conns))
+ {
+ if(g_slist_length(oca->conns) < OKC_MAX_CONNECTIONS)
+ {
+ okconn = g_queue_pop_tail(oca->waiting_conns);
+ okc_attempt_connection(okconn);
+ }
+ }
+}
+
+static void okc_attempt_connection(OkCupidConnection *okconn)
+{
+ OkCupidAccount *oca = okconn->oca;
+
+ oca->conns = g_slist_prepend(oca->conns, okconn);
+
+ if (okconn->method & OKC_METHOD_SSL) {
+ okconn->ssl_conn = purple_ssl_connect(oca->account, okconn->hostname,
+ 443, okc_post_or_get_ssl_connect_cb,
+ okc_ssl_connection_error, okconn);
+ } else {
+ okconn->connect_data = purple_proxy_connect(NULL, oca->account,
+ okconn->hostname, 80, okc_post_or_get_connect_cb, okconn);
+ }
+
+ return;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,62 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OKCUPID_CONNECTION_H
+#define OKCUPID_CONNECTION_H
+
+#include "libokcupid.h"
+#include <core.h>
+#include <zlib.h>
+
+/*
+ * This is a bitmask.
+ */
+typedef enum
+{
+ OKC_METHOD_GET = 0x0001,
+ OKC_METHOD_POST = 0x0002,
+ OKC_METHOD_SSL = 0x0004
+} OkCupidMethod;
+
+typedef struct _OkCupidConnection OkCupidConnection;
+struct _OkCupidConnection {
+ OkCupidAccount *oca;
+ OkCupidMethod method;
+ gchar *hostname;
+ GString *request;
+ OkCupidProxyCallbackFunc callback;
+ gpointer user_data;
+ char *rx_buf;
+ size_t rx_len;
+ PurpleProxyConnectData *connect_data;
+ PurpleSslConnection *ssl_conn;
+ int fd;
+ guint input_watcher;
+ gboolean connection_keepalive;
+ time_t request_time;
+};
+
+void okc_connection_destroy(OkCupidConnection *okcconn);
+void okc_post_or_get(OkCupidAccount *oca, OkCupidMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ OkCupidProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive);
+
+#endif /* OKCUPID_CONNECTION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_messages.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,537 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libokcupid.h"
+#include "okc_messages.h"
+#include "okc_connection.h"
+
+typedef struct _OkCupidOutgoingMessage OkCupidOutgoingMessage;
+
+struct _OkCupidOutgoingMessage {
+ OkCupidAccount *oca;
+ gchar *who;
+ time_t time;
+ gchar *message;
+ gint msg_id;
+ guint retry_count;
+ gint rid;
+};
+
+gboolean okc_send_im_fom(OkCupidOutgoingMessage *msg);
+void okc_check_inbox_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data);
+
+void okc_buddy_icon_cb(OkCupidAccount *oca, gchar *data, gsize data_len,
+ gpointer user_data)
+{
+ gchar *buddyname;
+ PurpleBuddy *buddy;
+ OkCupidBuddy *obuddy;
+ gpointer buddy_icon_data;
+
+ buddyname = user_data;
+
+ purple_debug_info("okcupid",
+ "buddy icon for buddy %s %" G_GSIZE_FORMAT "\n",
+ buddyname, data_len);
+
+ buddy = purple_find_buddy(oca->account, buddyname);
+ g_free(buddyname);
+ if (buddy == NULL || buddy->proto_data == NULL)
+ return;
+ obuddy = buddy->proto_data;
+
+ buddy_icon_data = g_memdup(data, data_len);
+
+ purple_buddy_icons_set_for_user(oca->account, buddy->name,
+ buddy_icon_data, data_len, obuddy->thumb_url);
+}
+
+void got_new_messages(OkCupidAccount *oca, gchar *data,
+ gsize data_len, gpointer userdata)
+{
+ PurpleConnection *pc = userdata;
+
+ /* NULL data will crash on Windows */
+ if (data == NULL)
+ data = "(null)";
+
+ purple_debug_misc("okcupid", "got new messages: %s\n", data);
+
+ /* Process incomming messages here */
+
+ gchar *start_of_json = strchr(data, '{');
+ gchar *end_of_json = strrchr(data, '}');
+
+ if (!start_of_json || !end_of_json || start_of_json >= end_of_json)
+ {
+ okc_get_new_messages(oca);
+ return;
+ }
+
+ gchar *json_string = g_strndup(start_of_json, end_of_json-start_of_json+1);
+
+ JsonParser *parser;
+ JsonNode *root;
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, json_string, -1, NULL))
+ {
+ g_free(json_string);
+ okc_get_new_messages(oca);
+ return;
+ }
+ g_free(json_string);
+ root = json_parser_get_root(parser);
+ JsonObject *objnode;
+ objnode = json_node_get_object(root);
+
+ JsonArray *events = NULL;
+ JsonArray *people = NULL;
+ int unread_message_count = 0;
+
+ if(json_object_has_member(objnode, "people"))
+ people = json_node_get_array(json_object_get_member(objnode, "people"));
+ if(json_object_has_member(objnode, "events"))
+ events = json_node_get_array(json_object_get_member(objnode, "events"));
+ if(json_object_has_member(objnode, "num_unread"))
+ unread_message_count = json_node_get_int(json_object_get_member(objnode, "num_unread"));
+
+ // Look through the buddy list for people to add first
+ if (people != NULL)
+ {
+ GList *people_list = json_array_get_elements(people);
+ GList *current;
+ for (current = people_list; current; current = g_list_next(current))
+ {
+ JsonNode *currentNode = current->data;
+ JsonObject *person = json_node_get_object(currentNode);
+
+ if (!json_node_get_int(json_object_get_member(person, "is_buddy")) &&
+ !purple_account_get_bool(oca->account, "show_stalkers", TRUE))
+ {
+ continue;
+ }
+
+ const gchar *buddy_name = json_node_get_string(json_object_get_member(person, "screenname"));
+ const gchar *buddy_icon = json_node_get_string(json_object_get_member(person, "thumbnail"));
+ gint is_online = json_node_get_int(json_object_get_member(person, "im_ok"));
+ if (!json_object_has_member(person, "im_ok"))
+ is_online = 1;
+
+ PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name);
+ if (!pbuddy)
+ {
+ //Not everyone we talk to will be on our buddylist
+ pbuddy = purple_buddy_new(oca->account, buddy_name, NULL);
+ purple_blist_add_buddy(pbuddy, NULL, NULL, NULL);
+ }
+ if (pbuddy != NULL)
+ {
+ if (!json_node_get_int(json_object_get_member(person, "is_buddy")))
+ {
+ purple_blist_node_set_flags(&(pbuddy->node), PURPLE_BLIST_NODE_FLAG_NO_SAVE);
+ } else {
+ purple_blist_node_set_flags(&(pbuddy->node), 0);
+ }
+ if (is_online && !PURPLE_BUDDY_IS_ONLINE(pbuddy))
+ {
+ purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
+ } else if (!is_online && PURPLE_BUDDY_IS_ONLINE(pbuddy))
+ {
+ purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE), NULL);
+ }
+ OkCupidBuddy *obuddy = pbuddy->proto_data;
+ if (obuddy == NULL)
+ {
+ const gchar *buddy_icon_url;
+
+ obuddy = g_new0(OkCupidBuddy, 1);
+ obuddy->buddy = pbuddy;
+ obuddy->oca = oca;
+
+ // load the old buddy icon url from the icon 'checksum'
+ buddy_icon_url = purple_buddy_icons_get_checksum_for_user(pbuddy);
+ if (buddy_icon_url != NULL)
+ obuddy->thumb_url = g_strdup(buddy_icon_url);
+
+ pbuddy->proto_data = obuddy;
+ }
+ if (!obuddy->thumb_url || !g_str_equal(obuddy->thumb_url, buddy_icon))
+ {
+ gchar *host, *path, *path2;
+
+ g_free(obuddy->thumb_url);
+ obuddy->thumb_url = purple_strreplace(buddy_icon, "/60x60/", "/256x256/");
+
+ purple_url_parse(obuddy->thumb_url, &host, NULL, &path, NULL, NULL);
+ if (path[0] != '/')
+ path2 = g_strdup_printf("/%s", path);
+ else
+ path2 = g_strdup(path);
+ okc_post_or_get(oca, OKC_METHOD_GET, host, path2, NULL, okc_buddy_icon_cb, g_strdup(buddy_name), FALSE);
+
+ g_free(host);
+ g_free(path);
+ g_free(path2);
+ }
+ }
+ }
+ g_list_free(people_list);
+ }
+
+ //loop through events looking for messages
+ if (events != NULL)
+ {
+ GList *event_list = json_array_get_elements(events);
+ GList *current;
+ for (current = event_list; current; current = g_list_next(current))
+ {
+ JsonNode *currentNode = current->data;
+ JsonObject *event = json_node_get_object(currentNode);
+ const gchar *event_type;
+
+ event_type = json_node_get_string(json_object_get_member(event, "type"));
+ if (g_str_equal(event_type, "im"))
+ {
+ //instant message
+ const gchar *message = json_node_get_string(json_object_get_member(event, "contents"));
+ gchar *message_html = okc_strdup_withhtml(message);
+
+ const gchar *who = NULL;
+ PurpleMessageFlags flags;
+ if (json_object_has_member(event, "to"))
+ {
+ who = json_node_get_string(json_object_get_member(event, "to"));
+ flags = PURPLE_MESSAGE_SEND;
+ } else if (json_object_has_member(event, "from"))
+ {
+ who = json_node_get_string(json_object_get_member(event, "from"));
+ flags = PURPLE_MESSAGE_RECV;
+ }
+ if (who && (flags != PURPLE_MESSAGE_SEND || purple_account_get_bool(oca->account, "show_sent_messages", FALSE)))
+ serv_got_im (pc, who, message_html, flags, time(NULL));
+
+ g_free(message_html);
+ } else if (g_str_equal(event_type, "orbit_user_signoff"))
+ {
+ //buddy signed off
+ const gchar *buddy_name = json_node_get_string(json_object_get_member(event, "from"));
+ PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name);
+
+ if (pbuddy && PURPLE_BUDDY_IS_ONLINE(pbuddy))
+ {
+ purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE), NULL);
+ }
+ } else if (g_str_equal(event_type, "stalk") && purple_account_get_bool(oca->account, "show_stalkers", TRUE))
+ {
+ //someone looked at the profile page (ie 'stalked' the user)
+ const gchar *buddy_name = json_node_get_string(json_object_get_member(event, "from"));
+ PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name);
+ gchar *stalk_message = g_strdup_printf("%s just viewed your profile", buddy_name);
+
+ serv_got_im(oca->pc, buddy_name, stalk_message, PURPLE_MESSAGE_SYSTEM, time(NULL));
+ g_free(stalk_message);
+ }
+ }
+ g_list_free(event_list);
+ }
+
+ if (unread_message_count != oca->last_message_count)
+ {
+ oca->last_message_count = unread_message_count;
+ if (unread_message_count > 0)
+ {
+ okc_post_or_get(oca, OKC_METHOD_GET, NULL, "/mailbox?folderid=1&low=1", NULL, okc_check_inbox_cb, NULL, FALSE);
+ }
+ }
+
+ if (json_object_has_member(objnode, "server_seqid"))
+ oca->server_seqid = json_node_get_int(json_object_get_member(objnode, "server_seqid"));
+ if (json_object_has_member(objnode, "server_gmt"))
+ oca->server_gmt = json_node_get_int(json_object_get_member(objnode, "server_gmt"));
+
+ g_object_unref(parser);
+
+ /* Continue looping, waiting for more messages */
+ okc_get_new_messages(oca);
+}
+
+gchar *
+okc_get_stalkers(OkCupidAccount *oca)
+{
+ GString *stalkers;
+ GSList *buddies;
+ GSList *l;
+ PurpleBuddy *buddy;
+
+ buddies = purple_find_buddies(oca->account, NULL);
+ stalkers = g_string_new(NULL);
+
+ for(l = buddies; l; l = l->next)
+ {
+ buddy = l->data;
+ if (buddy->node.flags & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
+ {
+ // A temp buddy? They're a stalker!
+ g_string_append_printf(stalkers, "%s,", buddy->name);
+ }
+ }
+ g_slist_free(buddies);
+
+ return g_string_free(stalkers, FALSE);
+}
+
+void okc_get_new_messages_now(OkCupidAccount *oca)
+{
+ gchar *fetch_url;
+ gchar *stalkers;
+ purple_debug_info("okcupid", "getting new messages now\n");
+
+ stalkers = okc_get_stalkers(oca);
+
+ fetch_url = g_strdup_printf("/instantevents?rand=0.%u&server_seqid=%u&server_gmt=%u&"
+ "load_thumbnails=1&buddylist=1&"
+ "show_offline=1&num_unread=1&im_status=1&"
+ "usernames=%s",
+ g_random_int(), oca->server_seqid, oca->server_gmt,
+ stalkers);
+
+ okc_post_or_get(oca, OKC_METHOD_GET, NULL, fetch_url, NULL, got_new_messages, oca->pc, FALSE);
+
+ g_free(fetch_url);
+ g_free(stalkers);
+}
+
+gboolean okc_get_new_messages(OkCupidAccount *oca)
+{
+ time_t now;
+ gchar *fetch_url;
+ gchar *stalkers;
+
+ oca->new_messages_check_timer = 0;
+
+ now = time(NULL);
+ if (oca->last_messages_download_time > now - 3) {
+ /*
+ * Wait a bit before fetching more messages, to make sure we
+ * never hammer their servers.
+ *
+ * TODO: This could be smarter. Like, allow 3 requests per
+ * 10 seconds or something.
+ */
+ oca->new_messages_check_timer = purple_timeout_add_seconds(
+ 3 - (now - oca->last_messages_download_time),
+ (GSourceFunc)okc_get_new_messages, oca);
+ return FALSE;
+ }
+
+ purple_debug_info("okcupid", "getting new messages\n");
+
+ stalkers = okc_get_stalkers(oca);
+ fetch_url = g_strdup_printf("/instantevents?rand=0.%u&server_seqid=%u&server_gmt=%u&"
+ "load_thumbnails=1&do_event_poll=1&buddylist=1&"
+ "show_offline=1&num_unread=1&im_status=1&"
+ "do_post_read=1&usernames=%s",
+ g_random_int(), oca->server_seqid, oca->server_gmt,
+ stalkers);
+
+ okc_post_or_get(oca, OKC_METHOD_GET, NULL, fetch_url, NULL, got_new_messages, oca->pc, TRUE);
+ oca->last_messages_download_time = now;
+
+ g_free(fetch_url);
+ g_free(stalkers);
+
+ return FALSE;
+}
+
+void okc_msg_destroy(OkCupidOutgoingMessage *msg)
+{
+ if (msg == NULL)
+ return;
+
+ g_free(msg->who);
+ g_free(msg->message);
+ g_free(msg);
+}
+
+void okc_send_im_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data)
+{
+ OkCupidOutgoingMessage *msg = user_data;
+
+ if (data == NULL || data_len == 0)
+ {
+ //No response, resend message
+ okc_send_im_fom(msg);
+ return;
+ }
+
+ purple_debug_misc("okcupid", "sent im response: %s\n", data);
+
+ /*
+ Possible responses:
+
+ {
+ "message_sent" : 0,
+ "reason" : "recip_not_online"
+ }
+ {
+ "message_sent" : 0,
+ "reason" : "im_self"
+ }
+
+ */
+ JsonParser *parser;
+ JsonNode *root;
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, data, data_len, NULL))
+ {
+ okc_msg_destroy(msg);
+ return;
+ }
+ root = json_parser_get_root(parser);
+ JsonObject *response;
+ response = json_node_get_object(root);
+
+ gint message_sent = json_node_get_int(json_object_get_member(response, "status"));
+
+ if (message_sent < 100)
+ {
+ //Save the message we sent
+ purple_debug_info("okcupid", "putting message into hashtable: '%s'\n", msg->message);
+
+ okc_msg_destroy(msg);
+ g_object_unref(parser);
+ return;
+ }
+
+ const gchar *reason = json_node_get_string(json_object_get_member(response, "status_str"));
+ if (reason != NULL)
+ {
+ if (g_str_equal(reason, "recip_not_online"))
+ {
+ serv_got_im(oca->pc, msg->who, _("Recipient not online"), PURPLE_MESSAGE_ERROR, time(NULL));
+ } else if (g_str_equal(reason, "im_self"))
+ {
+ serv_got_im(oca->pc, msg->who, _("You cannot send an IM to yourself"), PURPLE_MESSAGE_ERROR, time(NULL));
+ } else if (g_str_equal(reason, "im_not_ok"))
+ {
+ serv_got_im(oca->pc, msg->who, _("Recipient is 'missing'"), PURPLE_MESSAGE_ERROR, time(NULL));
+ } else if (g_str_equal(reason, "recip_im_off"))
+ {
+ serv_got_im(oca->pc, msg->who, _("Recipient turned IM off"), PURPLE_MESSAGE_ERROR, time(NULL));
+ }
+ }
+
+ okc_msg_destroy(msg);
+ g_object_unref(parser);
+}
+
+gboolean okc_send_im_fom(OkCupidOutgoingMessage *msg)
+{
+ gchar *encoded_message;
+ gchar *encoded_recipient;
+ gchar *postdata;
+
+ encoded_message = g_strdup(purple_url_encode(msg->message));
+ encoded_recipient = g_strdup(purple_url_encode(msg->who));
+ postdata = g_strdup_printf("send=1&attempt=%d&rid=%d&recipient=%s&topic=false&body=%s",
+ msg->retry_count + 1,
+ msg->rid,
+ encoded_recipient,
+ encoded_message);
+ g_free(encoded_message);
+ g_free(encoded_recipient);
+
+ okc_post_or_get(msg->oca, OKC_METHOD_POST, NULL, "/instantevents", postdata, okc_send_im_cb, msg, FALSE);
+ g_free(postdata);
+
+ return FALSE;
+}
+
+int okc_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, PurpleMessageFlags flags)
+{
+ OkCupidOutgoingMessage *msg;
+
+ msg = g_new0(OkCupidOutgoingMessage, 1);
+ msg->oca = pc->proto_data;
+
+ /* convert html to plaintext, removing trailing spaces */
+ msg->message = purple_markup_strip_html(message);
+
+ msg->rid = g_random_int_range(0, 2000000000); /* just fits inside a 32bit int */
+ msg->who = g_strdup(who);
+ msg->time = time(NULL);
+ msg->retry_count = 0;
+
+ okc_send_im_fom(msg);
+
+ if (purple_account_get_bool(pc->account, "show_sent_messages", FALSE))
+ {
+ return 0;
+ }
+
+ return strlen(message);
+}
+
+void okc_check_inbox_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data)
+{
+ JsonParser *parser;
+ JsonNode *root;
+ JsonObject *mailbox;
+ JsonArray *messages;
+
+ purple_debug_misc("okcupid", "check_inbox_cb\n%s", (data?data:"(null)"));
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, data, data_len, NULL))
+ {
+ purple_debug_warning("okcupid", "Could not parse mailbox data\n");
+ return;
+ }
+ root = json_parser_get_root(parser);
+ mailbox = json_node_get_object(root);
+ if (json_object_has_member(mailbox, "messages"))
+ {
+ messages = json_node_get_array(json_object_get_member(mailbox, "messages"));
+ GList *message_list = json_array_get_elements(messages);
+ GList *current;
+ for (current = message_list; current; current = g_list_next(current))
+ {
+ JsonNode *currentNode = current->data;
+ JsonObject *message = json_node_get_object(currentNode);
+ gboolean is_new = (gboolean) json_node_get_int(json_object_get_member(message, "is_new"));
+ if (!is_new)
+ {
+ continue;
+ }
+ const gchar *subject = json_node_get_string(json_object_get_member(message, "subject"));
+ const gchar *from = json_node_get_string(json_object_get_member(message, "person"));
+ const gchar *to = oca->account->username;
+ const gchar *thread_id = json_node_get_string(json_object_get_member(message, "thread_id"));
+ gchar *url = g_strdup_printf("http://www.okcupid.com/messages?readmsg=true&threadid=%s&folder=1", thread_id);
+ purple_notify_email(oca->pc, subject, from, to, url, NULL, NULL);
+ g_free(url);
+ }
+ g_list_free(message_list);
+ }
+
+ g_object_unref(parser);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_messages.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,33 @@
+/*
+ * libokcupid
+ *
+ * libokcupid is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OKCUPID_MESSAGES_H
+#define OKCUPID_MESSAGES_H
+
+#include "libokcupid.h"
+
+int okc_send_im(PurpleConnection *pc, const gchar *who, const gchar *message,
+ PurpleMessageFlags flags);
+gboolean okc_get_new_messages(OkCupidAccount *oca);
+void okc_get_new_messages_now(OkCupidAccount *oca);
+void okc_buddy_icon_cb(OkCupidAccount *oca, gchar *data, gsize data_len,
+ gpointer user_data);
+
+#endif /* OKCUPID_MESSAGES_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/libomegle.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,428 @@
+/*
+ * libfacebook
+ *
+ * libfacebook is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libomegle.h"
+#include "om_connection.h"
+
+#include <json-glib/json-glib.h>
+
+static void om_got_events(OmegleAccount *oma, gchar *response, gsize len,
+ gpointer userdata);
+
+/******************************************************************************/
+/* PRPL functions */
+/******************************************************************************/
+
+static const char *om_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
+{
+ return "omegle";
+}
+
+static GList *om_statuses(PurpleAccount *account)
+{
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, FALSE, FALSE, FALSE);
+ types = g_list_append(types, status);
+
+ return types;
+}
+
+static void om_login(PurpleAccount *account)
+{
+ PurpleBuddy *bud;
+ OmegleAccount *oma;
+
+ //make sure there's an Omegle buddy on the buddy list
+ bud = purple_find_buddy(account, "omegle");
+ if (bud == NULL)
+ {
+ bud = purple_buddy_new(account, "omegle", "Omegle");
+ purple_blist_add_buddy(bud, NULL, NULL, NULL);
+ }
+ purple_prpl_got_user_status(account, "omegle", purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
+
+
+ oma = g_new0(OmegleAccount, 1);
+ oma->account = account;
+ oma->pc = purple_account_get_connection(account);
+ oma->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ oma->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ account->gc->proto_data = oma;
+
+ //No such thing as a login
+ purple_connection_set_state(purple_account_get_connection(account), PURPLE_CONNECTED);
+
+}
+
+static void om_close(PurpleConnection *pc)
+{
+ OmegleAccount *oma;
+ GList *ims;
+
+ g_return_if_fail(pc != NULL);
+ g_return_if_fail(pc->proto_data != NULL);
+
+ ims = purple_get_ims();
+ //TODO: Loop through all im's and disconnect them all
+
+ oma = pc->proto_data;
+
+ while (oma->conns != NULL)
+ om_connection_destroy(oma->conns->data);
+ while (oma->dns_queries != NULL) {
+ PurpleDnsQueryData *dns_query = oma->dns_queries->data;
+ oma->dns_queries = g_slist_remove(oma->dns_queries, dns_query);
+ purple_dnsquery_destroy(dns_query);
+ }
+
+ g_hash_table_destroy(oma->cookie_table);
+ g_hash_table_destroy(oma->hostname_ip_cache);
+
+ g_free(oma);
+}
+
+static void om_convo_closed(PurpleConnection *pc, const char *who)
+{
+ OmegleAccount *oma;
+ gchar *postdata;
+
+ oma = pc->proto_data;
+ postdata = g_strdup_printf("id=%s", purple_url_encode(who));
+
+ om_post_or_get(oma, OM_METHOD_POST, NULL, "/disconnect",
+ postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+}
+
+static void om_fetch_events(OmegleAccount *oma, gchar *who)
+{
+ gchar *postdata;
+
+ postdata = g_strdup_printf("id=%s", purple_url_encode(who));
+
+ om_post_or_get(oma, OM_METHOD_POST, NULL, "/events",
+ postdata, om_got_events, who, FALSE);
+
+ g_free(postdata);
+}
+
+static void om_got_events(OmegleAccount *oma, gchar *response, gsize len,
+ gpointer userdata)
+{
+ //[["waiting"], ["connected"]]
+ gchar *who = userdata;
+ const gchar *message;
+ const gchar *event_type;
+ JsonParser *parser;
+ JsonNode *rootnode, *currentnode;
+ JsonArray *array, *current;
+ guint i;
+
+ purple_debug_info("omegle", "got events: %s\n", response?response:"(null)");
+
+ if (!response || g_str_equal(response, "null"))
+ {
+ g_free(who);
+ return;
+ }
+
+ parser = json_parser_new();
+ json_parser_load_from_data(parser, response, len, NULL);
+ rootnode = json_parser_get_root(parser);
+ if (!rootnode)
+ {
+ g_object_unref(parser);
+ return;
+ }
+ array = json_node_get_array(rootnode);
+
+ for(i=0; i<json_array_get_length(array); i++)
+ {
+ currentnode = json_array_get_element(array, i);
+ current = json_node_get_array(currentnode);
+ event_type = json_node_get_string(json_array_get_element(current, 0));
+ if (!event_type)
+ {
+ continue;
+ } else if (g_str_equal(event_type, "waiting")) {
+ serv_got_im(oma->pc, who, "Looking for someone you can chat with. Hang on.", PURPLE_MESSAGE_SYSTEM, time(NULL));
+ } else if (g_str_equal(event_type, "connected")) {
+ serv_got_im(oma->pc, who, "You're now chatting with a random stranger. Say hi!", PURPLE_MESSAGE_SYSTEM, time(NULL));
+ } else if (g_str_equal(event_type, "gotMessage")) {
+ //[["gotMessage","message goes here"]]
+ message = json_node_get_string(json_array_get_element(current, 1));
+ if (message)
+ serv_got_im(oma->pc, who, message, PURPLE_MESSAGE_RECV, time(NULL));
+ } else if (g_str_equal(event_type, "typing")) {
+ serv_got_typing(oma->pc, who, 10, PURPLE_TYPING);
+ } else if (g_str_equal(event_type, "stoppedTyping")) {
+ serv_got_typing(oma->pc, who, 10, PURPLE_TYPED);
+ } else if (g_str_equal(event_type, "strangerDisconnected")) {
+ serv_got_im(oma->pc, who, "Your conversational partner has disconnected", PURPLE_MESSAGE_SYSTEM, time(NULL));
+ }
+ }
+
+ om_fetch_events(oma, g_strdup(who));
+
+ g_free(who);
+ g_object_unref(parser);
+}
+
+static void om_start_im_cb(OmegleAccount *oma, gchar *response, gsize len,
+ gpointer userdata)
+{
+ gchar *id;
+
+ //This should come back with an ID that we pass around
+ id = g_strdup(response);
+ purple_str_strip_char(id, '"');
+
+ //Start the event loop
+ om_fetch_events(oma, g_strdup(id));
+
+ g_free(id);
+}
+
+static void om_start_im(PurpleBlistNode *node, gpointer data)
+{
+ PurpleBuddy *buddy;
+ OmegleAccount *oma;
+ PurpleConnection *pc;
+
+ if(!PURPLE_BLIST_NODE_IS_BUDDY(node))
+ return;
+ buddy = (PurpleBuddy *) node;
+ if (!buddy)
+ return;
+ pc = purple_account_get_connection(buddy->account);
+ oma = pc->proto_data;
+
+ om_post_or_get(oma, OM_METHOD_POST, NULL, "/start",
+ NULL, om_start_im_cb, NULL, FALSE);
+}
+
+static GList *om_node_menu(PurpleBlistNode *node)
+{
+ GList *m = NULL;
+ PurpleMenuAction *act;
+ PurpleBuddy *buddy;
+
+ if(PURPLE_BLIST_NODE_IS_BUDDY(node))
+ {
+ buddy = (PurpleBuddy *)node;
+
+ act = purple_menu_action_new(_("_Start random IM"),
+ PURPLE_CALLBACK(om_start_im),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+ return m;
+}
+
+static unsigned int om_send_typing(PurpleConnection *pc, const gchar *name,
+ PurpleTypingState state)
+{
+ gchar *postdata;
+ OmegleAccount *oma = pc->proto_data;
+ gchar *url;
+
+ g_return_val_if_fail(oma != NULL, 0);
+
+ if (state == PURPLE_TYPING)
+ {
+ url = "/typing";
+ } else if (state == PURPLE_TYPED)
+ {
+ url = "/stoppedtyping";
+ } else {
+ return 0;
+ }
+
+ postdata = g_strdup_printf("id=%s", purple_url_encode(name));
+
+ om_post_or_get(oma, OM_METHOD_POST, NULL, url, postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+
+ return 10;
+}
+
+static int om_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, PurpleMessageFlags flags)
+{
+ OmegleAccount *oma;
+ gchar *encoded_name;
+ gchar *encoded_message;
+ gchar *postdata;
+
+ oma = pc->proto_data;
+ encoded_name = g_strdup(purple_url_encode(who));
+ encoded_message = g_strdup(purple_url_encode(message));
+
+ postdata = g_strdup_printf("id=%s&msg=%s", encoded_name, encoded_message);
+
+ om_post_or_get(oma, OM_METHOD_POST, NULL, "/send", postdata, NULL, NULL, FALSE);
+
+ g_free(postdata);
+ g_free(encoded_name);
+ g_free(encoded_message);
+
+ return strlen(message);
+}
+
+/******************************************************************************/
+/* Plugin functions */
+/******************************************************************************/
+
+static gboolean plugin_load(PurplePlugin *plugin)
+{
+ PurpleAccountOption *option;
+ PurplePluginInfo *info = plugin->info;
+ PurplePluginProtocolInfo *prpl_info = info->extra_info;
+
+ option = purple_account_option_string_new("Server", "host", "bajor.omegle.com");
+ prpl_info->protocol_options = g_list_append(
+ prpl_info->protocol_options, option);
+
+ return TRUE;
+}
+
+static gboolean plugin_unload(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static void plugin_init(PurplePlugin *plugin)
+{
+
+}
+
+static PurplePluginProtocolInfo prpl_info = {
+ /* options */
+ OPT_PROTO_NO_PASSWORD,
+
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ NO_BUDDY_ICONS, /* icon_spec */
+ om_list_icon, /* list_icon */
+ NULL, /* list_emblems */
+ NULL, /* status_text */
+ NULL, /* tooltip_text */
+ om_statuses, /* status_types */
+ om_node_menu, /* blist_node_menu */
+ NULL, /* chat_info */
+ NULL, /* chat_info_defaults */
+ om_login, /* login */
+ om_close, /* close */
+ om_send_im, /* send_im */
+ NULL, /* set_info */
+ om_send_typing, /* send_typing */
+ NULL, /* get_info */
+ NULL, /* set_status */
+ NULL, /* set_idle */
+ NULL, /* change_passwd */
+ NULL, /* add_buddy */
+ NULL, /* add_buddies */
+ NULL, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ NULL, /* join_chat */
+ NULL, /* reject chat invite */
+ NULL, /* get_chat_name */
+ NULL, /* chat_invite */
+ NULL, /* chat_leave */
+ NULL, /* chat_whisper */
+ NULL, /* chat_send */
+ NULL, /* keepalive */
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ NULL, /* alias_buddy */
+ NULL, /* group_buddy */
+ NULL, /* rename_group */
+ NULL, /* buddy_free */
+ om_convo_closed, /* convo_closed */
+ purple_normalize_nocase,/* normalize */
+ NULL, /* set_buddy_icon */
+ NULL, /* remove_group */
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ NULL, /* send_file */
+ NULL, /* new_xfer */
+ NULL, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
+ NULL, /* get_account_text_table */
+#else
+ (gpointer) sizeof(PurplePluginProtocolInfo)
+#endif
+};
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC,
+ 2, /* major_version */
+ 3, /* minor version */
+ PURPLE_PLUGIN_PROTOCOL, /* type */
+ NULL, /* ui_requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ OMEGLE_PLUGIN_ID, /* id */
+ "Omegle", /* name */
+ OMEGLE_PLUGIN_VERSION, /* version */
+ N_("Omegle Protocol Plugin"), /* summary */
+ N_("Omegle Protocol Plugin"), /* description */
+ "Eion Robb <eionrobb@gmail.com>", /* author */
+ "http://pidgin-omegle.googlecode.com/", /* homepage */
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ &prpl_info, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PURPLE_INIT_PLUGIN(facebook, plugin_init, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/libomegle.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,90 @@
+/*
+ * libomegle
+ *
+ * libomegle is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBOMEGLE_H
+#define LIBOMEGLE_H
+
+#define OMEGLE_PLUGIN_VERSION "0"
+#define OMEGLE_PLUGIN_ID "prpl-bigbrownchunx-omegle"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef G_GNUC_NULL_TERMINATED
+# if __GNUC__ >= 4
+# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+# else
+# define G_GNUC_NULL_TERMINATED
+# endif /* __GNUC__ >= 4 */
+#endif /* G_GNUC_NULL_TERMINATED */
+
+#ifdef _WIN32
+# include "win32dep.h"
+# define dlopen(a,b) LoadLibrary(a)
+# define RTLD_LAZY
+# define dlsym(a,b) GetProcAddress(a,b)
+# define dlclose(a) FreeLibrary(a)
+#else
+# include <arpa/inet.h>
+# include <dlfcn.h>
+# include <netinet/in.h>
+# include <sys/socket.h>
+#endif
+
+#ifndef PURPLE_PLUGINS
+# define PURPLE_PLUGINS
+#endif
+
+#include "accountopt.h"
+#include "connection.h"
+#include "debug.h"
+#include "dnsquery.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "sslconn.h"
+#include "version.h"
+
+#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 12
+# define atoll(a) g_ascii_strtoll(a, NULL, 0)
+#endif
+
+#define OM_MAX_MSG_RETRY 2
+
+typedef struct _OmegleAccount OmegleAccount;
+typedef struct _OmegleBuddy OmegleBuddy;
+
+typedef void (*OmegleProxyCallbackFunc)(OmegleAccount *oma, gchar *data, gsize data_len, gpointer user_data);
+
+struct _OmegleAccount {
+ PurpleAccount *account;
+ PurpleConnection *pc;
+ GSList *conns; /**< A list of all active OmegleConnections */
+ GSList *dns_queries;
+ GHashTable *cookie_table;
+ GHashTable *hostname_ip_cache;
+};
+
+#endif /* LIBOMEGLE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/om_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,632 @@
+/*
+ * libomegle
+ *
+ * libomegle is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "om_connection.h"
+
+static void om_attempt_connection(OmegleConnection *);
+
+#include <zlib.h>
+
+static gchar *om_gunzip(const guchar *gzip_data, ssize_t *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("omegle", "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("omegle", "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("omegle", "gzip inflate error\n");
+ }
+ inflateEnd(&zstr);
+
+ g_free(data_buffer);
+
+ gchar *output_data = g_strdup(output_string->str);
+ *len_ptr = output_string->len;
+
+ g_string_free(output_string, TRUE);
+
+ return output_data;
+}
+
+void om_connection_destroy(OmegleConnection *omconn)
+{
+ omconn->oma->conns = g_slist_remove(omconn->oma->conns, omconn);
+
+ if (omconn->request != NULL)
+ g_string_free(omconn->request, TRUE);
+
+ g_free(omconn->rx_buf);
+
+ if (omconn->connect_data != NULL)
+ purple_proxy_connect_cancel(omconn->connect_data);
+
+ if (omconn->ssl_conn != NULL)
+ purple_ssl_close(omconn->ssl_conn);
+
+ if (omconn->fd >= 0) {
+ close(omconn->fd);
+ }
+
+ if (omconn->input_watcher > 0)
+ purple_input_remove(omconn->input_watcher);
+
+ g_free(omconn->url);
+ g_free(omconn->hostname);
+ g_free(omconn);
+}
+
+static void om_update_cookies(OmegleAccount *oma, 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(oma->cookie_table, cookie_name,
+ cookie_value);
+ }
+}
+
+static void om_connection_process_data(OmegleConnection *omconn)
+{
+ ssize_t len;
+ gchar *tmp;
+
+ len = omconn->rx_len;
+ tmp = g_strstr_len(omconn->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(omconn->rx_buf, len);
+ } else {
+ tmp += 4;
+ len -= g_strstr_len(omconn->rx_buf, len, "\r\n\r\n") -
+ omconn->rx_buf + 4;
+ tmp = g_memdup(tmp, len + 1);
+ tmp[len] = '\0';
+ omconn->rx_buf[omconn->rx_len - len] = '\0';
+ om_update_cookies(omconn->oma, omconn->rx_buf);
+
+ if (strstr(omconn->rx_buf, "Content-Encoding: gzip"))
+ {
+ /* we've received compressed gzip data, decompress */
+ gchar *gunzipped;
+ gunzipped = om_gunzip((const guchar *)tmp, &len);
+ g_free(tmp);
+ tmp = gunzipped;
+ }
+ }
+
+ g_free(omconn->rx_buf);
+ omconn->rx_buf = NULL;
+
+ if (omconn->callback != NULL) {
+ purple_debug_info("omegle", "executing callback for %s\n", omconn->url);
+ omconn->callback(omconn->oma, tmp, len, omconn->user_data);
+ }
+
+ g_free(tmp);
+}
+
+static void om_fatal_connection_cb(OmegleConnection *omconn)
+{
+ PurpleConnection *pc = omconn->oma->pc;
+
+ purple_debug_error("omegle", "fatal connection error\n");
+
+ om_connection_destroy(omconn);
+
+ /* 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_reason(pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Server closed the connection."));
+
+}
+
+static void om_post_or_get_readdata_cb(gpointer data, gint source,
+ PurpleInputCondition cond)
+{
+ OmegleConnection *omconn;
+ gchar buf[4096];
+ ssize_t len;
+
+ omconn = data;
+
+ if (omconn->method & OM_METHOD_SSL) {
+ len = purple_ssl_read(omconn->ssl_conn,
+ buf, sizeof(buf) - 1);
+ } else {
+ len = recv(omconn->fd, buf, sizeof(buf) - 1, 0);
+ }
+
+ if (len < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ /* Try again later */
+ return;
+ }
+
+ if (omconn->method & OM_METHOD_SSL && omconn->rx_len > 0) {
+ /*
+ * This is a slightly hacky workaround for a bug in either
+ * GNU TLS or in the SSL implementation on Omegle'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("omegle",
+ "ssl error, but data received. attempting to continue\n");
+ } else {
+ /* TODO: Is this a regular occurrence? If so then maybe resend the request? */
+ om_fatal_connection_cb(omconn);
+ return;
+ }
+ }
+
+ if (len > 0)
+ {
+ buf[len] = '\0';
+
+ omconn->rx_buf = g_realloc(omconn->rx_buf,
+ omconn->rx_len + len + 1);
+ memcpy(omconn->rx_buf + omconn->rx_len, buf, len + 1);
+ omconn->rx_len += len;
+
+ /* Wait for more data before processing */
+ return;
+ }
+
+ /* The server closed the connection, let's parse the data */
+ om_connection_process_data(omconn);
+
+ om_connection_destroy(omconn);
+}
+
+static void om_post_or_get_ssl_readdata_cb (gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ om_post_or_get_readdata_cb(data, -1, cond);
+}
+
+static void om_post_or_get_connect_cb(gpointer data, gint source,
+ const gchar *error_message)
+{
+ OmegleConnection *omconn;
+ ssize_t len;
+
+ omconn = data;
+ omconn->connect_data = NULL;
+
+ if (error_message)
+ {
+ purple_debug_error("omegle", "post_or_get_connect failure to %s\n", omconn->url);
+ purple_debug_error("omegle", "post_or_get_connect_cb %s\n",
+ error_message);
+ om_fatal_connection_cb(omconn);
+ return;
+ }
+
+ omconn->fd = source;
+
+ /* TODO: Check the return value of write() */
+ len = write(omconn->fd, omconn->request->str,
+ omconn->request->len);
+ omconn->input_watcher = purple_input_add(omconn->fd,
+ PURPLE_INPUT_READ,
+ om_post_or_get_readdata_cb, omconn);
+}
+
+static void om_post_or_get_ssl_connect_cb(gpointer data,
+ PurpleSslConnection *ssl, PurpleInputCondition cond)
+{
+ OmegleConnection *omconn;
+ ssize_t len;
+
+ omconn = data;
+
+ purple_debug_info("omegle", "post_or_get_ssl_connect_cb\n");
+
+ /* TODO: Check the return value of write() */
+ len = purple_ssl_write(omconn->ssl_conn,
+ omconn->request->str, omconn->request->len);
+ purple_ssl_input_add(omconn->ssl_conn,
+ om_post_or_get_ssl_readdata_cb, omconn);
+}
+
+static void om_host_lookup_cb(GSList *hosts, gpointer data,
+ const char *error_message)
+{
+ GSList *host_lookup_list;
+ struct sockaddr_in *addr;
+ gchar *hostname;
+ gchar *ip_address;
+ OmegleAccount *oma;
+ PurpleDnsQueryData *query;
+
+ /* Extract variables */
+ host_lookup_list = data;
+
+ oma = 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. */
+ oma->dns_queries = g_slist_remove(oma->dns_queries, query);
+
+ /* Any problems, capt'n? */
+ if (error_message != NULL)
+ {
+ purple_debug_warning("omegle",
+ "Error doing host lookup: %s\n", error_message);
+ return;
+ }
+
+ if (hosts == NULL)
+ {
+ purple_debug_warning("omegle",
+ "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(oma->hostname_ip_cache, hostname, ip_address);
+}
+
+static void om_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 oma->cookie_table hash table to a string.
+ */
+static gchar *om_cookies_to_string(OmegleAccount *oma)
+{
+ GString *str;
+
+ str = g_string_new(NULL);
+
+ g_hash_table_foreach(oma->cookie_table,
+ (GHFunc)om_cookie_foreach_cb, str);
+
+ return g_string_free(str, FALSE);
+}
+
+static void om_ssl_connection_error(PurpleSslConnection *ssl,
+ PurpleSslErrorType errortype, gpointer data)
+{
+ OmegleConnection *omconn = data;
+ PurpleConnection *pc = omconn->oma->pc;
+
+ omconn->ssl_conn = NULL;
+ om_connection_destroy(omconn);
+ purple_connection_ssl_error(pc, errortype);
+}
+
+void om_post_or_get(OmegleAccount *oma, OmegleMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ OmegleProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive)
+{
+ GString *request;
+ gchar *cookies;
+ OmegleConnection *omconn;
+ 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 = purple_account_get_string(oma->account, "host", "bajor.omegle.com");
+
+ if (oma && oma->account && !(method & OM_METHOD_SSL))
+ {
+ proxy_info = purple_proxy_get_setup(oma->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 = om_cookies_to_string(oma);
+ user_agent = purple_account_get_string(oma->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)");
+
+ if (method & OM_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 & OM_METHOD_POST) ? "POST" : "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 & OM_METHOD_POST) {
+ 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: application/json, text/html, */*\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("omegle", "getting url %s\n", url);
+
+ g_string_append_printf(request, "\r\n");
+ if (method & OM_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 == OM_METHOD_POST)
+ purple_debug_info("omegle", "sending request data:\n%s\n",
+ postdata);
+
+ g_free(cookies);
+
+ /*
+ * 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)
+ {
+ /* Don't do this for proxy connections, since proxies do the DNS lookup */
+ gchar *host_ip;
+
+ host_ip = g_hash_table_lookup(oma->hostname_ip_cache, host);
+ if (host_ip != NULL) {
+ host = host_ip;
+ } else if (oma->account && !oma->account->disconnecting) {
+ GSList *host_lookup_list = NULL;
+ PurpleDnsQueryData *query;
+
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, g_strdup(host));
+ host_lookup_list = g_slist_prepend(
+ host_lookup_list, oma);
+
+ query = purple_dnsquery_a(host, 80,
+ om_host_lookup_cb, host_lookup_list);
+ oma->dns_queries = g_slist_prepend(oma->dns_queries, query);
+ host_lookup_list = g_slist_append(host_lookup_list, query);
+ }
+ }
+
+ omconn = g_new0(OmegleConnection, 1);
+ omconn->oma = oma;
+ omconn->url = real_url;
+ omconn->method = method;
+ omconn->hostname = g_strdup(host);
+ omconn->request = request;
+ omconn->callback = callback_func;
+ omconn->user_data = user_data;
+ omconn->fd = -1;
+ omconn->connection_keepalive = keepalive;
+ omconn->request_time = time(NULL);
+ oma->conns = g_slist_prepend(oma->conns, omconn);
+
+ om_attempt_connection(omconn);
+}
+
+static void om_attempt_connection(OmegleConnection *omconn)
+{
+ OmegleAccount *oma = omconn->oma;
+
+#if 0
+ /* Connection to attempt retries. This code doesn't work perfectly, but
+ * remains here for future reference if needed */
+ if (time(NULL) - omconn->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("omegle", "could not connect after retries\n");
+ om_fatal_connection_cb(omconn);
+ return;
+ }
+
+ purple_debug_info("omegle", "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
+
+ if (omconn->method & OM_METHOD_SSL) {
+ omconn->ssl_conn = purple_ssl_connect(oma->account, omconn->hostname,
+ 443, om_post_or_get_ssl_connect_cb,
+ om_ssl_connection_error, omconn);
+ } else {
+ omconn->connect_data = purple_proxy_connect(NULL, oma->account,
+ omconn->hostname, 80, om_post_or_get_connect_cb, omconn);
+ }
+
+ return;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/om_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,61 @@
+/*
+ * libomegle
+ *
+ * libomegle is the property of its developers. See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OMEGLE_CONNECTION_H
+#define OMEGLE_CONNECTION_H
+
+#include "libomegle.h"
+
+/*
+ * This is a bitmask.
+ */
+typedef enum
+{
+ OM_METHOD_GET = 0x0001,
+ OM_METHOD_POST = 0x0002,
+ OM_METHOD_SSL = 0x0004
+} OmegleMethod;
+
+typedef struct _OmegleConnection OmegleConnection;
+struct _OmegleConnection {
+ OmegleAccount *oma;
+ OmegleMethod method;
+ gchar *hostname;
+ gchar *url;
+ GString *request;
+ OmegleProxyCallbackFunc callback;
+ gpointer user_data;
+ char *rx_buf;
+ size_t rx_len;
+ PurpleProxyConnectData *connect_data;
+ PurpleSslConnection *ssl_conn;
+ int fd;
+ guint input_watcher;
+ gboolean connection_keepalive;
+ time_t request_time;
+};
+
+void om_connection_destroy(OmegleConnection *omconn);
+void om_post_or_get(OmegleAccount *oma, OmegleMethod method,
+ const gchar *host, const gchar *url, const gchar *postdata,
+ OmegleProxyCallbackFunc callback_func, gpointer user_data,
+ gboolean keepalive);
+
+#endif /* OMEGLE_CONNECTION_H */