/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * OHMYCGI * * 2021 by Max Christian Pohle * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // {{{ INCLUDES #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 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; 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]); } 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); } 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, 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); if (read_buffer_length > POST_DATA_MAX_LENGTH) return EXIT_FAILURE; if (size < read_buffer_length) { // I expect one nmemb of data break; } } fflush(output); return EXIT_SUCCESS; } void * next_customer(size_t new_socket) { 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); 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 DEBUG("\n\n########################################## Content Parser\n"); char * start = output_buffer; char * end = NULL; char * search = "\r\n"; Http_Header http_header = {0}; char * name = NULL; while(NULL != (end = strpbrk(start, search))) { size_t matchlen = strspn(end, search); switch(end[0]) { case ':': end[0] = '\0'; // {{{ remember header 'names' and search for the value end++; name = start; if (0 == strcasecmp("Content-Type", start)) { 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)) { 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) { 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 { 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; } } // }}} 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) { 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) { DEBUG("================================================================================\n"); http_header.boundary_size = strlen(http_header.boundary); // skip the first header and boundary... start = end; start += strspn(start, "-"); start += http_header.boundary_size; start += http_header.newline_length; continue; } else { char * content_start = end; while(1) { size_t size_remaining = (size_t) output_buffer_length - (end - output_buffer) - 1; DEBUG("%ld remaining.\n", size_remaining); if(size_remaining <= 0) { DEBUG("> not even the boundary would fit in that what is left.\n"); break; } if(NULL == (end = memchr((void*) end, '-', size_remaining))) { 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; DEBUG("> Content ends here, size of the last file is %ld.", file_size); end += http_header.boundary_size; matchlen = strspn(end, "\r\n"); DEBUG("> end is at %p, matchlen is %ld\n", end, matchlen); search = ":"; break; } else { end = end + 1; } } } break; } // }}} if condition after a header } // switch if(NULL == end) break; else start = end + matchlen; } DEBUG("> sending answer..."); send_answer(new_socket, &http_header); DEBUG("> answer sent."); fclose(f_r); fclose(output); free(output_buffer); return NULL; } int serve(int server_fd) { struct sockaddr_in address; socklen_t address_len = sizeof(address); 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))) { DEBUG("> 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); #ifdef VALGRIND break; // only run once, so that valgrind can test allocations&frees #endif } err(errno, "error serving"); close(server_fd); } #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; } // vim: shiftwidth=2 tabstop=2 number foldmethod=marker