Minor fixes
[MonkeyD.git] / src / http.c
blob034cc39ecdc80fe5b42307b8b392263ca3d8a5ed
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.
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 (strncasecmp(method.data, HTTP_METHOD_GET_STR, method.len) == 0) {
48 return HTTP_METHOD_GET;
51 if (strncasecmp(method.data, HTTP_METHOD_POST_STR, method.len) == 0) {
52 return HTTP_METHOD_POST;
55 if (strncasecmp(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 (strncasecmp(protocol, HTTP_PROTOCOL_11_STR, len) == 0) {
100 return HTTP_PROTOCOL_11;
102 if (strncasecmp(protocol, HTTP_PROTOCOL_10_STR, len) == 0) {
103 return HTTP_PROTOCOL_10;
105 if (strncasecmp(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 mk_pointer gmt_file_unix_time; // gmt time of server file (unix time)
134 #ifdef TRACE
135 MK_TRACE("HTTP Protocol Init");
136 #endif
138 /* Normal request default site */
139 if ((strcmp(sr->uri_processed, "/")) == 0) {
140 sr->real_path.data = mk_string_dup(sr->host_conf->documentroot.data);
141 sr->real_path.len = sr->host_conf->documentroot.len;
144 if (sr->user_home == VAR_OFF) {
145 mk_buffer_cat(&sr->real_path, sr->host_conf->documentroot.data,
146 sr->uri_processed);
149 if (sr->method != HTTP_METHOD_HEAD) {
150 debug_error = 1;
153 if (mk_string_search_n(sr->uri.data, HTTP_DIRECTORY_BACKWARD,
154 sr->uri.len) >= 0) {
155 sr->log->final_response = M_CLIENT_FORBIDDEN;
156 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
157 return -1;
160 /* Plugin Stage 30: look for handlers for this request */
161 if (mk_plugin_stage_run(MK_PLUGIN_STAGE_30, 0, NULL, cr, sr) ==
162 MK_PLUGIN_RET_CLOSE_CONX) {
163 sr->log->final_response = M_CLIENT_FORBIDDEN;
164 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
165 return -1;
168 sr->file_info = mk_file_get_info(sr->real_path.data);
170 if (!sr->file_info) {
171 /* if the resource requested doesn't exists, let's
172 * check if some plugin would like to handle it
174 if (mk_plugin_stage_run(MK_PLUGIN_STAGE_40, cr->socket, NULL, cr, sr)
175 == 0) {
176 return -1;
179 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr, debug_error, sr->log);
180 return -1;
183 /* Check symbolic link file */
184 if (sr->file_info->is_link == MK_FILE_TRUE) {
185 if (config->symlink == VAR_OFF) {
186 sr->log->final_response = M_CLIENT_FORBIDDEN;
187 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr,
188 debug_error, sr->log);
189 return -1;
191 else {
192 int n;
193 char linked_file[MAX_PATH];
194 n = readlink(sr->real_path.data, linked_file, MAX_PATH);
196 if(Deny_Check(linked_file)==-1) {
197 sr->log->final_response=M_CLIENT_FORBIDDEN;
198 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
199 return -1;
206 /* is it a valid directory ? */
207 if (sr->file_info->is_directory == MK_FILE_TRUE) {
208 /* Send redirect header if end slash is not found */
209 if (mk_http_directory_redirect_check(cr, sr) == -1) {
210 /* Redirect has been sent */
211 return -1;
214 /* looking for a index file */
215 mk_pointer index_file;
216 index_file = mk_request_index(sr->real_path.data);
218 if (index_file.data) {
219 mk_mem_free(sr->file_info);
220 mk_pointer_free(&sr->real_path);
222 sr->real_path = index_file;
223 sr->file_info = mk_file_get_info(sr->real_path.data);
227 /* read permissions and check file */
228 if (sr->file_info->read_access == MK_FILE_FALSE) {
229 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
230 return -1;
233 /* Matching MimeType */
234 mime = mk_mimetype_find(&sr->real_path);
235 if (!mime) {
236 mime = mimetype_default;
239 if (sr->file_info->is_directory == MK_FILE_TRUE) {
240 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
241 return -1;
244 /* Plugin Stage 40: look for handlers for this request */
245 ret = mk_plugin_stage_run(MK_PLUGIN_STAGE_40, cr->socket, NULL, cr, sr);
246 if (ret == MK_PLUGIN_RET_CONTINUE) {
247 return ret;
250 /* get file size */
251 if (sr->file_info->size < 0) {
252 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr, 1, sr->log);
253 return -1;
256 /* counter connections */
257 sr->headers->pconnections_left = (int)
258 (config->max_keep_alive_request - cr->counter_connections);
261 gmt_file_unix_time =
262 PutDate_string((time_t) sr->file_info->last_modification);
264 if (sr->if_modified_since.data && sr->method == HTTP_METHOD_GET) {
265 time_t date_client; // Date send by client
266 time_t date_file_server; // Date server file
268 date_client = PutDate_unix(sr->if_modified_since.data);
269 date_file_server = sr->file_info->last_modification;
271 if ((date_file_server <= date_client) && (date_client > 0)) {
272 sr->headers->status = M_NOT_MODIFIED;
273 mk_header_send(cr->socket, cr, sr, sr->log);
274 mk_pointer_free(&gmt_file_unix_time);
275 return 0;
278 sr->headers->status = M_HTTP_OK;
279 sr->headers->cgi = SH_NOCGI;
280 sr->headers->last_modified = gmt_file_unix_time;
281 sr->headers->location = NULL;
283 /* Object size for log and response headers */
284 sr->log->size = sr->headers->content_length = sr->file_info->size;
285 sr->log->size_p = sr->headers->content_length_p =
286 mk_utils_int2mkp(sr->file_info->size);
288 if (sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST) {
289 sr->headers->content_type = mime->type;
290 /* Range */
291 if (sr->range.data != NULL && config->resume == VAR_ON) {
292 if (mk_http_range_parse(sr) < 0) {
293 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr, 1, sr->log);
294 mk_pointer_free(&gmt_file_unix_time);
295 return -1;
297 if (sr->headers->ranges[0] >= 0 || sr->headers->ranges[1] >= 0)
298 sr->headers->status = M_HTTP_PARTIAL;
301 else { /* without content-type */
302 mk_pointer_reset(&sr->headers->content_type);
305 mk_header_send(cr->socket, cr, sr, sr->log);
307 if (sr->headers->content_length == 0) {
308 return 0;
311 /* Sending file */
312 if ((sr->method == HTTP_METHOD_GET || sr->method == HTTP_METHOD_POST)
313 && sr->file_info->size > 0) {
314 sr->fd_file = open(sr->real_path.data, config->open_flags);
316 if (sr->fd_file == -1) {
317 perror("open");
318 return -1;
321 /* Calc bytes to send & offset */
322 if (mk_http_range_set(sr, sr->file_info->size) != 0) {
323 mk_request_error(M_CLIENT_BAD_REQUEST, cr, sr, 1, sr->log);
324 return -1;
326 bytes = SendFile(cr->socket, cr, sr);
329 return bytes;
332 int mk_http_directory_redirect_check(struct client_request *cr,
333 struct request *sr)
335 char *host;
336 char *location = 0;
337 char *real_location = 0;
338 unsigned long len;
341 * We have to check if exist an slash to the end of
342 * this string, if doesn't exist we send a redirection header
344 if (sr->uri_processed[strlen(sr->uri_processed) - 1] == '/') {
345 return 0;
348 host = mk_pointer_to_buf(sr->host);
350 m_build_buffer(&location, &len, "%s/", sr->uri_processed);
351 if (config->serverport == config->standard_port) {
352 m_build_buffer(&real_location, &len, "http://%s%s", host, location);
354 else {
355 m_build_buffer(&real_location, &len, "http://%s:%i%s",
356 host, config->serverport, location);
359 mk_mem_free(host);
361 sr->headers->status = M_REDIR_MOVED;
362 sr->headers->content_length = -1;
363 mk_pointer_reset(&sr->headers->content_type);
364 sr->headers->location = real_location;
365 sr->headers->cgi = SH_NOCGI;
366 sr->headers->pconnections_left =
367 (config->max_keep_alive_request - cr->counter_connections);
369 mk_header_send(cr->socket, cr, sr, sr->log);
370 mk_socket_set_cork_flag(cr->socket, TCP_CORK_OFF);
373 * we do not free() real_location
374 * as it's freed by iov
376 mk_mem_free(location);
377 sr->headers->location = NULL;
378 return -1;
382 * Check if a connection can continue open using as criteria
383 * the keepalive headers vars and Monkey configuration
385 int mk_http_keepalive_check(int socket, struct client_request *cr)
387 if (!cr->request) {
388 return -1;
391 if (config->keep_alive == VAR_OFF || cr->request->keep_alive == VAR_OFF) {
392 return -1;
395 if (cr->counter_connections >= config->max_keep_alive_request) {
396 return -1;
399 return 0;
402 int mk_http_range_set(struct request *sr, long file_size)
404 struct header_values *sh = sr->headers;
406 sr->bytes_to_send = file_size;
407 sr->bytes_offset = 0;
409 if (config->resume == VAR_ON && sr->range.data) {
410 /* yyy- */
411 if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) {
412 sr->bytes_offset = sh->ranges[0];
413 sr->bytes_to_send = file_size - sr->bytes_offset;
416 /* yyy-xxx */
417 if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) {
418 sr->bytes_offset = sh->ranges[0];
419 sr->bytes_to_send = labs(sh->ranges[1] - sh->ranges[0]) + 1;
422 /* -xxx */
423 if (sh->ranges[0] == -1 && sh->ranges[1] > 0) {
424 sr->bytes_to_send = sh->ranges[1];
425 sr->bytes_offset = file_size - sh->ranges[1];
428 if (sr->bytes_offset > file_size || sr->bytes_to_send > file_size) {
429 return -1;
432 lseek(sr->fd_file, sr->bytes_offset, SEEK_SET);
434 return 0;
439 int mk_http_range_parse(struct request *sr)
441 int eq_pos, sep_pos, len;
442 char *buffer = 0;
444 if (!sr->range.data)
445 return -1;
447 if ((eq_pos = mk_string_search_n(sr->range.data, "=", sr->range.len)) < 0)
448 return -1;
450 if (strncasecmp(sr->range.data, "Bytes", eq_pos) != 0)
451 return -1;
453 if ((sep_pos = mk_string_search_n(sr->range.data, "-",
454 sr->range.len)) < 0)
455 return -1;
457 len = sr->range.len;
459 /* =-xxx */
460 if (eq_pos + 1 == sep_pos) {
461 sr->headers->ranges[0] = -1;
462 sr->headers->ranges[1] =
463 (unsigned long) atol(sr->range.data + sep_pos + 1);
465 if (sr->headers->ranges[1] <= 0) {
466 return -1;
468 return 0;
471 /* =yyy-xxx */
472 if ((eq_pos + 1 != sep_pos) && (len > sep_pos + 1)) {
473 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, sep_pos);
474 sr->headers->ranges[0] = (unsigned long) atol(buffer);
475 mk_mem_free(buffer);
477 buffer = mk_string_copy_substr(sr->range.data, sep_pos + 1, len);
478 sr->headers->ranges[1] = (unsigned long) atol(buffer);
479 mk_mem_free(buffer);
481 if (sr->headers->ranges[1] <= 0 ||
482 sr->headers->ranges[0] > sr->headers->ranges[1]) {
483 return -1;
486 return 0;
488 /* =yyy- */
489 if ((eq_pos + 1 != sep_pos) && (len == sep_pos + 1)) {
490 buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, len);
491 sr->headers->ranges[0] = (unsigned long) atol(buffer);
492 mk_mem_free(buffer);
493 return 0;
496 return -1;
500 * Check if client request still has pending data
502 * Return 0 when all expected data has arrived or -1 when
503 * the connection is on a pending status due to HTTP spec
505 * This function is called from request.c :: mk_handler_read(..)
507 int mk_http_pending_request(struct client_request *cr)
509 int n, len;
510 char *str;
512 len = cr->body_length;
514 /* try to match CRLF end */
515 if (strcmp(cr->body + len - mk_endblock.len, mk_endblock.data) == 0) {
516 n = len - mk_endblock.len;
518 else {
519 n = mk_string_search(cr->body, mk_endblock.data);
522 if (n <= 0) {
523 return -1;
526 if (cr->first_block_end < 0) {
527 cr->first_block_end = n;
530 str = cr->body + n + mk_endblock.len;
532 if (cr->first_method == HTTP_METHOD_UNKNOWN) {
533 cr->first_method = mk_http_method_get(cr->body);
536 if (cr->first_method == HTTP_METHOD_POST) {
537 if (cr->first_block_end > 0) {
538 /* if first block has ended, we need to verify if exists
539 * a previous block end, that will means that the POST
540 * method has sent the whole information.
541 * just for ref: pipelining is not allowed with POST
543 if (cr->first_block_end == cr->body_length - mk_endblock.len) {
544 /* Content-length is required, if is it not found,
545 * we pass as successfull in order to raise the error
546 * later
548 if (mk_method_post_content_length(cr->body) < 0) {
549 cr->status = MK_REQUEST_STATUS_COMPLETED;
550 return 0;
553 else {
554 cr->status = MK_REQUEST_STATUS_COMPLETED;
555 return 0;
558 else {
559 return -1;
563 cr->status = MK_REQUEST_STATUS_COMPLETED;
564 return 0;
567 mk_pointer *mk_http_status_get(short int code)
569 mk_list_sint_t *l;
571 l = mk_http_status_list;
572 while (l) {
573 if (l->index == code) {
574 return &l->value;
576 else {
577 l = l->next;
581 return NULL;
584 void mk_http_status_add(short int val[2])
586 short i, len = 6;
587 char *str_val;
588 mk_list_sint_t *list, *new;
590 for (i = val[0]; i <= val[1]; i++) {
592 new = mk_mem_malloc(sizeof(mk_list_sint_t));
593 new->index = i;
594 new->next = NULL;
596 str_val = mk_mem_malloc(6);
597 snprintf(str_val, len - 1, "%i", i);
599 new->value.data = str_val;
600 new->value.len = 3;
602 if (!mk_http_status_list) {
603 mk_http_status_list = new;
605 else {
606 list = mk_http_status_list;
607 while (list->next)
608 list = list->next;
610 list->next = new;
611 list = new;
616 void mk_http_status_list_init()
618 /* Status type */
619 short int success[2] = { 200, 206 };
620 short int redirections[2] = { 300, 305 };
621 short int client_errors[2] = { 400, 415 };
622 short int server_errors[2] = { 500, 505 };
624 mk_http_status_add(success);
625 mk_http_status_add(redirections);
626 mk_http_status_add(client_errors);
627 mk_http_status_add(server_errors);