#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); } #define EWOULDBLOCK_DELAY 1000 #define READ_BUFFER_LENGTH 10 #define DEBUG_SLEEP_TIME 50000 static inline int verbose(const char * format, ...) { va_list va; va_start(va, format); usleep(DEBUG_SLEEP_TIME); return vprintf(format, va); } void 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)) { 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; 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; break; } } } 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"); 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); 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; char * content_start = NULL; char * content_end = NULL; 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; 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++; name = start; if (0 == strcasecmp("Content-Type", start)) { search = "\r\n;"; } else { search = "\r\n"; } break; case ';': { 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; } case '\r': // fallthrough case '\n': end[0] = '\0'; search = ":"; // we will continue to search for headers if(http_header.boundary && http_header.boundary_size == 0) { http_header.boundary_size = strlen(http_header.boundary); } if(NULL == name) { if(NULL == http_header.method) { verbose("[%ld]> HTTP REQUEST LINE :: %s \n", matchlen, start); 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; 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); if(matchlen > http_header.newline_length) { verbose("END HEADERS, boundary='%s'[%ld]\n", http_header.boundary, http_header.boundary_size); search = "-"; } break; case '-': { 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; } } start = end + matchlen; } printf("last known position: %p / %p with %s\n", start, end, search); char * p; if(NULL != (p = strstr(start, http_header.boundary))) { puts("OKAY?"); } p = start; while(1) { size_t size_remaining = (size_t) output_buffer_length - (p - output_buffer) - 1; printf("%ld remaining.\n", size_remaining); fflush(stdout); if(size_remaining <= 0) break; p = (char *) memchr( (void *) p, '-', size_remaining); p += strspn(p, "-"); if(0 >= strcmp(p, http_header.boundary)) { verbose("content ends here"); break; } else { printf("[%ld] kack: %c\n", size_remaining, p[0]); fflush(stdout); p = p + 1; } } start = p; // exit(0); if(http_header.boundary) verbose("http-boundary: %s", http_header.boundary); verbose("> sending answer..."); send_answer(f_r); fclose(f_r); verbose("> answer sent."); 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); 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); // 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 // // 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