Project

General

Profile

Feature #2315 » mod_dirlisting.c

molyland, 2011-05-12 11:18

 
1
#include "base.h"
2
#include "log.h"
3
#include "buffer.h"
4

    
5
#include "plugin.h"
6

    
7
#include "response.h"
8
#include "stat_cache.h"
9
#include "stream.h"
10

    
11
#include <ctype.h>
12
#include <stdlib.h>
13
#include <string.h>
14
#include <dirent.h>
15
#include <assert.h>
16
#include <errno.h>
17
#include <stdio.h>
18
#include <unistd.h>
19
#include <time.h>
20

    
21
/**
22
 * this is a dirlisting for a lighttpd plugin
23
 */
24

    
25

    
26
#ifdef HAVE_SYS_SYSLIMITS_H
27
#include <sys/syslimits.h>
28
#endif
29

    
30
#ifdef HAVE_ATTR_ATTRIBUTES_H
31
#include <attr/attributes.h>
32
#endif
33

    
34
#include "version.h"
35

    
36
/* plugin config for all request/connections */
37

    
38
typedef struct {
39
#ifdef HAVE_PCRE_H
40
	pcre *regex;
41
#endif
42
	buffer *string;
43
} excludes;
44

    
45
typedef struct {
46
	excludes **ptr;
47

    
48
	size_t used;
49
	size_t size;
50
} excludes_buffer;
51

    
52
typedef struct {
53
	unsigned short dir_listing;
54
	unsigned short hide_dot_files;
55
	unsigned short show_readme;
56
	unsigned short hide_readme_file;
57
	unsigned short encode_readme;
58
	unsigned short show_header;
59
	unsigned short hide_header_file;
60
	unsigned short encode_header;
61
	unsigned short auto_layout;
62

    
63
	excludes_buffer *excludes;
64

    
65
	buffer *external_css;
66
	buffer *external_js;
67
	buffer *encoding;
68
	buffer *set_footer;
69
} plugin_config;
70

    
71
typedef struct {
72
	PLUGIN_DATA;
73

    
74
	buffer *tmp_buf;
75
	buffer *content_charset;
76

    
77
	plugin_config **config_storage;
78

    
79
	plugin_config conf;
80
} plugin_data;
81

    
82
static excludes_buffer *excludes_buffer_init(void) {
83
	excludes_buffer *exb;
84

    
85
	exb = calloc(1, sizeof(*exb));
86

    
87
	return exb;
88
}
89

    
90
static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
91
#ifdef HAVE_PCRE_H
92
	size_t i;
93
	const char *errptr;
94
	int erroff;
95

    
96
	if (!string) return -1;
97

    
98
	if (exb->size == 0) {
99
		exb->size = 4;
100
		exb->used = 0;
101

    
102
		exb->ptr = malloc(exb->size * sizeof(*exb->ptr));
103

    
104
		for(i = 0; i < exb->size ; i++) {
105
			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
106
		}
107
	} else if (exb->used == exb->size) {
108
		exb->size += 4;
109

    
110
		exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
111

    
112
		for(i = exb->used; i < exb->size; i++) {
113
			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
114
		}
115
	}
116

    
117

    
118
	if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
119
						    &errptr, &erroff, NULL))) {
120
		return -1;
121
	}
122

    
123
	exb->ptr[exb->used]->string = buffer_init();
124
	buffer_copy_string_buffer(exb->ptr[exb->used]->string, string);
125

    
126
	exb->used++;
127

    
128
	return 0;
129
#else
130
	UNUSED(exb);
131
	UNUSED(string);
132

    
133
	return -1;
