1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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.
26 #include <sys/types.h>
33 #include "http_status.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
)
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;
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
;
90 method
.len
= (unsigned long) pos
;
92 int_method
= mk_http_method_check(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
)
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)
135 MK_TRACE("HTTP Protocol Init");
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
,
149 if (sr
->method
!= HTTP_METHOD_HEAD
) {
153 if (mk_string_search_n(sr
->uri
.data
, HTTP_DIRECTORY_BACKWARD
,
155 sr
->log
->final_response
= M_CLIENT_FORBIDDEN
;
156 mk_request_error(M_CLIENT_FORBIDDEN
, cr
, sr
, debug_error
, sr
->log
);
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
);
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
)
179 mk_request_error(M_CLIENT_NOT_FOUND
, cr
, sr
, debug_error
, sr
->log
);
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
);
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);
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 */
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
);
233 /* Matching MimeType */
234 mime
= mk_mimetype_find(&sr
->real_path
);
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
);
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
) {
251 if (sr
->file_info
->size
< 0) {
252 mk_request_error(M_CLIENT_NOT_FOUND
, cr
, sr
, 1, sr
->log
);
256 /* counter connections */
257 sr
->headers
->pconnections_left
= (int)
258 (config
->max_keep_alive_request
- cr
->counter_connections
);
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
);
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
;
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
);
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) {
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) {
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
);
326 bytes
= SendFile(cr
->socket
, cr
, sr
);
332 int mk_http_directory_redirect_check(struct client_request
*cr
,
337 char *real_location
= 0;
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] == '/') {
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
);
355 m_build_buffer(&real_location
, &len
, "http://%s:%i%s",
356 host
, config
->serverport
, location
);
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
;
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
)
391 if (config
->keep_alive
== VAR_OFF
|| cr
->request
->keep_alive
== VAR_OFF
) {
395 if (cr
->counter_connections
>= config
->max_keep_alive_request
) {
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
) {
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
;
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;
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
) {
432 lseek(sr
->fd_file
, sr
->bytes_offset
, SEEK_SET
);
439 int mk_http_range_parse(struct request
*sr
)
441 int eq_pos
, sep_pos
, len
;
447 if ((eq_pos
= mk_string_search_n(sr
->range
.data
, "=", sr
->range
.len
)) < 0)
450 if (strncasecmp(sr
->range
.data
, "Bytes", eq_pos
) != 0)
453 if ((sep_pos
= mk_string_search_n(sr
->range
.data
, "-",
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) {
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
);
477 buffer
= mk_string_copy_substr(sr
->range
.data
, sep_pos
+ 1, len
);
478 sr
->headers
->ranges
[1] = (unsigned long) atol(buffer
);
481 if (sr
->headers
->ranges
[1] <= 0 ||
482 sr
->headers
->ranges
[0] > sr
->headers
->ranges
[1]) {
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
);
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
)
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
;
519 n
= mk_string_search(cr
->body
, mk_endblock
.data
);
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
548 if (mk_method_post_content_length(cr
->body
) < 0) {
549 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
554 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
563 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
567 mk_pointer
*mk_http_status_get(short int code
)
571 l
= mk_http_status_list
;
573 if (l
->index
== code
) {
584 void mk_http_status_add(short int val
[2])
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
));
596 str_val
= mk_mem_malloc(6);
597 snprintf(str_val
, len
- 1, "%i", i
);
599 new->value
.data
= str_val
;
602 if (!mk_http_status_list
) {
603 mk_http_status_list
= new;
606 list
= mk_http_status_list
;
616 void mk_http_status_list_init()
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
);