diff --git a/CHANGELOG.md b/CHANGELOG.md index 3795d4a..9d590a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Add support for hosting arbitrary files * Content type of file is set if provided when uploading file * Support removing entries +* Landerctl + * Replaced old Bash script with Libcurl-based application + * Supporting posting redirects, pastes & arbitrarily large files ## [0.1.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.1.0) diff --git a/landerctl/include/landerctl.h b/landerctl/include/landerctl.h index 7e01ed2..28a231a 100644 --- a/landerctl/include/landerctl.h +++ b/landerctl/include/landerctl.h @@ -32,11 +32,28 @@ typedef enum landerctl_mode { landerctl_mode_file, } landerctl_mode; -struct curl_slist *landerctl_set_common(const landerctl_cfg *cfg, CURL *curl, - landerctl_mode mode, bool secure, - const char *key); -void landerctl_post_short(CURL *curl, const char *url); -void landerctl_post_paste(CURL *curl, const char *path); -void landerctl_post_file(CURL *curl, const char *path); +typedef enum landerctl_err { + landerctl_err_ok = 0, + landerctl_err_not_found +} landerctl_err; + +typedef struct landerctl_ctx { + landerctl_cfg cfg; + landerctl_mode mode; + bool secure; + bool verbose; + const char *arg; + const char *key; + CURL *curl; + struct curl_slist *headers; + FILE *data_file; +} landerctl_ctx; + +const char *landerctl_err_msg(landerctl_err err); + +void landerctl_set_common(landerctl_ctx *ctx); +landerctl_err landerctl_post_short(landerctl_ctx *ctx); +landerctl_err landerctl_post_paste(landerctl_ctx *ctx); +landerctl_err landerctl_post_file(landerctl_ctx *ctx); #endif diff --git a/landerctl/src/main.c b/landerctl/src/main.c index a88d0cc..7805635 100644 --- a/landerctl/src/main.c +++ b/landerctl/src/main.c @@ -13,10 +13,10 @@ const char *default_cfg_path = ".landerrc"; const char *usage = "%s [-SPFsv] arg [key]\n"; int main(int argc, char **argv) { - landerctl_cfg cfg; + landerctl_ctx ctx = {0}; char *err_msg = NULL; - switch (landerctl_cfg_parse(&cfg, default_cfg_path)) { + switch (landerctl_cfg_parse(&ctx.cfg, default_cfg_path)) { case landerctl_cfg_err_ok: break; case landerctl_cfg_err_not_found: @@ -38,26 +38,23 @@ int main(int argc, char **argv) { opterr = 0; int c; - landerctl_mode mode = landerctl_mode_none; - bool secure = false; - bool verbose = false; while ((c = getopt(argc, argv, "SPFsv")) != -1) { switch (c) { case 'S': - mode = landerctl_mode_short; + ctx.mode = landerctl_mode_short; break; case 'P': - mode = landerctl_mode_paste; + ctx.mode = landerctl_mode_paste; break; case 'F': - mode = landerctl_mode_file; + ctx.mode = landerctl_mode_file; break; case 's': - secure = true; + ctx.secure = true; break; case 'v': - verbose = true; + ctx.verbose = true; break; case '?': printf(usage, argv[0]); @@ -65,7 +62,7 @@ int main(int argc, char **argv) { } } - if (mode == landerctl_mode_none) { + if (ctx.mode == landerctl_mode_none) { printf("No mode specified.\n\n"); printf(usage, argv[0]); exit(2); @@ -76,33 +73,49 @@ int main(int argc, char **argv) { exit(2); } - const char *arg = argv[optind]; - const char *key = argc - optind == 2 ? argv[optind + 1] : NULL; + ctx.arg = argv[optind]; + ctx.key = argc - optind == 2 ? argv[optind + 1] : NULL; curl_global_init(CURL_GLOBAL_ALL); - CURL *curl = curl_easy_init(); + ctx.curl = curl_easy_init(); - if (curl == NULL) { + if (ctx.curl == NULL) { exit(255); } - struct curl_slist *list = landerctl_set_common(&cfg, curl, mode, secure, key); + landerctl_set_common(&ctx); + landerctl_err res; - switch (mode) { + switch (ctx.mode) { case landerctl_mode_short: - landerctl_post_short(curl, arg); + res = landerctl_post_short(&ctx); break; + case landerctl_mode_paste: + res = landerctl_post_paste(&ctx); + break; + case landerctl_mode_file: + res = landerctl_post_file(&ctx); + break; + default: + return 7; } - if (verbose) { - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (res != landerctl_err_ok) { + printf("%s\n", landerctl_err_msg(res)); + exit(6); } + if (ctx.verbose) { + curl_easy_setopt(ctx.curl, CURLOPT_VERBOSE, 1L); + } + + curl_easy_setopt(ctx.curl, CURLOPT_HTTPHEADER, ctx.headers); + int exit_code = 0; - if (curl_easy_perform(curl) == CURLE_OK) { + if (curl_easy_perform(ctx.curl) == CURLE_OK) { long response_code; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + curl_easy_getinfo(ctx.curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code < 200 || response_code > 299) { fprintf(stderr, "HTTP status code %li\n", response_code); @@ -110,9 +123,9 @@ int main(int argc, char **argv) { } else { struct curl_header *location_header; - if (curl_easy_header(curl, "Location", 0, CURLH_HEADER, -1, + if (curl_easy_header(ctx.curl, "Location", 0, CURLH_HEADER, -1, &location_header) == CURLHE_OK) { - printf("%s%s\n", cfg.server_url, location_header->value); + printf("%s%s\n", ctx.cfg.server_url, location_header->value); } else { fprintf(stderr, "Server returned a 2xx without a Location header.\n"); exit_code = 5; @@ -123,8 +136,12 @@ int main(int argc, char **argv) { exit_code = 4; } - curl_easy_cleanup(curl); - curl_slist_free_all(list); + curl_easy_cleanup(ctx.curl); + curl_slist_free_all(ctx.headers); + + if (ctx.data_file != NULL) { + fclose(ctx.data_file); + } return exit_code; diff --git a/landerctl/src/post.c b/landerctl/src/post.c index c0dd653..76e5b04 100644 --- a/landerctl/src/post.c +++ b/landerctl/src/post.c @@ -1,20 +1,31 @@ -#include +#include #include +#include + +#include +#include #include "landerctl.h" -struct curl_slist *landerctl_set_common(const landerctl_cfg *cfg, CURL *curl, - landerctl_mode mode, bool secure, - const char *key) { - size_t url_len = strlen(cfg->server_url) + 4; +const char *landerctl_err_msg(landerctl_err err) { + switch (err) { + case landerctl_err_not_found: + return "File not found"; + default: + return ""; + } +} - if (key != NULL) { - url_len += strlen(key); +void landerctl_set_common(landerctl_ctx *ctx) { + size_t url_len = strlen(ctx->cfg.server_url) + 4; + + if (ctx->key != NULL) { + url_len += strlen(ctx->key); } char mode_char; - switch (mode) { + switch (ctx->mode) { case landerctl_mode_short: mode_char = 's'; break; @@ -26,35 +37,85 @@ struct curl_slist *landerctl_set_common(const landerctl_cfg *cfg, CURL *curl, break; // Shouldn't be able to happen default: - return NULL; + return; } char url[url_len + 1]; - if (key == NULL) { - sprintf(url, "%s/%c%s/", cfg->server_url, mode_char, secure ? "l" : ""); + if (ctx->key == NULL) { + sprintf(url, "%s/%c%s/", ctx->cfg.server_url, mode_char, + ctx->secure ? "l" : ""); } else { - sprintf(url, "%s/%c%s/%s", cfg->server_url, mode_char, secure ? "l" : "", - key); + sprintf(url, "%s/%c%s/%s", ctx->cfg.server_url, mode_char, + ctx->secure ? "l" : "", ctx->key); } - curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(ctx->curl, CURLOPT_URL, url); // Add API key header - char api_key_header[strlen(cfg->api_key) + 12]; - sprintf(api_key_header, "X-Api-Key: %s", cfg->api_key); + char api_key_header[strlen(ctx->cfg.api_key) + 12]; + sprintf(api_key_header, "X-Api-Key: %s", ctx->cfg.api_key); - struct curl_slist *list = NULL; - list = curl_slist_append(list, api_key_header); + ctx->headers = curl_slist_append(NULL, api_key_header); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - - curl_easy_setopt(curl, CURLOPT_USERAGENT, "landerctl/" LANDER_VERSION ""); - - return list; + curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, + "landerctl/" LANDER_VERSION ""); } -void landerctl_post_short(CURL *curl, const char *url) { - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(url)); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, url); +landerctl_err landerctl_post_short(landerctl_ctx *ctx) { + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDSIZE, strlen(ctx->arg)); + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, ctx->arg); + + return landerctl_err_ok; +} + +landerctl_err landerctl_post_paste(landerctl_ctx *ctx) { + ctx->data_file = fopen(ctx->arg, "rb"); + + if (ctx->data_file == NULL) { + return landerctl_err_not_found; + } + + struct stat sb; + stat(ctx->arg, &sb); + + curl_easy_setopt(ctx->curl, CURLOPT_POST, 1L); + curl_easy_setopt(ctx->curl, CURLOPT_READDATA, ctx->data_file); + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDSIZE, sb.st_size); + + return landerctl_err_ok; +} + +landerctl_err landerctl_post_file(landerctl_ctx *ctx) { + ctx->data_file = fopen(ctx->arg, "rb"); + + if (ctx->data_file == NULL) { + return landerctl_err_not_found; + } + + struct stat sb; + stat(ctx->arg, &sb); + + curl_easy_setopt(ctx->curl, CURLOPT_POST, 1L); + curl_easy_setopt(ctx->curl, CURLOPT_READDATA, ctx->data_file); + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDSIZE_LARGE, sb.st_size); + + magic_t cookie = magic_open(MAGIC_MIME_TYPE); + magic_load(cookie, NULL); + const char *mime_type = magic_file(cookie, ctx->arg); + + char content_type_header[strlen(mime_type) + 24]; + sprintf(content_type_header, "X-Lander-Content-Type: %s", mime_type); + + char s[strlen(ctx->arg) + 1]; + strcpy(s, ctx->arg); + const char *base_name = basename(s); + + char filename_header[strlen(base_name) + 20]; + sprintf(filename_header, "X-Lander-Filename: %s", base_name); + + ctx->headers = curl_slist_append(ctx->headers, content_type_header); + ctx->headers = curl_slist_append(ctx->headers, filename_header); + + return landerctl_err_ok; }