134
#endif
135
}
136

    
137
static void excludes_buffer_free(excludes_buffer *exb) {
138
#ifdef HAVE_PCRE_H
139
	size_t i;
140

    
141
	for (i = 0; i < exb->size; i++) {
142
		if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
143
		if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
144
		free(exb->ptr[i]);
145
	}
146

    
147
	if (exb->ptr) free(exb->ptr);
148
#endif
149

    
150
	free(exb);
151
}
152

    
153
/* init the plugin data */
154
INIT_FUNC(mod_dirlisting_init) {
155
	plugin_data *p;
156

    
157
	p = calloc(1, sizeof(*p));
158

    
159
	p->tmp_buf = buffer_init();
160
	p->content_charset = buffer_init();
161

    
162
	return p;
163
}
164

    
165
/* detroy the plugin data */
166
FREE_FUNC(mod_dirlisting_free) {
167
	plugin_data *p = p_d;
168

    
169
	UNUSED(srv);
170

    
171
	if (!p) return HANDLER_GO_ON;
172

    
173
	if (p->config_storage) {
174
		size_t i;
175
		for (i = 0; i < srv->config_context->used; i++) {
176
			plugin_config *s = p->config_storage[i];
177

    
178
			if (!s) continue;
179

    
180
			excludes_buffer_free(s->excludes);
181
			buffer_free(s->external_css);
182
      buffer_free(s->external_js);
183
			buffer_free(s->encoding);
184
			buffer_free(s->set_footer);
185

    
186
			free(s);
187
		}
188
		free(p->config_storage);
189
	}
190

    
191
	buffer_free(p->tmp_buf);
192
	buffer_free(p->content_charset);
193

    
194
	free(p);
195

    
196
	return HANDLER_GO_ON;
197
}
198

    
199
static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) {
200
	data_unset *du;
201

    
202
	if (NULL != (du = array_get_element(ca, option))) {
203
		data_array *da;
204
		size_t j;
205

    
206
		if (du->type != TYPE_ARRAY) {
207
			log_error_write(srv, __FILE__, __LINE__, "sss",
208
				"unexpected type for key: ", option, "array of strings");
209

    
210
			return HANDLER_ERROR;
211
		}
212

    
213
		da = (data_array *)du;
214

    
215
		for (j = 0; j < da->value->used; j++) {
216
			if (da->value->data[j]->type != TYPE_STRING) {
217
				log_error_write(srv, __FILE__, __LINE__, "sssbs",
218
					"unexpected type for key: ", option, "[",
219
					da->value->data[j]->key, "](string)");
220

    
221
				return HANDLER_ERROR;
222
			}
223

    
224
			if (0 != excludes_buffer_append(s->excludes,
225
				    ((data_string *)(da->value->data[j]))->value)) {
226
#ifdef HAVE_PCRE_H
227
				log_error_write(srv, __FILE__, __LINE__, "sb",
228
						"pcre-compile failed for", ((data_string *)(da->value->data[j]))->value);
229
#else
230
				log_error_write(srv, __FILE__, __LINE__, "s",
231
						"pcre support is missing, please install libpcre and the headers");
232
#endif
233
			}
234
		}
235
	}
236

    
237
	return 0;
238
}
239

    
240
/* handle plugin config and check values */
241

    
242
#define CONFIG_EXCLUDE          "dir-listing.exclude"
243
#define CONFIG_ACTIVATE         "dir-listing.activate"
244
#define CONFIG_HIDE_DOTFILES    "dir-listing.hide-dotfiles"
245
#define CONFIG_EXTERNAL_CSS     "dir-listing.external-css"
246
#define CONFIG_EXTERNAL_JS      "dir-listing.external-js"
247
#define CONFIG_ENCODING         "dir-listing.encoding"
248
#define CONFIG_SHOW_README      "dir-listing.show-readme"
249
#define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
250
#define CONFIG_SHOW_HEADER      "dir-listing.show-header"
251
#define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
252
#define CONFIG_DIR_LISTING      "server.dir-listing"
253
#define CONFIG_SET_FOOTER       "dir-listing.set-footer"
254
#define CONFIG_ENCODE_README    "dir-listing.encode-readme"
255
#define CONFIG_ENCODE_HEADER    "dir-listing.encode-header"
256
#define CONFIG_AUTO_LAYOUT      "dir-listing.auto-layout"
257

    
258

    
259
SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
260
	plugin_data *p = p_d;
261
	size_t i = 0;
