From c9fd3111a73f3fa66b99b1819ecb8e54688b87d0 Mon Sep 17 00:00:00 2001 From: Max Christian Pohle Date: Sat, 20 Nov 2021 02:20:05 +0100 Subject: The server actually serves --- Makefile | 7 +- main.c | 459 ++++++++++++++++++--------------------------------------------- test.sh | 3 + 3 files changed, 139 insertions(+), 330 deletions(-) create mode 100644 test.sh diff --git a/Makefile b/Makefile index 1f8a372..bfb3df5 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,8 @@ +BUILD := debug + +cflags.debug := -Wall -o0 -g -DDEBUG=1 +cflags.release := -os -s +CFLAGS := ${cflags.${BUILD}} test: main ./main @@ -8,5 +13,5 @@ clean: rm -f ./main %: - $(CC) -pthread -g -o $@ $@.c + $(CC) $(CFLAGS) -o $@ $@.c diff --git a/main.c b/main.c index 18b9900..2a01a4e 100644 --- a/main.c +++ b/main.c @@ -1,139 +1,129 @@ -#include -#include -#include -#include -#include +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * + * OHMYCGI * + * 2021 by Max Christian Pohle * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +// {{{ INCLUDES #include +#include #include -#include -#include -#include -#include #include -#include - +#include #include #include - -#include +#include +#include +#include +#include +// #include // maybe later +// }}} +// {{{ MACROS +#define EWOULDBLOCK_DELAY 100 +#define READ_BUFFER_LENGTH 9000 // jumboframe? +#define POST_DATA_MAX_LENGTH 18000 +#define DEBUG_SLEEP_TIME 50000 + +#ifndef DEBUG +#define DEBUG(X, ...) // (X, ...) +#else #include - - -void send_answer(FILE * f) { - fputs("HTTP/1.0 200 OK\n", f); - fputs("content-type: text/html\n\n", f); - fputs("", f); - fputs("", f); - fputs("test", f); - fputs("", f); - fputs("", f); - fputs("
", f); - fputs("
\n", f); - fputs("", f); - fputs("", f); - fputs("
", f); - fputs("", f); +static inline int verbose(const char * format, ...) { + va_list va; va_start(va, format); usleep(DEBUG_SLEEP_TIME); return vprintf(format, va); } +#undef DEBUG +#define DEBUG verbose +#endif +// }}} +typedef struct { + int newline_length; // lenght of one newline in bytes (\n has 1, CR/LF has 2) + char * method; + char * boundary; + size_t boundary_size; + char * content_type; + char * content_disposition; + char * url; +} Http_Header; -#define EWOULDBLOCK_DELAY 1000 -#define READ_BUFFER_LENGTH 10 -#define DEBUG_SLEEP_TIME 50000 +void send_answer(int fd_socket, Http_Header * http_header) { + FILE * f = fdopen((size_t) fd_socket, "w"); + fputs("HTTP/1.0 200 OK\n", f); + fputs("content-type: text/plain\n\n", f); + fflush(f); + + int file = open(&http_header->url[1], O_RDONLY); + if(0 < file) { + struct stat stat; + fstat(file, &stat); + sendfile (fileno(f), file, NULL, stat.st_size); + } else { + fprintf(f, "could not open file \"%s\"\n", &http_header->url[1]); + } -static inline int verbose(const char * format, ...) { - va_list va; va_start(va, format); usleep(DEBUG_SLEEP_TIME); return vprintf(format, va); + fclose(f); + + // fputs("", f); + // fputs("", f); + // fputs("test", f); + // fputs("", f); + // fputs("", f); + // fputs("
", f); + // fputs("
\n", f); + // fputs("", f); + // fputs("", f); + // fputs("
", f); + // fputs("", f); } - -void read_everything(FILE * f_r, FILE * output) { +int read_everything(FILE * f_r, FILE * output) { const int read_buffer_length = READ_BUFFER_LENGTH; char read_buffer[read_buffer_length]; - for(size_t size = -2 ; ; size = fread(read_buffer, read_buffer_length, 1, f_r)) { + for(size_t size = -2 ; ; size = fread(read_buffer, 1, read_buffer_length, f_r)) { if(-2 == size || (-1 == size && EWOULDBLOCK == errno)) { usleep(EWOULDBLOCK_DELAY); // try again a little later continue; } fwrite(read_buffer, read_buffer_length, 1, output); - fwrite(read_buffer, read_buffer_length, 1, stdout); - - if (1 != size) { // I expect one nmemb of data - break; - } - } - fflush(stdout); - fflush(output); -} -void header_next(const char * key, const char * value) { - // this sample function will be removed later - typedef struct { - const char * name; - const char * value; - } NameValue; + if (read_buffer_length > POST_DATA_MAX_LENGTH) + return EXIT_FAILURE; - NameValue namevalue[] = { - {"Content-Type", NULL}, - {"Content-Length", NULL} - }; - 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; + if (size < read_buffer_length) { // I expect one nmemb of data break; } } + fflush(output); + return EXIT_SUCCESS; } -typedef struct { - int newline_length; // lenght of one newline in bytes (\n has 1, CR/LF has 2) - char * method; - char * boundary; - size_t boundary_size; - char * content_type; - char * content_disposition; -} Http_Header; - void * next_customer(size_t new_socket) { - FILE * f_r = fdopen((size_t) new_socket, "rw"); + FILE * f_r = fdopen((size_t) new_socket, "r"); char * output_buffer = NULL; size_t output_buffer_length = 0; FILE * output = open_memstream(&output_buffer, &output_buffer_length); - verbose("\n\n########################################## Content Reader [%d]\n", f_r); + DEBUG("\n\n########################################## Content Reader [%d]\n", f_r); read_everything(f_r, output); shutdown(new_socket, SHUT_RD); // shutdown the reading half of the connection - verbose("\n\n########################################## Content Parser\n"); - - // token parser... - char * key = output_buffer; - - char * saveptr = NULL; + DEBUG("\n\n########################################## Content Parser\n"); char * start = output_buffer; char * end = NULL; char * search = "\r\n"; - Http_Header http_header; - http_header.boundary_size = 0; - http_header.newline_length = 1; - http_header.method = NULL; - http_header.boundary = NULL; - http_header.content_disposition = NULL; - http_header.content_type = NULL; + Http_Header http_header = {0}; char * name = NULL; - // while(NULL != (end = strpbrk(start, search))) { while(NULL != (end = strpbrk(start, search))) { - // verbose("\033[31m[%ld|%ld|%ld]\033[0m\n", start - output_buffer, end - start, end - output_buffer); size_t matchlen = strspn(end, search); switch(end[0]) { case ':': - end[0] = '\0'; + end[0] = '\0'; // {{{ remember header 'names' and search for the value end++; name = start; @@ -142,52 +132,56 @@ void * next_customer(size_t new_socket) { search = "\r\n;"; } else { search = "\r\n"; - } + } // }}} break; case ';': + start += strspn(start, "; "); // {{{ find the form-data boundary in the main header + + const char s_multipart_form_data[] = "boundary="; + if(NULL == http_header.boundary && 0 < strcasecmp(start, s_multipart_form_data)) { - start += strspn(start, "; "); - - const char s_multipart_form_data[] = "boundary="; - if(NULL == http_header.boundary && 0 < strcasecmp(start, s_multipart_form_data)) - { - http_header.boundary = end + sizeof(s_multipart_form_data) + 1; - http_header.boundary += strspn(http_header.boundary, "-"); - // verbose("GESCHAFFT %s [%ld] QQQQ %s\n", http_header.boundary, http_header.boundary_size, end); - search = "\r\n"; - continue; - } - break; - } + http_header.boundary = end + sizeof(s_multipart_form_data) + 1; + http_header.boundary += strspn(http_header.boundary, "-"); + DEBUG("> Boundary found, now looking where it ends...\n"); + search = "\r\n"; + continue; + } /// }}} + break; case '\r': // fallthrough case '\n': + // {{{ newlines are special: sometimes content parts follow and sometimes headers, guess what... end[0] = '\0'; search = ":"; // we will continue to search for headers - - if(NULL == name) { if(NULL == http_header.method) { - verbose("[%ld]> HTTP REQUEST LINE :: %s \n", matchlen, start); + DEBUG("[%ld]> HTTP REQUEST LINE :: %s \n", matchlen, start); + end[0] = '\0'; + + while(NULL != (start = memchr(start, ' ', end - start))) { + if(NULL == http_header.url) + http_header.url = ++start; + else + start[0] = '\0'; + } http_header.method = start; http_header.newline_length = matchlen; } else { - verbose("[...]\n"); // if we want to intentially skip something, we land here by setting name = NUL; + DEBUG("[...]\n"); // if we want to intentially skip something, we land here by setting name = NUL; break; } } else { // we know that name is not NULL and can work with it if (0 == strcasecmp("Content-Disposition", name)) { http_header.content_disposition = start; } - } - - verbose("\033[32m[%ld]> '% 20s' = '%s'\033[0m\n", matchlen, name, start); - + } // }}} + DEBUG("\033[32m[%ld]> '% 20s' = '%s'\033[0m\n", matchlen, name, start); + // {{{ check if a http header ended (e.g. two newlines) if(matchlen > http_header.newline_length) { - verbose("> END HEADERS, because there were %d newlines; boundary='%s'[%ld]\n", matchlen / http_header.newline_length, http_header.boundary, http_header.boundary_size); + DEBUG("> END HEADERS, because there were %d newlines; boundary='%s'[%ld]\n", matchlen / http_header.newline_length, http_header.boundary, http_header.boundary_size); end += matchlen; // if it was the first header, we calculate the boundary size and expect more headers to come after a boundary if(http_header.boundary && http_header.boundary_size == 0) { - verbose("================================================================================\n"); + DEBUG("================================================================================\n"); http_header.boundary_size = strlen(http_header.boundary); // skip the first header and boundary... start = end; @@ -200,95 +194,68 @@ void * next_customer(size_t new_socket) { while(1) { size_t size_remaining = (size_t) output_buffer_length - (end - output_buffer) - 1; - verbose("%ld remaining.\n", size_remaining); fflush(stdout); + DEBUG("%ld remaining.\n", size_remaining); if(size_remaining <= 0) { - verbose("> not even the boundary would fit in that what is left.\n"); + DEBUG("> not even the boundary would fit in that what is left.\n"); break; } if(NULL == (end = memchr((void*) end, '-', size_remaining))) { - verbose("no further '-' found\n"); + DEBUG("no further '-' found\n"); break; } char * content_end = end - http_header.newline_length; - end += strspn(end, "-"); if(0 == strncmp(end, http_header.boundary, http_header.boundary_size)) { size_t file_size = content_end - content_start; - verbose("> Content ends here, size of the last file is %ld: {begin}", file_size); - fwrite(content_start, file_size, 1, stdout); - verbose("{end}\n"); - - - // puts(" <[last 20 bytes]\n"); - + DEBUG("> Content ends here, size of the last file is %ld.", file_size); end += http_header.boundary_size; matchlen = strspn(end, "\r\n"); - // matchlen = http_header.boundary_size; - verbose("> end is at %p, matchlen is %ld\n", end, matchlen); - + DEBUG("> end is at %p, matchlen is %ld\n", end, matchlen); search = ":"; break; } else { - // printf("[%ld] kack: %c\n", size_remaining, p[0]); fflush(stdout); end = end + 1; } } } - /* - char * content_start = end; - content_start += strspn(content_start, "-"); - // verbose("CONTENT: %s\n", content_start); - if(0 <= strcmp(content_start, http_header.boundary)) { - verbose("MATCH for %s // %s\n", http_header.content_type, http_header.content_disposition); - - search = "\r\n"; - matchlen = http_header.boundary_size; - name = NULL; - } else { - verbose("NO MATCH\n"); - } - */ break; - } - } + } // }}} if condition after a header + } // switch - // verbose("> end is at %p, matchlen is %ld\n", end, matchlen); - start = end + matchlen; - // fwrite(start, 42, 1, stdout); - // printf(" <[first 42 bytes of new start; matchlen was %ld; searching for %s\n", matchlen, search); + if(NULL == end) + break; + else + start = end + matchlen; } - printf("last known position: %p / %p with %s\n", start, end, search); - if(http_header.boundary) - verbose("http-boundary: %s", http_header.boundary); + DEBUG("> sending answer..."); + send_answer(new_socket, &http_header); + DEBUG("> answer sent."); - verbose("> sending answer..."); - send_answer(f_r); fclose(f_r); - verbose("> answer sent."); + fclose(output); + free(output_buffer); return NULL; } - - int serve(int server_fd) { struct sockaddr_in address; socklen_t address_len = sizeof(address); - verbose("waiting for connections on server file descriptor %d", server_fd); + DEBUG("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))) { - verbose("> Client %ld is connected via port %d", new_socket, address.sin_port); + DEBUG("> Client %ld is connected via port %d", new_socket, address.sin_port); // set non blocking mode... fcntl( @@ -299,23 +266,13 @@ int serve(int server_fd) 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); +#ifdef VALGRIND + break; // only run once, so that valgrind can test allocations&frees +#endif } err(errno, "error serving"); + close(server_fd); } #define PORT 8080 @@ -341,163 +298,7 @@ int main(int argc, char const *argv[]) : serve(server_fd) ? err(errno, NULL) : exit(EXIT_SUCCESS) - ; - return EXIT_FAILURE; + ; 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 -// -// typedef struct { -// const int length; -// const char * string; -// } length_value; -// -// 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) { - - const int MAX_HEADER_LINE_LENGTH = 1024; - const int MAX_BOUNDARY_LENGTH = 64; - - char boundary[64] = {0}; - int boundary_size = 0; - - char current_name[64] = {0}; - - usleep(100); // wait to avoid poll, epoll or evil select - - char * output_buffer = NULL; - size_t output_buffer_length = 0; - FILE * output = open_memstream(&output_buffer, &output_buffer_length); - - FILE * f = fdopen((size_t) new_socket, "r"); - char buffer[MAX_HEADER_LINE_LENGTH]; - char * s = NULL; - while(1) { - // printf("[errno: %d] %s", errno, buffer); - s = fgets(buffer, MAX_HEADER_LINE_LENGTH, f); - - if(boundary[0] != '\0' && buffer[0] == '-') { // first char minus. could be a new chunk of post data - int i = 0; - for(i = 1; i < MAX_HEADER_LINE_LENGTH; i++) { - if(buffer[i] != '-') { // skip all minus - if(0 == strncmp(&buffer[i], boundary, boundary_size)) { - fflush(output); - printf("> name='%s' value='%*s'\n", current_name, (int) output_buffer_length, output_buffer); - setenv(current_name, output_buffer, 0); - current_name[0] = '\0'; - rewind(output); - i = 0; - - puts("================================================================================"); - } - break; - } - } - if (i == 0) continue; - } - - - if(!s || strspn(buffer, "\n\r") > 0) { // either LF or CR/LF (windows) - puts("END HEADER\n"); - // s != NULL ? break : continue; - if(!s) break; else continue; // either eof or only end of (chunk-)header - } - - const char header_multipart[] = "Content-Type: multipart/form-data; boundary="; - if (!boundary[0] && 0 == strncasecmp(buffer, header_multipart, sizeof(header_multipart) - 1)) { - // we could potentially optimize this part with strspn and moving the buffer point, why not? - for(int i=sizeof(header_multipart); i [%s] %s\n", header_multipart, boundary); - break; - } - continue; - } - - const char header_disposition[] = "Content-Disposition: form-data; "; - if (boundary[0] && 0 == strncasecmp(header_disposition, buffer, sizeof(header_disposition) - 1)) { - printf("> %s :: %s\n", header_disposition, buffer); - const char header_disposition_name[] = "name=\""; - s = strstr(&buffer[sizeof(header_disposition) - 1], header_disposition_name); - if(s) { - s += sizeof(header_disposition_name) - 1; - s[strcspn(s, "\"")] = '\0'; - strncpy(current_name, s, 64); - printf("> /%s.*%s/ :: \"%s\"\n", header_disposition, header_disposition_name, s); - } else { - warnx("string not found in \"%s\".", &buffer[sizeof(header_disposition)]); - } - continue; - }// else { printf("FUCK: %*s [%d/%ld]\n", (int) sizeof(header_disposition), buffer, strncasecmp(header_disposition, buffer, sizeof(header_disposition)), sizeof(header_disposition)); } - - printf("> [unknown] %s", buffer); - fputs(buffer, output); - - - // I think another if could now search for "Content-Disposition: form-data; " and store the remaining fields - // somewhere, e.g. name="okay"; filename="1.gif". Also there may also be a content-type per chunk, which we - // could also add and erase, where I have currently NEXT CHUNK. - // After that the text output (printf("%s", buffer)) will only contain content without headers, so that printf - // can then be replaced with some fwrite to a memstream and we are done :) - } - - puts("> sending answer..."); - send_answer(new_socket); - puts("> answer sent."); - - return NULL; -} -*/ // vim: shiftwidth=2 tabstop=2 number foldmethod=marker diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..6cfe39c --- /dev/null +++ b/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +(echo -e "POST / HTTP/1.1\nContent-Type: text/html; boundary=--1234\n\n--1234\nContent-Disposition: fooo\n\n" ; dd if=/dev/urandom) | nc localhost 8080 -- cgit v1.2.3