diff options
| -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. **/  }; | 