262

    
263
	config_values_t cv[] = {
264
		{ CONFIG_EXCLUDE,          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },   /* 0 */
265
		{ CONFIG_ACTIVATE,         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
266
		{ CONFIG_HIDE_DOTFILES,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
267
		{ CONFIG_EXTERNAL_CSS,     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 3 */
268
    { CONFIG_EXTERNAL_JS,      NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 4 */
269
		{ CONFIG_ENCODING,         NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 5 */
270
		{ CONFIG_SHOW_README,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
271
		{ CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
272
		{ CONFIG_SHOW_HEADER,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
273
		{ CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
274
		{ CONFIG_DIR_LISTING,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
275
		{ CONFIG_SET_FOOTER,       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 11 */
276
		{ CONFIG_ENCODE_README,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
277
		{ CONFIG_ENCODE_HEADER,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
278
		{ CONFIG_AUTO_LAYOUT,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
279

    
280
		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
281
	};
282

    
283
	if (!p) return HANDLER_ERROR;
284

    
285
	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
286

    
287
	for (i = 0; i < srv->config_context->used; i++) {
288
		plugin_config *s;
289
		array *ca;
290

    
291
		s = calloc(1, sizeof(plugin_config));
292
		s->excludes = excludes_buffer_init();
293
		s->dir_listing = 0;
294
		s->external_css = buffer_init();
295
		s->external_js = buffer_init();
296
		s->hide_dot_files = 0;
297
		s->show_readme = 0;
298
		s->hide_readme_file = 0;
299
		s->show_header = 0;
300
		s->hide_header_file = 0;
301
		s->encode_readme = 1;
302
		s->encode_header = 1;
303
		s->auto_layout = 1;
304

    
305
		s->encoding = buffer_init();
306
		s->set_footer = buffer_init();
307

    
308
		cv[0].destination = s->excludes;
309
		cv[1].destination = &(s->dir_listing);
310
		cv[2].destination = &(s->hide_dot_files);
311
		cv[3].destination = s->external_css;
312
		cv[4].destination = s->external_js;
313
		cv[5].destination = s->encoding;
314
		cv[6].destination = &(s->show_readme);
315
		cv[7].destination = &(s->hide_readme_file);
316
		cv[8].destination = &(s->show_header);
317
		cv[9].destination = &(s->hide_header_file);
318
		cv[10].destination = &(s->dir_listing); /* old name */
319
		cv[11].destination = s->set_footer;
320
		cv[12].destination = &(s->encode_readme);
321
		cv[13].destination = &(s->encode_header);
322
		cv[14].destination = &(s->auto_layout);
323

    
324
		p->config_storage[i] = s;
325
		ca = ((data_config *)srv->config_context->data[i])->value;
326

    
327
		if (0 != config_insert_values_global(srv, ca, cv)) {
328
			return HANDLER_ERROR;
329
		}
330

    
331
		parse_config_entry(srv, s, ca, CONFIG_EXCLUDE);
332
	}
333

    
334
	return HANDLER_GO_ON;
335
}
336

    
337
#define PATCH(x) \
338
	p->conf.x = s->x;
339
static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
340
	size_t i, j;
341
	plugin_config *s = p->config_storage[0];
342

    
343
	PATCH(dir_listing);
344
	PATCH(external_css);
345
	PATCH(external_js);
346
	PATCH(hide_dot_files);
347
	PATCH(encoding);
348
	PATCH(show_readme);
349
	PATCH(hide_readme_file);
350
	PATCH(show_header);
351
	PATCH(hide_header_file);
352
	PATCH(excludes);
353
	PATCH(set_footer);
354
	PATCH(encode_readme);
355
	PATCH(encode_header);
356
	PATCH(auto_layout);
357

    
358
	/* skip the first, the global context */
359
	for (i = 1; i < srv->config_context->used; i++) {
360
		data_config *dc = (data_config *)srv->config_context->data[i];
361
		s = p->config_storage[i];
362

    
363
		/* condition didn't match */
364
		if (!config_check_cond(srv, con, dc)) continue;
365

    
366
		/* merge config */
367
		for (j = 0; j < dc->value->used; j++) {
368
			data_unset *du = dc->value->data[j];
369

    
370
			if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
371
			    buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
372
				PATCH(dir_listing);
373
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
374
				PATCH(hide_dot_files);
375
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
376
				PATCH(external_css);
377
      } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_JS))) {
378
        PATCH(external_js);
379
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
380
				PATCH(encoding);
381
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
382
				PATCH(show_readme);
383
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
384
				PATCH(hide_readme_file);
385
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
386
				PATCH(show_header);
387
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
388
				PATCH(hide_header_file);
389
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
390
				PATCH(set_footer);
391
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
392
				PATCH(excludes);
393
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_README))) {
394
				PATCH(encode_readme);
395
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_HEADER))) {
396
				PATCH(encode_header);
397
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_AUTO_LAYOUT))) {
398
				PATCH(auto_layout);
399
			}
400
		}
401
	}
402

    
403
	return 0;
404
}
405
#undef PATCH
406

    
407
typedef struct {
408
	size_t  namelen;
409
	time_t  mtime;
410
	off_t   size;
411
} dirls_entry_t;
412

    
413
typedef struct {
414
	dirls_entry_t **ent;
415
	size_t used;
416
	size_t size;
417
} dirls_list_t;
418

    
419
#define DIRLIST_ENT_NAME(ent)	((char*)(ent) + sizeof(dirls_entry_t))
420
#define DIRLIST_BLOB_SIZE		16
421

    
422
/* simple combsort algorithm */
423
static void http_dirls_sort(dirls_entry_t **ent, int num) {
424
	int gap = num;
425
	int i, j;
426
	int swapped;
427
	dirls_entry_t *tmp;
428

    
429
	do {
430
		gap = (gap * 10) / 13;
431
		if (gap == 9 || gap == 10)
432
			gap = 11;
433
		if (gap < 1)
434
			gap = 1;
435
		swapped = 0;
436

    
437
		for (i = 0; i < num - gap; i++) {
438
			j = i + gap;
439
			if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
440
				tmp = ent[i];
441
				ent[i] = ent[j];
442
				ent[j] = tmp;
443
				swapped = 1;
444
			}
445
		}
446

    
447
	} while (gap > 1 || swapped);
448
}
449

    
450
/* buffer must be able to hold "999.9K"
451
 * conversion is simple but not perfect
452
 */
453
static int http_list_directory_sizefmt(char *buf, off_t size) {
454
	const char unit[] = "KMGTPE";	/* Kilo, Mega, Tera, Peta, Exa */
455
	const char *u = unit - 1;		/* u will always increment at least once */
456
	int remain;
457
	char *out = buf;
458

    
459
	if (size < 100)
460
		size += 99;
461
	if (size < 100)
462
		size = 0;
463

    
464
	while (1) {
465
		remain = (int) size & 1023;
466
		size >>= 10;
467
		u++;
468
		if ((size & (~0 ^ 1023)) == 0)
469
			break;
470
	}
471

    
472
	remain /= 100;
473
	if (remain > 9)
474
		remain = 9;
475
	if (size > 999) {
476
		size   = 0;
477
		remain = 9;
478
		u++;
479
	}
480

    
481
	out   += LI_ltostr(out, size);
482
	out[0] = '.';
483
	out[1] = remain + '0';
484
	out[2] = *u;
485
	out[3] = '\0';
486

    
487
	return (out + 3 - buf);
488
}
489

    
490
static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
491
	UNUSED(srv);
492

    
493
	if (p->conf.auto_layout) {
494
		buffer_append_string_len(out, CONST_STR_LEN(
495
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
496
			"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
497
			"<head>\n"
498
			"<title>Index of "
499
		));
500
		buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
501
		buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
502

    
503
    if (p->conf.external_js->used > 1) {
504
      buffer_append_string_len(out, CONST_STR_LEN("<script type=\"text/javascript\" src=\""));
505
      buffer_append_string_buffer(out, p->conf.external_js);
506
      buffer_append_string_len(out, CONST_STR_LEN("\" ></script>\n"));
507
    }
508

    
509
		if (p->conf.external_css->used > 1) {
510
			buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
511
			buffer_append_string_buffer(out, p->conf.external_css);
512
			buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
513
		} else {
514
			buffer_append_string_len(out, CONST_STR_LEN(
515
				"<style type=\"text/css\">\n"
516
				"a, a:active {text-decoration: none; color: blue;}\n"
517
				"a:visited {color: #48468F;}\n"
518
				"a:hover, a:focus {text-decoration: underline; color: red;}\n"
519
				"body {background-color: #F5F5F5;}\n"
520
				"h2 {margin-bottom: 12px;}\n"
521
				"table {margin-left: 12px;}\n"
522
				"th, td {"
523
				" font: 90% monospace;"
524
				" text-align: left;"
525
				"}\n"
526
				"th {"
527
				" font-weight: bold;"
528
				" padding-right: 14px;"
529
				" padding-bottom: 3px;"
530
				"}\n"
531
				"td {padding-right: 14px;}\n"
532
				"td.s, th.s {text-align: right;}\n"
533
				"div.list {"
534
				" background-color: white;"
535
				" border-top: 1px solid #646464;"
536
				" border-bottom: 1px solid #646464;"
537
				" padding-top: 10px;"
538
				" padding-bottom: 14px;"
539
				"}\n"
540
				"div.foot {"
541
				" font: 90% monospace;"
542
				" color: #787878;"
543
				" padding-top: 4px;"
544
				"}\n"
545
				"</style>\n"
546
			));
547
		}
548

    
549
		buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
550
	}
551

    
552
	/* HEADER.txt */
553
	if (p->conf.show_header) {
554
		stream s;
555
		/* if we have a HEADER file, display it in <pre class="header"></pre> */
556

    
557
		buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
558
		BUFFER_APPEND_SLASH(p->tmp_buf);
559
		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("HEADER.txt"));
560

    
561
		if (-1 != stream_open(&s, p->tmp_buf)) {
562
			if (p->conf.encode_header) {
563
				buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"header\">"));
564
				buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
565
				buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
566
			} else {
567
				buffer_append_string_len(out, s.start, s.size);
568
			}
