diff --git a/.gitignore b/.gitignore index f50972e..cba7efc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ a.out -hashmap.c \ No newline at end of file diff --git a/README.md b/README.md index 918960d..d647f50 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,12 @@ Wow! Looks pretty similar to the Express functions. Below lays out the current f 1. [teru() -- new Teru](#New-Teru) ## App functionalities: -2. [app_use()](#Use-Teru) -3. [app_set()](#Set-Teru) +2. [app_use() -- Public directories and library usage](#Use-Teru) +3. [app_set() -- Views directory](#Set-Teru) ## Send functionalities: -4. [res_sendFile()](#Send-File-to-User) -5. [res_end()](#Send-Message-to-User) +4. [res_sendFile() -- Send a file](#Send-File-to-User) +5. [res_end() -- Send a string](#Send-Message-to-User) +6. [res_render() -- Send a file with match keys that replace to allow for dynamic HTML pages](#Render-File-to-User) ## Request parameters: 6. [req_query()](#See-Request-Query-Parameters) 7. [req_body()](#See-Request-Body-Parameters) @@ -112,6 +113,30 @@ Then connect to an endpoint: app_get(app, "/hi", hello_there); ``` +# Render File to User +This allows for a server to take an HTML page and find and replace occurences of _match strings_. There are a few steps for setting this up: +- Create an HTML file with match strings. The _start match_ and _end match_ can be whatever strings you wish. However, these strings must match what you give the `render()` function in the following steps. + +```HTML + + + Render Example + + +

Hi there {{NAME}}!

+ + +``` +- Next, set the match parameters using the `res_matches()` function, which for the previous example would look like the following. Note that `res` references the second parameter of the handler function (see [res_sendFile](#Send-File-to-User) for an example). +```C +res_matches(res, "NAME", "charlie-map"); +``` +- Finally, use the `res_render()` function to interpret the match strings and send the result to the user. `res_render()` takes in `res`, the name of the file, and the _start match_ and _end match_. Assuming the above HTML file is named "home.html", the `res_render()` call would look like: + +```C +res_render(res, "home", "{{", "}}"); +``` + # See Request Query Parameters Request query parameters are added at the end of the URL (for example `localhost:8888/hi?name=Charlie`). `req_query()` allows access to these by inserting the name of the query parameter: @@ -147,7 +172,5 @@ app_post(app, "/hi", hello_name); # Teru's Future Currently there are a few bucket list items that will be check off over time. However, feel free to [leave an issue](https://github.com/charlie-map/wiki_backend/issues) with any suggested enhancements. -- `app_use()` functionality - `app_put()`, `app_delete()`, etc. - more `app_set()` functionality -- `res_render()` see [Mustache Express](https://www.npmjs.com/package/mustache-express) diff --git a/compile b/compile index f97213b..fd6c718 100755 --- a/compile +++ b/compile @@ -1 +1 @@ -gcc -g server.c teru.c hashmap.c request.c -pthread -lm \ No newline at end of file +gcc -g server.c teru.c hashmap.c request.c typeinfer.c -pthread -lm \ No newline at end of file diff --git a/hashmap.c b/hashmap.c new file mode 100644 index 0000000..c48e18f --- /dev/null +++ b/hashmap.c @@ -0,0 +1,718 @@ +#include +#include +#include +#include +#include "hashmap.h" + +/* + vtableKeyStore is used for using unknown type keys in the + hashmap. Because the variable type is unknown, the use + of this struct plus the function (some function name)__hashmap() can + be used to decipher any type of possible key in the hashmap + + SEE bottom of the page for the function declarations and uses +*/ +typedef struct VTable { + void *key; + + // these define how we utilize the key + // DEFAULT behavior is using a char * setup + void (*printKey)(void *); + int (*compareKey)(void *, void *); + void (*destroyKey)(void *); +} vtableKeyStore; + +typedef struct ll_def { + struct ll_def *next; + + vtableKeyStore key; + int max__arrLength, arrIndex; // only for hash type 1 + int isArray; + void *ll_meat; // single value pointer + // for hash__type = 0 + // or array for + // hash__type = 1 +} ll_main_t; + +// will store a single hashmap +struct Store { + int hashmap__size; + // takes a type of hashmap + // 0: replace value + // 1: append to growing value list + int hash__type; + ll_main_t **map; + + // used for printing the hashmap values + void (*printer)(void *); + // destroying hashmap values + void (*destroy)(void *); +}; + +const int MAX_BUCKET_SIZE = 5; +const int START_SIZE = 1023; // how many initial buckets in array + +unsigned long hash(unsigned char *str) { + unsigned long hash = 5381; + int c; + + while ((c = *str++) != 0) + hash = ((hash << 5) + hash) + c; + + return hash; +} + + +// define some linked list functions (see bottom of file for function write outs): +ll_main_t *ll_makeNode(vtableKeyStore key, void *value, int hash__type); +int ll_insert(ll_main_t *node, vtableKeyStore key, void *payload, int hash__type, void (*destroy)(void *)); + +ll_main_t *ll_next(ll_main_t *curr); + +int ll_print(ll_main_t *curr, void (*printer)(void *)); + +int ll_isolate(ll_main_t *node); +int ll_destroy(ll_main_t *node, void (destroyObjectPayload)(void *)); + + +hashmap *make__hashmap(int hash__type, void (*printer)(void *), void (*destroy)(void *)) { + hashmap *newMap = (hashmap *) malloc(sizeof(hashmap)); + + newMap->hash__type = hash__type; + newMap->hashmap__size = START_SIZE; + + // define needed input functions + newMap->printer = printer; + newMap->destroy = destroy; + + newMap->map = (ll_main_t **) malloc(sizeof(ll_main_t *) * newMap->hashmap__size); + + for (int i = 0; i < newMap->hashmap__size; i++) { + newMap->map[i] = NULL; + } + + return newMap; +} + +// take a previous hashmap and double the size of the array +// this means we have to take all the inputs and calculate +// a new position in the new array size +// within hash__m->map we can access each of the linked list pointer +// and redirect them since we store the keys +int re__hashmap(hashmap *hash__m) { + // define the sizes / updates to size: + int old__mapLength = hash__m->hashmap__size; + int new__mapLength = hash__m->hashmap__size * 2; + + // create new map (twice the size of old map (AKA hash__m->map)) + ll_main_t **new__map = (ll_main_t **) malloc(sizeof(ll_main_t *) * new__mapLength); + + for (int set__newMapNulls = 0; set__newMapNulls < new__mapLength; set__newMapNulls++) + new__map[set__newMapNulls] = NULL; + + int new__mapPos = 0; + + for (int old__mapPos = 0; old__mapPos < old__mapLength; old__mapPos++) { + // look at each bucket + // if there is contents + while (hash__m->map[old__mapPos]) { // need to look at each linked node + // recalculate hash + new__mapPos = hash(hash__m->map[old__mapPos]->key.key) % new__mapLength; + + // store the node in temporary storage + ll_main_t *currNode = hash__m->map[old__mapPos]; + + // extract currNode from old map (hash__m->map) + hash__m->map[old__mapPos] = ll_next(currNode); // advance root + ll_isolate(currNode); // isolate old root + + // defines the linked list head in the new map + ll_main_t *new__mapBucketPtr = new__map[new__mapPos]; + + // if there is one, we have to go to the tail + if (new__mapBucketPtr) { + + while (new__mapBucketPtr->next) new__mapBucketPtr = ll_next(new__mapBucketPtr); + + new__mapBucketPtr->next = currNode; + } else + new__map[new__mapPos] = currNode; + } + } + + free(hash__m->map); + hash__m->map = new__map; + hash__m->hashmap__size = new__mapLength; + + return 0; +} + +int METAinsert__hashmap(hashmap *hash__m, vtableKeyStore key, void *value) { + int mapPos = hash(key.key) % hash__m->hashmap__size; + int bucketLength = 0; // counts size of the bucket at mapPos + + // see if there is already a bucket defined at mapPos + if (hash__m->map[mapPos]) + bucketLength = ll_insert(hash__m->map[mapPos], key, value, hash__m->hash__type, hash__m->destroy); + else + hash__m->map[mapPos] = ll_makeNode(key, value, hash__m->hash__type); + + // if bucketLength is greater than an arbitrary amount, + // need to grow the size of the hashmap (doubling) + if (bucketLength >= MAX_BUCKET_SIZE) + re__hashmap(hash__m); + + return 0; +} + +int ll_get_keys(ll_main_t *ll_node, void **keys, int *max_key, int key_index) { + while(ll_node) { + keys[key_index++] = ll_node->key.key; + + if (key_index == *max_key) { // resize + *max_key *= 2; + + keys = realloc(keys, sizeof(void *) * *max_key); + } + + ll_node = ll_node->next; + } + + return key_index; +} + +// creates an array of all keys in the hash map +void **keys__hashmap(hashmap *hash__m, int *max_key) { + int key_index = 0; + *max_key = 16; + void **keys = malloc(sizeof(void *) * *max_key); + + for (int find_keys = 0; find_keys < hash__m->hashmap__size; find_keys++) { + if (hash__m->map[find_keys]) { + // search LL: + key_index = ll_get_keys(hash__m->map[find_keys], keys, max_key, key_index); + } + } + + *max_key = key_index; + + return keys; +} + +/* + get__hashmap search through a bucket for the inputted key + the response varies widely based on hash__type + + -- for all of them: NULL is return if the key is not found + + TYPE 0: + Returns the single value that is found + TYPE 1: + Returns a pointer to a struct (hashmap__response) with an array that can be + searched through and a length of said array. + This array should be used with caution because accidental + free()ing of this array will leave the key to that array + pointing to unknown memory. However, the freeing of the + returned struct will be left to the user + -- TERU update: update for "wildcarding" the get request + use "i" to implement this and an + int is_match(void *, void *); + to compare keys + leave blank for no is_match option + + use "w" to implement a weight function + int is_lower(void *, void *); + to compare values + leave blank for no is_lower option +*/ +void *get__hashmap(hashmap *hash__m, void *key, char *ep, ...) { + if (!hash__m) + return NULL; + + int (*is_match)(void *, void *) = NULL; + int (*is_lower)(void *, void *) = NULL; + + va_list new_poly_reader; + va_start(new_poly_reader, ep); + + for (int check_ep = 0; ep[check_ep]; check_ep++) { + if (ep[check_ep] == 'w') // found match + is_lower = va_arg(new_poly_reader, int (*)(void *, void *)); + if (ep[check_ep] == 'i') // found match + is_match = va_arg(new_poly_reader, int (*)(void *, void *)); + } + + // get hash position + int mapPos; + + for (mapPos = is_match ? 0 : hash(key) % hash__m->hashmap__size; mapPos < hash__m->hashmap__size; mapPos++) { + ll_main_t *ll_search = hash__m->map[mapPos]; + + // search through the bucket to find any keys that match + while (ll_search) { + if ((is_match && is_match(ll_search->key.key, key)) || ll_search->key.compareKey(ll_search->key.key, key)) { // found a match + + // depending on the type and mode, this will just return + // the value: + if (hash__m->hash__type == 0) + return ll_search->ll_meat; + else { + hashmap__response *returnMeat = malloc(sizeof(hashmap__response)); + + if (ll_search->isArray) { + returnMeat->payload = ((void **) ll_search->ll_meat)[0]; + returnMeat->next = NULL; + + hashmap__response *set_intern_meat = returnMeat; + + for (int set_return_meat = 1; set_return_meat < ll_search->arrIndex + 1; set_return_meat++) { + // if is_lower exists, find position in return: + if (is_lower) { + set_intern_meat = returnMeat; + + // search for the position of the next value based on is_lower + while (set_intern_meat->next && is_lower(set_intern_meat->next->payload, ((void **) ll_search->ll_meat)[set_return_meat])) { + set_intern_meat = set_intern_meat->next; + } + + hashmap__response *new_node_element = malloc(sizeof(hashmap__response)); + new_node_element->payload = ((void **) ll_search->ll_meat)[set_return_meat]; + + // check for setting as head + if (is_lower(((void **) ll_search->ll_meat)[set_return_meat], set_intern_meat->payload)) { + returnMeat->next = returnMeat; + returnMeat = new_node_element; + } else { // insert as next of set_intern_meat + hashmap__response *curr_next = set_intern_meat->next; + + // splicing ll_meat value into the linked list + set_intern_meat->next = new_node_element; + new_node_element->next = curr_next; + } + + continue; + } + + // otherwise add the value at the end of the linked list + set_intern_meat->next = malloc(sizeof(hashmap__response)); + set_intern_meat = set_intern_meat->next; + + set_intern_meat->payload = ((void **) ll_search->ll_meat)[set_return_meat]; + } + } else { // define array + void *ll_tempMeatStorage = ll_search->ll_meat; + + ll_search->max__arrLength = 2; + ll_search->arrIndex = 0; + + ll_search->ll_meat = malloc(sizeof(void *) * ll_search->max__arrLength * 2); + ((void **) ll_search->ll_meat)[0] = ll_tempMeatStorage; + + returnMeat->payload = ll_search->ll_meat; + returnMeat->next = NULL; + + ll_search->isArray = 1; + } + + return returnMeat; + } + } + + ll_search = ll_next(ll_search); + } + + if (!is_match) + break; + } + + // no key found + return NULL; +} + +// scroll through linked list and delete headers +int clear__hashmap__response(hashmap__response *hr) { + while (hr) { + hashmap__response *n = hr->next; + free(hr); + + hr = n; + } + + return 0; +} + +int print__hashmap(hashmap *hash__m) { + for (int i = 0; i < hash__m->hashmap__size; i++) { + if (hash__m->map[i]) { + printf("Linked list at index %d ", i); + ll_print(hash__m->map[i], hash__m->printer); + printf("\n"); + } + } + + return 0; +} + +// uses the same process as get__hashmap, but deletes the result +// instead. Unfortunately, the get__hashmap function cannot be +// utilized in this context because when the linked list node +// is being extracted, we need to know what the parent of +// the node is +int delete__hashmap(hashmap *hash__m, void *key) { + // get hash position + int mapPos = hash(key) % hash__m->hashmap__size; + + ll_main_t *ll_parent = hash__m->map[mapPos]; + ll_main_t *ll_search = ll_next(ll_parent); + + // check parent then move into children nodes in linked list + if (ll_parent->key.compareKey(ll_parent->key.key, key)) { + // extract parent from the hashmap: + hash__m->map[mapPos] = ll_search; + + ll_destroy(ll_parent, hash__m->destroy); + + return 0; + } + + // search through the bucket to find any keys that match + while (ll_search) { + if (ll_search->key.compareKey(ll_search->key.key, key)) { // found a match + + // we can then delete the key using the same approach as above + // extract the key from the linked list + ll_parent->next = ll_next(ll_search); + + ll_destroy(ll_search, hash__m->destroy); + + return 0; + } + + ll_parent = ll_search; + ll_search = ll_next(ll_search); + } + + return 0; +} + +int deepdestroy__hashmap(hashmap *hash) { + // destroy linked list children + for (int i = 0; i < hash->hashmap__size; i++) { + if (hash->map[i]) { + ll_destroy(hash->map[i], hash->destroy); + } + } + + // destroy map + free(hash->map); + free(hash); + + return 0; +} + + +ll_main_t *ll_makeNode(vtableKeyStore key, void *newValue, int hash__type) { + ll_main_t *new__node = (ll_main_t *) malloc(sizeof(ll_main_t)); + + new__node->isArray = 0; + new__node->next = NULL; + new__node->key = key; + new__node->ll_meat = newValue; + + return new__node; +} + +/* + for hash__type = 0 + takes a linked list node value ptr + and replaces the value with + updated value +*/ +void *ll_specialUpdateIgnore(void *ll_oldVal, void *newValue, void (*destroy)(void *)) { + // clean up previous info at this pointer + destroy(ll_oldVal); + + // update + return newValue; +} + +// takes the ll_pointer->ll_meat and doubles +// size of current array +int ll_resizeArray(ll_main_t *ll_pointer) { + // declare new array + void **new__arrayPtr = malloc(sizeof(void *) * ll_pointer->max__arrLength * 2); + + // copy values over + for (int copyVal = 0; copyVal < ll_pointer->max__arrLength; copyVal++) { + new__arrayPtr[copyVal] = (void *) ((void **) ll_pointer->ll_meat)[copyVal]; + } + + // free old array pointer + free(ll_pointer->ll_meat); + + // update to new meat + ll_pointer->ll_meat = new__arrayPtr; + + return 0; +} + +/* + for hash__type = 1 + takes linked list pointer + - first makes sure that ll_pointer->ll_meat + is an array, if not it creates and array + - then appends value to end of array +*/ +int ll_specialUpdateArray(ll_main_t *ll_pointer, void *newValue) { + // The same "leave key be" for update works here as with ignore + + if (!ll_pointer->isArray) { // need to create an array + void *ll_tempMeatStorage = ll_pointer->ll_meat; + + // update settings for this pointer + ll_pointer->max__arrLength = 8; + ll_pointer->arrIndex = 0; + ll_pointer->isArray = 1; + + ll_pointer->ll_meat = (void **) malloc(sizeof(void *) * ll_pointer->max__arrLength); + ((void **) (ll_pointer->ll_meat))[0] = ll_tempMeatStorage; + + for (int memSet = 1; memSet < ll_pointer->arrIndex; memSet++) + ((void **) (ll_pointer->ll_meat))[memSet] = NULL; + } + + // add new value + ll_pointer->arrIndex++; + ((void **) (ll_pointer->ll_meat))[ll_pointer->arrIndex] = newValue; + + if (ll_pointer->arrIndex == ll_pointer->max__arrLength - 1) + ll_resizeArray(ll_pointer); + + return 0; +} + +// finds the tail and appends +int ll_insert(ll_main_t *crawler__node, vtableKeyStore key, void *newValue, int hash__type, void (*destroy)(void *)) { + + int bucket_size = 1, addedPayload = 0; + + // search through the entire bucket + // (each node in this linked list) + while (crawler__node->next) { + // found a duplicate (only matters + // for hash__type == 0 or 1) + if (crawler__node->key.compareKey(crawler__node->key.key, key.key)) { + if (hash__type == 0) { + crawler__node->ll_meat = ll_specialUpdateIgnore(crawler__node->ll_meat, newValue, destroy); + addedPayload = 1; + } else if (hash__type == 1) { + ll_specialUpdateArray(crawler__node, newValue); + addedPayload = 1; + } + } + + crawler__node = ll_next(crawler__node); + bucket_size++; + } + + if (crawler__node->key.compareKey(crawler__node->key.key, key.key)) { + if (hash__type == 0) { + crawler__node->ll_meat = ll_specialUpdateIgnore(crawler__node->ll_meat, newValue, destroy); + addedPayload = 1; + } else if (hash__type == 1) { + ll_specialUpdateArray(crawler__node, newValue); + addedPayload = 1; + } + } + + if (addedPayload == 0) { + crawler__node->next = ll_makeNode(key, newValue, hash__type); + } + + // return same head + return bucket_size; +} + +ll_main_t *ll_next(ll_main_t *curr) { + return curr->next; +} + +int ll_printNodeArray(ll_main_t *curr, void (*printer)(void *)) { + for (int printVal = 0; printVal < curr->arrIndex + 1; printVal++) { + printer(((void **) curr->ll_meat)[printVal]); + } + + return 0; +} + +int ll_print(ll_main_t *curr, void (*printer)(void *)) { + printf("\n\tLL node "); + //printVoid() + curr->key.printKey(curr->key.key); + printf(" with payload(s):\n"); + if (curr->isArray) + ll_printNodeArray(curr, printer); + else + printer(curr->ll_meat); + + while ((curr = ll_next(curr)) != NULL) { + printf("\tLL node "); + printf(" with payload(s):\n"); + if (curr->isArray) + ll_printNodeArray(curr, printer); + else + printer(curr->ll_meat); + } + + return 0; +} + +int ll_isolate(ll_main_t *node) { + node->next = NULL; + + return 0; +} + +int ll_destroy(ll_main_t *node, void (destroyObjectPayload)(void *)) { + ll_main_t *node_nextStore; + + do { + if (node->key.destroyKey) + node->key.destroyKey(node->key.key); + + if (node->isArray) { + if (destroyObjectPayload) + for (int destroyVal = 0; destroyVal < node->arrIndex + 1; destroyVal++) + destroyObjectPayload(((void **)node->ll_meat)[destroyVal]); + + free(node->ll_meat); + } else if (destroyObjectPayload) + destroyObjectPayload(node->ll_meat); + + node_nextStore = node->next; + free(node); + } while ((node = node_nextStore) != NULL); + + return 0; +} + + +// DEFAULT function declarations +void printCharKey(void *characters) { + printf("%s", (char *) characters); +} +int compareCharKey(void *characters, void *otherValue) { + return strcmp((char *) characters, (char *) otherValue) == 0; +} +void destroyCharKey(void *characters) { free(characters); } + +void printIntKey(void *integer) { + printf("%d", *((int *) integer)); +} +int compareIntKey(void *integer, void *otherValue) { + return *((int *) integer) == *((int *) otherValue); +} +void destroyIntKey(void *integer) { /* We can't free that! */ } + + +int insert__hashmap(hashmap *hash__m, void *key, void *value, ...) { + va_list ap; + vtableKeyStore inserter = { .key = key }; + + va_start(ap, value); + // could be param definer or the printKey function + void *firstArgumentCheck = va_arg(ap, char *); + + // check for DEFAULT ("-d") or INT ("-i"): + if (strcmp((char *) firstArgumentCheck, "-d") == 0) {// use default + inserter.printKey = printCharKey; + inserter.compareKey = compareCharKey; + inserter.destroyKey = NULL; + } else if (strcmp((char *) firstArgumentCheck, "-i") == 0) { + inserter.printKey = printIntKey; + inserter.compareKey = compareIntKey; + inserter.destroyKey = NULL; + } else { + inserter.printKey = firstArgumentCheck; + // do the same for compareKey + inserter.compareKey = va_arg(ap, int (*)(void *, void *)); + + inserter.destroyKey = va_arg(ap, void (*)(void *)); + // if destroy is NULL, we don't want to add since this by DEFAULT + // does no exist + } + + METAinsert__hashmap(hash__m, inserter, value); + + return 0; +} + +char *read_key(char *buffer, int *key_len) { + *key_len = 8; + int key_index = 0; + + char *key = malloc(sizeof(char) * *key_len); + + while (buffer[key_index] != '|') { + key[key_index] = buffer[key_index]; + key_index++; + + if (key_index + 1 == *key_len) { + *key_len *= 2; + + key = realloc(key, sizeof(char) * *key_len); + } + + key[key_index] = '\0'; + } + + *key_len = key_index; + return key; +} +// assumes the pattern: +// name|Charlie\nname|Jack +// NOTE: hashmap should have a destroyValue function +int batchInsert__hashmap(hashmap *hash__m, char *filename) { + void (*printKey)(void *) = printCharKey; + int (*compareKey)(void *, void *) = compareCharKey; + void (*destroyKey)(void *) = destroyCharKey; + + FILE *fp = fopen(filename, "r"); + + if (!fp) { + printf("\033[0;31m"); + printf("\n** Batch Insert File Error **\n"); + printf("\033[0;37m"); + + return 1; + } + + size_t buff_size = sizeof(char) * 8; + char *buffer = malloc(buff_size); + int buffer_len; + + int *key_len = malloc(sizeof(int)); + while ((buffer_len = getline(&buffer, &buff_size, fp)) != -1) { + buffer_len = strlen(buffer); + vtableKeyStore newtable = { .key = read_key(buffer, key_len), + .printKey = printKey, + .compareKey = compareKey, + .destroyKey = destroyKey }; + + // read value starting from where key_len stopped + char *value = malloc(sizeof(char) * (buffer_len - *key_len - 1)); + int cp_value; + for (cp_value = 0; cp_value < buffer_len - *key_len - 2; cp_value++) { + value[cp_value] = *(buffer + sizeof(char) * (*key_len + cp_value + 1)); + } + value[cp_value] = '\0'; + + METAinsert__hashmap(hash__m, newtable, value); + } + + free(buffer); + free(key_len); + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/hashmap.h b/hashmap.h index 052637f..5dd0891 100644 --- a/hashmap.h +++ b/hashmap.h @@ -2,33 +2,35 @@ #define __HASH_T__ typedef struct ReturnHashmap { // used only for type 1 - void **payload; - int payload__length; + void *payload; + + struct ReturnHashmap *next; } hashmap__response; +int clear__hashmap__response(hashmap__response *); typedef struct Store hashmap; -hashmap *make__hashmap(int hash__type, void (*printer)(void *), void (*destroy)(void *)); +hashmap *make__hashmap(int, void (*)(void *), void (*)(void *)); -void **keys__hashmap(hashmap *hash__m, int *max_key); -void *get__hashmap(hashmap *hash__m, void *key); +void **keys__hashmap(hashmap *, int *); +void *get__hashmap(hashmap *, void *, char *, ...); -int print__hashmap(hashmap *hash__m); +int print__hashmap(hashmap *); -int delete__hashmap(hashmap *hash__m, void *key); +int delete__hashmap(hashmap *, void *); -int deepdestroy__hashmap(hashmap *hash); +int deepdestroy__hashmap(hashmap *); -int batchInsert__hashmap(hashmap *hash__m, char *filename); -int insert__hashmap(hashmap *hash__m, void *key, void *value, ...); +int batchInsert__hashmap(hashmap *, char *); +int insert__hashmap(hashmap *, void *, void *, ...); // simple key type functions -void printCharKey(void *characters); -int compareCharKey(void *characters, void *otherValue); -void destroyCharKey(void *characters); +void printCharKey(void *); +int compareCharKey(void *, void *); +void destroyCharKey(void *); -void printIntKey(void *integer); -int compareIntKey(void *integer, void *otherValue); -void destroyIntKey(void *integer); +void printIntKey(void *); +int compareIntKey(void *, void *); +void destroyIntKey(void *); #endif \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..889ea5f --- /dev/null +++ b/public/style.css @@ -0,0 +1,4 @@ +/* TEST CSS */ +* { + background: green; +} \ No newline at end of file diff --git a/request.c b/request.c index 78c79c9..c5e357d 100644 --- a/request.c +++ b/request.c @@ -170,13 +170,12 @@ char *build_url(char *request_url, int *req_length, char *query_param, char **at } char *create_header(int STATUS, int *header_max, hashmap *status_code, hashmap *headers, int post_data_size, char *post_data) { - int header_size; int header_index = 0; *header_max = 32; char *header = malloc(sizeof(char) * *header_max); char *status_char = malloc(sizeof(char) * 4); sprintf(status_char, "%d", STATUS); - char *status_phrase = (char *) get__hashmap(status_code, status_char); + char *status_phrase = (char *) get__hashmap(status_code, status_char, ""); int status_phrase_len = strlen(status_char) + strlen(status_phrase); header_index = status_phrase_len + 11; @@ -190,7 +189,7 @@ char *create_header(int STATUS, int *header_max, hashmap *status_code, hashmap * char **header_key = (char **) keys__hashmap(headers, key_num); for (int cp_header = 0; cp_header < *key_num; cp_header++) { - char *header_value = (char *) get__hashmap(headers, header_key[cp_header]); + char *header_value = (char *) get__hashmap(headers, header_key[cp_header], ""); int head_add_on = strlen(header_key[cp_header]) + strlen(header_value) + 3; header = resize_array(header, header_max, header_index + head_add_on + 4, sizeof(char)); @@ -203,15 +202,15 @@ char *create_header(int STATUS, int *header_max, hashmap *status_code, hashmap * free(key_num); free(header_key); - int add_on = 0; - if (post_data) { - add_on = strlen(post_data) + 3; - header = resize_array(header, header_max, header_index + add_on, sizeof(char)); + int add_on = (post_data ? strlen(post_data) : 0) + 3; + header = resize_array(header, header_max, header_index + add_on, sizeof(char)); - sprintf(header + sizeof(char) * header_index, "\n\n%s\0", post_data); - } + strcat(header, "\n\n"); + if (post_data) + strcat(header, post_data); *header_max = header_index + add_on; + return header; } @@ -292,7 +291,8 @@ hashmap *read_headers(char *header_str, void (*print_key)(void *), int *header_e // head head tag while ((int) header_str[past_lines + head_index] != 58) { - head_tag[head_index++] = header_str[past_lines + head_index]; + head_tag[head_index] = header_str[past_lines + head_index]; + head_index++; head_tag = resize_array(head_tag, head_max, head_index, sizeof(char)); head_tag[head_index] = '\0'; @@ -302,7 +302,8 @@ hashmap *read_headers(char *header_str, void (*print_key)(void *), int *header_e // read attr tag while ((int) header_str[past_lines + attr_index + 1] != 10) { - attr_tag[attr_index++] = header_str[past_lines + attr_index]; + attr_tag[attr_index] = header_str[past_lines + attr_index]; + attr_index++; attr_tag = resize_array(attr_tag, attr_max, attr_index, sizeof(char)); attr_tag[attr_index] = '\0'; @@ -326,8 +327,6 @@ socket_t *get_socket(char *HOST, char *PORT) { //int *sock_fd = malloc(sizeof(int)); // listen on sock_fd int sock; struct addrinfo hints, *servinfo, *p; - struct sockaddr_storage their_addr; // connector's address information - struct sigaction sa; int yes = 1; int status; diff --git a/request.h b/request.h index bb502e5..4ce1a3f 100644 --- a/request.h +++ b/request.h @@ -10,7 +10,7 @@ typedef struct SocketData { } socket_t; socket_t *get_socket(char *HOST, char *PORT); -int destroy_socket(socket_t *socket_data); +int destroy_socket(socket_t *); char *create_header(int STATUS, int *header_max, hashmap *status_code, hashmap *headers, int post_data_size, char *post_data); hashmap *read_headers(char *header_str, void (*print_key)(void *), int *header_end); diff --git a/server.c b/server.c index 2194831..b1ad261 100644 --- a/server.c +++ b/server.c @@ -12,8 +12,8 @@ void home_page(req_t req, res_t res) { char *name = req_query(req, "name"); printf("name is %s\n", name); - printf("%d %s\n", res.socket, res.__dirname); - res_sendFile(res, "home.html"); + res_matches(res, "NAME", name); + res_render(res, "home", "{{", "}}"); return; } diff --git a/teru.c b/teru.c index d11e47d..e88750a 100644 --- a/teru.c +++ b/teru.c @@ -17,22 +17,30 @@ #include #include "teru.h" +#include "typeinfer.h" #define MAXLINE 4096 typedef struct Listener { char *r_type; // "GET" / "POST" / etc. + // "TERU_PUBLIC" for app_use() + char *url_wrap; // for app_use() only + int add_num; // for calculating which values to look at first (PQ-esque) void (*handler)(req_t, res_t); // handles processing for the request - // made by user + // made by user /* more to come! */ } listen_t; -listen_t *new_listener(char *r_type, void (*handler)(req_t, res_t)) { +listen_t *new_listener(char *r_type, void (*handler)(req_t, res_t), int add_num, char *url_wrap) { listen_t *r = malloc(sizeof(listen_t)); r->r_type = r_type; + r->add_num = add_num; + + r->url_wrap = url_wrap; + r->handler = handler; return r; @@ -67,8 +75,10 @@ teru_t teru() { app->status_code = make__hashmap(0, NULL, destroyCharKey); batchInsert__hashmap(app->status_code, "request_code.data"); + app->curr_add_num = 0; app->routes = make__hashmap(1, print_listen_t, free_listen_t); - app->app_settings = make__hashmap(0, print_app_settings, destroyCharKey); + app->use_settings = make__hashmap(0, print_app_settings, destroyCharKey); + app->set_settings = make__hashmap(0, print_app_settings, destroyCharKey); app->server_active = 1; @@ -78,17 +88,34 @@ teru_t teru() { void destroy_teru(teru_t *app) { deepdestroy__hashmap(app->status_code); deepdestroy__hashmap(app->routes); - deepdestroy__hashmap(app->app_settings); + + deepdestroy__hashmap(app->use_settings); + deepdestroy__hashmap(app->set_settings); free(app); return; } +hashmap *inferer_map; + void *acceptor_function(void *app_ptr); +int fsck_directory(char *major_path, char *minor_fp); // check for if a file exists int app_listen(char *HOST, char *PORT, teru_t app) { + printf("\033[0;32m"); + printf("\nTeru Server starting on "); + printf("\033[0;37m"); + printf("%s", HOST); + printf("\033[0;32m"); + printf(":"); + printf("\033[0;37m"); + printf("%s", PORT); + printf("\033[0;32m"); + printf("...\n\n"); + socket_t *socket = get_socket(HOST, PORT); + inferer_map = infer_load(); if (listen(socket->sock, BACKLOG) == -1) { perror("listen error"); @@ -102,6 +129,7 @@ int app_listen(char *HOST, char *PORT, teru_t app) { pthread_create(&accept_thread, NULL, &acceptor_function, app.app_ptr); printf("server go vroom\n"); + printf("\033[0;37m"); while (getchar() != '0'); @@ -112,18 +140,20 @@ int app_listen(char *HOST, char *PORT, teru_t app) { destroy_socket(socket); destroy_teru(app.app_ptr); + deepdestroy__hashmap(inferer_map); + return 0; } /* ROUTE BUILDER -- POINTS TO GENERIC ROUTE BUILDER |__ the only reason for these functions is to build a slightly simpler interface on top of the library */ -int build_new_route(teru_t app, char *type, char *endpoint, void (*handler)(req_t, res_t)) { +int build_new_route(teru_t app, char *type, char *endpoint, void (*handler)(req_t, res_t), char *url_wrap) { // check that the route doesn't exist (assumes the type matches) - hashmap__response *routes = (hashmap__response *) get__hashmap(app.routes, endpoint); + hashmap__response *routes = (hashmap__response *) get__hashmap(app.routes, endpoint, ""); - for (int check_route = 0; routes && check_route < routes->payload__length; check_route) { - listen_t *r = (listen_t *) routes->payload[check_route]; + for (hashmap__response *rt_thru = routes; rt_thru; rt_thru = rt_thru->next) { + listen_t *r = (listen_t *) rt_thru->payload; if (strcmp(type, r->r_type) == 0) { // found match // replace current handler with new handler @@ -136,8 +166,12 @@ int build_new_route(teru_t app, char *type, char *endpoint, void (*handler)(req_ } } + if (routes) + free(routes); + // otherwise insert new listen_t - listen_t *r = new_listener(type, handler); + listen_t *r = new_listener(type, handler, app.app_ptr->curr_add_num, url_wrap); + app.app_ptr->curr_add_num += 1; insert__hashmap(app.routes, endpoint, r, "", compareCharKey, NULL); @@ -145,14 +179,25 @@ int build_new_route(teru_t app, char *type, char *endpoint, void (*handler)(req_ } int app_get(teru_t app, char *endpoint, void (*handler)(req_t, res_t)) { - return build_new_route(app, "GET", endpoint, handler); + return build_new_route(app, "GET", endpoint, handler, NULL); } int app_post(teru_t app, char *endpoint, void (*handler)(req_t, res_t)) { - return build_new_route(app, "POST", endpoint, handler); + return build_new_route(app, "POST", endpoint, handler, NULL); } /* TERU APP SETTINGS BUILDER */ +void get_public_file(req_t req, res_t res) { + // req for specific file name + // "/public/style.css" for example + res_sendFile(res, req.url); + + return; +} + +/* UPDATE: + if route starts with a "/", the it assumes setting of a public directory +*/ void app_use(teru_t app, char *route, ...) { // update route name to have a "set" at the beginning char *descript = NULL; @@ -167,12 +212,12 @@ void app_use(teru_t app, char *route, ...) { strcpy(descript, file_path); strcat(descript, sub_path); - // update route name to have a "use" at the beginning - char *new_route_name = malloc(sizeof(char) * (strlen(route) + 4)); - new_route_name[0] = 'u'; new_route_name[1] = 's'; new_route_name[2] = 'e'; new_route_name[3] = '\0'; - strcat(new_route_name, route); + insert__hashmap(app.use_settings, route, descript, "", compareCharKey, NULL); - insert__hashmap(app.app_settings, new_route_name, descript, "", compareCharKey, destroyCharKey); + if (route[0] == '/') { + // add if a new route + build_new_route(app, "TERU_PUBLIC", route, get_public_file, route); + } return; } @@ -202,7 +247,7 @@ void app_set(teru_t app, char *route, ...) { strcat(new_route_name, route); if (descript) - insert__hashmap(app.app_settings, new_route_name, descript, "", compareCharKey, destroyCharKey); + insert__hashmap(app.set_settings, new_route_name, descript, "", compareCharKey, destroyCharKey); return; } @@ -211,11 +256,8 @@ void app_set(teru_t app, char *route, ...) { options: -- SENDING DATA -t for simple text/plain -- expects a single char * (for data) in the overload - -h for text/html; charset=UTF-8 - -- expects a char * (for data) and an int (length of char *) in the overload - -hc for text/html; charset=? - -- expects a char * (for data) and an int (length of char *) - and a char * (for charset) in the overload + -i for infering the text type (simple function currently) + -- expects a char * (for FILENAME), a char * (for data) and an int (length of char *) -- ADDITIONAL HEADER OPTIONS: -o for adding another parameter -- expects a char * for the header name and a char * @@ -241,23 +283,12 @@ void data_send(int sock, hashmap *status_code, int status, char *options, ...) { data = va_arg(read_opts, char *); data_length = strlen(data) + 1; - } else if (options[check_option + 1] == 'h') { - // start with default values + } else if (options[check_option + 1] == 'i') { + char *file_data_name = va_arg(read_opts, char *); data = va_arg(read_opts, char *); - data_length = va_arg(read_opts, int); - char *html_option = va_arg(read_opts, char *); - - char *char_set = NULL; - content_type = malloc(sizeof(char) * 25); - strcpy(content_type, "text/html; charset="); + data_length = va_arg(read_opts, int) + 1; - if (options[check_option + 2] == 'c') { - char_set = va_arg(read_opts, char *); - - content_type = realloc(content_type, sizeof(char) * (20 + strlen(char_set))); - strcat(content_type, char_set); - } else - strcat(content_type, "UTF-8"); + char *content_type = content_type_infer(inferer_map, file_data_name, data, data_length); insert__hashmap(headers, "Content-Type", content_type, "", compareCharKey, NULL); } else if (options[check_option + 1] == 'o') { @@ -279,7 +310,7 @@ void data_send(int sock, hashmap *status_code, int status, char *options, ...) { char *main_head_msg = create_header(status, head_msg_len, status_code, headers, data_length, data); int bytes_sent = 0; - while ((bytes_sent = send(sock, main_head_msg, *head_msg_len - bytes_sent / sizeof(char), 0)) < sizeof(char) * *head_msg_len); + while ((bytes_sent = send(sock, main_head_msg + bytes_sent, *head_msg_len - bytes_sent / sizeof(char), 0)) < sizeof(char) * *head_msg_len); free(head_msg_len); if (lengthOf_data_length) free(lengthOf_data_length); @@ -309,7 +340,9 @@ void destroy_req_t(req_t *r) { } void destroy_res_t(res_t *r) { - /* NEEDS UPDATING */ + free(r); + + return; } /* HEADER PARSER This handles taking in a header like: @@ -490,11 +523,28 @@ req_t *read_header_helper(char *header_str, int header_length) { } char *req_query(req_t req, char *name) { - return (char *) get__hashmap(req.query_map, name); + return (char *) get__hashmap(req.query_map, name, ""); } char *req_body(req_t req, char *name) { - return (char *) get__hashmap(req.body_map, name); + return (char *) get__hashmap(req.body_map, name, ""); +} + +res_t *create_response_struct(int socket, hashmap *status_code, char *__dirname) { + res_t *res = malloc(sizeof(res_t)); + + res->res_self = res; + + res->render = 0; + res->fileName = NULL; res->matchStart = NULL; res->matchEnd = NULL; + res->render_matches = NULL; + + res->socket = socket; + res->status_code = status_code; + + res->__dirname = __dirname; + + return res; } typedef struct ConnectionHandle { @@ -580,6 +630,25 @@ int join_all_threads(ch_t *curr) { return 0; } +int match_hashmap_substrings(void *other_key, void *curr_key) { + char *io_key = (char *) other_key, *ic_key = (char *) curr_key; + + // compare using length of ic_key + int ic_key_len = strlen(ic_key), io_key_len = strlen(io_key); + int check_length = io_key_len < ic_key_len ? io_key_len : ic_key_len; + + int check_match; + for (check_match = 0; io_key[check_match] && check_match < check_length; check_match++) { + if (io_key[check_match] != ic_key[check_match]) + break; + } + + return check_match == check_length; +} + +int is_lower_hashmap_data(void *key1, void *key2) { + return ((listen_t *) key1)->add_num < ((listen_t *) key2)->add_num; +} /* LISTENER FUNCTIONS FOR SOCKET */ // Based on my trie-suggestor-app (https://github.com/charlie-map/trie-suggestorC/blob/main/server.c) and // Beej Hall websockets (http://beej.us/guide/bgnet/html/) @@ -604,9 +673,14 @@ void *connection(void *app_ptr) { // otherwise parse header data req_t *new_request = read_header_helper(buffer, recv_res / sizeof(char)); + int request_url_len = strlen(new_request->url); // using the new_request, acceess the app to see how to handle it: - hashmap__response *handler = get__hashmap(app.routes, new_request->url); + hashmap__response *handler; + if (new_request->url[request_url_len - 1] == '/') // is not a file -- don't look for public directories + handler = get__hashmap(app.routes, new_request->url, "w", is_lower_hashmap_data); + else + handler = get__hashmap(app.routes, new_request->url, "iw", match_hashmap_substrings, is_lower_hashmap_data); if (!handler) { /* ERROR HANDLE */ char *err_msg = malloc(sizeof(char) * (10 + strlen(new_request->type) + strlen(new_request->url))); sprintf(err_msg, "Cannot %s %s\n", new_request->type, new_request->url); @@ -621,13 +695,24 @@ void *connection(void *app_ptr) { // there might be multiple (aka "/number" is a POST and a GET) // need to find the one that matches the request: - int find_handle; - for (find_handle = 0; find_handle < handler->payload__length; find_handle++) { - if (strcmp(((listen_t *) handler->payload[find_handle])->r_type, new_request->type) == 0) + hashmap__response *reader; + int is_public = 0; + for (reader = handler; reader; reader = reader->next) { // nice + // case: if r_type == "TERU_PUBLIC", check system directory to + // see if there is a file in that folder that corresponds with + // the name of the request + if (!(new_request->url[request_url_len - 1] == '/') && strcmp(((listen_t *) reader->payload)->r_type, "TERU_PUBLIC") == 0) { + if (fsck_directory((char *) get__hashmap(app.use_settings, ((listen_t *) reader->payload)->url_wrap, new_request->url), new_request->url)) { + is_public = 1; + break; + } + } + + if (strcmp(((listen_t *) reader->payload)->r_type, new_request->type) == 0) break; } - if (find_handle == handler->payload__length) { /* not found -- ERROR HANDLE */ + if (!reader) { /* not found -- ERROR HANDLE */ char *err_msg = malloc(sizeof(char) * (10 + strlen(new_request->type) + strlen(new_request->url))); sprintf(err_msg, "Cannot %s %s\n", new_request->type, new_request->url); data_send(new_fd, app.status_code, 404, "-t", err_msg); @@ -641,12 +726,13 @@ void *connection(void *app_ptr) { // find handle will now have the handler within it // can call the handler with the data - res_t res = { .socket = new_fd, .status_code = app.status_code, - .__dirname = (char *) get__hashmap(app.app_settings, "setviews") }; - ((listen_t *) handler->payload[find_handle])->handler(*new_request, res); - + res_t *res = create_response_struct(new_fd, app.status_code, + (char *) (is_public ? get__hashmap(app.use_settings, ((listen_t *) reader->payload)->url_wrap, "") : get__hashmap(app.set_settings, "setviews", ""))); + ((listen_t *) reader->payload)->handler(*new_request, *res); + destroy_req_t(new_request); - free(handler); + destroy_res_t(res); + clear__hashmap__response(handler); } // if the close occurs due to thread_status, send an error page @@ -701,15 +787,199 @@ void *acceptor_function(void *app_ptr) { } +typedef struct RenderSchema { + int *max_render_match_check, render_match_check_index, + render_start_matcher_index, render_end_matcher_index; + + int render_start_matcher_length, render_end_matcher_length; + + int *max_render_match_buffer, render_match_buffer_index; + char *render_match_buffer; + + char *render_match_check; + char *render_start_matcher, *render_end_matcher; + + hashmap *res_render_matches; +} render_scheme_t; +render_scheme_t *create_render_scheme(res_t *res) { + render_scheme_t *new_scheme = malloc(sizeof(render_scheme_t)); + + new_scheme->max_render_match_check = malloc(sizeof(int)); + *new_scheme->max_render_match_check = 8; + new_scheme->render_match_check_index = 0; + new_scheme->render_start_matcher_index = 0; + new_scheme->render_end_matcher_index = 0; + + new_scheme->max_render_match_buffer = malloc(sizeof(int)); + *new_scheme->max_render_match_buffer = 8; + new_scheme->render_match_buffer_index = 0; + + new_scheme->render_start_matcher_length = strlen(res->matchStart); + new_scheme->render_end_matcher_length = strlen(res->matchEnd); + + new_scheme->res_render_matches = res->render_matches; + + new_scheme->render_match_buffer = malloc(sizeof(char) * 8); + new_scheme->render_match_check = malloc(sizeof(char) * 8); + new_scheme->render_start_matcher = res->matchStart; + new_scheme->render_end_matcher = res->matchEnd; + + return new_scheme; +} + +int scheme_reset(render_scheme_t *r_scheme) { + r_scheme->render_match_check_index = 0; + r_scheme->render_start_matcher_index = 0; + r_scheme->render_end_matcher_index = 0; + + r_scheme->render_match_check[0] = '\0'; + + return 0; +} + +int destroy_render_scheme(render_scheme_t *r_scheme) { + free(r_scheme->max_render_match_check); + free(r_scheme->max_render_match_buffer); + + free(r_scheme->render_match_check); + free(r_scheme->render_match_buffer); + + free(r_scheme); + + return 0; +} + +int check_renders(render_scheme_t *r_scheme, char *full_line, char **full_data, + int *full_data_max, int full_data_index) { + + int has_found_start_match; // checking if the matcher indices have changed + int has_index_change; // for checking if data in buffer needs + // to be written to full_data + // read through the line and check against render matches and see if we should start reading a key + for (int read_full_line = 0; full_line[read_full_line]; read_full_line++) { + has_index_change = 0; + has_found_start_match = 0; + // casing: + // no nothing, look for render_start_matcher + if (r_scheme->render_start_matcher_index < r_scheme->render_start_matcher_length) { + // check for if line matches renderer: + if (full_line[read_full_line] == r_scheme->render_start_matcher[r_scheme->render_start_matcher_index]) { + has_found_start_match = 1; + r_scheme->render_start_matcher_index++; + + if (r_scheme->render_start_matcher_index == r_scheme->render_start_matcher_length - 1) + r_scheme->render_match_buffer_index = 0; + } else { + has_index_change = r_scheme->render_start_matcher_index > 0; + r_scheme->render_start_matcher_index = 0; + } + } + if (r_scheme->render_start_matcher_index == r_scheme->render_end_matcher_length && + r_scheme->render_end_matcher_index < r_scheme->render_end_matcher_length) { + if (full_line[read_full_line] == r_scheme->render_end_matcher[r_scheme->render_end_matcher_index]) { + has_found_start_match = 1; + r_scheme->render_end_matcher_index++; + } else { + has_index_change = r_scheme->render_end_matcher_index > 0; + r_scheme->render_end_matcher_index = 0; + } + } + + // check if a character should be briefly stored in buffer to see + // if the character is part of the start or end matcher + if (has_found_start_match && (r_scheme->render_start_matcher_index < r_scheme->render_start_matcher_length || + r_scheme->render_end_matcher_index < r_scheme->render_end_matcher_length)) { + + // copy character into buffer + r_scheme->render_match_buffer[r_scheme->render_match_buffer_index] = full_line[read_full_line]; + r_scheme->render_match_buffer_index++; + + r_scheme->render_match_buffer = resize_array(r_scheme->render_match_buffer, r_scheme->max_render_match_buffer, r_scheme->render_match_buffer_index, sizeof(char)); + r_scheme->render_match_buffer[r_scheme->render_match_buffer_index] = '\0'; + + continue; + } + + if (has_index_change) { + // read render_match_buffer into full_data + *full_data = resize_array(*full_data, full_data_max, full_data_index + r_scheme->render_match_buffer_index, sizeof(char)); + + strcat(*full_data, r_scheme->render_match_buffer); + r_scheme->render_match_buffer_index = 0; + } + + // have render_start_matcher, start reading directly into render_match_check + if (r_scheme->render_start_matcher_index == r_scheme->render_start_matcher_length && + r_scheme->render_end_matcher_index != r_scheme->render_end_matcher_length) { + + r_scheme->render_match_check[r_scheme->render_match_check_index] = full_line[read_full_line]; + r_scheme->render_match_check_index++; + + r_scheme->render_match_check = resize_array(r_scheme->render_match_check, r_scheme->max_render_match_check, r_scheme->render_match_check_index, sizeof(char)); + r_scheme->render_match_check[r_scheme->render_match_check_index] = '\0'; + + continue; + } + // find render_end_matcher: + // close connection + if (r_scheme->render_start_matcher_index == r_scheme->render_start_matcher_length && + r_scheme->render_end_matcher_index == r_scheme->render_end_matcher_length) { + // see what should be in the place of this found word: + char *replacer = get__hashmap(r_scheme->res_render_matches, r_scheme->render_match_check, ""); + + if (!replacer) { + scheme_reset(r_scheme); + continue; + } + + int replacer_len = strlen(replacer); + + *full_data = resize_array(*full_data, full_data_max, full_data_index + replacer_len, sizeof(char)); + + strcat(*full_data, replacer); + + // reset data + scheme_reset(r_scheme); + + full_data_index += replacer_len; + } else { + // add to full_data + (*full_data)[full_data_index] = full_line[read_full_line]; + full_data_index++; + + (*full_data)[full_data_index] = '\0'; + } + } + + return full_data_index; +} /* RESULT SENDERS */ int res_sendFile(res_t res, char *name) { + res_t *res_pt = res.res_self; + + if (res_pt->render && (!res_pt->matchStart || !res_pt->matchEnd)) { + printf("\033[0;31m"); + printf("\n** Render match schema failed -- missing: %s%s%s **\n", + res_pt->matchStart ? "" : "start match ", !res_pt->matchStart && !res_pt->matchEnd ? + "and " : "", res_pt->matchEnd ? "" : "end match"); + printf("\033[0;37m"); + + char *err_msg = malloc(sizeof(char) * (27)); + sprintf(err_msg, "Render match schema failed"); + data_send(res.socket, res.status_code, 500, "-t", err_msg); + free(err_msg); + + return 0; + } + // load full file path - int full_fpath_len = strlen(res.__dirname ? res.__dirname : getenv("PWD")) + strlen(name) + 1 + (!res.__dirname ? 1 : 0); + int full_fpath_len = strlen(res.__dirname ? res.__dirname : getenv("PWD")) + strlen(name) + + (name[0] == '/' ? -1 : 0) + 1 + (!res.__dirname ? 1 : 0); char *full_fpath = malloc(sizeof(char) * full_fpath_len); strcpy(full_fpath, res.__dirname ? res.__dirname : getenv("PWD")); if (!res.__dirname) strcat(full_fpath, "/"); - strcat(full_fpath, name); + strcat(full_fpath, name + (name[0] == '/' ? 1 : 0)); FILE *f_pt = fopen(full_fpath, "r"); @@ -727,8 +997,6 @@ int res_sendFile(res_t res, char *name) { return 1; } - free(full_fpath); - // if file opens, read from file to create a new request size_t read_line_size = sizeof(char) * 64; char *read_line = malloc(read_line_size); @@ -738,20 +1006,32 @@ int res_sendFile(res_t res, char *name) { char *full_data = malloc(sizeof(char) * *full_data_max); full_data[0] = '\0'; + /* RENDER SCHEMES */ + render_scheme_t *r_scheme = res_pt->render ? create_render_scheme(res_pt) : NULL; + int curr_line_len = 0; while ((curr_line_len = getline(&read_line, &read_line_size, f_pt)) != -1) { - full_data_index += curr_line_len; - full_data = resize_array(full_data, full_data_max, full_data_index + 1, sizeof(char)); + full_data = resize_array(full_data, full_data_max, full_data_index + curr_line_len + 1, sizeof(char)); + + // check for if any rendering calculations should occur + if (res_pt->render) { + full_data_index = check_renders(r_scheme, read_line, &full_data, full_data_max, full_data_index); + } else { + strcat(full_data, read_line); + } - strcat(full_data, read_line); full_data[full_data_index] = '\0'; } + if (r_scheme) + destroy_render_scheme(r_scheme); + free(read_line); fclose(f_pt); - data_send(res.socket, res.status_code, 200, "-h", full_data, full_data_index); + data_send(res.socket, res.status_code, 200, "-i", full_fpath, full_data, full_data_index); + free(full_fpath); free(full_data_max); free(full_data); return 0; @@ -759,4 +1039,58 @@ int res_sendFile(res_t res, char *name) { int res_end(res_t res, char *data) { data_send(res.socket, res.status_code, 200, "-t", data); + + return 0; +} + +int res_matches(res_t res, char *match, char *replacer) { + res_t *res_pt = res.res_self; + + if (!res_pt->render_matches) + res_pt->render_matches = make__hashmap(0, NULL, NULL); + + insert__hashmap(res_pt->render_matches, match, replacer, "", compareCharKey, NULL); + + return 0; +} + +int res_render(res_t res, char *name, char *match_start, char *match_end) { + res_t *res_pt = res.res_self; + res_pt->render = 1; + + // open file: + char *full_file_name = malloc(sizeof(char) * (strlen(name) + ((res_pt->fileName && res_pt->fileName[0] == 'f') ? 0 : 6))); + strcpy(full_file_name, name); + printf("%d %s\n", strlen(name) + ((res_pt->fileName && res_pt->fileName[0] == 'f') ? 0 : 5), full_file_name); + if (!res_pt->fileName) + strcat(full_file_name, ".html"); + + res_pt->matchStart = match_start; + res_pt->matchEnd = match_end; + + int response = res_sendFile(res, full_file_name); + + free(full_file_name); + deepdestroy__hashmap(res_pt->render_matches); + + return response; +} + +int fsck_directory(char *major_path, char *minor_fp) { + // combine the char *: + char *full_path = malloc(sizeof(char) * (strlen(major_path) + strlen(minor_fp))); + + strcpy(full_path, major_path); + strcat(full_path, minor_fp + sizeof(char)); // remove first "/" + + FILE *f_ck = fopen(full_path, "r"); + + int have_file = f_ck ? 1 : 0; + + if (f_ck) + fclose(f_ck); + + free(full_path); + + return have_file; } \ No newline at end of file diff --git a/teru.h b/teru.h index a83ffe0..2abb4f5 100644 --- a/teru.h +++ b/teru.h @@ -16,10 +16,11 @@ typedef struct Teru { hashmap *status_code; // holds the code -> textual phrase pair // routes for different request types (currently on GET and POST) - hashmap *routes; + int curr_add_num; + hashmap *routes; // also for any public routes... /* currently only hashes string to string */ - hashmap *app_settings; + hashmap *use_settings, *set_settings; /* `use`d parameters -- with concatenated string ("use") `set` parameters -- with concatenated string ("set") @@ -45,12 +46,26 @@ typedef struct HeaderMap { hashmap *query_map; hashmap *body_map; + } req_t; typedef struct ResStruct { + struct ResStruct *res_self; + int socket; hashmap *status_code; char *__dirname; + + + int render; // to check if rendering the document should occur + // (instead of just reading) -- INTERNAL + char *fileName; // empty for default render, + // "f" for full file name render + // more to come! + // set each one using res.matchStart = ""; and res.matchEnd = ""; + // to use the render functionality + char *matchStart, *matchEnd; + hashmap *render_matches; } res_t; teru_t teru(); @@ -64,6 +79,12 @@ int app_post(teru_t app, char *endpoint, void (*handler)(req_t, res_t)); int res_sendFile(res_t res, char *name); int res_end(res_t res, char *data); +int res_matches(res_t res, char *match, char *replacer); +/* assumes a .html file (so inputting "home" will look for "home.html") + However, can also use a full path by setting res.fileName = 'f'; +*/ +int res_render(res_t res, char *name, char *match_start, char *match_end); + char *req_query(req_t req, char *name); char *req_body(req_t req, char *name); diff --git a/typeinfer.c b/typeinfer.c new file mode 100644 index 0000000..df6a6fa --- /dev/null +++ b/typeinfer.c @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "typeinfer.h" + +hashmap *infer_load() { + hashmap *load_map = make__hashmap(0, NULL, destroyCharKey); + + char *text_plain = malloc(sizeof(char) * 11); strcpy(text_plain, "text/plain"); + char *text_html = malloc(sizeof(char) * 10); strcpy(text_html, "text/html"); + char *text_css = malloc(sizeof(char) * 9); strcpy(text_css, "text/css"); + + insert__hashmap(load_map, "txt", text_plain, "", compareCharKey, NULL); + insert__hashmap(load_map, "html", text_html, "", compareCharKey, NULL); + insert__hashmap(load_map, "css", text_css, "", compareCharKey, NULL); + + return load_map; +} + +// reads final values from filename (after find_p) and looks in load_map +char *end_type_script_check(hashmap *load_map, char *filename, int find_p) { + char *sub_filename = malloc(sizeof(char) * 8); + int max_sub_filename = 8, sub_filename_index = 0; + + for (int read_file_name = find_p + 1; filename[read_file_name]; read_file_name++) { + sub_filename[sub_filename_index] = filename[read_file_name]; + sub_filename_index++; + + if (sub_filename_index + 1 == max_sub_filename) { + max_sub_filename *= 2; + + sub_filename = realloc(sub_filename, sizeof(char) * max_sub_filename); + } + } + + sub_filename[sub_filename_index] = '\0'; + char *res = (char *) get__hashmap(load_map, sub_filename, ""); + + free(sub_filename); + return res ? res : "text/html"; // default handle +} + +char *content_type_infer(hashmap *load_map, char *filename, char *data, int data_length) { + // first check the file for an ending within the system + // find last "." and read data after that + // if no ".", try inferring from the data within char *data + + int find_p; + for (find_p = strlen(filename) - 1; find_p >= 0; find_p--) + if (filename[find_p] == '.') + break; + + if (find_p >= 0) + return end_type_script_check(load_map, filename, find_p); + + return "text/plain"; // no value +} \ No newline at end of file diff --git a/typeinfer.h b/typeinfer.h new file mode 100644 index 0000000..939f85c --- /dev/null +++ b/typeinfer.h @@ -0,0 +1,9 @@ +#ifndef __TYPEINFER_L__ +#define __TYPEINFER_L__ + +#include "hashmap.h" + +hashmap *infer_load(); +char *content_type_infer(hashmap *load_map, char *filename, char *data, int data_length); + +#endif \ No newline at end of file diff --git a/views/home.html b/views/home.html index 69d900f..f92ab1e 100644 --- a/views/home.html +++ b/views/home.html @@ -1,9 +1,11 @@ + + Example Page -

Hey there!!!

+

Hey there {{NAME}}!

\ No newline at end of file