summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Christian Pohle2021-11-24 01:22:54 +0100
committerMax Christian Pohle2021-11-24 01:22:54 +0100
commitdb864e290ba1ec4acd74371b150e7770585ef284 (patch)
tree80f2ba16e4520bc1f4bfe28eac7fdb747c58710e
parent21ab0292a9c07d2377047969a4d530a15e465907 (diff)
downloadohmycgi-db864e290ba1ec4acd74371b150e7770585ef284.tar.bz2
ohmycgi-db864e290ba1ec4acd74371b150e7770585ef284.zip
Made the implementation more generic again
-rw-r--r--Makefile2
-rw-r--r--http_parser.c173
-rw-r--r--main.c154
-rw-r--r--main.h19
4 files changed, 197 insertions, 151 deletions
diff --git a/Makefile b/Makefile
index 9a41fc9..2458ed9 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ LIBS := -lcups
9CFLAGS += -L/usr/local/lib 9CFLAGS += -L/usr/local/lib
10CFLAGS += -I/usr/local/include 10CFLAGS += -I/usr/local/include
11 11
12FILES := cgi.c 12FILES := cgi.c http_parser.c
13 13
14test: main 14test: 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
40void 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
diff --git a/main.c b/main.c
index c1a7c30..8eb6f40 100644
--- a/main.c
+++ b/main.c
@@ -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>
49static 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
57static int read_everything(FILE * f_r, FILE * output) { 40static 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
79static void * next_customer(size_t new_socket) { 62static 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
diff --git a/main.h b/main.h
index 02a9000..6588a40 100644
--- a/main.h
+++ b/main.h
@@ -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>
64static 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
54typedef struct { 72typedef 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
64void next_part(Http_Header * http_header, const char * content, size_t content_size); 82void next_part(Http_Header * http_header, const char * content, size_t content_size);
65void send_answer(Http_Header * http_header, int fd_socket); 83void send_answer(Http_Header * http_header, int fd_socket);
84void 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
..