569
		}
570
		stream_close(&s);
571
	}
572

    
573
	buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
574
	buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
575
	buffer_append_string_len(out, CONST_STR_LEN(
576
		"</h2>\n"
577
		"<div class=\"list\">\n"
578
		"<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
579
		"<thead>"
580
		"<tr>"
581
			"<th class=\"n\">Name</th>"
582
			"<th class=\"m\">Last Modified</th>"
583
			"<th class=\"s\">Size</th>"
584
			"<th class=\"t\">Type</th>"
585
		"</tr>"
586
		"</thead>\n"
587
		"<tbody>\n"
588
		"<tr>"
589
			"<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
590
			"<td class=\"m\">&nbsp;</td>"
591
			"<td class=\"s\">- &nbsp;</td>"
592
			"<td class=\"t\">Directory</td>"
593
		"</tr>\n"
594
	));
595
}
596

    
597
static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
598
	UNUSED(srv);
599

    
600
	buffer_append_string_len(out, CONST_STR_LEN(
601
		"</tbody>\n"
602
		"</table>\n"
603
		"</div>\n"
604
	));
605

    
606
	if (p->conf.show_readme) {
607
		stream s;
608
		/* if we have a README file, display it in <pre class="readme"></pre> */
609

    
610
		buffer_copy_string_buffer(p->tmp_buf,  con->physical.path);
611
		BUFFER_APPEND_SLASH(p->tmp_buf);
612
		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("README.txt"));
