manage content-encoding
[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 enum {
45 encoding_plain_e = 0, encoding_gzip_e, encoding_compress_e
46 } encoding_t;
47
48 typedef struct {
49 char *allow;
50 char *authorization;
51 encoding_t content_encoding;
52 char *content_length;
53 char *content_type;
54 char *date;
55 char *expires;
56 char *from;
57 char *if_modified_since;
58 char *last_modified;
59 char *location;
60 char *pragma;
61 char *referer;
62 char *server;
63 char *user_agent;
64 char *www_authenticate;
65 } header_t;
66
67 typedef struct {
68 char *ext;
69 char *type;
70 char *charset;
71 } mime_t;
72
73 #define NB_MIMES 8
74
75 mime_t mimes[NB_MIMES] = {
76 {"js", "application/javascript", "iso-8859-1"},
77 {"css", "text/css", "iso-8859-1"},
78 {"htm", "text/html", "iso-8859-1"},
79 {"html", "text/html", "iso-8859-1"},
80 {"png", "image/png", NULL},
81 {"jpeg", "image/jpeg", NULL},
82 {"jpg", "image/jpeg", NULL},
83 {"txt", "text/plain", "iso-8859-1"}
84 };
85
86 /* find mime type */
87
88 mime_t *find_mime_type (char *filename)
89 {
90 /* find extention */
91 char *ext = filename + strlen (filename);
92 while (--ext > filename) {
93 if (ext[-1] == '.') {
94 break;
95 }
96 }
97 if (ext == filename) {
98 return NULL;
99 }
100
101 /* find mime */
102 int i;
103 for (i = 0; i < NB_MIMES; i++) {
104 if (strcmp (ext, (mimes + i)->ext) == 0) {
105 return mimes + i;
106 }
107 }
108
109 return NULL;
110 }
111
112 /* print header values */
113
114 void print_header_values (header_t *header)
115 {
116 printf ("Header values\n");
117 if (header->allow) printf ("allow = '%s'\n", header->allow);
118 if (header->authorization) printf ("authorization = '%s'\n", header->authorization);
119 if (header->content_encoding) {
120 printf ("content_encoding = ");
121 switch (header->content_encoding) {
122 case encoding_plain_e: printf ("plain\n"); break;
123 case encoding_gzip_e: printf ("gzip\n"); break;
124 case encoding_compress_e: printf ("compress\n"); break;
125 }
126 }
127 if (header->content_length) printf ("content_length = '%s'\n", header->content_length);
128 if (header->content_type) printf ("content_type = '%s'\n", header->content_type);
129 if (header->date) printf ("date = '%s'\n", header->date);
130 if (header->expires) printf ("expires = '%s'\n", header->expires);
131 if (header->from) printf ("from = '%s'\n", header->from);
132 if (header->if_modified_since) printf ("if_modified_since = '%s'\n", header->if_modified_since);
133 if (header->last_modified) printf ("last_modified = '%s'\n", header->last_modified);
134 if (header->location) printf ("location = '%s'\n", header->location);
135 if (header->pragma) printf ("pragma = '%s'\n", header->pragma);
136 if (header->referer) printf ("referer = '%s'\n", header->referer);
137 if (header->server) printf ("server = '%s'\n", header->server);
138 if (header->user_agent) printf ("user_agent = '%s'\n", header->user_agent);
139 if (header->www_authenticate) printf ("www_authenticate = '%s'\n", header->www_authenticate);
140 }
141
142 /* find sequence*/
143
144 char *find_sequence (char *data, int len, char *seq, char **pdata)
145 {
146
147 int size = strlen (seq);
148
149 int i;
150 for (i = 0; i < len - size + 1; i++) {
151 if (strncmp (data + i, seq, size) == 0) {
152 data[i] = 0;
153 if (pdata != NULL) {
154 *pdata = data + i + size;
155 }
156 return data;
157 }
158 }
159
160 return NULL;
161 }
162
163 /* response entity */
164
165 int add_line (char **buffer, char *str)
166 {
167 VERBOSE (DEBUG, PRINT ("add line: %s\n", str));
168 int len = ((*buffer) ? strlen (*buffer) : 0) + strlen (str) + 3;
169 VERBOSE (DEBUG, PRINT ("len: %d\n", len));
170 if (*buffer) {
171 *buffer = (char *)realloc (*buffer, len);
172 } else {
173 *buffer = (char *)calloc (len, 1);
174 }
175 strcat (*buffer, str);
176 strcat (*buffer, "\r\n");
177 return len;
178 }
179
180 int add_status_line (char **buffer, code_t code)
181 {
182 char tmp[BUFFER_SIZE] = {0};
183
184 /* Status */
185 sprintf (tmp, "%s %s", HTTP_VERSION, codes[code]);
186 add_line (buffer, tmp);
187
188 return strlen (*buffer);
189 }
190
191 int add_general_header (char **buffer)
192 {
193 char tmp[BUFFER_SIZE] = {0};
194
195 /* Date */
196 time_t ts = time (NULL);
197 sprintf (tmp, "Date: %s", ctime (&ts));
198 tmp[strlen (tmp) - 1] = 0; // remove last \n
199 add_line (buffer, tmp);
200
201 /* Pragma */
202
203 return strlen (*buffer);
204 }
205
206 int add_response_header (char **buffer, char *uri)
207 {
208 char tmp[BUFFER_SIZE] = {0};
209
210 /* Location */
211 sprintf (tmp, "Location: %s", uri);
212 add_line (buffer, tmp);
213
214 /* Server */
215 sprintf (tmp, "Server: %s", SERVER_NAME);
216 add_line (buffer, tmp);
217
218 /* WWW-Authentificate */
219
220 return strlen (*buffer);
221 }
222
223 int add_entity (char **buffer, char *entity, int size, char *type, char *encoding)
224 {
225 char tmp[BUFFER_SIZE] = {0};
226 int len = strlen (*buffer);
227
228 /* Allow */
229 /* Expires */
230 /* Last-Modified */
231
232 if (entity != NULL) {
233
234 /* Content-Encoding */
235 if (encoding != NULL) {
236 sprintf (tmp, "Content-Encoding: %s", encoding);
237 add_line (buffer, tmp);
238 }
239
240 /* Content-Length */
241 sprintf (tmp, "Content-Length: %d", size);
242 add_line (buffer, tmp);
243
244 /* Content-Type */
245 sprintf (tmp, "Content-Type: %s", type);
246 add_line (buffer, tmp);
247
248 add_line (buffer, "");
249
250 /* Entity */
251 len = strlen (*buffer);
252 *buffer = realloc (*buffer, len + size);
253 memcpy (*buffer + len, entity, size);
254 len += size;
255 }
256
257 return len;
258 }
259
260 /* error 400 */
261
262 int error_400 (char **buffer)
263 {
264 add_status_line (buffer, c400);
265 add_general_header (buffer);
266 add_response_header (buffer, NULL);
267
268 char *response = "<html><head><title>Error 400</title></head><body><p>Bad Request</p></body></html>";
269 return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
270 }
271
272 /* error 404 */
273
274 int error_404 (char **buffer, char *uri)
275 {
276 add_status_line (buffer, c404);
277 add_general_header (buffer);
278 add_response_header (buffer, uri);
279
280 char *response = "<html><head><title>Error 404</title></head><body><p>File not found</p></body></html>";
281 return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
282 }
283
284 /* generic response */
285
286 int generic_response (char **buffer, char *location, char *response, int size)
287 {
288 int len = 0;
289 VERBOSE (DEBUG, PRINT ("add_status_line %d\n", len));
290 len = add_status_line (buffer, c200);
291 VERBOSE (DEBUG, PRINT ("add_general_header %d\n", len));
292 len = add_general_header (buffer);
293 VERBOSE (DEBUG, PRINT ("add_response_header %d\n", len));
294 len = add_response_header (buffer, location);
295 mime_t *mime = find_mime_type (location);
296
297 VERBOSE (DEBUG, PRINT ("add_entity %d\n", len));
298 return add_entity (buffer, response, size, mime->type, mime->charset);
299 }
300
301 /* trim string */
302
303 char *trim (char *str)
304 {
305 if (str != NULL) {
306 while ((*str == ' ') || (*str == '\t')) {
307 str++;
308 }
309 }
310 return str;
311 }
312
313 /* creqte command */
314
315 char *createcommand (char *format, char *name)
316 {
317 char *command = (char *) calloc (strlen (format) + strlen (name) + 1, 1);
318 sprintf (command, format, name);
319 return command;
320 }
321
322 /* main HTTP processing */
323
324 int processing (char *data, int len, conf_t *conf, char **pdata)
325 {
326 char *saved_data = data;
327 char location[BUFFER_SIZE] = {0};
328 VERBOSE (DEBUG, PRINT ("Start processing\n"));
329
330 /* check method */
331 char *line = find_sequence (data, len + data - saved_data, "\r\n", &data);
332 if (line == NULL) {
333 VERBOSE (WARNING, PRINT ("Unknown received data\n"));
334 if (pdata != NULL) {
335 *pdata = NULL;
336 }
337 return 0;
338 }
339 VERBOSE (DEBUG, PRINT ("Command line: '%s'\n", line));
340
341 char *method = strtok (line, " ");
342 char *uri = strtok (NULL, " ");
343 char *version = strtok (NULL, " ");
344 method_t type = not_supported_e;
345 if (strcmp (method, "GET") == 0) {
346 type = get_e;
347 } else if (strcmp ("HEAD", method) == 0) {
348 type = head_e;
349 } else if (strcmp ("POST", method) == 0) {
350 type = post_e;
351 } else {
352 VERBOSE (WARNING, PRINT ("Unkown method: %s\n", method));
353 return error_400 (pdata);
354 }
355 VERBOSE (INFO, PRINT ("%s %s (%s)\n", (type == get_e) ? "Get" : (type == head_e) ? "Head" : "Post", uri, version));
356
357 /* analyse uri */
358 char *filename = strtok (uri, "&");
359 char *variables = strtok (NULL, "\n");
360 char *path = (char *) calloc (strlen (conf->root) + strlen (filename) + 2, 1);
361 //sprintf (filename, "%s%s%s", conf->root, ((conf->root[strlen (conf->root) - 1] != '/') && (uri[0] != '/')) ? "/" : "", uri);
362 sprintf (path, "%s/%s", conf->root, filename);
363
364 /* check header */
365 header_t header = {0};
366 while (strcmp (line = find_sequence (data, len + data - saved_data, "\r\n", &data), "") != 0) {
367 VERBOSE (DEBUG, PRINT ("Header line: '%s'\n", line));
368 char *field = strtok (line, ":");
369 char *value = trim (strtok (NULL, "\r"));
370 VERBOSE (DEBUG, PRINT ("Field: %s\nValue: %s\n", field, value));
371 if (*line == 0) {
372 break;
373 }
374
375 VERBOSE (DEBUG, PRINT ("Analyse field\n"));
376 if (strcmp (field, "Allow") == 0) { header.allow = value; }
377 else if (strcmp (field, "Authorization") == 0) { header.authorization = value; }
378 else if (strcmp (field, "Content-Encoding") == 0) {
379 if (strcmp (value, "x-gzip") == 0) {
380 header.content_encoding = encoding_gzip_e;
381 } else if (strcmp (value, "x-compress") == 0) {
382 header.content_encoding = encoding_compress_e;
383 } else {
384 VERBOSE (WARNING, PRINT ("Unknown content encoding: %s\n", value));
385 }
386 } else if (strcmp (field, "Content-Length") == 0) { header.content_length = value; }
387 else if (strcmp (field, "Content-Type") == 0) { header.content_type = value; }
388 else if (strcmp (field, "Date") == 0) { header.date = value; }
389 else if (strcmp (field, "Expires") == 0) { header.expires = value; }
390 else if (strcmp (field, "From") == 0) { header.from = value; }
391 else if (strcmp (field, "If-Modified-Since") == 0) { header.if_modified_since = value; }
392 else if (strcmp (field, "Last-Modified") == 0) { header.last_modified = value; }
393 else if (strcmp (field, "Location") == 0) { header.location = value; }
394 else if (strcmp (field, "Pragma") == 0) { header.pragma = value; }
395 else if (strcmp (field, "Referer") == 0) { header.referer = value; }
396 else if (strcmp (field, "Server") == 0) { header.server = value; }
397 else if (strcmp (field, "User-Agent") == 0) { header.user_agent = value; }
398 else if (strcmp (field, "WWW-Authenticate") == 0) { header.www_authenticate = value; }
399 else { VERBOSE (WARNING, PRINT ("Unknown header field: '%s'\n", field)); }
400 }
401 VERBOSE (DEBUG, print_header_values (&header));
402
403 /* body */
404 char *body = data;
405 char *newbody = NULL;
406 len -= saved_data - data;
407 if (len != (header.content_length ? atoi (header.content_length) : 0)) {
408 VERBOSE (WARNING, PRINT ("Incoherent size (%d <> %s)\n", len, header.content_length));
409 }
410 if (len > 0) {
411 int i;
412 char *fcomp= NULL;
413 char *fdecomp = NULL;
414 char *command = NULL;
415
416 switch (header.content_encoding) {
417 case encoding_plain_e:
418 break;
419
420 case encoding_gzip_e:
421 fcomp = tempname (conf->temp, ".gz");
422 writefile (fcomp, body, len);
423 command = createcommand ("gunzip %s", fcomp);
424 system (command);
425 fdecomp = strdup (fcomp);
426 for (i = strlen (fdecomp) - 1; i > 0; i--) {
427 if (fdecomp[i] == '.') {
428 fdecomp[i] = 0;
429 break;
430 }
431 }
432 len = readfile (&newbody, fdecomp);
433 break;
434
435 case encoding_compress_e:
436 fcomp = tempname (conf->temp, ".Z");
437 writefile (fcomp, body, len);
438 command = createcommand ("compress %s", fcomp);
439 system (command);
440 fdecomp = strdup (fcomp);
441 for (i = strlen (fdecomp) - 1; i > 0; i--) {
442 if (fdecomp[i] == '.') {
443 fdecomp[i] = 0;
444 break;
445 }
446 }
447 len = readfile (&newbody, fdecomp);
448 break;
449 }
450
451 if (fcomp) {
452 unlink (fcomp);
453 free (fcomp);
454 }
455 if (fdecomp) {
456 unlink (fdecomp);
457 free (fdecomp);
458 }
459 if (command) {
460 free (command);
461 }
462 }
463
464 /* response */
465 char *buffer = NULL;
466 FILE *fid = NULL;
467 switch (type) {
468 case get_e:
469 case post_e: /* fall through */
470 VERBOSE (DEBUG, PRINT ("Read file %s\n", path));
471 len = readfile (&buffer, path);
472 if (len < 0) {
473 sprintf (location, "http://%s/", conf->servername);
474 len = error_404 (pdata, location);
475 } else {
476 sprintf (location, "http://%s%s", conf->servername, path);
477 len = generic_response (pdata, location, buffer, len);
478 free (buffer);
479 }
480 break;
481 case head_e:
482 VERBOSE (DEBUG, PRINT ("Test file %s\n", path));
483 fid = fopen (path, "rb");
484 if (fid == NULL) {
485 sprintf (location, "http://%s/", conf->servername);
486 len = error_404 (pdata, location);
487 } else {
488 fclose (fid);
489 sprintf (location, "http://%s%s", conf->servername, path);
490 len = generic_response (pdata, location, NULL, 0);
491 }
492 break;
493 case not_supported_e:
494 break;
495 }
496
497 /* cleaning */
498 VERBOSE (DEBUG, PRINT ("Cleaning\n"));
499 if (path) {
500 free (path);
501 }
502 if (newbody) {
503 free (newbody);
504 }
505
506 return len;
507 }
508
509 /* vim: set ts=4 sw=4 et: */