From 1bec937076d8d40f94ff4603479f21bca2de6c61 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sun, 4 Aug 2019 11:03:38 +0200 Subject: Added http lib and basic restapi with rocket_close_im binary --- CMakeLists.txt | 22 +++++++- close_im/CMakeLists.txt | 8 +-- close_im/main.c | 69 +++++++++++++++++++++++++ close_im/main.cpp | 46 ----------------- common/config.c | 18 +++++++ common/config.h.in | 2 + common/http.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ common/http.h | 10 ++++ common/util.h | 4 +- restapi/CMakeLists.txt | 5 ++ restapi/auth.c | 80 +++++++++++++++++++++++++++++ restapi/auth.h | 11 ++++ restapi/im.c | 58 +++++++++++++++++++++ restapi/im.h | 7 +++ 14 files changed, 420 insertions(+), 53 deletions(-) create mode 100644 close_im/main.c delete mode 100644 close_im/main.cpp create mode 100644 common/http.c create mode 100644 common/http.h create mode 100644 restapi/CMakeLists.txt create mode 100644 restapi/auth.c create mode 100644 restapi/auth.h create mode 100644 restapi/im.c create mode 100644 restapi/im.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ea79a7f..c6b6b2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0) -project(rocket-client LANGUAGES C CXX VERSION 0.0.1) +project(rocket-client LANGUAGES C VERSION 0.0.1) set(CMAKE_VERBOSE_MAKEFILE FALSE) if(EXISTS "${CMAKE_SOURCE_DIR}/.git") @@ -13,7 +13,8 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -set(CMAKE_C_FLAGS "-Wall -Werror -Wextra -pedantic -D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED") +SET (CMAKE_C_COMPILER "clang") +set(CMAKE_C_FLAGS "-Wall -Werror -Wextra -pedantic -D_XOPEN_SOURCE=10000 -D_XOPEN_SOURCE_EXTENDED") set(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb -pg -fsanitize=address") set(CMAKE_C_FLAGS_RELEASE "-O2") set(CMAKE_C_FLAGS_MinSizeRel "-Os") @@ -26,4 +27,21 @@ configure_file("common/config.h.in" "common/config.h") include_directories("${CMAKE_CURRENT_BINARY_DIR}") include_directories("${CMAKE_CURRENT_SOURCE_DIR}") +### cjson library ##### +include(ExternalProject) +list(APPEND CMAKE_ARGS "-DBUILD_SHARED_LIBS=OFF") +list(APPEND CMAKE_ARGS "-DENABLE_CUSTOM_COMPILER_FLAGS=OFF") +list(APPEND CMAKE_ARGS "-DENABLE_CJSON_TEST=OFF") +ExternalProject_Add(cjson + PREFIX ${CMAKE_BINARY_DIR}/cjson + GIT_REPOSITORY https://github.com/DaveGamble/cJSON + CMAKE_ARGS "${CMAKE_ARGS}" + STEP_TARGETS build + EXCLUDE_FROM_ALL TRUE + ) +include_directories(${CMAKE_BINARY_DIR}/cjson/src) + +### Project subdirectories ##### +add_subdirectory(close_im) add_subdirectory(common) +add_subdirectory(restapi) diff --git a/close_im/CMakeLists.txt b/close_im/CMakeLists.txt index fd5ce8c..aec4090 100644 --- a/close_im/CMakeLists.txt +++ b/close_im/CMakeLists.txt @@ -1,7 +1,9 @@ -file(GLOB_RECURSE SOURCES *.cpp) +file(GLOB_RECURSE SOURCES *.c) ADD_EXECUTABLE(rocket_close_im ${SOURCES}) -target_link_libraries(rocket_close_im common rest) -target_link_libraries(rocket_close_im curl) +target_link_libraries(rocket_close_im common restapi) +target_link_libraries(rocket_close_im config curl) +add_dependencies(rocket_close_im cjson-build) +target_link_libraries(rocket_close_im ${CMAKE_CURRENT_BINARY_DIR}/../cjson/src/cjson-build/libcjson.a) install(TARGETS rocket_close_im DESTINATION bin) diff --git a/close_im/main.c b/close_im/main.c new file mode 100644 index 0000000..b770f3d --- /dev/null +++ b/close_im/main.c @@ -0,0 +1,69 @@ +#include "common/util.h" +#include +#include +#include +#include + +#include "common/config.h" +#include "restapi/auth.h" +#include "restapi/im.h" + +int main(void) +{ + if (config_load(CONFIG_PATH) != 0) { + return 1; + } + + char* login = NULL; + size_t len = 0; + printf("Login: "); + ssize_t read = getline(&login, &len, stdin); + if (read > 1) login[read-1] = 0; + + struct termios oflags, nflags; + tcgetattr(fileno(stdin), &oflags); + nflags = oflags; + nflags.c_lflag &= ~ECHO; + nflags.c_lflag |= ECHONL; + + if (tcsetattr(fileno(stdin), TCSADRAIN, &nflags) != 0) { + perror("tcsetattr"); + return -1; + } + + char* password = NULL; + printf("Password: "); + read = getline(&password, &len, stdin); + if (read > 1) password[read-1] = 0; + + if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) { + perror("tcsetattr"); + return -1; + } + + if (restapi_login(login, password) == 0) { + while(1) { + char* buff = NULL; + size_t len2; + printf("IM to close: "); + ssize_t entry = getline(&buff, &len2, stdin); + if (entry > 1) { + buff[entry-1] = 0; + } else { + free(buff); + break; + } + restapi_im_close(buff); + free(buff); + } + } else { + printf("Couldn't init rest api.\n"); + } + + restapi_logout(); + config_clean(); + free(login); + free(password); + + return 0; +} diff --git a/close_im/main.cpp b/close_im/main.cpp deleted file mode 100644 index c412d8b..0000000 --- a/close_im/main.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -#include "common/config.h" -#include "rest/rest.hpp" -#include "common/util.h" - -int main(void) -{ - char* login = NULL; - size_t len = 0; - printf("Login: "); - ssize_t read = getline(&login, &len, stdin); - if (read > 1) login[read-1] = 0; - - char* password = getpass("Password: "); - - rest_init(WEB_URL); - - if (rest_login(login, password)) { - while(1) { - char* buff = NULL; - size_t len2; - printf("IM to close: "); - ssize_t entry = getline(&buff, &len2, stdin); - if (entry > 1) { - buff[entry-1] = 0; - } else { - free(buff); - break; - } - rest_im_close(buff); - free(buff); - } - } else { - printf("Couldn't init rest api.\n"); - } - - rest_logout(); - rest_clear(); - - free(login); - - return 0; -} diff --git a/common/config.c b/common/config.c index 3320d7c..ff55595 100644 --- a/common/config.c +++ b/common/config.c @@ -48,6 +48,24 @@ const char * config_get_wss_url(void) return url; } +const char * config_get_login(void) +{ + const char * login; + if (config_lookup_string(config, "login", &login) != CONFIG_TRUE) { + return NULL; + } + return login; +} + +const char * config_get_password(void) +{ + const char * password; + if (config_lookup_string(config, "password", &password) != CONFIG_TRUE) { + return NULL; + } + return password; +} + void config_clean(void) { if (config != NULL) { diff --git a/common/config.h.in b/common/config.h.in index 9f27b70..4e90ba2 100644 --- a/common/config.h.in +++ b/common/config.h.in @@ -12,6 +12,8 @@ char config_load(const char *config_file); const char * config_get_web_url(void); const char * config_get_wss_url(void); +const char * config_get_login(void); +const char * config_get_password(void); void config_clean(void); #endif diff --git a/common/http.c b/common/http.c new file mode 100644 index 0000000..275a18f --- /dev/null +++ b/common/http.c @@ -0,0 +1,133 @@ +#include +#include +#include + +#include "config.h" +#include "http.h" +#include "util.h" + +// Curl internal handling +CURL* curl = NULL; +struct curl_slist *headers = NULL; + +// Curl buffer handling +char* buffer = NULL; +size_t buffer_len = 0; +size_t buffer_fill = 0; + +char initialized = 0; + +#define HEADER_SEP ": " + +static size_t writeCallback(char* buf, size_t size, size_t nmemb, void* userp); +const char * http_perform(const char* path, const char* postfields); + +void http_add_header(const char* name, const char* value) +{ + if (!initialized) { + fprintf(stderr, "BUG: http_init wasn't called!\n"); + return; + } + + size_t name_len = strlen(name); + size_t sep_len = strlen(HEADER_SEP); + size_t value_len = strlen(value); + char* header = malloc(name_len + sep_len + value_len + 1); + strcpy(header, name); + strcpy(header + name_len, HEADER_SEP); + strcpy(header + name_len + sep_len, value); + headers = curl_slist_append(headers, header); + free(header); +} + +void http_clean(void) +{ + if (!initialized) + return; + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + curl = NULL; + curl_global_cleanup(); + free(buffer); +} + +const char * http_get(const char* path) +{ + curl_easy_setopt(curl, CURLOPT_POST, 0); + return http_perform(path, NULL); +} + +void http_init(void) +{ + if (initialized) { + fprintf(stderr, "BUG: http_init already called!\n"); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + initialized = 1; +} + +const char * http_perform(const char* path, const char* postfields) +{ + if (!initialized) { + fprintf(stderr, "BUG: http_init wasn't called!\n"); + return NULL; + } + + if (buffer != NULL) { + memset(buffer, 0, buffer_fill); + buffer_fill = 0; + } + + curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCallback); + + const char * weburl = config_get_web_url(); + size_t weburl_len = strlen(weburl); + size_t path_len = strlen(path); + char * url = malloc(weburl_len + path_len + 1); + strcpy(url, weburl); + strcpy(url + weburl_len, path); + curl_easy_setopt(curl, CURLOPT_URL, url); + free(url); + if (postfields != NULL) + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); + if (headers != NULL) + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + CURLcode res = curl_easy_perform(curl); + if(res != CURLE_OK) { + fprintf(stderr, "HTTP get to %s failed: %s\n", url, curl_easy_strerror(res)); + return NULL; + } + + return buffer; +} + +const char * http_post(const char* path, const char* postfields) +{ + if (postfields == NULL) + curl_easy_setopt(curl, CURLOPT_POST, 1); + return http_perform(path, postfields); +} + +static size_t writeCallback(char* buf, size_t size, size_t nmemb, UNUSED void* userp) +{ //callback must have this declaration + //buf is a pointer to the data that curl has for us + //size*nmemb is the size of the buffer + + register size_t sn = size * nmemb; + if (buffer == NULL) { + buffer_len = sn; + buffer = malloc (buffer_len + 1); + } else if (buffer_fill + sn >= buffer_len) { + buffer_len += sn; + buffer = realloc(buffer, buffer_len + 1); + } + for (unsigned int c = 0; c < size * nmemb; ++c) { + buffer[buffer_fill + c] = buf[c]; + } + buffer_fill += sn; + buffer[buffer_fill] = 0; + return sn; //tell curl how many bytes we handled +} diff --git a/common/http.h b/common/http.h new file mode 100644 index 0000000..ed63c9f --- /dev/null +++ b/common/http.h @@ -0,0 +1,10 @@ +#ifndef COMMON_HTTP_H_ +#define COMMON_HTTP_H_ + +void http_add_header(const char* name, const char* value); +void http_clean(void); +const char * http_get(const char* path); +void http_init(void); +const char * http_post(const char* path, const char* postfields); + +#endif diff --git a/common/util.h b/common/util.h index 464e7dc..35970d4 100644 --- a/common/util.h +++ b/common/util.h @@ -1,5 +1,5 @@ -#ifndef _UTIL_H_ -#define _UTIL_H_ +#ifndef COMMON_UTIL_H_ +#define COMMON_UTIL_H_ #include #include diff --git a/restapi/CMakeLists.txt b/restapi/CMakeLists.txt new file mode 100644 index 0000000..45a22c2 --- /dev/null +++ b/restapi/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE SOURCES *.c) + +ADD_LIBRARY(restapi STATIC ${SOURCES}) +add_dependencies(restapi cjson-build) +target_link_libraries(restapi common) diff --git a/restapi/auth.c b/restapi/auth.c new file mode 100644 index 0000000..e3f1b74 --- /dev/null +++ b/restapi/auth.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#include "auth.h" +#include "common/config.h" +#include "common/http.h" + +#define USER_STR "user=" +#define PASS_STR "&password=" + +char // returns 0 if ok, greater than 0 otherwise +restapi_login(const char* username, const char* password) +{ + char ret = 0; + http_init(); + size_t user_str_len = strlen(USER_STR); + size_t user_len = strlen(username); + size_t pass_str_len = strlen(PASS_STR); + size_t pass_len = strlen(password); + char* login_args = malloc(user_str_len + user_len + pass_str_len + pass_len + 1); + strcpy(login_args, USER_STR); + strcpy(login_args + user_str_len, username); + strcpy(login_args + user_str_len + user_len, PASS_STR); + strcpy(login_args + user_str_len + user_len + pass_str_len, password); + const char* buffer = http_post("/api/v1/login", login_args); + free(login_args); + if (buffer == NULL) { + fprintf(stderr, "Error while authenticating, http post didn't return any data.\n"); + return 1; + } + + cJSON* json = cJSON_Parse(buffer); + if (json == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) + fprintf(stderr, "Json parsing error before: %s\n", error_ptr); + fprintf(stderr, "Error while authenticating, couldn't parse json output :\n%s\n", buffer); + ret = 2; + goto login_json_cleanup; + } + + const cJSON* status = cJSON_GetObjectItemCaseSensitive(json, "status"); + if (cJSON_IsString(status) && status->valuestring != NULL && strcmp(status->valuestring, "success") == 0) { + cJSON* data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) { + fprintf(stderr, "Error while authenticating, couldn't parse json output :\n%s\n", buffer); + ret = 4; + goto login_json_cleanup; + } + cJSON* userId = cJSON_GetObjectItemCaseSensitive(data, "userId"); + cJSON* authToken = cJSON_GetObjectItemCaseSensitive(data, "authToken"); + if (!cJSON_IsString(userId) || userId->valuestring == NULL || !cJSON_IsString(authToken) || authToken->valuestring == NULL) { + fprintf(stderr, "Error while authenticating, couldn't parse json output :\n%s\n", buffer); + ret = 5; + goto login_json_cleanup; + } + + printf("userid: %s\nauthtoken: %s\n", userId->valuestring, authToken->valuestring); + http_add_header("X-User-Id", userId->valuestring); + http_add_header("X-Auth-Token", authToken->valuestring); + } else { + const cJSON* msg = cJSON_GetObjectItemCaseSensitive(json, "message"); + if (cJSON_IsString(msg) && msg->valuestring != NULL) + fprintf(stderr, "Error while authenticating: %s\n", msg->valuestring); + else + fprintf(stderr, "Error while authenticating.\n%s\n", buffer); + ret = 3; + } +login_json_cleanup: + cJSON_Delete(json); + return ret; +} + +void restapi_logout(void) +{ + (void) http_post("/api/v1/logout", NULL); + http_clean(); +} diff --git a/restapi/auth.h b/restapi/auth.h new file mode 100644 index 0000000..ce5ea10 --- /dev/null +++ b/restapi/auth.h @@ -0,0 +1,11 @@ +#ifndef RESTAPI_AUTH_H_ +#define RESTAPI_AUTH_H_ + +extern char * userId; +extern char * authToken; + +char // returns 0 if ok, greater than 0 otherwise +restapi_login(const char* username, const char* password); +void restapi_logout(void); + +#endif diff --git a/restapi/im.c b/restapi/im.c new file mode 100644 index 0000000..6b9f75c --- /dev/null +++ b/restapi/im.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include "common/http.h" +#include "im.h" + +#define LOGIN_ARG_PRE "{ \"username\": \"" +#define LOGIN_ARG_POST "\" }" + +char // returns 0 if ok, greater than 0 otherwise +restapi_im_close(const char* username) +{ + char ret = 0; + + size_t pre_len = strlen(LOGIN_ARG_PRE); + size_t user_len = strlen(username); + size_t post_len = strlen(LOGIN_ARG_POST); + char* login_args = malloc(pre_len + user_len + post_len + 1); + strcpy(login_args, LOGIN_ARG_PRE); + strcpy(login_args + pre_len, username); + strcpy(login_args + pre_len + user_len, LOGIN_ARG_POST); + http_add_header("Content-type", "application/json"); + const char* buffer = http_post("/api/v1/im.close", login_args); + free(login_args); + + if (buffer == NULL) { + fprintf(stderr, "Error while im_close, http post didn't return any data.\n"); + return 1; + } + + cJSON* json = cJSON_Parse(buffer); + if (json == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) + fprintf(stderr, "Json parsing error before: %s\n", error_ptr); + fprintf(stderr, "Error while im_close, couldn't parse json output :\n%s\n", buffer); + ret = 2; + goto login_json_cleanup; + } + + const cJSON* status = cJSON_GetObjectItemCaseSensitive(json, "status"); + if (cJSON_IsString(status) && status->valuestring != NULL && strcmp(status->valuestring, "success") == 0) { + printf("user %s removed\n", username); + } else { + const cJSON* msg = cJSON_GetObjectItemCaseSensitive(json, "message"); + if (cJSON_IsString(msg) && msg->valuestring != NULL) + fprintf(stderr, "Error while im_close: %s\n", msg->valuestring); + else + fprintf(stderr, "Error while im_close.\n%s\n", buffer); + ret = 3; + } +login_json_cleanup: + cJSON_Delete(json); + return ret; +} diff --git a/restapi/im.h b/restapi/im.h new file mode 100644 index 0000000..fe85860 --- /dev/null +++ b/restapi/im.h @@ -0,0 +1,7 @@ +#ifndef RESTAPI_IM_H_ +#define RESTAPI_IM_H_ + +char // returns 0 if ok, greater than 0 otherwise +restapi_im_close(const char* username); + +#endif -- cgit v1.2.3