aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjgeboski <jgeboski@gmail.com>2015-01-01 16:21:43 -0500
committerjgeboski <jgeboski@gmail.com>2015-01-03 14:40:48 -0500
commitaaf01c2b00420fd7e28b80c1a3c419f074b2b542 (patch)
treec22ccc9533d6850d8c22e4c5d3c9d52e0bda41d7
parentc33c1ed33b5ab7eea97402a498e4c101f6d43202 (diff)
downloadbitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.gz
bitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.bz2
bitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.xz
Implemented user authentication
-rw-r--r--facebook/Makefile.am10
-rw-r--r--facebook/facebook-api.c294
-rw-r--r--facebook/facebook-api.h123
-rw-r--r--facebook/facebook-http.c764
-rw-r--r--facebook/facebook-http.h177
-rw-r--r--facebook/facebook-json.c316
-rw-r--r--facebook/facebook-json.h74
-rw-r--r--facebook/facebook-util.c55
-rw-r--r--facebook/facebook-util.h49
-rw-r--r--facebook/facebook.c67
-rw-r--r--facebook/facebook.h2
11 files changed, 1927 insertions, 4 deletions
diff --git a/facebook/Makefile.am b/facebook/Makefile.am
index d546682..f183717 100644
--- a/facebook/Makefile.am
+++ b/facebook/Makefile.am
@@ -5,7 +5,15 @@ facebook_la_CFLAGS = $(BITLBEE_CFLAGS) $(GLIB_CFLAGS)
facebook_la_LDFLAGS = $(BITLBEE_LIBS) $(GLIB_LIBS)
facebook_la_SOURCES = \
facebook.c \
- facebook.h
+ facebook-api.c \
+ facebook-http.c \
+ facebook-json.c \
+ facebook-util.c \
+ facebook.h \
+ facebook-api.h
+ facebook-http.h \
+ facebook-json.h \
+ facebook-util.h
# Build the library as a module
facebook_la_LDFLAGS += -module -avoid-version
diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c
new file mode 100644
index 0000000..817e19c
--- /dev/null
+++ b/facebook/facebook-api.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "facebook-api.h"
+
+/**
+ * Gets the error domain for #fb_api.
+ *
+ * @return The #GQuark of the error domain.
+ **/
+GQuark fb_api_error_quark(void)
+{
+ static GQuark q;
+
+ if (G_UNLIKELY(q == 0))
+ q = g_quark_from_static_string("fb-api-error-quark");
+
+ return q;
+}
+
+/**
+ * Creates a new #fb_api. The returned #fb_api should be freed with
+ * #fb_api_free() when no longer needed.
+ *
+ * @param funcs The #fb_api_funcs.
+ * @param data The user-defined data or NULL.
+ *
+ * @return The #fb_api or NULL on error.
+ **/
+fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data)
+{
+ fb_api_t *api;
+
+ g_return_val_if_fail(funcs != NULL, NULL);
+
+ api = g_new0(fb_api_t, 1);
+ memcpy(&api->funcs, funcs, sizeof *funcs);
+ api->data = data;
+ api->http = fb_http_new(FB_API_AGENT);
+
+ return api;
+}
+
+/**
+ * Frees all memory used by a #fb_api.
+ *
+ * @param api The #fb_api.
+ **/
+void fb_api_free(fb_api_t *api)
+{
+ if (G_UNLIKELY(api == NULL))
+ return;
+
+ if (api->err != NULL)
+ g_error_free(api->err);
+
+ fb_http_free(api->http);
+
+ g_free(api->token);
+ g_free(api);
+}
+
+/**
+ * Handles an error within an #fb_api. This sets #fb_api->err and calls
+ * the error function. If the fmt argument is NULL, then #fb_api->err
+ * is handled.
+ *
+ * @param api The #fb_api.
+ * @param err The #fb_api_error.
+ * @param fmt The format string or NULL.
+ * @param ... The arguments of the format string.
+ **/
+void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...)
+{
+ gchar *str;
+ va_list ap;
+
+ g_return_if_fail(api != NULL);
+
+ if (fmt != NULL) {
+ va_start(ap, fmt);
+ str = g_strdup_vprintf(fmt, ap);
+ va_end(ap);
+
+ g_clear_error(&api->err);
+ g_set_error_literal(&api->err, FB_API_ERROR, err, str);
+ g_free(str);
+ }
+
+ if (api->err != NULL)
+ FB_API_FUNC(api, error, api->err);
+}
+
+/**
+ * Creates a new #json_value for JSON contents of the #fb_api. This
+ * function is special in that it handles all errors, unlike the parent
+ * function #fb_json_new(). The returned #json_value should be freed
+ * with #json_value_free() when no longer needed.
+ *
+ * @param api The #fb_api.
+ * @param data The data.
+ * @param size The size of the data.
+ *
+ * @return TRUE if the data was parsed without error, otherwise FALSE.
+ **/
+static json_value *fb_api_json_new(fb_api_t *api, const gchar *data,
+ gsize size)
+{
+ json_value *json;
+ const gchar *msg;
+ gint64 code;
+
+ json = fb_json_new(data, size, &api->err);
+
+ if (api->err != NULL) {
+ fb_api_error(api, 0, NULL);
+ return NULL;
+ }
+
+ if (fb_json_int_chk(json, "error_code", &code)) {
+ if (!fb_json_str_chk(json, "error_msg", &msg))
+ msg = "Generic Error";
+
+ fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg);
+ json_value_free(json);
+ return NULL;
+ }
+
+ return json;
+}
+
+/**
+ * Creates a new #fb_http_req for a #fb_api request.
+ *
+ * @param api The #fb_api.
+ * @param host The host.
+ * @param path The path.
+ * @param func The #fb_http_func.
+ * @param class The class.
+ * @param name The friendly name.
+ * @param method The method.
+ **/
+static fb_http_req_t *fb_api_req_new(fb_api_t *api, const gchar *host,
+ const gchar *path, fb_http_func_t func,
+ const gchar *class, const gchar *name,
+ const gchar *method)
+{
+ fb_http_req_t *req;
+
+ req = fb_http_req_new(api->http, host, 443, path, func, api);
+ req->flags = FB_HTTP_REQ_FLAG_POST | FB_HTTP_REQ_FLAG_SSL;
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("api_key", FB_API_KEY),
+ FB_HTTP_PAIR("fb_api_caller_class", class),
+ FB_HTTP_PAIR("fb_api_req_friendly_name", name),
+ FB_HTTP_PAIR("method", method),
+ FB_HTTP_PAIR("client_country_code", "US"),
+ FB_HTTP_PAIR("format", "json"),
+ FB_HTTP_PAIR("locale", "en_US"),
+ NULL
+ );
+
+ return req;
+}
+
+/**
+ * Sends a #fb_http_req for a #fb_api. This computes the signature for
+ * the request and sets the "sig" parameter. This sets the OAuth header
+ * for authorization.
+ *
+ * @param api The #fb_api.
+ * @param req The #fb_http_req.
+ **/
+static void fb_api_req_send(fb_api_t *api, fb_http_req_t *req)
+{
+ GString *gstr;
+ GList *keys;
+ GList *l;
+ const gchar *key;
+ const gchar *val;
+ gchar *hash;
+ gchar *auth;
+
+ /* Ensure an old signature is not computed */
+ g_hash_table_remove(req->params, "sig");
+
+ gstr = g_string_sized_new(128);
+ keys = g_hash_table_get_keys(req->params);
+ keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp);
+
+ for (l = keys; l != NULL; l = l->next) {
+ key = l->data;
+ val = g_hash_table_lookup(req->params, key);
+ g_string_append_printf(gstr, "%s=%s", key, val);
+ }
+
+ g_string_append(gstr, FB_API_SECRET);
+ hash = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len);
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("sig", hash),
+ NULL
+ );
+
+ g_free(hash);
+ g_list_free(keys);
+ g_string_free(gstr, TRUE);
+
+ if (api->token != NULL) {
+ auth = g_strdup_printf("OAuth %s", api->token);
+ fb_http_req_headers_set(req,
+ FB_HTTP_PAIR("Authorization", auth),
+ NULL
+ );
+ g_free(auth);
+ }
+
+ fb_http_req_send(req);
+}
+
+/**
+ * Implemented #fb_http_func for #fb_api_auth().
+ *
+ * @param req The #fb_http_req.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_auth(fb_http_req_t *req, gpointer data)
+{
+ fb_api_t *api = data;
+ json_value *json;
+ const gchar *str;
+
+ json = fb_api_json_new(api, req->body, req->body_size);
+
+ if (json == NULL)
+ return;
+
+ if (!fb_json_str_chk(json, "access_token", &str)) {
+ fb_api_error(api, FB_API_ERROR_GENERAL, "Failed to obtain token");
+ goto finish;
+ }
+
+ g_free(api->token);
+ api->token = g_strdup(str);
+ FB_API_FUNC(api, auth);
+
+finish:
+ json_value_free(json);
+}
+
+/**
+ * Sends a authentication request.
+ *
+ * @param api The #fb_api.
+ * @param user The username (email).
+ * @param pass The password.
+ **/
+void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass)
+{
+ fb_http_req_t *req;
+
+ g_return_if_fail(api != NULL);
+
+ req = fb_api_req_new(api, FB_API_BHOST, FB_API_PATH_AUTH,
+ fb_api_cb_auth,
+ "com.facebook.auth.protocol.d",
+ "authenticate",
+ "auth.login");
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("email", user),
+ FB_HTTP_PAIR("password", pass),
+ NULL
+ );
+
+ fb_api_req_send(api, req);
+}
diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h
new file mode 100644
index 0000000..f8711ad
--- /dev/null
+++ b/facebook/facebook-api.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file **/
+
+#ifndef _FACEBOOK_API_H
+#define _FACEBOOK_API_H
+
+#include <bitlbee.h>
+
+#include "facebook-http.h"
+#include "facebook-json.h"
+
+#define FB_API_HOST "api.facebook.com"
+#define FB_API_BHOST "b-api.facebook.com"
+#define FB_API_AGENT "Facebook App / " PACKAGE " / " PACKAGE_VERSION
+#define FB_API_KEY "256002347743983"
+#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895"
+
+#define FB_API_PATH_AUTH "/method/auth.login"
+
+
+/**
+ * Executes one of the #fb_api_funcs.
+ *
+ * @param a The #fb_api.
+ * @param f The function to execute.
+ * @param ... The function arguments.
+ **/
+#define FB_API_FUNC(m, f, ...) \
+ G_STMT_START { \
+ if (G_LIKELY((m)->funcs.f != NULL)) { \
+ (m)->funcs.f(m, ##__VA_ARGS__, (m)->data); \
+ } \
+ } G_STMT_END
+
+
+/** The #GError codes of #fb_api. **/
+typedef enum fb_api_error fb_api_error_t;
+
+/** The structure for interacting with the Facebook API. **/
+typedef struct fb_api fb_api_t;
+
+/** The main structure for #fb_api callback functions. **/
+typedef struct fb_api_funcs fb_api_funcs_t;
+
+
+/**
+ * The #GError codes of #fb_api.
+ **/
+enum fb_api_error
+{
+ FB_API_ERROR_GENERAL /** General **/
+};
+
+/**
+ * The main structure for #fb_api callback functions.
+ **/
+struct fb_api_funcs
+{
+ /**
+ * The error function. This is called whenever an error occurs
+ * within the #fb_api.
+ *
+ * @param api The #fb_api.
+ * @param err The #GError.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*error) (fb_api_t *api, GError *err, gpointer data);
+
+ /**
+ * The auth function. This is called whenever authentication has
+ * been successfully completed. This is called as a result of
+ * #fb_api_auth().
+ *
+ * @param api The #fb_api.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*auth) (fb_api_t *api, gpointer data);
+};
+
+/**
+ * The structure for interacting with the Facebook API.
+ **/
+struct fb_api
+{
+ fb_api_funcs_t funcs; /** The #fb_api_funcs. **/
+ gpointer data; /** The user-defined data or NULL. **/
+
+ fb_http_t *http; /** The #fb_http. **/
+ GError *err; /** The #GError or NULL. **/
+
+ gchar *token; /** The session token. **/
+};
+
+
+#define FB_API_ERROR fb_api_error_quark()
+
+GQuark fb_api_error_quark(void);
+
+fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data);
+
+void fb_api_free(fb_api_t *api);
+
+void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...);
+
+void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass);
+
+#endif /* _FACEBOOK_API_H */
diff --git a/facebook/facebook-http.c b/facebook/facebook-http.c
new file mode 100644
index 0000000..c5554b2
--- /dev/null
+++ b/facebook/facebook-http.c
@@ -0,0 +1,764 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <bitlbee.h>
+#include <string.h>
+
+#include "facebook-http.h"
+#include "facebook-util.h"
+
+/**
+ * Gets the error domain for #fb_http.
+ *
+ * @return The #GQuark of the error domain.
+ **/
+GQuark fb_http_error_quark(void)
+{
+ static GQuark q;
+
+ if (G_UNLIKELY(q == 0))
+ q = g_quark_from_static_string("fb-http-error-quark");
+
+ return q;
+}
+
+/**
+ * Creates a new #fb_http. The returned #fb_http should be freed with
+ * #fb_http_free() when no longer needed.
+ *
+ * @param agent The HTTP agent.
+ *
+ * @return The #fb_http or NULL on error.
+ **/
+fb_http_t *fb_http_new(const gchar *agent)
+{
+ fb_http_t *http;
+
+ http = g_new0(fb_http_t, 1);
+
+ http->agent = g_strdup(agent);
+ http->reqs = g_hash_table_new(g_direct_hash, g_direct_equal);
+ http->cookies = g_hash_table_new_full(g_str_hash,
+ (GEqualFunc) fb_util_str_iequal,
+ g_free, g_free);
+ return http;
+}
+
+/**
+ * Frees all #fb_http_req inside a #fb_http.
+ *
+ * @param http The #fb_http.
+ **/
+void fb_http_free_reqs(fb_http_t *http)
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ if (G_UNLIKELY(http == NULL))
+ return;
+
+ g_hash_table_iter_init(&iter, http->reqs);
+
+ while (g_hash_table_iter_next(&iter, &key, NULL)) {
+ g_hash_table_iter_remove(&iter);
+ fb_http_req_free(key);
+ }
+}
+
+/**
+ * Frees all memory used by a #fb_http.
+ *
+ * @param http The #fb_http.
+ **/
+void fb_http_free(fb_http_t *http)
+{
+ if (G_UNLIKELY(http == NULL))
+ return;
+
+ fb_http_free_reqs(http);
+ g_hash_table_destroy(http->reqs);
+ g_hash_table_destroy(http->cookies);
+
+ g_free(http->agent);
+ g_free(http);
+}
+
+/**
+ * Inserts a #va_list into a #GHashTable.
+ *
+ * @param table The #GHashTable.
+ * @param pair The first #fb_http_pair.
+ * @param ap The #va_list.
+ **/
+static void fb_http_tree_ins(GHashTable *table, const fb_http_pair_t *pair,
+ va_list ap)
+{
+ const fb_http_pair_t *p;
+ gchar *key;
+ gchar *val;
+
+ for (p = pair; p != NULL; ) {
+ if (p->key == NULL)
+ continue;
+
+ key = g_strdup(p->key);
+ val = g_strdup(p->val);
+
+ g_hash_table_replace(table, key, val);
+ p = va_arg(ap, const fb_http_pair_t*);
+ }
+}
+
+/**
+ * Sets cookies from #fb_http_pair. If a cookie already exists, it is
+ * overwritten with the new value.
+ *
+ * @param http The #fb_http.
+ * @param pair The first #fb_http_pair.
+ * @param ... The additional #fb_http_pair.
+ **/
+void fb_http_cookies_set(fb_http_t *http, const fb_http_pair_t *pair, ...)
+{
+ va_list ap;
+
+ g_return_if_fail(http != NULL);
+
+ va_start(ap, pair);
+ fb_http_tree_ins(http->cookies, pair, ap);
+ va_end(ap);
+}
+
+/**
+ * Parses cookies from a #fb_http_req. If a cookie already exists, it
+ * is overwritten with the new value.
+ *
+ * @param http The #fb_http.
+ * @param req The #fb_http_req.
+ **/
+void fb_http_cookies_parse_req(fb_http_t *http, const fb_http_req_t *req)
+{
+ gchar **hdrs;
+ gchar **kv;
+ gchar *str;
+ gsize i;
+ gsize j;
+
+ g_return_if_fail(http != NULL);
+ g_return_if_fail(req != NULL);
+
+ if (req->request == NULL)
+ return;
+
+ hdrs = g_strsplit(req->request->reply_headers, "\r\n", 0);
+
+ for (i = 0; hdrs[i] != NULL; i++) {
+ if (g_ascii_strncasecmp(hdrs[i], "Set-Cookie", 10) != 0)
+ continue;
+
+ str = strchr(hdrs[i], ';');
+
+ if (str != NULL)
+ str[0] = 0;
+
+ str = strchr(hdrs[i], ':');
+
+ if (str == NULL)
+ continue;
+
+ str = g_strstrip(++str);
+ kv = g_strsplit(str, "=", 2);
+
+ for (j = 0; kv[j] != NULL; j++) {
+ str = fb_http_uri_unescape(kv[j]);
+ g_free(kv[j]);
+ kv[j] = str;
+ }
+
+ if (g_strv_length(kv) > 1)
+ fb_http_cookies_set(http, FB_HTTP_PAIR(kv[0], kv[1]), NULL);
+
+ g_strfreev(kv);
+ }
+
+ g_strfreev(hdrs);
+}
+
+/**
+ * Parses cookies from a string. If a cookie already exists, it is
+ * overwritten with the new value.
+ *
+ * @param http The #fb_http.
+ * @param data The string.
+ **/
+void fb_http_cookies_parse_str(fb_http_t *http, const gchar *data)
+{
+ gchar **ckis;
+ gchar **kv;
+ gchar *str;
+ gsize i;
+ gsize j;
+
+ g_return_if_fail(http != NULL);
+ g_return_if_fail(data != NULL);
+
+ ckis = g_strsplit(data, ";", 0);
+
+ for (i = 0; ckis[i] != NULL; i++) {
+ str = g_strstrip(ckis[i]);
+ kv = g_strsplit(str, "=", 2);
+
+ for (j = 0; kv[j] != NULL; j++) {
+ str = fb_http_uri_unescape(kv[j]);
+ g_free(kv[j]);
+ kv[j] = str;
+ }
+
+ if (g_strv_length(kv) > 1)
+ fb_http_cookies_set(http, FB_HTTP_PAIR(kv[0], kv[1]), NULL);
+
+ g_strfreev(kv);
+ }
+
+ g_strfreev(ckis);
+}
+
+/**
+ * Gets a string representation of the cookies of a #fb_http. The
+ * returned string should be freed with #g_free() when no longer
+ * needed.
+ *
+ * @param http The #fb_http.
+ *
+ * @return The string representation of the cookies.
+ **/
+gchar *fb_http_cookies_str(fb_http_t *http)
+{
+ GHashTableIter iter;
+ GString *gstr;
+ gchar *key;
+ gchar *val;
+ gchar *str;
+
+ g_return_val_if_fail(http != NULL, NULL);
+
+ gstr = g_string_sized_new(128);
+ g_hash_table_iter_init(&iter, http->cookies);
+
+ while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) {
+ if (val == NULL)
+ val = "";
+
+ key = fb_http_uri_escape(key);
+ val = fb_http_uri_escape(val);
+
+ str = (gstr->len > 0) ? "; " : "";
+ g_string_append_printf(gstr, "%s%s=%s", str, key, val);
+
+ g_free(key);
+ g_free(val);
+ }
+
+ str = g_strdup(gstr->str);
+ g_string_free(gstr, TRUE);
+
+ return str;
+}
+
+/**
+ * Creates a new #fb_http_req. The returned #fb_http_req should be
+ * freed with #fb_http_req_free() when no longer needed.
+ *
+ * @param http The #fb_http.
+ * @param host The hostname.
+ * @param port The port number.
+ * @param path The pathname.
+ * @param func The user callback function or NULL.
+ * @param data The user define data or NULL.
+ *
+ * @return The #fb_http_req or NULL on error.
+ **/
+fb_http_req_t *fb_http_req_new(fb_http_t *http, const gchar *host,
+ gint port, const gchar *path,
+ fb_http_func_t func, gpointer data)
+{
+ fb_http_req_t *req;
+
+ req = g_new0(fb_http_req_t, 1);
+
+ req->http = http;
+ req->host = g_strdup(host);
+ req->port = port;
+ req->path = g_strdup(path);
+ req->func = func;
+ req->data = data;
+
+ req->headers = g_hash_table_new_full(g_str_hash,
+ (GEqualFunc) fb_util_str_iequal,
+ g_free, g_free);
+ req->params = g_hash_table_new_full(g_str_hash,
+ (GEqualFunc) fb_util_str_iequal,
+ g_free, g_free);
+
+ fb_http_req_headers_set(req,
+ FB_HTTP_PAIR("User-Agent", http->agent),
+ FB_HTTP_PAIR("Host", host),
+ FB_HTTP_PAIR("Accept", "*/*"),
+ FB_HTTP_PAIR("Connection", "Close"),
+ NULL
+ );
+
+ return req;
+}
+
+/**
+ * Implemented #http_input_function for nulling the callback operation.
+ *
+ * @param request The #http_request.
+ **/
+static void fb_http_req_close_nuller(struct http_request *request)
+{
+
+}
+
+/**
+ * Closes the underlying #http_request.
+ *
+ * @param callback TRUE to execute the callback, otherwise FALSE.
+ *
+ * @param req The #fb_http_req.
+ **/
+static void fb_http_req_close(fb_http_req_t *req, gboolean callback)
+{
+ g_return_if_fail(req != NULL);
+
+ b_event_remove(req->toid);
+
+ if ((req->err == NULL) && (req->scode == 0)) {
+ g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_CLOSED,
+ "Request closed");
+ }
+
+ if (callback && (req->func != NULL))
+ req->func(req, req->data);
+
+ if (req->request != NULL) {
+ /* Prevent more than one call to request->func() */
+ req->request->func = fb_http_req_close_nuller;
+ req->request->data = NULL;
+
+ if (!(req->request->flags & FB_HTTP_CLIENT_FREED))
+ http_close(req->request);
+ }
+
+ req->status = NULL;
+ req->scode = 0;
+ req->header = NULL;
+ req->body = NULL;
+ req->body_size = 0;
+ req->toid = 0;
+ req->request = NULL;
+}
+
+/**
+ * Frees all memory used by a #fb_http_req.
+ *
+ * @param req The #fb_http_req.
+ **/
+void fb_http_req_free(fb_http_req_t *req)
+{
+ if (G_UNLIKELY(req == NULL))
+ return;
+
+ fb_http_req_close(req, TRUE);
+
+ if (req->err != NULL)
+ g_error_free(req->err);
+
+ g_hash_table_destroy(req->headers);
+ g_hash_table_destroy(req->params);
+
+ g_free(req->path);
+ g_free(req->host);
+ g_free(req);
+}
+
+#ifdef DEBUG_FACEBOOK
+static void fb_http_req_debug(fb_http_req_t *req, gboolean response,
+ const gchar *header, const gchar *body)
+{
+ const gchar *act;
+ const gchar *type;
+ const gchar *prot;
+ gchar *str;
+ gchar **ls;
+ guint i;
+
+ if (req->err != NULL)
+ str = g_strdup_printf(" (%s)", req->err->message);
+ else if (req->status != NULL)
+ str = g_strdup_printf(" (%s)", req->status);
+ else
+ str = g_strdup("");
+
+ act = response ? "Response" : "Request";
+ type = (req->flags & FB_HTTP_REQ_FLAG_POST) ? "POST" : "GET";
+ prot = (req->flags & FB_HTTP_REQ_FLAG_SSL) ? "https" : "http";
+
+ FB_UTIL_DEBUGLN("%s %s (%p): %s://%s:%d%s%s",
+ type, act, req, prot,
+ req->host, req->port,
+ req->path, str);
+ g_free(str);
+
+ if (req->rsc > 0)
+ FB_UTIL_DEBUGLN("Reattempt: #%u", req->rsc);
+
+ if ((header != NULL) && (strlen(header) > 0)) {
+ ls = g_strsplit(header, "\n", 0);
+
+ for (i = 0; ls[i] != NULL; i++)
+ FB_UTIL_DEBUGLN(" %s", ls[i]);
+
+ g_strfreev(ls);
+ } else {
+ FB_UTIL_DEBUGLN(" ** No header data **");
+ FB_UTIL_DEBUGLN("");
+ }
+
+ if ((body != NULL) && (strlen(body) > 0)) {
+ ls = g_strsplit(body, "\n", 0);
+
+ for (i = 0; ls[i] != NULL; i++)
+ FB_UTIL_DEBUGLN(" %s", ls[i]);
+
+ g_strfreev(ls);
+ } else {
+ FB_UTIL_DEBUGLN(" ** No body data **");
+ }
+}
+#endif /* DEBUG_FACEBOOK */
+
+/**
+ * Sets headers from #fb_http_pair. If a header already exists, it is
+ * overwritten with the new value.
+ *
+ * @param req The #fb_http_req.
+ * @param pair The first #fb_http_pair.
+ * @param ... The additional #fb_http_pair.
+ **/
+void fb_http_req_headers_set(fb_http_req_t *req, const fb_http_pair_t *pair,
+ ...)
+{
+ va_list ap;
+
+ g_return_if_fail(req != NULL);
+
+ va_start(ap, pair);
+ fb_http_tree_ins(req->headers, pair, ap);
+ va_end(ap);
+}
+
+/**
+ * Sets parameters from #fb_http_pair. If a parameter already exists,
+ * it is overwritten with the new value.
+ *
+ * @param req The #fb_http_req.
+ * @param pair The first #fb_http_pair.
+ * @param ... The additional #fb_http_pair.
+ **/
+void fb_http_req_params_set(fb_http_req_t *req, const fb_http_pair_t *pair,
+ ...)
+{
+ va_list ap;
+
+ g_return_if_fail(req != NULL);
+
+ va_start(ap, pair);
+ fb_http_tree_ins(req->params, pair, ap);
+ va_end(ap);
+}
+
+/**
+ * Implemented #b_event_handler for resending failed a #fb_http_req.
+ *
+ * @param data The user defined data, which is a #fb_http_req.
+ * @param fd The file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return FALSE to kill the timer.
+ **/
+static gboolean fb_http_req_done_error(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_http_req_t *req = data;
+
+ fb_http_req_send(req);
+ return FALSE;
+}
+
+/**
+ * Processes all #fb_http_req by resending, queuing, and freeing.
+ *
+ * @param req The #fb_http_req.
+ **/
+static void fb_http_req_done(fb_http_req_t *req)
+{
+#ifdef DEBUG_FACEBOOK
+ fb_http_req_debug(req, TRUE, req->header, req->body);
+#endif /* DEBUG_FACEBOOK */
+
+ if (req->err != NULL) {
+ if (req->rsc < FB_HTTP_RESEND_MAX) {
+ fb_http_req_close(req, FALSE);
+ g_error_free(req->err);
+ req->err = NULL;
+
+ req->toid = b_timeout_add(FB_HTTP_RESEND_TIMEOUT,
+ fb_http_req_done_error, req);
+ req->rsc++;
+ return;
+ }
+
+ g_prefix_error(&req->err, "HTTP: ");
+ }
+
+ g_hash_table_remove(req->http->reqs, req);
+ fb_http_req_free(req);
+}
+
+/**
+ * Implemented #http_input_function for all #fb_http_req.
+ *
+ * @param request The #http_request.
+ **/
+static void fb_http_req_cb(struct http_request *request)
+{
+ fb_http_req_t *req = request->data;
+
+ /* Shortcut request elements */
+ req->status = request->status_string;
+ req->scode = request->status_code;
+ req->header = request->reply_headers;
+ req->body = request->reply_body;
+ req->body_size = request->body_size;
+
+ switch (req->scode) {
+ case 200:
+ case 301:
+ case 302:
+ case 303:
+ case 307:
+ break;
+
+ default:
+ g_set_error(&req->err, FB_HTTP_ERROR, req->scode, "%s", req->status);
+ }
+
+ req->request->flags |= FB_HTTP_CLIENT_FREED;
+ fb_http_req_done(req);
+}
+
+/**
+ * Implemented #b_event_handler for handling a timed out #fb_http_req.
+ *
+ * @param data The user defined data, which is a #fb_http_req.
+ * @param fd The file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return FALSE to kill the timer.
+ **/
+static gboolean fb_http_req_send_timeout(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_http_req_t *req = data;
+
+ g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_TIMEOUT,
+ "Request timed out");
+
+ req->toid = 0;
+ fb_http_req_done(req);
+ return FALSE;
+}
+
+/**
+ * Assembles a #fb_http_req. The returned strings should be freed with
+ * #g_free() when no longer needed.
+ *
+ * @param req The #fb_http_req.
+ * @param hs The return location for the header string.
+ * @param ps The return location for the param string.
+ * @param fs The return location for the full string.
+ **/
+static void fb_http_req_asm(fb_http_req_t *req, gchar **hs, gchar **ps,
+ gchar **fs)
+{
+ GHashTableIter iter;
+ GString *hgs;
+ GString *pgs;
+ gchar *str;
+ gchar *key;
+ gchar *val;
+
+ g_hash_table_iter_init(&iter, req->params);
+ pgs = g_string_sized_new(128);
+
+ while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) {
+ if (val == NULL)
+ val = "";
+
+ key = fb_http_uri_escape(key);
+ val = fb_http_uri_escape(val);
+
+ str = (pgs->len > 0) ? "&" : "";
+ g_string_append_printf(pgs, "%s%s=%s", str, key, val);
+
+ g_free(key);
+ g_free(val);
+ }
+
+ if (g_hash_table_size(req->http->cookies) > 0) {
+ str = fb_http_cookies_str(req->http);
+ fb_http_req_headers_set(req, FB_HTTP_PAIR("Cookie", str), NULL);
+ g_free(str);
+ }
+
+ if (req->flags & FB_HTTP_REQ_FLAG_POST) {
+ str = g_strdup_printf("%" G_GSIZE_FORMAT, pgs->len);
+
+ fb_http_req_headers_set(req,
+ FB_HTTP_PAIR("Content-Type", "application/"
+ "x-www-form-urlencoded"),
+ FB_HTTP_PAIR("Content-Length", str),
+ NULL
+ );
+
+ g_free(str);
+ }
+
+ g_hash_table_iter_init(&iter, req->headers);
+ hgs = g_string_sized_new(128);
+
+ while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) {
+ if (val == NULL)
+ val = "";
+
+ g_string_append_printf(hgs, "%s: %s\r\n", key, val);
+ }
+
+ if (req->flags & FB_HTTP_REQ_FLAG_POST) {
+ *fs = g_strdup_printf("POST %s HTTP/1.1\r\n%s\r\n%s",
+ req->path, hgs->str, pgs->str);
+ } else {
+ *fs = g_strdup_printf("GET %s?%s HTTP/1.1\r\n%s\r\n",
+ req->path, pgs->str, hgs->str);
+ }
+
+ *hs = g_string_free(hgs, FALSE);
+ *ps = g_string_free(pgs, FALSE);
+}
+
+/**
+ * Sends a #fb_http_req.
+ *
+ * @param req The #fb_http_req.
+ **/
+void fb_http_req_send(fb_http_req_t *req)
+{
+ gchar *str;
+ gchar *hs;
+ gchar *ps;
+
+ g_return_if_fail(req != NULL);
+
+ fb_http_req_asm(req, &hs, &ps, &str);
+
+#ifdef DEBUG_FACEBOOK
+ fb_http_req_debug(req, FALSE, hs, ps);
+#endif /* DEBUG_FACEBOOK */
+
+ req->request = http_dorequest(req->host, req->port,
+ (req->flags & FB_HTTP_REQ_FLAG_SSL),
+ str, fb_http_req_cb, req);
+ g_hash_table_add(req->http->reqs, req);
+
+ g_free(hs);
+ g_free(ps);
+ g_free(str);
+
+ if (G_UNLIKELY(req->request == NULL)) {
+ g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_INIT,
+ "Failed to init request");
+ fb_http_req_done(req);
+ return;
+ }
+
+ /* Prevent automatic redirection */
+ req->request->redir_ttl = 0;
+
+ if (req->timeout > 0) {
+ req->toid = b_timeout_add(req->timeout, fb_http_req_send_timeout,
+ req);
+ }
+}
+
+/**
+ * Escapes the characters of a string to make it URL safe. The returned
+ * string should be freed with #g_free() when no longer needed.
+ *
+ * @param unescaped The string.
+ *
+ * @return The escaped string or NULL on error.
+ **/
+gchar *fb_http_uri_escape(const gchar *unescaped)
+{
+ gchar *ret;
+ gchar *str;
+
+ g_return_val_if_fail(unescaped != NULL, NULL);
+
+ str = g_strndup(unescaped, (strlen(unescaped) * 3) + 1);
+ http_encode(str);
+
+ ret = g_strdup(str);
+ g_free(str);
+
+ return ret;
+}
+
+/**
+ * Unescapes the characters of a string to make it a normal string. The
+ * returned string should be freed with #g_free() when no longer needed.
+ *
+ * @param escaped The string.
+ *
+ * @return The unescaped string or NULL on error.
+ **/
+gchar *fb_http_uri_unescape(const gchar *escaped)
+{
+ gchar *ret;
+ gchar *str;
+
+ g_return_val_if_fail(escaped != NULL, NULL);
+
+ str = g_strdup(escaped);
+ http_decode(str);
+
+ ret = g_strdup(str);
+ g_free(str);
+
+ return ret;
+}
diff --git a/facebook/facebook-http.h b/facebook/facebook-http.h
new file mode 100644
index 0000000..6e83338
--- /dev/null
+++ b/facebook/facebook-http.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file **/
+
+#ifndef _FACEBOOK_HTTP_H
+#define _FACEBOOK_HTTP_H
+
+#include <glib.h>
+#include <http_client.h>
+
+
+#define FB_HTTP_CLIENT_FREED (1 << 31)
+#define FB_HTTP_RESEND_MAX 3
+#define FB_HTTP_RESEND_TIMEOUT 2000
+
+
+/**
+ * Creates a #fb_http_pair in-line.
+ *
+ * @param k The key.
+ * @param v The value.
+ *
+ * @return The resulting fb_http_pair.
+ **/
+#define FB_HTTP_PAIR(k, v) ((fb_http_pair_t *) &((fb_http_pair_t) {k, v}))
+
+
+/** The #GError codes of #fb_http. **/
+typedef enum fb_http_error fb_http_error_t;
+
+/** The flags of #fb_http_req. **/
+typedef enum fb_http_req_flags fb_http_req_flags_t;
+
+/** The structure for managing #fb_http_req. **/
+typedef struct fb_http fb_http_t;
+
+/** The structure for key/value pairs of strings. **/
+typedef struct fb_http_pair fb_http_pair_t;
+
+/** The structure for a #fb_http request. **/
+typedef struct fb_http_req fb_http_req_t;
+
+
+/**
+ * The type of callback for #fb_http_req operations.
+ *
+ * @param req The #fb_http_req.
+ * @param data The user defined data or NULL.
+ **/
+typedef void (*fb_http_func_t) (fb_http_req_t *req, gpointer data);
+
+
+/**
+ * The #GError codes of #fb_http.
+ **/
+enum fb_http_error
+{
+ FB_HTTP_ERROR_CLOSED = 1, /** Closed **/
+ FB_HTTP_ERROR_INIT, /** Initializing **/
+ FB_HTTP_ERROR_TIMEOUT, /** Timeout **/
+};
+
+/**
+ * The flags of #fb_http_req.
+ **/
+enum fb_http_req_flags
+{
+ FB_HTTP_REQ_FLAG_GET = 1 << 0, /** Use the GET method **/
+ FB_HTTP_REQ_FLAG_POST = 1 << 1, /** Use the POST method **/
+ FB_HTTP_REQ_FLAG_SSL = 1 << 2 /** Use encryption via SSL **/
+};
+
+/**
+ * The structure for managing #fb_http_req.
+ **/
+struct fb_http
+{
+ gchar *agent; /** The agent. **/
+ GHashTable *cookies; /** The #GHashTable of cookies. **/
+ GHashTable *reqs; /** The #GHashTable of #fb_http_req. **/
+};
+
+/**
+ * The structure for key/value pairs of strings.
+ **/
+struct fb_http_pair
+{
+ const gchar *key; /** The key. **/
+ const gchar *val; /** The value. **/
+};
+
+/**
+ * he structure for a #fb_http request.
+ **/
+struct fb_http_req
+{
+ fb_http_t *http; /** The #fb_http. **/
+ fb_http_req_flags_t flags; /** The #fb_http_req_flags. **/
+
+ gchar *host; /** The hostname. **/
+ gint port; /** The port number. **/
+ gchar *path; /** The pathname. **/
+ gint timeout; /** The timeout. **/
+
+ GHashTable *headers; /** The #GHashTable of headers. **/
+ GHashTable *params; /** The #GHashTable of parameters. **/
+
+ fb_http_func_t func; /** The user callback function or NULL. **/
+ gpointer data; /** The user define data or NULL. **/
+
+ struct http_request *request; /** The underlying #http_request. **/
+
+ GError *err; /** The #GError or NULL. **/
+ gchar *status; /** Shortcut to request->status_string. **/
+ gint scode; /** Shortcut to request->status_code. **/
+ gchar *header; /** Shortcut to request->reply_headers. **/
+ gchar *body; /** Shortcut to request->reply_body. **/
+ gint body_size; /** Shortcut to request->body_size. **/
+
+ gint toid; /** The event ID for the timeout. **/
+ guint8 rsc; /** The resend count. **/
+};
+
+
+#define FB_HTTP_ERROR fb_http_error_quark()
+
+GQuark fb_http_error_quark(void);
+
+fb_http_t *fb_http_new(const gchar *agent);
+
+void fb_http_free_reqs(fb_http_t *http);
+
+void fb_http_free(fb_http_t *http);
+
+void fb_http_cookies_set(fb_http_t *http, const fb_http_pair_t *pair, ...)
+ G_GNUC_NULL_TERMINATED;
+
+void fb_http_cookies_parse_req(fb_http_t *http, const fb_http_req_t *req);
+
+void fb_http_cookies_parse_str(fb_http_t *http, const gchar *data);
+
+gchar *fb_http_cookies_str(fb_http_t *http);
+
+fb_http_req_t *fb_http_req_new(fb_http_t *http, const gchar *host,
+ gint port, const gchar *path,
+ fb_http_func_t func, gpointer data);
+
+void fb_http_req_free(fb_http_req_t *req);
+
+void fb_http_req_headers_set(fb_http_req_t *req, const fb_http_pair_t *pair,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void fb_http_req_params_set(fb_http_req_t *req, const fb_http_pair_t *pair,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void fb_http_req_send(fb_http_req_t *req);
+
+gchar *fb_http_uri_escape(const gchar *unescaped);
+
+gchar *fb_http_uri_unescape(const gchar *escaped);
+
+#endif /* _FACEBOOK_HTTP_H */
diff --git a/facebook/facebook-json.c b/facebook/facebook-json.c
new file mode 100644
index 0000000..6d26289
--- /dev/null
+++ b/facebook/facebook-json.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "facebook-json.h"
+
+/**
+ * Gets the error domain for the JSON parser.
+ *
+ * @return The #GQuark of the error domain.
+ **/
+GQuark fb_json_error_quark(void)
+{
+ static GQuark q;
+
+ if (G_UNLIKELY(q == 0))
+ q = g_quark_from_static_string("fb-json-error-quark");
+
+ return q;
+}
+
+/**
+ * Creates a new #json_value from JSON data. The returned #json_value
+ * should be freed with #json_value_free() when no longer needed.
+ *
+ * @param data The JSON data.
+ * @param length The length of the JSON data.
+ * @param err The return location for a GError or NULL.
+ *
+ * @return The #json_value or NULL on error.
+ **/
+json_value *fb_json_new(const gchar *data, gsize length, GError **err)
+{
+ json_value *json;
+ json_settings js;
+ gchar *estr;
+
+ memset(&js, 0, sizeof js);
+ estr = g_new0(gchar, json_error_max);
+ json = json_parse_ex(&js, data, length, estr);
+
+ if ((json != NULL) && (strlen(estr) < 1)) {
+ g_free(estr);
+ return json;
+ }
+
+ g_set_error(err, FB_JSON_ERROR, FB_JSON_ERROR_PARSER,
+ "Parser: %s", estr);
+
+ g_free(estr);
+ return NULL;
+}
+
+/**
+ * Gets the string representation of a #json_value. The returned string
+ * should be freed with #g_free() when no longer needed.
+ *
+ * @param json The #json_value.
+ *
+ * @return The resulting string, or NULL on error.
+ **/
+gchar *fb_json_valstr(const json_value *json)
+{
+ g_return_val_if_fail(json != NULL, NULL);
+
+ switch (json->type) {
+ case json_integer:
+ return g_strdup_printf("%" PRId64, json->u.integer);
+
+ case json_double:
+ return g_strdup_printf("%f", json->u.dbl);
+
+ case json_string:
+ return g_strdup(json->u.string.ptr);
+
+ case json_boolean:
+ return g_strdup(json->u.boolean ? "true" : "false");
+
+ case json_null:
+ return g_strdup("null");
+
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * Gets a #json_value by name from a parent #json_value.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param type The #json_type.
+ *
+ * @return The json_value if found, otherwise NULL.
+ **/
+json_value *fb_json_val(const json_value *json, const gchar *name,
+ json_type type)
+{
+ json_value *val;
+
+ if (!fb_json_val_chk(json, name, type, &val))
+ return NULL;
+
+ return val;
+}
+
+/**
+ * Gets a #json_value by name from a parent #json_value, and checks
+ * for its existence and type.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param type The #json_type.
+ * @param val The return location for the value.
+ *
+ * @return TRUE if the value was found, or FALSE on error.
+ **/
+gboolean fb_json_val_chk(const json_value *json, const gchar *name,
+ json_type type, json_value **val)
+{
+ g_return_val_if_fail(json != NULL, FALSE);
+ g_return_val_if_fail(name != NULL, FALSE);
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ *val = json_o_get(json, name);
+
+ if ((*val == NULL) || ((*val)->type != type)) {
+ *val = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Gets an array by name from a parent #json_value.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ *
+ * @return The #json_value if found, otherwise NULL.
+ **/
+json_value *fb_json_array(const json_value *json, const gchar *name)
+{
+ json_value *val;
+
+ if (!fb_json_array_chk(json, name, &val))
+ return NULL;
+
+ return val;
+}
+
+/**
+ * Gets an array by name from a parent #json_value, and checks for its
+ * existence and type.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param type The #json_type.
+ * @param val The return location for the value.
+ *
+ * @return TRUE if the value was found, or FALSE on error.
+ **/
+gboolean fb_json_array_chk(const json_value *json, const gchar *name,
+ json_value **val)
+{
+ return fb_json_val_chk(json, name, json_array, val);
+}
+
+/**
+ * Gets a boolean value by name from a parent #json_value.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ *
+ * @return The boolean value if found, otherwise FALSE.
+ **/
+gboolean fb_json_bool(const json_value *json, const gchar *name)
+{
+ gboolean val;
+
+ if (!fb_json_bool_chk(json, name, &val))
+ return FALSE;
+
+ return val;
+}
+
+/**
+ * Gets a boolean value by name from a parent #json_value, and checks
+ * for its existence and type.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param val The return location for the value.
+ *
+ * @return The boolean value if found, otherwise FALSE.
+ **/
+gboolean fb_json_bool_chk(const json_value *json, const gchar *name,
+ gboolean *val)
+{
+ json_value *jv;
+
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ if (!fb_json_val_chk(json, name, json_boolean, &jv)) {
+ *val = FALSE;
+ return FALSE;
+ }
+
+ *val = jv->u.boolean;
+ return TRUE;
+}
+
+/**
+ * Gets a integer value by name from a parent #json_value.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ *
+ * @return The integer value if found, otherwise 0.
+ **/
+gint64 fb_json_int(const json_value *json, const gchar *name)
+{
+ gint64 val;
+
+ if (!fb_json_int_chk(json, name, &val))
+ return 0;
+
+ return val;
+}
+
+/**
+ * Gets a integer value by name from a parent #json_value, and checks
+ * for its existence and type.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param val The return location for the value.
+ *
+ * @return TRUE if the value was found, or FALSE on error.
+ **/
+gboolean fb_json_int_chk(const json_value *json, const gchar *name,
+ gint64 *val)
+{
+ json_value *jv;
+
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ if (!fb_json_val_chk(json, name, json_integer, &jv)) {
+ *val = 0;
+ return FALSE;
+ }
+
+ *val = jv->u.integer;
+ return TRUE;
+}
+
+/**
+ * Gets a string value by name from a parent #json_value.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ *
+ * @return The string value if found, otherwise NULL.
+ **/
+const gchar *fb_json_str(const json_value *json, const gchar *name)
+{
+ const gchar *val;
+
+ if (!fb_json_str_chk(json, name, &val))
+ return NULL;
+
+ return val;
+}
+
+/**
+ * Gets a string value by name from a parent #json_value, and checks
+ * for its existence and type.
+ *
+ * @param json The #json_value.
+ * @param name The name.
+ * @param val The return location for the value.
+ *
+ * @return TRUE if the value was found, or FALSE on error.
+ **/
+gboolean fb_json_str_chk(const json_value *json, const gchar *name,
+ const gchar **val)
+{
+ json_value *jv;
+
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ if (!fb_json_val_chk(json, name, json_string, &jv)) {
+ *val = NULL;
+ return FALSE;
+ }
+
+ *val = jv->u.string.ptr;
+ return TRUE;
+}
diff --git a/facebook/facebook-json.h b/facebook/facebook-json.h
new file mode 100644
index 0000000..1720137
--- /dev/null
+++ b/facebook/facebook-json.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file **/
+
+#ifndef _FACEBOOK_JSON_H
+#define _FACEBOOK_JSON_H
+
+#include <glib.h>
+#include <json_util.h>
+
+
+/** The #GError codes of the JSON parser. **/
+typedef enum fb_json_error fb_json_error_t;
+
+
+/**
+ * The #GError codes of JSON parser.
+ **/
+enum fb_json_error
+{
+ FB_JSON_ERROR_PARSER
+};
+
+
+#define FB_JSON_ERROR fb_json_error_quark()
+
+GQuark fb_json_error_quark(void);
+
+json_value *fb_json_new(const gchar *data, gsize length, GError **err);
+
+gchar *fb_json_valstr(const json_value *json);
+
+json_value *fb_json_val(const json_value *json, const gchar *name,
+ json_type type);
+
+gboolean fb_json_val_chk(const json_value *json, const gchar *name,
+ json_type type, json_value **val);
+
+json_value *fb_json_array(const json_value *json, const gchar *name);
+
+gboolean fb_json_array_chk(const json_value *json, const gchar *name,
+ json_value **val);
+
+gboolean fb_json_bool(const json_value *json, const gchar *name);
+
+gboolean fb_json_bool_chk(const json_value *json, const gchar *name,
+ gboolean *val);
+
+gint64 fb_json_int(const json_value *json, const gchar *name);
+
+gboolean fb_json_int_chk(const json_value *json, const gchar *name,
+ gint64 *val);
+
+const gchar *fb_json_str(const json_value *json, const gchar *name);
+
+gboolean fb_json_str_chk(const json_value *json, const gchar *name,
+ const gchar **val);
+
+#endif /* _FACEBOOK_JSON_H */
diff --git a/facebook/facebook-util.c b/facebook/facebook-util.c
new file mode 100644
index 0000000..84cc581
--- /dev/null
+++ b/facebook/facebook-util.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "facebook-util.h"
+
+/**
+ * Determines the debugging state of the plugin.
+ *
+ * @return TRUE if debugging is enabled, otherwise FALSE.
+ **/
+#ifdef DEBUG_FACEBOOK
+gboolean fb_util_debugging(void)
+{
+ static gboolean debug = FALSE;
+ static gboolean setup = FALSE;
+
+ if (G_UNLIKELY(!setup)) {
+ debug = g_getenv("BITLBEE_DEBUG") ||
+ g_getenv("BITLBEE_DEBUG_FACEBOOK");
+ setup = TRUE;
+ }
+
+ return debug;
+}
+#endif /* DEBUG_FACEBOOK */
+
+/**
+ * Compare two strings case insensitively. This is useful for where
+ * the return value must be a boolean, such as with a #GEqualFunc.
+ *
+ * @param s1 The first string.
+ * @param s2 The second string.
+ *
+ * @return TRUE if the strings are equal, otherwise FALSE.
+ **/
+gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2)
+{
+ return g_ascii_strcasecmp(s1, s2) == 0;
+}
diff --git a/facebook/facebook-util.h b/facebook/facebook-util.h
new file mode 100644
index 0000000..3385a6b
--- /dev/null
+++ b/facebook/facebook-util.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file **/
+
+#ifndef _FACEBOOK_UTIL_H
+#define _FACEBOOK_UTIL_H
+
+#include <glib.h>
+
+/**
+ * Prints a debugging line to stdout.
+ *
+ * @param f The format string literal.
+ * @param ... The arguments for the format string.
+ **/
+#ifdef DEBUG_FACEBOOK
+#define FB_UTIL_DEBUGLN(f, ...) \
+ G_STMT_START { \
+ if (fb_util_debugging()) { \
+ g_print("[" PACKAGE_NAME "] " f "\n", ##__VA_ARGS__); \
+ } \
+ } G_STMT_END
+#else /* DEBUG_FACEBOOK */
+#define FB_UTIL_DEBUGLN(f, ...)
+#endif /* DEBUG_FACEBOOK */
+
+
+#ifdef DEBUG_FACEBOOK
+gboolean fb_util_debugging(void);
+#endif /* DEBUG_FACEBOOK */
+
+gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2);
+
+#endif /* _FACEBOOK_UTIL_H */
diff --git a/facebook/facebook.c b/facebook/facebook.c
index 2561674..f96b05b 100644
--- a/facebook/facebook.c
+++ b/facebook/facebook.c
@@ -16,6 +16,41 @@
*/
#include "facebook.h"
+#include "facebook-util.h"
+
+/**
+ * Implemented #fb_api_funcs->error().
+ *
+ * @param api The #fb_api.
+ * @param err The #GError.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_error(fb_api_t *api, GError *err, gpointer data)
+{
+ fb_data_t *fata = data;
+
+ FB_UTIL_DEBUGLN("Error: %s", err->message);
+ imcb_error(fata->ic, "%s", err->message);
+ imc_logout(fata->ic, TRUE);
+}
+
+/**
+ * Implemented #fb_api_funcs->auth().
+ *
+ * @param api The #fb_api.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_auth(fb_api_t *api, gpointer data)
+{
+ fb_data_t *fata = data;
+ account_t *acc = fata->ic->acc;
+
+ set_setstr(&acc->set, "token", api->token);
+ imcb_log(fata->ic, "Authentication finished");
+
+ account_off(acc->bee, acc);
+ account_on(acc->bee, acc);
+}
/**
* Creates a new #fb_data with an #account. The returned #fb_data
@@ -23,19 +58,27 @@
*
* @param acc The #account.
*
- * @return The #fb_data_t or NULL on error.
+ * @return The #fb_data or NULL on error.
**/
fb_data_t *fb_data_new(account_t *acc)
{
fb_data_t *fata;
+ static const fb_api_funcs_t funcs = {
+ .error = fb_cb_api_error,
+ .auth = fb_cb_api_auth
+ };
+
g_return_val_if_fail(acc != NULL, NULL);
fata = g_new0(fb_data_t, 1);
+ fata->api = fb_api_new(&funcs, fata);
fata->ic = imcb_new(acc);
fata->ic->proto_data = fata;
+ fata->api->token = g_strdup(set_getstr(&acc->set, "token"));
+
return fata;
}
@@ -49,9 +92,11 @@ void fb_data_free(fb_data_t *fata)
if (G_UNLIKELY(fata == NULL))
return;
+ fb_api_free(fata->api);
g_free(fata);
}
+
/**
* Implements #prpl->init(). This initializes an account.
*
@@ -59,7 +104,10 @@ void fb_data_free(fb_data_t *fata)
**/
static void fb_init(account_t *acc)
{
+ set_t *s;
+ s = set_add(&acc->set, "token", NULL, NULL, acc);
+ s->flags = SET_NULL_OK | SET_HIDDEN | SET_PASSWORD;
}
/**
@@ -69,7 +117,18 @@ static void fb_init(account_t *acc)
**/
static void fb_login(account_t *acc)
{
+ fb_data_t *fata;
+
+ fata = fb_data_new(acc);
+ imcb_log(fata->ic, "Connecting");
+
+ if (fata->api->token == NULL) {
+ imcb_log(fata->ic, "Requesting authentication token");
+ fb_api_auth(fata->api, acc->user, acc->pass);
+ return;
+ }
+ imcb_connected(fata->ic);
}
/**
@@ -79,7 +138,9 @@ static void fb_login(account_t *acc)
**/
static void fb_logout(struct im_connection *ic)
{
+ fb_data_t *fata = ic->proto_data;
+ fb_data_free(fata);
}
/**
@@ -216,7 +277,7 @@ static void fb_auth_deny(struct im_connection *ic, const char *who)
/**
* Implements #prpl->buddy_data_add(). This adds data to the buddy.
*
- * @param bu The #bee_user_t.
+ * @param bu The #bee_user.
**/
static void fb_buddy_data_add(struct bee_user *bu)
{
@@ -226,7 +287,7 @@ static void fb_buddy_data_add(struct bee_user *bu)
/**
* Implements #prpl->buddy_data_free(). This frees the buddy data.
*
- * @param bu The #bee_user_t.
+ * @param bu The #bee_user.
**/
static void fb_buddy_data_free(struct bee_user *bu)
{
diff --git a/facebook/facebook.h b/facebook/facebook.h
index 6d1b10c..d22c567 100644
--- a/facebook/facebook.h
+++ b/facebook/facebook.h
@@ -22,6 +22,7 @@
#include <bitlbee.h>
+#include "facebook-api.h"
/** The main structure for the plugin. **/
typedef struct fb_data fb_data_t;
@@ -33,6 +34,7 @@ typedef struct fb_data fb_data_t;
struct fb_data
{
struct im_connection *ic; /** The #im_connection. **/
+ fb_api_t *api; /** The #fb_api. **/
};