HTTP: Optimize request parser
[MonkeyD.git] / src / http.c
blob77a2132a35eb1766799a6e86aed6c1a59226c4c6
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
3 /* Monkey HTTP Daemon
4 * ------------------
5 * Copyright (C) 2001-2008, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "cgi.h"
38 #include "str.h"
39 #include "method.h"
40 #include "socket.h"
41 #include "mimetype.h"
42 #include "logfile.h"
43 #include "header.h"
44 #include "plugin.h"
46 int mk_http_method_check(mk_pointer method)
48 if(strncasecmp(method.data,
49 HTTP_METHOD_GET_STR,
50 method.len)==0)
52 return HTTP_METHOD_GET;
55 if(strncasecmp(method.data,
56 HTTP_METHOD_POST_STR,
57 method.len)==0)
59 return HTTP_METHOD_POST;
62 if(strncasecmp(method.data,
63 HTTP_METHOD_HEAD_STR,
64 method.len)==0)
66 return HTTP_METHOD_HEAD;
69 return METHOD_NOT_FOUND;
72 mk_pointer mk_http_method_check_str(int method)
74 switch(method){
75 case HTTP_METHOD_GET:
76 return mk_http_method_get_p;
78 case HTTP_METHOD_POST:
79 return mk_http_method_post_p;
81 case HTTP_METHOD_HEAD:
82 return mk_http_method_head_p;
84 return mk_http_method_null_p;
87 int mk_http_method_get(char *body)
89 int int_method, pos = 0;
90 int max_len_method = 5;
91 mk_pointer method;
93 /* Max method length is 4 (POST/HEAD) */
94 pos = mk_string_char_search(body, ' ', 4);
95 if(pos<=2 || pos>=max_len_method){
96 return METHOD_NOT_FOUND;
99 method.data = body;
100 method.len = (unsigned long) pos;
102 int_method = mk_http_method_check(method);
104 return int_method;
107 int mk_http_protocol_check(char *protocol, int len)
109 if(strncasecmp(protocol, HTTP_PROTOCOL_11_STR, len)==0)
111 return HTTP_PROTOCOL_11;
113 if(strncasecmp(protocol, HTTP_PROTOCOL_10_STR, len)==0)
115 return HTTP_PROTOCOL_10;
117 if(strncasecmp(protocol, HTTP_PROTOCOL_09_STR, len)==0)
119 return HTTP_PROTOCOL_09;
122 return HTTP_PROTOCOL_UNKNOWN;
125 mk_pointer mk_http_protocol_check_str(int protocol)
127 if(protocol==HTTP_PROTOCOL_11)
129 return mk_http_protocol_11_p;
131 if(protocol==HTTP_PROTOCOL_10)
133 return mk_http_protocol_10_p;
135 if(protocol==HTTP_PROTOCOL_09)
137 return mk_http_protocol_09_p;
140 return mk_http_protocol_null_p;
143 int mk_http_init(struct client_request *cr, struct request *sr)
145 int debug_error=0, bytes=0;
146 struct mimetype *mime;
147 mk_pointer gmt_file_unix_time; // gmt time of server file (unix time)
148 char *palm;
150 /* Normal request default site */
151 if((strcmp(sr->uri_processed,"/"))==0)
153 sr->real_path.data = mk_string_dup(sr->host_conf->documentroot.data);
154 sr->real_path.len = sr->host_conf->documentroot.len;
157 if(sr->user_home==VAR_OFF)
159 mk_buffer_cat(&sr->real_path, sr->host_conf->documentroot.data,
160 sr->uri_processed);
163 if(sr->method!=HTTP_METHOD_HEAD){
164 debug_error=1;
167 sr->file_info = mk_file_get_info(sr->real_path.data);
169 if(!sr->file_info){
170 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr,
171 debug_error, sr->log);
172 return -1;
175 /* Check symbolic link file */
176 if(sr->file_info->is_link == MK_FILE_TRUE){
177 if(config->symlink==VAR_OFF){
178 sr->log->final_response=M_CLIENT_FORBIDDEN;
179 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr,
180 debug_error, sr->log);
181 return -1;
183 else{
184 int n;
185 char linked_file[MAX_PATH];
186 n = readlink(sr->real_path.data, linked_file, MAX_PATH);
188 if(Deny_Check(linked_file)==-1) {
189 sr->log->final_response=M_CLIENT_FORBIDDEN;
190 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, debug_error, sr->log);
191 return -1;
198 /* is it a valid directory ? */
199 if(sr->file_info->is_directory == MK_FILE_TRUE) {
200 /* Send redirect header if end slash is not found */
201 if(mk_http_directory_redirect_check(cr, sr) == -1){
202 /* Redirect has been sent */
203 return -1;
206 /* looking for a index file */
207 mk_pointer index_file;
208 index_file = mk_request_index(sr->real_path.data);
210 if(index_file.data) {
211 mk_mem_free(sr->file_info);
212 mk_pointer_free(&sr->real_path);
214 sr->real_path = index_file;
215 sr->file_info = mk_file_get_info(sr->real_path.data);
219 /* read permissions and check file */
220 if(sr->file_info->read_access == MK_FILE_FALSE){
221 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
222 return -1;
225 /* Matching MimeType */
226 mime = mk_mimetype_find(&sr->real_path);
227 if(!mime)
229 mime = mimetype_default;
232 /* Plugin Stage 40: look for handlers for this request */
233 if(mk_plugin_stage_run(MK_PLUGIN_STAGE_40, cr, sr) == 0){
234 return -1;
237 if(sr->file_info->is_directory == MK_FILE_TRUE){
238 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
239 return -1;
242 /* FIXME: Move palm code to a plugin */
243 palm = mk_palm_check_request(cr, sr);
244 if(palm)
246 mk_palm_send_response(cr, sr, palm);
247 return -1;
251 /* get file size */
252 if(sr->file_info->size < 0) {
253 mk_request_error(M_CLIENT_NOT_FOUND, cr, sr, 1, sr->log);
254 return -1;
257 /* counter connections */
258 sr->headers->pconnections_left = (int)
259 (config->max_keep_alive_request - cr->counter_connections);
262 gmt_file_unix_time =
263 PutDate_string((time_t) sr->file_info->last_modification);
265 if(sr->if_modified_since.data && sr->method==HTTP_METHOD_GET){
266 time_t date_client; // Date send by client
267 time_t date_file_server; // Date server file
269 date_client = PutDate_unix(sr->if_modified_since.data);
270 date_file_server = sr->file_info->last_modification;
272 if( (date_file_server <= date_client) && (date_client > 0) )
274 sr->headers->status = M_NOT_MODIFIED;
275 mk_header_send(cr->socket, cr, sr, sr->log);
276 mk_pointer_free(&gmt_file_unix_time);
277 return 0;
280 sr->headers->status = M_HTTP_OK;
281 sr->headers->cgi = SH_NOCGI;
282 sr->headers->last_modified = gmt_file_unix_time;
283 sr->headers->location = NULL;
285 /* Object size for log and response headers */
286 sr->log->size = sr->headers->content_length = sr->file_info->size;
287 sr->log->size_p = sr->headers->content_length_p =
288 mk_utils_int2mkp(sr->file_info->size);
290 if(sr->method==HTTP_METHOD_GET || sr->method==HTTP_METHOD_POST)
292 sr->headers->content_type = mime->type;
293 /* Range */
294 if(sr->range.data!=NULL && config->resume==VAR_ON){
295 if(mk_http_range_parse(sr)<0)
297 mk_request_error(M_CLIENT_BAD_REQUEST, cr,
298 sr, 1, sr->log);
299 mk_pointer_free(&gmt_file_unix_time);
300 return -1;
302 if(sr->headers->ranges[0]>=0 || sr->headers->ranges[1]>=0)
303 sr->headers->status = M_HTTP_PARTIAL;
306 else{ /* without content-type */
307 mk_pointer_reset(&sr->headers->content_type);
310 mk_header_send(cr->socket, cr, sr, sr->log);
312 if(sr->headers->content_length==0){
313 return 0;
316 /* Sending file */
317 if((sr->method==HTTP_METHOD_GET || sr->method==HTTP_METHOD_POST)
318 && sr->file_info->size>0)
320 sr->fd_file = open(sr->real_path.data, config->open_flags);
322 if(sr->fd_file == -1){
323 perror("open");
324 return -1;
327 /* Calc bytes to send & offset */
328 if(mk_http_range_set(sr, sr->file_info->size)!=0)
330 mk_request_error(M_CLIENT_BAD_REQUEST, cr,
331 sr, 1, sr->log);
332 return -1;
335 bytes = SendFile(cr->socket, sr);
338 return bytes;
341 int mk_http_directory_redirect_check(struct client_request *cr,
342 struct request *sr)
344 char *host;
345 char *location=0;
346 char *real_location=0;
347 unsigned long len;
350 * We have to check if exist an slash to the end of
351 * this string, if doesn't exist we send a redirection header
353 if(sr->uri_processed[strlen(sr->uri_processed) - 1] == '/') {
354 return 0;
357 host = mk_pointer_to_buf(sr->host);
359 m_build_buffer(&location, &len, "%s/", sr->uri_processed);
360 if(config->serverport == config->standard_port)
362 m_build_buffer(&real_location, &len, "http://%s%s",
363 host, location);
365 else{
366 m_build_buffer(&real_location, &len, "http://%s:%i%s",
367 host, config->serverport,
368 location);
371 mk_mem_free(host);
373 sr->headers->status = M_REDIR_MOVED;
374 sr->headers->content_length = -1;
375 mk_pointer_reset(&sr->headers->content_type);
376 sr->headers->location = real_location;
377 sr->headers->cgi = SH_NOCGI;
378 sr->headers->pconnections_left =
379 (config->max_keep_alive_request -
380 cr->counter_connections);
382 mk_header_send(cr->socket, cr, sr, sr->log);
383 mk_socket_set_cork_flag(cr->socket, TCP_CORK_OFF);
386 * we do not free() real_location
387 * as it's freed by iov
389 mk_mem_free(location);
390 sr->headers->location=NULL;
391 return -1;
395 * Check if a connection can continue open using as criteria
396 * the keepalive headers vars and Monkey configuration
398 int mk_http_keepalive_check(int socket, struct client_request *cr)
400 if(!cr->request)
402 return -1;
405 if(config->keep_alive==VAR_OFF || cr->request->keep_alive==VAR_OFF)
407 return -1;
410 if(cr->counter_connections>=config->max_keep_alive_request)
412 return -1;
415 return 0;
418 int mk_http_range_set(struct request *sr, long file_size)
420 struct header_values *sh = sr->headers;
422 sr->bytes_to_send = file_size;
423 sr->bytes_offset = 0;
425 if(config->resume==VAR_ON && sr->range.data){
426 /* yyy- */
427 if(sh->ranges[0]>=0 && sh->ranges[1]==-1){
428 sr->bytes_offset = sh->ranges[0];
429 sr->bytes_to_send = file_size - sr->bytes_offset;
432 /* yyy-xxx */
433 if(sh->ranges[0]>=0 && sh->ranges[1]>=0){
434 sr->bytes_offset = sh->ranges[0];
435 sr->bytes_to_send = labs(sh->ranges[1]-sh->ranges[0])+1;
438 /* -xxx */
439 if(sh->ranges[0]==-1 && sh->ranges[1]>0){
440 sr->bytes_to_send = sh->ranges[1];
441 sr->bytes_offset = file_size - sh->ranges[1];
444 if(sr->bytes_offset>file_size || sr->bytes_to_send>file_size)
446 return -1;
449 lseek(sr->fd_file, sr->bytes_offset, SEEK_SET);
451 return 0;
456 int mk_http_range_parse(struct request *sr)
458 int eq_pos, sep_pos, len;
459 char *buffer=0;
461 if(!sr->range.data)
462 return -1;
464 if((eq_pos = mk_string_search_n(sr->range.data, "=",
465 sr->range.len))<0)
466 return -1;
468 if(strncasecmp(sr->range.data, "Bytes", eq_pos)!=0)
469 return -1;
471 if((sep_pos = mk_string_search_n(sr->range.data, "-",
472 sr->range.len))<0)
473 return -1;
475 len = sr->range.len;
477 /* =-xxx */
478 if(eq_pos+1 == sep_pos){
479 sr->headers->ranges[0] = -1;
480 sr->headers->ranges[1] = (unsigned long) atol(sr->range.data + sep_pos + 1);
482 if(sr->headers->ranges[1]<=0)
484 return -1;
486 return 0;
489 /* =yyy-xxx */
490 if( (eq_pos+1 != sep_pos) && (len > sep_pos + 1))
492 buffer = mk_string_copy_substr(sr->range.data, eq_pos+1, sep_pos);
493 sr->headers->ranges[0] = (unsigned long) atol(buffer);
494 mk_mem_free(buffer);
496 buffer = mk_string_copy_substr(sr->range.data, sep_pos+1, len);
497 sr->headers->ranges[1] = (unsigned long) atol(buffer);
498 mk_mem_free(buffer);
500 if(sr->headers->ranges[1]<=0 ||
501 sr->headers->ranges[0]>sr->headers->ranges[1])
503 return -1;
506 return 0;
508 /* =yyy- */
509 if( (eq_pos+1 != sep_pos) && (len == sep_pos + 1))
511 buffer = mk_string_copy_substr(sr->range.data, eq_pos+1, len);
512 sr->headers->ranges[0] = (unsigned long) atol(buffer);
513 mk_mem_free(buffer);
514 return 0;
517 return -1;
521 * Check if client request still has pending data
523 * Return 0 when all expected data has arrived or -1 when
524 * the connection is on a pending status due to HTTP spec
526 * This function is called from request.c :: mk_handler_read(..)
528 int mk_http_pending_request(struct client_request *cr)
530 int n,len;
531 char *str;
533 len = cr->body_length;
535 /* try to match CRLF end */
536 if(strcmp(cr->body+len-mk_endblock.len, mk_endblock.data) == 0){
537 n = len-mk_endblock.len;
539 else{
540 n = mk_string_search(cr->body, mk_endblock.data);
543 if(n<=0)
545 return -1;
548 if(cr->first_block_end<0)
550 cr->first_block_end = n;
553 str = cr->body + n + mk_endblock.len;
555 if(cr->first_method == HTTP_METHOD_UNKNOWN){
556 cr->first_method = mk_http_method_get(cr->body);
559 if(cr->first_method == HTTP_METHOD_POST)
561 if(cr->first_block_end > 0){
562 /* if first block has ended, we need to verify if exists
563 * a previous block end, that will means that the POST
564 * method has sent the whole information.
565 * just for ref: pipelining is not allowed with POST
567 if(cr->first_block_end == cr->body_length-mk_endblock.len){
568 /* Content-length is required, if is it not found,
569 * we pass as successfull in order to raise the error
570 * later
572 if(mk_method_post_content_length(cr->body) < 0){
573 cr->status = MK_REQUEST_STATUS_COMPLETED;
574 return 0;
577 else{
578 cr->status = MK_REQUEST_STATUS_COMPLETED;
579 return 0;
582 else{
583 return -1;
587 cr->status = MK_REQUEST_STATUS_COMPLETED;
588 return 0;
591 mk_pointer *mk_http_status_get(short int code)
593 mk_list_sint_t *l;
595 l = mk_http_status_list;
596 while(l)
598 if(l->index == code)
600 return &l->value;
602 else {
603 l = l->next;
607 return NULL;
610 void mk_http_status_add(short int val[2])
612 short i, len=6;
613 char *str_val;
614 mk_list_sint_t *list, *new;
616 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)
631 mk_http_status_list = new;
633 else{
634 list = mk_http_status_list;
635 while(list->next)
636 list = list->next;
638 list->next = new;
639 list = new;
644 void mk_http_status_list_init()
646 /* Status type */
647 short int success[2] = {200, 206};
648 short int redirections[2] = {300, 305};
649 short int client_errors[2] = {400, 415};
650 short int server_errors[2] = {500, 505};
652 mk_http_status_add(success);
653 mk_http_status_add(redirections);
654 mk_http_status_add(client_errors);
655 mk_http_status_add(server_errors);