diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | http_parser.c | 173 | ||||
| -rw-r--r-- | main.c | 154 | ||||
| -rw-r--r-- | main.h | 19 |
4 files changed, 197 insertions, 151 deletions
| @@ -9,7 +9,7 @@ LIBS := -lcups | |||
| 9 | CFLAGS += -L/usr/local/lib | 9 | CFLAGS += -L/usr/local/lib |
| 10 | CFLAGS += -I/usr/local/include | 10 | CFLAGS += -I/usr/local/include |
| 11 | 11 | ||
| 12 | FILES := cgi.c | 12 | FILES := cgi.c http_parser.c |
| 13 | 13 | ||
| 14 | test: main | 14 | test: main |
| 15 | ./main | 15 | ./main |
diff --git a/http_parser.c b/http_parser.c new file mode 100644 index 0000000..05cd094 --- /dev/null +++ b/http_parser.c | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | /* __ _ | ||
| 2 | * ____ / /_ ____ ___ __ ___________ _(_) | ||
| 3 | * / __ \/ __ \/ __ `__ \/ / / / ___/ __ `/ / | ||
| 4 | * / /_/ / / / / / / / / / /_/ / /__/ /_/ / / | ||
| 5 | * \____/_/ /_/_/ /_/ /_/\__, /\___/\__, /_/ | ||
| 6 | * /____/ /____/ | ||
| 7 | * | ||
| 8 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD | ||
| 9 | * | ||
| 10 | * Copyright (c) 2021, Max Christian Pohle <max@coderonline.de> | ||
| 11 | * | ||
| 12 | * Redistribution and use in source and binary forms, with or without | ||
| 13 | * modification, are permitted provided that the following conditions | ||
| 14 | * are met: | ||
| 15 | * | ||
| 16 | * 1. Redistributions of source code must retain the above copyright | ||
| 17 | * notice, this list of conditions and the following disclaimer. | ||
| 18 | * | ||
| 19 | * 2. Redistributions in binary form must reproduce the above copyright | ||
| 20 | * notice, this list of conditions and the following disclaimer in the | ||
| 21 | * documentation and/or other materials provided with the distribution. | ||
| 22 | * | ||
| 23 | * {{{ DISCLAIMER | ||
| 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
| 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 34 | * POSSIBILITY OF SUCH DAMAGE. | ||
| 35 | * }}} | ||
| 36 | */ | ||
| 37 | |||
| 38 | #include "main.h" | ||
| 39 | |||
| 40 | void parse_http(size_t new_socket, char * request, size_t request_length) { | ||
| 41 | char * start = request; | ||
| 42 | char * end = NULL; | ||
| 43 | char * search = "\r\n"; | ||
| 44 | |||
| 45 | Http_Header http_header = {0}; | ||
| 46 | |||
| 47 | char * name = NULL; | ||
| 48 | while(NULL != (end = strpbrk(start, search))) { // TODO: try harder to break things (are SEGFAULTs possible?) | ||
| 49 | |||
| 50 | size_t matchlen = strspn(end, search); | ||
| 51 | switch(end[0]) { | ||
| 52 | case ':': | ||
| 53 | end[0] = '\0'; // {{{ remember header 'names' and search for the value | ||
| 54 | end++; // jump over the colon | ||
| 55 | |||
| 56 | name = start; // remember, where name starts, will be important in the newline case | ||
| 57 | |||
| 58 | if (0 == strcasecmp("Content-Type", start)) { | ||
| 59 | search = "\r\n;"; // (more unlikely) also search for a semicolon in Content-Type: [...]; boundary=[...] | ||
| 60 | } else { | ||
| 61 | search = "\r\n"; // (likely) search for some kind of newline | ||
| 62 | } // }}} | ||
| 63 | break; | ||
| 64 | case ';': | ||
| 65 | // {{{ find the form-data boundary in the main header | ||
| 66 | start += strspn(start, "; "); // remove spaces and semicolons (boundary check implicit; also stops at '\0') | ||
| 67 | |||
| 68 | const char s_multipart_form_data[] = "boundary="; | ||
| 69 | if(NULL == http_header.boundary && 0 < strcasecmp(start, s_multipart_form_data)) | ||
| 70 | { | ||
| 71 | http_header.boundary = end + sizeof(s_multipart_form_data) + 1; | ||
| 72 | http_header.boundary += strspn(http_header.boundary, "-"); | ||
| 73 | DEBUG("> Boundary found, now looking where it ends...\n"); | ||
| 74 | search = "\r\n"; | ||
| 75 | continue; | ||
| 76 | } /// }}} | ||
| 77 | break; | ||
| 78 | case '\r': // fallthrough | ||
| 79 | case '\n': | ||
| 80 | // {{{ newlines are special: sometimes content parts follow and sometimes headers, guess what... | ||
| 81 | end[0] = '\0'; | ||
| 82 | search = ":"; // we will continue to search for headers | ||
| 83 | if(NULL == name) { | ||
| 84 | if(NULL == http_header.method) { | ||
| 85 | DEBUG("[%ld]> HTTP REQUEST LINE :: %s \n", matchlen, start); | ||
| 86 | end[0] = '\0'; | ||
| 87 | |||
| 88 | while(NULL != (start = memchr(start, ' ', end - start))) { | ||
| 89 | if(NULL == http_header.url) | ||
| 90 | http_header.url = ++start; | ||
| 91 | else | ||
| 92 | start[0] = '\0'; | ||
| 93 | } | ||
| 94 | http_header.method = start; | ||
| 95 | http_header.newline_length = matchlen; | ||
| 96 | } else { | ||
| 97 | DEBUG("[...]\n"); // if we want to intentially skip something, we land here by setting name = NUL; | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | } else { // we know that name is not NULL and can work with it | ||
| 101 | if (0 == strcasecmp("Content-Disposition", name)) | ||
| 102 | { http_header.content_disposition = start; } | ||
| 103 | } // }}} | ||
| 104 | DEBUG("\033[32m[%ld]> '% 20s' = '%s'\033[0m\n", matchlen, name, start); | ||
| 105 | // {{{ check if a http header ended (e.g. two newlines) | ||
| 106 | if(matchlen > http_header.newline_length) { | ||
| 107 | DEBUG("> END HEADERS, because there were %d newlines; boundary='%s'[%ld]\n", matchlen / http_header.newline_length, http_header.boundary, http_header.boundary_size); | ||
| 108 | end += matchlen; | ||
| 109 | |||
| 110 | // if it was the first header, we calculate the boundary size and expect more headers to come after a boundary | ||
| 111 | if(http_header.boundary && http_header.boundary_size == 0) { | ||
| 112 | DEBUG("================================================================================\n"); | ||
| 113 | http_header.boundary_size = strlen(http_header.boundary); | ||
| 114 | // skip the first header and boundary... | ||
| 115 | start = end; | ||
| 116 | start += strspn(start, "-"); | ||
| 117 | start += http_header.boundary_size; | ||
| 118 | start += http_header.newline_length; | ||
| 119 | continue; | ||
| 120 | } else { | ||
| 121 | char * content_start = end; | ||
| 122 | while(1) | ||
| 123 | { | ||
| 124 | size_t size_remaining = (size_t) request_length - (end - request) - 1; | ||
| 125 | DEBUG("%ld remaining.\n", size_remaining); | ||
| 126 | |||
| 127 | if(size_remaining <= 0) { | ||
| 128 | DEBUG("> not even the boundary would fit in that what is left.\n"); | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | |||
| 132 | if(NULL == (end = memchr((void*) end, '-', size_remaining))) { | ||
| 133 | DEBUG("no further '-' found\n"); | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | |||
| 137 | char * content_end = end - http_header.newline_length; | ||
| 138 | |||
| 139 | end += strspn(end, "-"); | ||
| 140 | if(0 == strncmp(end, http_header.boundary, http_header.boundary_size)) { | ||
| 141 | size_t file_size = content_end - content_start; | ||
| 142 | DEBUG("> Content ends here, size of the last file is %ld\n", file_size); | ||
| 143 | |||
| 144 | content_start[file_size + 1] = '\0'; | ||
| 145 | next_part(&http_header, content_start, file_size); | ||
| 146 | |||
| 147 | end += http_header.boundary_size; | ||
| 148 | matchlen = strspn(end, "\r\n"); | ||
| 149 | DEBUG("> end is at %p, matchlen is %ld\n", end, matchlen); | ||
| 150 | |||
| 151 | search = ":"; | ||
| 152 | break; | ||
| 153 | } else { | ||
| 154 | end = end + 1; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | break; | ||
| 159 | } // }}} if condition after a header | ||
| 160 | } // switch | ||
| 161 | |||
| 162 | if(NULL == end) | ||
| 163 | break; | ||
| 164 | else | ||
| 165 | start = end + matchlen; | ||
| 166 | } | ||
| 167 | |||
| 168 | DEBUG("> sending answer...\n"); | ||
| 169 | send_answer(&http_header, new_socket); | ||
| 170 | DEBUG("> answer sent.\n"); | ||
| 171 | } | ||
| 172 | |||
| 173 | // modeline for vim: shiftwidth=2 tabstop=2 number foldmethod=marker | ||
| @@ -36,23 +36,6 @@ | |||
| 36 | */ | 36 | */ |
| 37 | 37 | ||
| 38 | #include "main.h" | 38 | #include "main.h" |
| 39 | // {{{ MACROS | ||
| 40 | #define EWOULDBLOCK_DELAY 100 | ||
| 41 | #define READ_BUFFER_LENGTH 9000 // jumboframe? | ||
| 42 | #define POST_DATA_MAX_LENGTH 18000 | ||
| 43 | #define DEBUG_SLEEP_TIME 50000 | ||
| 44 | |||
| 45 | #ifndef DEBUG | ||
| 46 | #define DEBUG(X, ...) // (X, ...) | ||
| 47 | #else | ||
| 48 | #include <stdarg.h> | ||
| 49 | static inline int verbose(const char * format, ...) { | ||
| 50 | va_list va; va_start(va, format); usleep(DEBUG_SLEEP_TIME); return vprintf(format, va); | ||
| 51 | } | ||
| 52 | #undef DEBUG | ||
| 53 | #define DEBUG verbose | ||
| 54 | #endif | ||
| 55 | // }}} | ||
| 56 | 39 | ||
| 57 | static int read_everything(FILE * f_r, FILE * output) { | 40 | static int read_everything(FILE * f_r, FILE * output) { |
| 58 | const int read_buffer_length = READ_BUFFER_LENGTH; | 41 | const int read_buffer_length = READ_BUFFER_LENGTH; |
| @@ -76,7 +59,7 @@ static int read_everything(FILE * f_r, FILE * output) { | |||
| 76 | return EXIT_SUCCESS; | 59 | return EXIT_SUCCESS; |
| 77 | } | 60 | } |
| 78 | 61 | ||
| 79 | static void * next_customer(size_t new_socket) { | 62 | static void * answer_request(size_t new_socket) { |
| 80 | FILE * f_r = fdopen((size_t) new_socket, "r"); | 63 | FILE * f_r = fdopen((size_t) new_socket, "r"); |
| 81 | 64 | ||
| 82 | char * output_buffer = NULL; | 65 | char * output_buffer = NULL; |
| @@ -86,137 +69,8 @@ static void * next_customer(size_t new_socket) { | |||
| 86 | read_everything(f_r, output); // TODO: catch return value and error handling | 69 | read_everything(f_r, output); // TODO: catch return value and error handling |
| 87 | shutdown(new_socket, SHUT_RD); // shutdown the reading half of the connection | 70 | shutdown(new_socket, SHUT_RD); // shutdown the reading half of the connection |
| 88 | 71 | ||
| 89 | char * start = output_buffer; | 72 | // TODO: make parsing function abstract (e.g. parse(...) function point with dlsym) |
| 90 | char * end = NULL; | 73 | parse_http(new_socket, output_buffer, output_buffer_length); |
| 91 | char * search = "\r\n"; | ||
| 92 | |||
| 93 | Http_Header http_header = {0}; | ||
| 94 | |||
| 95 | char * name = NULL; | ||
| 96 | while(NULL != (end = strpbrk(start, search))) { // TODO: try harder to break things (are SEGFAULTs possible?) | ||
| 97 | |||
| 98 | size_t matchlen = strspn(end, search); | ||
| 99 | switch(end[0]) { | ||
| 100 | case ':': | ||
| 101 | end[0] = '\0'; // {{{ remember header 'names' and search for the value | ||
| 102 | end++; // jump over the colon | ||
| 103 | |||
| 104 | name = start; // remember, where name starts, will be important in the newline case | ||
| 105 | |||
| 106 | if (0 == strcasecmp("Content-Type", start)) { | ||
| 107 | search = "\r\n;"; // (more unlikely) also search for a semicolon in Content-Type: [...]; boundary=[...] | ||
| 108 | } else { | ||
| 109 | search = "\r\n"; // (likely) search for some kind of newline | ||
| 110 | } // }}} | ||
| 111 | break; | ||
| 112 | case ';': | ||
| 113 | // {{{ find the form-data boundary in the main header | ||
| 114 | start += strspn(start, "; "); // remove spaces and semicolons (boundary check implicit; also stops at '\0') | ||
| 115 | |||
| 116 | const char s_multipart_form_data[] = "boundary="; | ||
| 117 | if(NULL == http_header.boundary && 0 < strcasecmp(start, s_multipart_form_data)) | ||
| 118 | { | ||
| 119 | http_header.boundary = end + sizeof(s_multipart_form_data) + 1; | ||
| 120 | http_header.boundary += strspn(http_header.boundary, "-"); | ||
| 121 | DEBUG("> Boundary found, now looking where it ends...\n"); | ||
| 122 | search = "\r\n"; | ||
| 123 | continue; | ||
| 124 | } /// }}} | ||
| 125 | break; | ||
| 126 | case '\r': // fallthrough | ||
| 127 | case '\n': | ||
| 128 | // {{{ newlines are special: sometimes content parts follow and sometimes headers, guess what... | ||
| 129 | end[0] = '\0'; | ||
| 130 | search = ":"; // we will continue to search for headers | ||
| 131 | if(NULL == name) { | ||
| 132 | if(NULL == http_header.method) { | ||
| 133 | DEBUG("[%ld]> HTTP REQUEST LINE :: %s \n", matchlen, start); | ||
| 134 | end[0] = '\0'; | ||
| 135 | |||
| 136 | while(NULL != (start = memchr(start, ' ', end - start))) { | ||
| 137 | if(NULL == http_header.url) | ||
| 138 | http_header.url = ++start; | ||
| 139 | else | ||
| 140 | start[0] = '\0'; | ||
| 141 | } | ||
| 142 | http_header.method = start; | ||
| 143 | http_header.newline_length = matchlen; | ||
| 144 | } else { | ||
| 145 | DEBUG("[...]\n"); // if we want to intentially skip something, we land here by setting name = NUL; | ||
| 146 | break; | ||
| 147 | } | ||
| 148 | } else { // we know that name is not NULL and can work with it | ||
| 149 | if (0 == strcasecmp("Content-Disposition", name)) | ||
| 150 | { http_header.content_disposition = start; } | ||
| 151 | } // }}} | ||
| 152 | DEBUG("\033[32m[%ld]> '% 20s' = '%s'\033[0m\n", matchlen, name, start); | ||
| 153 | // {{{ check if a http header ended (e.g. two newlines) | ||
| 154 | if(matchlen > http_header.newline_length) { | ||
| 155 | DEBUG("> END HEADERS, because there were %d newlines; boundary='%s'[%ld]\n", matchlen / http_header.newline_length, http_header.boundary, http_header.boundary_size); | ||
| 156 | end += matchlen; | ||
| 157 | |||
| 158 | // if it was the first header, we calculate the boundary size and expect more headers to come after a boundary | ||
| 159 | if(http_header.boundary && http_header.boundary_size == 0) { | ||
| 160 | DEBUG("================================================================================\n"); | ||
| 161 | http_header.boundary_size = strlen(http_header.boundary); | ||
| 162 | // skip the first header and boundary... | ||
| 163 | start = end; | ||
| 164 | start += strspn(start, "-"); | ||
| 165 | start += http_header.boundary_size; | ||
| 166 | start += http_header.newline_length; | ||
| 167 | continue; | ||
| 168 | } else { | ||
| 169 | char * content_start = end; | ||
| 170 | while(1) | ||
| 171 | { | ||
| 172 | size_t size_remaining = (size_t) output_buffer_length - (end - output_buffer) - 1; | ||
| 173 | DEBUG("%ld remaining.\n", size_remaining); | ||
| 174 | |||
| 175 | if(size_remaining <= 0) { | ||
| 176 | DEBUG("> not even the boundary would fit in that what is left.\n"); | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | |||
| 180 | if(NULL == (end = memchr((void*) end, '-', size_remaining))) { | ||
| 181 | DEBUG("no further '-' found\n"); | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | |||
| 185 | char * content_end = end - http_header.newline_length; | ||
| 186 | |||
| 187 | end += strspn(end, "-"); | ||
| 188 | if(0 == strncmp(end, http_header.boundary, http_header.boundary_size)) { | ||
| 189 | size_t file_size = content_end - content_start; | ||
| 190 | DEBUG("> Content ends here, size of the last file is %ld\n", file_size); | ||
| 191 | |||
| 192 | content_start[file_size + 1] = '\0'; | ||
| 193 | next_part(&http_header, content_start, file_size); | ||
| 194 | |||
| 195 | end += http_header.boundary_size; | ||
| 196 | matchlen = strspn(end, "\r\n"); | ||
| 197 | DEBUG("> end is at %p, matchlen is %ld\n", end, matchlen); | ||
| 198 | |||
| 199 | |||
| 200 | search = ":"; | ||
| 201 | break; | ||
| 202 | } else { | ||
| 203 | end = end + 1; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | break; | ||
| 208 | } // }}} if condition after a header | ||
| 209 | } // switch | ||
| 210 | |||
| 211 | if(NULL == end) | ||
| 212 | break; | ||
| 213 | else | ||
| 214 | start = end + matchlen; | ||
| 215 | } | ||
| 216 | |||
| 217 | DEBUG("> sending answer...\n"); | ||
| 218 | send_answer(&http_header, new_socket); | ||
| 219 | DEBUG("> answer sent.\n"); | ||
| 220 | 74 | ||
| 221 | fclose(f_r); | 75 | fclose(f_r); |
| 222 | 76 | ||
| @@ -242,7 +96,7 @@ static int serve(int server_fd) | |||
| 242 | fcntl(new_socket, F_GETFL) | O_NONBLOCK | 96 | fcntl(new_socket, F_GETFL) | O_NONBLOCK |
| 243 | ); | 97 | ); |
| 244 | 98 | ||
| 245 | next_customer(new_socket); | 99 | answer_request(new_socket); |
| 246 | 100 | ||
| 247 | #ifdef VALGRIND | 101 | #ifdef VALGRIND |
| 248 | break; // only run once, so that valgrind can test allocations&frees | 102 | break; // only run once, so that valgrind can test allocations&frees |
| @@ -51,6 +51,24 @@ | |||
| 51 | // #include <pthread.h> // maybe later | 51 | // #include <pthread.h> // maybe later |
| 52 | // }}} | 52 | // }}} |
| 53 | 53 | ||
| 54 | // {{{ MACROS | ||
| 55 | #define EWOULDBLOCK_DELAY 100 | ||
| 56 | #define READ_BUFFER_LENGTH 9000 // jumboframe? | ||
| 57 | #define POST_DATA_MAX_LENGTH 18000 | ||
| 58 | #define DEBUG_SLEEP_TIME 50000 | ||
| 59 | |||
| 60 | #ifndef DEBUG | ||
| 61 | #define DEBUG(X, ...) // (X, ...) | ||
| 62 | #else | ||
| 63 | #include <stdarg.h> | ||
| 64 | static inline int verbose(const char * format, ...) { | ||
| 65 | va_list va; va_start(va, format); usleep(DEBUG_SLEEP_TIME); return vprintf(format, va); | ||
| 66 | } | ||
| 67 | #undef DEBUG | ||
| 68 | #define DEBUG verbose | ||
| 69 | #endif | ||
| 70 | // }}} | ||
| 71 | |||
| 54 | typedef struct { | 72 | typedef struct { |
| 55 | int newline_length; // lenght of one newline in bytes (\n has 1, CR/LF has 2) | 73 | int newline_length; // lenght of one newline in bytes (\n has 1, CR/LF has 2) |
| 56 | char * method; // GET/POST or something like that | 74 | char * method; // GET/POST or something like that |
| @@ -63,5 +81,6 @@ typedef struct { | |||
| 63 | 81 | ||
| 64 | void next_part(Http_Header * http_header, const char * content, size_t content_size); | 82 | void next_part(Http_Header * http_header, const char * content, size_t content_size); |
| 65 | void send_answer(Http_Header * http_header, int fd_socket); | 83 | void send_answer(Http_Header * http_header, int fd_socket); |
| 84 | void parse_http(size_t new_socket, char * request, size_t request_length); | ||
| 66 | 85 | ||
| 67 | // modeline for vim: shiftwidth=2 tabstop=2 number foldmethod=marker | 86 | // modeline for vim: shiftwidth=2 tabstop=2 number foldmethod=marker |
