From e2ca5eb8dde31ea31b7fa1014d087829e4146bac Mon Sep 17 00:00:00 2001 From: Max Christian Pohle Date: Tue, 16 Nov 2021 03:35:13 +0100 Subject: Basic functionality is finally there We can: * skip HTTP status header * read the HTTP headers * find a boundary of multipart form data and if there was a boundary * parse further 'chunks', separated by this boundary, each with a HTTP header All of that was tested with text fields and binary data. This server implementation is insecure. Please do not use it for anything connected to the internet. It is simple though and can serve local daemons. TODO: callback to a parser function. --- Makefile | 12 +++ main.c | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 290 insertions(+), 58 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1f8a372 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ + +test: main + ./main + +main: clean + +clean: + rm -f ./main + +%: + $(CC) -pthread -g -o $@ $@.c + diff --git a/main.c b/main.c index 6613df0..69ac01e 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -16,10 +18,7 @@ #include - -void send_answer(void * new_socket) { - FILE * f = fdopen((size_t) new_socket, "w"); - puts("> sending answer..."); +void send_answer(FILE * f) { fputs("HTTP/1.0 200 OK\n", f); fputs("content-type: text/html\n\n", f); fputs("", f); @@ -34,13 +33,256 @@ void send_answer(void * new_socket) { fputs("", f); fputs("", f); fputs("", f); - fputs("\n\0", f); - fflush(f); + fputs("", f); +} + +void * next_customer(size_t new_socket) { + + char * output_buffer = NULL; + size_t output_buffer_length = 0; + FILE * output = open_memstream(&output_buffer, &output_buffer_length); + + const int read_buffer_length = 10; + char read_buffer[read_buffer_length]; + + puts("\n\n########################################## Content Reader"); + FILE * q = fdopen((size_t) new_socket, "r"); + printf("fd : %p\n", q); + + for(size_t size = -2 ; ; size = fread(read_buffer, read_buffer_length, 1, q)) { + if(-2 == size || (-1 == size && EWOULDBLOCK == errno)) { + usleep(1000); + continue; + } else if (1 != size) { // I expect one nmemb of data + break; + } + + fwrite(read_buffer, read_buffer_length, 1, output); + fwrite(read_buffer, read_buffer_length, 1, stdout); + } + fflush(output); + + + puts("\n\n########################################## Content Parser"); + typedef struct { + const char * name; + const char * value; + } NameValue; + + NameValue namevalue[] = { + {"Content-Type", NULL}, + {"Content-Length", NULL} + }; + + // token parser... + const char * boundary = NULL; + size_t boundary_length = 0; + char * search = NULL; + char * key = output_buffer; + + char * saveptr = NULL; + char * content_start = NULL; + char * content_end = NULL; + + while(1) { + + printf("NEXT ITERATION with search '%s'\n", search); + + for(char * + token = strtok_r(NULL == saveptr ? output_buffer : saveptr, NULL == search ? "\n\r" : search, &saveptr); + token != NULL; + token = strtok_r(NULL, search, &saveptr)) + { + + if(!search) { // first round: HTTP status code + puts("FIRST ITERATION"); + printf("HTTP sanity check: %s\n", token); + search = ":"; + continue; + } + + if(!key) { + search = "\r\n"; // we found a key and expect the value to be between here and EOL + key = &token[strspn(token, search)]; // also remove leading spaces + + if(0 == strncmp(token, "\n\r\n", 3) + || 0 == strncmp(token, "\n\n", 2)) { + search = "\r\n"; + break; + } + } else { // value + + token += strspn(token, ": "); + const char * value = token; + + const char pattern[] = "multipart/form-data; boundary="; + if(!boundary && strstr(key, "Content-Type") && strncmp(value, pattern, sizeof(pattern))) { + boundary = &value[sizeof(pattern) + strspn(&value[sizeof(pattern)], "-")]; + boundary_length = strlen(boundary); + printf("> [!] boundary detected '%s' ", boundary); + } + + for(int i=0; i < sizeof(namevalue) / sizeof(NameValue); i++) { + // printf("next name: %s\n", namevalue[i].name); + if(NULL == namevalue[i].value && 0 == strcasecmp(namevalue[i].name, key)) { + namevalue[i].value = value; + break; + } + } + + printf("> [%ld] \"%s\" :: \"%s\"\n", key - output_buffer, key, value); fflush(stdout); + + search = ":"; + key = NULL; + } + } + + if(!key) + break; + // printf("We are now here: %ld\n", key - output_buffer); + printf("We are now here: %s\n", key); + + // jump over the content... + content_start = key; + while((key = memchr(key, '\n', output_buffer_length - (key - output_buffer)))) { + if(!key) { + warnx("out at %p\n", key); + break; + } else { + key++; + } + + if(key[0] == '-') { + if(0 == strncmp(&key[strspn(key, "-")], boundary, boundary_length)) { + puts("GEIL1"); + + if(key[-1] == '\n') key--; + if(key[-1] == '\r') key--; + + content_end = key; + key += strspn(key, "\r\n-"); + key += boundary_length; + key += strspn(key, "\r\n"); + saveptr = key; + key = NULL; + search = ":"; + break; + } + } + } + + printf("content is %p - %p = %ld in size\n", content_end, content_start, content_end - content_start); + if(content_end - content_start > 1000) { + FILE * f_w = fopen("/tmp/test.gif", "w"); + fwrite(content_start, content_end - content_start, 1, f_w); + fclose(f_w); + puts("FILE WRITTEN"); + } + + // printf("We have gone to %ld\n", key - output_buffer); fflush(stdout); + if(key && key[0] == '\0') // final boundary reached? + break; + + // usleep(100000); + } + + + puts("> sending answer..."); + FILE * f_w = fdopen((size_t) new_socket, "w"); + send_answer(f_w); + + fclose(f_w); + fclose(q); puts("> answer sent."); - fclose(f); + + printf("still in knowledge of out boundary: %s\n", namevalue[0].value); + + return NULL; } + +int serve(int server_fd) +{ + struct sockaddr_in address; + socklen_t address_len = sizeof(address); + warnx("waiting for connections on server file descriptor %d", server_fd); + + size_t new_socket = -1; + while(-1 != + (new_socket = accept(server_fd, + (struct sockaddr*) &address, + &address_len))) + { + warnx("> Client %ld is connected via port %d", new_socket, address.sin_port); + + // set non blocking mode... + fcntl((size_t) new_socket, F_SETFL, + fcntl((size_t) new_socket, F_GETFL) | O_NONBLOCK); + + next_customer(new_socket); + + /* + if(fork()) { + close(new_socket); + } else { + close(server_fd); // give the server free + next_customer(new_socket); + shutdown(new_socket, SHUT_RDWR); + exit(0); + } + */ + + // pthread_t thread_id; + // pthread_create(&thread_id, NULL, next_customer, (void*) new_socket); + // pthread_join(thread_id, NULL); + } + + err(errno, "error serving"); +} + +#define PORT 8080 +int main(int argc, char const *argv[]) +{ + int server_fd = -1, opt = 1; + + struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(PORT) + }; + + // I <3 C + 0 == (server_fd = socket(AF_INET, SOCK_STREAM, 0)) + ? err(errno, NULL) + : setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) + ? err(errno, "setsockopt failed on socket with fileno %d", server_fd) + : bind(server_fd, (struct sockaddr*) &address, sizeof(address)) + ? err(errno, NULL) + : listen(server_fd, SOMAXCONN) + ? err(errno, NULL) + : serve(server_fd) + ? err(errno, NULL) + : exit(EXIT_SUCCESS) + ; + return EXIT_FAILURE; +} + +// void * next_customer_old(int new_socket) +// { +// char *hello = "Hello from server"; +// char buffer[1024] = {0}; +// int valread = read(new_socket, buffer, 1024); +// printf("%s %d\n", buffer, valread); +// +// send(new_socket, hello, strlen(hello), 0); +// +// printf("Hello message sent\n"); +// return NULL; +// } + +// IDEA ... +// // #define FOREACH_HEADER(HEADER) \ // HEADER("Content-Type: multipart/form-data; boundary=") // #define GENERATE_HEADERS(H) sizeof(H), H @@ -53,10 +295,37 @@ void send_answer(void * new_socket) { // const length_value headers[] = { // FOREACH_HEADER(GENERATE_HEADERS) // }; +// +// READ WITH recv ... + /* + for(size_t size = -1 ; ; (size = recv(new_socket, read_buffer, read_buffer_length, 0))) { + if(-1 == size) { + usleep(10000); + printf("size: %ld\n", size); + if(EWOULDBLOCK == errno){ + continue; + } else if (size == 0 || size < read_buffer_length) { + break; + } + } else { + read_buffer[size] = '\0'; + + fwrite(read_buffer, size, 1, output); + fwrite(read_buffer, size, 1, stdout); + + // if(output_buffer_length > 4 + // && (strspn(&output_buffer[output_buffer_length - 2], "\n") == 2 + // || strspn(&output_buffer[output_buffer_length - 4], "\r\n") == 4)) + // break; + } + }*/ + + +// fully working implementation... +/* void * next_customer(void * new_socket) { - if (new_socket == 0) return NULL; const int MAX_HEADER_LINE_LENGTH = 1024; const int MAX_BOUNDARY_LENGTH = 64; @@ -151,56 +420,7 @@ void * next_customer(void * new_socket) { send_answer(new_socket); puts("> answer sent."); - - puts("> file closed."); - //close((size_t) new_socket); - // pthread_exit(EXIT_SUCCESS); return NULL; } - -void serve(int server_fd) -{ - struct sockaddr_in address; - int addrlen = sizeof(address); - warn("waiting for connections on %d", server_fd); - - for(size_t new_socket = 0 - ; 1 ; new_socket=accept(server_fd, (struct sockaddr*) &address, (socklen_t*) &addrlen)) - { - warn("next: %ld\n", new_socket); - // set non blocking mode... - fcntl((size_t) new_socket, F_SETFL, - fcntl((size_t) new_socket, F_GETFL) | O_NONBLOCK); - - next_customer((void*) new_socket); - // pthread_t thread_id; - // pthread_create(&thread_id, NULL, next_customer, (void*) new_socket); - // pthread_join(thread_id, NULL); - } -} - -#define PORT 8080 -int main(int argc, char const *argv[]) -{ - int server_fd; - int opt = 1; - - struct sockaddr_in address; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(PORT); - - 0 == (server_fd = socket(AF_INET, SOCK_STREAM, 0)) - ? err(errno, NULL) - : setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) - ? err(errno, "setsockopt failed on socket with fileno %d", server_fd) - : bind(server_fd, (struct sockaddr*) &address, sizeof(address)) - ? err(errno, NULL) - : listen(server_fd, SOMAXCONN) - ? err(errno, NULL) - : serve(server_fd); - - return 0; -} - +*/ // vim: shiftwidth=2 tabstop=2 number -- cgit v1.2.3