10 #include "stat_cache.h"
24 * this is a dirlisting for a lighttpd plugin
27 #ifdef HAVE_ATTR_ATTRIBUTES_H
28 #include <attr/attributes.h>
31 #ifdef HAVE_SYS_EXTATTR_H
32 #include <sys/extattr.h>
37 /* plugin config for all request/connections */
54 unsigned short dir_listing
;
55 unsigned short hide_dot_files
;
56 unsigned short show_readme
;
57 unsigned short hide_readme_file
;
58 unsigned short encode_readme
;
59 unsigned short show_header
;
60 unsigned short hide_header_file
;
61 unsigned short encode_header
;
62 unsigned short auto_layout
;
64 excludes_buffer
*excludes
;
75 buffer
*content_charset
;
77 plugin_config
**config_storage
;
82 static excludes_buffer
*excludes_buffer_init(void) {
85 exb
= calloc(1, sizeof(*exb
));
90 static int excludes_buffer_append(excludes_buffer
*exb
, buffer
*string
) {
96 if (!string
) return -1;
102 exb
->ptr
= malloc(exb
->size
* sizeof(*exb
->ptr
));
104 for(i
= 0; i
< exb
->size
; i
++) {
105 exb
->ptr
[i
] = calloc(1, sizeof(**exb
->ptr
));
107 } else if (exb
->used
== exb
->size
) {
110 exb
->ptr
= realloc(exb
->ptr
, exb
->size
* sizeof(*exb
->ptr
));
112 for(i
= exb
->used
; i
< exb
->size
; i
++) {
113 exb
->ptr
[i
] = calloc(1, sizeof(**exb
->ptr
));
118 if (NULL
== (exb
->ptr
[exb
->used
]->regex
= pcre_compile(string
->ptr
, 0,
119 &errptr
, &erroff
, NULL
))) {
123 exb
->ptr
[exb
->used
]->string
= buffer_init();
124 buffer_copy_buffer(exb
->ptr
[exb
->used
]->string
, string
);
137 static void excludes_buffer_free(excludes_buffer
*exb
) {
141 for (i
= 0; i
< exb
->size
; i
++) {
142 if (exb
->ptr
[i
]->regex
) pcre_free(exb
->ptr
[i
]->regex
);
143 if (exb
->ptr
[i
]->string
) buffer_free(exb
->ptr
[i
]->string
);
147 if (exb
->ptr
) free(exb
->ptr
);
153 /* init the plugin data */
154 INIT_FUNC(mod_dirlisting_init
) {
157 p
= calloc(1, sizeof(*p
));
159 p
->tmp_buf
= buffer_init();
160 p
->content_charset
= buffer_init();
165 /* detroy the plugin data */
166 FREE_FUNC(mod_dirlisting_free
) {
167 plugin_data
*p
= p_d
;
171 if (!p
) return HANDLER_GO_ON
;
173 if (p
->config_storage
) {
175 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
176 plugin_config
*s
= p
->config_storage
[i
];
180 excludes_buffer_free(s
->excludes
);
181 buffer_free(s
->external_css
);
182 buffer_free(s
->encoding
);
183 buffer_free(s
->set_footer
);
187 free(p
->config_storage
);
190 buffer_free(p
->tmp_buf
);
191 buffer_free(p
->content_charset
);
195 return HANDLER_GO_ON
;
198 /* handle plugin config and check values */
200 #define CONFIG_EXCLUDE "dir-listing.exclude"
201 #define CONFIG_ACTIVATE "dir-listing.activate"
202 #define CONFIG_HIDE_DOTFILES "dir-listing.hide-dotfiles"
203 #define CONFIG_EXTERNAL_CSS "dir-listing.external-css"
204 #define CONFIG_ENCODING "dir-listing.encoding"
205 #define CONFIG_SHOW_README "dir-listing.show-readme"
206 #define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
207 #define CONFIG_SHOW_HEADER "dir-listing.show-header"
208 #define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
209 #define CONFIG_DIR_LISTING "server.dir-listing"
210 #define CONFIG_SET_FOOTER "dir-listing.set-footer"
211 #define CONFIG_ENCODE_README "dir-listing.encode-readme"
212 #define CONFIG_ENCODE_HEADER "dir-listing.encode-header"
213 #define CONFIG_AUTO_LAYOUT "dir-listing.auto-layout"
216 SETDEFAULTS_FUNC(mod_dirlisting_set_defaults
) {
217 plugin_data
*p
= p_d
;
220 config_values_t cv
[] = {
221 { CONFIG_EXCLUDE
, NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
222 { CONFIG_ACTIVATE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
223 { CONFIG_HIDE_DOTFILES
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
224 { CONFIG_EXTERNAL_CSS
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
225 { CONFIG_ENCODING
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
226 { CONFIG_SHOW_README
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
227 { CONFIG_HIDE_README_FILE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
228 { CONFIG_SHOW_HEADER
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 7 */
229 { CONFIG_HIDE_HEADER_FILE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 8 */
230 { CONFIG_DIR_LISTING
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 9 */
231 { CONFIG_SET_FOOTER
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 10 */
232 { CONFIG_ENCODE_README
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 11 */
233 { CONFIG_ENCODE_HEADER
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 12 */
234 { CONFIG_AUTO_LAYOUT
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 13 */
236 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
239 if (!p
) return HANDLER_ERROR
;
241 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
243 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
244 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
246 data_unset
*du_excludes
;
248 s
= calloc(1, sizeof(plugin_config
));
249 s
->excludes
= excludes_buffer_init();
251 s
->external_css
= buffer_init();
252 s
->hide_dot_files
= 1;
254 s
->hide_readme_file
= 0;
256 s
->hide_header_file
= 0;
257 s
->encode_readme
= 1;
258 s
->encode_header
= 1;
261 s
->encoding
= buffer_init();
262 s
->set_footer
= buffer_init();
264 cv
[0].destination
= s
->excludes
;
265 cv
[1].destination
= &(s
->dir_listing
);
266 cv
[2].destination
= &(s
->hide_dot_files
);
267 cv
[3].destination
= s
->external_css
;
268 cv
[4].destination
= s
->encoding
;
269 cv
[5].destination
= &(s
->show_readme
);
270 cv
[6].destination
= &(s
->hide_readme_file
);
271 cv
[7].destination
= &(s
->show_header
);
272 cv
[8].destination
= &(s
->hide_header_file
);
273 cv
[9].destination
= &(s
->dir_listing
); /* old name */
274 cv
[10].destination
= s
->set_footer
;
275 cv
[11].destination
= &(s
->encode_readme
);
276 cv
[12].destination
= &(s
->encode_header
);
277 cv
[13].destination
= &(s
->auto_layout
);
279 p
->config_storage
[i
] = s
;
281 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
282 return HANDLER_ERROR
;
285 if (NULL
!= (du_excludes
= array_get_element(config
->value
, CONFIG_EXCLUDE
))) {
286 array
*excludes_list
;
289 if (du_excludes
->type
!= TYPE_ARRAY
) {
290 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
291 "unexpected type for key: ", CONFIG_EXCLUDE
, "array of strings");
292 return HANDLER_ERROR
;
295 excludes_list
= ((data_array
*)du_excludes
)->value
;
298 if (excludes_list
->used
> 0) {
299 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
300 "pcre support is missing for: ", CONFIG_EXCLUDE
, ", please install libpcre and the headers");
301 return HANDLER_ERROR
;
304 for (j
= 0; j
< excludes_list
->used
; j
++) {
305 data_unset
*du_exclude
= excludes_list
->data
[j
];
307 if (du_exclude
->type
!= TYPE_STRING
) {
308 log_error_write(srv
, __FILE__
, __LINE__
, "sssbs",
309 "unexpected type for key: ", CONFIG_EXCLUDE
, "[",
310 du_exclude
->key
, "](string)");
311 return HANDLER_ERROR
;
314 if (0 != excludes_buffer_append(s
->excludes
, ((data_string
*)(du_exclude
))->value
)) {
315 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
316 "pcre-compile failed for", ((data_string
*)(du_exclude
))->value
);
317 return HANDLER_ERROR
;
324 return HANDLER_GO_ON
;
329 static int mod_dirlisting_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
331 plugin_config
*s
= p
->config_storage
[0];
335 PATCH(hide_dot_files
);
338 PATCH(hide_readme_file
);
340 PATCH(hide_header_file
);
343 PATCH(encode_readme
);
344 PATCH(encode_header
);
347 /* skip the first, the global context */
348 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
349 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
350 s
= p
->config_storage
[i
];
352 /* condition didn't match */
353 if (!config_check_cond(srv
, con
, dc
)) continue;
356 for (j
= 0; j
< dc
->value
->used
; j
++) {
357 data_unset
*du
= dc
->value
->data
[j
];
359 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ACTIVATE
)) ||
360 buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_DIR_LISTING
))) {
362 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_DOTFILES
))) {
363 PATCH(hide_dot_files
);
364 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_EXTERNAL_CSS
))) {
366 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODING
))) {
368 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SHOW_README
))) {
370 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_README_FILE
))) {
371 PATCH(hide_readme_file
);
372 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SHOW_HEADER
))) {
374 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE
))) {
375 PATCH(hide_header_file
);
376 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SET_FOOTER
))) {
378 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_EXCLUDE
))) {
380 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODE_README
))) {
381 PATCH(encode_readme
);
382 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODE_HEADER
))) {
383 PATCH(encode_header
);
384 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_AUTO_LAYOUT
))) {
406 #define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t))
407 #define DIRLIST_BLOB_SIZE 16
409 /* simple combsort algorithm */
410 static void http_dirls_sort(dirls_entry_t
**ent
, int num
) {
417 gap
= (gap
* 10) / 13;
418 if (gap
== 9 || gap
== 10)
424 for (i
= 0; i
< num
- gap
; i
++) {
426 if (strcmp(DIRLIST_ENT_NAME(ent
[i
]), DIRLIST_ENT_NAME(ent
[j
])) > 0) {
434 } while (gap
> 1 || swapped
);
437 /* buffer must be able to hold "999.9K"
438 * conversion is simple but not perfect
440 static int http_list_directory_sizefmt(char *buf
, off_t size
) {
441 const char unit
[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */
442 const char *u
= unit
- 1; /* u will always increment at least once */
452 remain
= (int) size
& 1023;
455 if ((size
& (~0 ^ 1023)) == 0)
468 li_itostrn(out
, 4, size
);
471 out
[1] = remain
+ '0';
475 return (out
+ 3 - buf
);
478 static void http_list_directory_header(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*out
) {
481 if (p
->conf
.auto_layout
) {
482 buffer_append_string_len(out
, CONST_STR_LEN(
483 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
484 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
488 buffer_append_string_encoded(out
, CONST_BUF_LEN(con
->uri
.path
), ENCODING_MINIMAL_XML
);
489 buffer_append_string_len(out
, CONST_STR_LEN("</title>\n"));
491 if (!buffer_string_is_empty(p
->conf
.external_css
)) {
492 buffer_append_string_len(out
, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
493 buffer_append_string_buffer(out
, p
->conf
.external_css
);
494 buffer_append_string_len(out
, CONST_STR_LEN("\" />\n"));
496 buffer_append_string_len(out
, CONST_STR_LEN(
497 "<style type=\"text/css\">\n"
498 "a, a:active {text-decoration: none; color: blue;}\n"
499 "a:visited {color: #48468F;}\n"
500 "a:hover, a:focus {text-decoration: underline; color: red;}\n"
501 "body {background-color: #F5F5F5;}\n"
502 "h2 {margin-bottom: 12px;}\n"
503 "table {margin-left: 12px;}\n"
505 " font: 90% monospace;"
509 " font-weight: bold;"
510 " padding-right: 14px;"
511 " padding-bottom: 3px;"
513 "td {padding-right: 14px;}\n"
514 "td.s, th.s {text-align: right;}\n"
516 " background-color: white;"
517 " border-top: 1px solid #646464;"
518 " border-bottom: 1px solid #646464;"
519 " padding-top: 10px;"
520 " padding-bottom: 14px;"
523 " font: 90% monospace;"
531 buffer_append_string_len(out
, CONST_STR_LEN("</head>\n<body>\n"));
535 if (p
->conf
.show_header
) {
537 /* if we have a HEADER file, display it in <pre class="header"></pre> */
539 buffer_copy_buffer(p
->tmp_buf
, con
->physical
.path
);
540 buffer_append_slash(p
->tmp_buf
);
541 buffer_append_string_len(p
->tmp_buf
, CONST_STR_LEN("HEADER.txt"));
543 if (-1 != stream_open(&s
, p
->tmp_buf
)) {
544 if (p
->conf
.encode_header
) {
545 buffer_append_string_len(out
, CONST_STR_LEN("<pre class=\"header\">"));
546 buffer_append_string_encoded(out
, s
.start
, s
.size
, ENCODING_MINIMAL_XML
);
547 buffer_append_string_len(out
, CONST_STR_LEN("</pre>"));
549 buffer_append_string_len(out
, s
.start
, s
.size
);
555 buffer_append_string_len(out
, CONST_STR_LEN("<h2>Index of "));
556 buffer_append_string_encoded(out
, CONST_BUF_LEN(con
->uri
.path
), ENCODING_MINIMAL_XML
);
557 buffer_append_string_len(out
, CONST_STR_LEN(
559 "<div class=\"list\">\n"
560 "<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
563 "<th class=\"n\">Name</th>"
564 "<th class=\"m\">Last Modified</th>"
565 "<th class=\"s\">Size</th>"
566 "<th class=\"t\">Type</th>"
571 "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
572 "<td class=\"m\"> </td>"
573 "<td class=\"s\">- </td>"
574 "<td class=\"t\">Directory</td>"
579 static void http_list_directory_footer(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*out
) {
582 buffer_append_string_len(out
, CONST_STR_LEN(
588 if (p
->conf
.show_readme
) {
590 /* if we have a README file, display it in <pre class="readme"></pre> */
592 buffer_copy_buffer(p
->tmp_buf
, con
->physical
.path
);
593 buffer_append_slash(p
->tmp_buf
);
594 buffer_append_string_len(p
->tmp_buf
, CONST_STR_LEN("README.txt"));
596 if (-1 != stream_open(&s
, p
->tmp_buf
)) {
597 if (p
->conf
.encode_readme
) {
598 buffer_append_string_len(out
, CONST_STR_LEN("<pre class=\"readme\">"));
599 buffer_append_string_encoded(out
, s
.start
, s
.size
, ENCODING_MINIMAL_XML
);
600 buffer_append_string_len(out
, CONST_STR_LEN("</pre>"));
602 buffer_append_string_len(out
, s
.start
, s
.size
);
608 if(p
->conf
.auto_layout
) {
609 buffer_append_string_len(out
, CONST_STR_LEN(
610 "<div class=\"foot\">"
613 if (!buffer_string_is_empty(p
->conf
.set_footer
)) {
614 buffer_append_string_buffer(out
, p
->conf
.set_footer
);
615 } else if (buffer_is_empty(con
->conf
.server_tag
)) {
616 buffer_append_string_len(out
, CONST_STR_LEN(PACKAGE_DESC
));
618 buffer_append_string_buffer(out
, con
->conf
.server_tag
);
621 buffer_append_string_len(out
, CONST_STR_LEN(
629 static int http_list_directory(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*dir
) {
634 char *path
, *path_file
;
636 int hide_dotfiles
= p
->conf
.hide_dot_files
;
637 dirls_list_t dirs
, files
, *list
;
639 char sizebuf
[sizeof("999.9K")];
640 char datebuf
[sizeof("2005-Jan-01 22:23:24")];
642 const char *content_type
;
644 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
648 #ifdef HAVE_LOCALTIME_R
652 if (buffer_string_is_empty(dir
)) return -1;
654 i
= buffer_string_length(dir
);
657 if (0 >= (name_max
= pathconf(dir
->ptr
, _PC_NAME_MAX
))) {
658 /* some broken fs (fuse) return 0 instead of -1 */
662 name_max
= 255; /* stupid default */
665 #elif defined __WIN32
666 name_max
= FILENAME_MAX
;
671 path
= malloc(buffer_string_length(dir
) + name_max
+ 1);
672 force_assert(NULL
!= path
);
673 strcpy(path
, dir
->ptr
);
674 path_file
= path
+ i
;
676 if (NULL
== (dp
= opendir(path
))) {
677 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
678 "opendir failed:", dir
, strerror(errno
));
684 dirs
.ent
= (dirls_entry_t
**) malloc(sizeof(dirls_entry_t
*) * DIRLIST_BLOB_SIZE
);
685 force_assert(dirs
.ent
);
686 dirs
.size
= DIRLIST_BLOB_SIZE
;
688 files
.ent
= (dirls_entry_t
**) malloc(sizeof(dirls_entry_t
*) * DIRLIST_BLOB_SIZE
);
689 force_assert(files
.ent
);
690 files
.size
= DIRLIST_BLOB_SIZE
;
693 while ((dent
= readdir(dp
)) != NULL
) {
694 unsigned short exclude_match
= 0;
696 if (dent
->d_name
[0] == '.') {
699 if (dent
->d_name
[1] == '\0')
701 if (dent
->d_name
[1] == '.' && dent
->d_name
[2] == '\0')
705 if (p
->conf
.hide_readme_file
) {
706 if (strcmp(dent
->d_name
, "README.txt") == 0)
709 if (p
->conf
.hide_header_file
) {
710 if (strcmp(dent
->d_name
, "HEADER.txt") == 0)
714 /* compare d_name against excludes array
715 * elements, skipping any that match.
718 for(i
= 0; i
< p
->conf
.excludes
->used
; i
++) {
722 pcre
*regex
= p
->conf
.excludes
->ptr
[i
]->regex
;
724 if ((n
= pcre_exec(regex
, NULL
, dent
->d_name
,
725 strlen(dent
->d_name
), 0, 0, ovec
, 3 * N
)) < 0) {
726 if (n
!= PCRE_ERROR_NOMATCH
) {
727 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
728 "execution error while matching:", n
);
730 /* aborting would require a lot of manual cleanup here.
731 * skip instead (to not leak names that break pcre matching)
748 i
= strlen(dent
->d_name
);
750 /* NOTE: the manual says, d_name is never more than NAME_MAX
751 * so this should actually not be a buffer-overflow-risk
753 if (i
> (size_t)name_max
) continue;
755 memcpy(path_file
, dent
->d_name
, i
+ 1);
756 if (stat(path
, &st
) != 0)
760 if (S_ISDIR(st
.st_mode
))
763 if (list
->used
== list
->size
) {
764 list
->size
+= DIRLIST_BLOB_SIZE
;
765 list
->ent
= (dirls_entry_t
**) realloc(list
->ent
, sizeof(dirls_entry_t
*) * list
->size
);
766 force_assert(list
->ent
);
769 tmp
= (dirls_entry_t
*) malloc(sizeof(dirls_entry_t
) + 1 + i
);
770 tmp
->mtime
= st
.st_mtime
;
771 tmp
->size
= st
.st_size
;
773 memcpy(DIRLIST_ENT_NAME(tmp
), dent
->d_name
, i
+ 1);
775 list
->ent
[list
->used
++] = tmp
;
779 if (dirs
.used
) http_dirls_sort(dirs
.ent
, dirs
.used
);
781 if (files
.used
) http_dirls_sort(files
.ent
, files
.used
);
784 buffer_copy_string_len(out
, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
785 if (buffer_string_is_empty(p
->conf
.encoding
)) {
786 buffer_append_string_len(out
, CONST_STR_LEN("iso-8859-1"));
788 buffer_append_string_buffer(out
, p
->conf
.encoding
);
790 buffer_append_string_len(out
, CONST_STR_LEN("\"?>\n"));
791 http_list_directory_header(srv
, con
, p
, out
);
794 for (i
= 0; i
< dirs
.used
; i
++) {
797 #ifdef HAVE_LOCALTIME_R
798 localtime_r(&(tmp
->mtime
), &tm
);
799 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", &tm
);
801 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", localtime(&(tmp
->mtime
)));
804 buffer_append_string_len(out
, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\""));
805 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_REL_URI_PART
);
806 buffer_append_string_len(out
, CONST_STR_LEN("/\">"));
807 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_MINIMAL_XML
);
808 buffer_append_string_len(out
, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
809 buffer_append_string_len(out
, datebuf
, sizeof(datebuf
) - 1);
810 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"));
816 for (i
= 0; i
< files
.used
; i
++) {
820 #if defined(HAVE_XATTR)
821 if (con
->conf
.use_xattr
) {
822 memcpy(path_file
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
+ 1);
823 attrlen
= sizeof(attrval
) - 1;
824 if (attr_get(path
, srv
->srvconf
.xattr_name
->ptr
, attrval
, &attrlen
, 0) == 0) {
825 attrval
[attrlen
] = '\0';
826 content_type
= attrval
;
829 #elif defined(HAVE_EXTATTR)
830 if (con
->conf
.use_xattr
) {
831 memcpy(path_file
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
+ 1);
832 if(-1 != (attrlen
= extattr_get_file(path
, EXTATTR_NAMESPACE_USER
, srv
->srvconf
.xattr_name
->ptr
, attrval
, sizeof(attrval
)-1))) {
833 attrval
[attrlen
] = '\0';
834 content_type
= attrval
;
839 if (content_type
== NULL
) {
840 content_type
= "application/octet-stream";
841 for (k
= 0; k
< con
->conf
.mimetypes
->used
; k
++) {
842 data_string
*ds
= (data_string
*)con
->conf
.mimetypes
->data
[k
];
845 if (buffer_is_empty(ds
->key
))
848 ct_len
= buffer_string_length(ds
->key
);
849 if (tmp
->namelen
< ct_len
)
852 if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp
) + tmp
->namelen
- ct_len
, ds
->key
->ptr
, ct_len
)) {
853 content_type
= ds
->value
->ptr
;
859 #ifdef HAVE_LOCALTIME_R
860 localtime_r(&(tmp
->mtime
), &tm
);
861 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", &tm
);
863 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", localtime(&(tmp
->mtime
)));
865 http_list_directory_sizefmt(sizebuf
, tmp
->size
);
867 buffer_append_string_len(out
, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
868 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_REL_URI_PART
);
869 buffer_append_string_len(out
, CONST_STR_LEN("\">"));
870 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_MINIMAL_XML
);
871 buffer_append_string_len(out
, CONST_STR_LEN("</a></td><td class=\"m\">"));
872 buffer_append_string_len(out
, datebuf
, sizeof(datebuf
) - 1);
873 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"s\">"));
874 buffer_append_string(out
, sizebuf
);
875 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"t\">"));
876 buffer_append_string(out
, content_type
);
877 buffer_append_string_len(out
, CONST_STR_LEN("</td></tr>\n"));
886 http_list_directory_footer(srv
, con
, p
, out
);
888 /* Insert possible charset to Content-Type */
889 if (buffer_string_is_empty(p
->conf
.encoding
)) {
890 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
892 buffer_copy_string_len(p
->content_charset
, CONST_STR_LEN("text/html; charset="));
893 buffer_append_string_buffer(p
->content_charset
, p
->conf
.encoding
);
894 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->content_charset
));
897 con
->file_finished
= 1;
898 chunkqueue_append_buffer(con
->write_queue
, out
);
906 URIHANDLER_FUNC(mod_dirlisting_subrequest
) {
907 plugin_data
*p
= p_d
;
908 stat_cache_entry
*sce
= NULL
;
912 /* we only handle GET, POST and HEAD */
913 switch(con
->request
.http_method
) {
914 case HTTP_METHOD_GET
:
915 case HTTP_METHOD_POST
:
916 case HTTP_METHOD_HEAD
:
919 return HANDLER_GO_ON
;
922 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
924 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
925 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
926 if (con
->uri
.path
->ptr
[buffer_string_length(con
->uri
.path
) - 1] != '/') return HANDLER_GO_ON
;
928 mod_dirlisting_patch_connection(srv
, con
, p
);
930 if (!p
->conf
.dir_listing
) return HANDLER_GO_ON
;
932 if (con
->conf
.log_request_handling
) {
933 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- handling the request as Dir-Listing");
934 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "URI :", con
->uri
.path
);
937 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
938 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "stat_cache_get_entry failed: ", con
->physical
.path
);
942 if (!S_ISDIR(sce
->st
.st_mode
)) return HANDLER_GO_ON
;
944 if (http_list_directory(srv
, con
, p
, con
->physical
.path
)) {
945 /* dirlisting failed */
946 con
->http_status
= 403;
949 buffer_reset(con
->physical
.path
);
952 return HANDLER_FINISHED
;
955 /* this function is called at dlopen() time and inits the callbacks */
957 int mod_dirlisting_plugin_init(plugin
*p
);
958 int mod_dirlisting_plugin_init(plugin
*p
) {
959 p
->version
= LIGHTTPD_VERSION_ID
;
960 p
->name
= buffer_init_string("dirlisting");
962 p
->init
= mod_dirlisting_init
;
963 p
->handle_subrequest_start
= mod_dirlisting_subrequest
;
964 p
->set_defaults
= mod_dirlisting_set_defaults
;
965 p
->cleanup
= mod_dirlisting_free
;