Little Palm fixes
[MonkeyD.git] / src / http.c
blob5616c22a31349e049cda7a3d15abdddf7778424e
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /* Monkey HTTP Daemon
4 * ------------------
5 * Copyright (C) 2001-2010, Eduardo Silva P. <edsiper@gmail.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Library General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
30 #include "monkey.h"
31 #include "memory.h"
32 #include "http.h"
33 #include "http_status.h"
34 #include "file.h"
35 #include "utils.h"
36 #include "config.h"
37 #include "str.h"
38 #include "method.h"
39 #include "socket.h"
40 #include "mimetype.h"
41 #include "header.h"
42 #include "epoll.h"
43 #include "plugin.h"
45 int mk_http_method_check(mk_pointer method)
47 if (strncmp(method.data, HTTP_METHOD_GET_STR, method.len) == 0) {
48 return HTTP_METHOD_GET;
51 if (strncmp(method.data, HTTP_METHOD_POST_STR, method.len) == 0) {
52 return HTTP_METHOD_POST;
55 if (strncmp(method.data, HTTP_METHOD_HEAD_STR, method.len) == 0) {
56 return HTTP_METHOD_HEAD;
59 return METHOD_NOT_FOUND;
62 mk_pointer mk_http_method_check_str(int method)
64 switch (method) {
65 case HTTP_METHOD_GET:
66 return mk_http_method_get_p;
68 case HTTP_METHOD_POST:
69 return mk_http_method_post_p;
71 case HTTP_METHOD_HEAD:
72 return mk_http_method_head_p;
74 return mk_http_method_null_p;
77 int mk_http_method_get(char *body)
79 int int_method, pos = 0;
80 int max_len_method = 5;
81 mk_pointer method;
83 /* Max method length is 4 (POST/HEAD) */
84 pos = mk_string_char_search(body, ' ', 5);
85 if (pos <= 2 || pos >= max_len_method) {
86 return METHOD_NOT_FOUND;
89 method.data = body;
90 method.len = (unsigned long) pos;
92 int_method = mk_http_method_check(method);
94 return int_method;
97 int mk_http_protocol_check(char *protocol, int len)
99 if (strncmp(protocol, HTTP_PROTOCOL_11_STR, len) == 0) {
100 return HTTP_PROTOCOL_11;
102 if (strncmp(protocol, HTTP_PROTOCOL_10_STR, len) == 0) {
103 return HTTP_PROTOCOL_10;
105 if (strncmp(protocol, HTTP_PROTOCOL_09_STR, len) == 0) {
106 return HTTP_PROTOCOL_09;
109 return HTTP_PROTOCOL_UNKNOWN;
112 mk_pointer mk_http_protocol_check_str(int protocol)
114 if (protocol == HTTP_PROTOCOL_11) {
115 return mk_http_protocol_11_p;
117 if (protocol == HTTP_PROTOCOL_10) {
118 return mk_http_protocol_10_p;
120 if (protocol == HTTP_PROTOCOL_09) {
121 return mk_http_protocol_09_p;
124 return mk_http_protocol_null_p;
127 int mk_http_init(struct client_request *cr, struct request *sr)
129 int ret;
130 int bytes = 0;
131 struct mimetype *mime;
132 char *uri_data = NULL;
133 int uri_len = 0;
135 #ifdef TRACE
136 MK_TRACE("HTTP Protocol Init");
137 #endif
139 /* Normal request default site */
140 if ((strcmp(sr->uri_processed, "/")) == 0) {
141 sr->real_path.data = mk_string_dup(sr->host_conf->documentroot.data);
142 sr->real_path.len = sr->host_conf->documentroot.len;
145 /* Map URI */
146 if (sr->uri_processed) {
147 uri_data = sr->uri_processed;
148 uri_len = strlen(sr->uri_processed);
150 else{
151 uri_data = sr->uri.data;
152 uri_len = sr->uri.len;
155 /* Compose real path */
156 if (sr->user_home == VAR_OFF) {
157 ret = mk_buffer_cat(&sr->real_path,
158 sr->host_conf->documentroot.data,
159 sr->host_conf->documentroot.len,
160 uri_data,
161 uri_len);
163 if (ret < 0) {
164 #ifdef TRACE
165 MK_TRACE("Error composing real path");
166 #endif
167 return EXIT_ERROR;
171 /* Check backward directory request */
172 if (mk_string_search_n(uri_data,
173 HTTP_DIRECTORY_BACKWARD,
174 uri_len) >= 0) {
175 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
176 return EXIT_ERROR;
179 sr->file_info = mk_file_get_info(sr->real_path.data);
181 if (!sr->file_info) {
182 /* if the resource requested doesn't exists, let's
183 * check if some plugin would like to handle it
185 #ifdef TRACE
186 MK_TRACE("No file, look for handler plugin");
187 #endif
188 ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_30, cr->socket, NULL, cr, sr);
189 if (ret == MK_PLUGIN_RET_CLOSE_CONX) {
190 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
191 return EXIT_ERROR;
193 else if (ret == MK_PLUGIN_RET_CONTINUE) {
194 return MK_PLUGIN_RET_CONTINUE;
196 else if (ret == MK_PLUGIN_RET_END) {
197 return EXIT_NORMAL;
200 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr);
201 return -1;
204 /* is it a valid directory ? */
205 if (sr->file_info->is_directory == MK_FILE_TRUE) {
206 /* Send redirect header if end slash is not found */
207 if (mk_http_directory_redirect_check(cr, sr) == -1) {
208 #ifdef TRACE
209 MK_TRACE("Directory Redirect");
210 #endif
211 /* Redirect has been sent */
212 return -1;
215 /* looking for a index file */
216 mk_pointer index_file;
217 index_file = mk_request_index(sr->real_path.data);
219 if (index_file.data) {
220 mk_mem_free(sr->file_info);
221 mk_pointer_free(&sr->real_path);
223 sr->real_path = index_file;
224 sr->file_info = mk_file_get_info(sr->real_path.data);
228 /* Check symbolic link file */
229 if (sr->file_info->is_link == MK_FILE_TRUE) {
230 if (config->symlink == VAR_OFF) {
231 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
232 return EXIT_ERROR;
234 else {
235 int n;
236 char linked_file[MAX_PATH];
237 n = readlink(sr->real_path.data, linked_file, MAX_PATH);
241 /* Plugin Stage 30: look for handlers for this request */
242 ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_30, 0, NULL, cr, sr);
243 #ifdef TRACE
244 MK_TRACE("STAGE_30 returned %i", ret);
245 #endif
246 if (ret == MK_PLUGIN_RET_CLOSE_CONX) {
247 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
248 return EXIT_ERROR;
250 else if (ret == MK_PLUGIN_RET_CONTINUE) {
251 return MK_PLUGIN_RET_CONTINUE;
253 else if (ret == MK_PLUGIN_RET_END) {
254 return EXIT_NORMAL;
257 /* read permissions and check file */
258 if (sr->file_info->read_access == MK_FILE_FALSE) {
259 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
260 return EXIT_ERROR;
263 /* Matching MimeType */
264 mime = mk_mimetype_find(&sr->real_path);
265 if (!mime) {
266 mime = mimetype_default;
269 if (sr->file_info->is_directory == MK_FILE_TRUE) {
270 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
271 return EXIT_ERROR;
274 /* get file size */
275 if (sr->file_info->size < 0) {
276 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr);
277 return EXIT_ERROR;
280 /* counter connections */
281 sr->headers->pconnections_left = (int)
282 (config->max_keep_alive_request - cr->counter_connections);
285 sr->headers->last_modified = sr->file_info->last_modification;
287 if (sr->if_modified_since.data && sr->method == HTTP_METHOD_GET) {
288 time_t date_client; /* Date sent by client */
289 time_t date_file_server; /* Date server file */
291 date_client = PutDate_unix(sr->if_modified_since.data);
292 date_file_server = sr->file_info->last_modification;
294 if ((date_file_server <= date_client) && (date_client > 0)) {
295 mk_header_set_http_status(sr, M_NOT_MODIFIED);
296 mk_header_send(cr->socket, cr, sr);
297 return EXIT_NORMAL;
300 mk_header_set_http_status(sr, M_HTTP_OK);
301 sr->headers->cgi = SH_NOCGI;
302 sr->headers->location = NULL;
304 /* Object size for log and response headers */
305 sr->headers->content_length = sr->file_info->size;
306 sr->headers->real_length = sr->file_info->size;
308 /* Process methods */
309 if (sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST) {
310 sr->headers->content_type = mime->type;
311 /* Range */
312 if (sr->range.data != NULL && config->resume == VAR_ON) {
313 if (mk_http_range_parse(sr) < 0) {
314 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr);
315 return EXIT_ERROR;
317 if (sr->headers->ranges[0] >= 0 || sr->headers->ranges[1] >= 0) {
318 mk_header_set_http_status(sr, M_HTTP_PARTIAL);
322 else {
323 /* without content-type */
324 mk_pointer_reset(&sr->headers->content_type);
327 /* Open file */
328 if (sr->file_info->size > 0) {
329 sr->fd_file = open(sr->real_path.data, config->open_flags);
330 if (sr->fd_file == -1) {
331 #ifdef TRACE
332 MK_TRACE("open() failed");
333 #endif
334 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr);
335 return EXIT_ERROR;
339 /* Send headers */
340 mk_header_send(cr->socket, cr, sr);
342 if (sr->headers->content_length == 0) {
343 return 0;
346 /* Send file content*/
347 if (sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST) {
348 /* Calc bytes to send & offset */
349 if (mk_http_range_set(sr, sr->file_info->size) != 0) {
350 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr);
351 return EXIT_ERROR;
354 bytes = mk_http_send_file(cr, sr);
357 return bytes;
360 int mk_http_send_file(struct client_request *cr, struct request *sr)
362 long int nbytes = 0;
364 nbytes = mk_socket_send_file(cr->socket, sr->fd_file,
365 &sr->bytes_offset, sr->bytes_to_send);
367 if (nbytes > 0) {
368 if (sr->loop == 0) {
369 mk_socket_set_cork_flag(cr->socket, TCP_CORK_OFF);
371 sr->bytes_to_send -= nbytes;
374 sr->loop++;
375 return sr->bytes_to_send;
378 int mk_http_directory_redirect_check(struct client_request *cr,
379 struct request *sr)
381 char *host;
382 char *location = 0;
383 char *real_location = 0;
384 unsigned long len;
387 * We have to check if exist an slash to the end of
388 * this string, if doesn't exist we send a redirection header
390 if (sr->uri_processed[strlen(sr->uri_processed) - 1] == '/') {
391 return 0;
394 host = mk_pointer_to_buf(sr->host);
396 mk_string_build(&location, &len, "%s/", sr->uri_processed);
397 if (config->serverport == config->standard_port) {
398 mk_string_build(&real_location, &len, "http://%s%s", host, location);
400 else {
401 mk_string_build(&real_location, &len, "http://%s:%i%s",
402 host, config->serverport, location);
405 #ifdef TRACE
406 MK_TRACE("Redirecting to '%s'", real_location);
407 #endif
409 mk_mem_free(host);
411 mk_header_set_http_status(sr, M_REDIR_MOVED);
412 sr->headers->content_length = 0;
414 mk_pointer_reset(&sr->headers->content_type);
415 sr->headers->location = real_location;
416 sr->headers->cgi = SH_NOCGI;
417 sr->headers->pconnections_left =
418 (config->max_keep_alive_request - cr->counter_connections);
420 mk_header_send(cr->socket, cr, sr);
421 mk_socket_set_cork_flag(cr->socket, TCP_CORK_OFF);
424 * we do not free() real_location
425 * as it's freed by iov
427 mk_mem_free(location);
428 sr->headers->location = NULL;
429 return -1;
433 * Check if a connection can continue open using as criteria
434 * the keepalive headers vars and Monkey configuration
436 int mk_http_keepalive_check(int socket, struct client_request *cr)
438 if (!cr->request) {
439 return -1;
442 if (config->keep_alive == VAR_OFF || cr->request->keep_alive == VAR_OFF) {
443 return -1;
446 if (cr->counter_connections >= config->max_keep_alive_request) {
447 return -1;
450 return 0;
453 int mk_http_range_set(struct request *sr, long file_size)
455 struct header_values *sh = sr->headers;
457 sr->bytes_to_send = file_size;
458 sr->bytes_offset = 0;
460 if (config->resume == VAR_ON && sr->range.data) {
461 /* yyy- */
462 if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) {
463 sr->bytes_offset = sh->ranges[0];
464 sr->bytes_to_send = file_size - sr->bytes_offset;
467 /* yyy-xxx */
468 if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) {
469 sr->bytes_offset = sh->ranges[0];
470 sr->bytes_to_send = labs(sh->ranges[1] - sh->ranges[0]) + 1;
473 /* -xxx */
474 if (sh->ranges[0] == -1 && sh->ranges[1] > 0) {
475 sr->bytes_to_send = sh->ranges[1];
476 sr->bytes_offset = file_size - sh->ranges[1];
479 if (sr->bytes_offset > file_size || sr->bytes_to_send > file_size) {
480 return -1;
483 lseek(sr->fd_file, sr->bytes_offset, SEEK_SET);
485 return 0;
490 int mk_http_range_parse(struct request *sr)
492 int eq_pos, sep_pos, len;
493 char *buffer = 0;
494 struct header_values *sh;
496 if (!sr->range.data)
497 return -1;
499 if ((eq_pos = mk_string_search_n(sr->range.data, "=", sr->range.len)) < 0)
500 return -1;
502 if (strncasecmp(sr->range.data, "Bytes", eq_pos) != 0)
503 return -1;
505 if ((sep_pos = mk_string_search_n(sr->range.data, "-", sr->range.len)) < 0)
506 return -1;
508 len = sr->range.len;
509 sh = sr->headers;
511 /* =-xxx */
512 if (eq_pos + 1 == sep_pos) {
513 sh->ranges[0] = -1;
514 sh->ranges[1] = (unsigned long) atol(sr->range.data + sep_pos + 1);
516 if (sh->ranges[1] <= 0) {
517 return -1;
520 sh->content_length = sh->ranges[1];
521 return 0;
524 /* =yyy-xxx */
525 if ((eq_pos + 1 != sep_pos) && (len > sep_pos + 1)) {
526 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, sep_pos);
527 sh->ranges[0] = (unsigned long) atol(buffer);
528 mk_mem_free(buffer);
530 buffer = mk_string_copy_substr(sr->range.data, sep_pos + 1, len);
531 sh->ranges[1] = (unsigned long) atol(buffer);
532 mk_mem_free(buffer);
534 if (sh->ranges[1] <= 0 || (sh->ranges[0] > sh->ranges[1])) {
535 return -1;
538 sh->content_length = abs(sh->ranges[1] - sh->ranges[0]) + 1;
539 return 0;
541 /* =yyy- */
542 if ((eq_pos + 1 != sep_pos) && (len == sep_pos + 1)) {
543 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, len);
544 sr->headers->ranges[0] = (unsigned long) atol(buffer);
545 mk_mem_free(buffer);
547 sh->content_length = (sh->content_length - sh->ranges[0]);
548 return 0;
551 return -1;
555 * Check if client request still has pending data
557 * Return 0 when all expected data has arrived or -1 when
558 * the connection is on a pending status due to HTTP spec
560 * This function is called from request.c :: mk_handler_read(..)
562 int mk_http_pending_request(struct client_request *cr)
564 int n;
565 char *end;
567 if (cr->body_length >= mk_endblock.len) {
568 end = (cr->body + cr->body_length) - mk_endblock.len;
570 else {
572 return -1;
575 /* try to match CRLF at the end of the request */
576 if (cr->body_pos_end < 0) {
577 if (strncmp(end, mk_endblock.data, mk_endblock.len) == 0) {
578 cr->body_pos_end = cr->body_length - mk_endblock.len;
580 else if ((n = mk_string_search(cr->body, mk_endblock.data)) >= 0 ){
582 cr->body_pos_end = n;
584 else {
585 return -1;
589 if (cr->first_method == HTTP_METHOD_UNKNOWN) {
590 cr->first_method = mk_http_method_get(cr->body);
593 if (cr->first_method == HTTP_METHOD_POST) {
594 if (cr->body_pos_end > 0) {
595 /* if first block has ended, we need to verify if exists
596 * a previous block end, that will means that the POST
597 * method has sent the whole information.
598 * just for ref: pipelining is not allowed with POST
600 if (cr->body_pos_end == cr->body_length - mk_endblock.len) {
601 /* Content-length is required, if is it not found,
602 * we pass as successfull in order to raise the error
603 * later
605 if (mk_method_post_content_length(cr->body) < 0) {
606 cr->status = MK_REQUEST_STATUS_COMPLETED;
607 return 0;
610 else {
611 cr->status = MK_REQUEST_STATUS_COMPLETED;
612 return 0;
615 else {
616 return -1;
620 cr->status = MK_REQUEST_STATUS_COMPLETED;
621 return 0;
624 mk_pointer *mk_http_status_get(short int code)
626 mk_list_sint_t *l;
628 l = mk_http_status_list;
629 while (l) {
630 if (l->index == code) {
631 return &l->value;
633 else {
634 l = l->next;
638 return NULL;
641 void mk_http_status_add(short int val[2])
643 short i, len = 6;
644 char *str_val;
645 mk_list_sint_t *list, *new;
647 for (i = val[0]; i <= val[1]; i++) {
649 new = mk_mem_malloc(sizeof(mk_list_sint_t));
650 new->index = i;
651 new->next = NULL;
653 str_val = mk_mem_malloc(6);
654 snprintf(str_val, len - 1, "%i", i);
656 new->value.data = str_val;
657 new->value.len = 3;
659 if (!mk_http_status_list) {
660 mk_http_status_list = new;
662 else {
663 list = mk_http_status_list;
664 while (list->next)
665 list = list->next;
667 list->next = new;
668 list = new;
673 void mk_http_status_list_init()
675 /* Status type */
676 short int success[2] = { 200, 206 };
677 short int redirections[2] = { 300, 305 };
678 short int client_errors[2] = { 400, 415 };
679 short int server_errors[2] = { 500, 505 };
681 mk_http_status_add(success);
682 mk_http_status_add(redirections);
683 mk_http_status_add(client_errors);
684 mk_http_status_add(server_errors);
687 int mk_http_request_end(int socket)
689 int ka;
690 struct client_request *cr;
691 struct sched_list_node *sched;
693 sched = mk_sched_get_thread_conf();
694 cr = mk_request_client_get(socket);
696 if (!cr) {
697 #ifdef TRACE
698 MK_TRACE("[FD %i] Not found", socket);
699 #endif
700 return -1;
703 if (!sched) {
704 #ifdef TRACE
705 MK_TRACE("Could not find sched list node :/");
706 #endif
707 return -1;
710 /* We need to ask to http_keepalive if this
711 * connection can continue working or we must
712 * close it.
714 ka = mk_http_keepalive_check(socket, cr);
715 mk_request_free_list(cr);
717 if (ka < 0) {
718 #ifdef TRACE
719 MK_TRACE("[FD %i] No KeepAlive mode, remove", cr->socket);
720 #endif
721 mk_request_client_remove(socket);
723 else {
724 mk_request_ka_next(cr);
725 mk_epoll_change_mode(sched->epoll_fd,
726 socket, MK_EPOLL_READ);
727 return 0;
730 return -1;