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; #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) char attrval[128]; int attrlen; #endif #ifdef HAVE_LOCALTIME_R struct tm tm; #endif if (buffer_string_is_empty(dir)) return -1; i = buffer_string_length(dir); #ifdef HAVE_PATHCONF if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) { /* some broken fs (fuse) return 0 instead of -1 */ #ifdef NAME_MAX name_max = NAME_MAX; #else name_max = 255; /* stupid default */ #endif } #elif defined __WIN32 name_max = FILENAME_MAX; #else name_max = NAME_MAX; #endif path = malloc(i + name_max + 1); force_assert(NULL != path); memcpy(path, dir->ptr, i+1); 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); force_assert(dirs.ent); dirs.size = DIRLIST_BLOB_SIZE; dirs.used = 0; files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); force_assert(files.ent); files.size = DIRLIST_BLOB_SIZE; files.used = 0; while ((dent = readdir(dp)) != NULL) { unsigned short exclude_match = 0; 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; } if (p->conf.hide_readme_file && !buffer_string_is_empty(p->conf.show_readme)) { if (strcmp(dent->d_name, p->conf.show_readme->ptr) == 0) continue; } if (p->conf.hide_header_file && !buffer_string_is_empty(p->conf.show_header)) { if (strcmp(dent->d_name, p->conf.show_header->ptr) == 0) continue; } /* compare d_name against excludes array * elements, skipping any that match. */ #ifdef HAVE_PCRE_H for(i = 0; i < p->conf.excludes->used; i++) { int n; #define N 10 int ovec[N * 3]; pcre *regex = p->conf.excludes->ptr[i]->regex; if ((n = pcre_exec(regex, NULL, dent->d_name, strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching:", n); /* aborting would require a lot of manual cleanup here. * skip instead (to not leak names that break pcre matching) */ exclude_match = 1; break; } } else { exclude_match = 1; break; } } if (exclude_match) { continue; } #endif 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 > (size_t)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); force_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 = buffer_init(); 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_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("/\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</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; #if defined(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, srv->srvconf.xattr_name->ptr, attrval, &attrlen, 0) == 0) { attrval[attrlen] = '\0'; content_type = attrval; } } #elif defined(HAVE_EXTATTR) if (con->conf.use_xattr) { memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); if(-1 != (attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, srv->srvconf.xattr_name->ptr, attrval, sizeof(attrval)-1))) { 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 (buffer_is_empty(ds->key)) continue; ct_len = buffer_string_length(ds->key); 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, sizeof(sizebuf), tmp->size); buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">")); buffer_append_string(out, sizebuf); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">")); buffer_append_string(out, content_type); buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n")); free(tmp); } free(files.ent); free(dirs.ent); free(path); http_list_directory_footer(srv, con, p, out); /* Insert possible charset to Content-Type */ if (buffer_string_is_empty(p->conf.encoding)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset=")); buffer_append_string_buffer(p->content_charset, p->conf.encoding); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset)); } con->file_finished = 1; chunkqueue_append_buffer(con->write_queue, out); buffer_free(out); return 0; }
static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) { DIR *dp; buffer *out; struct dirent *dent; struct stat st; 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 /* empty pathname, never ... */ if (buffer_is_empty(dir)) return -1; /* max-length for the opendir */ #ifdef HAVE_PATHCONF if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) { #ifdef NAME_MAX name_max = NAME_MAX; #else name_max = 256; /* stupid default */ #endif } #elif defined _WIN32 name_max = FILENAME_MAX; #else name_max = NAME_MAX; #endif buffer_copy_string_buffer(p->path, dir); PATHNAME_APPEND_SLASH(p->path); #ifdef _WIN32 /* append *.* to the path */ buffer_append_string_len(p->path, CONST_STR_LEN("*.*")); #endif if (NULL == (dp = opendir(p->path->ptr))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opendir failed:", dir, strerror(errno)); 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) { unsigned short exclude_match = 0; 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; } if (p->conf.hide_readme_file) { if (strcmp(dent->d_name, "README.txt") == 0) continue; } if (p->conf.hide_header_file) { if (strcmp(dent->d_name, "HEADER.txt") == 0) continue; } /* compare d_name against excludes array * elements, skipping any that match. */ #ifdef HAVE_PCRE_H for(i = 0; i < p->conf.excludes->used; i++) { int n; #define N 10 int ovec[N * 3]; pcre *regex = p->conf.excludes->ptr[i]->regex; if ((n = pcre_exec(regex, NULL, dent->d_name, strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching:", n); return -1; } } else { exclude_match = 1; break; } } if (exclude_match) { continue; } #endif 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 > (size_t)name_max) continue; /* build the dirname */ buffer_copy_string_buffer(p->path, dir); PATHNAME_APPEND_SLASH(p->path); buffer_append_string(p->path, dent->d_name); if (stat(p->path->ptr, &st) != 0) { fprintf(stderr, "%s.%d: %s, %s\r\n", __FILE__, __LINE__, p->path->ptr, strerror(errno)); 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->send); buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\"")); if (buffer_is_empty(p->conf.encoding)) { buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1")); } else { buffer_append_string_buffer(out, p->conf.encoding); } buffer_append_string_len(out, CONST_STR_LEN("\"?>\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_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("/\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</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) { /* build the dirname */ buffer_copy_string_buffer(p->path, dir); PATHNAME_APPEND_SLASH(p->path); buffer_append_string_len(p->path, DIRLIST_ENT_NAME(tmp), tmp->namelen); attrlen = sizeof(attrval) - 1; if (attr_get(p->path->ptr, "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_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">")); buffer_append_string(out, sizebuf); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">")); buffer_append_string(out, content_type); buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n")); free(tmp); } free(files.ent); free(dirs.ent); http_list_directory_footer(srv, con, p, out); /* Insert possible charset to Content-Type */ if (buffer_is_empty(p->conf.encoding)) { response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset=")); buffer_append_string_buffer(p->content_charset, p->conf.encoding); response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset)); } con->send->bytes_in += out->used - 1; con->send->is_closed = 1; return 0; }