613

    
614
		if (-1 != stream_open(&s, p->tmp_buf)) {
615
			if (p->conf.encode_readme) {
616
				buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"readme\">"));
617
				buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
618
				buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
619
			} else {
620
				buffer_append_string_len(out, s.start, s.size);
621
			}
622
		}
623
		stream_close(&s);
624
	}
625

    
626
	if(p->conf.auto_layout) {
627
		buffer_append_string_len(out, CONST_STR_LEN(
628
			"<div class=\"foot\">"
629
		));
630

    
631
		if (p->conf.set_footer->used > 1) {
632
			buffer_append_string_buffer(out, p->conf.set_footer);
633
		} else if (buffer_is_empty(con->conf.server_tag)) {
634
			buffer_append_string_len(out, CONST_STR_LEN(PACKAGE_DESC));
635
		} else {
636
			buffer_append_string_buffer(out, con->conf.server_tag);
637
		}
638

    
639
		buffer_append_string_len(out, CONST_STR_LEN(
640
			"</div>\n"
641
			"</body>\n"
642
			"</html>\n"
643
		));
644
	}
645
}
646

    
647
static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
648
	DIR *dp;
649
	buffer *out;
650
	struct dirent *dent;
651
	struct stat st;
652
	char *path, *path_file;
653
	size_t i;
654
	int hide_dotfiles = p->conf.hide_dot_files;
655
	dirls_list_t dirs, files, *list;
656
	dirls_entry_t *tmp;
657
	char sizebuf[sizeof("999.9K")];
658
	char datebuf[sizeof("2005-Jan-01 22:23:24")];
659
	size_t k;
660
	const char *content_type;
661
	long name_max;
662
#ifdef HAVE_XATTR
663
	char attrval[128];
664
	int attrlen;
665
#endif
666
#ifdef HAVE_LOCALTIME_R
667
	struct tm tm;
668
#endif
669

    
670
	if (dir->used == 0) return -1;
671

    
672
	i = dir->used - 1;
673

    
674
#ifdef HAVE_PATHCONF
675
	if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
676
#ifdef NAME_MAX
677
		name_max = NAME_MAX;
