diff options
author | nirav <nirav@teisuu.com> | 2019-03-28 09:10:52 +0530 |
---|---|---|
committer | nirav <nirav@teisuu.com> | 2019-03-28 09:10:52 +0530 |
commit | 477e1ba2977435ae7bb75c7dbd95cf28247f89bd (patch) | |
tree | ce1f9719a7d11bbd47e9adea26dd610bc087962e /libgs/src | |
parent | cc727e972f7fdc871ee1f42cf014151b67422bf0 (diff) | |
download | ap_client-master.tar.gz ap_client-master.zip |
Diffstat (limited to 'libgs/src')
-rw-r--r-- | libgs/src/account.c | 93 | ||||
-rw-r--r-- | libgs/src/account.h | 30 | ||||
-rw-r--r-- | libgs/src/app.c | 115 | ||||
-rw-r--r-- | libgs/src/app.h | 13 | ||||
-rw-r--r-- | libgs/src/auth.c | 109 | ||||
-rw-r--r-- | libgs/src/auth.h | 12 | ||||
-rw-r--r-- | libgs/src/client.c | 72 | ||||
-rw-r--r-- | libgs/src/client.h | 29 | ||||
-rw-r--r-- | libgs/src/config.c | 174 | ||||
-rw-r--r-- | libgs/src/config.h | 28 | ||||
-rw-r--r-- | libgs/src/gs.c | 13 | ||||
-rw-r--r-- | libgs/src/gs.h | 17 | ||||
-rw-r--r-- | libgs/src/http.c | 178 | ||||
-rw-r--r-- | libgs/src/http.h | 9 | ||||
-rw-r--r-- | libgs/src/instance_info.c | 38 | ||||
-rw-r--r-- | libgs/src/instance_info.h | 31 | ||||
-rw-r--r-- | libgs/src/log.c | 30 | ||||
-rw-r--r-- | libgs/src/log.h | 13 | ||||
-rw-r--r-- | libgs/src/status.c | 120 | ||||
-rw-r--r-- | libgs/src/status.h | 31 | ||||
-rw-r--r-- | libgs/src/string-util.c | 86 | ||||
-rw-r--r-- | libgs/src/string-util.h | 10 | ||||
-rw-r--r-- | libgs/src/timeline.c | 93 | ||||
-rw-r--r-- | libgs/src/timeline.h | 17 |
24 files changed, 1361 insertions, 0 deletions
diff --git a/libgs/src/account.c b/libgs/src/account.c new file mode 100644 index 0000000..ecf3c28 --- /dev/null +++ b/libgs/src/account.c @@ -0,0 +1,93 @@ +#define _POSIX_C_SOURCE 200809L +#include <err.h> +#include <string.h> +#include <jansson.h> +#include "account.h" +#include "log.h" + +struct gs_account *gs_account_from_json(const char *json_data) +{ + struct gs_account *a; + json_t *root; + json_error_t error; + + root = json_loads(json_data, 0, &error); + if (!root) { + gs_log(GS_WARNING, "gs_account_from_json", + "json parse error: line %d: %s", error.line, error.text); + return NULL; + } + + a = gs_account_from_json_t(root); + json_decref(root); + return a; +} + +struct gs_account *gs_account_from_json_t(const json_t *root) +{ + struct gs_account *a; + if (!root) { + gs_log(GS_WARNING, "gs_account_from_json_t", "json data is null"); + return NULL; + } + + if (!json_is_object(root)) { + gs_log(GS_WARNING, "gs_account_from_json_t", + "json root is not object"); + return NULL; + } + + a = calloc(1, sizeof(struct gs_account)); + if (!a) { + err(1, "gs_account_from_json_t"); + return NULL; + } + + json_t *id = json_object_get(root, "id"); + if (json_is_string(id)) { + a->id = strdup(json_string_value(id)); + } + + json_t *username = json_object_get(root, "username"); + if (json_is_string(username)) { + a->username = strdup(json_string_value(username)); + } + + json_t *acct = json_object_get(root, "acct"); + if (json_is_string(acct)) { + a->acct = strdup(json_string_value(acct)); + } + + json_t *display_name = json_object_get(root, "display_name"); + if (json_is_string(display_name)) { + a->display_name = strdup(json_string_value(display_name)); + } + + return a; +} + +void gs_account_free(struct gs_account *a) +{ + if (a->id) + free(a->id); + if (a->username) + free(a->username); + if (a->acct) + free(a->acct); + if (a->display_name) + free(a->display_name); + if (a->note) + free(a->note); + if (a->url) + free(a->url); + if (a->avatar) + free(a->avatar); + if (a->avatar_static) + free(a->avatar_static); + if (a->header) + free(a->header); + if (a->header_static) + free(a->header_static); + free(a); +} + diff --git a/libgs/src/account.h b/libgs/src/account.h new file mode 100644 index 0000000..c9d36d2 --- /dev/null +++ b/libgs/src/account.h @@ -0,0 +1,30 @@ +#ifndef __GS_ACCOUNT_H +#define __GS_ACCOUNT_H + +#include <stdbool.h> +#include <jansson.h> + +struct gs_account { + char *id; + char *username; + char *acct; + char *display_name; + bool locked; + unsigned int follower_count; + unsigned int following_count; + unsigned int statuses_count; + char *note; + char *url; + char *avatar; + char *avatar_static; + char *header; + char *header_static; + struct gs_account *moved; + bool bot; +}; + +struct gs_account *gs_account_from_json(const char *json_data); +struct gs_account *gs_account_from_json_t(const json_t *); +void gs_account_free(struct gs_account *); + +#endif diff --git a/libgs/src/app.c b/libgs/src/app.c new file mode 100644 index 0000000..b3f6e20 --- /dev/null +++ b/libgs/src/app.c @@ -0,0 +1,115 @@ +#define _POSIX_C_SOURCE 200809L +#include <err.h> +#include <jansson.h> +#include <string.h> +#include "app.h" +#include "http.h" +#include "log.h" + +#define CLIENT_NAME "ap_client" +#define REGISTER_URL "https://%s/api/v1/apps" + +static char *get_register_url(const char *domain) +{ + char *url; + size_t size; + + size = strlen(REGISTER_URL) + strlen(domain) - 1; + url = malloc(size); + if (!url) { + err(1, NULL); + return NULL; + } + + sprintf(url, REGISTER_URL, domain); + return url; +} + +static char *get_register_req() +{ + char *req; + json_t *json_root; + + json_root = json_pack_ex(NULL, 1, "{s:s, s:s, s:s}", "client_name", + CLIENT_NAME, "redirect_uris", "urn:ietf:wg:oauth:2.0:oob", "scopes", + "read write push"); + if (!json_root) { + return NULL; + } + + req = json_dumps(json_root, 0); + json_decref(json_root); + + return req; +} + +static struct gs_app *get_register_resp(const char *data) +{ + struct gs_app *resp; + json_t *json_root; + json_t *cid, *csec; + + json_root = json_loads(data, 0, NULL); + if (!json_root) { + return NULL; + } + + if (!json_is_object(json_root)) { + json_decref(json_root); + return NULL; + } + + cid = json_object_get(json_root, "client_id"); + csec = json_object_get(json_root, "client_secret"); + if (!json_is_string(cid) || !json_is_string(csec)) { + json_decref(json_root); + return NULL; + } + + resp = calloc(1, sizeof(struct gs_app)); + resp->client_id = strdup(json_string_value(cid)); + resp->client_secret = strdup(json_string_value(csec)); + + json_decref(json_root); + return resp; +} + +struct gs_app *gs_app_register(const char *domain) +{ + char *url, *req_data, *resp_data; + struct gs_app *resp; + + if (!domain || !*domain) { + gs_log(GS_WARNING, "gs_app_register", "invalid argument"); + return NULL; + } + + url = get_register_url(domain); + if (!url) { + gs_log(GS_WARNING, "gs_app_register", "invalid argument"); + return NULL; + } + + req_data = get_register_req(); + if (!req_data) { + free(url); + return NULL; + } + + resp_data = gs_http_post(url, NULL, req_data); + free(url); + free(req_data); + if (!resp_data) { + gs_log(GS_WARNING, "gs_app_register", "register request failed"); + return NULL; + } + + resp = get_register_resp(resp_data); + free(resp_data); + if (!resp) { + gs_log(GS_WARNING, "gs_app_register", "invalid response"); + return NULL; + } + + return resp; +} diff --git a/libgs/src/app.h b/libgs/src/app.h new file mode 100644 index 0000000..f7b6e8a --- /dev/null +++ b/libgs/src/app.h @@ -0,0 +1,13 @@ +#ifndef __GS_APP_H +#define __GS_APP_H + +struct gs_app { + char *client_id; + char *client_secret; +}; + +struct gs_app *gs_app_register(const char *domain); + +#endif + + diff --git a/libgs/src/auth.c b/libgs/src/auth.c new file mode 100644 index 0000000..88ab7b7 --- /dev/null +++ b/libgs/src/auth.c @@ -0,0 +1,109 @@ +#define _POSIX_C_SOURCE 200809L +#include <err.h> +#include <string.h> +#include <jansson.h> +#include "auth.h" +#include "client.h" +#include "log.h" +#include "string-util.h" + +struct login_response { + char *access_token; + char *scope; +}; + +static char *get_login_req(const char *client_id, const char *client_secret, + const char *email, const char *password) +{ + char *req; + json_t *json_root; + json_error_t error; + + json_root = json_pack_ex(&error, 1, "{s:s, s:s, s:s, s:s, s:s}", + "client_id", client_id, "client_secret", + client_secret, "grant_type", "password", "username", email, + "password", password); + if (!json_root) { + return NULL; + } + + req = json_dumps(json_root, 0); + json_decref(json_root); + + return req; +} + +static struct login_response *get_login_resp(char *data) +{ + struct login_response *resp; + json_t *json_root; + json_t *access_token, *scope; + + json_root = json_loads(data, 0, NULL); + if (!json_root) { + return NULL; + } + + if (!json_is_object(json_root)) { + json_decref(json_root); + return NULL; + } + + access_token = json_object_get(json_root, "access_token"); + scope = json_object_get(json_root, "scope"); + if (!json_is_string(access_token) || !json_is_string(scope)) { + json_decref(json_root); + return NULL; + } + + resp = calloc(1, sizeof(struct login_response)); + resp->access_token = strdup(json_string_value(access_token)); + resp->scope = strdup(json_string_value(scope)); + + json_decref(json_root); + return resp; +} + +int gs_auth_authenticate(GSClient *c, const char *email, const char *password) +{ + char *req_data, *resp_data; + struct login_response *resp; + + if (!email || !*email || !password || !*password) { + gs_log(GS_WARNING, "gs_auth_authenticate", "invalid argument"); + return -1; + } + + req_data = get_login_req(c->client_id, c->client_secret, email, password); + if (!req_data) { + gs_log(GS_WARNING, "gs_auth_authenticate", "invalid argument"); + return -1; + } + + resp_data = (char *)gs_client_do_api(c, 2, "/oauth/token", req_data); + free(req_data); + if (!resp_data) { + gs_log(GS_WARNING, "gs_auth_authenticate", "invalid response"); + return -1; + } + + resp = get_login_resp(resp_data); + free(resp_data); + if (!resp) { + gs_log(GS_WARNING, "gs_auth_authenticate", "invalid response"); + return -1; + } + + gs_client_set_token(c, resp->access_token); + + free(resp->access_token); + free(resp->scope); + free(resp); + + return 0; +} + +bool gs_auth_is_logged_in(GSClient *c) +{ + return c->access_token != NULL; +} diff --git a/libgs/src/auth.h b/libgs/src/auth.h new file mode 100644 index 0000000..431e0d7 --- /dev/null +++ b/libgs/src/auth.h @@ -0,0 +1,12 @@ +#ifndef __GS_AUTH_H +#define __GS_AUTH_H + +#include <stdbool.h> +#include "client.h" + +int gs_auth_authenticate(GSClient *c, const char *email, const char *password); +bool gs_auth_is_logged_in(GSClient *c); + + +#endif + diff --git a/libgs/src/client.c b/libgs/src/client.c new file mode 100644 index 0000000..7c0b62c --- /dev/null +++ b/libgs/src/client.c @@ -0,0 +1,72 @@ +#define _POSIX_C_SOURCE 200809L + +#include <err.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "client.h" +#include "http.h" +#include "log.h" +#include "string-util.h" + +GSClient *gs_client_new(const char *server, const char *client_id, + const char *client_secret) +{ + GSClient *c; + + c = calloc(1, sizeof(GSClient)); + if (!c) { + err(1, "gs_new_client"); + return NULL; + } + + c->server = strdup(server); + c->client_id = strdup(client_id); + c->client_secret = strdup(client_secret); + + return c; +} + +void *gs_client_do_api(GSClient *c, int method, const char *url, + const char *req) +{ + char *resp; + + size_t s = strlen(c->server) + strlen(url) + 1; + char full_url[s]; + strlcpy(full_url, c->server, s); + strlcat(full_url, url, s); + + switch (method) { + case 1: + resp = gs_http_get(full_url, c->access_token); + break; + case 2: + resp = gs_http_post(full_url, c->access_token, req); + break; + default: + gs_log(GS_WARNING, "gs_client_do_api", "invalid api method"); + return NULL; + } + + return resp; +} + +void gs_client_set_token(GSClient *c, const char *access_token) +{ + c->access_token = strdup(access_token); +} + +void gs_client_free(GSClient *c) +{ + if (!c) + return; + if (c->server) + free(c->server); + if (c->client_id) + free(c->client_id); + if (c->client_secret) + free(c->client_secret); + if (c->access_token) + free(c->access_token); +} diff --git a/libgs/src/client.h b/libgs/src/client.h new file mode 100644 index 0000000..d24b1ff --- /dev/null +++ b/libgs/src/client.h @@ -0,0 +1,29 @@ +#ifndef __GS_CLIENT_H +#define __GS_CLIENT_H + +typedef struct { + char *server; + char *client_id; + char *client_secret; + char *access_token; +} GSClient; + +/* + * Create new GSClient with server url, client_id and client_secret returned + * from gs_register call. + * It will add `http://` prefix to server if it doesn't exists. + */ +GSClient *gs_client_new(const char *server, const char *client_id, + const char *client_secret); + +/* Generic helper fuction to make http calls */ +void *gs_client_do_api(GSClient *c, int method, const char *url, + const char *req); + +/* Set user access_token */ +void gs_client_set_token(GSClient *c, const char *access_token); + +/* Free GSClient */ +void gs_client_free(GSClient *c); + +#endif diff --git a/libgs/src/config.c b/libgs/src/config.c new file mode 100644 index 0000000..167c9af --- /dev/null +++ b/libgs/src/config.c @@ -0,0 +1,174 @@ +#define _POSIX_C_SOURCE 200809L +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <err.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <libgen.h> +#include <jansson.h> +#include "string-util.h" +#include "config.h" +#include "log.h" + +#define CONFIG_PATH_SUFFIX "/.config/ap_client/config" + +struct config _config; +const struct config *config = (const struct config *)&_config; + +bool is_logged_in() +{ + if (!(config->access_token) || !*(config->access_token) || + !(config->instance_url) || !*(config->instance_url)) { + return false; + } + return true; +} + +const char *get_access_token() +{ + return (const char *)config->access_token; +} + +const char *get_instance_url() +{ + return (const char *)config->instance_url; +} + +bool is_registered() +{ + if (!(_config.client_id) || !*(_config.client_id) || + !(_config.client_secret) || !*(_config.client_secret) || + !(_config.instance_url) || !*(_config.instance_url)) { + return false; + } + return true; +} + +char *get_config_path() +{ + char *home = getenv("HOME"); + if (!home || !*home) { + return NULL; + } + char config_path[PATH_MAX]; + strlcpy(config_path, home, sizeof(config_path)); + strlcat(config_path, CONFIG_PATH_SUFFIX, sizeof(config_path)); + return strdup(config_path); +} + +void config_load() +{ + json_t *root; + json_error_t error; + + char *path = get_config_path(); + root = json_load_file(path, 0, &error); + if (!root) { + gs_log(GS_WARNING, "config_load", "json parse error: line %d: %s", + error.line, error.text); + free(path); + return; + } + free(path); + + if (!json_is_object(root)) { + gs_log(GS_WARNING, "config_load", "json root is not object"); + json_decref(root); + return; + } + + json_t *instance_url = json_object_get(root, "instance_url"); + if (json_is_string(instance_url)) { + _config.instance_url = strdup(json_string_value(instance_url)); + } + + json_t *client_id = json_object_get(root, "client_id"); + if (json_is_string(client_id)) { + _config.client_id = strdup(json_string_value(client_id)); + } + + json_t *client_secret = json_object_get(root, "client_secret"); + if (json_is_string(client_secret)) { + _config.client_secret = strdup(json_string_value(client_secret)); + } + + json_t *access_token = json_object_get(root, "access_token"); + if (json_is_string(access_token)) { + _config.access_token = strdup(json_string_value(access_token)); + } + + json_decref(root); +} + +int config_save() +{ + json_t *root; + json_error_t error; + + root = json_pack_ex(&error, 1, "{s:s, s:s, s:s, s:s}", "instance_url", + _config.instance_url, "client_id", _config.client_id, + "client_secret", _config.client_secret, "access_token", + _config.access_token); + + if (!root) { + gs_log(GS_WARNING, "register_app", "json pack error: line %d: %s", + error.line, error.text); + return -1; + } + + char *config_path = get_config_path(); + struct stat st; + char *dir = dirname(config_path); + if (stat(dir, &st)) { + if (mkdir(dir, S_IRWXU)) { + free(config_path); + gs_log(GS_WARNING, "config_save", "failed to create config dir"); + return -1; + } + } + free(config_path); + + if (json_dump_file(root, get_config_path(), 0)) { + gs_log(GS_WARNING, "config_save", "unable to save json"); + json_decref(root); + return -1; + } + + json_decref(root); + return 0; +} + +void config_set_client_id(const char *cid) +{ + _config.client_id = strdup(cid); +} + +void config_set_client_secret(const char *cs) +{ + _config.client_secret = strdup(cs); +} + +void config_set_instance_url(const char *iu) +{ + _config.instance_url = strdup(iu); +} + +void config_set_access_token(const char *a) +{ + _config.access_token = strdup(a); +} + +void config_cleanup() +{ + if (_config.client_id) + free(_config.client_id); + if (_config.client_secret) + free(_config.client_secret); + if (_config.instance_url) + free(_config.instance_url); + if (_config.access_token) + free(_config.access_token); +} diff --git a/libgs/src/config.h b/libgs/src/config.h new file mode 100644 index 0000000..dd1f38d --- /dev/null +++ b/libgs/src/config.h @@ -0,0 +1,28 @@ +#ifndef __GS_CONFIG_H +#define __GS_CONFIG_H + +#include <stdbool.h> + +struct gs_config { + char *instance_url; + char *client_id; + char *client_secret; + char *access_token; +}; + +extern const struct gs_config *config; + +bool gs_is_logged_in(); +const char* gs_get_access_token(); +const char* gs_get_instance_url(); +int gs_read_local_credentials(); +bool gs_is_registered(); +void gs_config_load(); +int gs_config_save(); +void gs_config_set_client_id(const char *cid); +void gs_config_set_client_secret(const char *cs); +void gs_config_set_instance_url(const char *iu); +void gs_config_set_access_token(const char *a); +void gs_config_cleanup(); + +#endif diff --git a/libgs/src/gs.c b/libgs/src/gs.c new file mode 100644 index 0000000..587de59 --- /dev/null +++ b/libgs/src/gs.c @@ -0,0 +1,13 @@ +#define _POSIX_C_SOURCE 200809L +#include "gs.h" +#include "http.h" + +int gs_init() +{ + return gs_http_init(); +} + +void gs_cleanup() +{ + gs_http_cleanup(); +} diff --git a/libgs/src/gs.h b/libgs/src/gs.h new file mode 100644 index 0000000..c69ca40 --- /dev/null +++ b/libgs/src/gs.h @@ -0,0 +1,17 @@ +#ifndef __GS_H +#define __GS_H + +#include "app.h" +#include "account.h" +#include "auth.h" +#include "client.h" +#include "http.h" +#include "log.h" +#include "string-util.h" +#include "status.h" +#include "timeline.h" + +int gs_init(); +void gs_cleanup(); + +#endif diff --git a/libgs/src/http.c b/libgs/src/http.c new file mode 100644 index 0000000..e1224ff --- /dev/null +++ b/libgs/src/http.c @@ -0,0 +1,178 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <err.h> +#include <curl/curl.h> +#include "string-util.h" +#include "auth.h" +#include "client.h" +#include "log.h" + +#define BUFFER_SIZE (256 * 1024) +#define AUTH_HEADER_STR_PREFIX "Authorization: Bearer " + +int gs_http_init() +{ + if (curl_global_init(CURL_GLOBAL_ALL)) { + gs_log(GS_WARNING, "http_init", "failed to init curl"); + return -1; + } + return 0; +} + +void gs_http_cleanup() +{ + curl_global_cleanup(); + return; +} + +struct write_result { + char *data; + int pos; +}; + +static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct write_result *result = (struct write_result *)stream; + + if (result->pos + size * nmemb >= BUFFER_SIZE - 1) { + gs_log(GS_WARNING, "write_response", "buffer too small"); + return 0; + } + + memcpy(result->data + result->pos, ptr, size * nmemb); + result->pos += size * nmemb; + + return size * nmemb; +} + +char *gs_http_get(const char *url, const char *token) +{ + CURL *curl = NULL; + CURLcode status; + struct curl_slist *headers = NULL; + char *data = NULL; + long code; + + curl = curl_easy_init(); + if (!curl) + goto error; + + data = malloc(BUFFER_SIZE); + if (!data) + goto error; + + struct write_result write_result = {.data = data, .pos = 0}; + + if (token) { + size_t s = strlen(AUTH_HEADER_STR_PREFIX) + strlen(token) + 1; + char auth_header_val[s]; + strlcpy(auth_header_val, AUTH_HEADER_STR_PREFIX, s); + strlcat(auth_header_val, token, s); + headers = curl_slist_append(headers, auth_header_val); + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result); + + status = curl_easy_perform(curl); + if (status) { + gs_log(GS_WARNING, "get_request", + "unable to request data from %s: %s", url, + curl_easy_strerror(status)); + goto error; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + if (code != 200) { + gs_log(GS_WARNING, "get_request", "server responded with code %ld", + code); + goto error; + } + + data[write_result.pos] = '\0'; + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + return data; + +error: + if (data) + free(data); + if (headers) + curl_slist_free_all(headers); + if (curl) + curl_easy_cleanup(curl); + return NULL; +} + +char *gs_http_post(const char *url, const char *token, const char *post_data) +{ + CURL *curl = NULL; + CURLcode status; + struct curl_slist *headers = NULL; + char *data = NULL; + long code; + + curl = curl_easy_init(); + if (!curl) + goto error; + + data = malloc(BUFFER_SIZE); + if (!data) + goto error; + + struct write_result write_result = {.data = data, .pos = 0}; + + if (token) { + size_t s = strlen(AUTH_HEADER_STR_PREFIX) + strlen(token) + 1; + char auth_header_val[s]; + strlcpy(auth_header_val, AUTH_HEADER_STR_PREFIX, s); + strlcat(auth_header_val, token, s); + headers = curl_slist_append(headers, auth_header_val); + } + if (post_data) { + char *content_type_header_val = "Content-Type: application/json"; + headers = curl_slist_append(headers, content_type_header_val); + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result); + if (post_data) + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); + + status = curl_easy_perform(curl); + if (status) { + gs_log(GS_WARNING, "post_request", + "unable to request data from %s: %s", + url, curl_easy_strerror(status)); + goto error; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + if (code != 200) { + gs_log(GS_WARNING, "post_request", "server responded with code %ld", + code); + goto error; + } + + data[write_result.pos] = '\0'; + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + return data; + +error: + if (data) + free(data); + if (headers) + curl_slist_free_all(headers); + if (curl) + curl_easy_cleanup(curl); + return NULL; +} diff --git a/libgs/src/http.h b/libgs/src/http.h new file mode 100644 index 0000000..a0d850d --- /dev/null +++ b/libgs/src/http.h @@ -0,0 +1,9 @@ +#ifndef __GS_HTTP_H +#define __GS_HTTP_H + +int gs_http_init(); +void gs_http_cleanup(); +char *gs_http_get(const char *url, const char *token); +char *gs_http_post(const char *url, const char *token, const char *data); + +#endif diff --git a/libgs/src/instance_info.c b/libgs/src/instance_info.c new file mode 100644 index 0000000..40f07c6 --- /dev/null +++ b/libgs/src/instance_info.c @@ -0,0 +1,38 @@ +#define _POSIX_C_SOURCE 200809L +#include <string.h> +#include <jansson.h> +#include "instance_info.h" +#include "log.h" + +struct instance_info *instance_info_from_json(char *json_data) +{ + struct instance_info *info; + info = malloc(sizeof(struct instance_info)); + + json_t *root; + json_error_t error; + + root = json_loads(json_data, 0, &error); + + if (!root) { + gs_log(GS_WARNING, "instance_info_from_json", "error: on line %d: %s", + error.line, error.text); + return NULL; + } + + if (!json_is_object(root)) { + gs_log(GS_WARNING, "instance_info_from_json", "root is not object"); + json_decref(root); + return NULL; + } + + json_t *title = json_object_get(root, "title"); + if (!json_is_string(title)) { + gs_log(GS_WARNING, "instance_info_from_json", "title is not string"); + return NULL; + } + info->title = strdup(json_string_value(title)); + + json_decref(root); + return info; +} diff --git a/libgs/src/instance_info.h b/libgs/src/instance_info.h new file mode 100644 index 0000000..cdb6e84 --- /dev/null +++ b/libgs/src/instance_info.h @@ -0,0 +1,31 @@ +#ifndef __GS_INSTANCE_INFO_H +#define __GS_INSTANCE_INFO_H + +#include <stdbool.h> + +struct instance_info_urls { + char *streaming_api; +}; + +struct instance_info_stats { + int user_count; + int status_count; + int domain_count; +}; + +struct instance_info { + char *url; + char *title; + char *description; + char *email; + char *version; + struct instance_info_urls urls; + struct instance_info_stats states; + char *thumbnail; + char **languages; + bool registrations; +}; + +struct instance_info *instance_info_from_json(char *json_data); + +#endif diff --git a/libgs/src/log.c b/libgs/src/log.c new file mode 100644 index 0000000..dad6d66 --- /dev/null +++ b/libgs/src/log.c @@ -0,0 +1,30 @@ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include "log.h" + +static const char *log_levels[] = { + "info", + "warning", + "error", + "fatal", +}; + +void gs_log(enum gs_log_level level, const char *namespace, + const char *format, ...) { + va_list args; + va_start(args, format); + + fprintf(stderr, "%s: %s: ", log_levels[level], namespace); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + + if (level == GS_ERROR) { + exit(EXIT_FAILURE); + } else if (level == GS_FATAL) { + abort(); + } + + va_end(args); +} + diff --git a/libgs/src/log.h b/libgs/src/log.h new file mode 100644 index 0000000..83e3784 --- /dev/null +++ b/libgs/src/log.h @@ -0,0 +1,13 @@ +#ifndef __GS_LOG_H +#define __GS_LOG_H + +enum gs_log_level { + GS_INFO = 0, + GS_WARNING, + GS_ERROR, + GS_FATAL +}; + +void gs_log(enum gs_log_level level, const char *namespace, const char *format, ...); + +#endif diff --git a/libgs/src/status.c b/libgs/src/status.c new file mode 100644 index 0000000..5bcdb74 --- /dev/null +++ b/libgs/src/status.c @@ -0,0 +1,120 @@ +#define _POSIX_C_SOURCE 200809L +#include <err.h> +#include <string.h> +#include <jansson.h> +#include "status.h" +#include "account.h" +#include "log.h" + +struct gs_status *gs_status_from_json(const char *json_data) +{ + struct gs_status *s; + json_t *root; + json_error_t error; + + root = json_loads(json_data, 0, &error); + if (!root) { + gs_log(GS_WARNING, "status_from_json", + "json parse error: line %d: %s", error.line, error.text); + return NULL; + } + + s = gs_status_from_json_t(root); + json_decref(root); + return s; +} + +struct gs_status *gs_status_from_json_t(const json_t *root) +{ + struct gs_status *s; + if (!root) { + gs_log(GS_WARNING, "gs_status_from_json_t", "json data is null"); + return NULL; + } + + if (!json_is_object(root)) { + gs_log(GS_WARNING, "gs_status_from_json_t", "json root is not object"); + return NULL; + } + + s = calloc(1, sizeof(struct gs_status)); + if (!s) { + err(1, "gs_status_from_json_t"); + return NULL; + } + + json_t *id = json_object_get(root, "id"); + if (json_is_string(id)) { + s->id = strdup(json_string_value(id)); + } + + json_t *uri = json_object_get(root, "uri"); + if (json_is_string(uri)) { + s->uri = strdup(json_string_value(uri)); + } + + json_t *url = json_object_get(root, "url"); + if (json_is_string(url)) { + s->url = strdup(json_string_value(url)); + } + + json_t *account = json_object_get(root, "account"); + if (json_is_object(account)) { + s->account = gs_account_from_json_t(account); + } + + json_t *in_reply_to_id = json_object_get(root, "in_reply_to_id"); + if (json_is_string(in_reply_to_id)) { + s->in_reply_to_id = strdup(json_string_value(in_reply_to_id)); + } + + json_t *in_reply_to_account_id = + json_object_get(root, "in_reply_to_account_id"); + if (json_is_string(in_reply_to_account_id)) { + s->in_reply_to_account_id = + strdup(json_string_value(in_reply_to_account_id)); + } + + json_t *content = json_object_get(root, "content"); + if (json_is_string(content)) { + s->content = strdup(json_string_value(content)); + } + + json_t *replies_count = json_object_get(root, "replies_count"); + if (json_is_integer(replies_count)) { + s->replies_count = json_integer_value(replies_count); + } + + json_t *reblogs_count = json_object_get(root, "reblogs_count"); + if (json_is_integer(reblogs_count)) { + s->reblogs_count = json_integer_value(reblogs_count); + } + + json_t *favourites_count = json_object_get(root, "favourites_count"); + if (json_is_integer(favourites_count)) { + s->favourites_count = json_integer_value(favourites_count); + } + + return s; +} + +void gs_status_free(struct gs_status *s) +{ + if (s->id) + free(s->id); + if (s->uri) + free(s->uri); + if (s->url) + free(s->url); + if (s->account) + gs_account_free(s->account); + if (s->in_reply_to_id) + free(s->in_reply_to_id); + if (s->in_reply_to_account_id) + free(s->in_reply_to_account_id); + if (s->reblog) + gs_status_free(s->reblog); + if (s->content) + free(s->content); + free(s); +} diff --git a/libgs/src/status.h b/libgs/src/status.h new file mode 100644 index 0000000..431a027 --- /dev/null +++ b/libgs/src/status.h @@ -0,0 +1,31 @@ +#ifndef __GS_STATUS_H +#define __GS_STATUS_H + +#include <stdbool.h> +#include <jansson.h> + +#include "account.h" + +struct gs_status { + char *id; + char *uri; + char *url; + struct gs_account *account; + char *in_reply_to_id; + char *in_reply_to_account_id; + struct gs_status *reblog; + char *content; + unsigned int replies_count; + unsigned int reblogs_count; + unsigned int favourites_count; + bool reblogged; + bool favourited; + bool muted; + bool sensitive; +}; + +struct gs_status *gs_status_from_json(const char *); +struct gs_status *gs_status_from_json_t(const json_t *); +void gs_status_free(struct gs_status *); + +#endif diff --git a/libgs/src/string-util.c b/libgs/src/string-util.c new file mode 100644 index 0000000..7f151ae --- /dev/null +++ b/libgs/src/string-util.c @@ -0,0 +1,86 @@ +/* $OpenBSD: src/lib/libc/string/strlcpy.c,v 1.11 2006/05/05 15:27:38 + * millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return (s - src - 1); /* count does not include NUL */ +} + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return (dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ +} diff --git a/libgs/src/string-util.h b/libgs/src/string-util.h new file mode 100644 index 0000000..6acee6d --- /dev/null +++ b/libgs/src/string-util.h @@ -0,0 +1,10 @@ +#ifndef __GS_STRING_UTIL_H +#define __GS_STRING_UTIL_H + +#include <sys/types.h> + +size_t strlcpy(char *dst, const char *src, size_t siz); +size_t strlcat(char *dst, const char *src, size_t siz); +char *str_replace(const char *str, const char *old, const char *new); + +#endif diff --git a/libgs/src/timeline.c b/libgs/src/timeline.c new file mode 100644 index 0000000..fa0a2e8 --- /dev/null +++ b/libgs/src/timeline.c @@ -0,0 +1,93 @@ +#define _POSIX_C_SOURCE 200809L +#include <err.h> +#include <string.h> +#include <pthread.h> +#include <jansson.h> +#include "timeline.h" +#include "auth.h" +#include "config.h" +#include "http.h" +#include "string-util.h" +#include "log.h" + +struct gs_timeline *gs_timeline_from_json(const char *json_data) +{ + struct gs_timeline *t; + json_t *root; + json_error_t error; + + root = json_loads(json_data, 0, &error); + if (!root) { + gs_log(GS_WARNING, "timeline_from_json", "json root it null"); + return NULL; + } + + if (!json_is_array(root)) { + gs_log(GS_WARNING, "timeline_from_json", "json root is not array"); + json_decref(root); + return NULL; + } + + t = calloc(1, sizeof(struct gs_timeline)); + if (!t) { + err(1, NULL); + json_decref(root); + return NULL; + } + + t->size = json_array_size(root); + t->statuses = calloc(t->size, sizeof(struct status *)); + if (!(t->statuses)) { + err(1, NULL); + gs_timeline_free(t); + json_decref(root); + return NULL; + } + + json_t *data; + for (size_t i = 0; i < t->size; i++) { + data = json_array_get(root, i); + if (!data) + goto error; + t->statuses[i] = gs_status_from_json_t(data); + if (!(t->statuses[i])) + goto error; + } + json_decref(root); + return t; + +error: + gs_timeline_free(t); + json_decref(root); + return NULL; +} + +void gs_timeline_free(struct gs_timeline *t) +{ + for (size_t i = 0; i < t->size; i++) { + if (t->statuses[i]) + gs_status_free(t->statuses[i]); + } + free(t->statuses); + free(t); +} + +struct gs_timeline *gs_timeline_get(GSClient *c, const char *max_id, + const char *since_id, const char *min_id, int limit) +{ + char *resp; + struct gs_timeline *t; + + resp = (char *)gs_client_do_api(c, 1, "/api/v1/timelines/home", NULL); + if (!resp) { + return NULL; + } + + t = gs_timeline_from_json(resp); + free(resp); + if (!t) { + return NULL; + } + + return t; +} diff --git a/libgs/src/timeline.h b/libgs/src/timeline.h new file mode 100644 index 0000000..73f46bb --- /dev/null +++ b/libgs/src/timeline.h @@ -0,0 +1,17 @@ +#ifndef __GS_TIMELINE_H +#define __GS_TIMELINE_H + +#include "status.h" +#include "client.h" + +struct gs_timeline { + struct gs_status **statuses; + size_t size; +}; + +struct gs_timeline *gs_timeline_get(GSClient *c, const char *max_id, + const char *since_id, const char *min_id, int limit); +struct gs_timeline *gs_timeline_from_json(const char *); +void gs_timeline_free(struct gs_timeline *); + +#endif |