diff options
author | Max Christian Pohle | 2021-11-16 03:35:13 +0100 |
---|---|---|
committer | Max Christian Pohle | 2021-11-16 03:35:13 +0100 |
commit | e2ca5eb8dde31ea31b7fa1014d087829e4146bac (patch) | |
tree | 79ddc9068ce9add59f5903d127f16d2c03314f23 | |
parent | 89944e70b8e100e39ef65190ad1816dafde326eb (diff) | |
download | ohmycgi-e2ca5eb8dde31ea31b7fa1014d087829e4146bac.tar.bz2 ohmycgi-e2ca5eb8dde31ea31b7fa1014d087829e4146bac.zip |
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.
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | main.c | 336 |
2 files changed, 290 insertions, 58 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1f8a372 --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,12 @@ | |||
1 | |||
2 | test: main | ||
3 | ./main | ||
4 | |||
5 | main: clean | ||
6 | |||
7 | clean: | ||
8 | rm -f ./main | ||
9 | |||
10 | %: | ||
11 | $(CC) -pthread -g -o $@ $@.c | ||
12 | |||
@@ -1,3 +1,5 @@ | |||
1 | #include <asm-generic/errno-base.h> | ||
2 | #include <asm-generic/errno.h> | ||
1 | #include <netinet/in.h> | 3 | #include <netinet/in.h> |
2 | #include <stdio.h> | 4 | #include <stdio.h> |
3 | #include <stdio_ext.h> | 5 | #include <stdio_ext.h> |
@@ -16,10 +18,7 @@ | |||
16 | #include <pthread.h> | 18 | #include <pthread.h> |
17 | 19 | ||
18 | 20 | ||
19 | 21 | void send_answer(FILE * f) { | |
20 | void send_answer(void * new_socket) { | ||
21 | FILE * f = fdopen((size_t) new_socket, "w"); | ||
22 | puts("> sending answer..."); | ||
23 | fputs("HTTP/1.0 200 OK\n", f); | 22 | fputs("HTTP/1.0 200 OK\n", f); |
24 | fputs("content-type: text/html\n\n", f); | 23 | fputs("content-type: text/html\n\n", f); |
25 | fputs("<html>", f); | 24 | fputs("<html>", f); |
@@ -34,13 +33,256 @@ void send_answer(void * new_socket) { | |||
34 | fputs("<input type=\"file\" name=\"okay\" value=\"ok\" />", f); | 33 | fputs("<input type=\"file\" name=\"okay\" value=\"ok\" />", f); |
35 | fputs("<input type=\"submit\" name=\"okay\" value=\"ok\" />", f); | 34 | fputs("<input type=\"submit\" name=\"okay\" value=\"ok\" />", f); |
36 | fputs("</form>", f); | 35 | fputs("</form>", f); |
37 | fputs("</html>\n\0", f); | 36 | fputs("</html>", f); |
38 | fflush(f); | 37 | } |
38 | |||
39 | void * next_customer(size_t new_socket) { | ||
40 | |||
41 | char * output_buffer = NULL; | ||
42 | size_t output_buffer_length = 0; | ||
43 | FILE * output = open_memstream(&output_buffer, &output_buffer_length); | ||
44 | |||
45 | const int read_buffer_length = 10; | ||
46 | char read_buffer[read_buffer_length]; | ||
47 | |||
48 | puts("\n\n########################################## Content Reader"); | ||
49 | FILE * q = fdopen((size_t) new_socket, "r"); | ||
50 | printf("fd : %p\n", q); | ||
51 | |||
52 | for(size_t size = -2 ; ; size = fread(read_buffer, read_buffer_length, 1, q)) { | ||
53 | if(-2 == size || (-1 == size && EWOULDBLOCK == errno)) { | ||
54 | usleep(1000); | ||
55 | continue; | ||
56 | } else if (1 != size) { // I expect one nmemb of data | ||
57 | break; | ||
58 | } | ||
59 | |||
60 | fwrite(read_buffer, read_buffer_length, 1, output); | ||
61 | fwrite(read_buffer, read_buffer_length, 1, stdout); | ||
62 | } | ||
63 | fflush(output); | ||
64 | |||
65 | |||
66 | puts("\n\n########################################## Content Parser"); | ||
67 | typedef struct { | ||
68 | const char * name; | ||
69 | const char * value; | ||
70 | } NameValue; | ||
71 | |||
72 | NameValue namevalue[] = { | ||
73 | {"Content-Type", NULL}, | ||
74 | {"Content-Length", NULL} | ||
75 | }; | ||
76 | |||
77 | // token parser... | ||
78 | const char * boundary = NULL; | ||
79 | size_t boundary_length = 0; | ||
80 | char * search = NULL; | ||
81 | char * key = output_buffer; | ||
82 | |||
83 | char * saveptr = NULL; | ||
84 | char * content_start = NULL; | ||
85 | char * content_end = NULL; | ||
86 | |||
87 | while(1) { | ||
88 | |||
89 | printf("NEXT ITERATION with search '%s'\n", search); | ||
90 | |||
91 | for(char * | ||
92 | token = strtok_r(NULL == saveptr ? output_buffer : saveptr, NULL == search ? "\n\r" : search, &saveptr); | ||
93 | token != NULL; | ||
94 | token = strtok_r(NULL, search, &saveptr)) | ||
95 | { | ||
96 | |||
97 | if(!search) { // first round: HTTP status code | ||
98 | puts("FIRST ITERATION"); | ||
99 | printf("HTTP sanity check: %s\n", token); | ||
100 | search = ":"; | ||
101 | continue; | ||
102 | } | ||
103 | |||
104 | if(!key) { | ||
105 | search = "\r\n"; // we found a key and expect the value to be between here and EOL | ||
106 | key = &token[strspn(token, search)]; // also remove leading spaces | ||
107 | |||
108 | if(0 == strncmp(token, "\n\r\n", 3) | ||
109 | || 0 == strncmp(token, "\n\n", 2)) { | ||
110 | search = "\r\n"; | ||
111 | break; | ||
112 | } | ||
113 | } else { // value | ||
114 | |||
115 | token += strspn(token, ": "); | ||
116 | const char * value = token; | ||
117 | |||
118 | const char pattern[] = "multipart/form-data; boundary="; | ||
119 | if(!boundary && strstr(key, "Content-Type") && strncmp(value, pattern, sizeof(pattern))) { | ||
120 | boundary = &value[sizeof(pattern) + strspn(&value[sizeof(pattern)], "-")]; | ||
121 | boundary_length = strlen(boundary); | ||
122 | printf("> [!] boundary detected '%s' ", boundary); | ||
123 | } | ||
124 | |||
125 | for(int i=0; i < sizeof(namevalue) / sizeof(NameValue); i++) { | ||
126 | // printf("next name: %s\n", namevalue[i].name); | ||
127 | if(NULL == namevalue[i].value && 0 == strcasecmp(namevalue[i].name, key)) { | ||
128 | namevalue[i].value = value; | ||
129 | break; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | printf("> [%ld] \"%s\" :: \"%s\"\n", key - output_buffer, key, value); fflush(stdout); | ||
134 | |||
135 | search = ":"; | ||
136 | key = NULL; | ||
137 | } | ||
138 | } | ||
139 | |||
140 | if(!key) | ||
141 | break; | ||
142 | // printf("We are now here: %ld\n", key - output_buffer); | ||
143 | printf("We are now here: %s\n", key); | ||
144 | |||
145 | // jump over the content... | ||
146 | content_start = key; | ||
147 | while((key = memchr(key, '\n', output_buffer_length - (key - output_buffer)))) { | ||
148 | if(!key) { | ||
149 | warnx("out at %p\n", key); | ||
150 | break; | ||
151 | } else { | ||
152 | key++; | ||
153 | } | ||
154 | |||
155 | if(key[0] == '-') { | ||
156 | if(0 == strncmp(&key[strspn(key, "-")], boundary, boundary_length)) { | ||
157 | puts("GEIL1"); | ||
158 | |||
159 | if(key[-1] == '\n') key--; | ||
160 | if(key[-1] == '\r') key--; | ||
161 | |||
162 | content_end = key; | ||
163 | key += strspn(key, "\r\n-"); | ||
164 | key += boundary_length; | ||
165 | key += strspn(key, "\r\n"); | ||
166 | saveptr = key; | ||
167 | key = NULL; | ||
168 | search = ":"; | ||
169 | break; | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | |||
174 | printf("content is %p - %p = %ld in size\n", content_end, content_start, content_end - content_start); | ||
175 | if(content_end - content_start > 1000) { | ||
176 | FILE * f_w = fopen("/tmp/test.gif", "w"); | ||
177 | fwrite(content_start, content_end - content_start, 1, f_w); | ||
178 | fclose(f_w); | ||
179 | puts("FILE WRITTEN"); | ||
180 | } | ||
181 | |||
182 | // printf("We have gone to %ld\n", key - output_buffer); fflush(stdout); | ||
183 | if(key && key[0] == '\0') // final boundary reached? | ||
184 | break; | ||
185 | |||
186 | // usleep(100000); | ||
187 | } | ||
188 | |||
189 | |||
190 | puts("> sending answer..."); | ||
191 | FILE * f_w = fdopen((size_t) new_socket, "w"); | ||
192 | send_answer(f_w); | ||
193 | |||
194 | fclose(f_w); | ||
195 | fclose(q); | ||
39 | puts("> answer sent."); | 196 | puts("> answer sent."); |
40 | fclose(f); | 197 | |
198 | printf("still in knowledge of out boundary: %s\n", namevalue[0].value); | ||
199 | |||
200 | return NULL; | ||
41 | } | 201 | } |
42 | 202 | ||
43 | 203 | ||
204 | |||
205 | int serve(int server_fd) | ||
206 | { | ||
207 | struct sockaddr_in address; | ||
208 | socklen_t address_len = sizeof(address); | ||
209 | warnx("waiting for connections on server file descriptor %d", server_fd); | ||
210 | |||
211 | size_t new_socket = -1; | ||
212 | while(-1 != | ||
213 | (new_socket = accept(server_fd, | ||
214 | (struct sockaddr*) &address, | ||
215 | &address_len))) | ||
216 | { | ||
217 | warnx("> Client %ld is connected via port %d", new_socket, address.sin_port); | ||
218 | |||
219 | // set non blocking mode... | ||
220 | fcntl((size_t) new_socket, F_SETFL, | ||
221 | fcntl((size_t) new_socket, F_GETFL) | O_NONBLOCK); | ||
222 | |||
223 | next_customer(new_socket); | ||
224 | |||
225 | /* | ||
226 | if(fork()) { | ||
227 | close(new_socket); | ||
228 | } else { | ||
229 | close(server_fd); // give the server free | ||
230 | next_customer(new_socket); | ||
231 | shutdown(new_socket, SHUT_RDWR); | ||
232 | exit(0); | ||
233 | } | ||
234 | */ | ||
235 | |||
236 | // pthread_t thread_id; | ||
237 | // pthread_create(&thread_id, NULL, next_customer, (void*) new_socket); | ||
238 | // pthread_join(thread_id, NULL); | ||
239 | } | ||
240 | |||
241 | err(errno, "error serving"); | ||
242 | } | ||
243 | |||
244 | #define PORT 8080 | ||
245 | int main(int argc, char const *argv[]) | ||
246 | { | ||
247 | int server_fd = -1, opt = 1; | ||
248 | |||
249 | struct sockaddr_in address = { | ||
250 | .sin_family = AF_INET, | ||
251 | .sin_addr.s_addr = INADDR_ANY, | ||
252 | .sin_port = htons(PORT) | ||
253 | }; | ||
254 | |||
255 | // I <3 C | ||
256 | 0 == (server_fd = socket(AF_INET, SOCK_STREAM, 0)) | ||
257 | ? err(errno, NULL) | ||
258 | : setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) | ||
259 | ? err(errno, "setsockopt failed on socket with fileno %d", server_fd) | ||
260 | : bind(server_fd, (struct sockaddr*) &address, sizeof(address)) | ||
261 | ? err(errno, NULL) | ||
262 | : listen(server_fd, SOMAXCONN) | ||
263 | ? err(errno, NULL) | ||
264 | : serve(server_fd) | ||
265 | ? err(errno, NULL) | ||
266 | : exit(EXIT_SUCCESS) | ||
267 | ; | ||
268 | return EXIT_FAILURE; | ||
269 | } | ||
270 | |||
271 | // void * next_customer_old(int new_socket) | ||
272 | // { | ||
273 | // char *hello = "Hello from server"; | ||
274 | // char buffer[1024] = {0}; | ||
275 | // int valread = read(new_socket, buffer, 1024); | ||
276 | // printf("%s %d\n", buffer, valread); | ||
277 | // | ||
278 | // send(new_socket, hello, strlen(hello), 0); | ||
279 | // | ||
280 | // printf("Hello message sent\n"); | ||
281 | // return NULL; | ||
282 | // } | ||
283 | |||
284 | // IDEA ... | ||
285 | // | ||
44 | // #define FOREACH_HEADER(HEADER) \ | 286 | // #define FOREACH_HEADER(HEADER) \ |
45 | // HEADER("Content-Type: multipart/form-data; boundary=") | 287 | // HEADER("Content-Type: multipart/form-data; boundary=") |
46 | // #define GENERATE_HEADERS(H) sizeof(H), H | 288 | // #define GENERATE_HEADERS(H) sizeof(H), H |
@@ -53,10 +295,37 @@ void send_answer(void * new_socket) { | |||
53 | // const length_value headers[] = { | 295 | // const length_value headers[] = { |
54 | // FOREACH_HEADER(GENERATE_HEADERS) | 296 | // FOREACH_HEADER(GENERATE_HEADERS) |
55 | // }; | 297 | // }; |
298 | // | ||
56 | 299 | ||
57 | 300 | ||
301 | // READ WITH recv ... | ||
302 | /* | ||
303 | for(size_t size = -1 ; ; (size = recv(new_socket, read_buffer, read_buffer_length, 0))) { | ||
304 | if(-1 == size) { | ||
305 | usleep(10000); | ||
306 | printf("size: %ld\n", size); | ||
307 | if(EWOULDBLOCK == errno){ | ||
308 | continue; | ||
309 | } else if (size == 0 || size < read_buffer_length) { | ||
310 | break; | ||
311 | } | ||
312 | } else { | ||
313 | read_buffer[size] = '\0'; | ||
314 | |||
315 | fwrite(read_buffer, size, 1, output); | ||
316 | fwrite(read_buffer, size, 1, stdout); | ||
317 | |||
318 | // if(output_buffer_length > 4 | ||
319 | // && (strspn(&output_buffer[output_buffer_length - 2], "\n") == 2 | ||
320 | // || strspn(&output_buffer[output_buffer_length - 4], "\r\n") == 4)) | ||
321 | // break; | ||
322 | } | ||
323 | }*/ | ||
324 | |||
325 | |||
326 | // fully working implementation... | ||
327 | /* | ||
58 | void * next_customer(void * new_socket) { | 328 | void * next_customer(void * new_socket) { |
59 | if (new_socket == 0) return NULL; | ||
60 | 329 | ||
61 | const int MAX_HEADER_LINE_LENGTH = 1024; | 330 | const int MAX_HEADER_LINE_LENGTH = 1024; |
62 | const int MAX_BOUNDARY_LENGTH = 64; | 331 | const int MAX_BOUNDARY_LENGTH = 64; |
@@ -151,56 +420,7 @@ void * next_customer(void * new_socket) { | |||
151 | send_answer(new_socket); | 420 | send_answer(new_socket); |
152 | puts("> answer sent."); | 421 | puts("> answer sent."); |
153 | 422 | ||
154 | |||
155 | puts("> file closed."); | ||
156 | //close((size_t) new_socket); | ||
157 | // pthread_exit(EXIT_SUCCESS); | ||
158 | return NULL; | 423 | return NULL; |
159 | } | 424 | } |
160 | 425 | */ | |
161 | void serve(int server_fd) | ||
162 | { | ||
163 | struct sockaddr_in address; | ||
164 | int addrlen = sizeof(address); | ||
165 | warn("waiting for connections on %d", server_fd); | ||
166 | |||
167 | for(size_t new_socket = 0 | ||
168 | ; 1 ; new_socket=accept(server_fd, (struct sockaddr*) &address, (socklen_t*) &addrlen)) | ||
169 | { | ||
170 | warn("next: %ld\n", new_socket); | ||
171 | // set non blocking mode... | ||
172 | fcntl((size_t) new_socket, F_SETFL, | ||
173 | fcntl((size_t) new_socket, F_GETFL) | O_NONBLOCK); | ||
174 | |||
175 | next_customer((void*) new_socket); | ||
176 | // pthread_t thread_id; | ||
177 | // pthread_create(&thread_id, NULL, next_customer, (void*) new_socket); | ||
178 | // pthread_join(thread_id, NULL); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | #define PORT 8080 | ||
183 | int main(int argc, char const *argv[]) | ||
184 | { | ||
185 | int server_fd; | ||
186 | int opt = 1; | ||
187 | |||
188 | struct sockaddr_in address; | ||
189 | address.sin_family = AF_INET; | ||
190 | address.sin_addr.s_addr = INADDR_ANY; | ||
191 | address.sin_port = htons(PORT); | ||
192 | |||
193 | 0 == (server_fd = socket(AF_INET, SOCK_STREAM, 0)) | ||
194 | ? err(errno, NULL) | ||
195 | : setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) | ||
196 | ? err(errno, "setsockopt failed on socket with fileno %d", server_fd) | ||
197 | : bind(server_fd, (struct sockaddr*) &address, sizeof(address)) | ||
198 | ? err(errno, NULL) | ||
199 | : listen(server_fd, SOMAXCONN) | ||
200 | ? err(errno, NULL) | ||
201 | : serve(server_fd); | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | // vim: shiftwidth=2 tabstop=2 number | 426 | // vim: shiftwidth=2 tabstop=2 number |