diff --git a/doc/Makefile.am b/doc/Makefile.am index 41a3d34b..cc748db5 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -64,6 +64,8 @@ EXTRA_DIST=lighttpd.conf lighttpd.user \ state.ps.gz rrdtool-graph.sh \ state.dot fastcgi-state.dot fastcgi-state.ps.gz \ spawn-php.sh \ + newstyle.css \ + oldstyle.css \ $(DOCS) %.html: %.txt diff --git a/doc/configuration.txt b/doc/configuration.txt index 741a38a1..f5d3387a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -182,6 +182,15 @@ server.dir-listing enables virtual directory listings if a directory is requested no index-file was found +dir-listing.hide-dotfiles + if enabled, does not list hidden files in directory listings generated + by the dir-listing option. + + default: enabled + +dir-listing.external-css + path to an external css stylesheet for the directory listing + server.follow-symlink allow to follow-symlinks diff --git a/doc/newstyle.css b/doc/newstyle.css new file mode 100644 index 00000000..26f91d37 --- /dev/null +++ b/doc/newstyle.css @@ -0,0 +1,49 @@ +body { + background-color: #F5F5F5; +} +a, a:active { + text-decoration: none; + color: blue; +} +a:visited { + color: #48468F; +} +a:hover, a:focus { + text-decoration: underline; + color: red; +} +h2 { + margin-bottom: 12px; +} +table { + margin-left: 12px; +} +th, td { + font-family: "Courier New", Courier, monospace; + font-size: 10pt; + text-align: left; +} +th { + font-weight: bold; + padding-right: 14px; + padding-bottom: 3px; +} +td { + padding-right: 14px; +} +td.s, th.s { + text-align: right; +} +div.list { + background-color: white; + border-top: 1px solid #646464; + border-bottom: 1px solid #646464; + padding-top: 10px; + padding-bottom: 14px; +} +div.foot { + font-family: "Courier New", Courier, monospace; + font-size: 10pt; + color: #787878; + padding-top: 4px; +} diff --git a/doc/oldstyle.css b/doc/oldstyle.css new file mode 100644 index 00000000..f3e26db3 --- /dev/null +++ b/doc/oldstyle.css @@ -0,0 +1,25 @@ +table { + border: 1px solid black; + padding: 1px; +} +th { + background-color: black; + border: 1px solid white; + color: white; + padding-right: 2px; + padding-left: 2px; +} +td { + background-color: #f0f0f0; + border: 1px solid white; + padding-right: 2px; + padding-left: 2px; +} +td.s { + background-color: #f0f0f0; + text-align: right; + padding-left: 14px; +} +div.foot { + margin-top: 4px; +} diff --git a/src/base.h b/src/base.h index ffe90af4..b821eb5a 100644 --- a/src/base.h +++ b/src/base.h @@ -222,8 +222,10 @@ typedef struct { buffer *server_name; buffer *error_handler; buffer *server_tag; + buffer *dirlist_css; 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; diff --git a/src/config.c b/src/config.c index cbc32c08..f719e961 100644 --- a/src/config.c +++ b/src/config.c @@ -72,6 +72,8 @@ static int config_insert(server *srv) { { "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 */ { "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 }, @@ -115,6 +117,7 @@ static int config_insert(server *srv) { 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(); @@ -122,6 +125,7 @@ static int config_insert(server *srv) { s->ssl_ca_file = buffer_init(); s->error_handler = buffer_init(); s->server_tag = buffer_init(); + s->dirlist_css = buffer_init(); s->max_keep_alive_requests = 128; s->max_keep_alive_idle = 30; s->max_read_idle = 60; @@ -167,6 +171,8 @@ 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; srv->config_storage[i] = s; @@ -188,6 +194,8 @@ int config_setup_connection(server *srv, connection *con) { PATCH(mimetypes); PATCH(document_root); PATCH(dir_listing); + PATCH(dirlist_css); + PATCH(hide_dotfiles); PATCH(indexfiles); PATCH(max_keep_alive_requests); PATCH(max_keep_alive_idle); @@ -233,6 +241,10 @@ int config_patch_connection(server *srv, connection *con, const char *stage, siz 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("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("server.error-handler-404"))) { PATCH(error_handler); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) { diff --git a/src/response.c b/src/response.c index e7deef68..c01e9454 100644 --- a/src/response.c +++ b/src/response.c @@ -458,206 +458,372 @@ static int http_response_parse_range(server *srv, connection *con) { } typedef struct { - char **ptr; - size_t used; - size_t size; -} string_buffer; + 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, + "\n" + "\n" + "\n" + "Index of " + ); + buffer_append_string_html_encoded(out, con->uri.path->ptr); + BUFFER_APPEND_STRING_CONST(out, "\n"); + + if (con->conf.dirlist_css->used > 1) { + BUFFER_APPEND_STRING_CONST(out, "conf.dirlist_css); + BUFFER_APPEND_STRING_CONST(out, "\" />\n"); + } else { + BUFFER_APPEND_STRING_CONST(out, + "\n" + ); + } + + BUFFER_APPEND_STRING_CONST(out, "\n\n

Index of "); + buffer_append_string_html_encoded(out, con->uri.path->ptr); + BUFFER_APPEND_STRING_CONST(out, + "

\n" + "
\n" + "\n" + "" + "" + "" + "" + "" + "" + "" + "\n" + "\n" + "" + "" + "" + "" + "" + "\n" + ); +} + +static void http_list_directory_footer(buffer *out, connection *con) { + BUFFER_APPEND_STRING_CONST(out, + "\n" + "
NameLast ModifiedSizeType
Parent Directory/ -  Directory
\n" + "
\n" + "
" + ); + + 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, + "
\n" + "\n" + "\n" + ); +} static int http_list_directory(server *srv, connection *con, buffer *dir) { - DIR *d; + DIR *dp; + buffer *out; struct dirent *dent; - buffer *b, *date_buf; - string_buffer *sb; - size_t i; + 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; +#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; + + path = malloc(i + NAME_MAX + 1); + assert(path); + strcpy(path, dir->ptr); + path_file = path + i; - if (NULL == (d = opendir(dir->ptr))) { + if (NULL == (dp = opendir(path))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opendir failed:", dir, strerror(errno)); + + free(path); return -1; } - - b = chunkqueue_get_append_buffer(con->write_queue); - buffer_copy_string(b, - "\n" - "\n" - "\n" - " \n" - " Directory Listing for "); - buffer_append_string_html_encoded(b, con->uri.path->ptr); - - BUFFER_APPEND_STRING_CONST(b, - "\n" - " \n" - " \n" - " \n" - "

Directory Listing for "); - buffer_append_string_html_encoded(b, con->uri.path->ptr); - BUFFER_APPEND_STRING_CONST(b,"

\n \n"); - BUFFER_APPEND_STRING_CONST(b," \n"); - - - /* allocate memory */ - sb = calloc(1, sizeof(*sb)); - assert(sb); - - sb->ptr = NULL; - sb->used = 0; - sb->size = 0; - - while(NULL != (dent = readdir(d))) { - size_t j; - if (sb->size == 0) { - sb->size = 4; - sb->ptr = malloc(sizeof(*sb->ptr) * sb->size); - assert(sb->ptr); - } else if (sb->used == sb->size) { - sb->size += 4; - sb->ptr = realloc(sb->ptr, sizeof(*sb->ptr) * sb->size); - assert(sb->ptr); - } - - for (i = 0; i < sb->used; i++) { - if (strcmp(dent->d_name, sb->ptr[i]) < 0) { - break; - } - } - - /* */ - /* [i] */ - - for (j = sb->used - 1; sb->used && j >= i && (j+1) > 0; j--) { - sb->ptr[j + 1] = sb->ptr[j]; + + 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; } - sb->ptr[i] = strdup(dent->d_name); - - sb->used++; - - } - - closedir(d); - - date_buf = buffer_init(); - buffer_prepare_copy(date_buf, 22); - for (i = 0; i < sb->used; i++) { - struct stat st; - struct tm tm; - size_t s_len = strlen(sb->ptr[i]); - - - buffer_copy_string(srv->tmp_buf, dir->ptr); - buffer_append_string(srv->tmp_buf, sb->ptr[i]); - - if (0 != stat(srv->tmp_buf->ptr, &st)) { - free(sb->ptr[i]); + + /* 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); } - - BUFFER_APPEND_STRING_CONST(b, - " \n"); + + free(tmp); + } + + /* files */ + for (i = 0; i < files.used; i++) { + tmp = files.ent[i]; #ifdef HAVE_XATTR - char attrval[128]; - int attrlen = sizeof(attrval) - 1; - - if(con->conf.use_xattr && 0 == attr_get(srv->tmp_buf->ptr, "Content-Type", attrval, &attrlen, 0)) { - attrval[attrlen] = 0; - buffer_append_string_rfill(b, attrval, 28); - have_content_type = 1; + 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 - - if(!have_content_type) { - 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 (s_len < ct_len) continue; - - if (0 == strncmp(sb->ptr[i] + s_len - ct_len, ds->key->ptr, ct_len)) { - buffer_append_string_rfill(b, ds->value->ptr, 28); - break; - } - } - - if (k == con->conf.mimetypes->used) { - buffer_append_string_rfill(b, "application/octet-stream", 28); + 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; } } } - - /* URL */ - BUFFER_APPEND_STRING_CONST(b,"\n"); - - - free(sb->ptr[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 + http_list_directory_sizefmt(sizebuf, tmp->size); + + BUFFER_APPEND_STRING_CONST(out, "\n"); + + free(tmp); } - - buffer_free(date_buf); - free(sb->ptr); - free(sb); - + + 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")); - - BUFFER_APPEND_STRING_CONST(b, - "
datesizetypename
"); - + + 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, "\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 is faster */ - localtime_r(&(st.st_mtime), &tm); - - date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", &tm); + localtime_r(&(tmp->mtime), &tm); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); #else - date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(st.st_mtime))); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); #endif - date_buf->used++; - - buffer_append_string_buffer(b, date_buf); - BUFFER_APPEND_STRING_CONST(b, - ""); - - buffer_append_off_t(b, st.st_size); - - /* mime type */ - BUFFER_APPEND_STRING_CONST(b, - ""); - - if (S_ISDIR(st.st_mode)) { - buffer_append_string_rfill(b, "directory", 28); - } else { - size_t k; - unsigned short have_content_type = 0; + + BUFFER_APPEND_STRING_CONST(out, "
"); + buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp)); + BUFFER_APPEND_STRING_CONST(out, "/"); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, "-  Directory
ptr[i]); - if (S_ISDIR(st.st_mode)) { - BUFFER_APPEND_STRING_CONST(b,"/"); - } - BUFFER_APPEND_STRING_CONST(b,"\">"); - /* HTML encode */ - buffer_append_string_html_encoded(b, sb->ptr[i]); - if (S_ISDIR(st.st_mode)) { - BUFFER_APPEND_STRING_CONST(b,"/"); - } - BUFFER_APPEND_STRING_CONST(b,"
"); + buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp)); + BUFFER_APPEND_STRING_CONST(out, ""); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, ""); + buffer_append_string(out, sizebuf); + BUFFER_APPEND_STRING_CONST(out, ""); + buffer_append_string(out, content_type); + BUFFER_APPEND_STRING_CONST(out, "
\n" - " \n" - "\n" ); - con->file_finished = 1; - + return 0; } diff --git a/src/server.c b/src/server.c index a5bdda26..2a7de554 100644 --- a/src/server.c +++ b/src/server.c @@ -241,6 +241,7 @@ static void server_free(server *srv) { buffer_free(s->ssl_pemfile); buffer_free(s->ssl_ca_file); buffer_free(s->error_handler); + buffer_free(s->dirlist_css); array_free(s->indexfiles); array_free(s->mimetypes);