678
#else
679
		name_max = 255; /* stupid default */
680
#endif
681
	}
682
#elif defined __WIN32
683
	name_max = FILENAME_MAX;
684
#else
685
	name_max = NAME_MAX;
686
#endif
687

    
688
	path = malloc(dir->used + name_max);
689
	assert(path);
690
	strcpy(path, dir->ptr);
691
	path_file = path + i;
692

    
693
	if (NULL == (dp = opendir(path))) {
694
		log_error_write(srv, __FILE__, __LINE__, "sbs",
695
			"opendir failed:", dir, strerror(errno));
696

    
697
		free(path);
698
		return -1;
699
	}
700

    
701
	dirs.ent   = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
702
	assert(dirs.ent);
703
	dirs.size  = DIRLIST_BLOB_SIZE;
704
	dirs.used  = 0;
705
	files.ent  = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
706
	assert(files.ent);
707
	files.size = DIRLIST_BLOB_SIZE;
708
	files.used = 0;
709

    
710
	while ((dent = readdir(dp)) != NULL) {
711
		unsigned short exclude_match = 0;
712

    
713
		if (dent->d_name[0] == '.') {
714
			if (hide_dotfiles)
715
				continue;
716
			if (dent->d_name[1] == '\0')
717
				continue;
718
			if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
719
				continue;
720
		}
721

    
722
		if (p->conf.hide_readme_file) {
723
			if (strcmp(dent->d_name, "README.txt") == 0)
724
				continue;
725
		}
726
		if (p->conf.hide_header_file) {
727
			if (strcmp(dent->d_name, "HEADER.txt") == 0)
728
				continue;
729
		}
730

    
731
		/* compare d_name against excludes array
732
		 * elements, skipping any that match.
733
		 */
734
#ifdef HAVE_PCRE_H
735
		for(i = 0; i < p->conf.excludes->used; i++) {
736
			int n;
737
#define N 10
738
			int ovec[N * 3];
739
			pcre *regex = p->conf.excludes->ptr[i]->regex;
740

    
741
			if ((n = pcre_exec(regex, NULL, dent->d_name,
742
				    strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
743
				if (n != PCRE_ERROR_NOMATCH) {
744
					log_error_write(srv, __FILE__, __LINE__, "sd",
745
						"execution error while matching:", n);
746

    
747
					return -1;
748
				}
749
			}
750
			else {
751
				exclude_match = 1;
752
				break;
753
			}
754
		}
755

    
756
		if (exclude_match) {
757
			continue;
758
		}
759
#endif
760

    
761
		i = strlen(dent->d_name);
762

    
763
		/* NOTE: the manual says, d_name is never more than NAME_MAX
764
		 *       so this should actually not be a buffer-overflow-risk
765
		 */
766
		if (i > (size_t)name_max) continue;
767

    
768
		memcpy(path_file, dent->d_name, i + 1);
769
		if (stat(path, &st) != 0)
770
			continue;
771

    
772
		list = &files;
773
		if (S_ISDIR(st.st_mode))
774
			list = &dirs;
775

    
776
		if (list->used == list->size) {
777
			list->size += DIRLIST_BLOB_SIZE;
778
			list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
779
			assert(list->ent);
780
		}
781

    
782
		tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
783
		tmp->mtime = st.st_mtime;
784
		tmp->size  = st.st_size;
785
		tmp->namelen = i;
786
		memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
787

    
788
		list->ent[list->used++] = tmp;
789
	}
790
	closedir(dp);
791

    
792
	if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
793

    
794
	if (files.used) http_dirls_sort(files.ent, files.used);
795

    
796
	out = chunkqueue_get_append_buffer(con->write_queue);
797
	buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
798
	if (buffer_is_empty(p->conf.encoding)) {
799
		buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
800
	} else {
801
		buffer_append_string_buffer(out, p->conf.encoding);
802
	}
803
	buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
804
	http_list_directory_header(srv, con, p, out);
805

    
806
	/* directories */
807
	for (i = 0; i < dirs.used; i++) {
808
		tmp = dirs.ent[i];
809

    
810
#ifdef HAVE_LOCALTIME_R
811
		localtime_r(&(tmp->mtime), &tm);
812
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
813
#else
814
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
815
#endif
816

    
817
		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
818
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
819
		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
820
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
821
		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
822
		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
823
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
824

    
825
		free(tmp);
826
	}
827

    
828
	/* files */
829
	for (i = 0; i < files.used; i++) {
830
		tmp = files.ent[i];
831

    
832
		content_type = NULL;
833
#ifdef HAVE_XATTR
834

    
835
		if (con->conf.use_xattr) {
836
			memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
837
			attrlen = sizeof(attrval) - 1;
838
			if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
839
				attrval[attrlen] = '\0';
840
				content_type = attrval;
841
			}
842
		}
843
#endif
844

    
845
		if (content_type == NULL) {
846
			content_type = "application/octet-stream";
847
			for (k = 0; k < con->conf.mimetypes->used; k++) {
848
				data_string *ds = (data_string *)con->conf.mimetypes->data[k];
849
				size_t ct_len;
850

    
851
				if (ds->key->used == 0)
852
					continue;
853

    
854
				ct_len = ds->key->used - 1;
855
				if (tmp->namelen < ct_len)
856
					continue;
857

    
858
				if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
859
					content_type = ds->value->ptr;
860
					break;
861
				}
862
			}
863
		}
864

    
865
#ifdef HAVE_LOCALTIME_R
866
		localtime_r(&(tmp->mtime), &tm);
867
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
868
#else
869
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
870
#endif
871
		http_list_directory_sizefmt(sizebuf, tmp->size);
872

    
873
		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
874
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
875
		buffer_append_string_len(out, CONST_STR_LEN("\">"));
876
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
877
		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
878
		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
879
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
880
		buffer_append_string(out, sizebuf);
881
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
882
		buffer_append_string(out, content_type);
883
		buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
884

    
885
		free(tmp);
886
	}
