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 (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
)
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 (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
)
130 int debug_error
= 0, bytes
= 0;
131 struct mimetype
*mime
;
132 char *uri_data
= NULL
;
134 mk_pointer gmt_file_unix_time
; // gmt time of server file (unix time)
137 MK_TRACE("HTTP Protocol Init");
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
;
147 if (sr
->uri_processed
) {
148 uri_data
= sr
->uri_processed
;
149 uri_len
= strlen(sr
->uri_processed
);
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
,
166 MK_TRACE("Error composing real path");
172 if (sr
->method
!= HTTP_METHOD_HEAD
) {
176 /* Check backward directory request */
177 if (mk_string_search_n(uri_data
,
178 HTTP_DIRECTORY_BACKWARD
,
180 sr
->log
->final_response
= M_CLIENT_FORBIDDEN
;
181 mk_request_error(M_CLIENT_FORBIDDEN
, cr
, sr
, debug_error
, sr
->log
);
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
);
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
)
204 mk_request_error(M_CLIENT_NOT_FOUND
, cr
, sr
, debug_error
, sr
->log
);
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
);
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 */
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
);
250 /* Matching MimeType */
251 mime
= mk_mimetype_find(&sr
->real_path
);
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
);
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
) {
268 if (sr
->file_info
->size
< 0) {
269 mk_request_error(M_CLIENT_NOT_FOUND
, cr
, sr
, 1, sr
->log
);
273 /* counter connections */
274 sr
->headers
->pconnections_left
= (int)
275 (config
->max_keep_alive_request
- cr
->counter_connections
);
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
);
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
) {
306 sr
->log
->size_p
= mk_utils_int2mkp(0);
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
;
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
);
322 if (sr
->headers
->ranges
[0] >= 0 || sr
->headers
->ranges
[1] >= 0)
323 sr
->headers
->status
= M_HTTP_PARTIAL
;
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) {
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) {
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
);
352 bytes
= SendFile(cr
->socket
, cr
, sr
);
358 int mk_http_directory_redirect_check(struct client_request
*cr
,
363 char *real_location
= 0;
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] == '/') {
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
);
381 m_build_buffer(&real_location
, &len
, "http://%s:%i%s",
382 host
, config
->serverport
, location
);
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
;
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
)
417 if (config
->keep_alive
== VAR_OFF
|| cr
->request
->keep_alive
== VAR_OFF
) {
421 if (cr
->counter_connections
>= config
->max_keep_alive_request
) {
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
) {
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
;
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;
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
) {
458 lseek(sr
->fd_file
, sr
->bytes_offset
, SEEK_SET
);
465 int mk_http_range_parse(struct request
*sr
)
467 int eq_pos
, sep_pos
, len
;
473 if ((eq_pos
= mk_string_search_n(sr
->range
.data
, "=", sr
->range
.len
)) < 0)
476 if (strncasecmp(sr
->range
.data
, "Bytes", eq_pos
) != 0)
479 if ((sep_pos
= mk_string_search_n(sr
->range
.data
, "-",
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) {
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
);
504 buffer
= mk_string_copy_substr(sr
->range
.data
, sep_pos
+ 1, len
);
505 sr
->headers
->ranges
[1] = (unsigned long) atol(buffer
);
508 if (sr
->headers
->ranges
[1] <= 0 ||
509 sr
->headers
->ranges
[0] > sr
->headers
->ranges
[1]) {
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
);
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
)
538 if (cr
->body_length
>= mk_endblock
.len
) {
539 end
= (cr
->body
+ cr
->body_length
) - mk_endblock
.len
;
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
;
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
575 if (mk_method_post_content_length(cr
->body
) < 0) {
576 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
581 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
590 cr
->status
= MK_REQUEST_STATUS_COMPLETED
;
594 mk_pointer
*mk_http_status_get(short int code
)
598 l
= mk_http_status_list
;
600 if (l
->index
== code
) {
611 void mk_http_status_add(short int val
[2])
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
));
623 str_val
= mk_mem_malloc(6);
624 snprintf(str_val
, len
- 1, "%i", i
);
626 new->value
.data
= str_val
;
629 if (!mk_http_status_list
) {
630 mk_http_status_list
= new;
633 list
= mk_http_status_list
;
643 void mk_http_status_list_init()
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
);