Annotation of embedaddon/nginx/src/http/modules/ngx_http_autoindex_module.c, revision 1.1.1.1
1.1 misho 1:
2: /*
3: * Copyright (C) Igor Sysoev
4: * Copyright (C) Nginx, Inc.
5: */
6:
7:
8: #include <ngx_config.h>
9: #include <ngx_core.h>
10: #include <ngx_http.h>
11:
12:
13: #if 0
14:
15: typedef struct {
16: ngx_buf_t *buf;
17: size_t size;
18: ngx_pool_t *pool;
19: size_t alloc_size;
20: ngx_chain_t **last_out;
21: } ngx_http_autoindex_ctx_t;
22:
23: #endif
24:
25:
26: typedef struct {
27: ngx_str_t name;
28: size_t utf_len;
29: size_t escape;
30: size_t escape_html;
31:
32: unsigned dir:1;
33:
34: time_t mtime;
35: off_t size;
36: } ngx_http_autoindex_entry_t;
37:
38:
39: typedef struct {
40: ngx_flag_t enable;
41: ngx_flag_t localtime;
42: ngx_flag_t exact_size;
43: } ngx_http_autoindex_loc_conf_t;
44:
45:
46: #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
47:
48: #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
49:
50:
51: static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
52: const void *two);
53: static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
54: ngx_dir_t *dir, ngx_str_t *name);
55: static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
56: static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
57: static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
58: void *parent, void *child);
59:
60:
61: static ngx_command_t ngx_http_autoindex_commands[] = {
62:
63: { ngx_string("autoindex"),
64: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
65: ngx_conf_set_flag_slot,
66: NGX_HTTP_LOC_CONF_OFFSET,
67: offsetof(ngx_http_autoindex_loc_conf_t, enable),
68: NULL },
69:
70: { ngx_string("autoindex_localtime"),
71: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
72: ngx_conf_set_flag_slot,
73: NGX_HTTP_LOC_CONF_OFFSET,
74: offsetof(ngx_http_autoindex_loc_conf_t, localtime),
75: NULL },
76:
77: { ngx_string("autoindex_exact_size"),
78: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
79: ngx_conf_set_flag_slot,
80: NGX_HTTP_LOC_CONF_OFFSET,
81: offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
82: NULL },
83:
84: ngx_null_command
85: };
86:
87:
88: static ngx_http_module_t ngx_http_autoindex_module_ctx = {
89: NULL, /* preconfiguration */
90: ngx_http_autoindex_init, /* postconfiguration */
91:
92: NULL, /* create main configuration */
93: NULL, /* init main configuration */
94:
95: NULL, /* create server configuration */
96: NULL, /* merge server configuration */
97:
98: ngx_http_autoindex_create_loc_conf, /* create location configuration */
99: ngx_http_autoindex_merge_loc_conf /* merge location configuration */
100: };
101:
102:
103: ngx_module_t ngx_http_autoindex_module = {
104: NGX_MODULE_V1,
105: &ngx_http_autoindex_module_ctx, /* module context */
106: ngx_http_autoindex_commands, /* module directives */
107: NGX_HTTP_MODULE, /* module type */
108: NULL, /* init master */
109: NULL, /* init module */
110: NULL, /* init process */
111: NULL, /* init thread */
112: NULL, /* exit thread */
113: NULL, /* exit process */
114: NULL, /* exit master */
115: NGX_MODULE_V1_PADDING
116: };
117:
118:
119: static u_char title[] =
120: "<html>" CRLF
121: "<head><title>Index of "
122: ;
123:
124:
125: static u_char header[] =
126: "</title></head>" CRLF
127: "<body bgcolor=\"white\">" CRLF
128: "<h1>Index of "
129: ;
130:
131: static u_char tail[] =
132: "</body>" CRLF
133: "</html>" CRLF
134: ;
135:
136:
137: static ngx_int_t
138: ngx_http_autoindex_handler(ngx_http_request_t *r)
139: {
140: u_char *last, *filename, scale;
141: off_t length;
142: size_t len, char_len, escape_html, allocated, root;
143: ngx_tm_t tm;
144: ngx_err_t err;
145: ngx_buf_t *b;
146: ngx_int_t rc, size;
147: ngx_str_t path;
148: ngx_dir_t dir;
149: ngx_uint_t i, level, utf8;
150: ngx_pool_t *pool;
151: ngx_time_t *tp;
152: ngx_chain_t out;
153: ngx_array_t entries;
154: ngx_http_autoindex_entry_t *entry;
155: ngx_http_autoindex_loc_conf_t *alcf;
156:
157: static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
158: "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
159:
160: if (r->uri.data[r->uri.len - 1] != '/') {
161: return NGX_DECLINED;
162: }
163:
164: if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
165: return NGX_DECLINED;
166: }
167:
168: alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
169:
170: if (!alcf->enable) {
171: return NGX_DECLINED;
172: }
173:
174: /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
175:
176: last = ngx_http_map_uri_to_path(r, &path, &root,
177: NGX_HTTP_AUTOINDEX_PREALLOCATE);
178: if (last == NULL) {
179: return NGX_HTTP_INTERNAL_SERVER_ERROR;
180: }
181:
182: allocated = path.len;
183: path.len = last - path.data;
184: if (path.len > 1) {
185: path.len--;
186: }
187: path.data[path.len] = '\0';
188:
189: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
190: "http autoindex: \"%s\"", path.data);
191:
192: if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
193: err = ngx_errno;
194:
195: if (err == NGX_ENOENT
196: || err == NGX_ENOTDIR
197: || err == NGX_ENAMETOOLONG)
198: {
199: level = NGX_LOG_ERR;
200: rc = NGX_HTTP_NOT_FOUND;
201:
202: } else if (err == NGX_EACCES) {
203: level = NGX_LOG_ERR;
204: rc = NGX_HTTP_FORBIDDEN;
205:
206: } else {
207: level = NGX_LOG_CRIT;
208: rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
209: }
210:
211: ngx_log_error(level, r->connection->log, err,
212: ngx_open_dir_n " \"%s\" failed", path.data);
213:
214: return rc;
215: }
216:
217: #if (NGX_SUPPRESS_WARN)
218:
219: /* MSVC thinks 'entries' may be used without having been initialized */
220: ngx_memzero(&entries, sizeof(ngx_array_t));
221:
222: #endif
223:
224: /* TODO: pool should be temporary pool */
225: pool = r->pool;
226:
227: if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
228: != NGX_OK)
229: {
230: return ngx_http_autoindex_error(r, &dir, &path);
231: }
232:
233: r->headers_out.status = NGX_HTTP_OK;
234: r->headers_out.content_type_len = sizeof("text/html") - 1;
235: ngx_str_set(&r->headers_out.content_type, "text/html");
236:
237: rc = ngx_http_send_header(r);
238:
239: if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
240: if (ngx_close_dir(&dir) == NGX_ERROR) {
241: ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
242: ngx_close_dir_n " \"%V\" failed", &path);
243: }
244:
245: return rc;
246: }
247:
248: filename = path.data;
249: filename[path.len] = '/';
250:
251: if (r->headers_out.charset.len == 5
252: && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
253: == 0)
254: {
255: utf8 = 1;
256:
257: } else {
258: utf8 = 0;
259: }
260:
261: for ( ;; ) {
262: ngx_set_errno(0);
263:
264: if (ngx_read_dir(&dir) == NGX_ERROR) {
265: err = ngx_errno;
266:
267: if (err != NGX_ENOMOREFILES) {
268: ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
269: ngx_read_dir_n " \"%V\" failed", &path);
270: return ngx_http_autoindex_error(r, &dir, &path);
271: }
272:
273: break;
274: }
275:
276: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
277: "http autoindex file: \"%s\"", ngx_de_name(&dir));
278:
279: len = ngx_de_namelen(&dir);
280:
281: if (ngx_de_name(&dir)[0] == '.') {
282: continue;
283: }
284:
285: if (!dir.valid_info) {
286:
287: /* 1 byte for '/' and 1 byte for terminating '\0' */
288:
289: if (path.len + 1 + len + 1 > allocated) {
290: allocated = path.len + 1 + len + 1
291: + NGX_HTTP_AUTOINDEX_PREALLOCATE;
292:
293: filename = ngx_pnalloc(pool, allocated);
294: if (filename == NULL) {
295: return ngx_http_autoindex_error(r, &dir, &path);
296: }
297:
298: last = ngx_cpystrn(filename, path.data, path.len + 1);
299: *last++ = '/';
300: }
301:
302: ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
303:
304: if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
305: err = ngx_errno;
306:
307: if (err != NGX_ENOENT && err != NGX_ELOOP) {
308: ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
309: ngx_de_info_n " \"%s\" failed", filename);
310:
311: if (err == NGX_EACCES) {
312: continue;
313: }
314:
315: return ngx_http_autoindex_error(r, &dir, &path);
316: }
317:
318: if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
319: ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
320: ngx_de_link_info_n " \"%s\" failed",
321: filename);
322: return ngx_http_autoindex_error(r, &dir, &path);
323: }
324: }
325: }
326:
327: entry = ngx_array_push(&entries);
328: if (entry == NULL) {
329: return ngx_http_autoindex_error(r, &dir, &path);
330: }
331:
332: entry->name.len = len;
333:
334: entry->name.data = ngx_pnalloc(pool, len + 1);
335: if (entry->name.data == NULL) {
336: return ngx_http_autoindex_error(r, &dir, &path);
337: }
338:
339: ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
340:
341: entry->escape = 2 * ngx_escape_uri(NULL, ngx_de_name(&dir), len,
342: NGX_ESCAPE_URI_COMPONENT);
343:
344: entry->escape_html = ngx_escape_html(NULL, entry->name.data,
345: entry->name.len);
346:
347: if (utf8) {
348: entry->utf_len = ngx_utf8_length(entry->name.data, entry->name.len);
349: } else {
350: entry->utf_len = len;
351: }
352:
353: entry->dir = ngx_de_is_dir(&dir);
354: entry->mtime = ngx_de_mtime(&dir);
355: entry->size = ngx_de_size(&dir);
356: }
357:
358: if (ngx_close_dir(&dir) == NGX_ERROR) {
359: ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
360: ngx_close_dir_n " \"%s\" failed", &path);
361: }
362:
363: escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
364:
365: len = sizeof(title) - 1
366: + r->uri.len + escape_html
367: + sizeof(header) - 1
368: + r->uri.len + escape_html
369: + sizeof("</h1>") - 1
370: + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
371: + sizeof("</pre><hr>") - 1
372: + sizeof(tail) - 1;
373:
374: entry = entries.elts;
375: for (i = 0; i < entries.nelts; i++) {
376: len += sizeof("<a href=\"") - 1
377: + entry[i].name.len + entry[i].escape
378: + 1 /* 1 is for "/" */
379: + sizeof("\">") - 1
380: + entry[i].name.len - entry[i].utf_len
381: + entry[i].escape_html
382: + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof(">") - 2
383: + sizeof("</a>") - 1
384: + sizeof(" 28-Sep-1970 12:00 ") - 1
385: + 20 /* the file size */
386: + 2;
387: }
388:
389: b = ngx_create_temp_buf(r->pool, len);
390: if (b == NULL) {
391: return NGX_ERROR;
392: }
393:
394: if (entries.nelts > 1) {
395: ngx_qsort(entry, (size_t) entries.nelts,
396: sizeof(ngx_http_autoindex_entry_t),
397: ngx_http_autoindex_cmp_entries);
398: }
399:
400: b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
401:
402: if (escape_html) {
403: b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
404: b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
405: b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
406:
407: } else {
408: b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
409: b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
410: b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
411: }
412:
413: b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
414:
415: b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
416: sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
417:
418: tp = ngx_timeofday();
419:
420: for (i = 0; i < entries.nelts; i++) {
421: b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
422:
423: if (entry[i].escape) {
424: ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
425: NGX_ESCAPE_URI_COMPONENT);
426:
427: b->last += entry[i].name.len + entry[i].escape;
428:
429: } else {
430: b->last = ngx_cpymem(b->last, entry[i].name.data,
431: entry[i].name.len);
432: }
433:
434: if (entry[i].dir) {
435: *b->last++ = '/';
436: }
437:
438: *b->last++ = '"';
439: *b->last++ = '>';
440:
441: len = entry[i].utf_len;
442:
443: if (entry[i].name.len != len) {
444: if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
445: char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
446:
447: } else {
448: char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
449: }
450:
451: last = b->last;
452: b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
453: char_len, entry[i].name.len + 1);
454:
455: if (entry[i].escape_html) {
456: b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
457: b->last - last);
458: }
459:
460: last = b->last;
461:
462: } else {
463: if (entry[i].escape_html) {
464: if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
465: char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
466:
467: } else {
468: char_len = len;
469: }
470:
471: b->last = (u_char *) ngx_escape_html(b->last,
472: entry[i].name.data, char_len);
473: last = b->last;
474:
475: } else {
476: b->last = ngx_cpystrn(b->last, entry[i].name.data,
477: NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
478: last = b->last - 3;
479: }
480: }
481:
482: if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
483: b->last = ngx_cpymem(last, "..></a>", sizeof("..></a>") - 1);
484:
485: } else {
486: if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
487: *b->last++ = '/';
488: len++;
489: }
490:
491: b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
492:
493: if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
494: ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
495: b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
496: }
497: }
498:
499: *b->last++ = ' ';
500:
501: ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
502:
503: b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
504: tm.ngx_tm_mday,
505: months[tm.ngx_tm_mon - 1],
506: tm.ngx_tm_year,
507: tm.ngx_tm_hour,
508: tm.ngx_tm_min);
509:
510: if (alcf->exact_size) {
511: if (entry[i].dir) {
512: b->last = ngx_cpymem(b->last, " -",
513: sizeof(" -") - 1);
514: } else {
515: b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
516: }
517:
518: } else {
519: if (entry[i].dir) {
520: b->last = ngx_cpymem(b->last, " -",
521: sizeof(" -") - 1);
522:
523: } else {
524: length = entry[i].size;
525:
526: if (length > 1024 * 1024 * 1024 - 1) {
527: size = (ngx_int_t) (length / (1024 * 1024 * 1024));
528: if ((length % (1024 * 1024 * 1024))
529: > (1024 * 1024 * 1024 / 2 - 1))
530: {
531: size++;
532: }
533: scale = 'G';
534:
535: } else if (length > 1024 * 1024 - 1) {
536: size = (ngx_int_t) (length / (1024 * 1024));
537: if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
538: size++;
539: }
540: scale = 'M';
541:
542: } else if (length > 9999) {
543: size = (ngx_int_t) (length / 1024);
544: if (length % 1024 > 511) {
545: size++;
546: }
547: scale = 'K';
548:
549: } else {
550: size = (ngx_int_t) length;
551: scale = '\0';
552: }
553:
554: if (scale) {
555: b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
556:
557: } else {
558: b->last = ngx_sprintf(b->last, " %6i", size);
559: }
560: }
561: }
562:
563: *b->last++ = CR;
564: *b->last++ = LF;
565: }
566:
567: /* TODO: free temporary pool */
568:
569: b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
570:
571: b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
572:
573: if (r == r->main) {
574: b->last_buf = 1;
575: }
576:
577: b->last_in_chain = 1;
578:
579: out.buf = b;
580: out.next = NULL;
581:
582: return ngx_http_output_filter(r, &out);
583: }
584:
585:
586: static int ngx_libc_cdecl
587: ngx_http_autoindex_cmp_entries(const void *one, const void *two)
588: {
589: ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
590: ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
591:
592: if (first->dir && !second->dir) {
593: /* move the directories to the start */
594: return -1;
595: }
596:
597: if (!first->dir && second->dir) {
598: /* move the directories to the start */
599: return 1;
600: }
601:
602: return (int) ngx_strcmp(first->name.data, second->name.data);
603: }
604:
605:
606: #if 0
607:
608: static ngx_buf_t *
609: ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
610: {
611: ngx_chain_t *cl;
612:
613: if (ctx->buf) {
614:
615: if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
616: return ctx->buf;
617: }
618:
619: ctx->size += ctx->buf->last - ctx->buf->pos;
620: }
621:
622: ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
623: if (ctx->buf == NULL) {
624: return NULL;
625: }
626:
627: cl = ngx_alloc_chain_link(ctx->pool);
628: if (cl == NULL) {
629: return NULL;
630: }
631:
632: cl->buf = ctx->buf;
633: cl->next = NULL;
634:
635: *ctx->last_out = cl;
636: ctx->last_out = &cl->next;
637:
638: return ctx->buf;
639: }
640:
641: #endif
642:
643:
644: static ngx_int_t
645: ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
646: {
647: if (ngx_close_dir(dir) == NGX_ERROR) {
648: ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
649: ngx_close_dir_n " \"%V\" failed", name);
650: }
651:
652: return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
653: }
654:
655:
656: static void *
657: ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
658: {
659: ngx_http_autoindex_loc_conf_t *conf;
660:
661: conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
662: if (conf == NULL) {
663: return NULL;
664: }
665:
666: conf->enable = NGX_CONF_UNSET;
667: conf->localtime = NGX_CONF_UNSET;
668: conf->exact_size = NGX_CONF_UNSET;
669:
670: return conf;
671: }
672:
673:
674: static char *
675: ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
676: {
677: ngx_http_autoindex_loc_conf_t *prev = parent;
678: ngx_http_autoindex_loc_conf_t *conf = child;
679:
680: ngx_conf_merge_value(conf->enable, prev->enable, 0);
681: ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
682: ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
683:
684: return NGX_CONF_OK;
685: }
686:
687:
688: static ngx_int_t
689: ngx_http_autoindex_init(ngx_conf_t *cf)
690: {
691: ngx_http_handler_pt *h;
692: ngx_http_core_main_conf_t *cmcf;
693:
694: cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
695:
696: h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
697: if (h == NULL) {
698: return NGX_ERROR;
699: }
700:
701: *h = ngx_http_autoindex_handler;
702:
703: return NGX_OK;
704: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>