major fixes and better tests
[webserver.git] / http.c
1 #include <malloc.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <time.h>
5
6 #include "debug.h"
7 #include "file.h"
8
9 #include "http.h"
10
11 #define BUFFER_SIZE 128
12
13 #define HTTP_VERSION "HTTP/1.0"
14 #define SERVER_NAME "Webserver/0.0.1"
15
16 char *codes[15] = {
17 "200 OK",
18 "201 Created",
19 "202 Accepted",
20 "204 No Content",
21 "301 Moved Permanently",
22 "302 Moved Temporarily",
23 "304 Not Modified",
24 "400 Bad Request",
25 "401 Unauthorized",
26 "403 Forbidden",
27 "404 Not Found",
28 "500 Internal Server Error",
29 "501 Not Implemented",
30 "502 Bad Gateway",
31 "503 Service Unavailable"};
32
33 typedef enum {
34 c200 = 0, c201, c202, c204,
35 c301, c302, c304, c400,
36 c401, c403, c404, c500,
37 c501, c502, c503
38 } code_t;
39
40 typedef enum {
41 not_supported_e = 0, get_e, head_e, post_e
42 } method_t;
43
44 typedef struct {
45 char *allow;
46 char *authorization;
47 char *content_encoding;
48 char *content_length;
49 char *content_type;
50 char *date;
51 char *expires;
52 char *from;
53 char *if_modified_since;
54 char *last_modified;
55 char *location;
56 char *pragma;
57 char *referer;
58 char *server;
59 char *user_agent;
60 char *www_authenticate;
61 } header_t;
62
63 typedef struct {
64 char *ext;
65 char *type;
66 char *charset;
67 } mime_t;
68
69 #define NB_MIMES 8
70
71 mime_t mimes[NB_MIMES] = {
72 {"js", "application/javascript", "iso-8859-1"},
73 {"css", "text/css", "iso-8859-1"},
74 {"htm", "text/html", "iso-8859-1"},
75 {"html", "text/html", "iso-8859-1"},
76 {"png", "image/png", NULL},
77 {"jpeg", "image/jpeg", NULL},
78 {"jpg", "image/jpeg", NULL},
79 {"txt", "text/plain", "iso-8859-1"}
80 };
81
82 /* find mime type */
83
84 mime_t *find_mime_type (char *filename)
85 {
86 /* find extention */
87 char *ext = filename + strlen (filename);
88 while (--ext > filename) {
89 if (ext[-1] == '.') {
90 break;
91 }
92 }
93 if (ext == filename) {
94 return NULL;
95 }
96
97 /* find mime */
98 int i;
99 for (i = 0; i < NB_MIMES; i++) {
100 if (strcmp (ext, (mimes + i)->ext) == 0) {
101 return mimes + i;
102 }
103 }
104
105 return NULL;
106 }
107
108 /* print header values */
109
110 void print_header_values (header_t *header)
111 {
112 printf ("Header values\n");
113 if (header->allow) printf ("allow = '%s'\n", header->allow);
114 if (header->authorization) printf ("authorization = '%s'\n", header->authorization);
115 if (header->content_encoding) printf ("content_encoding = '%s'\n", header->content_encoding);
116 if (header->content_length) printf ("content_length = '%s'\n", header->content_length);
117 if (header->content_type) printf ("content_type = '%s'\n", header->content_type);
118 if (header->date) printf ("date = '%s'\n", header->date);
119 if (header->expires) printf ("expires = '%s'\n", header->expires);
120 if (header->from) printf ("from = '%s'\n", header->from);
121 if (header->if_modified_since) printf ("if_modified_since = '%s'\n", header->if_modified_since);
122 if (header->last_modified) printf ("last_modified = '%s'\n", header->last_modified);
123 if (header->location) printf ("location = '%s'\n", header->location);
124 if (header->pragma) printf ("pragma = '%s'\n", header->pragma);
125 if (header->referer) printf ("referer = '%s'\n", header->referer);
126 if (header->server) printf ("server = '%s'\n", header->server);
127 if (header->user_agent) printf ("user_agent = '%s'\n", header->user_agent);
128 if (header->www_authenticate) printf ("www_authenticate = '%s'\n", header->www_authenticate);
129 }
130
131 /* find sequence*/
132
133 char *find_sequence (char *data, int len, char *seq, char **pdata)
134 {
135
136 int size = strlen (seq);
137
138 int i;
139 for (i = 0; i < len - size + 1; i++) {
140 if (strncmp (data + i, seq, size) == 0) {
141 data[i] = 0;
142 if (pdata != NULL) {
143 *pdata = data + i + size;
144 }
145 return data;
146 }
147 }
148
149 return NULL;
150 }
151
152 /* response entity */
153
154 int add_line (char **buffer, char *str)
155 {
156 VERBOSE (DEBUG, PRINT ("add line: %s\n", str));
157 int len = ((*buffer) ? strlen (*buffer) : 0) + strlen (str) + 3;
158 VERBOSE (DEBUG, PRINT ("len: %d\n", len));
159 if (*buffer) {
160 *buffer = (char *)realloc (*buffer, len);
161 } else {
162 *buffer = (char *)calloc (len, 1);
163 }
164 strcat (*buffer, str);
165 strcat (*buffer, "\r\n");
166 return len;
167 }
168
169 int add_status_line (char **buffer, code_t code)
170 {
171 char tmp[BUFFER_SIZE] = {0};
172
173 /* Status */
174 sprintf (tmp, "%s %s", HTTP_VERSION, codes[code]);
175 add_line (buffer, tmp);
176
177 return strlen (*buffer);
178 }
179
180 int add_general_header (char **buffer)
181 {
182 char tmp[BUFFER_SIZE] = {0};
183
184 /* Date */
185 time_t ts = time (NULL);
186 sprintf (tmp, "Date: %s", ctime (&ts));
187 tmp[strlen (tmp) - 1] = 0; // remove last \n
188 add_line (buffer, tmp);
189
190 /* Pragma */
191
192 return strlen (*buffer);
193 }
194
195 int add_response_header (char **buffer, char *uri)
196 {
197 char tmp[BUFFER_SIZE] = {0};
198
199 /* Location */
200 sprintf (tmp, "Location: %s", uri);
201 add_line (buffer, tmp);
202
203 /* Server */
204 sprintf (tmp, "Server: %s", SERVER_NAME);
205 add_line (buffer, tmp);
206
207 /* WWW-Authentificate */
208
209 return strlen (*buffer);
210 }
211
212 int add_entity (char **buffer, char *entity, int size, char *type, char *encoding)
213 {
214 char tmp[BUFFER_SIZE] = {0};
215 int len = strlen (*buffer);
216
217 /* Allow */
218 /* Expires */
219 /* Last-Modified */
220
221 if (entity != NULL) {
222
223 /* Content-Encoding */
224 if (encoding != NULL) {
225 sprintf (tmp, "Content-Encoding: %s", encoding);
226 add_line (buffer, tmp);
227 }
228
229 /* Content-Length */
230 sprintf (tmp, "Content-Length: %d", size);
231 add_line (buffer, tmp);
232
233 /* Content-Type */
234 sprintf (tmp, "Content-Type: %s", type);
235 add_line (buffer, tmp);
236
237 add_line (buffer, "");
238
239 /* Entity */
240 len = strlen (*buffer);
241 *buffer = realloc (*buffer, len + size);
242 memcpy (*buffer + len, entity, size);
243 len += size;
244 }
245
246 return len;
247 }
248
249 /* error 400 */
250
251 int error_400 (char **buffer)
252 {
253 add_status_line (buffer, c400);
254 add_general_header (buffer);
255 add_response_header (buffer, NULL);
256
257 char *response = "<html><head><title>Error 400</title></head><body><p>Bad Request</p></body></html>";
258 return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
259 }
260
261 /* error 404 */
262
263 int error_404 (char **buffer, char *uri)
264 {
265 add_status_line (buffer, c404);
266 add_general_header (buffer);
267 add_response_header (buffer, uri);
268
269 char *response = "<html><head><title>Error 404</title></head><body><p>File not found</p></body></html>";
270 return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
271 }
272
273 /* generic response */
274
275 int generic_response (char **buffer, char *location, char *response, int size)
276 {
277 int len = 0;
278 VERBOSE (DEBUG, PRINT ("add_status_line %d\n", len));
279 len = add_status_line (buffer, c200);
280 VERBOSE (DEBUG, PRINT ("add_general_header %d\n", len));
281 len = add_general_header (buffer);
282 VERBOSE (DEBUG, PRINT ("add_response_header %d\n", len));
283 len = add_response_header (buffer, location);
284 mime_t *mime = find_mime_type (location);
285
286 VERBOSE (DEBUG, PRINT ("add_entity %d\n", len));
287 return add_entity (buffer, response, size, mime->type, mime->charset);
288 }
289
290 /* trim string */
291
292 char *trim (char *str)
293 {
294 if (str != NULL) {
295 while ((*str == ' ') || (*str == '\t')) {
296 str++;
297 }
298 }
299 return str;
300 }
301
302 /* main HTTP processing */
303
304 int processing (char *data, int len, conf_t *conf, char **pdata)
305 {
306 char *saved_data = data;
307 char location[BUFFER_SIZE] = {0};
308 VERBOSE (DEBUG, PRINT ("Start processing\n"));
309
310 /* check method */
311 char *line = find_sequence (data, len + data - saved_data, "\r\n", &data);
312 if (line == NULL) {
313 VERBOSE (WARNING, PRINT ("Unknown received data\n"));
314 if (pdata != NULL) {
315 *pdata = NULL;
316 }
317 return 0;
318 }
319 VERBOSE (DEBUG, PRINT ("Command line: '%s'\n", line));
320
321 char *method = strtok (line, " ");
322 char *uri = strtok (NULL, " ");
323 char *version = strtok (NULL, " ");
324 method_t type = not_supported_e;
325 if (strcmp (method, "GET") == 0) {
326 type = get_e;
327 } else if (strcmp ("HEAD", method) == 0) {
328 type = head_e;
329 } else if (strcmp ("POST", method) == 0) {
330 type = post_e;
331 } else {
332 VERBOSE (WARNING, PRINT ("Unkown method: %s\n", method));
333 return error_400 (pdata);
334 }
335 VERBOSE (INFO, PRINT ("%s %s (%s)\n", (type == get_e) ? "Get" : (type == head_e) ? "Head" : "Post", uri, version));
336
337 /* analyse uri */
338 char *filename = strtok (uri, "&");
339 char *variables = strtok (NULL, "\n");
340 char *path = (char *) calloc (strlen (conf->root) + strlen (filename) + 2, 1);
341 //sprintf (filename, "%s%s%s", conf->root, ((conf->root[strlen (conf->root) - 1] != '/') && (uri[0] != '/')) ? "/" : "", uri);
342 sprintf (path, "%s/%s", conf->root, filename);
343
344 /* check header */
345 header_t header = {0};
346 while (strcmp (line = find_sequence (data, len + data - saved_data, "\r\n", &data), "") != 0) {
347 VERBOSE (DEBUG, PRINT ("Header line: '%s'\n", line));
348 char *field = strtok (line, ":");
349 char *value = trim (strtok (NULL, "\r"));
350 VERBOSE (DEBUG, PRINT ("Field: %s\nValue: %s\n", field, value));
351 if (*line == 0) {
352 break;
353 }
354
355 VERBOSE (DEBUG, PRINT ("Analyse field\n"));
356 if (strcmp (field, "Allow") == 0) { header.allow = value; }
357 else if (strcmp (field, "Authorization") == 0) { header.authorization = value; }
358 else if (strcmp (field, "Content-Encoding") == 0) { header.content_encoding = value; }
359 else if (strcmp (field, "Content-Length") == 0) { header.content_length = value; }
360 else if (strcmp (field, "Content-Type") == 0) { header.content_type = value; }
361 else if (strcmp (field, "Date") == 0) { header.date = value; }
362 else if (strcmp (field, "Expires") == 0) { header.expires = value; }
363 else if (strcmp (field, "From") == 0) { header.from = value; }
364 else if (strcmp (field, "If-Modified-Since") == 0) { header.if_modified_since = value; }
365 else if (strcmp (field, "Last-Modified") == 0) { header.last_modified = value; }
366 else if (strcmp (field, "Location") == 0) { header.location = value; }
367 else if (strcmp (field, "Pragma") == 0) { header.pragma = value; }
368 else if (strcmp (field, "Referer") == 0) { header.referer = value; }
369 else if (strcmp (field, "Server") == 0) { header.server = value; }
370 else if (strcmp (field, "User-Agent") == 0) { header.user_agent = value; }
371 else if (strcmp (field, "WWW-Authenticate") == 0) { header.www_authenticate = value; }
372 else { VERBOSE (WARNING, PRINT ("Unknown header field: '%s'\n", field)); }
373 }
374 VERBOSE (DEBUG, print_header_values (&header));
375
376 /* body */
377 char *body = data;
378 len -= saved_data - data;
379 if (len != (header.content_length ? atoi (header.content_length) : 0)) {
380 VERBOSE (WARNING, PRINT ("Incoherent size (%d <> %s)\n", len, header.content_length));
381 }
382
383 /* response */
384 char *buffer = NULL;
385 FILE *fid = NULL;
386 switch (type) {
387 case get_e:
388 case post_e: /* fall through */
389 VERBOSE (DEBUG, PRINT ("Read file %s\n", path));
390 len = readfile (&buffer, path);
391 if (len < 0) {
392 sprintf (location, "http://%s/", conf->servername);
393 len = error_404 (pdata, location);
394 } else {
395 sprintf (location, "http://%s%s", conf->servername, path);
396 len = generic_response (pdata, location, buffer, len);
397 free (buffer);
398 }
399 break;
400 case head_e:
401 VERBOSE (DEBUG, PRINT ("Test file %s\n", path));
402 fid = fopen (path, "rb");
403 if (fid == NULL) {
404 sprintf (location, "http://%s/", conf->servername);
405 len = error_404 (pdata, location);
406 } else {
407 fclose (fid);
408 sprintf (location, "http://%s%s", conf->servername, path);
409 len = generic_response (pdata, location, NULL, 0);
410 }
411 break;
412 case not_supported_e:
413 break;
414 }
415
416 /* cleaning */
417 VERBOSE (DEBUG, PRINT ("Cleaning\n"));
418 if (path) {
419 VERBOSE (DEBUG, PRINT ("Cleaning path\n"));
420 free (path);
421 }
422
423 return len;
424 }
425
426 /* vim: set ts=4 sw=4 et: */