diff options
author | Jan Kneschke <jan@kneschke.de> | 2005-08-15 09:55:23 +0000 |
---|---|---|
committer | Jan Kneschke <jan@kneschke.de> | 2005-08-15 09:55:23 +0000 |
commit | d8394f7f2e9b144ede2477d082d6669dec52acc7 (patch) | |
tree | bff5733fd85ab6ddaf94307294d15f8d02c605e7 | |
parent | c92984c270b4260325578265bb606b4bb8efe8fd (diff) | |
download | lighttpd-git-d8394f7f2e9b144ede2477d082d6669dec52acc7.tar.gz |
moved code to mod_staticfile, mod_dirlisting and mod_indexfile
git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-merge-1.4.x@541 152afb58-edef-0310-8abb-c4023f1b3aa9
-rw-r--r-- | src/Makefile.am | 14 | ||||
-rw-r--r-- | src/base.h | 9 | ||||
-rw-r--r-- | src/configfile-glue.c | 68 | ||||
-rw-r--r-- | src/configfile.c | 135 | ||||
-rw-r--r-- | src/connections.c | 314 | ||||
-rw-r--r-- | src/mod_dirlisting.c | 650 | ||||
-rw-r--r-- | src/mod_indexfile.c | 213 | ||||
-rw-r--r-- | src/mod_staticfile.c | 575 | ||||
-rw-r--r-- | src/request.c | 8 | ||||
-rw-r--r-- | src/response.c | 1104 | ||||
-rw-r--r-- | src/response.h | 5 | ||||
-rw-r--r-- | src/server.c | 11 |
12 files changed, 1769 insertions, 1337 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 8236b82a..e32c5dbd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -94,6 +94,20 @@ mod_scgi_la_SOURCES = mod_scgi.c mod_scgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_scgi_la_LIBADD = $(common_libadd) +lib_LTLIBRARIES += mod_staticfile.la +mod_staticfile_la_SOURCES = mod_staticfile.c +mod_staticfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_staticfile_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_dirlisting.la +mod_dirlisting_la_SOURCES = mod_dirlisting.c +mod_dirlisting_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_dirlisting_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_indexfile.la +mod_indexfile_la_SOURCES = mod_indexfile.c +mod_indexfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_indexfile_la_LIBADD = $(common_libadd) lib_LTLIBRARIES += mod_setenv.la mod_setenv_la_SOURCES = mod_setenv.c @@ -223,7 +223,6 @@ typedef struct { } stat_cache; typedef struct { - array *indexfiles; array *mimetypes; /* virtual-servers */ @@ -231,12 +230,9 @@ typedef struct { buffer *server_name; buffer *error_handler; buffer *server_tag; - buffer *dirlist_css; buffer *dirlist_encoding; buffer *errorfile_prefix; - unsigned short dir_listing; - unsigned short hide_dotfiles; unsigned short max_keep_alive_requests; unsigned short max_keep_alive_idle; unsigned short max_read_idle; @@ -260,6 +256,7 @@ typedef struct { unsigned short use_ipv6; unsigned short is_ssl; unsigned short allow_http11; + unsigned short force_lower_case; /* if the FS is case-insensitive, force all files to lower-case */ unsigned short max_request_size; unsigned short kbytes_per_second; /* connection kb/s limit */ @@ -513,9 +510,7 @@ typedef struct { buffer *tmp_chunk_len; - buffer *range_buf; - - buffer *empty_string; /* is necessary for cond_match */ + buffer *cond_check_buf; /* caches */ #ifdef HAVE_IPV6 diff --git a/src/configfile-glue.c b/src/configfile-glue.c index 3f20f6e7..c4c4c4ee 100644 --- a/src/configfile-glue.c +++ b/src/configfile-glue.c @@ -147,6 +147,14 @@ int config_insert_values_global(server *srv, array *ca, const config_values_t cv return config_insert_values_internal(srv, ca, cv); } +unsigned short sock_addr_get_port(sock_addr *addr) { +#ifdef HAVE_IPV6 + return ntohs(addr->plain.sa_family ? addr->ipv6.sin6_port : addr->ipv4.sin_port); +#else + return ntohs(addr->ipv4.sin_port); +#endif +} + static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc); static cond_result_t config_check_cond_nocache(server *srv, connection *con, data_config *dc) { @@ -176,13 +184,8 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat /* pass the rules */ - l = srv->empty_string; - switch (dc->comp) { case COMP_HTTP_HOST: { - l = con->uri.authority; -#if 0 - /* FIXME: get this working again */ char *ck_colon = NULL, *val_colon = NULL; if (!buffer_is_empty(con->uri.authority)) { @@ -191,23 +194,35 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat * append server-port to the HTTP_POST if necessary */ - buffer_copy_string_buffer(srv->cond_check_buf, con->uri.authority); + l = con->uri.authority; switch(dc->cond) { case CONFIG_COND_NE: case CONFIG_COND_EQ: ck_colon = strchr(dc->string->ptr, ':'); - val_colon = strchr(con->uri.authority->ptr, ':'); + val_colon = strchr(l->ptr, ':'); - if (ck_colon && !val_colon) { - /* colon found */ + if (ck_colon == val_colon) { + /* nothing to do with it */ + break; + } + if (ck_colon) { + /* condition "host:port" but client send "host" */ + buffer_copy_string_buffer(srv->cond_check_buf, l); BUFFER_APPEND_STRING_CONST(srv->cond_check_buf, ":"); buffer_append_long(srv->cond_check_buf, sock_addr_get_port(&(srv_sock->addr))); + l = srv->cond_check_buf; + } else if (!ck_colon) { + /* condition "host" but client send "host:port" */ + buffer_copy_string_len(srv->cond_check_buf, l->ptr, val_colon - l->ptr); + l = srv->cond_check_buf; } break; default: break; } + } else { + l = NULL; } break; } @@ -271,21 +286,8 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE; } } else { - const char *s; -#ifdef HAVE_IPV6 - char b2[INET6_ADDRSTRLEN + 1]; - - s = inet_ntop(con->dst_addr.plain.sa_family, - con->dst_addr.plain.sa_family == AF_INET6 ? - (const void *) &(con->dst_addr.ipv6.sin6_addr) : - (const void *) &(con->dst_addr.ipv4.sin_addr), - b2, sizeof(b2)-1); -#else - s = inet_ntoa(con->dst_addr.ipv4.sin_addr); -#endif - buffer_copy_string(srv->cond_check_buf, s); + l = con->dst_addr_buf; } -#endif break; } case COMP_HTTP_URL: @@ -301,6 +303,8 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) { l = ds->value; + } else { + l = NULL; } break; } @@ -308,6 +312,8 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat data_string *ds; if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) { l = ds->value; + } else { + l = NULL; } break; } @@ -315,6 +321,8 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat data_string *ds; if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) { l = ds->value; + } else { + l = NULL; } break; } @@ -323,8 +331,17 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat return COND_RESULT_FALSE; } + if (NULL == l) { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "bsbs", dc->comp_key, + "(", l, ") compare to NULL"); + } + return COND_RESULT_FALSE; + } + if (con->conf.log_condition_handling) { - log_error_write(srv, __FILE__, __LINE__, "bsbsb", dc->comp_key, "(", l, ") compare to ", dc->string); + log_error_write(srv, __FILE__, __LINE__, "bsbsb", dc->comp_key, + "(", l, ") compare to ", dc->string); } switch(dc->cond) { case CONFIG_COND_NE: @@ -387,8 +404,7 @@ static cond_result_t config_check_cond_cached(server *srv, connection *con, data "(uncached) result:", caches[dc->context_ndx].result == COND_RESULT_TRUE ? "true" : "false"); } - } - else { + } else { if (con->conf.log_condition_handling) { log_error_write(srv, __FILE__, __LINE__, "dss", dc->context_ndx, "(cached) result:", diff --git a/src/configfile.c b/src/configfile.c index a58de1a1..537de673 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -26,6 +26,7 @@ static int config_insert(server *srv) { size_t i; int ret = 0; buffer *stat_cache_string; + data_string *ds; config_values_t cv[] = { { "server.bind", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 0 */ @@ -44,8 +45,8 @@ static int config_insert(server *srv) { { "server.max-request-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ { "server.max-worker", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 13 */ { "server.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ - { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ - { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "server.force-lower-case-files", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 15 */ + { "debug.log-condition-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 16 */ { "server.max-keep-alive-requests", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ { "server.name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ { "server.max-keep-alive-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 19 */ @@ -73,15 +74,9 @@ static int config_insert(server *srv) { { "debug.log-state-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 37 */ { "ssl.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 38 */ - { "dir-listing.hide-dotfiles", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 39 */ - { "dir-listing.external-css", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 40 */ - - { "dir-listing.encoding", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 41 */ - { "server.errorlog-use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 42 */ - { "server.range-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 43 */ - { "server.stat-cache-engine", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 44 */ - - { "debug.log-condition-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 45 */ + { "server.errorlog-use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 39 */ + { "server.range-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 40 */ + { "server.stat-cache-engine", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 41 */ { "server.host", "use server.bind instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, { "server.docroot", "use server.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, @@ -113,10 +108,10 @@ static int config_insert(server *srv) { cv[36].destination = &(srv->srvconf.log_request_header_on_error); cv[37].destination = &(srv->srvconf.log_state_handling); - cv[42].destination = &(srv->srvconf.errorlog_use_syslog); + cv[39].destination = &(srv->srvconf.errorlog_use_syslog); stat_cache_string = buffer_init(); - cv[44].destination = stat_cache_string; + cv[41].destination = stat_cache_string; srv->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); @@ -128,9 +123,6 @@ static int config_insert(server *srv) { s = calloc(1, sizeof(specific_config)); assert(s); s->document_root = buffer_init(); - s->dir_listing = 0; - s->hide_dotfiles = 1; - s->indexfiles = array_init(); s->mimetypes = array_init(); s->server_name = buffer_init(); s->ssl_pemfile = buffer_init(); @@ -138,8 +130,6 @@ static int config_insert(server *srv) { s->error_handler = buffer_init(); s->server_tag = buffer_init(); s->errorfile_prefix = buffer_init(); - s->dirlist_css = buffer_init(); - s->dirlist_encoding = buffer_init(); s->max_keep_alive_requests = 128; s->max_keep_alive_idle = 30; s->max_read_idle = 60; @@ -151,6 +141,7 @@ static int config_insert(server *srv) { s->kbytes_per_second = 0; s->allow_http11 = 1; s->range_requests = 1; + s->force_lower_case = 0; s->global_kbytes_per_second = 0; s->global_bytes_per_second_cnt = 0; s->global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt; @@ -164,8 +155,8 @@ static int config_insert(server *srv) { cv[12].destination = &(s->max_request_size); /* 13 max-worker */ cv[14].destination = s->document_root; - cv[15].destination = &(s->dir_listing); - cv[16].destination = s->indexfiles; + cv[15].destination = &(s->force_lower_case); + cv[16].destination = &(s->log_condition_handling); cv[17].destination = &(s->max_keep_alive_requests); cv[18].destination = s->server_name; cv[19].destination = &(s->max_keep_alive_idle); @@ -188,12 +179,7 @@ static int config_insert(server *srv) { cv[35].destination = &(s->allow_http11); cv[38].destination = s->ssl_ca_file; - cv[39].destination = &(s->hide_dotfiles); - cv[40].destination = s->dirlist_css; - cv[41].destination = s->dirlist_encoding; - cv[43].destination = &(s->range_requests); - - cv[45].destination = &(s->log_condition_handling); + cv[39].destination = &(s->range_requests); srv->config_storage[i] = s; @@ -218,6 +204,27 @@ static int config_insert(server *srv) { buffer_free(stat_cache_string); + srv->srvconf.modules->unique_ndx = srv->srvconf.modules->used; + + /* append default modules */ + if (NULL == array_get_element(srv->srvconf.modules, "mod_indexfile")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_indexfile"); + array_insert_unique(srv->srvconf.modules, (data_unset *)ds); + } + + if (NULL == array_get_element(srv->srvconf.modules, "mod_dirlisting")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_dirlisting"); + array_insert_unique(srv->srvconf.modules, (data_unset *)ds); + } + + if (NULL == array_get_element(srv->srvconf.modules, "mod_staticfile")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_staticfile"); + array_insert_unique(srv->srvconf.modules, (data_unset *)ds); + } + return ret; } @@ -230,11 +237,6 @@ int config_setup_connection(server *srv, connection *con) { PATCH(allow_http11); PATCH(mimetypes); PATCH(document_root); - PATCH(dir_listing); - PATCH(dirlist_css); - PATCH(dirlist_encoding); - PATCH(hide_dotfiles); - PATCH(indexfiles); PATCH(max_keep_alive_requests); PATCH(max_keep_alive_idle); PATCH(max_read_idle); @@ -258,6 +260,7 @@ int config_setup_connection(server *srv, connection *con) { PATCH(log_file_not_found); PATCH(range_requests); + PATCH(force_lower_case); return 0; } @@ -282,22 +285,12 @@ int config_patch_connection(server *srv, connection *con, comp_key_t comp) { if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.document-root"))) { PATCH(document_root); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.dir-listing"))) { - PATCH(dir_listing); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.range-requests"))) { PATCH(range_requests); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-dotfiles"))) { - PATCH(hide_dotfiles); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.external-css"))) { - PATCH(dirlist_css); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.encoding"))) { - PATCH(dirlist_encoding); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.error-handler-404"))) { PATCH(error_handler); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.errorfile-prefix"))) { PATCH(errorfile_prefix); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) { - PATCH(indexfiles); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.assign"))) { PATCH(mimetypes); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-requests"))) { @@ -336,6 +329,8 @@ int config_patch_connection(server *srv, connection *con, comp_key_t comp) { PATCH(log_file_not_found); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.protocol-http11"))) { PATCH(allow_http11); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.force-lower-case-files"))) { + PATCH(force_lower_case); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.kbytes-per-second"))) { PATCH(global_kbytes_per_second); PATCH(global_bytes_per_second_cnt); @@ -977,6 +972,7 @@ int config_read(server *srv, const char *fn) { int config_set_defaults(server *srv) { size_t i; specific_config *s = srv->config_storage[0]; + struct stat st1, st2; struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] = { @@ -1005,32 +1001,45 @@ int config_set_defaults(server *srv) { { FDEVENT_HANDLER_UNSET, NULL } }; -#ifdef USE_LICENSE - license_t *l; - - if (srv->srvconf.license->used == 0) { - /* license is missing */ - return -1; - } - - l = license_init(); - - if (0 != license_parse(l, srv->srvconf.license)) { - log_error_write(srv, __FILE__, __LINE__, "sb", - "parsing license information failed", srv->srvconf.license); + if (buffer_is_empty(s->document_root)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "a default document-root has to be set"); - license_free(l); + return -1; + } + if (-1 == stat(s->document_root->ptr, &st1)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "base-docroot doesn't exist:", + s->document_root); return -1; } - if (!license_is_valid(l)) { - log_error_write(srv, __FILE__, __LINE__, "s", - "license is not valid"); + + buffer_copy_string_buffer(srv->tmp_buf, s->document_root); + + buffer_to_lower(srv->tmp_buf); + + if (0 == stat(srv->tmp_buf->ptr, &st1)) { + + /* lower-case existed, check upper-case */ + + buffer_copy_string_buffer(srv->tmp_buf, s->document_root); + + buffer_to_upper(srv->tmp_buf); - license_free(l); - return -1; - } - license_free(l); -#endif + if (0 == stat(srv->tmp_buf->ptr, &st2)) { + + /* upper case exists too, doesn't the FS handle this ? */ + + /* upper and lower have the same inode -> case-insensitve FS */ + + if (st1.st_ino == st2.st_ino) { + /* upper and lower have the same inode -> case-insensitve FS */ + + s->force_lower_case = 1; + } + } + } + if (srv->srvconf.port == 0) { srv->srvconf.port = s->is_ssl ? 443 : 80; } diff --git a/src/connections.c b/src/connections.c index f525a5d5..595d417f 100644 --- a/src/connections.c +++ b/src/connections.c @@ -297,205 +297,138 @@ static int connection_handle_read(server *srv, connection *con) { } static int connection_handle_write_prepare(server *srv, connection *con) { - struct stat st; - int s_len; - - switch(con->mode) { - case DIRECT: - switch(con->http_status) { - case 400: /* class: header + custom body */ - case 401: - case 403: - case 404: - case 408: - case 411: - case 416: - case 500: - case 501: - case 503: - case 505: { - con->file_finished = 1; + if (con->mode == DIRECT) { + /* static files */ + switch(con->request.http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_POST: + case HTTP_METHOD_HEAD: + case HTTP_METHOD_OPTIONS: + case HTTP_METHOD_PUT: + break; + default: + switch(con->http_status) { + case 400: /* bad request */ + case 505: /* unknown protocol */ + break; + default: + con->http_status = 501; + break; + } + break; + } + } + + if (con->http_status == 0) { + con->http_status = 403; + } + + switch(con->http_status) { + case 400: /* class: header + custom body */ + case 401: + case 403: + case 404: + case 408: + case 411: + case 416: + case 500: + case 501: + case 503: + case 505: + if (con->mode != DIRECT) break; + + con->file_finished = 0; + + buffer_reset(con->physical.path); - /* rewrite the filename */ - - /* FIXME: use con.physical.errorfile - * - * - */ - buffer_reset(con->physical.path); + /* try to send static errorfile */ + if (!buffer_is_empty(con->conf.errorfile_prefix)) { + stat_cache_entry *sce = NULL; - if (con->conf.errorfile_prefix->used) { - buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix); - buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status)); - } + buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix); + buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status)); - if ((con->physical.path->used <= 1) || - (-1 == (stat(con->physical.path->ptr, &st)))) { - buffer *b; - - buffer_reset(con->physical.path); - - b = chunkqueue_get_append_buffer(con->write_queue); - - /* build default error-page */ - buffer_copy_string(b, - "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" - " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" - "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" - " <head>\n" - " <title>"); - buffer_append_long(b, con->http_status); - buffer_append_string(b, " - "); - buffer_append_string(b, get_http_status_name(con->http_status)); - - buffer_append_string(b, - "</title>\n" - " </head>\n" - " <body>\n" - " <h1>"); - buffer_append_long(b, con->http_status); - buffer_append_string(b, " - "); - buffer_append_string(b, get_http_status_name(con->http_status)); - - buffer_append_string(b,"</h1>\n" - " </body>\n" - "</html>\n" - ); - - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); - } else { - /* get content-type */ - size_t k; - s_len = con->physical.path->used - 1; - - for (k = 0; k < con->conf.mimetypes->used; k++) { - data_string *ds = (data_string *)con->conf.mimetypes->data[k]; - int ct_len = ds->key->used - 1; - - if (s_len < ct_len || - ds->key->used == 0) continue; - - if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(ds->value)); - break; - } - } + if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + con->file_finished = 1; - if (k == con->conf.mimetypes->used) { - /* the error message should be HTML */ - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); - } + http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); } } - /* fall through */ + + if (!con->file_finished) { + buffer *b; - case 200: /* class: header + body */ - if (con->physical.path->used) { - stat_cache_entry *sce = NULL; - con->file_finished = 1; - - if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { - log_error_write(srv, __FILE__, __LINE__, "sb", - strerror(errno), con->physical.path); - - connection_set_state(srv, con, CON_STATE_ERROR); - - return -1; - } - - if (S_ISREG(sce->st.st_mode)) { - if (con->request.http_method == HTTP_METHOD_GET || - con->request.http_method == HTTP_METHOD_POST) { - http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); - con->response.content_length = http_chunkqueue_length(srv, con); - } else if (con->request.http_method == HTTP_METHOD_HEAD) { - con->response.content_length = sce->st.st_size; - } else { - connection_set_state(srv, con, CON_STATE_ERROR); - return -1; - } - - http_response_write_header(srv, con, - con->response.content_length, - sce->st.st_mtime); - - - } else { - /* why the heck ? */ - - log_error_write(srv, __FILE__, __LINE__, "sb", - "connection closed: no regular-file to send:", - con->physical.path); - - con->file_finished = 1; - } - } else { - if (con->file_finished) { - con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); - } - - /* disable keep-alive if size-info for the body is missing */ - if ((con->parsed_response & HTTP_CONTENT_LENGTH) && - ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) { - con->keep_alive = 0; - } - - if (con->request.http_method == HTTP_METHOD_HEAD) { - chunkqueue_reset(con->write_queue); - } - - http_response_write_header(srv, con, - con->response.content_length, - 0); - - } - break; + buffer_reset(con->physical.path); - case 206: /* write_queue is already prepared */ - http_response_write_header(srv, con, - con->response.content_length, - 0); - con->file_finished = 1; - break; - case 302: con->file_finished = 1; - - con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); + b = chunkqueue_get_append_buffer(con->write_queue); - http_response_write_header(srv, con, - con->response.content_length, - 0); - - break; - case 205: /* class: header only */ - case 301: - case 304: - default: - /* disable chunked encoding again as we have no body */ - con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; - chunkqueue_reset(con->write_queue); - - http_response_write_header(srv, con, 0, 0); - con->file_finished = 1; - break; + /* build default error-page */ + buffer_copy_string(b, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + " <head>\n" + " <title>"); + buffer_append_long(b, con->http_status); + buffer_append_string(b, " - "); + buffer_append_string(b, get_http_status_name(con->http_status)); + + buffer_append_string(b, + "</title>\n" + " </head>\n" + " <body>\n" + " <h1>"); + buffer_append_long(b, con->http_status); + buffer_append_string(b, " - "); + buffer_append_string(b, get_http_status_name(con->http_status)); + + buffer_append_string(b,"</h1>\n" + " </body>\n" + "</html>\n" + ); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } + /* fall through */ + case 200: /* class: header + body */ + break; + + case 206: /* write_queue is already prepared */ + case 302: + con->file_finished = 1; - case 207: break; + case 205: /* class: header only */ + case 301: + case 304: default: - if (con->request.http_method == HTTP_METHOD_HEAD || - con->http_status == 301 || - con->http_status == 304 || - con->http_status == 205) { - /* remove possible chunks */ - con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; - chunkqueue_reset(con->write_queue); + /* disable chunked encoding again as we have no body */ + con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; + chunkqueue_reset(con->write_queue); + + con->file_finished = 1; + break; + } + + + if (con->file_finished) { + /* content-len */ + + buffer_copy_off_t(srv->tmp_buf, chunkqueue_length(con->write_queue)); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf)); + } else { + /* disable keep-alive if size-info for the body is missing */ + if ((con->parsed_response & HTTP_CONTENT_LENGTH) && + ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) { + con->keep_alive = 0; } if (0 == (con->parsed_response & HTTP_CONNECTION)) { - /* (f)cgi did'nt send Connection: header - * + /* (f)cgi did'nt send Connection: header + * * shall we ? */ if (((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) && @@ -512,13 +445,14 @@ static int connection_handle_write_prepare(server *srv, connection *con) { /* FIXME: we have to drop the Connection: Header from the subrequest */ } } - - con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); - http_response_write_basic_header(srv, con); - - break; } + if (con->request.http_method == HTTP_METHOD_HEAD) { + chunkqueue_reset(con->write_queue); + } + + http_response_write_header(srv, con); + return 0; } @@ -1324,7 +1258,11 @@ int connection_state_machine(server *srv, connection *con) { "state for fd", con->fd, connection_get_state(con->state)); } - connection_handle_write_prepare(srv, con); + if (-1 == connection_handle_write_prepare(srv, con)) { + connection_set_state(srv, con, CON_STATE_ERROR); + + break; + } connection_set_state(srv, con, CON_STATE_WRITE); break; diff --git a/src/mod_dirlisting.c b/src/mod_dirlisting.c new file mode 100644 index 00000000..b24d63f1 --- /dev/null +++ b/src/mod_dirlisting.c @@ -0,0 +1,650 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <time.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "config.h" +#include "response.h" +#include "stat_cache.h" +#include "stream.h" + +/** + * this is a dirlisting for a lighttpd plugin + */ + + +#ifdef HAVE_SYS_SYSLIMITS_H +#include <sys/syslimits.h> +#endif + +#ifdef HAVE_ATTR_ATTRIBUTES_H +#include <attr/attributes.h> +#endif + +/* plugin config for all request/connections */ + +typedef struct { + unsigned short dir_listing; + unsigned short hide_dot_files; + unsigned short show_readme; + + buffer *external_css; + buffer *encoding; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_dirlisting_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + p->tmp_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_dirlisting_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (!s) continue; + + buffer_free(s->external_css); + buffer_free(s->encoding); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "dir-listing.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "dir-listing.hide-dotfiles", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "dir-listing.external-css", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "dir-listing.encoding", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { "dir-listing.show-readme", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->dir_listing = 0; + s->external_css = buffer_init(); + s->hide_dot_files = 0; + s->show_readme = 0; + s->encoding = buffer_init(); + + cv[0].destination = &(s->dir_listing); + cv[1].destination = &(s->hide_dot_files); + cv[2].destination = s->external_css; + cv[3].destination = s->encoding; + cv[4].destination = &(s->show_readme); + cv[5].destination = &(s->dir_listing); /* old name */ + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(dir_listing); + PATCH(external_css); + PATCH(hide_dot_files); + PATCH(encoding); + PATCH(show_readme); + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + s = p->config_storage[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.activate"))) { + PATCH(dir_listing); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-dotfiles"))) { + PATCH(hide_dot_files); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.external-css"))) { + PATCH(external_css); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.encoding"))) { + PATCH(encoding); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.show-readme"))) { + PATCH(show_readme); + } + } + } + + return 0; +} +#undef PATCH + +typedef struct { + size_t namelen; + time_t mtime; + off_t size; +} dirls_entry_t; + +typedef struct { + dirls_entry_t **ent; + size_t used; + size_t size; +} dirls_list_t; + +#define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t)) +#define DIRLIST_BLOB_SIZE 16 + +/* simple combsort algorithm */ +static void http_dirls_sort(dirls_entry_t **ent, int num) { + int gap = num; + int i, j; + int swapped; + dirls_entry_t *tmp; + + do { + gap = (gap * 10) / 13; + if (gap == 9 || gap == 10) + gap = 11; + if (gap < 1) + gap = 1; + swapped = 0; + + for (i = 0; i < num - gap; i++) { + j = i + gap; + if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) { + tmp = ent[i]; + ent[i] = ent[j]; + ent[j] = tmp; + swapped = 1; + } + } + + } while (gap > 1 || swapped); +} + +/* buffer must be able to hold "999.9K" + * conversion is simple but not perfect + */ +static int http_list_directory_sizefmt(char *buf, off_t size) { + const char unit[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */ + const char *u = unit - 1; /* u will always increment at least once */ + int remain; + char *out = buf; + + if (size < 100) + size += 99; + if (size < 100) + size = 0; + + while (1) { + remain = (int) size & 1023; + size >>= 10; + u++; + if ((size & (~0 ^ 1023)) == 0) + break; + } + + remain /= 100; + if (remain > 9) + remain = 9; + if (size > 999) { + size = 0; + remain = 9; + u++; + } + + out += ltostr(out, size); + out[0] = '.'; + out[1] = remain + '0'; + out[2] = *u; + out[3] = '\0'; + + return (out + 3 - buf); +} + +static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) { + UNUSED(srv); + + BUFFER_APPEND_STRING_CONST(out, + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" + "<head>\n" + "<title>Index of " + ); + buffer_append_string_html_encoded(out, CONST_BUF_LEN(con->uri.path)); + BUFFER_APPEND_STRING_CONST(out, "</title>\n"); + + if (p->conf.external_css->used > 1) { + BUFFER_APPEND_STRING_CONST(out, "<link rel=\"stylesheet\" type=\"text/css\" href=\""); + buffer_append_string_buffer(out, p->conf.external_css); + BUFFER_APPEND_STRING_CONST(out, "\" />\n"); + } else { + BUFFER_APPEND_STRING_CONST(out, + "<style type=\"text/css\">\n" + "a, a:active {text-decoration: none; color: blue;}\n" + "a:visited {color: #48468F;}\n" + "a:hover, a:focus {text-decoration: underline; color: red;}\n" + "body {background-color: #F5F5F5;}\n" + "h2 {margin-bottom: 12px;}\n" + "table {margin-left: 12px;}\n" + "th, td {" + " font-family: \"Courier New\", Courier, monospace;" + " font-size: 10pt;" + " text-align: left;" + "}\n" + "th {" + " font-weight: bold;" + " padding-right: 14px;" + " padding-bottom: 3px;" + "}\n" + ); + BUFFER_APPEND_STRING_CONST(out, + "td {padding-right: 14px;}\n" + "td.s, th.s {text-align: right;}\n" + "div.list {" + " background-color: white;" + " border-top: 1px solid #646464;" + " border-bottom: 1px solid #646464;" + " padding-top: 10px;" + " padding-bottom: 14px;" + "}\n" + "div.foot {" + " font-family: \"Courier New\", Courier, monospace;" + " font-size: 10pt;" + " color: #787878;" + " padding-top: 4px;" + "}\n" + "</style>\n" + ); + } + + BUFFER_APPEND_STRING_CONST(out, "</head>\n<body>\n<h2>Index of "); + buffer_append_string_html_encoded(out, CONST_BUF_LEN(con->uri.path)); + BUFFER_APPEND_STRING_CONST(out, + "</h2>\n" + "<div class=\"list\">\n" + "<table cellpadding=\"0\" cellspacing=\"0\">\n" + "<thead>" + "<tr>" + "<th class=\"n\">Name</th>" + "<th class=\"m\">Last Modified</th>" + "<th class=\"s\">Size</th>" + "<th class=\"t\">Type</th>" + "</tr>" + "</thead>\n" + "<tbody>\n" + "<tr>" + "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>" + "<td class=\"m\"> </td>" + "<td class=\"s\">- </td>" + "<td class=\"t\">Directory</td>" + "</tr>\n" + ); +} + +static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) { + UNUSED(srv); + + BUFFER_APPEND_STRING_CONST(out, + "</tbody>\n" + "</table>\n" + "</div>\n" + ); + + if (p->conf.show_readme) { + stream s; + /* if we have a README file, display it in <pre class="readme"></pre> */ + + buffer_copy_string_buffer(p->tmp_buf, con->physical.path); + BUFFER_APPEND_SLASH(p->tmp_buf); + BUFFER_APPEND_STRING_CONST(p->tmp_buf, "README.txt"); + + if (-1 != stream_open(&s, p->tmp_buf)) { + BUFFER_APPEND_STRING_CONST(out, "<pre class=\"readme\">"); + buffer_append_string_html_encoded(out, s.start, s.size); + BUFFER_APPEND_STRING_CONST(out, "</pre>"); + } + stream_close(&s); + } + + BUFFER_APPEND_STRING_CONST(out, + "<div class=\"foot\">" + ); + + if (buffer_is_empty(con->conf.server_tag)) { + BUFFER_APPEND_STRING_CONST(out, PACKAGE_NAME "/" PACKAGE_VERSION); + } else { + buffer_append_string_buffer(out, con->conf.server_tag); + } + + BUFFER_APPEND_STRING_CONST(out, + "</div>\n" + "</body>\n" + "</html>\n" + ); +} + +static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) { + DIR *dp; + buffer *out; + struct dirent *dent; + struct stat st; + char *path, *path_file; + size_t i; + int hide_dotfiles = p->conf.hide_dot_files; + dirls_list_t dirs, files, *list; + dirls_entry_t *tmp; + char sizebuf[sizeof("999.9K")]; + char datebuf[sizeof("2005-Jan-01 22:23:24")]; + size_t k; + const char *content_type; + long name_max; +#ifdef HAVE_XATTR + char attrval[128]; + int attrlen; +#endif +#ifdef HAVE_LOCALTIME_R + struct tm tm; +#endif + + if (dir->used == 0) return -1; + + i = dir->used - 1; + +#ifdef HAVE_PATHCONF + name_max = pathconf(dir->ptr, _PC_NAME_MAX); +#elif defined __WIN32 + name_max = FILENAME_MAX; +#else + name_max = NAME_MAX; +#endif + + path = malloc(dir->used + name_max); + assert(path); + strcpy(path, dir->ptr); + path_file = path + i; + + if (NULL == (dp = opendir(path))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "opendir failed:", dir, strerror(errno)); + + free(path); + return -1; + } + + dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); + assert(dirs.ent); + dirs.size = DIRLIST_BLOB_SIZE; + dirs.used = 0; + files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); + assert(files.ent); + files.size = DIRLIST_BLOB_SIZE; + files.used = 0; + + while ((dent = readdir(dp)) != NULL) { + if (dent->d_name[0] == '.') { + if (hide_dotfiles) + continue; + if (dent->d_name[1] == '\0') + continue; + if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') + continue; + } + + + i = strlen(dent->d_name); + + /* NOTE: the manual says, d_name is never more than NAME_MAX + * so this should actually not be a buffer-overflow-risk + */ + if (i > name_max) continue; + + memcpy(path_file, dent->d_name, i + 1); + if (stat(path, &st) != 0) + continue; + + list = &files; + if (S_ISDIR(st.st_mode)) + list = &dirs; + + if (list->used == list->size) { + list->size += DIRLIST_BLOB_SIZE; + list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); + assert(list->ent); + } + + tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); + tmp->mtime = st.st_mtime; + tmp->size = st.st_size; + tmp->namelen = i; + memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); + + list->ent[list->used++] = tmp; + } + closedir(dp); + + if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); + + if (files.used) http_dirls_sort(files.ent, files.used); + + out = chunkqueue_get_append_buffer(con->write_queue); + BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\""); + if (buffer_is_empty(p->conf.encoding)) { + BUFFER_APPEND_STRING_CONST(out, "iso-8859-1"); + } else { + buffer_append_string_buffer(out, p->conf.encoding); + } + BUFFER_APPEND_STRING_CONST(out, "\"?>\n"); + http_list_directory_header(srv, con, p, out); + + /* directories */ + for (i = 0; i < dirs.used; i++) { + tmp = dirs.ent[i]; + +#ifdef HAVE_LOCALTIME_R + localtime_r(&(tmp->mtime), &tm); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); +#else + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); +#endif + + BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); + buffer_append_string_url_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen); + BUFFER_APPEND_STRING_CONST(out, "/\">"); + buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen); + BUFFER_APPEND_STRING_CONST(out, "</a>/</td><td class=\"m\">"); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"); + + free(tmp); + } + + /* files */ + for (i = 0; i < files.used; i++) { + tmp = files.ent[i]; + + content_type = NULL; +#ifdef HAVE_XATTR + + if (con->conf.use_xattr) { + memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); + attrlen = sizeof(attrval) - 1; + if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) { + attrval[attrlen] = '\0'; + content_type = attrval; + } + } +#endif + + if (content_type == NULL) { + content_type = "application/octet-stream"; + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + size_t ct_len; + + if (ds->key->used == 0) + continue; + + ct_len = ds->key->used - 1; + if (tmp->namelen < ct_len) + continue; + + if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { + content_type = ds->value->ptr; + break; + } + } + } + +#ifdef HAVE_LOCALTIME_R + localtime_r(&(tmp->mtime), &tm); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); +#else + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); +#endif + http_list_directory_sizefmt(sizebuf, tmp->size); + + BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); + buffer_append_string_url_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen); + BUFFER_APPEND_STRING_CONST(out, "\">"); + buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen); + BUFFER_APPEND_STRING_CONST(out, "</a></td><td class=\"m\">"); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">"); + buffer_append_string(out, sizebuf); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"t\">"); + buffer_append_string(out, content_type); + BUFFER_APPEND_STRING_CONST(out, "</td></tr>\n"); + + free(tmp); + } + + free(files.ent); + free(dirs.ent); + free(path); + + http_list_directory_footer(srv, con, p, out); + response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + con->file_finished = 1; + + return 0; +} + + + +URIHANDLER_FUNC(mod_dirlisting_subrequest) { + plugin_data *p = p_d; + stat_cache_entry *sce = NULL; + + UNUSED(srv); + + if (con->physical.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON; + + mod_dirlisting_patch_connection(srv, con, p); + + if (!p->conf.dir_listing) return HANDLER_GO_ON; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Dir-Listing"); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path); + } + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + fprintf(stderr, "%s.%d: %s\n", __FILE__, __LINE__, con->physical.path->ptr); + SEGFAULT(); + } + + if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON; + + if (http_list_directory(srv, con, p, con->physical.path)) { + /* dirlisting failed */ + con->http_status = 403; + } + + buffer_reset(con->physical.path); + + /* not found */ + return HANDLER_FINISHED; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_dirlisting_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("dirlisting"); + + p->init = mod_dirlisting_init; + p->handle_subrequest_start = mod_dirlisting_subrequest; + p->set_defaults = mod_dirlisting_set_defaults; + p->cleanup = mod_dirlisting_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_indexfile.c b/src/mod_indexfile.c new file mode 100644 index 00000000..3a77f27b --- /dev/null +++ b/src/mod_indexfile.c @@ -0,0 +1,213 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "stat_cache.h" + +#include "config.h" + +/* plugin config for all request/connections */ + +typedef struct { + array *indexfiles; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_indexfile_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_indexfile_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->indexfiles); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_indexfile_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "index-file.names", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->indexfiles = array_init(); + + cv[0].destination = s->indexfiles; + cv[1].destination = s->indexfiles; /* old name for [0] */ + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_indexfile_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(indexfiles); + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + s = p->config_storage[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) { + PATCH(indexfiles); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("index-file.names"))) { + PATCH(indexfiles); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_indexfile_subrequest) { + plugin_data *p = p_d; + size_t k; + stat_cache_entry *sce = NULL; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON; + + mod_indexfile_patch_connection(srv, con, p); + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Indexfile"); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path); + } + + /* indexfile */ + for (k = 0; k < p->conf.indexfiles->used; k++) { + data_string *ds = (data_string *)p->conf.indexfiles->data[k]; + + buffer_copy_string_buffer(p->tmp_buf, con->physical.path); + buffer_append_string_buffer(p->tmp_buf, ds->value); + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) { + if (errno == EACCES) { + con->http_status = 403; + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + + if (errno != ENOENT && + errno != ENOTDIR) { + /* we have no idea what happend. let's tell the user so. */ + + con->http_status = 500; + + log_error_write(srv, __FILE__, __LINE__, "ssbsb", + "file not found ... or so: ", strerror(errno), + con->uri.path, + "->", con->physical.path); + + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + continue; + } + + /* rewrite uri.path to the real path (/ -> /index.php) */ + buffer_append_string_buffer(con->uri.path, ds->value); + buffer_copy_string_buffer(con->physical.path, p->tmp_buf); + + /* fce is already set up a few lines above */ + + return HANDLER_GO_ON; + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_indexfile_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("indexfile"); + + p->init = mod_indexfile_init; + p->handle_subrequest_start = mod_indexfile_subrequest; + p->set_defaults = mod_indexfile_set_defaults; + p->cleanup = mod_indexfile_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_staticfile.c b/src/mod_staticfile.c new file mode 100644 index 00000000..1a038678 --- /dev/null +++ b/src/mod_staticfile.c @@ -0,0 +1,575 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "stat_cache.h" +#include "etag.h" +#include "http_chunk.h" +#include "response.h" + +/** + * this is a staticfile for a lighttpd plugin + * + */ + + + +/* plugin config for all request/connections */ + +typedef struct { + array *exclude_ext; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *range_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_staticfile_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->range_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_staticfile_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->exclude_ext); + + free(s); + } + free(p->config_storage); + } + buffer_free(p->range_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_staticfile_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "static-file.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->exclude_ext = array_init(); + + cv[0].destination = s->exclude_ext; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(exclude_ext); + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + s = p->config_storage[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.exclude-extensions"))) { + PATCH(exclude_ext); + } + } + } + + return 0; +} +#undef PATCH + +static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { + int multipart = 0; + int error; + off_t start, end; + const char *s, *minus; + char *boundary = "fkj49sn38dcn3"; + data_string *ds; + stat_cache_entry *sce = NULL; + buffer *content_type = NULL; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + SEGFAULT(); + } + + start = 0; + end = sce->st.st_size - 1; + + con->response.content_length = 0; + + if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { + content_type = ds->value; + } + + for (s = con->request.http_range, error = 0; + !error && *s && NULL != (minus = strchr(s, '-')); ) { + char *err; + long la, le; + + if (s == minus) { + /* -<stop> */ + + le = strtol(s, &err, 10); + + if (le == 0) { + /* RFC 2616 - 14.35.1 */ + + con->http_status = 416; + error = 1; + } else if (*err == '\0') { + /* end */ + s = err; + + end = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else { + error = 1; + } + + } else if (*(minus+1) == '\0' || *(minus+1) == ',') { + /* <start>- */ + + la = strtol(s, &err, 10); + + if (err == minus) { + /* ok */ + + if (*(err + 1) == '\0') { + s = err + 1; + + end = sce->st.st_size - 1; + start = la; + + } else if (*(err + 1) == ',') { + multipart = 1; + s = err + 2; + + end = sce->st.st_size - 1; + start = la; + } else { + error = 1; + } + } else { + /* error */ + error = 1; + } + } else { + /* <start>-<stop> */ + + la = strtol(s, &err, 10); + + if (err == minus) { + le = strtol(minus+1, &err, 10); + + /* RFC 2616 - 14.35.1 */ + if (la > le) { + error = 1; + } + + if (*err == '\0') { + /* ok, end*/ + s = err; + + end = le; + start = la; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = le; + start = la; + } else { + /* error */ + + error = 1; + } + } else { + /* error */ + + error = 1; + } + } + + if (!error) { + if (start < 0) start = 0; + + /* RFC 2616 - 14.35.1 */ + if (end > sce->st.st_size - 1) end = sce->st.st_size - 1; + + if (start > sce->st.st_size - 1) { + error = 1; + + con->http_status = 416; + } + } + + if (!error) { + if (multipart) { + /* write boundary-header */ + buffer *b; + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string(b, "\r\n--"); + buffer_append_string(b, boundary); + + /* write Content-Range */ + buffer_append_string(b, "\r\nContent-Range: bytes "); + buffer_append_off_t(b, start); + buffer_append_string(b, "-"); + buffer_append_off_t(b, end); + buffer_append_string(b, "/"); + buffer_append_off_t(b, sce->st.st_size); + + buffer_append_string(b, "\r\nContent-Type: "); + buffer_append_string_buffer(b, content_type); + + /* write END-OF-HEADER */ + buffer_append_string(b, "\r\n\r\n"); + + con->response.content_length += b->used - 1; + + } + + chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); + con->response.content_length += end - start + 1; + } + } + + /* something went wrong */ + if (error) return -1; + + if (multipart) { + /* add boundary end */ + buffer *b; + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string_len(b, "\r\n--", 4); + buffer_append_string(b, boundary); + buffer_append_string_len(b, "--\r\n", 4); + + con->response.content_length += b->used - 1; + + /* set header-fields */ + + buffer_copy_string(p->range_buf, "multipart/byteranges; boundary="); + buffer_append_string(p->range_buf, boundary); + + /* overwrite content-type */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); + } else { + /* add Content-Range-header */ + + buffer_copy_string(p->range_buf, "bytes "); + buffer_append_off_t(p->range_buf, start); + buffer_append_string(p->range_buf, "-"); + buffer_append_off_t(p->range_buf, end); + buffer_append_string(p->range_buf, "/"); + buffer_append_off_t(p->range_buf, sce->st.st_size); + + response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); + } + + /* ok, the file is set-up */ + return 0; +} + +static buffer * strftime_cache_get(server *srv, time_t last_mod) { + struct tm *tm; + size_t i; + + for (i = 0; i < FILE_CACHE_MAX; i++) { + /* found cache-entry */ + if (srv->mtime_cache[i].mtime == last_mod) return srv->mtime_cache[i].str; + + /* found empty slot */ + if (srv->mtime_cache[i].mtime == 0) break; + } + + if (i == FILE_CACHE_MAX) { + i = 0; + } + + srv->mtime_cache[i].mtime = last_mod; + buffer_prepare_copy(srv->mtime_cache[i].str, 1024); + tm = gmtime(&(srv->mtime_cache[i].mtime)); + srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, + srv->mtime_cache[i].str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", tm); + srv->mtime_cache[i].str->used++; + + return srv->mtime_cache[i].str; +} + +URIHANDLER_FUNC(mod_staticfile_subrequest) { + plugin_data *p = p_d; + size_t k; + int s_len; + buffer *mtime; + stat_cache_entry *sce = NULL; + + /* someone else has done a decision for us */ + if (con->http_status != 0) return HANDLER_GO_ON; + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + /* someone else has handled this request */ + if (con->mode != DIRECT) return HANDLER_GO_ON; + + /* we only handle GET, POST and HEAD */ + switch(con->request.http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_POST: + case HTTP_METHOD_HEAD: + break; + default: + return HANDLER_GO_ON; + } + + mod_staticfile_patch_connection(srv, con, p); + + s_len = con->uri.path->used - 1; + + /* ignore certain extensions */ + for (k = 0; k < p->conf.exclude_ext->used; k++) { + data_string *ds = (data_string *)p->conf.exclude_ext->data[k]; + int ct_len = ds->value->used - 1; + + if (ct_len > s_len) continue; + + if (ds->value->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { + return HANDLER_GO_ON; + } + } + + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file"); + } + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + con->http_status = 403; + + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", con->physical.path); + + return HANDLER_FINISHED; + } + + /* we only handline regular files */ + if (!S_ISREG(sce->st.st_mode)) { + con->http_status = 404; + + if (con->conf.log_file_not_found) { + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", sce->name); + } + + return HANDLER_FINISHED; + } + + /* set response content-type */ + + if (buffer_is_empty(sce->content_type)) { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); + } else { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); + } + + /* generate e-tag */ + etag_mutate(con->physical.etag, sce->etag); + + response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); + response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes")); + + /* prepare header */ + mtime = strftime_cache_get(srv, sce->st.st_mtime); + response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); + + /* + * 14.26 If-None-Match + * [...] + * If none of the entity tags match, then the server MAY perform the + * requested method as if the If-None-Match header field did not exist, + * but MUST also ignore any If-Modified-Since header field(s) in the + * request. That is, if no entity tags match, then the server MUST NOT + * return a 304 (Not Modified) response. + */ + + /* last-modified handling */ + if (con->request.http_if_none_match) { + if (etag_is_equal(con->physical.etag, con->request.http_if_none_match)) { + if (con->request.http_method == HTTP_METHOD_GET || + con->request.http_method == HTTP_METHOD_HEAD) { + + /* check if etag + last-modified */ + if (con->request.http_if_modified_since) { + + size_t used_len; + char *semicolon; + + if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { + used_len = strlen(con->request.http_if_modified_since); + } else { + used_len = semicolon - con->request.http_if_modified_since; + } + + if (0 == strncmp(con->request.http_if_modified_since, mtime->ptr, used_len)) { + con->http_status = 304; + return HANDLER_FINISHED; + } else { + char buf[64]; + + /* convert to timestamp */ + if (used_len < sizeof(buf) - 1) { + time_t t; + struct tm tm; + + strncpy(buf, con->request.http_if_modified_since, used_len); + buf[used_len] = '\0'; + + strptime(buf, "%a, %d %b %Y %H:%M:%S GMT", &tm); + + if (-1 != (t = mktime(&tm)) && + t <= sce->st.st_mtime) { + con->http_status = 304; + return HANDLER_FINISHED; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", + con->request.http_if_modified_since, buf); + + con->http_status = 412; + return HANDLER_FINISHED; + } + } + } else { + con->http_status = 304; + return HANDLER_FINISHED; + } + } else { + con->http_status = 412; + return HANDLER_FINISHED; + } + } + } else if (con->request.http_if_modified_since) { + size_t used_len; + char *semicolon; + + if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { + used_len = strlen(con->request.http_if_modified_since); + } else { + used_len = semicolon - con->request.http_if_modified_since; + } + + if (0 == strncmp(con->request.http_if_modified_since, mtime->ptr, used_len)) { + con->http_status = 304; + return HANDLER_FINISHED; + } + } else if (con->request.http_range) { + /* content prepared, I'm done */ + con->file_finished = 1; + + if (0 == http_response_parse_range(srv, con, p)) { + con->http_status = 206; + } + return HANDLER_FINISHED; + } + + /* if we are still here, prepare body */ + + /* we add it here for all requests + * the HEAD request will drop it afterwards again + */ + http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); + + con->file_finished = 1; + + return HANDLER_FINISHED; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_staticfile_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("staticfile"); + + p->init = mod_staticfile_init; + p->handle_subrequest_start = mod_staticfile_subrequest; + p->set_defaults = mod_staticfile_set_defaults; + p->cleanup = mod_staticfile_free; + + p->data = NULL; + + return 0; +} diff --git a/src/request.c b/src/request.c index df5da536..40778e6f 100644 --- a/src/request.c +++ b/src/request.c @@ -950,7 +950,13 @@ int http_request_parse(server *srv, connection *con) { } /* check if we have read post data */ - if (con->request.http_method == HTTP_METHOD_POST) { + if (con->request.http_method == HTTP_METHOD_POST + || (con->request.http_method != HTTP_METHOD_GET + && con->request.http_method != HTTP_METHOD_HEAD + && con->request.http_method != HTTP_METHOD_OPTIONS + && con_length_set)) + { + server_socket *srv_socket = con->srv_socket; if (con->request.http_content_type == NULL) { log_error_write(srv, __FILE__, __LINE__, "s", diff --git a/src/response.c b/src/response.c index 0e07396e..98653fc0 100644 --- a/src/response.c +++ b/src/response.c @@ -1,7 +1,6 @@ #include <sys/types.h> #include <sys/stat.h> -#include <dirent.h> #include <limits.h> #include <errno.h> #include <fcntl.h> @@ -18,6 +17,7 @@ #include "keyvalue.h" #include "log.h" #include "stat_cache.h" +#include "chunk.h" #include "etag.h" #include "connections.h" @@ -26,110 +26,7 @@ #include "sys-socket.h" -#ifdef HAVE_ATTR_ATTRIBUTES_H -#include <attr/attributes.h> -#endif - -#ifdef HAVE_SYS_SYSLIMITS_H -#include <sys/syslimits.h> -#endif - -int http_response_write_basic_header(server *srv, connection *con) { - size_t i; - buffer *b; - - b = chunkqueue_get_prepend_buffer(con->write_queue); - - if (con->request.http_version == HTTP_VERSION_1_1) { - buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 ")); - } else { - buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 ")); - } - buffer_append_long(b, con->http_status); - buffer_append_string_len(b, CONST_STR_LEN(" ")); - buffer_append_string(b, get_http_status_name(con->http_status)); - - /* add the connection header if - * HTTP/1.1 -> close - * HTTP/1.0 -> keep-alive - */ - if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) { - BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: "); - if (con->keep_alive) { - BUFFER_APPEND_STRING_CONST(b, "keep-alive"); - } else { - BUFFER_APPEND_STRING_CONST(b, "close"); - } - } - - if ((con->parsed_response & HTTP_DATE) == 0) { - /* HTTP/1.1 requires a Date: header */ - BUFFER_APPEND_STRING_CONST(b, "\r\nDate: "); - - /* cache the generated timestamp */ - if (srv->cur_ts != srv->last_generated_date_ts) { - buffer_prepare_copy(srv->ts_date_str, 255); - - strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1, - "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts))); - - srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1; - - srv->last_generated_date_ts = srv->cur_ts; - } - - buffer_append_string_buffer(b, srv->ts_date_str); - } - - if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { - BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked"); - } - - /* add all headers */ - for (i = 0; i < con->response.headers->used; i++) { - data_string *ds; - - ds = (data_string *)con->response.headers->data[i]; - - if (ds->value->used && ds->key->used && - 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1) && - /* headers we send */ - !buffer_is_equal_string(ds->key, CONST_STR_LEN("Server")) && - !buffer_is_equal_string(ds->key, CONST_STR_LEN("Date")) && - !buffer_is_equal_string(ds->key, CONST_STR_LEN("Transfer-Encoding")) && - !buffer_is_equal_string(ds->key, CONST_STR_LEN("Connection"))) { - BUFFER_APPEND_STRING_CONST(b, "\r\n"); - buffer_append_string_buffer(b, ds->key); - BUFFER_APPEND_STRING_CONST(b, ": "); - buffer_append_string_buffer(b, ds->value); - } - } - - if (buffer_is_empty(con->conf.server_tag)) { - BUFFER_APPEND_STRING_CONST(b, "\r\nServer: " PACKAGE_NAME "/" PACKAGE_VERSION); - } else { - BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "); - buffer_append_string_buffer(b, con->conf.server_tag); - } - - BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n"); - - if (con->conf.log_response_header) { - log_error_write(srv, __FILE__, __LINE__, "sdsdSb", - "fd:", con->fd, - "response-header-len:", b->used - 1, - "\n", b); - } - - con->bytes_header = b->used - 1; - - return 0; -} - - -int http_response_write_header(server *srv, connection *con, - off_t file_size, - time_t last_mod) { +int http_response_write_header(server *srv, connection *con) { buffer *b; size_t i; @@ -151,9 +48,6 @@ int http_response_write_header(server *srv, connection *con, if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked"); - } else if (file_size >= 0 && con->http_status != 304) { - BUFFER_APPEND_STRING_CONST(b, "\r\nContent-Length: "); - buffer_append_off_t(b, file_size); } /* HTTP/1.1 requires a Date: header */ @@ -173,51 +67,6 @@ int http_response_write_header(server *srv, connection *con, buffer_append_string_buffer(b, srv->ts_date_str); - /* no Last-Modified specified */ - if (last_mod && NULL == array_get_element(con->response.headers, "Last-Modified")) { - struct tm *tm; - - for (i = 0; i < FILE_CACHE_MAX; i++) { - if (srv->mtime_cache[i].mtime == last_mod) break; - - if (srv->mtime_cache[i].mtime == 0) { - srv->mtime_cache[i].mtime = last_mod; - - buffer_prepare_copy(srv->mtime_cache[i].str, 1024); - - tm = gmtime(&(srv->mtime_cache[i].mtime)); - srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, - srv->mtime_cache[i].str->size - 1, - "%a, %d %b %Y %H:%M:%S GMT", tm); - - srv->mtime_cache[i].str->used++; - break; - } - } - - if (i == FILE_CACHE_MAX) { - i = 0; - - srv->mtime_cache[i].mtime = last_mod; - buffer_prepare_copy(srv->mtime_cache[i].str, 1024); - tm = gmtime(&(srv->mtime_cache[i].mtime)); - srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, - srv->mtime_cache[i].str->size - 1, - "%a, %d %b %Y %H:%M:%S GMT", tm); - srv->mtime_cache[i].str->used++; - } - - BUFFER_APPEND_STRING_CONST(b, "\r\nLast-Modified: "); - buffer_append_string_buffer(b, srv->mtime_cache[i].str); - } - - if (con->physical.path->used && con->physical.etag->used) { - BUFFER_APPEND_STRING_CONST(b, "\r\nETag: "); - buffer_append_string_buffer(b, con->physical.etag); - } - - BUFFER_APPEND_STRING_CONST(b, "\r\nAccept-Ranges: bytes"); - /* add all headers */ for (i = 0; i < con->response.headers->used; i++) { data_string *ds; @@ -246,6 +95,7 @@ int http_response_write_header(server *srv, connection *con, BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n"); + con->bytes_header = b->used - 1; if (con->conf.log_response_header) { @@ -255,703 +105,11 @@ int http_response_write_header(server *srv, connection *con, return 0; } -static int http_response_parse_range(server *srv, connection *con) { - struct stat st; - int multipart = 0; - int error; - off_t start, end; - const char *s, *minus; - char *boundary = "fkj49sn38dcn3"; - const char *content_type = NULL; - data_string *ds; - - if (-1 == stat(con->physical.path->ptr, &st)) { - log_error_write(srv, __FILE__, __LINE__, "ss", "stat failed: ", strerror(errno)); - return -1; - } - - start = 0; - end = st.st_size - 1; - - con->response.content_length = 0; - - if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { - content_type = ds->value->ptr; - } - - for (s = con->request.http_range, error = 0; - !error && *s && NULL != (minus = strchr(s, '-')); ) { - char *err; - off_t la, le; - - if (s == minus) { - /* -<stop> */ - - le = strtoll(s, &err, 10); - - if (le == 0) { - /* RFC 2616 - 14.35.1 */ - - con->http_status = 416; - error = 1; - } else if (*err == '\0') { - /* end */ - s = err; - - end = st.st_size - 1; - start = st.st_size + le; - } else if (*err == ',') { - multipart = 1; - s = err + 1; - - end = st.st_size - 1; - start = st.st_size + le; - } else { - error = 1; - } - - } else if (*(minus+1) == '\0' || *(minus+1) == ',') { - /* <start>- */ - - la = strtoll(s, &err, 10); - - if (err == minus) { - /* ok */ - - if (*(err + 1) == '\0') { - s = err + 1; - - end = st.st_size - 1; - start = la; - - } else if (*(err + 1) == ',') { - multipart = 1; - s = err + 2; - - end = st.st_size - 1; - start = la; - } else { - error = 1; - } - } else { - /* error */ - error = 1; - } - } else { - /* <start>-<stop> */ - - la = strtoll(s, &err, 10); - - if (err == minus) { - le = strtoll(minus+1, &err, 10); - - /* RFC 2616 - 14.35.1 */ - if (la > le) { - error = 1; - } - - if (*err == '\0') { - /* ok, end*/ - s = err; - - end = le; - start = la; - } else if (*err == ',') { - multipart = 1; - s = err + 1; - - end = le; - start = la; - } else { - /* error */ - - error = 1; - } - } else { - /* error */ - - error = 1; - } - } - - if (!error) { - if (start < 0) start = 0; - - /* RFC 2616 - 14.35.1 */ - if (end > st.st_size - 1) end = st.st_size - 1; - - if (start > st.st_size - 1) { - error = 1; - - con->http_status = 416; - } - } - - if (!error) { - if (multipart) { - /* write boundary-header */ - buffer *b; - - b = chunkqueue_get_append_buffer(con->write_queue); - - buffer_copy_string(b, "\r\n--"); - buffer_append_string(b, boundary); - - /* write Content-Range */ - buffer_append_string(b, "\r\nContent-Range: bytes "); - buffer_append_off_t(b, start); - buffer_append_string(b, "-"); - buffer_append_off_t(b, end); - buffer_append_string(b, "/"); - buffer_append_off_t(b, st.st_size); - - buffer_append_string(b, "\r\nContent-Type: "); - buffer_append_string(b, content_type); - - /* write END-OF-HEADER */ - buffer_append_string(b, "\r\n\r\n"); - - con->response.content_length += b->used - 1; - - } - - chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); - con->response.content_length += end - start + 1; - } - } - - /* something went wrong */ - if (error) { - return 0; - } - - if (multipart) { - /* add boundary end */ - buffer *b; - - b = chunkqueue_get_append_buffer(con->write_queue); - - buffer_copy_string_len(b, "\r\n--", 4); - buffer_append_string(b, boundary); - buffer_append_string_len(b, "--\r\n", 4); - - con->response.content_length += b->used - 1; - - /* set header-fields */ - - buffer_copy_string(srv->range_buf, "multipart/byteranges; boundary="); - buffer_append_string(srv->range_buf, boundary); - - /* overwrite content-type */ - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->range_buf)); - } else { - /* add Content-Range-header */ - - buffer_copy_string(srv->range_buf, "bytes "); - buffer_append_off_t(srv->range_buf, start); - buffer_append_string(srv->range_buf, "-"); - buffer_append_off_t(srv->range_buf, end); - buffer_append_string(srv->range_buf, "/"); - buffer_append_off_t(srv->range_buf, st.st_size); - - response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->range_buf)); - } - - /* ok, the file is set-up */ - con->http_status = 206; - - return 0; -} - -typedef struct { - size_t namelen; - time_t mtime; - off_t size; -} dirls_entry_t; - -typedef struct { - dirls_entry_t **ent; - int used; - int size; -} dirls_list_t; - -#define DIRLIST_ENT_NAME(ent) (char*) ent + sizeof(dirls_entry_t) -#define DIRLIST_BLOB_SIZE 16 - -/* simple combsort algorithm */ -static void http_dirls_sort(dirls_entry_t **ent, int num) { - int gap = num; - int i, j; - int swapped; - dirls_entry_t *tmp; - - do { - gap = (gap * 10) / 13; - if (gap == 9 || gap == 10) - gap = 11; - if (gap < 1) - gap = 1; - swapped = 0; - - for (i = 0; i < num - gap; i++) { - j = i + gap; - if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) { - tmp = ent[i]; - ent[i] = ent[j]; - ent[j] = tmp; - swapped = 1; - } - } - - } while (gap > 1 || swapped); -} - -/* buffer must be able to hold "999.9K" - * conversion is simple but not perfect - */ -static int http_list_directory_sizefmt(char *buf, off_t size) { - const char unit[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */ - const char *u = unit - 1; /* u will always increment at least once */ - int remain; - char *out = buf; - - if (size < 100) - size += 99; - if (size < 100) - size = 0; - - while (1) { - remain = (int) size & 1023; - size >>= 10; - u++; - if ((size & (~0 ^ 1023)) == 0) - break; - } - - remain /= 100; - if (remain > 9) - remain = 9; - if (size > 999) { - size = 0; - remain = 9; - u++; - } - - out += ltostr(out, size); - out[0] = '.'; - out[1] = remain + '0'; - out[2] = *u; - out[3] = '\0'; - return (out + 3 - buf); -} - -static void http_list_directory_header(buffer *out, connection *con) { - BUFFER_APPEND_STRING_CONST(out, - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" - "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" - "<head>\n" - "<title>Index of " - ); - buffer_append_string_html_encoded(out, con->uri.path->ptr); - BUFFER_APPEND_STRING_CONST(out, "</title>\n"); - - if (con->conf.dirlist_css->used > 1) { - BUFFER_APPEND_STRING_CONST(out, "<link rel=\"stylesheet\" type=\"text/css\" href=\""); - buffer_append_string_buffer(out, con->conf.dirlist_css); - BUFFER_APPEND_STRING_CONST(out, "\" />\n"); - } else { - BUFFER_APPEND_STRING_CONST(out, - "<style type=\"text/css\">\n" - "a, a:active {text-decoration: none; color: blue;}\n" - "a:visited {color: #48468F;}\n" - "a:hover, a:focus {text-decoration: underline; color: red;}\n" - "body {background-color: #F5F5F5;}\n" - "h2 {margin-bottom: 12px;}\n" - "table {margin-left: 12px;}\n" - "th, td {" - " font-family: \"Courier New\", Courier, monospace;" - " font-size: 10pt;" - " text-align: left;" - "}\n" - "th {" - " font-weight: bold;" - " padding-right: 14px;" - " padding-bottom: 3px;" - "}\n" - ); - BUFFER_APPEND_STRING_CONST(out, - "td {padding-right: 14px;}\n" - "td.s, th.s {text-align: right;}\n" - "div.list {" - " background-color: white;" - " border-top: 1px solid #646464;" - " border-bottom: 1px solid #646464;" - " padding-top: 10px;" - " padding-bottom: 14px;" - "}\n" - "div.foot {" - " font-family: \"Courier New\", Courier, monospace;" - " font-size: 10pt;" - " color: #787878;" - " padding-top: 4px;" - "}\n" - "</style>\n" - ); - } - - BUFFER_APPEND_STRING_CONST(out, "</head>\n<body>\n<h2>Index of "); - buffer_append_string_html_encoded(out, con->uri.path->ptr); - BUFFER_APPEND_STRING_CONST(out, - "</h2>\n" - "<div class=\"list\">\n" - "<table cellpadding=\"0\" cellspacing=\"0\">\n" - "<thead>" - "<tr>" - "<th class=\"n\">Name</th>" - "<th class=\"m\">Last Modified</th>" - "<th class=\"s\">Size</th>" - "<th class=\"t\">Type</th>" - "</tr>" - "</thead>\n" - "<tbody>\n" - "<tr>" - "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>" - "<td class=\"m\"> </td>" - "<td class=\"s\">- </td>" - "<td class=\"t\">Directory</td>" - "</tr>\n" - ); -} - -static void http_list_directory_footer(buffer *out, connection *con) { - BUFFER_APPEND_STRING_CONST(out, - "</tbody>\n" - "</table>\n" - "</div>\n" - "<div class=\"foot\">" - ); - - if (buffer_is_empty(con->conf.server_tag)) { - BUFFER_APPEND_STRING_CONST(out, PACKAGE_NAME "/" PACKAGE_VERSION); - } else { - buffer_append_string_buffer(out, con->conf.server_tag); - } - - BUFFER_APPEND_STRING_CONST(out, - "</div>\n" - "</body>\n" - "</html>\n" - ); -} - -static int http_list_directory(server *srv, connection *con, buffer *dir) { - DIR *dp; - buffer *out; - struct dirent *dent; - struct stat st; - char *path, *path_file; - int i; - int hide_dotfiles = con->conf.hide_dotfiles; - dirls_list_t dirs, files, *list; - dirls_entry_t *tmp; - char sizebuf[sizeof("999.9K")]; - char datebuf[sizeof("2005-Jan-01 22:23:24")]; - size_t k; - const char *content_type; - long name_max; -#ifdef HAVE_XATTR - char attrval[128]; - int attrlen; -#endif -#ifdef HAVE_LOCALTIME_R - struct tm tm; -#endif - - i = dir->used - 1; - if (i <= 0) return -1; - -#ifdef HAVE_PATHCONF - name_max = pathconf(dir->ptr, _PC_NAME_MAX); -#else - name_max = NAME_MAX; -#endif - - path = malloc(i + name_max + 1); - assert(path); - strcpy(path, dir->ptr); - path_file = path + i; - - if (NULL == (dp = opendir(path))) { - log_error_write(srv, __FILE__, __LINE__, "sbs", - "opendir failed:", dir, strerror(errno)); - - free(path); - return -1; - } - - dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); - assert(dirs.ent); - dirs.size = DIRLIST_BLOB_SIZE; - dirs.used = 0; - files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); - assert(files.ent); - files.size = DIRLIST_BLOB_SIZE; - files.used = 0; - - while ((dent = readdir(dp)) != NULL) { - if (dent->d_name[0] == '.') { - if (hide_dotfiles) - continue; - if (dent->d_name[1] == '\0') - continue; - if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') - continue; - } - - /* NOTE: the manual says, d_name is never more than NAME_MAX - * so this should actually not be a buffer-overflow-risk - */ - i = strlen(dent->d_name); - if (i > name_max) - continue; - memcpy(path_file, dent->d_name, i + 1); - if (stat(path, &st) != 0) - continue; - - list = &files; - if (S_ISDIR(st.st_mode)) - list = &dirs; - - if (list->used == list->size) { - list->size += DIRLIST_BLOB_SIZE; - list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); - assert(list->ent); - } - - tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); - tmp->mtime = st.st_mtime; - tmp->size = st.st_size; - tmp->namelen = i; - memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); - - list->ent[list->used++] = tmp; - } - closedir(dp); - - if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); - - if (files.used) http_dirls_sort(files.ent, files.used); - - out = chunkqueue_get_append_buffer(con->write_queue); - - if (buffer_is_empty(con->conf.dirlist_encoding)) { - BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"); - } else { - BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\""); - buffer_append_string_buffer(out, con->conf.dirlist_encoding); - BUFFER_APPEND_STRING_CONST(out, "\"?>\n"); - } - - http_list_directory_header(out, con); - - /* directories */ - for (i = 0; i < dirs.used; i++) { - tmp = dirs.ent[i]; - -#ifdef HAVE_LOCALTIME_R - localtime_r(&(tmp->mtime), &tm); - strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); -#else - strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); -#endif - - BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); - buffer_append_string_url_encoded(out, DIRLIST_ENT_NAME(tmp)); - BUFFER_APPEND_STRING_CONST(out, "/\">"); - buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp)); - BUFFER_APPEND_STRING_CONST(out, "</a>/</td><td class=\"m\">"); - buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); - BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"); - - free(tmp); - } - - /* files */ - for (i = 0; i < files.used; i++) { - tmp = files.ent[i]; - -#ifdef HAVE_XATTR - content_type = NULL; - if (con->conf.use_xattr) { - memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); - attrlen = sizeof(attrval) - 1; - if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) { - attrval[attrlen] = '\0'; - content_type = attrval; - } - } - if (content_type == NULL) { -#else - if (1) { -#endif - content_type = "application/octet-stream"; - for (k = 0; k < con->conf.mimetypes->used; k++) { - data_string *ds = (data_string *)con->conf.mimetypes->data[k]; - size_t ct_len; - - if (ds->key->used == 0) - continue; - - ct_len = ds->key->used - 1; - if (tmp->namelen < ct_len) - continue; - - if (0 == strncmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { - content_type = ds->value->ptr; - break; - } - } - } - -#ifdef HAVE_LOCALTIME_R - localtime_r(&(tmp->mtime), &tm); - strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); -#else - strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); -#endif - http_list_directory_sizefmt(sizebuf, tmp->size); - - BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); - buffer_append_string_url_encoded(out, DIRLIST_ENT_NAME(tmp)); - BUFFER_APPEND_STRING_CONST(out, "\">"); - buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp)); - BUFFER_APPEND_STRING_CONST(out, "</a></td><td class=\"m\">"); - buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); - BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">"); - buffer_append_string(out, sizebuf); - BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"t\">"); - buffer_append_string(out, content_type); - BUFFER_APPEND_STRING_CONST(out, "</td></tr>\n"); - - free(tmp); - } - - free(files.ent); - free(dirs.ent); - free(path); - - http_list_directory_footer(out, con); - response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); - con->file_finished = 1; - - return 0; -} - - -int http_response_handle_cachable(server *srv, connection *con, time_t mtime) { - if (con->http_status != 0) return 0; - - /* - * 14.26 If-None-Match - * [...] - * If none of the entity tags match, then the server MAY perform the - * requested method as if the If-None-Match header field did not exist, - * but MUST also ignore any If-Modified-Since header field(s) in the - * request. That is, if no entity tags match, then the server MUST NOT - * return a 304 (Not Modified) response. - */ - - /* last-modified handling */ - if (con->request.http_if_none_match) { - if (etag_is_equal(con->physical.etag, con->request.http_if_none_match)) { - if (con->request.http_method == HTTP_METHOD_GET || - con->request.http_method == HTTP_METHOD_HEAD) { - - /* check if etag + last-modified */ - if (con->request.http_if_modified_since) { - char buf[64]; - struct tm tm; - size_t used_len; - char *semicolon; - - strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); - - if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { - used_len = strlen(con->request.http_if_modified_since); - } else { - used_len = semicolon - con->request.http_if_modified_since; - } - - if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) { - con->http_status = 304; - return 1; - } else { - /* convert to timestamp */ - if (used_len < sizeof(buf) - 1) { - time_t t; - strncpy(buf, con->request.http_if_modified_since, used_len); - buf[used_len] = '\0'; - - strptime(buf, "%a, %d %b %Y %H:%M:%S GMT", &tm); - - if (-1 != (t = mktime(&tm)) && - t <= mtime) { - con->http_status = 304; - return 1; - } - } else { - log_error_write(srv, __FILE__, __LINE__, "ss", - con->request.http_if_modified_since, buf); - - con->http_status = 412; - return 1; - } - } - } else { - con->http_status = 304; - return 1; - } - } else { - con->http_status = 412; - return 1; - } - } - } else if (con->request.http_if_modified_since) { - char buf[64]; - struct tm *tm; - size_t used_len; - char *semicolon; - - tm = gmtime(&(mtime)); - strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm); - - if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { - used_len = strlen(con->request.http_if_modified_since); - } else { - used_len = semicolon - con->request.http_if_modified_since; - } - - if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) { - con->http_status = 304; - return 1; - } - } - - return 0; -} handler_t http_response_prepare(server *srv, connection *con) { handler_t r; - if (con->loops_per_request++ > 1000) { - /* protect us again endless loops in a single request */ - - log_error_write(srv, __FILE__, __LINE__, "s", "ENDLESS LOOP DETECTED ... aborting request"); - - return HANDLER_ERROR; - } - /* looks like someone has already done a decision */ if (con->mode == DIRECT && (con->http_status != 0 && con->http_status != 200)) { @@ -962,14 +120,15 @@ handler_t http_response_prepare(server *srv, connection *con) { return HANDLER_FINISHED; } - + if (con->request.http_method == HTTP_METHOD_OPTIONS) { + /* option requests are handled directly without checking of the path */ + con->http_status = 200; con->file_finished = 1; - con->file_started = 1; return HANDLER_FINISHED; } - + /* no decision yet, build conf->filename */ if (con->mode == DIRECT && con->physical.path->used == 0) { char *qstr; @@ -1003,6 +162,7 @@ handler_t http_response_prepare(server *srv, connection *con) { buffer_copy_string(con->uri.scheme, con->conf.is_ssl ? "https" : "http"); buffer_copy_string_buffer(con->uri.authority, con->request.http_host); + buffer_to_lower(con->uri.authority); config_patch_connection(srv, con, COMP_HTTP_HOST); /* Host: */ config_patch_connection(srv, con, COMP_HTTP_REMOTEIP); /* Client-IP */ @@ -1069,7 +229,7 @@ handler_t http_response_prepare(server *srv, connection *con) { buffer_path_simplify(con->uri.path, srv->tmp_buf); if (con->conf.log_request_handling) { - log_error_write(srv, __FILE__, __LINE__, "s", "-- sanitising URI"); + log_error_write(srv, __FILE__, __LINE__, "s", "-- sanatising URI"); log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path); } @@ -1129,14 +289,12 @@ handler_t http_response_prepare(server *srv, connection *con) { buffer_copy_string_buffer(con->physical.doc_root, con->conf.document_root); buffer_copy_string_buffer(con->physical.rel_path, con->uri.path); - buffer_reset(con->physical.path); - if (con->conf.log_request_handling) { log_error_write(srv, __FILE__, __LINE__, "s", "-- before doc_root"); log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root); log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); } - /* the docroot plugin should set the doc_root and might also set the physical.path * for us (all vhost-plugins are supposed to set the doc_root) * */ @@ -1153,20 +311,12 @@ handler_t http_response_prepare(server *srv, connection *con) { break; } - if (buffer_is_empty(con->physical.path)) { - /** - * create physical filename - * -> physical.path = docroot + rel_path - * - */ - - buffer_copy_string_buffer(con->physical.path, con->physical.doc_root); - BUFFER_APPEND_SLASH(con->physical.path); - if (con->physical.rel_path->ptr[0] == '/') { - buffer_append_string_len(con->physical.path, con->physical.rel_path->ptr + 1, con->physical.rel_path->used - 2); - } else { - buffer_append_string_buffer(con->physical.path, con->physical.rel_path); - } + /* MacOS X and Windows can't distiguish between upper and lower-case + * + * convert to lower-case + */ + if (con->conf.force_lower_case) { + buffer_to_lower(con->physical.rel_path); } /* the docroot plugins might set the servername, if they don't we take http-host */ @@ -1214,20 +364,19 @@ handler_t http_response_prepare(server *srv, connection *con) { log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root); log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path); log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); - log_error_write(srv, __FILE__, __LINE__, "sb", "Server-Name :", con->server_name); } } /* - * only if we are still in DIRECT mode we check for the real existence of the file + * Noone catched away the file from normal path of execution yet (like mod_access) * + * Go on and check of the file exists at all */ if (con->mode == DIRECT) { char *slash = NULL; char *pathinfo = NULL; int found = 0; - size_t k; stat_cache_entry *sce = NULL; if (con->conf.log_request_handling) { @@ -1235,19 +384,40 @@ handler_t http_response_prepare(server *srv, connection *con) { log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); } - switch (stat_cache_get_entry(srv, con, con->physical.path, &sce)) { - case HANDLER_ERROR: - if (errno == EACCES) { + if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + /* file exists */ + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- file found"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + if (S_ISDIR(sce->st.st_mode)) { + if (con->physical.path->ptr[con->physical.path->used - 2] != '/') { + /* redirect to .../ */ + + http_response_redirect_to_directory(srv, con); + + return HANDLER_FINISHED; + } + } + } else { + switch (errno) { + case EACCES: con->http_status = 403; buffer_reset(con->physical.path); return HANDLER_FINISHED; - } - - if (errno != ENOENT && - errno != ENOTDIR) { + case ENOENT: + con->http_status = 404; + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + case ENOTDIR: + /* PATH_INFO ! :) */ + break; + default: /* we have no idea what happend. let's tell the user so. */ - con->http_status = 500; buffer_reset(con->physical.path); @@ -1260,19 +430,8 @@ handler_t http_response_prepare(server *srv, connection *con) { } /* not found, perhaps PATHINFO */ - buffer_copy_string_buffer(srv->tmp_buf, con->physical.path); - /* - * - * FIXME: - * - * Check for PATHINFO fall to dir of - * - * /a is a dir and - * - * /a/b/c is requested - * - */ + buffer_copy_string_buffer(srv->tmp_buf, con->physical.path); do { struct stat st; @@ -1308,8 +467,8 @@ handler_t http_response_prepare(server *srv, connection *con) { if (con->conf.log_file_not_found) { log_error_write(srv, __FILE__, __LINE__, "sbsb", - "file not found:", con->uri.path, - "->", con->physical.path); + "file not found:", con->uri.path, + "->", con->physical.path); } buffer_reset(con->physical.path); @@ -1317,7 +476,6 @@ handler_t http_response_prepare(server *srv, connection *con) { return HANDLER_FINISHED; } - /* we have a PATHINFO */ if (pathinfo) { buffer_copy_string(con->request.pathinfo, pathinfo); @@ -1326,7 +484,7 @@ handler_t http_response_prepare(server *srv, connection *con) { * shorten uri.path */ - con->uri.path->used -= con->request.pathinfo->used - 1; + con->uri.path->used -= strlen(pathinfo); con->uri.path->ptr[con->uri.path->used - 1] = '\0'; } @@ -1336,168 +494,36 @@ handler_t http_response_prepare(server *srv, connection *con) { log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path); log_error_write(srv, __FILE__, __LINE__, "sb", "Pathinfo :", con->request.pathinfo); } - - /* setup the right file cache entry (FCE) */ - switch (stat_cache_get_entry(srv, con, con->physical.path, &sce)) { - case HANDLER_ERROR: - con->http_status = 404; - - if (con->conf.log_file_not_found) { - log_error_write(srv, __FILE__, __LINE__, "sbsb", - "file not found:", con->uri.path, - "->", con->physical.path); - } - - return HANDLER_FINISHED; - case HANDLER_GO_ON: - default: - break; - } - - break; - case HANDLER_GO_ON: - if (con->conf.log_request_handling) { - log_error_write(srv, __FILE__, __LINE__, "s", "-- file found"); - log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); - } - - if (S_ISDIR(sce->st.st_mode)) { - if (con->physical.path->ptr[con->physical.path->used - 2] != '/') { - /* redirect to .../ */ - - http_response_redirect_to_directory(srv, con); - - return HANDLER_FINISHED; - } else { - found = 0; - /* indexfile */ - - for (k = 0; !found && (k < con->conf.indexfiles->used); k++) { - data_string *ds = (data_string *)con->conf.indexfiles->data[k]; - - buffer_copy_string_buffer(srv->tmp_buf, con->physical.path); - buffer_append_string_buffer(srv->tmp_buf, ds->value); - - switch (stat_cache_get_entry(srv, con, srv->tmp_buf, &sce)) { - case HANDLER_COMEBACK: - case HANDLER_GO_ON: - /* rewrite uri.path to the real path (/ -> /index.php) */ - buffer_append_string_buffer(con->uri.path, ds->value); - - found = 1; - break; - case HANDLER_ERROR: - - if (errno == EACCES) { - con->http_status = 403; - buffer_reset(con->physical.path); - - return HANDLER_FINISHED; - } - - if (errno != ENOENT && - errno != ENOTDIR) { - /* we have no idea what happend. let's tell the user so. */ - - con->http_status = 500; - buffer_reset(con->physical.path); - - log_error_write(srv, __FILE__, __LINE__, "ssbsb", - "file not found ... or so: ", strerror(errno), - con->uri.path, - "->", con->physical.path); - - return HANDLER_FINISHED; - } - - break; - default: - break; - } - } - - if (!found && - (k == con->conf.indexfiles->used)) { - /* directory listing ? */ - - buffer_reset(srv->tmp_buf); - - if (con->conf.dir_listing == 0) { - /* dirlisting disabled */ - con->http_status = 403; - } else if (0 != http_list_directory(srv, con, con->physical.path)) { - /* dirlisting failed */ - con->http_status = 403; - } - - buffer_reset(con->physical.path); - - return HANDLER_FINISHED; - } - - buffer_copy_string_buffer(con->physical.path, srv->tmp_buf); - } - } - break; - default: - break; } - if (!S_ISREG(sce->st.st_mode)) { - con->http_status = 404; - - if (con->conf.log_file_not_found) { - log_error_write(srv, __FILE__, __LINE__, "sbsb", - "not a regular file:", con->uri.path, - "->", sce->name); - } - - return HANDLER_FINISHED; + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling subrequest"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); } /* call the handlers */ switch(r = plugins_call_handle_subrequest_start(srv, con)) { - case HANDLER_FINISHED: - /* request was handled */ - break; case HANDLER_GO_ON: /* request was not handled */ break; + case HANDLER_FINISHED: default: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- subrequest finished"); + } + /* something strange happend */ return r; } - /* ok, noone has handled the file up to now, so we do the fileserver-stuff */ - if (r == HANDLER_GO_ON) { - /* DIRECT */ - - /* set response content-type */ - - if (buffer_is_empty(sce->content_type)) { - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); - } else { - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); - } + /* if we are still here, no one wanted the file, status 403 is ok I think */ + + if (con->mode == DIRECT) { + con->http_status = 403; - /* generate e-tag */ - etag_mutate(con->physical.etag, sce->etag); - - http_response_handle_cachable(srv, con, sce->st.st_mtime); - - if (con->conf.range_requests && - con->http_status == 0 && - con->request.http_range) { - http_response_parse_range(srv, con); - } else if (con->http_status == 0) { - switch(r = plugins_call_handle_physical_path(srv, con)) { - case HANDLER_GO_ON: - break; - default: - return r; - } - } + return HANDLER_FINISHED; } + } switch(r = plugins_call_handle_subrequest(srv, con)) { diff --git a/src/response.h b/src/response.h index 2dfbf440..eda07653 100644 --- a/src/response.h +++ b/src/response.h @@ -4,10 +4,7 @@ #include "server.h" int http_response_parse(server *srv, connection *con); -int http_response_write_basic_header(server *srv, connection *con); -int http_response_write_header(server *srv, connection *con, - off_t file_size, - time_t last_mod); +int http_response_write_header(server *srv, connection *con); int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen); int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen); diff --git a/src/server.c b/src/server.c index 266bb2b6..aafa7333 100644 --- a/src/server.c +++ b/src/server.c @@ -124,10 +124,7 @@ static server *server_init(void) { CLEAN(errorlog_buf); CLEAN(response_range); CLEAN(tmp_buf); - CLEAN(range_buf); - CLEAN(empty_string); - - buffer_copy_string(srv->empty_string, ""); + CLEAN(cond_check_buf); CLEAN(srvconf.errorlog_file); CLEAN(srvconf.groupname); @@ -194,8 +191,7 @@ static void server_free(server *srv) { CLEAN(errorlog_buf); CLEAN(response_range); CLEAN(tmp_buf); - CLEAN(range_buf); - CLEAN(empty_string); + CLEAN(cond_check_buf); CLEAN(srvconf.errorlog_file); CLEAN(srvconf.groupname); @@ -229,9 +225,6 @@ static void server_free(server *srv) { buffer_free(s->ssl_ca_file); buffer_free(s->error_handler); buffer_free(s->errorfile_prefix); - buffer_free(s->dirlist_css); - buffer_free(s->dirlist_encoding); - array_free(s->indexfiles); array_free(s->mimetypes); free(s); |