diff options
author | jgeboski <jgeboski@gmail.com> | 2015-01-01 16:21:43 -0500 |
---|---|---|
committer | jgeboski <jgeboski@gmail.com> | 2015-01-03 14:40:48 -0500 |
commit | aaf01c2b00420fd7e28b80c1a3c419f074b2b542 (patch) | |
tree | c22ccc9533d6850d8c22e4c5d3c9d52e0bda41d7 | |
parent | c33c1ed33b5ab7eea97402a498e4c101f6d43202 (diff) | |
download | bitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.gz bitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.bz2 bitlbee-facebook-aaf01c2b00420fd7e28b80c1a3c419f074b2b542.tar.xz |
Implemented user authentication
-rw-r--r-- | facebook/Makefile.am | 10 | ||||
-rw-r--r-- | facebook/facebook-api.c | 294 | ||||
-rw-r--r-- | facebook/facebook-api.h | 123 | ||||
-rw-r--r-- | facebook/facebook-http.c | 764 | ||||
-rw-r--r-- | facebook/facebook-http.h | 177 | ||||
-rw-r--r-- | facebook/facebook-json.c | 316 | ||||
-rw-r--r-- | facebook/facebook-json.h | 74 | ||||
-rw-r--r-- | facebook/facebook-util.c | 55 | ||||
-rw-r--r-- | facebook/facebook-util.h | 49 | ||||
-rw-r--r-- | facebook/facebook.c | 67 | ||||
-rw-r--r-- | facebook/facebook.h | 2 |
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. **/ }; |