Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

898 lines (752 sloc) 21.462 kB
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Cherokee
*
* Authors:
* Alvaro Lopez Ortega <alvaro@alobbs.com>
*
* Copyright (C) 2001-2011 Alvaro Lopez Ortega
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "common-internal.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include "server.h"
#include "server-protected.h"
#include "dtm.h"
#include "mime.h"
#include "header.h"
#include "header-protected.h"
#include "handler_file.h"
#include "request.h"
#include "request-protected.h"
#include "module.h"
#include "iocache.h"
#include "util.h"
#include "handler_dirlist.h"
#include "error_log.h"
#define ENTRIES "handler,file"
/* Plug-in initialization
*/
PLUGIN_INFO_HANDLER_EASIEST_INIT (file, http_get | http_head | http_options);
/* Methods implementation
*/
ret_t
cherokee_handler_file_props_free (cherokee_handler_file_props_t *props)
{
return cherokee_handler_props_free_base (HANDLER_PROPS(props));
}
ret_t
cherokee_handler_file_configure (cherokee_config_node_t *conf,
cherokee_server_t *srv,
cherokee_module_props_t **_props)
{
ret_t ret;
cherokee_list_t *i;
cherokee_handler_file_props_t *props;
UNUSED(srv);
if (*_props == NULL) {
CHEROKEE_NEW_STRUCT (n, handler_file_props);
cherokee_handler_props_init_base (HANDLER_PROPS(n),
MODULE_PROPS_FREE(cherokee_handler_file_props_free));
n->use_cache = true;
*_props = MODULE_PROPS(n);
}
props = PROP_FILE(*_props);
cherokee_config_node_foreach (i, conf) {
cherokee_config_node_t *subconf = CONFIG_NODE(i);
if (equal_buf_str (&subconf->key, "iocache")) {
ret = cherokee_atob (subconf->val.buf, &props->use_cache);
if (ret != ret_ok) return ret;
}
}
return ret_ok;
}
ret_t
cherokee_handler_file_new (cherokee_handler_t **hdl,
cherokee_request_t *cnt,
cherokee_module_props_t *props)
{
CHEROKEE_NEW_STRUCT (n, handler_file);
TRACE_REQ(cnt);
/* Init the base class object
*/
cherokee_handler_init_base (HANDLER(n), cnt, HANDLER_PROPS(props), PLUGIN_INFO_HANDLER_PTR(file));
MODULE(n)->free = (module_func_free_t) cherokee_handler_file_free;
MODULE(n)->init = (handler_func_init_t) cherokee_handler_file_init;
HANDLER(n)->step = (handler_func_step_t) cherokee_handler_file_step;
HANDLER(n)->add_headers = (handler_func_add_headers_t) cherokee_handler_file_add_headers;
/* Supported features
*/
HANDLER(n)->support = hsupport_range;
/* Init
*/
n->fd = -1;
n->offset = 0;
n->mime = NULL;
n->info = NULL;
n->using_sendfile = false;
n->not_modified = false;
/* Return the object
*/
*hdl = HANDLER(n);
return ret_ok;
}
ret_t
cherokee_handler_file_free (cherokee_handler_file_t *fhdl)
{
if (fhdl->fd != -1) {
cherokee_fd_close (fhdl->fd);
fhdl->fd = -1;
}
return ret_ok;
}
static ret_t
check_cached (cherokee_handler_file_t *fhdl)
{
ret_t ret;
char *header;
cuint_t header_len;
cherokee_boolean_t has_modified_since = false;
cherokee_boolean_t has_etag = false;
cherokee_boolean_t not_modified_ms = false;
cherokee_boolean_t not_modified_etag = false;
cherokee_request_t *req = HANDLER_REQ(fhdl);
cherokee_thread_t *thread = HANDLER_THREAD(fhdl);
cherokee_buffer_t *etag_local = THREAD_TMP_BUF1(thread);
cherokee_buffer_clean (etag_local);
/* Based in time
*/
ret = cherokee_header_get_known (&req->header, header_if_modified_since, &header, &header_len);
if (ret == ret_ok) {
char tmp;
time_t req_time = 0;
char *end = header + header_len;
has_modified_since = true;
/* Set EOL
*/
tmp = *end;
*end = '\0';
/* Parse the Date string
*/
ret = cherokee_dtm_str2time (header, header_len, &req_time);
if (unlikely (ret == ret_error)) {
LOG_WARNING (CHEROKEE_ERROR_HANDLER_FILE_TIME_PARSE, header);
} else if (likely (ret == ret_ok)) {
/* The file is cached in the client
*/
if (fhdl->info->st_mtime <= req_time) {
not_modified_ms = true;
}
}
/* Restore EOL
*/
*end = tmp;
}
/* HTTP/1.1 only headers from now on
*/
if (req->header.version < http_version_11) {
fhdl->not_modified = not_modified_ms;
return ret_ok;
}
/* Based in ETag
*/
ret = cherokee_header_get_known (&req->header, header_if_none_match, &header, &header_len);
if (ret == ret_ok) {
/* Build local ETag
*/
if (cherokee_buffer_is_empty (etag_local)) {
cherokee_buffer_add_char (etag_local, '"');
cherokee_buffer_add_ullong16 (etag_local, (cullong_t) fhdl->info->st_mtime);
cherokee_buffer_add_str (etag_local, "=");
cherokee_buffer_add_ullong16 (etag_local, (cullong_t) fhdl->info->st_size);
cherokee_buffer_add_char (etag_local, '"');
}
/* Compare ETag(s)
*/
if ((header_len == etag_local->len) &&
(strncmp (header, etag_local->buf, etag_local->len) == 0))
{
not_modified_etag = true;
}
has_etag = true;
}
/* If-Range
*/
ret = cherokee_header_get_known (&req->header, header_if_range, &header, &header_len);
if (ret == ret_ok) {
char tmp;
char *end = header + header_len;
/* Set EOL
*/
tmp = *end;
*end = '\0';
/* If-Range: "<Etag>"
*/
if (strchr (header, '=')) {
/* Build local ETag if needed
*/
if (cherokee_buffer_is_empty (etag_local)) {
cherokee_buffer_add_char (etag_local, '"');
cherokee_buffer_add_ullong16 (etag_local, (cullong_t) fhdl->info->st_mtime);
cherokee_buffer_add_str (etag_local, "=");
cherokee_buffer_add_ullong16 (etag_local, (cullong_t) fhdl->info->st_size);
cherokee_buffer_add_char (etag_local, '"');
}
/* Compare ETags
*/
if ((header_len == etag_local->len) &&
(strncmp (header, etag_local->buf, etag_local->len) == 0))
{
not_modified_etag = true;
}
has_etag = true;
}
/* "If-Range: Sun, 14 Nov 2010 17:17:13 GMT"
*/
else {
time_t req_time = 0;
ret = cherokee_dtm_str2time (header, header_len, &req_time);
if (unlikely (ret == ret_error)) {
LOG_WARNING (CHEROKEE_ERROR_HANDLER_FILE_TIME_PARSE, header);
} else if (likely (ret == ret_ok)) {
/* If the entity tag given in the If-Range
* header matches the current entity tag for
* the entity, then the server SHOULD provide
* the specified sub-range of the entity using
* a 206 (Partial content) response. If the
* entity tag does not match, then the server
* SHOULD return the entire entity using a 200
* (OK) response.
*/
if (fhdl->info->st_mtime > req_time) {
req->error_code = http_ok;
req->range_start = -1;
req->range_end = -1;
}
}
}
/* Restore EOL
*/
*end = tmp;
}
/* If both If-Modified-Since and ETag have been found then
* both must match in order to return a not_modified response.
*/
if (has_modified_since && has_etag) {
if (not_modified_ms && not_modified_etag) {
fhdl->not_modified = true;
return ret_ok;
}
} else {
if (not_modified_ms || not_modified_etag) {
fhdl->not_modified = true;
return ret_ok;
}
}
return ret_ok;
}
static ret_t
open_local_directory (cherokee_handler_file_t *fhdl,
cherokee_buffer_t *local_file)
{
cherokee_request_t *req = HANDLER_REQ(fhdl);
/* Check if it is already open
*/
if (fhdl->fd > 0)
return ret_ok;
/* Open it
*/
fhdl->fd = cherokee_open (local_file->buf, O_RDONLY | O_BINARY, 0);
if (fhdl->fd > 0) {
cherokee_fd_set_closexec (fhdl->fd);
return ret_ok;
}
/* Manage errors
*/
switch (errno) {
case EACCES:
req->error_code = http_access_denied;
break;
case ENOENT:
req->error_code = http_not_found;
break;
default:
req->error_code = http_internal_error;
}
return ret_error;
}
static ret_t
stat_local_directory (cherokee_handler_file_t *fhdl,
cherokee_buffer_t *local_file,
cherokee_iocache_entry_t **io_entry,
struct stat **info)
{
int re;
ret_t ret;
cherokee_request_t *req = HANDLER_REQ(fhdl);
cherokee_server_t *srv = REQ_SRV(req);
/* I/O cache
*/
if ((srv->iocache != NULL) &&
(HDL_FILE_PROP(fhdl)->use_cache))
{
ret = cherokee_iocache_autoget (srv->iocache, local_file, iocache_stat, io_entry);
TRACE (ENTRIES, "%s, use_iocache=1 ret=%d\n", local_file->buf, ret);
switch (ret) {
case ret_ok:
case ret_ok_and_sent:
*info = &(*io_entry)->state;
return (*io_entry)->state_ret;
case ret_no_sys:
goto without;
case ret_deny:
req->error_code = http_access_denied;
break;
case ret_not_found:
req->error_code = http_not_found;
break;
default:
RET_UNKNOWN(ret);
req->error_code = http_internal_error;
}
return ret_error;
}
/* Without cache
*/
without:
re = cherokee_stat (local_file->buf, &fhdl->cache_info);
TRACE (ENTRIES, "%s, use_iocache=0 ret=%d\n", local_file->buf, re);
if (re >= 0) {
*info = &fhdl->cache_info;
return ret_ok;
}
switch (errno) {
case ENOENT:
req->error_code = http_not_found;
break;
case EACCES:
req->error_code = http_access_denied;
break;
default:
req->error_code = http_internal_error;
}
return ret_error;
}
ret_t
cherokee_handler_file_custom_init (cherokee_handler_file_t *fhdl,
cherokee_buffer_t *local_file)
{
ret_t ret;
char *ext;
cherokee_iocache_entry_t *io_entry = NULL;
cherokee_boolean_t use_io = false;
cherokee_request_t *req = HANDLER_REQ(fhdl);
cherokee_server_t *srv = HANDLER_SRV(fhdl);
/* Query the I/O cache
*/
ret = stat_local_directory (fhdl, local_file, &io_entry, &fhdl->info);
switch (ret) {
case ret_ok:
break;
case ret_not_found:
req->error_code = http_not_found;
ret = ret_error;
goto out;
default:
goto out;
}
/* Ensure it is a file
*/
if (S_ISDIR(fhdl->info->st_mode)) {
req->error_code = http_access_denied;
ret = ret_error;
goto out;
}
/* Look for the mime type
*/
if (srv->mime != NULL) {
ext = (local_file->buf + local_file->len) - 1;
while (ext > local_file->buf) {
if (*ext == '.') {
ret = cherokee_mime_get_by_suffix (srv->mime, ext+1, &fhdl->mime);
if (ret == ret_ok)
break;
}
ext--;
}
}
/* Is it cached on the client?
*/
ret = check_cached (fhdl);
if ((ret != ret_ok) || (fhdl->not_modified)) {
/* Set both ranges to zero to avoid file size errors in loggers
*/
req->range_start = 0;
req->range_end = 0;
goto out;
}
/* Is this file cached in the io cache?
*/
use_io = ((srv->iocache != NULL) &&
(req->encoder_new_func == NULL) &&
(HDL_FILE_PROP(fhdl)->use_cache) &&
(req->socket.is_tls == non_TLS) &&
(req->flcache.mode == flcache_mode_undef) &&
(http_method_with_body (req->header.method)) &&
(fhdl->info->st_size <= srv->iocache->max_file_size) &&
(fhdl->info->st_size >= srv->iocache->min_file_size));
TRACE(ENTRIES, "Using iocache %d\n", use_io);
if (use_io) {
ret = cherokee_iocache_autoget_fd (srv->iocache,
local_file,
iocache_mmap,
&fhdl->fd,
&io_entry);
TRACE (ENTRIES, "iocache looked up, local=%s ret=%d\n",
local_file->buf, ret);
switch (ret) {
case ret_ok:
case ret_ok_and_sent:
break;
case ret_no_sys:
use_io = false;
break;
case ret_deny:
req->error_code = http_access_denied;
ret = ret_error;
goto out;
case ret_not_found:
req->error_code = http_not_found;
ret = ret_error;
goto out;
default:
goto out;
}
/* Ensure the mmap content is ready
*/
if (io_entry->mmaped == NULL)
use_io = false;
}
/* Maybe open the file
*/
if (! use_io) {
ret = open_local_directory (fhdl, local_file);
if (ret != ret_ok) {
goto out;
}
}
/* Is it a directory?
*/
if (S_ISDIR (fhdl->info->st_mode)) {
req->error_code = http_access_denied;
ret = ret_error;
goto out;
}
/* Range 1: Check the range and file size
*/
if (unlikely (req->range_start >= fhdl->info->st_size))
{
/* Sets the range limits, so the error handler can
* report the error properly.
*/
req->range_start = 0;
req->range_end = fhdl->info->st_size;
req->error_code = http_range_not_satisfiable;
ret = ret_error;
goto out;
}
if (unlikely ((req->range_end >= fhdl->info->st_size)))
{
/* Range-end out of bounds. Set it to -1 so it is
* updated with the file size later on.
*/
req->range_end = -1;
}
/* Set the error code
*/
if ((req->range_start > -1) ||
(req->range_end > -1))
{
req->error_code = http_partial_content;
}
/* Range 2: Set the file length as the range end
*/
if (req->range_end == -1) {
if (req->range_start == -1) {
req->range_start = 0;
}
req->range_end = fhdl->info->st_size - 1;
} else {
if (req->range_start == -1) {
req->range_start = fhdl->info->st_size - req->range_end;
req->range_end = fhdl->info->st_size - 1;
}
}
/* Set mmap or file position
*/
if ((use_io) &&
(io_entry != NULL) &&
(io_entry->mmaped != NULL))
{
off_t len;
/* Set the mmap info
*/
req->io_entry_ref = io_entry;
len = req->range_end - req->range_start + 1;
req->mmaped = ((char *)io_entry->mmaped) + req->range_start;
req->mmaped_len = len;
} else {
/* Does no longer care about the io_entry
*/
cherokee_iocache_entry_unref (&io_entry);
/* Seek the file if needed
*/
if ((req->range_start != 0) && (req->mmaped == NULL)) {
fhdl->offset = req->range_start;
lseek (fhdl->fd, fhdl->offset, SEEK_SET);
}
}
/* Maybe use sendfile
*/
#ifdef WITH_SENDFILE
fhdl->using_sendfile = ((req->mmaped == NULL) &&
(req->encoder == NULL) &&
(req->encoder_new_func == NULL) &&
(req->socket.is_tls == non_TLS) &&
(req->flcache.mode == flcache_mode_undef) &&
(fhdl->info->st_size >= srv->sendfile.min) &&
(fhdl->info->st_size < srv->sendfile.max));
if (fhdl->using_sendfile) {
cherokee_request_set_cork (req, true);
BIT_SET (req->options, conn_op_tcp_cork);
}
#endif
return ret_ok;
out:
cherokee_iocache_entry_unref (&io_entry);
return ret;
}
ret_t
cherokee_handler_file_init (cherokee_handler_file_t *fhdl)
{
ret_t ret;
cherokee_request_t *req = HANDLER_REQ(fhdl);
/* OPTIONS request
*/
if (unlikely (HANDLER_REQ(fhdl)->header.method == http_options)) {
return ret_ok;
}
/* Build the local file path
*/
cherokee_buffer_add_buffer (&req->local_directory, &req->request);
ret = cherokee_handler_file_custom_init (fhdl, &req->local_directory);
/* Undo the local directory
*/
cherokee_buffer_drop_ending (&req->local_directory, req->request.len);
return ret;
}
ret_t
cherokee_handler_file_add_headers (cherokee_handler_file_t *fhdl,
cherokee_buffer_t *buffer)
{
ret_t ret;
char bufstr[DTM_SIZE_GMTTM_STR];
struct tm modified_tm;
size_t szlen = 0;
off_t content_length = 0;
cherokee_request_t *req = HANDLER_REQ(fhdl);
/* OPTIONS request
*/
if (unlikely (HANDLER_REQ(fhdl)->header.method == http_options)) {
cherokee_buffer_add_str (buffer, "Content-Length: 0"CRLF);
cherokee_handler_add_header_options (HANDLER(fhdl), buffer);
return ret_ok;
}
/* Regular request
*/
memset (&modified_tm, 0, sizeof(struct tm));
/* ETag: "<etag>"
*/
if (req->header.version >= http_version_11) {
/* ETag: "%lx= FMT_OFFSET_HEX" CRLF
*/
cherokee_buffer_add_str (buffer, "ETag: \"");
cherokee_buffer_add_ullong16(buffer, (cullong_t) fhdl->info->st_mtime);
cherokee_buffer_add_str (buffer, "=");
cherokee_buffer_add_ullong16(buffer, (cullong_t) fhdl->info->st_size);
cherokee_buffer_add_str (buffer, "\"" CRLF);
}
/* Last-Modified:
*/
cherokee_gmtime (&fhdl->info->st_mtime, &modified_tm);
szlen = cherokee_dtm_gmttm2str(bufstr, DTM_SIZE_GMTTM_STR, &modified_tm);
cherokee_buffer_add_str(buffer, "Last-Modified: ");
cherokee_buffer_add (buffer, bufstr, szlen);
cherokee_buffer_add_str(buffer, CRLF);
/* Add MIME related headers:
* "Content-Type:" and "Cache-Control: max-age="
*/
if (fhdl->mime != NULL) {
cuint_t maxage;
cherokee_buffer_t *mime = NULL;
cherokee_mime_entry_get_type (fhdl->mime, &mime);
cherokee_buffer_add_str (buffer, "Content-Type: ");
cherokee_buffer_add_buffer (buffer, mime);
cherokee_buffer_add_str (buffer, CRLF);
ret = cherokee_mime_entry_get_maxage (fhdl->mime, &maxage);
if (ret == ret_ok) {
/* Set the expiration if there wasn't a
* previous val. connection.c will render it.
*/
if (req->expiration == cherokee_expiration_none) {
req->expiration = cherokee_expiration_time;
req->expiration_time = maxage;
}
}
}
/* If it's replying "304 Not Modified", we're done here
*/
if (fhdl->not_modified) {
/* The handler will manage this special reply
*/
HANDLER(fhdl)->support |= hsupport_error;
req->error_code = http_not_modified;
return ret_ok;
}
if (cherokee_request_should_include_length(req)) {
HANDLER(fhdl)->support |= hsupport_length;
/* Content Length
*/
content_length = req->range_end - req->range_start + 1;
if (unlikely (content_length < 0)) {
content_length = 0;
}
if (req->error_code == http_partial_content) {
/*
* "Content-Range: bytes " FMT_OFFSET "-" FMT_OFFSET
* "/" FMT_OFFSET CRLF
*/
cherokee_buffer_add_str (buffer, "Content-Range: bytes ");
cherokee_buffer_add_ullong10(buffer, (cullong_t)req->range_start);
cherokee_buffer_add_str (buffer, "-");
cherokee_buffer_add_ullong10(buffer, (cullong_t)(req->range_end));
cherokee_buffer_add_str (buffer, "/");
cherokee_buffer_add_ullong10(buffer, (cullong_t)fhdl->info->st_size);
cherokee_buffer_add_str (buffer, CRLF);
}
/*
* "Content-Length: " FMT_OFFSET CRLF
*/
cherokee_buffer_add_str (buffer, "Content-Length: ");
cherokee_buffer_add_ullong10(buffer, (cullong_t) content_length);
cherokee_buffer_add_str (buffer, CRLF);
}
return ret_ok;
}
ret_t
cherokee_handler_file_step (cherokee_handler_file_t *fhdl, cherokee_buffer_t *buffer)
{
off_t total;
size_t size;
cherokee_request_t *req = HANDLER_REQ(fhdl);
/* OPTIONS request
*/
if (unlikely (HANDLER_REQ(fhdl)->header.method == http_options)) {
return ret_eof;
}
#ifdef WITH_SENDFILE
if (fhdl->using_sendfile) {
ret_t ret;
ssize_t sent;
off_t to_send;
to_send = req->range_end - fhdl->offset + 1;
if ((req->limit_bps > 0) &&
(req->limit_bps < to_send))
{
to_send = req->limit_bps;
}
ret = cherokee_socket_sendfile (&req->socket, /* cherokee_socket_t *socket */
fhdl->fd, /* int fd */
to_send, /* size_t size */
&fhdl->offset, /* off_t *offset */
&sent); /* ssize_t *sent */
/* cherokee_handler_file_init() activated the TCP_CORK
* flags. Then the response header was sent. Now, the
* first chunk of the file with sendfile(), so it's
* time to turn off the TCP_CORK flag again.
*/
if (req->options & conn_op_tcp_cork) {
cherokee_request_set_cork (req, false);
BIT_UNSET (req->options, conn_op_tcp_cork);
}
if (ret == ret_no_sys) {
fhdl->using_sendfile = false;
goto exit_sendfile;
}
if (ret != ret_ok) {
return ret;
}
/* This connection is not using the cherokee_request_send() method,
* so we have to update the connection traffic counter here.
*/
cherokee_request_tx_add (req, sent);
if (fhdl->offset >= req->range_end) {
return ret_eof;
}
return ret_ok_and_sent;
}
exit_sendfile:
#endif
/* Check the amount to read
*/
size = MIN (DEFAULT_READ_SIZE, (req->range_end - fhdl->offset + 1));
/* Ensure there's enough memory
*/
cherokee_buffer_ensure_size (buffer, size + 1);
/* Read
*/
do {
total = read (fhdl->fd, buffer->buf, size);
} while ((total == -1) && (errno == EINTR));
switch (total) {
case 0:
return ret_eof;
case -1:
return ret_error;
default:
buffer->len = total;
buffer->buf[buffer->len] = '\0';
fhdl->offset += total;
}
/* Maybe it was the last file chunk
*/
if (fhdl->offset >= req->range_end) {
return ret_eof_have_data;
}
return ret_ok;
}
ret_t
cherokee_handler_file_seek (cherokee_handler_file_t *hdl,
off_t start)
{
cherokee_request_t *req = HANDLER_REQ(hdl);
req->range_start = start;
hdl->offset = start;
return ret_ok;
}
Jump to Line
Something went wrong with that request. Please try again.