From 13b20715bfff3981b9cf8e03658b9a68e7cae776 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 14 Nov 2023 20:34:01 +0100 Subject: [PATCH 1/3] fix(http_loop): correctly parse content-type --- src/http_loop/http_loop_steps.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/http_loop/http_loop_steps.c b/src/http_loop/http_loop_steps.c index c3ff36a..99c5cce 100644 --- a/src/http_loop/http_loop_steps.c +++ b/src/http_loop/http_loop_steps.c @@ -4,6 +4,18 @@ #include "http_loop.h" #include "lander.h" +// Just a naive pow implementation; might improve later +static uint64_t ipow(uint64_t base, uint64_t power) { + uint64_t res = 1; + + while (power > 0) { + res *= base; + power--; + } + + return res; +} + /* * Converts a string to a number, returning true if the string contained a valid * positive number. @@ -18,7 +30,7 @@ static bool string_to_num(size_t *res, const char *s, size_t len) { return false; } - *res += val * (int)pow(10, (len - 1) - i); + *res += (uint64_t)val * ipow(10, (len - 1) - i); } return true; From 49c4c782421cc35c54a6be5a6d362048fdf94334 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 15 Nov 2023 09:35:59 +0100 Subject: [PATCH 2/3] feat(landerctl): started custom cli tool; wrote config parser --- landerctl | 53 --------------- landerctl/.landerrc | 1 + landerctl/Makefile | 120 ++++++++++++++++++++++++++++++++++ landerctl/config.mk | 22 +++++++ landerctl/include/landerctl.h | 23 +++++++ landerctl/src/cfg_parse.c | 59 +++++++++++++++++ landerctl/src/main.c | 87 ++++++++++++++++++++++++ 7 files changed, 312 insertions(+), 53 deletions(-) delete mode 100755 landerctl create mode 100644 landerctl/.landerrc create mode 100644 landerctl/Makefile create mode 100644 landerctl/config.mk create mode 100644 landerctl/include/landerctl.h create mode 100644 landerctl/src/cfg_parse.c create mode 100644 landerctl/src/main.c diff --git a/landerctl b/landerctl deleted file mode 100755 index 28f586f..0000000 --- a/landerctl +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env sh - -API_KEY=test -URL=http://localhost:18080 - -if [[ "$2" != '-' ]]; then - filename="$2" - content_type="$(file --mime-type --brief $2)" -fi - - -if [ "$1" = g ]; then - exec curl -is "$URL/$2" | - sed -En 's/^[lL]ocation: (.*)/\1/p' - -elif [ "$1" = s ] || [ "$1" = sl ]; then - exec curl \ - --fail \ - -w "${URL}%header{location}" \ - -XPOST \ - -d "$2" \ - -H "X-Api-Key: $API_KEY" \ - "$URL/$1/$3" - -elif [ "$1" = p ] || [ "$1" = pl ]; then - exec curl \ - --fail \ - -w "${URL}%header{location}" \ - -XPOST \ - -H "X-Api-Key: $API_KEY" \ - -H "X-Lander-Filename: ${filename}" \ - --data-binary @"$2" \ - "$URL/$1/$3" - -elif [ "$1" = f ] || [ "$1" = fl ]; then - exec curl \ - --fail \ - -v \ - -w "${URL}%header{location}" \ - -XPOST \ - -H "X-Api-Key: $API_KEY" \ - -H "X-Lander-Content-Type: ${content_type}" \ - -H "X-Lander-Filename: ${filename}" \ - -T "$2" \ - "$URL/$1/$3" - -elif [ "$1" = d ]; then - exec curl \ - --fail \ - -XDELETE \ - -H "X-Api-Key: $API_KEY" \ - "$URL/$2" -fi diff --git a/landerctl/.landerrc b/landerctl/.landerrc new file mode 100644 index 0000000..db525c7 --- /dev/null +++ b/landerctl/.landerrc @@ -0,0 +1 @@ +api_key = test diff --git a/landerctl/Makefile b/landerctl/Makefile new file mode 100644 index 0000000..72b8239 --- /dev/null +++ b/landerctl/Makefile @@ -0,0 +1,120 @@ +# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great +# base for this Makefile + +-include config.mk + +export CFLAGS +export LDFLAGS + +BIN := $(BUILD_DIR)/$(BIN_FILENAME) + +SRCS != find '$(SRC_DIR)' -iname '*.c' + +SRCS_H != find include -iname '*.h' + +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) + +_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra -DLANDER_VERSION=\"$(VERSION)\" +_LDFLAGS := $(addprefix -L,$(LIB_DIRS)) $(addprefix -l,$(LIBS)) $(LDFLAGS) + +.PHONY: all +all: $(BIN) + + +# =====COMPILATION===== +# Utility used by the CI to lint +.PHONY: objs +objs: $(OBJS) + +$(BIN): $(OBJS) + $(CC) -o $@ $(OBJS) $(_LDFLAGS) + +$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -c $< -o $@ + +# =====TESTING===== +.PHONY: run +run: $(BIN) + LANDER_API_KEY=test \ + LANDER_DATA_DIR=data \ + '$(BUILD_DIR)/$(BIN_FILENAME)' + +.PHONY: valgrind +valgrind: $(BIN) + LANDER_API_KEY=test \ + LANDER_DATA_DIR=data \ + valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' + +.PHONY: test +test: $(TARGETS_TEST) + +.PHONY: test-mem +test-mem: $(TARGETS_MEM_TEST) + +.PHONY: $(TARGETS_TEST) +$(TARGETS_TEST): test-%: % + ./$^ + +.PHONY: $(TARGETS_MEM_TEST) +$(TARGETS_MEM_TEST): test-mem-%: % + valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ + +.PHONY: build-test +build-test: $(BINS_TEST) + +$(BINS_TEST): %: %.c.o + $(CC) \ + $^ -o $@ + +# Along with the include directory, each test includes $(TEST_DIR) (which +# contains the acutest.h header file), and the src directory of the module it's +# testing. This allows tests to access internal methods, which aren't publicly +# exposed. +$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -I$(TEST_DIR) \ + -I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \ + -c $< -o $@ + + +# =====MAINTENANCE===== +.PHONY: lint +lint: + clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + +.PHONY: fmt +fmt: + clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + +.PHONY: check +check: + mkdir -p $(BUILD_DIR)/cppcheck + cppcheck \ + $(addprefix -I,$(INC_DIRS)) \ + --cppcheck-build-dir=$(BUILD_DIR)/cppcheck \ + --project=compile_commands.json \ + --error-exitcode=1 \ + --enable=warning,style \ + --inline-suppr \ + --check-level=exhaustive \ + --quiet \ + -j$(shell nproc) + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +.PHONY: bear +bear: clean + bear -- make + bear --append -- make build-test + + +# Make make aware of the .d files +-include $(DEPS) diff --git a/landerctl/config.mk b/landerctl/config.mk new file mode 100644 index 0000000..611faaf --- /dev/null +++ b/landerctl/config.mk @@ -0,0 +1,22 @@ +VERSION := 0.2.0 + +BIN_FILENAME = landerctl + +BUILD_DIR = build +SRC_DIR = src +TEST_DIR = test + +INC_DIRS = include +LIBS = magic curl +LIB_DIRS = + +# -MMD: generate a .d file for every source file. This file can be imported by +# make and makes make aware that a header file has been changed, ensuring an +# object file is also recompiled if only a header is changed. +# -MP: generate a dummy target for every header file (according to the docs it +# prevents some errors when removing header files) +CFLAGS = -MMD -MP -g + +# When compiling release builds, these flags are better +# CLAGS = -O3 +# LDFLAGS = -flto diff --git a/landerctl/include/landerctl.h b/landerctl/include/landerctl.h new file mode 100644 index 0000000..59a6301 --- /dev/null +++ b/landerctl/include/landerctl.h @@ -0,0 +1,23 @@ +#ifndef LANDERCTL +#define LANDERCTL + +typedef struct landerctl_cfg { + const char *api_key; +} landerctl_cfg; + +typedef enum landerctl_cfg_err { + landerctl_cfg_err_ok = 0, + landerctl_cfg_err_not_found, + landerctl_cfg_err_invalid, + landerctl_cfg_err_incomplete, +} landerctl_cfg_err; + +/** + * Try to parse the required config arguments from the config file + * + * @param out config to write values to. Existing values are overwritten + * @param path path to config file + */ +landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path); + +#endif diff --git a/landerctl/src/cfg_parse.c b/landerctl/src/cfg_parse.c new file mode 100644 index 0000000..4a681dc --- /dev/null +++ b/landerctl/src/cfg_parse.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include "landerctl.h" + +static const char cfg_line_regex_expr[] = "^([^ ]+) *= *([^ ]+)$"; + +landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path) { + FILE *f = fopen(path, "r"); + + if (f == NULL) { + return landerctl_cfg_err_not_found; + } + + regex_t cfg_line_regex; + regcomp(&cfg_line_regex, cfg_line_regex_expr, REG_EXTENDED); + + // Accept lines of at most 256 lines + char line[256]; + landerctl_cfg_err res = landerctl_cfg_err_incomplete; + + while (fgets(line, sizeof(line), f) != NULL) { + // Last character might be a newline + size_t len = strlen(line); + + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + regmatch_t reg_groups[3]; + + if (regexec(&cfg_line_regex, line, 3, reg_groups, 0) != 0) { + res = landerctl_cfg_err_not_found; + + break; + } + + // api_key is currently the only value we parse + int key_len = reg_groups[1].rm_eo - reg_groups[1].rm_so; + + if ((strlen("api_key") == key_len) && + (strncmp("api_key", &line[reg_groups[1].rm_so], key_len) == 0)) { + int val_len = reg_groups[2].rm_eo - reg_groups[2].rm_so; + char *buf = malloc(val_len + 1); + strncpy(buf, &line[reg_groups[2].rm_so], val_len); + + out->api_key = buf; + + res = landerctl_cfg_err_ok; + break; + } + } + + fclose(f); + + return res; +} diff --git a/landerctl/src/main.c b/landerctl/src/main.c new file mode 100644 index 0000000..3dd0dae --- /dev/null +++ b/landerctl/src/main.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +#include +#include + +#include "landerctl.h" + +const char default_cfg_path[] = ".landerrc"; + +int main(int argc, char **argv) { + landerctl_cfg cfg; + char *err_msg = NULL; + + switch (landerctl_cfg_parse(&cfg, default_cfg_path)) { + case landerctl_cfg_err_ok: + break; + case landerctl_cfg_err_not_found: + err_msg = "Config file not found"; + break; + case landerctl_cfg_err_invalid: + err_msg = "Invalid config file"; + break; + case landerctl_cfg_err_incomplete: + err_msg = "Incomplete config file"; + break; + } + + if (err_msg != NULL) { + fprintf(stderr, "%s\n", err_msg); + exit(1); + } + + /* struct stat sb; */ + + /* stat(argv[1], &sb); */ + + /* printf("file size: %lu\n", sb.st_size); */ + + /* FILE *f = fopen(argv[1], "rb"); */ + + /* if (f == NULL) { */ + /* printf("Couldn't open file.\n"); */ + /* exit(1); */ + /* } */ + + /* curl_global_init(CURL_GLOBAL_ALL); */ + + /* CURL *curl = curl_easy_init(); */ + + /* if (curl == NULL) { */ + /* exit(1); */ + /* } */ + + /* curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:18080/f/"); */ + /* curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); */ + /* curl_easy_setopt(curl, CURLOPT_READDATA, f); */ + + /* curl_off_t file_size = sb.st_size; */ + /* /1* curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, file_size); *1/ */ + /* curl_easy_setopt(curl, CURLOPT_POST, 1L); */ + /* curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, file_size); */ + + /* magic_t cookie = magic_open(MAGIC_MIME_TYPE); */ + /* magic_load(cookie, NULL); */ + /* const char *mime_type = magic_file(cookie, argv[1]); */ + + /* char content_type_header[strlen(mime_type) + 24]; */ + /* sprintf(content_type_header, "X-Lander-Content-Type: %s", mime_type); */ + + /* char content_length_header[32]; */ + /* sprintf(content_length_header, "Content-Length: %lu", sb.st_size); */ + + /* struct curl_slist *list = NULL; */ + /* list = curl_slist_append(list, content_type_header); */ + /* list = curl_slist_append(list, content_length_header); */ + /* list = curl_slist_append(list, "X-Api-Key: test"); */ + + /* curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); */ + + /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); */ + /* curl_easy_perform(curl); */ + + /* curl_slist_free_all(list); */ +} From 810bfd2bc91ceed964fbe0f115289b2f047bace8 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 15 Nov 2023 14:50:02 +0100 Subject: [PATCH 3/3] feat(landerctl): support posting redirects --- landerctl/.landerrc | 1 + landerctl/include/landerctl.h | 19 +++++++ landerctl/src/cfg_parse.c | 46 ++++++++++++---- landerctl/src/main.c | 99 +++++++++++++++++++++++++++++++++-- landerctl/src/post.c | 60 +++++++++++++++++++++ 5 files changed, 212 insertions(+), 13 deletions(-) create mode 100644 landerctl/src/post.c diff --git a/landerctl/.landerrc b/landerctl/.landerrc index db525c7..964d3f2 100644 --- a/landerctl/.landerrc +++ b/landerctl/.landerrc @@ -1 +1,2 @@ api_key = test +server_url = http://localhost:18080 diff --git a/landerctl/include/landerctl.h b/landerctl/include/landerctl.h index 59a6301..7e01ed2 100644 --- a/landerctl/include/landerctl.h +++ b/landerctl/include/landerctl.h @@ -1,8 +1,13 @@ #ifndef LANDERCTL #define LANDERCTL +#include + +#include + typedef struct landerctl_cfg { const char *api_key; + const char *server_url; } landerctl_cfg; typedef enum landerctl_cfg_err { @@ -20,4 +25,18 @@ typedef enum landerctl_cfg_err { */ landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path); +typedef enum landerctl_mode { + landerctl_mode_none = 0, + landerctl_mode_short, + landerctl_mode_paste, + 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); + #endif diff --git a/landerctl/src/cfg_parse.c b/landerctl/src/cfg_parse.c index 4a681dc..032a04b 100644 --- a/landerctl/src/cfg_parse.c +++ b/landerctl/src/cfg_parse.c @@ -14,12 +14,27 @@ landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path) { return landerctl_cfg_err_not_found; } + struct { + const char *key; + const char **var; + } key_to_vars[] = { + {"api_key", &out->api_key}, + {"server_url", &out->server_url}, + }; + size_t key_to_vars_len = sizeof(key_to_vars) / sizeof(key_to_vars[0]); + + // We NULL everything beforehand so we can check if we have all needed + // variables + for (size_t i = 0; i < key_to_vars_len; i++) { + *key_to_vars[i].var = NULL; + } + regex_t cfg_line_regex; regcomp(&cfg_line_regex, cfg_line_regex_expr, REG_EXTENDED); // Accept lines of at most 256 lines char line[256]; - landerctl_cfg_err res = landerctl_cfg_err_incomplete; + landerctl_cfg_err res = landerctl_cfg_err_ok; while (fgets(line, sizeof(line), f) != NULL) { // Last character might be a newline @@ -38,18 +53,29 @@ landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path) { } // api_key is currently the only value we parse - int key_len = reg_groups[1].rm_eo - reg_groups[1].rm_so; + size_t key_len = reg_groups[1].rm_eo - reg_groups[1].rm_so; - if ((strlen("api_key") == key_len) && - (strncmp("api_key", &line[reg_groups[1].rm_so], key_len) == 0)) { - int val_len = reg_groups[2].rm_eo - reg_groups[2].rm_so; - char *buf = malloc(val_len + 1); - strncpy(buf, &line[reg_groups[2].rm_so], val_len); + for (size_t i = 0; i < key_to_vars_len; i++) { + if ((key_len == strlen(key_to_vars[i].key)) && + (strncmp(&line[reg_groups[1].rm_so], key_to_vars[i].key, key_len) == + 0)) { + int val_len = reg_groups[2].rm_eo - reg_groups[2].rm_so; + char *buf = malloc(val_len + 1); + strncpy(buf, &line[reg_groups[2].rm_so], val_len); + buf[val_len] = '\0'; - out->api_key = buf; + *key_to_vars[i].var = buf; + break; + } + } + } - res = landerctl_cfg_err_ok; - break; + if (res == landerctl_cfg_err_ok) { + for (size_t i = 0; i < key_to_vars_len; i++) { + if (*key_to_vars[i].var == NULL) { + res = landerctl_cfg_err_incomplete; + break; + } } } diff --git a/landerctl/src/main.c b/landerctl/src/main.c index 3dd0dae..a88d0cc 100644 --- a/landerctl/src/main.c +++ b/landerctl/src/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,7 +9,8 @@ #include "landerctl.h" -const char default_cfg_path[] = ".landerrc"; +const char *default_cfg_path = ".landerrc"; +const char *usage = "%s [-SPFsv] arg [key]\n"; int main(int argc, char **argv) { landerctl_cfg cfg; @@ -33,6 +35,99 @@ int main(int argc, char **argv) { exit(1); } + 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; + break; + case 'P': + mode = landerctl_mode_paste; + break; + case 'F': + mode = landerctl_mode_file; + break; + case 's': + secure = true; + break; + case 'v': + verbose = true; + break; + case '?': + printf(usage, argv[0]); + exit(2); + } + } + + if (mode == landerctl_mode_none) { + printf("No mode specified.\n\n"); + printf(usage, argv[0]); + exit(2); + } + + if (optind == argc || (argc - optind > 2)) { + printf(usage, argv[0]); + exit(2); + } + + const char *arg = argv[optind]; + const char *key = argc - optind == 2 ? argv[optind + 1] : NULL; + + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl = curl_easy_init(); + + if (curl == NULL) { + exit(255); + } + + struct curl_slist *list = landerctl_set_common(&cfg, curl, mode, secure, key); + + switch (mode) { + case landerctl_mode_short: + landerctl_post_short(curl, arg); + break; + } + + if (verbose) { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + } + + int exit_code = 0; + + if (curl_easy_perform(curl) == CURLE_OK) { + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + + if (response_code < 200 || response_code > 299) { + fprintf(stderr, "HTTP status code %li\n", response_code); + exit_code = 3; + } else { + struct curl_header *location_header; + + if (curl_easy_header(curl, "Location", 0, CURLH_HEADER, -1, + &location_header) == CURLHE_OK) { + printf("%s%s\n", cfg.server_url, location_header->value); + } else { + fprintf(stderr, "Server returned a 2xx without a Location header.\n"); + exit_code = 5; + } + } + } else { + fprintf(stderr, "Libcurl encountered an error.\n"); + exit_code = 4; + } + + curl_easy_cleanup(curl); + curl_slist_free_all(list); + + return exit_code; + /* struct stat sb; */ /* stat(argv[1], &sb); */ @@ -46,8 +141,6 @@ int main(int argc, char **argv) { /* exit(1); */ /* } */ - /* curl_global_init(CURL_GLOBAL_ALL); */ - /* CURL *curl = curl_easy_init(); */ /* if (curl == NULL) { */ diff --git a/landerctl/src/post.c b/landerctl/src/post.c new file mode 100644 index 0000000..c0dd653 --- /dev/null +++ b/landerctl/src/post.c @@ -0,0 +1,60 @@ +#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; + + if (key != NULL) { + url_len += strlen(key); + } + + char mode_char; + + switch (mode) { + case landerctl_mode_short: + mode_char = 's'; + break; + case landerctl_mode_paste: + mode_char = 'p'; + break; + case landerctl_mode_file: + mode_char = 'f'; + break; + // Shouldn't be able to happen + default: + return NULL; + } + + char url[url_len + 1]; + + if (key == NULL) { + sprintf(url, "%s/%c%s/", cfg->server_url, mode_char, secure ? "l" : ""); + } else { + sprintf(url, "%s/%c%s/%s", cfg->server_url, mode_char, secure ? "l" : "", + key); + } + + curl_easy_setopt(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); + + struct curl_slist *list = NULL; + list = curl_slist_append(list, api_key_header); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "landerctl/" LANDER_VERSION ""); + + return list; +} + +void landerctl_post_short(CURL *curl, const char *url) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(url)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, url); +}