From f738e36e1c5d0478f0728b3143cc0e0e3c7498b6 Mon Sep 17 00:00:00 2001 From: condret Date: Wed, 7 Jun 2023 02:30:23 +0200 Subject: [PATCH] Add the fedi_post tool --- fedi_post.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 fedi_post.c diff --git a/fedi_post.c b/fedi_post.c new file mode 100644 index 0000000..6911e8f --- /dev/null +++ b/fedi_post.c @@ -0,0 +1,280 @@ +/* fedi_tools - GPL - Copyright 2023 - condret */ + +#include +#include +#include +#include +#include + +__attribute__((constructor)) void foo(void) { + curl_global_init (CURL_GLOBAL_ALL); +} + +__attribute__((destructor)) void bla(void) { + curl_global_cleanup (); +} + +typedef struct fedi_post_config_t { + char *instance; + char *creds; + char *post; + char **attachments; + ut32 attachment_count; +} FediPostCFG; + +// workaround for a bug in sdb +static Sdb *load_sdb (char *path) { + Sdb *ret = sdb_new0 (); + Sdb *on_disk = sdb_new0 (); + if (sdb_open (on_disk, path) < 0) { + sdb_free (ret); + sdb_free (on_disk); + return NULL; + } + sdb_copy (on_disk, ret); + sdb_close (on_disk); + sdb_free (on_disk); + return ret; +} + +static FediPostCFG *get_cfg (int argc, char *argv[]) { + FediPostCFG *fpcfg; + if (!argc || !(fpcfg = R_NEW0 (FediPostCFG))) { + return NULL; + } + char *config_path = r_file_home (".fedi_tools.sdb"); + if (!config_path) { + free (fpcfg); + return NULL; + } + Sdb *db = load_sdb (config_path); + if (!db) { + eprintf ("couldn't open %s :(\n", config_path); + sdb_free (db); + free (config_path); + free (fpcfg); + return NULL; + } + bool fail = false; + if (!sdb_exists (db, "instance")) { + eprintf ("'instance' is not set in %s :(\n", config_path); + fail = true; + } + puts (sdb_const_get (db, "instance", 0)); + if (!sdb_exists (db, "user")) { + eprintf ("'user' is not set in %s :(\n", config_path); + fail = true; + } + if (!sdb_exists (db, "password")) { + eprintf ("'password' is not set in %s :(\n", config_path); + fail = true; + } + R_FREE (config_path); + if (fail) { + sdb_free (db); + free (fpcfg); + return NULL; + } + fpcfg->instance = sdb_get (db, "instance", NULL); + fpcfg->creds = r_str_newf ("%s:%s", sdb_const_get (db, "user", NULL), + sdb_const_get (db, "password", NULL)); + sdb_free (db); + if (!fpcfg->instance || !fpcfg->creds) { + free (fpcfg->instance); + free (fpcfg->creds); + free (fpcfg); + return NULL; + } + int i; + for (i = 0; i < argc; i++) { + if (!fpcfg->attachment_count && !strcmp (argv[i], "-a")) { + if (!(argc - i) > 2) { + break; + } + if (r_num_is_valid_input (NULL, argv[++i])) { + int k = 0, j = fpcfg->attachment_count = R_MIN ( + r_num_get (NULL, argv[i]), argc - i); + if (!(fpcfg->attachments = R_NEWS0 (char *, j))) { + fail = true; + break; + } + for (; j;j--) { + if (r_file_exists (argv[++i]) && + !r_file_is_directory (argv[i])) { + fpcfg->attachments[k++] = argv[i]; + } else { + fpcfg->attachment_count--; + } + } + } + continue; + } + if (!strcmp (argv[i], "-i") && !fpcfg->post) { + fpcfg->post = (char *)(size_t)0x1; + continue; + } + if (!fpcfg->post && r_file_exists (argv[i]) && + !r_file_is_directory (argv[i])) { + fpcfg->post = argv[i]; + } + } + if (fail) { + free (fpcfg->instance); + free (fpcfg->creds); + free (fpcfg); + return NULL; + } + return fpcfg; +} + +static void cfg_free (FediPostCFG *cfg) { + if (cfg) { + free (cfg->instance); + free (cfg->creds); + free (cfg->attachments); + free (cfg); + } +} + +static char *cfg_get_post (FediPostCFG *cfg) { + if (((size_t)cfg->post) & 0x1) { + char *editor = r_sys_getenv ("EDITOR"); + if (!editor) { + return NULL; + } + char *temp = r_file_temp (NULL); + if (!temp) { + free (editor); + return NULL; + } + char *sysstr = r_str_newf ("%s %s", editor, temp); + if (!sysstr) { + free (temp); + free (editor); + return NULL; + } + system (sysstr); + free (sysstr); + free (editor); + char *post_data = r_file_slurp (temp, NULL); + r_file_rm (temp); + free (temp); + return post_data; + } + return cfg->post? r_file_slurp (cfg->post, NULL): NULL; +} + +static size_t write_data(void *ptr, size_t size, size_t nmemb, void *user) { + RIODesc *desc = (RIODesc *)user; + size_t dlen = size * nmemb; + while (dlen > 0x7fffffff) { + r_io_desc_write (desc, (const ut8 *)ptr, 0x7fffffff); + ptr += 0x7fffffff; + dlen -= 0x7fffffff; + } + r_io_desc_write (desc, (const ut8 *)ptr, (int)dlen); + return size * nmemb; +} + +static void upload_media (FediPostCFG *cfg, CURL *curl, char *media, RIODesc *tree_desc, RList *idlist) { + char *media_url = r_str_newf ("%s/api/v1/media", cfg->instance); + if (!media_url) { + return; + } + curl_easy_setopt (curl, CURLOPT_URL, media_url); + curl_mime *mime = curl_mime_init (curl); + curl_mimepart *part = curl_mime_addpart (mime); + curl_easy_setopt (curl, CURLOPT_MIMEPOST, mime); + curl_mime_name (part, "file"); + curl_mime_filedata (part, media); + curl_easy_setopt (curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_BASIC); + curl_easy_setopt (curl, CURLOPT_USERPWD, cfg->creds); + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, tree_desc); + curl_easy_perform (curl); + curl_mime_free (mime); + R_FREE (media_url); + ut64 rec_len = r_io_desc_seek (tree_desc, 0LL, R_IO_SEEK_CUR); + char *data = malloc (rec_len + 1); + r_io_desc_read_at (tree_desc, 0ULL, data, (int)rec_len); + data[rec_len] = '\x00'; + puts (data); + r_io_desc_seek (tree_desc, 0LL, R_IO_SEEK_SET); + r_io_use_fd (tree_desc->io, tree_desc->fd); + r_io_system (tree_desc->io, "reset"); // resets the treebuf desc; clears the internal rbtree + char *end, *id = strstr (data, "\"id\":\""); + if (id && (end = strchr (&id[6], '"'))) { + *end = '\x00'; + r_list_append (idlist, r_str_new (&id[6])); + } + free (data); +} + +int main (int argc, char *argv[]) { + FediPostCFG *cfg = get_cfg (argc - 1, &argv[1]); + if (!cfg) { + return -1; + } + char *statuses_url = r_str_newf ("%s/api/v1/statuses", cfg->instance); + if (!statuses_url) { + cfg_free (cfg); + return -1; + } + char *post_data = cfg_get_post (cfg); + RIO *io = r_io_new (); + if (!io) { + free (post_data); + free (statuses_url); + cfg_free (cfg); + return -1; + } + int tree_fd = r_io_fd_open (io, "treebuf://", R_PERM_RW, 0); + CURL *curl = curl_easy_init(); + if(!curl) { + r_io_free (io); + free (post_data); + free (statuses_url); + cfg_free (cfg); + return -1; + } + RList *ml = r_list_newf (free); + int i; + for (i = 0; i < cfg->attachment_count; i++) { + upload_media (cfg, curl, cfg->attachments[i], r_io_desc_get (io, tree_fd), ml); + } + curl_mime *mime = curl_mime_init (curl); + curl_mimepart *post = curl_mime_addpart (mime); + curl_mime_name (post, "status"); + curl_mime_data (post, post_data, CURL_ZERO_TERMINATED); + curl_mimepart *visibility = curl_mime_addpart (mime); + curl_mime_name (visibility, "visibility"); + curl_mime_data (visibility, "public", CURL_ZERO_TERMINATED); + RListIter *iter; + char *media_idstr; + r_list_foreach (ml, iter, media_idstr) { + curl_mimepart *media_id = curl_mime_addpart (mime); + curl_mime_name (media_id, "media_ids[]"); + curl_mime_data (media_id, media_idstr, CURL_ZERO_TERMINATED); + } + curl_easy_setopt (curl, CURLOPT_URL, statuses_url); + curl_easy_setopt (curl, CURLOPT_MIMEPOST, mime); + curl_easy_setopt (curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_BASIC); + curl_easy_setopt (curl, CURLOPT_USERPWD, cfg->creds); + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, r_io_desc_get (io, tree_fd)); + curl_easy_perform (curl); + ut64 rec_len = r_io_fd_seek (io, tree_fd, 0LL, R_IO_SEEK_CUR); + curl_mime_free (mime); + curl_easy_cleanup (curl); + r_list_free (ml); + R_FREE (post_data); + free (statuses_url); + cfg_free (cfg); + char *data = malloc (rec_len + 1); + r_io_fd_read_at (io, tree_fd, 0ULL, data, (int)rec_len); + data[rec_len] = '\x00'; + r_io_free (io); + puts (data); + free (data); + return 0; +}