Fix data type for uri_len in http.c
[MonkeyD.git] / src / http.c
blobfda715f91e3f955abe91e13e4e1592e1e82b8157
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 "logfile.h"
42 #include "header.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 debug_error = 0, bytes = 0;
131 struct mimetype *mime;
132 char *uri_data = NULL;
133 int uri_len = 0;
134 mk_pointer gmt_file_unix_time; // gmt time of server file (unix time)
136 #ifdef TRACE
137 MK_TRACE("HTTP Protocol Init");
138 #endif
140 /* Normal request default site */
141 if ((strcmp(sr->uri_processed, "/")) == 0) {
142 sr->real_path.data = mk_string_dup(sr->host_conf->documentroot.data);
143 sr->real_path.len = sr->host_conf->documentroot.len;
146 /* Map URI */
147 if (sr->uri_processed) {
148 uri_data = sr->uri_processed;
149 uri_len = strlen(sr->uri_processed);
151 else{
152 uri_data = sr->uri.data;
153 uri_len = sr->uri.len;
156 /* Compose real path */
157 if (sr->user_home == VAR_OFF) {
158 ret = mk_buffer_cat(&sr->real_path,
159 sr->host_conf->documentroot.data,
160 sr->host_conf->documentroot.len,
161 uri_data,
162 uri_len);
164 if (ret < 0) {
165 #ifdef TRACE
166 MK_TRACE("Error composing real path");
167 #endif
168 return EXIT_ERROR;
172 if (sr->method != HTTP_METHOD_HEAD) {
173 debug_error = 1;
176 /* Check backward directory request */
177 if (mk_string_search_n(uri_data,
178 HTTP_DIRECTORY_BACKWARD,
179 uri_len) >= 0) {
180 sr->log->final_response = M_CLIENT_FORBIDDEN;
181 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
182 return EXIT_ERROR;
185 /* Plugin Stage 30: look for handlers for this request */
186 if (mk_plugin_stage_run(MK_PLUGIN_STAGE_30, 0, NULL, cr, sr) ==
187 MK_PLUGIN_RET_CLOSE_CONX) {
188 sr->log->final_response = M_CLIENT_FORBIDDEN;
189 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
190 return EXIT_ERROR;
193 sr->file_info = mk_file_get_info(sr->real_path.data);
195 if (!sr->file_info) {
196 /* if the resource requested doesn't exists, let's
197 * check if some plugin would like to handle it
199 if (mk_plugin_stage_run(MK_PLUGIN_STAGE_40, cr->socket, NULL, cr, sr)
200 == 0) {
201 return -1;
204 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr, debug_error, sr->log);
205 return -1;
208 /* Check symbolic link file */
209 if (sr->file_info->is_link == MK_FILE_TRUE) {
210 if (config->symlink == VAR_OFF) {
211 sr->log->final_response = M_CLIENT_FORBIDDEN;
212 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr,
213 debug_error, sr->log);
214 return EXIT_ERROR;
216 else {
217 int n;
218 char linked_file[MAX_PATH];
219 n = readlink(sr->real_path.data, linked_file, MAX_PATH);
223 /* is it a valid directory ? */
224 if (sr->file_info->is_directory == MK_FILE_TRUE) {
225 /* Send redirect header if end slash is not found */
226 if (mk_http_directory_redirect_check(cr, sr) == -1) {
227 /* Redirect has been sent */
228 return -1;
231 /* looking for a index file */
232 mk_pointer index_file;
233 index_file = mk_request_index(sr->real_path.data);
235 if (index_file.data) {
236 mk_mem_free(sr->file_info);
237 mk_pointer_free(&sr->real_path);
239 sr->real_path = index_file;
240 sr->file_info = mk_file_get_info(sr->real_path.data);
244 /* read permissions and check file */
245 if (sr->file_info->read_access == MK_FILE_FALSE) {
246 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
247 return EXIT_ERROR;
250 /* Matching MimeType */
251 mime = mk_mimetype_find(&sr->real_path);
252 if (!mime) {
253 mime = mimetype_default;
256 if (sr->file_info->is_directory == MK_FILE_TRUE) {
257 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
258 return EXIT_ERROR;
261 /* Plugin Stage 40: look for handlers for this request */
262 ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_40, cr->socket, NULL, cr, sr);
263 if (ret == MK_PLUGIN_RET_CONTINUE) {
264 return ret;
267 /* get file size */
268 if (sr->file_info->size < 0) {
269 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr, 1, sr->log);
270 return EXIT_ERROR;
273 /* counter connections */
274 sr->headers->pconnections_left = (int)
275 (config->max_keep_alive_request - cr->counter_connections);
278 gmt_file_unix_time =
279 PutDate_string((time_t) sr->file_info->last_modification);
281 if (sr->if_modified_since.data && sr->method == HTTP_METHOD_GET) {
282 time_t date_client; /* Date sent by client */
283 time_t date_file_server; /* Date server file */
285 date_client = PutDate_unix(sr->if_modified_since.data);
286 date_file_server = sr->file_info->last_modification;
288 if ((date_file_server <= date_client) && (date_client > 0)) {
289 sr->headers->status = M_NOT_MODIFIED;
290 mk_header_send(cr->socket, cr, sr, sr->log);
291 mk_pointer_free(&gmt_file_unix_time);
292 return EXIT_NORMAL;
295 sr->headers->status = M_HTTP_OK;
296 sr->headers->cgi = SH_NOCGI;
297 sr->headers->last_modified = gmt_file_unix_time;
298 sr->headers->location = NULL;
300 /* Object size for log and response headers */
301 sr->headers->content_length = sr->file_info->size;
302 sr->headers->content_length_p = mk_utils_int2mkp(sr->file_info->size);
304 if (sr->method == HTTP_METHOD_HEAD) {
305 sr->log->size = 0;
306 sr->log->size_p = mk_utils_int2mkp(0);
308 else {
309 sr->log->size = sr->file_info->size;
310 sr->log->size_p = sr->headers->content_length_p;
313 if (sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST) {
314 sr->headers->content_type = mime->type;
315 /* Range */
316 if (sr->range.data != NULL && config->resume == VAR_ON) {
317 if (mk_http_range_parse(sr) < 0) {
318 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr, 1, sr->log);
319 mk_pointer_free(&gmt_file_unix_time);
320 return EXIT_ERROR;
322 if (sr->headers->ranges[0] >= 0 || sr->headers->ranges[1] >= 0)
323 sr->headers->status = M_HTTP_PARTIAL;
326 else {
327 /* without content-type */
328 mk_pointer_reset(&sr->headers->content_type);
331 mk_header_send(cr->socket, cr, sr, sr->log);
333 if (sr->headers->content_length == 0) {
334 return 0;
337 /* Sending file */
338 if ((sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST)
339 && sr->file_info->size > 0) {
340 sr->fd_file = open(sr->real_path.data, config->open_flags);
342 if (sr->fd_file == -1) {
343 perror("open");
344 return EXIT_ERROR;
347 /* Calc bytes to send & offset */
348 if (mk_http_range_set(sr, sr->file_info->size) != 0) {
349 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr, 1, sr->log);
350 return EXIT_ERROR;
352 bytes = SendFile(cr->socket, cr, sr);
355 return bytes;
358 int mk_http_directory_redirect_check(struct client_request *cr,
359 struct request *sr)
361 char *host;
362 char *location = 0;
363 char *real_location = 0;
364 unsigned long len;
367 * We have to check if exist an slash to the end of
368 * this string, if doesn't exist we send a redirection header
370 if (sr->uri_processed[strlen(sr->uri_processed) - 1] == '/') {
371 return 0;
374 host = mk_pointer_to_buf(sr->host);
376 m_build_buffer(&location, &len, "%s/", sr->uri_processed);
377 if (config->serverport == config->standard_port) {
378 m_build_buffer(&real_location, &len, "http://%s%s", host, location);
380 else {
381 m_build_buffer(&real_location, &len, "http://%s:%i%s",
382 host, config->serverport, location);
385 mk_mem_free(host);
387 sr->headers->status = M_REDIR_MOVED;
388 sr->headers->content_length = -1;
389 mk_pointer_reset(&sr->headers->content_type);
390 sr->headers->location = real_location;
391 sr->headers->cgi = SH_NOCGI;
392 sr->headers->pconnections_left =
393 (config->max_keep_alive_request - cr->counter_connections);
395 mk_header_send(cr->socket, cr, sr, sr->log);
396 mk_socket_set_cork_flag(cr->socket, TCP_CORK_OFF);
399 * we do not free() real_location
400 * as it's freed by iov
402 mk_mem_free(location);
403 sr->headers->location = NULL;
404 return -1;
408 * Check if a connection can continue open using as criteria
409 * the keepalive headers vars and Monkey configuration
411 int mk_http_keepalive_check(int socket, struct client_request *cr)
413 if (!cr->request) {
414 return -1;
417 if (config->keep_alive == VAR_OFF || cr->request->keep_alive == VAR_OFF) {
418 return -1;
421 if (cr->counter_connections >= config->max_keep_alive_request) {
422 return -1;
425 return 0;
428 int mk_http_range_set(struct request *sr, long file_size)
430 struct header_values *sh = sr->headers;
432 sr->bytes_to_send = file_size;
433 sr->bytes_offset = 0;
435 if (config->resume == VAR_ON && sr->range.data) {
436 /* yyy- */
437 if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) {
438 sr->bytes_offset = sh->ranges[0];
439 sr->bytes_to_send = file_size - sr->bytes_offset;
442 /* yyy-xxx */
443 if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) {
444 sr->bytes_offset = sh->ranges[0];
445 sr->bytes_to_send = labs(sh->ranges[1] - sh->ranges[0]) + 1;
448 /* -xxx */
449 if (sh->ranges[0] == -1 && sh->ranges[1] > 0) {
450 sr->bytes_to_send = sh->ranges[1];
451 sr->bytes_offset = file_size - sh->ranges[1];
454 if (sr->bytes_offset > file_size || sr->bytes_to_send > file_size) {
455 return -1;
458 lseek(sr->fd_file, sr->bytes_offset, SEEK_SET);
460 return 0;
465 int mk_http_range_parse(struct request *sr)
467 int eq_pos, sep_pos, len;
468 char *buffer = 0;
470 if (!sr->range.data)
471 return -1;
473 if ((eq_pos = mk_string_search_n(sr->range.data, "=", sr->range.len)) < 0)
474 return -1;
476 if (strncasecmp(sr->range.data, "Bytes", eq_pos) != 0)
477 return -1;
479 if ((sep_pos = mk_string_search_n(sr->range.data, "-",
480 sr->range.len)) < 0)
481 return -1;
483 len = sr->range.len;
485 /* =-xxx */
486 if (eq_pos + 1 == sep_pos) {
487 sr->headers->ranges[0] = -1;
488 sr->headers->ranges[1] =
489 (unsigned long) atol(sr->range.data + sep_pos + 1);
491 if (sr->headers->ranges[1] <= 0) {
492 return -1;
495 return 0;
498 /* =yyy-xxx */
499 if ((eq_pos + 1 != sep_pos) && (len > sep_pos + 1)) {
500 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, sep_pos);
501 sr->headers->ranges[0] = (unsigned long) atol(buffer);
502 mk_mem_free(buffer);
504 buffer = mk_string_copy_substr(sr->range.data, sep_pos + 1, len);
505 sr->headers->ranges[1] = (unsigned long) atol(buffer);
506 mk_mem_free(buffer);
508 if (sr->headers->ranges[1] <= 0 ||
509 sr->headers->ranges[0] > sr->headers->ranges[1]) {
510 return -1;
512 return 0;
514 /* =yyy- */
515 if ((eq_pos + 1 != sep_pos) && (len == sep_pos + 1)) {
516 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, len);
517 sr->headers->ranges[0] = (unsigned long) atol(buffer);
518 mk_mem_free(buffer);
519 return 0;
522 return -1;
526 * Check if client request still has pending data
528 * Return 0 when all expected data has arrived or -1 when
529 * the connection is on a pending status due to HTTP spec
531 * This function is called from request.c :: mk_handler_read(..)
533 int mk_http_pending_request(struct client_request *cr)
535 int n;
536 char *end;
538 if (cr->body_length >= mk_endblock.len) {
539 end = (cr->body + cr->body_length) - mk_endblock.len;
541 else {
542 return -1;
545 /* try to match CRLF at the end of the request */
546 if (cr->body_pos_end < 0) {
547 if (strncmp(end, mk_endblock.data, mk_endblock.len) == 0) {
548 cr->body_pos_end = cr->body_length - mk_endblock.len;
550 else if ((n = mk_string_search(cr->body, mk_endblock.data)) >= 0 ){
552 cr->body_pos_end = n;
554 else {
555 return -1;
559 if (cr->first_method == HTTP_METHOD_UNKNOWN) {
560 cr->first_method = mk_http_method_get(cr->body);
563 if (cr->first_method == HTTP_METHOD_POST) {
564 if (cr->body_pos_end > 0) {
565 /* if first block has ended, we need to verify if exists
566 * a previous block end, that will means that the POST
567 * method has sent the whole information.
568 * just for ref: pipelining is not allowed with POST
570 if (cr->body_pos_end == cr->body_length - mk_endblock.len) {
571 /* Content-length is required, if is it not found,
572 * we pass as successfull in order to raise the error
573 * later
575 if (mk_method_post_content_length(cr->body) < 0) {
576 cr->status = MK_REQUEST_STATUS_COMPLETED;
577 return 0;
580 else {
581 cr->status = MK_REQUEST_STATUS_COMPLETED;
582 return 0;
585 else {
586 return -1;
590 cr->status = MK_REQUEST_STATUS_COMPLETED;
591 return 0;
594 mk_pointer *mk_http_status_get(short int code)
596 mk_list_sint_t *l;
598 l = mk_http_status_list;
599 while (l) {
600 if (l->index == code) {
601 return &l->value;
603 else {
604 l = l->next;
608 return NULL;
611 void mk_http_status_add(short int val[2])
613 short i, len = 6;
614 char *str_val;
615 mk_list_sint_t *list, *new;
617 for (i = val[0]; i <= val[1]; i++) {
619 new = mk_mem_malloc(sizeof(mk_list_sint_t));
620 new->index = i;
621 new->next = NULL;
623 str_val = mk_mem_malloc(6);
624 snprintf(str_val, len - 1, "%i", i);
626 new->value.data = str_val;
627 new->value.len = 3;
629 if (!mk_http_status_list) {
630 mk_http_status_list = new;
632 else {
633 list = mk_http_status_list;
634 while (list->next)
635 list = list->next;
637 list->next = new;
638 list = new;
643 void mk_http_status_list_init()
645 /* Status type */
646 short int success[2] = { 200, 206 };
647 short int redirections[2] = { 300, 305 };
648 short int client_errors[2] = { 400, 415 };
649 short int server_errors[2] = { 500, 505 };
651 mk_http_status_add(success);
652 mk_http_status_add(redirections);
653 mk_http_status_add(client_errors);
654 mk_http_status_add(server_errors);