7 #include "http_header.h"
11 #include "stat_cache.h"
26 * this is a dirlisting for a lighttpd plugin
29 #ifdef HAVE_ATTR_ATTRIBUTES_H
30 #include <attr/attributes.h>
33 #ifdef HAVE_SYS_EXTATTR_H
34 #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 hide_readme_file
;
57 unsigned short encode_readme
;
58 unsigned short hide_header_file
;
59 unsigned short encode_header
;
60 unsigned short auto_layout
;
62 excludes_buffer
*excludes
;
76 buffer
*content_charset
;
78 plugin_config
**config_storage
;
83 static excludes_buffer
*excludes_buffer_init(void) {
86 exb
= calloc(1, sizeof(*exb
));
92 static int excludes_buffer_append(excludes_buffer
*exb
, buffer
*string
) {
97 if (!string
) return -1;
99 if (exb
->used
== exb
->size
) {
102 exb
->ptr
= realloc(exb
->ptr
, exb
->size
* sizeof(*exb
->ptr
));
104 for(i
= exb
->used
; i
< exb
->size
; i
++) {
105 exb
->ptr
[i
] = calloc(1, sizeof(**exb
->ptr
));
110 if (NULL
== (exb
->ptr
[exb
->used
]->regex
= pcre_compile(string
->ptr
, 0,
111 &errptr
, &erroff
, NULL
))) {
115 exb
->ptr
[exb
->used
]->string
= buffer_init();
116 buffer_copy_buffer(exb
->ptr
[exb
->used
]->string
, string
);
124 static void excludes_buffer_free(excludes_buffer
*exb
) {
128 for (i
= 0; i
< exb
->size
; i
++) {
129 if (exb
->ptr
[i
]->regex
) pcre_free(exb
->ptr
[i
]->regex
);
130 if (exb
->ptr
[i
]->string
) buffer_free(exb
->ptr
[i
]->string
);
134 if (exb
->ptr
) free(exb
->ptr
);
140 /* init the plugin data */
141 INIT_FUNC(mod_dirlisting_init
) {
144 p
= calloc(1, sizeof(*p
));
146 p
->tmp_buf
= buffer_init();
147 p
->content_charset
= buffer_init();
152 /* detroy the plugin data */
153 FREE_FUNC(mod_dirlisting_free
) {
154 plugin_data
*p
= p_d
;
158 if (!p
) return HANDLER_GO_ON
;
160 if (p
->config_storage
) {
162 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
163 plugin_config
*s
= p
->config_storage
[i
];
167 excludes_buffer_free(s
->excludes
);
168 buffer_free(s
->show_readme
);
169 buffer_free(s
->show_header
);
170 buffer_free(s
->external_css
);
171 buffer_free(s
->external_js
);
172 buffer_free(s
->encoding
);
173 buffer_free(s
->set_footer
);
177 free(p
->config_storage
);
180 buffer_free(p
->tmp_buf
);
181 buffer_free(p
->content_charset
);
185 return HANDLER_GO_ON
;
188 /* handle plugin config and check values */
190 #define CONFIG_EXCLUDE "dir-listing.exclude"
191 #define CONFIG_ACTIVATE "dir-listing.activate"
192 #define CONFIG_HIDE_DOTFILES "dir-listing.hide-dotfiles"
193 #define CONFIG_EXTERNAL_CSS "dir-listing.external-css"
194 #define CONFIG_EXTERNAL_JS "dir-listing.external-js"
195 #define CONFIG_ENCODING "dir-listing.encoding"
196 #define CONFIG_SHOW_README "dir-listing.show-readme"
197 #define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
198 #define CONFIG_SHOW_HEADER "dir-listing.show-header"
199 #define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
200 #define CONFIG_DIR_LISTING "server.dir-listing"
201 #define CONFIG_SET_FOOTER "dir-listing.set-footer"
202 #define CONFIG_ENCODE_README "dir-listing.encode-readme"
203 #define CONFIG_ENCODE_HEADER "dir-listing.encode-header"
204 #define CONFIG_AUTO_LAYOUT "dir-listing.auto-layout"
207 SETDEFAULTS_FUNC(mod_dirlisting_set_defaults
) {
208 plugin_data
*p
= p_d
;
211 config_values_t cv
[] = {
212 { CONFIG_EXCLUDE
, NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
213 { CONFIG_ACTIVATE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
214 { CONFIG_HIDE_DOTFILES
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
215 { CONFIG_EXTERNAL_CSS
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
216 { CONFIG_ENCODING
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
217 { CONFIG_SHOW_README
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
218 { CONFIG_HIDE_README_FILE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
219 { CONFIG_SHOW_HEADER
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 7 */
220 { CONFIG_HIDE_HEADER_FILE
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 8 */
221 { CONFIG_DIR_LISTING
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 9 */
222 { CONFIG_SET_FOOTER
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 10 */
223 { CONFIG_ENCODE_README
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 11 */
224 { CONFIG_ENCODE_HEADER
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 12 */
225 { CONFIG_AUTO_LAYOUT
, NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 13 */
226 { CONFIG_EXTERNAL_JS
, NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 14 */
228 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
231 if (!p
) return HANDLER_ERROR
;
233 p
->config_storage
= calloc(srv
->config_context
->used
, sizeof(plugin_config
*));
235 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
236 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
238 data_unset
*du_excludes
;
240 s
= calloc(1, sizeof(plugin_config
));
241 s
->excludes
= excludes_buffer_init();
243 s
->show_readme
= buffer_init();
244 s
->show_header
= buffer_init();
245 s
->external_css
= buffer_init();
246 s
->external_js
= buffer_init();
247 s
->hide_dot_files
= 1;
248 s
->hide_readme_file
= 0;
249 s
->hide_header_file
= 0;
250 s
->encode_readme
= 1;
251 s
->encode_header
= 1;
254 s
->encoding
= buffer_init();
255 s
->set_footer
= buffer_init();
257 cv
[0].destination
= s
->excludes
;
258 cv
[1].destination
= &(s
->dir_listing
);
259 cv
[2].destination
= &(s
->hide_dot_files
);
260 cv
[3].destination
= s
->external_css
;
261 cv
[4].destination
= s
->encoding
;
262 cv
[5].destination
= s
->show_readme
;
263 cv
[6].destination
= &(s
->hide_readme_file
);
264 cv
[7].destination
= s
->show_header
;
265 cv
[8].destination
= &(s
->hide_header_file
);
266 cv
[9].destination
= &(s
->dir_listing
); /* old name */
267 cv
[10].destination
= s
->set_footer
;
268 cv
[11].destination
= &(s
->encode_readme
);
269 cv
[12].destination
= &(s
->encode_header
);
270 cv
[13].destination
= &(s
->auto_layout
);
271 cv
[14].destination
= s
->external_js
;
273 p
->config_storage
[i
] = s
;
275 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
276 return HANDLER_ERROR
;
279 if (NULL
!= (du_excludes
= array_get_element(config
->value
, CONFIG_EXCLUDE
))) {
280 array
*excludes_list
;
282 excludes_list
= ((data_array
*)du_excludes
)->value
;
284 if (du_excludes
->type
!= TYPE_ARRAY
|| !array_is_vlist(excludes_list
)) {
285 log_error_write(srv
, __FILE__
, __LINE__
, "s",
286 "unexpected type for " CONFIG_EXCLUDE
"; expected list of \"regex\"");
287 return HANDLER_ERROR
;
291 if (excludes_list
->used
> 0) {
292 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
293 "pcre support is missing for: ", CONFIG_EXCLUDE
, ", please install libpcre and the headers");
294 return HANDLER_ERROR
;
297 for (size_t j
= 0; j
< excludes_list
->used
; ++j
) {
298 data_unset
*du_exclude
= excludes_list
->data
[j
];
300 if (du_exclude
->type
!= TYPE_STRING
) {
301 log_error_write(srv
, __FILE__
, __LINE__
, "sssbs",
302 "unexpected type for key: ", CONFIG_EXCLUDE
, "[",
303 du_exclude
->key
, "](string)");
304 return HANDLER_ERROR
;
307 if (0 != excludes_buffer_append(s
->excludes
, ((data_string
*)(du_exclude
))->value
)) {
308 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
309 "pcre-compile failed for", ((data_string
*)(du_exclude
))->value
);
310 return HANDLER_ERROR
;
316 if (!buffer_string_is_empty(s
->show_readme
)) {
317 if (buffer_is_equal_string(s
->show_readme
, CONST_STR_LEN("enable"))) {
318 buffer_copy_string_len(s
->show_readme
, CONST_STR_LEN("README.txt"));
320 else if (buffer_is_equal_string(s
->show_readme
, CONST_STR_LEN("disable"))) {
321 buffer_clear(s
->show_readme
);
325 if (!buffer_string_is_empty(s
->show_header
)) {
326 if (buffer_is_equal_string(s
->show_header
, CONST_STR_LEN("enable"))) {
327 buffer_copy_string_len(s
->show_header
, CONST_STR_LEN("HEADER.txt"));
329 else if (buffer_is_equal_string(s
->show_header
, CONST_STR_LEN("disable"))) {
330 buffer_clear(s
->show_header
);
335 return HANDLER_GO_ON
;
340 static int mod_dirlisting_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
342 plugin_config
*s
= p
->config_storage
[0];
347 PATCH(hide_dot_files
);
350 PATCH(hide_readme_file
);
352 PATCH(hide_header_file
);
355 PATCH(encode_readme
);
356 PATCH(encode_header
);
359 /* skip the first, the global context */
360 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
361 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
362 s
= p
->config_storage
[i
];
364 /* condition didn't match */
365 if (!config_check_cond(srv
, con
, dc
)) continue;
368 for (j
= 0; j
< dc
->value
->used
; j
++) {
369 data_unset
*du
= dc
->value
->data
[j
];
371 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ACTIVATE
)) ||
372 buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_DIR_LISTING
))) {
374 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_DOTFILES
))) {
375 PATCH(hide_dot_files
);
376 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_EXTERNAL_CSS
))) {
378 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_EXTERNAL_JS
))) {
380 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODING
))) {
382 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SHOW_README
))) {
384 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_README_FILE
))) {
385 PATCH(hide_readme_file
);
386 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SHOW_HEADER
))) {
388 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE
))) {
389 PATCH(hide_header_file
);
390 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_SET_FOOTER
))) {
392 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_EXCLUDE
))) {
394 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODE_README
))) {
395 PATCH(encode_readme
);
396 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_ENCODE_HEADER
))) {
397 PATCH(encode_header
);
398 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN(CONFIG_AUTO_LAYOUT
))) {
420 #define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t))
421 #define DIRLIST_BLOB_SIZE 16
423 /* simple combsort algorithm */
424 static void http_dirls_sort(dirls_entry_t
**ent
, int num
) {
431 gap
= (gap
* 10) / 13;
432 if (gap
== 9 || gap
== 10)
438 for (i
= 0; i
< num
- gap
; i
++) {
440 if (strcmp(DIRLIST_ENT_NAME(ent
[i
]), DIRLIST_ENT_NAME(ent
[j
])) > 0) {
448 } while (gap
> 1 || swapped
);
451 /* buffer must be able to hold "999.9K"
452 * conversion is simple but not perfect
454 static int http_list_directory_sizefmt(char *buf
, size_t bufsz
, off_t size
) {
455 const char unit
[] = " KMGTPE"; /* Kilo, Mega, Giga, Tera, Peta, Exa */
456 const char *u
= unit
; /* u will always increment at least once */
466 remain
= (int) size
& 1023;
469 if ((size
& (~0 ^ 1023)) == 0)
482 li_itostrn(buf
, bufsz
, size
);
483 buflen
= strlen(buf
);
484 if (buflen
+ 3 >= bufsz
) return buflen
;
486 buf
[buflen
+1] = remain
+ '0';
488 buf
[buflen
+3] = '\0';
493 static void http_list_directory_include_file(buffer
*out
, int symlinks
, buffer
*path
, const char *classname
, int encode
) {
494 int fd
= fdevent_open_cloexec(path
->ptr
, symlinks
, O_RDONLY
, 0);
498 if (-1 == fd
) return;
501 buffer_append_string_len(out
, CONST_STR_LEN("<pre class=\""));
502 buffer_append_string(out
, classname
);
503 buffer_append_string_len(out
, CONST_STR_LEN("\">"));
506 while ((rd
= read(fd
, buf
, sizeof(buf
))) > 0) {
508 buffer_append_string_encoded(out
, buf
, (size_t)rd
, ENCODING_MINIMAL_XML
);
510 buffer_append_string_len(out
, buf
, (size_t)rd
);
516 buffer_append_string_len(out
, CONST_STR_LEN("</pre>"));
520 /* portions copied from mod_status
521 * modified and specialized for stable dirlist sorting by name */
522 static const char js_simple_table_resort
[] = \
523 "var click_column;\n" \
524 "var name_column = 0;\n" \
525 "var date_column = 1;\n" \
526 "var size_column = 2;\n" \
527 "var type_column = 3;\n" \
528 "var prev_span = null;\n" \
530 "if (typeof(String.prototype.localeCompare) === 'undefined') {\n" \
531 " String.prototype.localeCompare = function(str, locale, options) {\n" \
532 " return ((this == str) ? 0 : ((this > str) ? 1 : -1));\n" \
536 "if (typeof(String.prototype.toLocaleUpperCase) === 'undefined') {\n" \
537 " String.prototype.toLocaleUpperCase = function() {\n" \
538 " return this.toUpperCase();\n" \
542 "function get_inner_text(el) {\n" \
543 " if((typeof el == 'string')||(typeof el == 'undefined'))\n" \
545 " if(el.innerText)\n" \
546 " return el.innerText;\n" \
548 " var str = \"\";\n" \
549 " var cs = el.childNodes;\n" \
550 " var l = cs.length;\n" \
551 " for (i=0;i<l;i++) {\n" \
552 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" \
553 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" \
559 "function isdigit(c) {\n" \
560 " return (c >= '0' && c <= '9');\n" \
563 "function unit_multiplier(unit) {\n" \
564 " return (unit=='K') ? 1000\n" \
565 " : (unit=='M') ? 1000000\n" \
566 " : (unit=='G') ? 1000000000\n" \
567 " : (unit=='T') ? 1000000000000\n" \
568 " : (unit=='P') ? 1000000000000000\n" \
569 " : (unit=='E') ? 1000000000000000000 : 1;\n" \
572 "var li_date_regex=/(\\d{4})-(\\w{3})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})/;\n" \
574 "var li_mon = ['Jan','Feb','Mar','Apr','May','Jun',\n" \
575 " 'Jul','Aug','Sep','Oct','Nov','Dec'];\n" \
577 "function li_mon_num(mon) {\n" \
578 " var i; for (i = 0; i < 12 && mon != li_mon[i]; ++i); return i;\n" \
581 "function li_date_cmp(s1, s2) {\n" \
582 " var dp1 = li_date_regex.exec(s1)\n" \
583 " var dp2 = li_date_regex.exec(s2)\n" \
584 " for (var i = 1; i < 7; ++i) {\n" \
585 " var cmp = (2 != i)\n" \
586 " ? parseInt(dp1[i]) - parseInt(dp2[i])\n" \
587 " : li_mon_num(dp1[2]) - li_mon_num(dp2[2]);\n" \
588 " if (0 != cmp) return cmp;\n" \
593 "function sortfn_then_by_name(a,b,sort_column) {\n" \
594 " if (sort_column == name_column || sort_column == type_column) {\n" \
595 " var ad = (a.cells[type_column].innerHTML === 'Directory');\n" \
596 " var bd = (b.cells[type_column].innerHTML === 'Directory');\n" \
597 " if (ad != bd) return (ad ? -1 : 1);\n" \
599 " var at = get_inner_text(a.cells[sort_column]);\n" \
600 " var bt = get_inner_text(b.cells[sort_column]);\n" \
602 " if (sort_column == name_column) {\n" \
603 " if (at == '..') return -1;\n" \
604 " if (bt == '..') return 1;\n" \
606 " if (a.cells[sort_column].className == 'int') {\n" \
607 " cmp = parseInt(at)-parseInt(bt);\n" \
608 " } else if (sort_column == date_column) {\n" \
609 " var ad = isdigit(at.substr(0,1));\n" \
610 " var bd = isdigit(bt.substr(0,1));\n" \
611 " if (ad != bd) return (!ad ? -1 : 1);\n" \
612 " cmp = li_date_cmp(at,bt);\n" \
613 " } else if (sort_column == size_column) {\n" \
614 " var ai = parseInt(at, 10) * unit_multiplier(at.substr(-1,1));\n" \
615 " var bi = parseInt(bt, 10) * unit_multiplier(bt.substr(-1,1));\n" \
616 " if (at.substr(0,1) == '-') ai = -1;\n" \
617 " if (bt.substr(0,1) == '-') bi = -1;\n" \
618 " cmp = ai - bi;\n" \
620 " cmp = at.toLocaleUpperCase().localeCompare(bt.toLocaleUpperCase());\n" \
621 " if (0 != cmp) return cmp;\n" \
622 " cmp = at.localeCompare(bt);\n" \
624 " if (0 != cmp || sort_column == name_column) return cmp;\n" \
625 " return sortfn_then_by_name(a,b,name_column);\n" \
628 "function sortfn(a,b) {\n" \
629 " return sortfn_then_by_name(a,b,click_column);\n" \
632 "function resort(lnk) {\n" \
633 " var span = lnk.childNodes[1];\n" \
634 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" \
635 " var rows = new Array();\n" \
636 " for (j=1;j<table.rows.length;j++)\n" \
637 " rows[j-1] = table.rows[j];\n" \
638 " click_column = lnk.parentNode.cellIndex;\n" \
639 " rows.sort(sortfn);\n" \
641 " if (prev_span != null) prev_span.innerHTML = '';\n" \
642 " if (span.getAttribute('sortdir')=='down') {\n" \
643 " span.innerHTML = '↑';\n" \
644 " span.setAttribute('sortdir','up');\n" \
645 " rows.reverse();\n" \
647 " span.innerHTML = '↓';\n" \
648 " span.setAttribute('sortdir','down');\n" \
650 " for (i=0;i<rows.length;i++)\n" \
651 " table.tBodies[0].appendChild(rows[i]);\n" \
652 " prev_span = span;\n" \
655 /* portions copied from mod_dirlist (lighttpd2) */
656 static const char js_simple_table_init_sort
[] = \
658 "function init_sort(init_sort_column, ascending) {\n" \
659 " var tables = document.getElementsByTagName(\"table\");\n" \
660 " for (var i = 0; i < tables.length; i++) {\n" \
661 " var table = tables[i];\n" \
662 " //var c = table.getAttribute(\"class\")\n" \
663 " //if (-1 != c.split(\" \").indexOf(\"sort\")) {\n" \
664 " var row = table.rows[0].cells;\n" \
665 " for (var j = 0; j < row.length; j++) {\n" \
666 " var n = row[j];\n" \
667 " if (n.childNodes.length == 1 && n.childNodes[0].nodeType == 3) {\n" \
668 " var link = document.createElement(\"a\");\n" \
669 " var title = n.childNodes[0].nodeValue.replace(/:$/, \"\");\n" \
670 " link.appendChild(document.createTextNode(title));\n" \
671 " link.setAttribute(\"href\", \"#\");\n" \
672 " link.setAttribute(\"class\", \"sortheader\");\n" \
673 " link.setAttribute(\"onclick\", \"resort(this);return false;\");\n" \
674 " var arrow = document.createElement(\"span\");\n" \
675 " arrow.setAttribute(\"class\", \"sortarrow\");\n" \
676 " arrow.appendChild(document.createTextNode(\":\"));\n" \
677 " link.appendChild(arrow)\n" \
678 " n.replaceChild(link, n.firstChild);\n" \
681 " var lnk = row[init_sort_column].firstChild;\n" \
682 " if (ascending) {\n" \
683 " var span = lnk.childNodes[1];\n" \
684 " span.setAttribute('sortdir','down');\n" \
691 static void http_dirlist_append_js_table_resort (buffer
*b
, connection
*con
) {
693 char ascending
= '0';
694 if (!buffer_string_is_empty(con
->uri
.query
)) {
695 const char *qs
= con
->uri
.query
->ptr
;
697 if (qs
[0] == 'C' && qs
[1] == '=') {
699 case 'N': col
= '0'; break;
700 case 'M': col
= '1'; break;
701 case 'S': col
= '2'; break;
703 case 'D': col
= '3'; break;
707 else if (qs
[0] == 'O' && qs
[1] == '=') {
709 case 'A': ascending
= '1'; break;
710 case 'D': ascending
= '0'; break;
714 } while ((qs
= strchr(qs
, '&')) && *++qs
);
717 buffer_append_string_len(b
, CONST_STR_LEN("\n<script type=\"text/javascript\">\n// <!--\n\n"));
718 buffer_append_string_len(b
, js_simple_table_resort
, sizeof(js_simple_table_resort
)-1);
719 buffer_append_string_len(b
, js_simple_table_init_sort
, sizeof(js_simple_table_init_sort
)-1);
720 buffer_append_string_len(b
, CONST_STR_LEN("\ninit_sort("));
721 buffer_append_string_len(b
, &col
, 1);
722 buffer_append_string_len(b
, CONST_STR_LEN(", "));
723 buffer_append_string_len(b
, &ascending
, 1);
724 buffer_append_string_len(b
, CONST_STR_LEN(");\n\n// -->\n</script>\n\n"));
727 static void http_list_directory_header(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*out
) {
730 if (p
->conf
.auto_layout
) {
731 buffer_append_string_len(out
, CONST_STR_LEN(
736 if (!buffer_string_is_empty(p
->conf
.encoding
)) {
737 buffer_append_string_len(out
, CONST_STR_LEN("<meta charset=\""));
738 buffer_append_string_buffer(out
, p
->conf
.encoding
);
739 buffer_append_string_len(out
, CONST_STR_LEN("\">\n"));
741 buffer_append_string_len(out
, CONST_STR_LEN("<title>Index of "));
742 buffer_append_string_encoded(out
, CONST_BUF_LEN(con
->uri
.path
), ENCODING_MINIMAL_XML
);
743 buffer_append_string_len(out
, CONST_STR_LEN("</title>\n"));
745 if (!buffer_string_is_empty(p
->conf
.external_css
)) {
746 buffer_append_string_len(out
, CONST_STR_LEN("<meta name=\"viewport\" content=\"initial-scale=1\">"));
747 buffer_append_string_len(out
, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
748 buffer_append_string_buffer(out
, p
->conf
.external_css
);
749 buffer_append_string_len(out
, CONST_STR_LEN("\">\n"));
751 buffer_append_string_len(out
, CONST_STR_LEN(
752 "<style type=\"text/css\">\n"
753 "a, a:active {text-decoration: none; color: blue;}\n"
754 "a:visited {color: #48468F;}\n"
755 "a:hover, a:focus {text-decoration: underline; color: red;}\n"
756 "body {background-color: #F5F5F5;}\n"
757 "h2 {margin-bottom: 12px;}\n"
758 "table {margin-left: 12px;}\n"
760 " font: 90% monospace;"
764 " font-weight: bold;"
765 " padding-right: 14px;"
766 " padding-bottom: 3px;"
768 "td {padding-right: 14px;}\n"
769 "td.s, th.s {text-align: right;}\n"
771 " background-color: white;"
772 " border-top: 1px solid #646464;"
773 " border-bottom: 1px solid #646464;"
774 " padding-top: 10px;"
775 " padding-bottom: 14px;"
778 " font: 90% monospace;"
786 buffer_append_string_len(out
, CONST_STR_LEN("</head>\n<body>\n"));
789 if (!buffer_string_is_empty(p
->conf
.show_header
)) {
790 /* if we have a HEADER file, display it in <pre class="header"></pre> */
792 buffer
*hb
= p
->conf
.show_header
;
793 if (hb
->ptr
[0] != '/') {
794 buffer_copy_buffer(p
->tmp_buf
, con
->physical
.path
);
795 buffer_append_path_len(p
->tmp_buf
, CONST_BUF_LEN(p
->conf
.show_header
));
799 http_list_directory_include_file(out
, con
->conf
.follow_symlink
, hb
, "header", p
->conf
.encode_header
);
802 buffer_append_string_len(out
, CONST_STR_LEN("<h2>Index of "));
803 buffer_append_string_encoded(out
, CONST_BUF_LEN(con
->uri
.path
), ENCODING_MINIMAL_XML
);
804 buffer_append_string_len(out
, CONST_STR_LEN(
806 "<div class=\"list\">\n"
807 "<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
810 "<th class=\"n\">Name</th>"
811 "<th class=\"m\">Last Modified</th>"
812 "<th class=\"s\">Size</th>"
813 "<th class=\"t\">Type</th>"
818 if (!buffer_is_equal_string(con
->uri
.path
, CONST_STR_LEN("/"))) {
819 buffer_append_string_len(out
, CONST_STR_LEN(
821 "<td class=\"n\"><a href=\"../\">..</a>/</td>"
822 "<td class=\"m\"> </td>"
823 "<td class=\"s\">- </td>"
824 "<td class=\"t\">Directory</td>"
830 static void http_list_directory_footer(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*out
) {
833 buffer_append_string_len(out
, CONST_STR_LEN(
839 if (!buffer_string_is_empty(p
->conf
.show_readme
)) {
840 /* if we have a README file, display it in <pre class="readme"></pre> */
842 buffer
*rb
= p
->conf
.show_readme
;
843 if (rb
->ptr
[0] != '/') {
844 buffer_copy_buffer(p
->tmp_buf
, con
->physical
.path
);
845 buffer_append_path_len(p
->tmp_buf
, CONST_BUF_LEN(p
->conf
.show_readme
));
849 http_list_directory_include_file(out
, con
->conf
.follow_symlink
, rb
, "readme", p
->conf
.encode_readme
);
852 if(p
->conf
.auto_layout
) {
854 buffer_append_string_len(out
, CONST_STR_LEN(
855 "<div class=\"foot\">"
858 if (!buffer_string_is_empty(p
->conf
.set_footer
)) {
859 buffer_append_string_buffer(out
, p
->conf
.set_footer
);
861 buffer_append_string_buffer(out
, con
->conf
.server_tag
);
864 buffer_append_string_len(out
, CONST_STR_LEN(
868 if (!buffer_string_is_empty(p
->conf
.external_js
)) {
869 buffer_append_string_len(out
, CONST_STR_LEN("<script type=\"text/javascript\" src=\""));
870 buffer_append_string_buffer(out
, p
->conf
.external_js
);
871 buffer_append_string_len(out
, CONST_STR_LEN("\"></script>\n"));
872 } else if (buffer_is_empty(p
->conf
.external_js
)) {
873 http_dirlist_append_js_table_resort(out
, con
);
876 buffer_append_string_len(out
, CONST_STR_LEN(
883 static int http_list_directory(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*dir
) {
888 char *path
, *path_file
;
890 int hide_dotfiles
= p
->conf
.hide_dot_files
;
891 dirls_list_t dirs
, files
, *list
;
893 char sizebuf
[sizeof("999.9K")];
894 char datebuf
[sizeof("2005-Jan-01 22:23:24")];
895 const char *content_type
;
897 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
901 #ifdef HAVE_LOCALTIME_R
905 if (buffer_string_is_empty(dir
)) return -1;
907 i
= buffer_string_length(dir
);
910 if (0 >= (name_max
= pathconf(dir
->ptr
, _PC_NAME_MAX
))) {
911 /* some broken fs (fuse) return 0 instead of -1 */
915 name_max
= 255; /* stupid default */
918 #elif defined __WIN32
919 name_max
= FILENAME_MAX
;
924 path
= malloc(i
+ name_max
+ 1);
925 force_assert(NULL
!= path
);
926 memcpy(path
, dir
->ptr
, i
+1);
927 path_file
= path
+ i
;
929 if (NULL
== (dp
= opendir(path
))) {
930 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
931 "opendir failed:", dir
, strerror(errno
));
937 dirs
.ent
= (dirls_entry_t
**) malloc(sizeof(dirls_entry_t
*) * DIRLIST_BLOB_SIZE
);
938 force_assert(dirs
.ent
);
939 dirs
.size
= DIRLIST_BLOB_SIZE
;
941 files
.ent
= (dirls_entry_t
**) malloc(sizeof(dirls_entry_t
*) * DIRLIST_BLOB_SIZE
);
942 force_assert(files
.ent
);
943 files
.size
= DIRLIST_BLOB_SIZE
;
946 while ((dent
= readdir(dp
)) != NULL
) {
948 unsigned short exclude_match
= 0;
951 if (dent
->d_name
[0] == '.') {
954 if (dent
->d_name
[1] == '\0')
956 if (dent
->d_name
[1] == '.' && dent
->d_name
[2] == '\0')
960 if (p
->conf
.hide_readme_file
&& !buffer_string_is_empty(p
->conf
.show_readme
)) {
961 if (strcmp(dent
->d_name
, p
->conf
.show_readme
->ptr
) == 0)
964 if (p
->conf
.hide_header_file
&& !buffer_string_is_empty(p
->conf
.show_header
)) {
965 if (strcmp(dent
->d_name
, p
->conf
.show_header
->ptr
) == 0)
969 /* compare d_name against excludes array
970 * elements, skipping any that match.
973 for(i
= 0; i
< p
->conf
.excludes
->used
; i
++) {
977 pcre
*regex
= p
->conf
.excludes
->ptr
[i
]->regex
;
979 if ((n
= pcre_exec(regex
, NULL
, dent
->d_name
,
980 strlen(dent
->d_name
), 0, 0, ovec
, 3 * N
)) < 0) {
981 if (n
!= PCRE_ERROR_NOMATCH
) {
982 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
983 "execution error while matching:", n
);
985 /* aborting would require a lot of manual cleanup here.
986 * skip instead (to not leak names that break pcre matching)
1003 i
= strlen(dent
->d_name
);
1005 /* NOTE: the manual says, d_name is never more than NAME_MAX
1006 * so this should actually not be a buffer-overflow-risk
1008 if (i
> (size_t)name_max
) continue;
1010 memcpy(path_file
, dent
->d_name
, i
+ 1);
1011 if (stat(path
, &st
) != 0)
1015 if (S_ISDIR(st
.st_mode
))
1018 if (list
->used
== list
->size
) {
1019 list
->size
+= DIRLIST_BLOB_SIZE
;
1020 list
->ent
= (dirls_entry_t
**) realloc(list
->ent
, sizeof(dirls_entry_t
*) * list
->size
);
1021 force_assert(list
->ent
);
1024 tmp
= (dirls_entry_t
*) malloc(sizeof(dirls_entry_t
) + 1 + i
);
1025 tmp
->mtime
= st
.st_mtime
;
1026 tmp
->size
= st
.st_size
;
1028 memcpy(DIRLIST_ENT_NAME(tmp
), dent
->d_name
, i
+ 1);
1030 list
->ent
[list
->used
++] = tmp
;
1034 if (dirs
.used
) http_dirls_sort(dirs
.ent
, dirs
.used
);
1036 if (files
.used
) http_dirls_sort(files
.ent
, files
.used
);
1038 out
= chunkqueue_append_buffer_open(con
->write_queue
);
1039 http_list_directory_header(srv
, con
, p
, out
);
1042 for (i
= 0; i
< dirs
.used
; i
++) {
1045 #ifdef HAVE_LOCALTIME_R
1046 localtime_r(&(tmp
->mtime
), &tm
);
1047 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", &tm
);
1049 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", localtime(&(tmp
->mtime
)));
1052 buffer_append_string_len(out
, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\""));
1053 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_REL_URI_PART
);
1054 buffer_append_string_len(out
, CONST_STR_LEN("/\">"));
1055 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_MINIMAL_XML
);
1056 buffer_append_string_len(out
, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
1057 buffer_append_string_len(out
, datebuf
, sizeof(datebuf
) - 1);
1058 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"));
1064 for (i
= 0; i
< files
.used
; i
++) {
1067 content_type
= NULL
;
1068 #if defined(HAVE_XATTR)
1069 if (con
->conf
.use_xattr
) {
1070 memcpy(path_file
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
+ 1);
1071 attrlen
= sizeof(attrval
) - 1;
1072 if (attr_get(path
, srv
->srvconf
.xattr_name
->ptr
, attrval
, &attrlen
, 0) == 0) {
1073 attrval
[attrlen
] = '\0';
1074 content_type
= attrval
;
1077 #elif defined(HAVE_EXTATTR)
1078 if (con
->conf
.use_xattr
) {
1079 memcpy(path_file
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
+ 1);
1080 if(-1 != (attrlen
= extattr_get_file(path
, EXTATTR_NAMESPACE_USER
, srv
->srvconf
.xattr_name
->ptr
, attrval
, sizeof(attrval
)-1))) {
1081 attrval
[attrlen
] = '\0';
1082 content_type
= attrval
;
1087 if (content_type
== NULL
) {
1088 const buffer
*type
= stat_cache_mimetype_by_ext(con
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
);
1089 content_type
= NULL
!= type
? type
->ptr
: "application/octet-stream";
1092 #ifdef HAVE_LOCALTIME_R
1093 localtime_r(&(tmp
->mtime
), &tm
);
1094 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", &tm
);
1096 strftime(datebuf
, sizeof(datebuf
), "%Y-%b-%d %H:%M:%S", localtime(&(tmp
->mtime
)));
1098 http_list_directory_sizefmt(sizebuf
, sizeof(sizebuf
), tmp
->size
);
1100 buffer_append_string_len(out
, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
1101 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_REL_URI_PART
);
1102 buffer_append_string_len(out
, CONST_STR_LEN("\">"));
1103 buffer_append_string_encoded(out
, DIRLIST_ENT_NAME(tmp
), tmp
->namelen
, ENCODING_MINIMAL_XML
);
1104 buffer_append_string_len(out
, CONST_STR_LEN("</a></td><td class=\"m\">"));
1105 buffer_append_string_len(out
, datebuf
, sizeof(datebuf
) - 1);
1106 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"s\">"));
1107 buffer_append_string(out
, sizebuf
);
1108 buffer_append_string_len(out
, CONST_STR_LEN("</td><td class=\"t\">"));
1109 buffer_append_string(out
, content_type
);
1110 buffer_append_string_len(out
, CONST_STR_LEN("</td></tr>\n"));
1119 http_list_directory_footer(srv
, con
, p
, out
);
1121 /* Insert possible charset to Content-Type */
1122 if (buffer_string_is_empty(p
->conf
.encoding
)) {
1123 http_header_response_set(con
, HTTP_HEADER_CONTENT_TYPE
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1125 buffer_copy_string_len(p
->content_charset
, CONST_STR_LEN("text/html; charset="));
1126 buffer_append_string_buffer(p
->content_charset
, p
->conf
.encoding
);
1127 http_header_response_set(con
, HTTP_HEADER_CONTENT_TYPE
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->content_charset
));
1130 chunkqueue_append_buffer_commit(con
->write_queue
);
1131 con
->file_finished
= 1;
1138 URIHANDLER_FUNC(mod_dirlisting_subrequest
) {
1139 plugin_data
*p
= p_d
;
1140 stat_cache_entry
*sce
= NULL
;
1144 /* we only handle GET and HEAD */
1145 switch(con
->request
.http_method
) {
1146 case HTTP_METHOD_GET
:
1147 case HTTP_METHOD_HEAD
:
1150 return HANDLER_GO_ON
;
1153 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1155 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1156 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
1157 if (con
->uri
.path
->ptr
[buffer_string_length(con
->uri
.path
) - 1] != '/') return HANDLER_GO_ON
;
1159 mod_dirlisting_patch_connection(srv
, con
, p
);
1161 if (!p
->conf
.dir_listing
) return HANDLER_GO_ON
;
1163 if (con
->conf
.log_request_handling
) {
1164 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- handling the request as Dir-Listing");
1165 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "URI :", con
->uri
.path
);
1168 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
1169 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "stat_cache_get_entry failed: ", con
->physical
.path
);
1173 if (!S_ISDIR(sce
->st
.st_mode
)) return HANDLER_GO_ON
;
1175 if (http_list_directory(srv
, con
, p
, con
->physical
.path
)) {
1176 /* dirlisting failed */
1177 con
->http_status
= 403;
1180 buffer_reset(con
->physical
.path
);
1183 return HANDLER_FINISHED
;
1186 /* this function is called at dlopen() time and inits the callbacks */
1188 int mod_dirlisting_plugin_init(plugin
*p
);
1189 int mod_dirlisting_plugin_init(plugin
*p
) {
1190 p
->version
= LIGHTTPD_VERSION_ID
;
1191 p
->name
= buffer_init_string("dirlisting");
1193 p
->init
= mod_dirlisting_init
;
1194 p
->handle_subrequest_start
= mod_dirlisting_subrequest
;
1195 p
->set_defaults
= mod_dirlisting_set_defaults
;
1196 p
->cleanup
= mod_dirlisting_free
;