887

    
888
	free(files.ent);
889
	free(dirs.ent);
890
	free(path);
891

    
892
	http_list_directory_footer(srv, con, p, out);
893

    
894
	/* Insert possible charset to Content-Type */
895
	if (buffer_is_empty(p->conf.encoding)) {
896
		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
897
	} else {
898
		buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
899
		buffer_append_string_buffer(p->content_charset, p->conf.encoding);
900
		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
901
	}
902

    
903
	con->file_finished = 1;
904

    
905
	return 0;
906
}
907

    
908

    
909

    
910
URIHANDLER_FUNC(mod_dirlisting_subrequest) {
911
	plugin_data *p = p_d;
912
	stat_cache_entry *sce = NULL;
913

    
914
	UNUSED(srv);
915

    
916
	/* we only handle GET, POST and HEAD */
917
	switch(con->request.http_method) {
918
	case HTTP_METHOD_GET:
919
	case HTTP_METHOD_POST:
920
	case HTTP_METHOD_HEAD:
921
		break;
922
	default:
923
		return HANDLER_GO_ON;
924
	}
925

    
926
	if (con->mode != DIRECT) return HANDLER_GO_ON;
927

    
928
	if (con->physical.path->used == 0) return HANDLER_GO_ON;
929
	if (con->uri.path->used == 0) return HANDLER_GO_ON;
930
	if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
931

    
932
	mod_dirlisting_patch_connection(srv, con, p);
933

    
934
	if (!p->conf.dir_listing) return HANDLER_GO_ON;
935

    
936
	if (con->conf.log_request_handling) {
937
		log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
938
		log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
939
	}
940

    
941
	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
942
		log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
943
		SEGFAULT();
944
	}
945

    
946
	if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
947

    
948
	if (http_list_directory(srv, con, p, con->physical.path)) {
949
		/* dirlisting failed */
950
		con->http_status = 403;
951
	}
952

    
953
	buffer_reset(con->physical.path);
954

    
955
	/* not found */
956
	return HANDLER_FINISHED;
957
}
958

    
959
/* this function is called at dlopen() time and inits the callbacks */
960

    
961
int mod_dirlisting_plugin_init(plugin *p);
962
int mod_dirlisting_plugin_init(plugin *p) {
963
	p->version     = LIGHTTPD_VERSION_ID;
964
	p->name        = buffer_init_string("dirlisting");
965

    
966
	p->init        = mod_dirlisting_init;
967
	p->handle_subrequest_start  = mod_dirlisting_subrequest;
968
	p->set_defaults  = mod_dirlisting_set_defaults;
969
	p->cleanup     = mod_dirlisting_free;
970

    
971
	p->data        = NULL;
972

    
973
	return 0;
974
}
(1-1/2)