xref: /lighttpd1.4/src/mod_dirlisting.c (revision 76b8298f)
1 /* fstatat() fdopendir() */
2 #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE-0 < 700
3 #undef  _XOPEN_SOURCE
4 #define _XOPEN_SOURCE 700
5 /* NetBSD dirent.h improperly hides fdopendir() (POSIX.1-2008) declaration
6  * which should be visible with _XOPEN_SOURCE 700 or _POSIX_C_SOURCE 200809L */
7 #ifdef __NetBSD__
8 #define _NETBSD_SOURCE
9 #endif
10 #endif
11 
12 #include "first.h"
13 
14 #include "sys-time.h"
15 
16 #include "base.h"
17 #include "log.h"
18 #include "buffer.h"
19 #include "fdevent.h"
20 #include "http_chunk.h"
21 #include "http_etag.h"
22 #include "http_header.h"
23 #include "keyvalue.h"
24 #include "response.h"
25 
26 #include "plugin.h"
27 
28 #include "stat_cache.h"
29 
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <dirent.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36 
37 #ifdef AT_FDCWD
38 #ifndef _ATFILE_SOURCE
39 #define _ATFILE_SOURCE
40 #endif
41 #endif
42 
43 #ifndef _D_EXACT_NAMLEN
44 #ifdef _DIRENT_HAVE_D_NAMLEN
45 #define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
46 #else
47 #define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name))
48 #endif
49 #endif
50 
51 /**
52  * this is a dirlisting for a lighttpd plugin
53  *
54  * Notes:
55  * - mod_dirlisting is a dir list implementation.  One size does not fit all.
56  *   mod_dirlisting aims to be somewhat flexible, but if special behavior
57  *   is needed, use custom CGI/FastCGI/SCGI to handle dir listing instead.
58  *   - backend daemon could implement custom caching
59  *   - backend daemon could monitor directory for changes (e.g. inotify)
60  *   - backend daemon or scripts could trigger when directory is modified
61  *     and could regenerate index.html (and mod_indexfile could be used
62  *     instead of mod_dirlisting)
63  * - basic alphabetical sorting (in C locale) is done on server side
64  *   in case client does not execute javascript
65  *   (otherwise, response could be streamed, which is not done)
66  *      (possible future dir-listing.* config option)
67  * - reading entire directory into memory for sorting large directory
68  *   can lead to large memory usage if many simultaneous requests occur
69  */
70 
71 struct dirlist_cache {
72 	int32_t max_age;
73 	buffer *path;
74 };
75 
76 typedef struct {
77 	char dir_listing;
78 	char json;
79 	char hide_dot_files;
80 	char hide_readme_file;
81 	char encode_readme;
82 	char hide_header_file;
83 	char encode_header;
84 	char auto_layout;
85 
86 	pcre_keyvalue_buffer *excludes;
87 
88 	const buffer *show_readme;
89 	const buffer *show_header;
90 	const buffer *external_css;
91 	const buffer *external_js;
92 	const buffer *encoding;
93 	const buffer *set_footer;
94 	const struct dirlist_cache *cache;
95 } plugin_config;
96 
97 typedef struct {
98 	PLUGIN_DATA;
99 	plugin_config defaults;
100 	plugin_config conf;
101 	int processing;
102 } plugin_data;
103 
104 typedef struct {
105 	uint32_t namelen;
106 	unix_time64_t mtime;
107 	off_t    size;
108 } dirls_entry_t;
109 
110 typedef struct {
111 	dirls_entry_t **ent;
112 	uint32_t used;
113 } dirls_list_t;
114 
115 #define DIRLIST_ENT_NAME(ent)  ((char*)(ent) + sizeof(dirls_entry_t))
116 /* DIRLIST_BLOB_SIZE must be power of 2 for current internal usage */
117 #define DIRLIST_BLOB_SIZE      16
118 
119 typedef struct {
120 	DIR *dp;
121 	dirls_list_t dirs;
122 	dirls_list_t files;
123 	char *path;
124 	char *path_file;
125 	int dfd; /*(dirfd() owned by (DIR *))*/
126 	uint32_t name_max;
127 	buffer *jb;
128 	int jcomma;
129 	int jfd;
130 	char *jfn;
131 	uint32_t jfn_len;
132 	plugin_config conf;
133 } handler_ctx;
134 
135 #define DIRLIST_BATCH 32
136 static int dirlist_max_in_progress;
137 
138 
mod_dirlisting_handler_ctx_init(plugin_data * const p)139 static handler_ctx * mod_dirlisting_handler_ctx_init (plugin_data * const p) {
140     handler_ctx *hctx = ck_calloc(1, sizeof(*hctx));
141     memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
142     return hctx;
143 }
144 
mod_dirlisting_handler_ctx_free(handler_ctx * hctx)145 static void mod_dirlisting_handler_ctx_free (handler_ctx *hctx) {
146     if (hctx->dp)
147         closedir(hctx->dp);
148     if (hctx->files.ent) {
149         dirls_entry_t ** const ent = hctx->files.ent;
150         for (uint32_t i = 0, used = hctx->files.used; i < used; ++i)
151             free(ent[i]);
152         free(ent);
153     }
154     if (hctx->dirs.ent) {
155         dirls_entry_t ** const ent = hctx->dirs.ent;
156         for (uint32_t i = 0, used = hctx->dirs.used; i < used; ++i)
157             free(ent[i]);
158         free(ent);
159     }
160     if (hctx->jb) {
161         chunk_buffer_release(hctx->jb);
162         if (hctx->jfn) {
163             unlink(hctx->jfn);
164             free(hctx->jfn);
165         }
166         if (-1 != hctx->jfd)
167             close(hctx->jfd);
168     }
169     free(hctx->path);
170     free(hctx);
171 }
172 
173 
mod_dirlisting_parse_cache(server * srv,const array * a)174 static struct dirlist_cache * mod_dirlisting_parse_cache(server *srv, const array *a) {
175     const data_unset *du;
176 
177     du = array_get_element_klen(a, CONST_STR_LEN("max-age"));
178     const int32_t max_age = config_plugin_value_to_int32(du, 15);
179 
180     buffer *path = NULL;
181     du = array_get_element_klen(a, CONST_STR_LEN("path"));
182     if (NULL == du) {
183         if (0 != max_age) {
184             log_error(srv->errh, __FILE__, __LINE__,
185               "dir-listing.cache must include \"path\"");
186             return NULL;
187         }
188     }
189     else {
190         if (du->type != TYPE_STRING) {
191             log_error(srv->errh, __FILE__, __LINE__,
192               "dir-listing.cache \"path\" must have string value");
193             return NULL;
194         }
195         path = &((data_string *)du)->value;
196         if (!stat_cache_path_isdir(path)) {
197             if (errno == ENOTDIR) {
198                 log_error(srv->errh, __FILE__, __LINE__,
199                   "dir-listing.cache \"path\" => \"%s\" is not a dir",
200                   path->ptr);
201                 return NULL;
202             }
203             if (errno == ENOENT) {
204                 log_error(srv->errh, __FILE__, __LINE__,
205                   "dir-listing.cache \"path\" => \"%s\" does not exist",
206                   path->ptr);
207                 /*(warning; not returning NULL)*/
208             }
209         }
210     }
211 
212     struct dirlist_cache * const cache =
213       ck_calloc(1, sizeof(struct dirlist_cache));
214     cache->max_age = max_age;
215     cache->path = path;
216     return cache;
217 }
218 
219 
mod_dirlisting_parse_excludes(server * srv,const array * a)220 static pcre_keyvalue_buffer * mod_dirlisting_parse_excludes(server *srv, const array *a) {
221     const int pcre_jit = config_feature_bool(srv, "server.pcre_jit", 1);
222     pcre_keyvalue_buffer * const kvb = pcre_keyvalue_buffer_init();
223     buffer empty = { NULL, 0, 0 };
224     for (uint32_t j = 0; j < a->used; ++j) {
225         const data_string *ds = (data_string *)a->data[j];
226         if (!pcre_keyvalue_buffer_append(srv->errh, kvb, &ds->value, &empty,
227                                          pcre_jit)) {
228             log_error(srv->errh, __FILE__, __LINE__,
229               "pcre_compile failed for %s", ds->key.ptr);
230             pcre_keyvalue_buffer_free(kvb);
231             return NULL;
232         }
233     }
234     return kvb;
235 }
236 
237 #ifdef __COVERITY__
238 #include "burl.h"
239 #endif
240 
mod_dirlisting_exclude(pcre_keyvalue_buffer * const kvb,const char * const name,const uint32_t len)241 static int mod_dirlisting_exclude(pcre_keyvalue_buffer * const kvb, const char * const name, const uint32_t len) {
242     /*(re-use keyvalue.[ch] for match-only;
243      *  must have been configured with empty kvb 'value' during init)*/
244     buffer input = { NULL, len+1, 0 };
245     *(const char **)&input.ptr = name;
246     pcre_keyvalue_ctx ctx = { NULL, NULL, -1, 0, NULL, NULL };
247   #ifdef __COVERITY__
248     /*(again, must have been configured w/ empty kvb 'value' during init)*/
249     struct cond_match_t cache;
250     memset(&cache, 0, sizeof(cache));
251     struct burl_parts_t bp;
252     memset(&bp, 0, sizeof(bp));
253     ctx.cache = &cache;
254     ctx.burl = &bp;
255   #endif
256     /*(fail closed (simulate match to exclude) if there is an error)*/
257     return HANDLER_ERROR == pcre_keyvalue_buffer_process(kvb,&ctx,&input,NULL)
258         || -1 != ctx.m;
259 }
260 
261 
INIT_FUNC(mod_dirlisting_init)262 INIT_FUNC(mod_dirlisting_init) {
263     return ck_calloc(1, sizeof(plugin_data));
264 }
265 
FREE_FUNC(mod_dirlisting_free)266 FREE_FUNC(mod_dirlisting_free) {
267     plugin_data * const p = p_d;
268     if (NULL == p->cvlist) return;
269     /* (init i to 0 if global context; to 1 to skip empty global context) */
270     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
271         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
272         for (; -1 != cpv->k_id; ++cpv) {
273             switch (cpv->k_id) {
274               case 2: /* dir-listing.exclude */
275                 if (cpv->vtype != T_CONFIG_LOCAL) continue;
276                 pcre_keyvalue_buffer_free(cpv->v.v);
277                 break;
278               case 15: /* dir-listing.cache */
279                 if (cpv->vtype != T_CONFIG_LOCAL) continue;
280                 free(cpv->v.v);
281                 break;
282               default:
283                 break;
284             }
285         }
286     }
287 }
288 
mod_dirlisting_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)289 static void mod_dirlisting_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
290     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
291       case 0: /* dir-listing.activate */
292       case 1: /* server.dir-listing *//*(historical)*/
293         pconf->dir_listing = (char)cpv->v.u;
294         break;
295       case 2: /* dir-listing.exclude */
296         if (cpv->vtype == T_CONFIG_LOCAL)
297             pconf->excludes = cpv->v.v;
298         break;
299       case 3: /* dir-listing.hide-dotfiles */
300         pconf->hide_dot_files = (char)cpv->v.u;
301         break;
302       case 4: /* dir-listing.external-css */
303         pconf->external_css = cpv->v.b;
304         break;
305       case 5: /* dir-listing.external-js */
306         pconf->external_js = cpv->v.b;
307         break;
308       case 6: /* dir-listing.encoding */
309         pconf->encoding = cpv->v.b;
310         break;
311       case 7: /* dir-listing.show-readme */
312         pconf->show_readme = cpv->v.b;
313         break;
314       case 8: /* dir-listing.hide-readme-file */
315         pconf->hide_readme_file = (char)cpv->v.u;
316         break;
317       case 9: /* dir-listing.show-header */
318         pconf->show_header = cpv->v.b;
319         break;
320       case 10:/* dir-listing.hide-header-file */
321         pconf->hide_header_file = (char)cpv->v.u;
322         break;
323       case 11:/* dir-listing.set-footer */
324         pconf->set_footer = cpv->v.b;
325         break;
326       case 12:/* dir-listing.encode-readme */
327         pconf->encode_readme = (char)cpv->v.u;
328         break;
329       case 13:/* dir-listing.encode-header */
330         pconf->encode_header = (char)cpv->v.u;
331         break;
332       case 14:/* dir-listing.auto-layout */
333         pconf->auto_layout = (char)cpv->v.u;
334         break;
335       case 15:/* dir-listing.cache */
336         if (cpv->vtype == T_CONFIG_LOCAL)
337             pconf->cache = cpv->v.v;
338         break;
339       default:/* should not happen */
340         return;
341     }
342 }
343 
mod_dirlisting_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)344 static void mod_dirlisting_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
345     do {
346         mod_dirlisting_merge_config_cpv(pconf, cpv);
347     } while ((++cpv)->k_id != -1);
348 }
349 
mod_dirlisting_patch_config(request_st * const r,plugin_data * const p)350 static void mod_dirlisting_patch_config(request_st * const r, plugin_data * const p) {
351     memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
352     for (int i = 1, used = p->nconfig; i < used; ++i) {
353         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
354             mod_dirlisting_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
355     }
356 }
357 
SETDEFAULTS_FUNC(mod_dirlisting_set_defaults)358 SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
359     static const config_plugin_keys_t cpk[] = {
360       { CONST_STR_LEN("dir-listing.activate"),
361         T_CONFIG_BOOL,
362         T_CONFIG_SCOPE_CONNECTION }
363      ,{ CONST_STR_LEN("server.dir-listing"),
364         T_CONFIG_BOOL,
365         T_CONFIG_SCOPE_CONNECTION }
366      ,{ CONST_STR_LEN("dir-listing.exclude"),
367         T_CONFIG_ARRAY_VLIST,
368         T_CONFIG_SCOPE_CONNECTION }
369      ,{ CONST_STR_LEN("dir-listing.hide-dotfiles"),
370         T_CONFIG_BOOL,
371         T_CONFIG_SCOPE_CONNECTION }
372      ,{ CONST_STR_LEN("dir-listing.external-css"),
373         T_CONFIG_STRING,
374         T_CONFIG_SCOPE_CONNECTION }
375      ,{ CONST_STR_LEN("dir-listing.external-js"),
376         T_CONFIG_STRING,
377         T_CONFIG_SCOPE_CONNECTION }
378      ,{ CONST_STR_LEN("dir-listing.encoding"),
379         T_CONFIG_STRING,
380         T_CONFIG_SCOPE_CONNECTION }
381      ,{ CONST_STR_LEN("dir-listing.show-readme"),
382         T_CONFIG_STRING,
383         T_CONFIG_SCOPE_CONNECTION }
384      ,{ CONST_STR_LEN("dir-listing.hide-readme-file"),
385         T_CONFIG_BOOL,
386         T_CONFIG_SCOPE_CONNECTION }
387      ,{ CONST_STR_LEN("dir-listing.show-header"),
388         T_CONFIG_STRING,
389         T_CONFIG_SCOPE_CONNECTION }
390      ,{ CONST_STR_LEN("dir-listing.hide-header-file"),
391         T_CONFIG_BOOL,
392         T_CONFIG_SCOPE_CONNECTION }
393      ,{ CONST_STR_LEN("dir-listing.set-footer"),
394         T_CONFIG_STRING,
395         T_CONFIG_SCOPE_CONNECTION }
396      ,{ CONST_STR_LEN("dir-listing.encode-readme"),
397         T_CONFIG_BOOL,
398         T_CONFIG_SCOPE_CONNECTION }
399      ,{ CONST_STR_LEN("dir-listing.encode-header"),
400         T_CONFIG_BOOL,
401         T_CONFIG_SCOPE_CONNECTION }
402      ,{ CONST_STR_LEN("dir-listing.auto-layout"),
403         T_CONFIG_BOOL,
404         T_CONFIG_SCOPE_CONNECTION }
405      ,{ CONST_STR_LEN("dir-listing.cache"),
406         T_CONFIG_ARRAY_KVANY,
407         T_CONFIG_SCOPE_CONNECTION }
408      ,{ NULL, 0,
409         T_CONFIG_UNSET,
410         T_CONFIG_SCOPE_UNSET }
411     };
412 
413     plugin_data * const p = p_d;
414     if (!config_plugin_values_init(srv, p, cpk, "mod_dirlisting"))
415         return HANDLER_ERROR;
416 
417     /* process and validate config directives
418      * (init i to 0 if global context; to 1 to skip empty global context) */
419     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
420         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
421         for (; -1 != cpv->k_id; ++cpv) {
422             switch (cpv->k_id) {
423               case 0: /* dir-listing.activate */
424               case 1: /* server.dir-listing *//*(historical)*/
425                 break;
426               case 2: /* dir-listing.exclude */
427                 cpv->v.v = mod_dirlisting_parse_excludes(srv, cpv->v.a);
428                 if (NULL == cpv->v.v) return HANDLER_ERROR;
429                 cpv->vtype = T_CONFIG_LOCAL;
430                 break;
431               case 3: /* dir-listing.hide-dotfiles */
432                 break;
433               case 4: /* dir-listing.external-css */
434               case 5: /* dir-listing.external-js */
435               case 6: /* dir-listing.encoding */
436                 if (buffer_is_blank(cpv->v.b))
437                     cpv->v.b = NULL;
438                 break;
439               case 7: /* dir-listing.show-readme */
440                 if (!buffer_is_blank(cpv->v.b)) {
441                     buffer *b;
442                     *(const buffer **)&b = cpv->v.b;
443                     if (buffer_is_equal_string(b, CONST_STR_LEN("enable")))
444                         buffer_copy_string_len(b, CONST_STR_LEN("README.txt"));
445                     else if (buffer_is_equal_string(b,CONST_STR_LEN("disable")))
446                         buffer_clear(b);
447                 }
448                 else
449                     cpv->v.b = NULL;
450                 break;
451               case 8: /* dir-listing.hide-readme-file */
452                 break;
453               case 9: /* dir-listing.show-header */
454                 if (!buffer_is_blank(cpv->v.b)) {
455                     buffer *b;
456                     *(const buffer **)&b = cpv->v.b;
457                     if (buffer_is_equal_string(b, CONST_STR_LEN("enable")))
458                         buffer_copy_string_len(b, CONST_STR_LEN("HEADER.txt"));
459                     else if (buffer_is_equal_string(b,CONST_STR_LEN("disable")))
460                         buffer_clear(b);
461                 }
462                 else
463                     cpv->v.b = NULL;
464                 break;
465               case 10:/* dir-listing.hide-header-file */
466                 break;
467               case 11:/* dir-listing.set-footer */
468                 if (buffer_is_blank(cpv->v.b))
469                     cpv->v.b = NULL;
470                 break;
471               case 12:/* dir-listing.encode-readme */
472               case 13:/* dir-listing.encode-header */
473               case 14:/* dir-listing.auto-layout */
474                 break;
475               case 15:/* dir-listing.cache */
476                 cpv->v.v = mod_dirlisting_parse_cache(srv, cpv->v.a);
477                 if (NULL == cpv->v.v) return HANDLER_ERROR;
478                 if (0 == ((struct dirlist_cache *)cpv->v.v)->max_age) {
479                     free(cpv->v.v);
480                     cpv->v.v = NULL; /*(to disable after having been enabled)*/
481                 }
482                 cpv->vtype = T_CONFIG_LOCAL;
483                 break;
484               default:/* should not happen */
485                 break;
486             }
487         }
488     }
489 
490     dirlist_max_in_progress = srv->srvconf.max_conns >> 4;
491     if (0 == dirlist_max_in_progress) dirlist_max_in_progress = 1;
492 
493     p->defaults.dir_listing = 0;
494     p->defaults.json = 0;
495     p->defaults.hide_dot_files = 1;
496     p->defaults.hide_readme_file = 0;
497     p->defaults.hide_header_file = 0;
498     p->defaults.encode_readme = 1;
499     p->defaults.encode_header = 1;
500     p->defaults.auto_layout = 1;
501 
502     /* initialize p->defaults from global config context */
503     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
504         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
505         if (-1 != cpv->k_id)
506             mod_dirlisting_merge_config(&p->defaults, cpv);
507     }
508 
509     return HANDLER_GO_ON;
510 }
511 
512 /* simple combsort algorithm */
http_dirls_sort(dirls_entry_t ** ent,int num)513 static void http_dirls_sort(dirls_entry_t **ent, int num) {
514 	int gap = num;
515 	int i, j;
516 	int swapped;
517 	dirls_entry_t *tmp;
518 
519 	do {
520 		gap = (gap * 10) / 13;
521 		if (gap == 9 || gap == 10)
522 			gap = 11;
523 		if (gap < 1)
524 			gap = 1;
525 		swapped = 0;
526 
527 		for (i = 0; i < num - gap; i++) {
528 			j = i + gap;
529 			if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
530 				tmp = ent[i];
531 				ent[i] = ent[j];
532 				ent[j] = tmp;
533 				swapped = 1;
534 			}
535 		}
536 
537 	} while (gap > 1 || swapped);
538 }
539 
540 /* buffer must be able to hold "999.9K"
541  * conversion is simple but not perfect
542  */
http_list_directory_sizefmt(char * buf,size_t bufsz,off_t size)543 static size_t http_list_directory_sizefmt(char *buf, size_t bufsz, off_t size) {
544 	int remain;
545 	int u = -1;  /* u will always increment at least once */
546 	size_t buflen;
547 
548 	if (0 < size && size < 100)
549 		size += 99;
550 
551 	do {
552 		remain = (int)(size & 1023);
553 		size >>= 10;
554 		++u;
555 	} while (size & ~1023);
556 
557 	remain /= 100;
558 	if (remain > 9)
559 		remain = 9;
560 	if (size > 999) {
561 		size   = 0;
562 		remain = 9;
563 		u++;
564 	}
565 
566 	buflen = li_itostrn(buf, bufsz, size);
567 	if (buflen + 3 >= bufsz) return buflen;
568 	buf[buflen+0] = '.';
569 	buf[buflen+1] = remain + '0';
570 	buf[buflen+2] = "KMGTPE"[u];  /* Kilo, Mega, Giga, Tera, Peta, Exa */
571 	buf[buflen+3] = '\0';
572 
573 	return buflen + 3;
574 }
575 
http_list_directory_include_file(request_st * const r,const handler_ctx * const p,int is_header)576 static void http_list_directory_include_file(request_st * const r, const handler_ctx * const p, int is_header) {
577     const buffer *path;
578     int encode = 0;
579     if (is_header) {
580         path = p->conf.show_header;
581         encode = p->conf.encode_header;
582     }
583     else {
584         path = p->conf.show_readme;
585         encode = p->conf.encode_readme;
586     }
587     if (NULL == path) return;
588 
589     uint32_t len = 0;
590     if (path->ptr[0] != '/') { /* temporarily extend r->physical.path */
591         len = buffer_clen(&r->physical.path);
592         buffer_append_path_len(&r->physical.path, BUF_PTR_LEN(path));
593         path = &r->physical.path;
594     }
595     stat_cache_entry * const sce =
596       stat_cache_get_entry_open(path, r->conf.follow_symlink);
597     if (len)
598         buffer_truncate(&r->physical.path, len);
599     if (NULL == sce || sce->fd < 0 || 0 == sce->st.st_size)
600         return;
601 
602     chunkqueue * const cq = &r->write_queue;
603     if (encode) {
604         if (is_header)
605             chunkqueue_append_mem(cq, CONST_STR_LEN("<pre class=\"header\">"));
606         else
607             chunkqueue_append_mem(cq, CONST_STR_LEN("<pre class=\"readme\">"));
608 
609         /* Note: encoding a very large file may cause lighttpd to pause handling
610          * other requests while lighttpd encodes the file, especially if file is
611          * on a remote filesystem */
612 
613         /* encoding can consume 6x file size in worst case scenario,
614          * so send encoded contents of files > 32k to tempfiles) */
615         buffer * const tb = r->tmp_buf;
616         buffer * const out = sce->st.st_size <= 32768
617           ? chunkqueue_append_buffer_open(cq)
618           : tb;
619         buffer_clear(out);
620         const int fd = sce->fd;
621         ssize_t rd;
622         char buf[8192];
623         while ((rd = read(fd, buf, sizeof(buf))) > 0) {
624             buffer_append_string_encoded(out, buf, (size_t)rd, ENCODING_MINIMAL_XML);
625             if (out == tb) {
626                 if (0 != chunkqueue_append_mem_to_tempfile(cq,
627                                                            BUF_PTR_LEN(out),
628                                                            r->conf.errh))
629                     break;
630                 buffer_clear(out);
631             }
632         }
633         if (out != tb)
634             chunkqueue_append_buffer_commit(cq);
635 
636         chunkqueue_append_mem(cq, CONST_STR_LEN("</pre>"));
637     }
638     else {
639         (void)http_chunk_append_file_ref(r, sce);
640     }
641 }
642 
643 /* portions copied from mod_status
644  * modified and specialized for stable dirlist sorting by name */
645 static const char js_simple_table_resort[] = \
646 "var click_column;\n" \
647 "var name_column = 0;\n" \
648 "var date_column = 1;\n" \
649 "var size_column = 2;\n" \
650 "var type_column = 3;\n" \
651 "var prev_span = null;\n" \
652 "\n" \
653 "if (typeof(String.prototype.localeCompare) === 'undefined') {\n" \
654 " String.prototype.localeCompare = function(str, locale, options) {\n" \
655 "   return ((this == str) ? 0 : ((this > str) ? 1 : -1));\n" \
656 " };\n" \
657 "}\n" \
658 "\n" \
659 "if (typeof(String.prototype.toLocaleUpperCase) === 'undefined') {\n" \
660 " String.prototype.toLocaleUpperCase = function() {\n" \
661 "  return this.toUpperCase();\n" \
662 " };\n" \
663 "}\n" \
664 "\n" \
665 "function get_inner_text(el) {\n" \
666 " if((typeof el == 'string')||(typeof el == 'undefined'))\n" \
667 "  return el;\n" \
668 " if(el.innerText)\n" \
669 "  return el.innerText;\n" \
670 " else {\n" \
671 "  var str = \"\";\n" \
672 "  var cs = el.childNodes;\n" \
673 "  var l = cs.length;\n" \
674 "  for (var i=0;i<l;i++) {\n" \
675 "   if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" \
676 "   else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" \
677 "  }\n" \
678 " }\n" \
679 " return str;\n" \
680 "}\n" \
681 "\n" \
682 "function isdigit(c) {\n" \
683 " return (c >= '0' && c <= '9');\n" \
684 "}\n" \
685 "\n" \
686 "function unit_multiplier(unit) {\n" \
687 " return (unit=='K') ? 1000\n" \
688 "      : (unit=='M') ? 1000000\n" \
689 "      : (unit=='G') ? 1000000000\n" \
690 "      : (unit=='T') ? 1000000000000\n" \
691 "      : (unit=='P') ? 1000000000000000\n" \
692 "      : (unit=='E') ? 1000000000000000000 : 1;\n" \
693 "}\n" \
694 "\n" \
695 "var li_date_regex=/(\\d{4})-(\\w{3})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})/;\n" \
696 "\n" \
697 "var li_mon = ['Jan','Feb','Mar','Apr','May','Jun',\n" \
698 "              'Jul','Aug','Sep','Oct','Nov','Dec'];\n" \
699 "\n" \
700 "function li_mon_num(mon) {\n" \
701 " var i; for (i = 0; i < 12 && mon != li_mon[i]; ++i); return i;\n" \
702 "}\n" \
703 "\n" \
704 "function li_date_cmp(s1, s2) {\n" \
705 " var dp1 = li_date_regex.exec(s1)\n" \
706 " var dp2 = li_date_regex.exec(s2)\n" \
707 " for (var i = 1; i < 7; ++i) {\n" \
708 "  var cmp = (2 != i)\n" \
709 "   ? parseInt(dp1[i]) - parseInt(dp2[i])\n" \
710 "   : li_mon_num(dp1[2]) - li_mon_num(dp2[2]);\n" \
711 "  if (0 != cmp) return cmp;\n" \
712 " }\n" \
713 " return 0;\n" \
714 "}\n" \
715 "\n" \
716 "function sortfn_then_by_name(a,b,sort_column) {\n" \
717 " if (sort_column == name_column || sort_column == type_column) {\n" \
718 "  var ad = (a.cells[type_column].innerHTML === 'Directory');\n" \
719 "  var bd = (b.cells[type_column].innerHTML === 'Directory');\n" \
720 "  if (ad != bd) return (ad ? -1 : 1);\n" \
721 " }\n" \
722 " var at = get_inner_text(a.cells[sort_column]);\n" \
723 " var bt = get_inner_text(b.cells[sort_column]);\n" \
724 " var cmp;\n" \
725 " if (sort_column == name_column) {\n" \
726 "  if (at == '../') return -1;\n" \
727 "  if (bt == '../') return  1;\n" \
728 " }\n" \
729 " if (a.cells[sort_column].className == 'int') {\n" \
730 "  cmp = parseInt(at)-parseInt(bt);\n" \
731 " } else if (sort_column == date_column) {\n" \
732 "  var ad = isdigit(at.substr(0,1));\n" \
733 "  var bd = isdigit(bt.substr(0,1));\n" \
734 "  if (ad != bd) return (!ad ? -1 : 1);\n" \
735 "  cmp = li_date_cmp(at,bt);\n" \
736 " } else if (sort_column == size_column) {\n" \
737 "  var ai = parseInt(at, 10) * unit_multiplier(at.substr(-1,1));\n" \
738 "  var bi = parseInt(bt, 10) * unit_multiplier(bt.substr(-1,1));\n" \
739 "  if (at.substr(0,1) == '-') ai = -1;\n" \
740 "  if (bt.substr(0,1) == '-') bi = -1;\n" \
741 "  cmp = ai - bi;\n" \
742 " } else {\n" \
743 "  cmp = at.toLocaleUpperCase().localeCompare(bt.toLocaleUpperCase());\n" \
744 "  if (0 != cmp) return cmp;\n" \
745 "  cmp = at.localeCompare(bt);\n" \
746 " }\n" \
747 " if (0 != cmp || sort_column == name_column) return cmp;\n" \
748 " return sortfn_then_by_name(a,b,name_column);\n" \
749 "}\n" \
750 "\n" \
751 "function sortfn(a,b) {\n" \
752 " return sortfn_then_by_name(a,b,click_column);\n" \
753 "}\n" \
754 "\n" \
755 "function resort(lnk) {\n" \
756 " var span = lnk.childNodes[1];\n" \
757 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" \
758 " var rows = new Array();\n" \
759 " for (var j=1;j<table.rows.length;j++)\n" \
760 "  rows[j-1] = table.rows[j];\n" \
761 " click_column = lnk.parentNode.cellIndex;\n" \
762 " rows.sort(sortfn);\n" \
763 "\n" \
764 " if (prev_span != null) prev_span.innerHTML = '';\n" \
765 " if (span.getAttribute('sortdir')=='down') {\n" \
766 "  span.innerHTML = '&uarr;';\n" \
767 "  span.setAttribute('sortdir','up');\n" \
768 "  rows.reverse();\n" \
769 " } else {\n" \
770 "  span.innerHTML = '&darr;';\n" \
771 "  span.setAttribute('sortdir','down');\n" \
772 " }\n" \
773 " for (var i=0;i<rows.length;i++)\n" \
774 "  table.tBodies[0].appendChild(rows[i]);\n" \
775 " prev_span = span;\n" \
776 "}\n";
777 
778 /* portions copied from mod_dirlist (lighttpd2) */
779 static const char js_simple_table_init_sort[] = \
780 "\n" \
781 "function init_sort(init_sort_column, ascending) {\n" \
782 " var tables = document.getElementsByTagName(\"table\");\n" \
783 " for (var i = 0; i < tables.length; i++) {\n" \
784 "  var table = tables[i];\n" \
785 "  //var c = table.getAttribute(\"class\")\n" \
786 "  //if (-1 != c.split(\" \").indexOf(\"sort\")) {\n" \
787 "   var row = table.rows[0].cells;\n" \
788 "   for (var j = 0; j < row.length; j++) {\n" \
789 "    var n = row[j];\n" \
790 "    if (n.childNodes.length == 1 && n.childNodes[0].nodeType == 3) {\n" \
791 "     var link = document.createElement(\"a\");\n" \
792 "     var title = n.childNodes[0].nodeValue.replace(/:$/, \"\");\n" \
793 "     link.appendChild(document.createTextNode(title));\n" \
794 "     link.setAttribute(\"href\", \"#\");\n" \
795 "     link.setAttribute(\"class\", \"sortheader\");\n" \
796 "     link.setAttribute(\"onclick\", \"resort(this);return false;\");\n" \
797 "     var arrow = document.createElement(\"span\");\n" \
798 "     arrow.setAttribute(\"class\", \"sortarrow\");\n" \
799 "     arrow.appendChild(document.createTextNode(\":\"));\n" \
800 "     link.appendChild(arrow)\n" \
801 "     n.replaceChild(link, n.firstChild);\n" \
802 "    }\n" \
803 "   }\n" \
804 "   var lnk = row[init_sort_column].firstChild;\n" \
805 "   if (ascending) {\n" \
806 "    var span = lnk.childNodes[1];\n" \
807 "    span.setAttribute('sortdir','down');\n" \
808 "   }\n" \
809 "   resort(lnk);\n" \
810 "  //}\n" \
811 " }\n" \
812 "}\n" \
813 "\n" \
814 "function init_sort_from_query() {\n" \
815 "  var urlParams = new URLSearchParams(location.search);\n" \
816 "  var c = 0;\n" \
817 "  var o = 0;\n" \
818 "  switch (urlParams.get('C')) {\n" \
819 "    case \"N\": c=0; break;\n" \
820 "    case \"M\": c=1; break;\n" \
821 "    case \"S\": c=2; break;\n" \
822 "    case \"T\":\n" \
823 "    case \"D\": c=3; break;\n" \
824 "  }\n" \
825 "  switch (urlParams.get('O')) {\n" \
826 "    case \"A\": o=1; break;\n" \
827 "    case \"D\": o=0; break;\n" \
828 "  }\n" \
829 "  init_sort(c,o);\n" \
830 "}\n" \
831 "init_sort_from_query();\n";
832 
833 
http_dirlist_append_js_table_resort(buffer * const b)834 static void http_dirlist_append_js_table_resort (buffer * const b) {
835 	struct const_iovec iov[] = {
836 	  { CONST_STR_LEN("\n<script type=\"text/javascript\">\n// <!--\n\n") }
837 	 ,{ CONST_STR_LEN(js_simple_table_resort) }
838 	 ,{ CONST_STR_LEN(js_simple_table_init_sort) }
839 	 ,{ CONST_STR_LEN("\n// -->\n</script>\n\n") }
840 	};
841 	buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
842 }
843 
http_list_directory_header(request_st * const r,const handler_ctx * const p)844 static void http_list_directory_header(request_st * const r, const handler_ctx * const p) {
845 
846 	chunkqueue * const cq = &r->write_queue;
847 	if (p->conf.auto_layout) {
848 		buffer * const out = chunkqueue_append_buffer_open(cq);
849 		buffer_append_string_len(out, CONST_STR_LEN(
850 			"<!DOCTYPE html>\n"
851 			"<html>\n"
852 			"<head>\n"
853 		));
854 		if (p->conf.encoding) {
855 			buffer_append_str3(out,
856 			  CONST_STR_LEN("<meta charset=\""),
857 			  BUF_PTR_LEN(p->conf.encoding),
858 			  CONST_STR_LEN("\">\n"));
859 		}
860 		buffer_append_string_len(out, CONST_STR_LEN("<title>Index of "));
861 		buffer_append_string_encoded(out, BUF_PTR_LEN(&r->uri.path), ENCODING_MINIMAL_XML);
862 		buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
863 
864 		if (p->conf.external_css) {
865 			buffer_append_str3(out,
866 			  CONST_STR_LEN("<meta name=\"viewport\" content=\"initial-scale=1\">"
867 			                "<link rel=\"stylesheet\" type=\"text/css\" href=\""),
868 			  BUF_PTR_LEN(p->conf.external_css),
869 			  CONST_STR_LEN("\">\n"));
870 		} else {
871 			buffer_append_string_len(out, CONST_STR_LEN(
872 				"<style type=\"text/css\">\n"
873 				"a, a:active {text-decoration: none; color: blue;}\n"
874 				"a:visited {color: #48468F;}\n"
875 				"a:hover, a:focus {text-decoration: underline; color: red;}\n"
876 				"body {background-color: #F5F5F5;}\n"
877 				"h2 {margin-bottom: 12px;}\n"
878 				"table {margin-left: 12px;}\n"
879 				"th, td {"
880 				" font: 90% monospace;"
881 				" text-align: left;"
882 				"}\n"
883 				"th {"
884 				" font-weight: bold;"
885 				" padding-right: 14px;"
886 				" padding-bottom: 3px;"
887 				"}\n"
888 				"td {padding-right: 14px;}\n"
889 				"td.s, th.s {text-align: right;}\n"
890 				"div.list {"
891 				" background-color: white;"
892 				" border-top: 1px solid #646464;"
893 				" border-bottom: 1px solid #646464;"
894 				" padding-top: 10px;"
895 				" padding-bottom: 14px;"
896 				"}\n"
897 				"div.foot {"
898 				" font: 90% monospace;"
899 				" color: #787878;"
900 				" padding-top: 4px;"
901 				"}\n"
902 				"</style>\n"
903 			));
904 		}
905 
906 		buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
907 		chunkqueue_append_buffer_commit(cq);
908 	}
909 
910 	if (p->conf.show_header) {
911 		http_list_directory_include_file(r, p, 1);/*0 for readme; 1 for header*/
912 	}
913 
914 	buffer * const out = chunkqueue_append_buffer_open(cq);
915 	buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
916 	buffer_append_string_encoded(out, BUF_PTR_LEN(&r->uri.path), ENCODING_MINIMAL_XML);
917 	buffer_append_string_len(out, CONST_STR_LEN(
918 		"</h2>\n"
919 		"<div class=\"list\">\n"
920 		"<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
921 		"<thead>"
922 		"<tr>"
923 			"<th class=\"n\">Name</th>"
924 			"<th class=\"m\">Last Modified</th>"
925 			"<th class=\"s\">Size</th>"
926 			"<th class=\"t\">Type</th>"
927 		"</tr>"
928 		"</thead>\n"
929 		"<tbody>\n"
930 	));
931 	if (!buffer_is_equal_string(&r->uri.path, CONST_STR_LEN("/"))) {
932 		buffer_append_string_len(out, CONST_STR_LEN(
933 		"<tr class=\"d\">"
934 			"<td class=\"n\"><a href=\"../\">..</a>/</td>"
935 			"<td class=\"m\">&nbsp;</td>"
936 			"<td class=\"s\">- &nbsp;</td>"
937 			"<td class=\"t\">Directory</td>"
938 		"</tr>\n"
939 		));
940 	}
941 	chunkqueue_append_buffer_commit(cq);
942 }
943 
http_list_directory_footer(request_st * const r,const handler_ctx * const p)944 static void http_list_directory_footer(request_st * const r, const handler_ctx * const p) {
945 
946 	chunkqueue * const cq = &r->write_queue;
947 	chunkqueue_append_mem(cq, CONST_STR_LEN(
948 		"</tbody>\n"
949 		"</table>\n"
950 		"</div>\n"
951 	));
952 
953 	if (p->conf.show_readme) {
954 		http_list_directory_include_file(r, p, 0);/*0 for readme; 1 for header*/
955 	}
956 
957 	if (p->conf.auto_layout) {
958 		buffer * const out = chunkqueue_append_buffer_open(cq);
959 		const buffer * const footer =
960 		  p->conf.set_footer
961 		    ? p->conf.set_footer
962 		    : r->conf.server_tag
963 		        ? r->conf.server_tag
964 		        : NULL;
965 		if (footer)
966 			buffer_append_str3(out,
967 			  CONST_STR_LEN("<div class=\"foot\">"),
968 			  BUF_PTR_LEN(footer),
969 			  CONST_STR_LEN("</div>\n"));
970 
971 		if (p->conf.external_js)
972 			buffer_append_str3(out,
973 			  CONST_STR_LEN("<script type=\"text/javascript\" src=\""),
974 			  BUF_PTR_LEN(p->conf.external_js),
975 			  CONST_STR_LEN("\"></script>\n"));
976 		else
977 			http_dirlist_append_js_table_resort(out);
978 
979 		buffer_append_string_len(out, CONST_STR_LEN(
980 			"</body>\n"
981 			"</html>\n"
982 		));
983 		chunkqueue_append_buffer_commit(cq);
984 	}
985 }
986 
http_open_directory(request_st * const r,handler_ctx * const hctx)987 static int http_open_directory(request_st * const r, handler_ctx * const hctx) {
988     const uint32_t dlen = buffer_clen(&r->physical.path);
989 #if defined __WIN32
990     hctx->name_max = FILENAME_MAX;
991 #else
992 #ifndef PATH_MAX
993 #define PATH_MAX 4096
994 #endif
995     /* allocate based on PATH_MAX rather than pathconf() to get _PC_NAME_MAX */
996     hctx->name_max = PATH_MAX - dlen - 1;
997 #endif
998     hctx->path = ck_malloc(dlen + hctx->name_max + 1);
999     memcpy(hctx->path, r->physical.path.ptr, dlen+1);
1000   #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) || !defined(_ATFILE_SOURCE)
1001     hctx->path_file = hctx->path + dlen;
1002   #endif
1003 
1004   #ifndef _ATFILE_SOURCE /*(not using fdopendir unless _ATFILE_SOURCE)*/
1005     hctx->dfd = -1;
1006     hctx->dp = opendir(hctx->path);
1007   #else
1008     hctx->dfd = fdevent_open_dirname(hctx->path, r->conf.follow_symlink);
1009     hctx->dp = (hctx->dfd >= 0) ? fdopendir(hctx->dfd) : NULL;
1010   #endif
1011     if (NULL == hctx->dp) {
1012         log_perror(r->conf.errh, __FILE__, __LINE__, "opendir %s", hctx->path);
1013         if (hctx->dfd >= 0) {
1014             close(hctx->dfd);
1015             hctx->dfd = -1;
1016         }
1017         return -1;
1018     }
1019 
1020     if (hctx->conf.json) return 0;
1021 
1022     dirls_list_t * const dirs = &hctx->dirs;
1023     dirls_list_t * const files = &hctx->files;
1024     dirs->ent   = NULL;
1025     dirs->used  = 0;
1026     files->ent  = NULL;
1027     files->used = 0;
1028 
1029     return 0;
1030 }
1031 
http_read_directory(handler_ctx * const p)1032 static int http_read_directory(handler_ctx * const p) {
1033 	struct dirent *dent;
1034 	const int hide_dotfiles = p->conf.hide_dot_files;
1035 	const uint32_t name_max = p->name_max;
1036 	struct stat st;
1037 	int count = -1;
1038 	while (++count < DIRLIST_BATCH && (dent = readdir(p->dp)) != NULL) {
1039 		const char * const d_name = dent->d_name;
1040 		const uint32_t dsz = (uint32_t) _D_EXACT_NAMLEN(dent);
1041 		if (d_name[0] == '.') {
1042 			if (hide_dotfiles)
1043 				continue;
1044 			if (d_name[1] == '\0')
1045 				continue;
1046 			if (d_name[1] == '.' && d_name[2] == '\0')
1047 				continue;
1048 		}
1049 
1050 		if (p->conf.hide_readme_file
1051 		    && p->conf.show_readme
1052 		    && buffer_eq_slen(p->conf.show_readme, d_name, dsz))
1053 			continue;
1054 		if (p->conf.hide_header_file
1055 		    && p->conf.show_header
1056 		    && buffer_eq_slen(p->conf.show_header, d_name, dsz))
1057 			continue;
1058 
1059 		/* compare d_name against excludes array
1060 		 * elements, skipping any that match.
1061 		 */
1062 		if (p->conf.excludes
1063 		    && mod_dirlisting_exclude(p->conf.excludes, d_name, dsz))
1064 			continue;
1065 
1066 		/* NOTE: the manual says, d_name is never more than NAME_MAX
1067 		 *       so this should actually not be a buffer-overflow-risk
1068 		 */
1069 		if (dsz > name_max) continue;
1070 	  #ifdef __COVERITY__
1071 		/* For some reason, Coverity overlooks the strlen() performed
1072 		 * a few lines above and thinks memcpy() below might access
1073 		 * bytes beyond end of d_name[] with dsz+1 */
1074 		force_assert(dsz < sizeof(dent->d_name));
1075 	  #endif
1076 
1077 	  #ifndef _ATFILE_SOURCE
1078 		memcpy(p->path_file, d_name, dsz + 1);
1079 		if (stat(p->path, &st) != 0)
1080 			continue;
1081 	  #else
1082 		/*(XXX: follow symlinks, like stat(); not using AT_SYMLINK_NOFOLLOW) */
1083 		if (0 != fstatat(p->dfd, d_name, &st, 0))
1084 			continue; /* file *just* disappeared? */
1085 	  #endif
1086 
1087 		if (p->jb) { /* json output */
1088 			if (__builtin_expect( (p->jcomma), 1))/*(to avoid excess comma)*/
1089 				buffer_append_string_len(p->jb, CONST_STR_LEN(",{\"name\":\""));
1090 			else {
1091 				p->jcomma = 1;
1092 				buffer_append_string_len(p->jb, CONST_STR_LEN( "{\"name\":\""));
1093 			}
1094 			buffer_append_bs_escaped_json(p->jb, d_name, dsz);
1095 
1096 			const char *t;
1097 			size_t tlen;
1098 			if (!S_ISDIR(st.st_mode)) {
1099 				t =           "\",\"type\":\"file\",\"size\":";
1100 				tlen = sizeof("\",\"type\":\"file\",\"size\":")-1;
1101 			}
1102 			else {
1103 				t =           "\",\"type\":\"dir\",\"size\":";
1104 				tlen = sizeof("\",\"type\":\"dir\",\"size\":")-1;
1105 			}
1106 			char sstr[LI_ITOSTRING_LENGTH];
1107 			char mstr[LI_ITOSTRING_LENGTH];
1108 			struct const_iovec iov[] = {
1109 			  { t, tlen }
1110 			 ,{ sstr, li_itostrn(sstr, sizeof(sstr), st.st_size) }
1111 			 ,{ CONST_STR_LEN(",\"mtime\":") }
1112 			 ,{ mstr, li_itostrn(mstr, sizeof(mstr), TIME64_CAST(st.st_mtime)) }
1113 			 ,{ CONST_STR_LEN("}") }
1114 			};
1115 			buffer_append_iovec(p->jb, iov, sizeof(iov)/sizeof(*iov));
1116 			continue;
1117 		}
1118 
1119 		dirls_list_t * const list = !S_ISDIR(st.st_mode) ? &p->files : &p->dirs;
1120 		if (!(list->used & (DIRLIST_BLOB_SIZE-1)))
1121 			ck_realloc_u32((void **)&list->ent, list->used,
1122 			               DIRLIST_BLOB_SIZE, sizeof(*list->ent));
1123 		dirls_entry_t * const tmp = list->ent[list->used++] =
1124 		  (dirls_entry_t*) ck_malloc(sizeof(dirls_entry_t) + 1 + dsz);
1125 		tmp->mtime = st.st_mtime;
1126 		tmp->size  = st.st_size;
1127 		tmp->namelen = dsz;
1128 		memcpy(DIRLIST_ENT_NAME(tmp), d_name, dsz + 1);
1129 	}
1130 	if (count == DIRLIST_BATCH)
1131 		return HANDLER_WAIT_FOR_EVENT;
1132 	closedir(p->dp);
1133 	p->dp = NULL;
1134 
1135 	return HANDLER_FINISHED;
1136 }
1137 
http_list_directory(request_st * const r,handler_ctx * const hctx)1138 static void http_list_directory(request_st * const r, handler_ctx * const hctx) {
1139 	dirls_list_t * const dirs = &hctx->dirs;
1140 	dirls_list_t * const files = &hctx->files;
1141 	/*(note: sorting can be time consuming on large dirs (O(n log n))*/
1142 	if (dirs->used) http_dirls_sort(dirs->ent, dirs->used);
1143 	if (files->used) http_dirls_sort(files->ent, files->used);
1144 
1145 	char sizebuf[sizeof("999.9K")];
1146 	struct tm tm;
1147 
1148 	/* generate large directory listings into tempfiles
1149 	 * (estimate approx 200-256 bytes of HTML per item; could be up to ~512) */
1150 	chunkqueue * const cq = &r->write_queue;
1151 	buffer * const tb = r->tmp_buf;
1152 	buffer_clear(tb);
1153 	buffer * const out = (dirs->used + files->used <= 256)
1154 	  ? chunkqueue_append_buffer_open(cq)
1155 	  : tb;
1156 	buffer_clear(out);
1157 
1158 	/* directories */
1159 	dirls_entry_t ** const dirs_ent = dirs->ent;
1160 	for (uint32_t i = 0, used = dirs->used; i < used; ++i) {
1161 		dirls_entry_t * const tmp = dirs_ent[i];
1162 
1163 		buffer_append_string_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\""));
1164 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
1165 		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
1166 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
1167 		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
1168 		buffer_append_strftime(out, "%Y-%b-%d %T", localtime64_r(&tmp->mtime, &tm));
1169 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
1170 
1171 		if (buffer_string_space(out) < 256) {
1172 			if (out == tb) {
1173 				if (0 != chunkqueue_append_mem_to_tempfile(cq,
1174 				                                           BUF_PTR_LEN(out),
1175 				                                           r->conf.errh))
1176 					break;
1177 				buffer_clear(out);
1178 			}
1179 		}
1180 	}
1181 
1182 	/* files */
1183 	const array * const mimetypes = r->conf.mimetypes;
1184 	dirls_entry_t ** const files_ent = files->ent;
1185 	for (uint32_t i = 0, used = files->used; i < used; ++i) {
1186 		dirls_entry_t * const tmp = files_ent[i];
1187 		const buffer *content_type;
1188 	  #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) /*(pass full path)*/
1189 		content_type = NULL;
1190 		if (r->conf.use_xattr) {
1191 			memcpy(hctx->path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
1192 			content_type = stat_cache_mimetype_by_xattr(hctx->path);
1193 		}
1194 		if (NULL == content_type)
1195 	  #endif
1196 			content_type = stat_cache_mimetype_by_ext(mimetypes, DIRLIST_ENT_NAME(tmp), tmp->namelen);
1197 		if (NULL == content_type) {
1198 			static const buffer octet_stream =
1199 			  { "application/octet-stream",
1200 			    sizeof("application/octet-stream"), 0 };
1201 			content_type = &octet_stream;
1202 		}
1203 
1204 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
1205 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
1206 		buffer_append_string_len(out, CONST_STR_LEN("\">"));
1207 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
1208 		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
1209 		buffer_append_strftime(out, "%Y-%b-%d %T", localtime64_r(&tmp->mtime, &tm));
1210 		size_t buflen =
1211 		  http_list_directory_sizefmt(sizebuf, sizeof(sizebuf), tmp->size);
1212 		struct const_iovec iov[] = {
1213 		  { CONST_STR_LEN("</td><td class=\"s\">") }
1214 		 ,{ sizebuf, buflen }
1215 		 ,{ CONST_STR_LEN("</td><td class=\"t\">") }
1216 		 ,{ BUF_PTR_LEN(content_type) }
1217 		 ,{ CONST_STR_LEN("</td></tr>\n") }
1218 		};
1219 		buffer_append_iovec(out, iov, sizeof(iov)/sizeof(*iov));
1220 
1221 		if (buffer_string_space(out) < 256) {
1222 			if (out == tb) {
1223 				if (0 != chunkqueue_append_mem_to_tempfile(cq,
1224 				                                           BUF_PTR_LEN(out),
1225 				                                           r->conf.errh))
1226 					break;
1227 				buffer_clear(out);
1228 			}
1229 		}
1230 	}
1231 
1232 	if (out == tb) {
1233 		if (!buffer_is_blank(out)
1234 		    && 0 != chunkqueue_append_mem_to_tempfile(cq,
1235 		                                              BUF_PTR_LEN(out),
1236 		                                              r->conf.errh)) {
1237 			/* ignore */
1238 		}
1239 	}
1240 	else {
1241 		chunkqueue_append_buffer_commit(cq);
1242 	}
1243 }
1244 
1245 
mod_dirlisting_content_type(request_st * const r,const buffer * const encoding)1246 static void mod_dirlisting_content_type (request_st * const r, const buffer * const encoding) {
1247     buffer * const vb =
1248       http_header_response_set_ptr(r, HTTP_HEADER_CONTENT_TYPE,
1249                                    CONST_STR_LEN("Content-Type"));
1250     if (NULL == encoding)
1251         buffer_copy_string_len(vb, CONST_STR_LEN("text/html"));
1252     else
1253         buffer_append_str2(vb, CONST_STR_LEN("text/html;charset="),
1254                                BUF_PTR_LEN(encoding));
1255 }
1256 
1257 
mod_dirlisting_response(request_st * const r,handler_ctx * const hctx)1258 static void mod_dirlisting_response (request_st * const r, handler_ctx * const hctx) {
1259     http_list_directory_header(r, hctx);
1260     http_list_directory(r, hctx);
1261     http_list_directory_footer(r, hctx);
1262     mod_dirlisting_content_type(r, hctx->conf.encoding);
1263 
1264     r->resp_body_finished = 1;
1265 }
1266 
1267 
mod_dirlisting_json_append(request_st * const r,handler_ctx * const hctx,const int fin)1268 static void mod_dirlisting_json_append (request_st * const r, handler_ctx * const hctx, const int fin) {
1269     buffer * const jb = hctx->jb;
1270     if (fin)
1271         buffer_append_string_len(jb, CONST_STR_LEN("]}"));
1272     else if (buffer_clen(jb) < 16384-1024)
1273         return; /* aggregate bunches of entries, even if streaming response */
1274 
1275     if (hctx->jfn) {
1276         if (__builtin_expect( (write_all(hctx->jfd, BUF_PTR_LEN(jb)) < 0), 0)) {
1277             /*(cleanup, cease caching if error occurs writing to cache file)*/
1278             unlink(hctx->jfn);
1279             free(hctx->jfn);
1280             hctx->jfn = NULL;
1281             close(hctx->jfd);
1282             hctx->jfd = -1;
1283         }
1284         /* Note: writing cache file is separate from the response so that if an
1285          * error occurs with cache, the response still proceeds.  While this is
1286          * duplicative if the response is large enough to spill to temporary
1287          * files, it is expected that only very large directories will spill to
1288          * temporary files, and even then most responses will be less than 1 MB.
1289          * The cache path can be different from server.upload-dirs.
1290          * Note: since responses are not expected to be large, no effort is
1291          * currently made here to handle FDEVENT_STREAM_RESPONSE_BUFMIN and to
1292          * defer reading more from directory while data is sent to client */
1293     }
1294 
1295     http_chunk_append_buffer(r, jb); /* clears jb */
1296 }
1297 
1298 
1299 SUBREQUEST_FUNC(mod_dirlisting_subrequest);
1300 REQUEST_FUNC(mod_dirlisting_reset);
1301 static handler_t mod_dirlisting_cache_check (request_st * const r, plugin_data * const p);
1302 __attribute_noinline__
1303 static void mod_dirlisting_cache_add (request_st * const r, handler_ctx * const hctx);
1304 __attribute_noinline__
1305 static void mod_dirlisting_cache_json_init (request_st * const r, handler_ctx * const hctx);
1306 __attribute_noinline__
1307 static void mod_dirlisting_cache_json (request_st * const r, handler_ctx * const hctx);
1308 
1309 
URIHANDLER_FUNC(mod_dirlisting_subrequest_start)1310 URIHANDLER_FUNC(mod_dirlisting_subrequest_start) {
1311 	plugin_data *p = p_d;
1312 
1313 	if (NULL != r->handler_module) return HANDLER_GO_ON;
1314 	if (!buffer_has_slash_suffix(&r->uri.path)) return HANDLER_GO_ON;
1315 	if (!http_method_get_or_head(r->http_method)) return HANDLER_GO_ON;
1316 	/* r->physical.path is non-empty for handle_subrequest_start */
1317 	/*if (buffer_is_blank(&r->physical.path)) return HANDLER_GO_ON;*/
1318 
1319 	mod_dirlisting_patch_config(r, p);
1320 
1321 	if (!p->conf.dir_listing) return HANDLER_GO_ON;
1322 
1323 	if (r->conf.log_request_handling) {
1324 		log_error(r->conf.errh, __FILE__, __LINE__,
1325 		  "-- handling the request as Dir-Listing");
1326 		log_error(r->conf.errh, __FILE__, __LINE__,
1327 		  "URI          : %s", r->uri.path.ptr);
1328 	}
1329 
1330   #if 0 /* redundant check; not necessary */
1331 	/* r->physical.path is a dir since it ends in slash, or else
1332 	 * http_response_physical_path_check() would have redirected
1333 	 * before calling handle_subrequest_start */
1334 	if (!stat_cache_path_isdir(&r->physical.path)) {
1335 		if (errno == ENOTDIR)
1336 			return HANDLER_GO_ON;
1337 		log_perror(r->conf.errh,__FILE__,__LINE__,"%s",r->physical.path.ptr);
1338 		r->http_status = 500;
1339 		return HANDLER_FINISHED;
1340 	}
1341   #endif
1342 
1343 	/* TODO: add option/mechanism to enable json output */
1344   #if 0 /* XXX: ??? might this be enabled accidentally by clients ??? */
1345 	const buffer * const vb =
1346 	  http_header_request_get(r, HTTP_HEADER_ACCEPT, CONST_STR_LEN("Accept"));
1347 	p->conf.json = (vb && strstr(vb->ptr, "application/json")); /*(coarse)*/
1348   #endif
1349 
1350 	if (p->conf.cache) {
1351 		handler_t rc = mod_dirlisting_cache_check(r, p);
1352 		if (rc != HANDLER_GO_ON)
1353 			return rc;
1354 	}
1355 
1356 	/* upper limit for dirlisting requests in progress (per lighttpd worker)
1357 	 * (attempt to avoid "livelock" scenarios or starvation of other requests)
1358 	 * (100 is still a high arbitrary limit;
1359 	 *  and limit applies only to directories larger than DIRLIST_BATCH-2) */
1360 	if (p->processing == dirlist_max_in_progress) {
1361 		r->http_status = 503;
1362 		http_header_response_set(r, HTTP_HEADER_OTHER,
1363 		                         CONST_STR_LEN("Retry-After"),
1364 		                         CONST_STR_LEN("2"));
1365 		return HANDLER_FINISHED;
1366 	}
1367 
1368 	handler_ctx * const hctx = mod_dirlisting_handler_ctx_init(p);
1369 
1370 	/* future: might implement a queue to limit max number of dirlisting
1371 	 * requests being serviced in parallel (increasing disk I/O), and if
1372 	 * caching is enabled, to avoid repeating the work on the same directory
1373 	 * in parallel.  Could continue serving (expired) cached entry while
1374 	 * updating, but burst of requests on first access to dir would still
1375 	 * need to be handled.
1376 	 *
1377 	 * If queueing (not implemented), defer opening dir until pulled off
1378 	 * queue.  Since joblist is per-connection, would need to handle single
1379 	 * request from queue even if multiple streams are queued on same
1380 	 * HTTP/2 connection.  If queueing, must check for and remove from
1381 	 * queue in mod_dirlisting_reset() if request is still queued. */
1382 
1383 	if (0 != http_open_directory(r, hctx)) {
1384 		/* dirlisting failed */
1385 		r->http_status = 403;
1386 	        mod_dirlisting_handler_ctx_free(hctx);
1387 		return HANDLER_FINISHED;
1388 	}
1389 	++p->processing;
1390 
1391 	if (p->conf.json) {
1392 		hctx->jfd = -1;
1393 		hctx->jb = chunk_buffer_acquire();
1394 		buffer_append_string_len(hctx->jb, CONST_STR_LEN("{["));
1395 		if (p->conf.cache)
1396 			mod_dirlisting_cache_json_init(r, hctx);
1397 		http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE,
1398 		                         CONST_STR_LEN("Content-Type"),
1399 		                         CONST_STR_LEN("application/json"));
1400 		r->http_status = 200;
1401 		r->resp_body_started = 1;
1402 	}
1403 
1404 	r->plugin_ctx[p->id] = hctx;
1405 	r->handler_module = p->self;
1406 	return mod_dirlisting_subrequest(r, p);
1407 }
1408 
1409 
SUBREQUEST_FUNC(mod_dirlisting_subrequest)1410 SUBREQUEST_FUNC(mod_dirlisting_subrequest) {
1411     plugin_data * const p = p_d;
1412     handler_ctx * const hctx = r->plugin_ctx[p->id];
1413     if (NULL == hctx) return HANDLER_GO_ON; /*(should not happen)*/
1414 
1415     handler_t rc = http_read_directory(hctx);
1416     switch (rc) {
1417       case HANDLER_FINISHED:
1418         if (hctx->jb) { /* (hctx->conf.json) */
1419             mod_dirlisting_json_append(r, hctx, 1);
1420             r->resp_body_finished = 1;
1421             if (hctx->jfn) /* (also (hctx->conf.cache) */
1422                 mod_dirlisting_cache_json(r, hctx);
1423         }
1424         else {
1425             mod_dirlisting_response(r, hctx);
1426             if (hctx->conf.cache)
1427                 mod_dirlisting_cache_add(r, hctx);
1428         }
1429         mod_dirlisting_reset(r, p); /*(release resources, including hctx)*/
1430         break;
1431       case HANDLER_WAIT_FOR_EVENT: /*(used here to mean 'yield')*/
1432         if (hctx->jb)   /* (hctx->conf.json) */
1433             mod_dirlisting_json_append(r, hctx, 0);
1434         joblist_append(r->con);
1435         break;
1436       default:
1437         break;
1438     }
1439 
1440     return rc;
1441 }
1442 
1443 
REQUEST_FUNC(mod_dirlisting_reset)1444 REQUEST_FUNC(mod_dirlisting_reset) {
1445     void ** const restrict dptr = &r->plugin_ctx[((plugin_data *)p_d)->id];
1446     if (*dptr) {
1447         --((plugin_data *)p_d)->processing;
1448         mod_dirlisting_handler_ctx_free(*dptr);
1449         *dptr = NULL;
1450     }
1451     return HANDLER_GO_ON;
1452 }
1453 
1454 
mod_dirlisting_cache_control(request_st * const r,unix_time64_t max_age)1455 static void mod_dirlisting_cache_control (request_st * const r, unix_time64_t max_age) {
1456     if (!light_btst(r->resp_htags, HTTP_HEADER_CACHE_CONTROL)) {
1457         buffer * const vb =
1458           http_header_response_set_ptr(r, HTTP_HEADER_CACHE_CONTROL,
1459                                        CONST_STR_LEN("Cache-Control"));
1460         buffer_append_string_len(vb, CONST_STR_LEN("max-age="));
1461         buffer_append_int(vb, max_age);
1462     }
1463 }
1464 
1465 
mod_dirlisting_cache_check(request_st * const r,plugin_data * const p)1466 static handler_t mod_dirlisting_cache_check (request_st * const r, plugin_data * const p) {
1467     /* optional: an external process can trigger a refresh by deleting the cache
1468      * entry when the external process detects (or initiates) changes to dir */
1469     buffer * const tb = r->tmp_buf;
1470     buffer_copy_path_len2(tb, BUF_PTR_LEN(p->conf.cache->path),
1471                               BUF_PTR_LEN(&r->physical.path));
1472     buffer_append_string_len(tb, p->conf.json ? "dirlist.json" : "dirlist.html",
1473                              sizeof("dirlist.html")-1);
1474     stat_cache_entry * const sce = stat_cache_get_entry_open(tb, 1);
1475     if (NULL == sce || sce->fd == -1)
1476         return HANDLER_GO_ON;
1477     const unix_time64_t max_age =
1478       TIME64_CAST(sce->st.st_mtime) + p->conf.cache->max_age - log_epoch_secs;
1479     if (max_age < 0)
1480         return HANDLER_GO_ON;
1481 
1482     !p->conf.json
1483       ? mod_dirlisting_content_type(r, p->conf.encoding)
1484       : http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE,
1485                                  CONST_STR_LEN("Content-Type"),
1486                                  CONST_STR_LEN("application/json"));
1487 
1488   #if 0
1489     /*(XXX: ETag needs to be set for mod_deflate to potentially handle)*/
1490     /*(XXX: should ETag be created from cache file mtime or directory mtime?)*/
1491     const int follow_symlink = r->conf.follow_symlink;
1492     r->conf.follow_symlink = 1; /*(skip symlink checks into cache)*/
1493     http_response_send_file(r, sce->name, sce);
1494     r->conf.follow_symlink = follow_symlink;
1495     if (r->http_status < 400)
1496         return HANDLER_FINISHED;
1497     r->http_status = 0;
1498   #endif
1499 
1500     /* Note: dirlist < 350 or so entries will generally trigger file
1501      * read into memory for dirlist < 32k, which will not be able to use
1502      * mod_deflate cache.  Still, this is much more efficient than lots of
1503      * stat() calls to generate the dirlisting for each and every request */
1504     if (0 != http_chunk_append_file_ref(r, sce)) {
1505         http_header_response_unset(r, HTTP_HEADER_CONTENT_TYPE,
1506                                    CONST_STR_LEN("Content-Type"));
1507         http_response_body_clear(r, 0);
1508         return HANDLER_GO_ON;
1509     }
1510 
1511     /* Cache-Control and ETag (also done in mod_dirlisting_cache_add())*/
1512     mod_dirlisting_cache_control(r, max_age);
1513     if (0 != r->conf.etag_flags) {
1514         const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
1515         if (etag && !buffer_is_blank(etag))
1516             http_header_response_set(r, HTTP_HEADER_ETAG,
1517                                      CONST_STR_LEN("ETag"),
1518                                      BUF_PTR_LEN(etag));
1519     }
1520 
1521     r->resp_body_finished = 1;
1522     return HANDLER_FINISHED;
1523 }
1524 
1525 
mod_dirlisting_write_cq(const int fd,chunkqueue * const cq,log_error_st * const errh)1526 static int mod_dirlisting_write_cq (const int fd, chunkqueue * const cq, log_error_st * const errh)
1527 {
1528     chunkqueue in;
1529     memset(&in, 0, sizeof(in));
1530     chunkqueue_append_chunkqueue(&in, cq);
1531     cq->bytes_in  -= in.bytes_in;
1532     cq->bytes_out -= in.bytes_in;
1533 
1534     /*(similar to mod_webdav.c:mod_webdav_write_cq(), but operates on two cqs)*/
1535     while (!chunkqueue_is_empty(&in)) {
1536         ssize_t wr = chunkqueue_write_chunk(fd, &in, errh);
1537         if (__builtin_expect( (wr > 0), 1))
1538             chunkqueue_steal(cq, &in, wr);
1539         else if (wr < 0) {
1540             /*(writing to tempfile failed; transfer remaining data back to cq)*/
1541             chunkqueue_append_chunkqueue(cq, &in);
1542             return 0;
1543         }
1544         else /*(wr == 0)*/
1545             chunkqueue_remove_finished_chunks(&in);
1546     }
1547     return 1;
1548 }
1549 
1550 
1551 #include <sys/stat.h>   /* mkdir() */
1552 #include <sys/types.h>
1553 /*(similar to mod_deflate.c:mkdir_recursive(), but starts mid-path)*/
mkdir_recursive(char * dir,size_t off)1554 static int mkdir_recursive (char *dir, size_t off) {
1555     char *p = dir+off;
1556     if (*p != '/') {
1557         if (off && p[-1] == '/')
1558             --p;
1559         else {
1560             errno = ENOTDIR;
1561             return -1;
1562         }
1563     }
1564     do {
1565         *p = '\0';
1566         int rc = mkdir(dir, 0700);
1567         *p = '/';
1568         if (0 != rc && errno != EEXIST) return -1;
1569     } while ((p = strchr(p+1, '/')) != NULL);
1570     return 0;
1571 }
1572 
1573 
1574 __attribute_noinline__
mod_dirlisting_cache_add(request_st * const r,handler_ctx * const hctx)1575 static void mod_dirlisting_cache_add (request_st * const r, handler_ctx * const hctx) {
1576   #ifndef PATH_MAX
1577   #define PATH_MAX 4096
1578   #endif
1579     char oldpath[PATH_MAX];
1580     char newpath[PATH_MAX];
1581     buffer * const tb = r->tmp_buf;
1582     buffer_copy_path_len2(tb, BUF_PTR_LEN(hctx->conf.cache->path),
1583                               BUF_PTR_LEN(&r->physical.path));
1584     if (!stat_cache_path_isdir(tb)
1585         && 0 != mkdir_recursive(tb->ptr, buffer_clen(hctx->conf.cache->path)))
1586         return;
1587     buffer_append_string_len(tb, CONST_STR_LEN("dirlist.html"));
1588     const size_t len = buffer_clen(tb);
1589     if (len + 7 >= PATH_MAX) return;
1590     memcpy(newpath, tb->ptr, len+1);   /*(include '\0')*/
1591     buffer_append_string_len(tb, CONST_STR_LEN(".XXXXXX"));
1592     memcpy(oldpath, tb->ptr, len+7+1); /*(include '\0')*/
1593     const int fd = fdevent_mkostemp(oldpath, 0);
1594     if (fd < 0) return;
1595     if (mod_dirlisting_write_cq(fd, &r->write_queue, r->conf.errh)
1596         && 0 == fdevent_rename(oldpath, newpath)) {
1597         stat_cache_invalidate_entry(newpath, len);
1598         /* Cache-Control and ETag (also done in mod_dirlisting_cache_check())*/
1599         mod_dirlisting_cache_control(r, hctx->conf.cache->max_age);
1600         if (0 != r->conf.etag_flags) {
1601             struct stat st;
1602             if (0 == fstat(fd, &st)) {
1603                 buffer * const vb =
1604                   http_header_response_set_ptr(r, HTTP_HEADER_ETAG,
1605                                                CONST_STR_LEN("ETag"));
1606                 http_etag_create(vb, &st, r->conf.etag_flags);
1607             }
1608         }
1609     }
1610     else
1611         unlink(oldpath);
1612     close(fd);
1613 }
1614 
1615 
1616 __attribute_noinline__
mod_dirlisting_cache_json_init(request_st * const r,handler_ctx * const hctx)1617 static void mod_dirlisting_cache_json_init (request_st * const r, handler_ctx * const hctx) {
1618   #ifndef PATH_MAX
1619   #define PATH_MAX 4096
1620   #endif
1621     buffer * const tb = r->tmp_buf;
1622     buffer_copy_path_len2(tb, BUF_PTR_LEN(hctx->conf.cache->path),
1623                               BUF_PTR_LEN(&r->physical.path));
1624     if (!stat_cache_path_isdir(tb)
1625         && 0 != mkdir_recursive(tb->ptr, buffer_clen(hctx->conf.cache->path)))
1626         return;
1627     buffer_append_string_len(tb, CONST_STR_LEN("dirlist.json.XXXXXX"));
1628     const int fd = fdevent_mkostemp(tb->ptr, 0);
1629     if (fd < 0) return;
1630     hctx->jfn_len = buffer_clen(tb);
1631     hctx->jfd = fd;
1632     hctx->jfn = ck_malloc(hctx->jfn_len+1);
1633     memcpy(hctx->jfn, tb->ptr, hctx->jfn_len+1); /*(include '\0')*/
1634 }
1635 
1636 
1637 __attribute_noinline__
mod_dirlisting_cache_json(request_st * const r,handler_ctx * const hctx)1638 static void mod_dirlisting_cache_json (request_st * const r, handler_ctx * const hctx) {
1639   #ifndef PATH_MAX
1640   #define PATH_MAX 4096
1641   #endif
1642     UNUSED(r);
1643     char newpath[PATH_MAX];
1644     const size_t len = hctx->jfn_len - 7; /*(-7 for .XXXXXX)*/
1645     force_assert(len < PATH_MAX);
1646     memcpy(newpath, hctx->jfn, len);
1647     newpath[len] = '\0';
1648     if (0 == fdevent_rename(hctx->jfn, newpath))
1649         stat_cache_invalidate_entry(newpath, len);
1650     else
1651         unlink(hctx->jfn);
1652     free(hctx->jfn);
1653     hctx->jfn = NULL;
1654 }
1655 
1656 
1657 __attribute_cold__
1658 int mod_dirlisting_plugin_init(plugin *p);
mod_dirlisting_plugin_init(plugin * p)1659 int mod_dirlisting_plugin_init(plugin *p) {
1660 	p->version     = LIGHTTPD_VERSION_ID;
1661 	p->name        = "dirlisting";
1662 
1663 	p->init        = mod_dirlisting_init;
1664 	p->handle_subrequest_start = mod_dirlisting_subrequest_start;
1665 	p->handle_subrequest       = mod_dirlisting_subrequest;
1666 	p->handle_request_reset    = mod_dirlisting_reset;
1667 	p->set_defaults  = mod_dirlisting_set_defaults;
1668 	p->cleanup     = mod_dirlisting_free;
1669 
1670 	return 0;
1671 }
1672