From 095995f6c13dde096e25ed9bd07195f747dba167 Mon Sep 17 00:00:00 2001 From: "Begerad, Stefan" Date: Fri, 17 Mar 2023 14:53:31 +0100 Subject: [PATCH] feat(http-get-param): initial commit --- http-get-param/.gitignore | 1 + http-get-param/cfg.txt | 8 + http-get-param/file2char.c | 84 ++++++++ http-get-param/file2char.h | 16 ++ http-get-param/main.c | 405 +++++++++++++++++++++++++++++++++++++ http-get-param/makefile | 25 +++ http-get-param/pqconfo.c | 89 ++++++++ http-get-param/pqconfo.h | 21 ++ http-get-param/readme.md | 21 ++ 9 files changed, 670 insertions(+) create mode 100644 http-get-param/.gitignore create mode 100644 http-get-param/cfg.txt create mode 100644 http-get-param/file2char.c create mode 100644 http-get-param/file2char.h create mode 100644 http-get-param/main.c create mode 100644 http-get-param/makefile create mode 100644 http-get-param/pqconfo.c create mode 100644 http-get-param/pqconfo.h create mode 100644 http-get-param/readme.md diff --git a/http-get-param/.gitignore b/http-get-param/.gitignore new file mode 100644 index 0000000..06b458e --- /dev/null +++ b/http-get-param/.gitignore @@ -0,0 +1 @@ +cfg-sib00_rgncycle.txt diff --git a/http-get-param/cfg.txt b/http-get-param/cfg.txt new file mode 100644 index 0000000..1dd7aed --- /dev/null +++ b/http-get-param/cfg.txt @@ -0,0 +1,8 @@ +pq = +{ +db = "database"; +host = "localhost"; +port = 5432; +secret = "secret"; +user = "user"; +} diff --git a/http-get-param/file2char.c b/http-get-param/file2char.c new file mode 100644 index 0000000..bff1b25 --- /dev/null +++ b/http-get-param/file2char.c @@ -0,0 +1,84 @@ +/*to use exit*/ +#include +/*to use *print**/ +#include +/*to user file2char*/ +#include "file2char.h" + +int convert_file2char(char const* path, char **buf){ + /*declarations*/ + FILE *fp; + long lSize; + + /*TODO Check path for validity!*/ + /*fopen() + *filename + *mode + *return: set errno value in error case + */ + fp=fopen(path,"r"); + + if(fp){ + fprintf(stdout,"file opened.\n"); + + /*set offset to file end*/ + /*fseek() + *stream + *offset + *origin + */ + fseek(fp,0L,SEEK_END); + if (ferror(fp)){ + printf ("Error seeking file\n"); + } + + /*get file size*/ + lSize = ftell( fp ); + fprintf(stdout,"lSize: %ld.\n",lSize); + + /*set offset to file start*/ + fseek(fp,0,SEEK_SET); + if (ferror(fp)){ + printf ("Error seeking file\n"); + } + + /*allocate memory for entire content + null termination*/ + *buf=(char *)malloc((lSize+1)*sizeof(char)); + if(!*buf){ + fclose(fp); + fputs("Error allocating memory",stderr); + exit(EXIT_FAILURE); + } + fprintf(stdout,"mem allocated.\n"); + + /*copy file into buffer*/ + /*fread() + *ptr + *size + *count + *stream + */ + if( 1!=fread(*buf,lSize,1,fp)){ + fclose(fp); + free(*buf); + fputs("Error reading entire file",stderr); + exit(EXIT_FAILURE); + } + fprintf(stdout,"file read.\n"); + + /*close file*/ + if(EOF==fclose(fp)){ + free(*buf); + exit(EXIT_FAILURE); + } + fprintf(stdout,"file closed.\n"); + + (*buf)[lSize] = '\0'; + fprintf(stdout,"mem null terminated.\n"); + }else{ + perror(path); + exit(EXIT_FAILURE); + } + /*done*/ + return 0; +} diff --git a/http-get-param/file2char.h b/http-get-param/file2char.h new file mode 100644 index 0000000..0e244a0 --- /dev/null +++ b/http-get-param/file2char.h @@ -0,0 +1,16 @@ +/*We can use CPP tricks to avoid parsing the same header file more than once*/ +#ifndef FILE2CHAR_H +#define FILE2CHAR_H + +/* + * convert_file2char reads the file identified by 'path' into a character buffer + * pointed at by 'buf'. + * On success, 0 is returned. + * On failure, exit(EXIT_FAILURE) is returned. + * + * WARNING: convert_file2char malloc()s memory to '*buf' which must be freed by + * the caller. + */ +int convert_file2char(char const* path, char **buf); + +#endif /* FILE2CHAR_H */ diff --git a/http-get-param/main.c b/http-get-param/main.c new file mode 100644 index 0000000..785e369 --- /dev/null +++ b/http-get-param/main.c @@ -0,0 +1,405 @@ +/*to create API*/ +#include +/*to connect to Postgresql*/ +#include +/* to use JSON in response body*/ +#include +/*to use strcat*/ +#include +/*to use inet_ntoa*/ +#include +#include +#include +/*to use *print**/ +#include + +/*to use struct pq_confo*/ +#include "pqconfo.h" +/*to use file2char*/ +#include "file2char.h" + +#define ROUTE_HELLO "/hello" +#define ROUTE_ENTITIES "/entities" +#define PREFIX "/test" + +/** + * callback functions declaration + */ +int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_get_entities (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_get_entity (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_set_entity (const struct _u_request * request, struct _u_response * response, void * user_data); + +/** + * decode a u_map into a string + */ +char * print_map(const struct _u_map * map) { + /*declaration*/ + char * line, * to_return = NULL; + const char **keys, * value; + int len, i; + + if (map != NULL) { + keys = u_map_enum_keys(map); + for (i=0; keys[i] != NULL; i++) { + value = u_map_get(map, keys[i]); + len = snprintf(NULL, 0, "key is %s, value is %s", keys[i], value); + line = malloc((size_t)(len+1)); + snprintf(line, (size_t)(len+1), "key is %s, value is %s", keys[i], value); + if (to_return != NULL) { + len = (int)(strlen(to_return) + strlen(line) + 1); + to_return = realloc(to_return, (size_t)(len+1)); + if (strlen(to_return) > 0) { + //concatenate strings + strcat(to_return, "\n"); + } + } else { + to_return = malloc((strlen(line) + 1)); + to_return[0] = 0; + } + strcat(to_return, line); + free(line); + } + return to_return; + } else { + return NULL; + } +} + +int main(int argc, char *argv[]) { + /*declaration*/ + struct _u_instance instance; + int frameworkRet, apiPort, apiPortRet, cx; + /*TODO How is const defined?*/ + const int pqConInfoSize = 100; + char pqConInfo[pqConInfoSize]; + int pqConfoRet; + const struct pq_confo * pqConfo; + + printf("main() Started...\n"); + if (argc != 3) { + fprintf(stderr, "Usage: ./main \n"); + return 1; + } + + printf("main() argv[1]: %s\n", argv[1]); + printf("main() argv[2]: %s\n", argv[2]); + + /*get Postgresql connection info from config*/ + /*TODO Validate argv!*/ + pqConfoRet=get_pq_confo(argv[2],&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT valid\n"); + return 2; + }else{ + fprintf(stdout,"pqConfoRet: %d\n",pqConfoRet); + } + + /*create pq connection info*/ + cx = snprintf(pqConInfo,pqConInfoSize,"postgresql://%s:%s@%s:%d/%s",pqConfo->user,pqConfo->secret,pqConfo->host,pqConfo->port,pqConfo->db); + if(0>cx && cx>=pqConInfoSize){ + fprintf(stderr,"main() error creating pqConInfo\n"); + /*free Postgresql connection info*/ + pqConfoRet=free_pq_confo(&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT free\n"); + return 3; + } + + return 4; + } + + /*free Postgresql connection info*/ + pqConfoRet=free_pq_confo(&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT free\n"); + return 5; + } + + /*store command line argument in variable*/ + /*validate user input*/ + /*omit injection*/ + /*stream:argv*/ + apiPortRet = sscanf(argv[1], "%d", &apiPort); + /*valid user input:1 successfully filled item*/ + if (apiPortRet != 1) { + fprintf(stderr, "The argument must be an integer\n"); + return 6; + } + if (apiPort < 0) { + fprintf(stderr, "Error passing a negative port\n"); + exit(1); + } + printf("main() port: %d\n", apiPort); + + /*TODO Is mem leaked when connection to pq failes?*/ + + if (ulfius_init_instance(&instance, apiPort, NULL, NULL) != U_OK) { + printf("main() Error ulfius_init_instance, abort"); + return 7; + } + + /*connect to database*/ + //TODO This is a synchronous call! Shall we block the rest? + PGconn *conn = PQconnectdb(pqConInfo); + if (PQstatus(conn) == CONNECTION_BAD) { + fprintf(stderr, "Connection to database failed: %s\n", + PQerrorMessage(conn)); + PQfinish(conn); + return 8; + }else if(PQstatus(conn)==CONNECTION_OK){ + printf("main() connected to database\n"); + }else{ + printf("main() connection status NOT known\n"); + } + + //TODO Why? + u_map_put(instance.default_headers, "Access-Control-Allow-Origin", "*"); + + // Maximum body size sent by the client is 1 Kb + instance.max_post_body_size = 1024; + + // Endpoint list declaration + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_HELLO, NULL, 0, &callback_get_hello, NULL); + + //user_data: pass pq connection information to callback + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_ENTITIES, "/info", 0, &callback_get_entities, (void *)conn); + + //user_data: pass pq connection information to callback + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_ENTITIES, "/:id/info", 0, &callback_get_entity, (void *)conn); + + //user_data: pass pq connection information to callback + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_ENTITIES, "/update", 0, &callback_set_entity, (void *)conn); + + // default_endpoint declaration + ulfius_set_default_endpoint(&instance, &callback_default, NULL); + + // Start the framework + // Open an http connection + frameworkRet = ulfius_start_framework(&instance); + + if (frameworkRet == U_OK) { + printf("main() Start framework on port %d\n", instance.port); + /*wait*/ + while(1){ + sleep(3600); + } + } else { + printf("main() Error starting framework; frameworkRet: %d\n",frameworkRet); + } + + printf("main() End framework\n"); + + /*clean up API*/ + ulfius_stop_framework(&instance); + ulfius_clean_instance(&instance); + + /*clean up Postgresql connection*/ + PQfinish(conn); + + printf("main() Done.\n"); + + return 0; +} + +/** + * Callback function that put a "Hello World!" string in the response + */ +int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data) { + ulfius_set_string_body_response(response, 200, "Hello World!"); + return U_CALLBACK_CONTINUE; +} + +/** + * Default callback function called if no endpoint has a match + */ +int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data) { + ulfius_set_string_body_response(response, 404, "Page not found, do what you want"); + return U_CALLBACK_CONTINUE; +} + +/** + * Callback function for route /entities to reply the entity at question + */ +int callback_get_entity (const struct _u_request * request, struct _u_response * response, void * user_data) { + /*declarations*/ + char *url_params; + char *response_body; + int r; + const char **keys; + const char *value; + char *line; + int len; + int i; + PGconn *conn; + char *prepStmEntities; + PGresult *pqRes; + int nbRecords,nbFields; + json_t *arrayRecords, *arrayFields, *stringField; + + printf("callback_get_entity() Started...\n"); + url_params = print_map(request->map_url); + r=asprintf( + &response_body, + "parameters from the url are \n%s\n\n", + url_params); + //printf("callback_get_entity() %d characters generated\n",r); + + if(request->map_url != NULL){ + keys=u_map_enum_keys(request->map_url); + if(keys[0] == NULL){ + //TODO error + printf("callback_get_entity() param NOT available\n"); + //TODO Why this casting? + (void)(request); + (void)(user_data); + ulfius_set_string_body_response(response, 200, "This is the /entity/:id/info route: Param error!"); + }else{ + value = u_map_get(request->map_url, keys[0]); + //printf("callback_get_entity() key is %s, value is %s\n", keys[0], value); + + conn = (PGconn *)user_data; + + /*define prepared statement*/ + len=snprintf(NULL,0,"SELECT * FROM entities WHERE id=%s;",value); + prepStmEntities=malloc((size_t)(len+1)); + //TODO Release prepStmEntities! + snprintf(prepStmEntities,(size_t)(len+1),"SELECT * FROM entities WHERE id=%s;",value); + //printf("callback_get_entity() prepStmEntities: %s\n",prepStmEntities); + + /*create prepared statement*/ + /*conn:connection*/ + /*stm:statement*/ + /*0:number of passed parameters*/ + /*NULL: server figures out parameter type*/ + /*paramValues:pointer of an array of strings containing parameters*/ + /*NULL:relevant for binary parameters*/ + /*NULL:relevant for binary parameters*/ + /*0:obtain result in text format*/ + //TODO How to pass params? + pqRes = PQexecParams(conn, prepStmEntities, 0, NULL, NULL, NULL, NULL, 0); + if(PQresultStatus(pqRes) != PGRES_TUPLES_OK) { + /*handle error*/ + printf("callback_get_entity() No data retrieved\n"); + /*clean up result*/ + PQclear(pqRes); + //TODO Why this casting? + (void)(request); + (void)(user_data); + ulfius_set_string_body_response(response, 200, "This is the /entity/:id/info route: Dba error!"); + }else{ + /*evaluate result*/ + nbRecords = PQntuples(pqRes); + //printf("callback_get_entity() nbRecords: %d\n",nbRecords); + nbFields = PQnfields(pqRes); + arrayRecords=json_array(); + for(int i=0;i +/*to use libconfig*/ +#include +/*to use struct pq_confo*/ +#include "pqconfo.h" + +/*get Postgresql connection information*/ +int get_pq_confo(const char * file,const struct pq_confo ** confo){ + /*declaration*/ + config_t cfg; + const char * db, * host, * secret, * user; + int port; + + fprintf(stdout,"get_pq_confo() Started...\n"); + /*initialise new and EMPTY config*/ + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + /*TODO Validate config file name!*/ + if(! config_read_file(&cfg, file)) + { + fprintf(stderr, "get_pq_confo() %s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + /*destroy config, BUT NOT the structure*/ + config_destroy(&cfg); + return 2; + } + fprintf(stdout,"get_pq_confo() Parsing config file done\n"); + + /*lookup db string*/ + if(config_lookup_string(&cfg, "pq.db", &db)){ + printf("get_pq_confo() db: %s\n", db); + }else{ + fprintf(stderr, "get_pq_confo() NO 'db' setting in config file found.\n"); + return 3; + } + + /*lookup host string*/ + if(config_lookup_string(&cfg, "pq.host", &host)){ + printf("get_pq_confo() host: %s\n", host); + }else{ + fprintf(stderr, "get_pq_confo() NO 'host' setting in config file found.\n"); + return 4; + } + + /*lookup secret string*/ + if(!config_lookup_string(&cfg, "pq.secret", &secret)){ + fprintf(stderr, "get_pq_confo() NO 'secret' setting in config file found.\n"); + return 5; + } + + /*lookup user string*/ + if(config_lookup_string(&cfg, "pq.user", &user)){ + printf("get_pq_confo() user: %s\n", user); + }else{ + fprintf(stderr, "get_pq_confo() NO 'user' setting in config file found.\n"); + return 6; + } + + /*lookup port int*/ + if(config_lookup_int(&cfg, "pq.port", &port)){ + printf("get_pq_confo() port: %d\n", port); + }else{ + fprintf(stderr, "get_pq_confo() NO 'port' setting in config file found.\n"); + return 7; + } + + /*create struct*/ + struct pq_confo * pqConfo = (struct pq_confo *)malloc( sizeof(struct pq_confo)); + sprintf(pqConfo->db,"%s",db); + sprintf(pqConfo->host,"%s",host); + pqConfo->port=port; + sprintf(pqConfo->secret,"%s",secret); + sprintf(pqConfo->user,"%s",user); + *confo = pqConfo; + + /*destroy config, BUT NOT the structure*/ + config_destroy(&cfg); + + /*return success*/ + return 0; +} + +/*free Postgresql connection information*/ +int free_pq_confo(const struct pq_confo ** confo){ + free((struct pq_confo *)*confo); + return 0; +} diff --git a/http-get-param/pqconfo.h b/http-get-param/pqconfo.h new file mode 100644 index 0000000..24eb475 --- /dev/null +++ b/http-get-param/pqconfo.h @@ -0,0 +1,21 @@ +/*We can use CPP tricks to avoid parsing the same header file more than once*/ +#ifndef PQCONFO_H +# define PQCONFO_H + +/*structure for Postgresql connection information*/ +struct pq_confo +{ + char db[23]; + char host[23]; + int port; + char secret[42]; + char user[23]; +}; + +/*get Postgresql connection information*/ +int get_pq_confo(const char * file,const struct pq_confo ** confo); + +/*free Postgresql connection information*/ +int free_pq_confo(const struct pq_confo ** confo); + +#endif /* PQCONFO_H */ diff --git a/http-get-param/readme.md b/http-get-param/readme.md new file mode 100644 index 0000000..fed76da --- /dev/null +++ b/http-get-param/readme.md @@ -0,0 +1,21 @@ +# rgncycle-libulfius + +libulfius API for rgncycle + +* build + +``` +make +``` + +* run + +``` +./main +``` + +* run Valgrind tool `Memcheck` + +``` +valgrind --leak-check=yes -s ./main +```