nginx 1.1.11
[nginx.git] / src / http / modules / ngx_http_autoindex_module.c
blobbd7388126ae137ae618a5effd71b76463ddbf690
2 /*
3 * Copyright (C) Igor Sysoev
4 */
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
12 #if 0
14 typedef struct {
15 ngx_buf_t *buf;
16 size_t size;
17 ngx_pool_t *pool;
18 size_t alloc_size;
19 ngx_chain_t **last_out;
20 } ngx_http_autoindex_ctx_t;
22 #endif
25 typedef struct {
26 ngx_str_t name;
27 size_t utf_len;
28 size_t escape;
29 size_t escape_html;
31 unsigned dir:1;
33 time_t mtime;
34 off_t size;
35 } ngx_http_autoindex_entry_t;
38 typedef struct {
39 ngx_flag_t enable;
40 ngx_flag_t localtime;
41 ngx_flag_t exact_size;
42 } ngx_http_autoindex_loc_conf_t;
45 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
47 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
50 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
51 const void *two);
52 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
53 ngx_dir_t *dir, ngx_str_t *name);
54 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
55 static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
56 static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
57 void *parent, void *child);
60 static ngx_command_t ngx_http_autoindex_commands[] = {
62 { ngx_string("autoindex"),
63 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
64 ngx_conf_set_flag_slot,
65 NGX_HTTP_LOC_CONF_OFFSET,
66 offsetof(ngx_http_autoindex_loc_conf_t, enable),
67 NULL },
69 { ngx_string("autoindex_localtime"),
70 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
71 ngx_conf_set_flag_slot,
72 NGX_HTTP_LOC_CONF_OFFSET,
73 offsetof(ngx_http_autoindex_loc_conf_t, localtime),
74 NULL },
76 { ngx_string("autoindex_exact_size"),
77 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
78 ngx_conf_set_flag_slot,
79 NGX_HTTP_LOC_CONF_OFFSET,
80 offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
81 NULL },
83 ngx_null_command
87 static ngx_http_module_t ngx_http_autoindex_module_ctx = {
88 NULL, /* preconfiguration */
89 ngx_http_autoindex_init, /* postconfiguration */
91 NULL, /* create main configuration */
92 NULL, /* init main configuration */
94 NULL, /* create server configuration */
95 NULL, /* merge server configuration */
97 ngx_http_autoindex_create_loc_conf, /* create location configration */
98 ngx_http_autoindex_merge_loc_conf /* merge location configration */
102 ngx_module_t ngx_http_autoindex_module = {
103 NGX_MODULE_V1,
104 &ngx_http_autoindex_module_ctx, /* module context */
105 ngx_http_autoindex_commands, /* module directives */
106 NGX_HTTP_MODULE, /* module type */
107 NULL, /* init master */
108 NULL, /* init module */
109 NULL, /* init process */
110 NULL, /* init thread */
111 NULL, /* exit thread */
112 NULL, /* exit process */
113 NULL, /* exit master */
114 NGX_MODULE_V1_PADDING
118 static u_char title[] =
119 "<html>" CRLF
120 "<head><title>Index of "
124 static u_char header[] =
125 "</title></head>" CRLF
126 "<body bgcolor=\"white\">" CRLF
127 "<h1>Index of "
130 static u_char tail[] =
131 "</body>" CRLF
132 "</html>" CRLF
136 static ngx_int_t
137 ngx_http_autoindex_handler(ngx_http_request_t *r)
139 u_char *last, *filename, scale;
140 off_t length;
141 size_t len, char_len, escape_html, allocated, root;
142 ngx_tm_t tm;
143 ngx_err_t err;
144 ngx_buf_t *b;
145 ngx_int_t rc, size;
146 ngx_str_t path;
147 ngx_dir_t dir;
148 ngx_uint_t i, level, utf8;
149 ngx_pool_t *pool;
150 ngx_time_t *tp;
151 ngx_chain_t out;
152 ngx_array_t entries;
153 ngx_http_autoindex_entry_t *entry;
154 ngx_http_autoindex_loc_conf_t *alcf;
156 static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
157 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
159 if (r->uri.data[r->uri.len - 1] != '/') {
160 return NGX_DECLINED;
163 if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
164 return NGX_DECLINED;
167 alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
169 if (!alcf->enable) {
170 return NGX_DECLINED;
173 /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
175 last = ngx_http_map_uri_to_path(r, &path, &root,
176 NGX_HTTP_AUTOINDEX_PREALLOCATE);
177 if (last == NULL) {
178 return NGX_HTTP_INTERNAL_SERVER_ERROR;
181 allocated = path.len;
182 path.len = last - path.data;
183 if (path.len > 1) {
184 path.len--;
186 path.data[path.len] = '\0';
188 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
189 "http autoindex: \"%s\"", path.data);
191 if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
192 err = ngx_errno;
194 if (err == NGX_ENOENT
195 || err == NGX_ENOTDIR
196 || err == NGX_ENAMETOOLONG)
198 level = NGX_LOG_ERR;
199 rc = NGX_HTTP_NOT_FOUND;
201 } else if (err == NGX_EACCES) {
202 level = NGX_LOG_ERR;
203 rc = NGX_HTTP_FORBIDDEN;
205 } else {
206 level = NGX_LOG_CRIT;
207 rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
210 ngx_log_error(level, r->connection->log, err,
211 ngx_open_dir_n " \"%s\" failed", path.data);
213 return rc;
216 #if (NGX_SUPPRESS_WARN)
218 /* MSVC thinks 'entries' may be used without having been initialized */
219 ngx_memzero(&entries, sizeof(ngx_array_t));
221 #endif
223 /* TODO: pool should be temporary pool */
224 pool = r->pool;
226 if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
227 != NGX_OK)
229 return ngx_http_autoindex_error(r, &dir, &path);
232 r->headers_out.status = NGX_HTTP_OK;
233 r->headers_out.content_type_len = sizeof("text/html") - 1;
234 ngx_str_set(&r->headers_out.content_type, "text/html");
236 rc = ngx_http_send_header(r);
238 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
239 if (ngx_close_dir(&dir) == NGX_ERROR) {
240 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
241 ngx_close_dir_n " \"%V\" failed", &path);
244 return rc;
247 filename = path.data;
248 filename[path.len] = '/';
250 if (r->headers_out.charset.len == 5
251 && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
252 == 0)
254 utf8 = 1;
256 } else {
257 utf8 = 0;
260 for ( ;; ) {
261 ngx_set_errno(0);
263 if (ngx_read_dir(&dir) == NGX_ERROR) {
264 err = ngx_errno;
266 if (err != NGX_ENOMOREFILES) {
267 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
268 ngx_read_dir_n " \"%V\" failed", &path);
269 return ngx_http_autoindex_error(r, &dir, &path);
272 break;
275 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
276 "http autoindex file: \"%s\"", ngx_de_name(&dir));
278 len = ngx_de_namelen(&dir);
280 if (ngx_de_name(&dir)[0] == '.') {
281 continue;
284 if (!dir.valid_info) {
286 /* 1 byte for '/' and 1 byte for terminating '\0' */
288 if (path.len + 1 + len + 1 > allocated) {
289 allocated = path.len + 1 + len + 1
290 + NGX_HTTP_AUTOINDEX_PREALLOCATE;
292 filename = ngx_pnalloc(pool, allocated);
293 if (filename == NULL) {
294 return ngx_http_autoindex_error(r, &dir, &path);
297 last = ngx_cpystrn(filename, path.data, path.len + 1);
298 *last++ = '/';
301 ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
303 if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
304 err = ngx_errno;
306 if (err != NGX_ENOENT) {
307 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
308 ngx_de_info_n " \"%s\" failed", filename);
310 if (err == NGX_EACCES) {
311 continue;
314 return ngx_http_autoindex_error(r, &dir, &path);
317 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
318 ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
319 ngx_de_link_info_n " \"%s\" failed",
320 filename);
321 return ngx_http_autoindex_error(r, &dir, &path);
326 entry = ngx_array_push(&entries);
327 if (entry == NULL) {
328 return ngx_http_autoindex_error(r, &dir, &path);
331 entry->name.len = len;
333 entry->name.data = ngx_pnalloc(pool, len + 1);
334 if (entry->name.data == NULL) {
335 return ngx_http_autoindex_error(r, &dir, &path);
338 ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
340 entry->escape = 2 * ngx_escape_uri(NULL, ngx_de_name(&dir), len,
341 NGX_ESCAPE_URI_COMPONENT);
343 entry->escape_html = ngx_escape_html(NULL, entry->name.data,
344 entry->name.len);
346 if (utf8) {
347 entry->utf_len = ngx_utf8_length(entry->name.data, entry->name.len);
348 } else {
349 entry->utf_len = len;
352 entry->dir = ngx_de_is_dir(&dir);
353 entry->mtime = ngx_de_mtime(&dir);
354 entry->size = ngx_de_size(&dir);
357 if (ngx_close_dir(&dir) == NGX_ERROR) {
358 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
359 ngx_close_dir_n " \"%s\" failed", &path);
362 escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
364 len = sizeof(title) - 1
365 + r->uri.len + escape_html
366 + sizeof(header) - 1
367 + r->uri.len + escape_html
368 + sizeof("</h1>") - 1
369 + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
370 + sizeof("</pre><hr>") - 1
371 + sizeof(tail) - 1;
373 entry = entries.elts;
374 for (i = 0; i < entries.nelts; i++) {
375 len += sizeof("<a href=\"") - 1
376 + entry[i].name.len + entry[i].escape
377 + 1 /* 1 is for "/" */
378 + sizeof("\">") - 1
379 + entry[i].name.len - entry[i].utf_len
380 + entry[i].escape_html
381 + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
382 + sizeof("</a>") - 1
383 + sizeof(" 28-Sep-1970 12:00 ") - 1
384 + 20 /* the file size */
385 + 2;
388 b = ngx_create_temp_buf(r->pool, len);
389 if (b == NULL) {
390 return NGX_HTTP_INTERNAL_SERVER_ERROR;
393 if (entries.nelts > 1) {
394 ngx_qsort(entry, (size_t) entries.nelts,
395 sizeof(ngx_http_autoindex_entry_t),
396 ngx_http_autoindex_cmp_entries);
399 b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
401 if (escape_html) {
402 b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
403 b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
404 b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
406 } else {
407 b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
408 b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
409 b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
412 b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
414 b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
415 sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
417 tp = ngx_timeofday();
419 for (i = 0; i < entries.nelts; i++) {
420 b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
422 if (entry[i].escape) {
423 ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
424 NGX_ESCAPE_URI_COMPONENT);
426 b->last += entry[i].name.len + entry[i].escape;
428 } else {
429 b->last = ngx_cpymem(b->last, entry[i].name.data,
430 entry[i].name.len);
433 if (entry[i].dir) {
434 *b->last++ = '/';
437 *b->last++ = '"';
438 *b->last++ = '>';
440 len = entry[i].utf_len;
442 if (entry[i].name.len != len) {
443 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
444 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
446 } else {
447 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
450 last = b->last;
451 b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
452 char_len, entry[i].name.len + 1);
454 if (entry[i].escape_html) {
455 b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
456 b->last - last);
459 last = b->last;
461 } else {
462 if (entry[i].escape_html) {
463 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
464 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
466 } else {
467 char_len = len;
470 b->last = (u_char *) ngx_escape_html(b->last,
471 entry[i].name.data, char_len);
472 last = b->last;
474 } else {
475 b->last = ngx_cpystrn(b->last, entry[i].name.data,
476 NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
477 last = b->last - 3;
481 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
482 b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
484 } else {
485 if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
486 *b->last++ = '/';
487 len++;
490 b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
491 ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
492 b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
495 *b->last++ = ' ';
497 ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
499 b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
500 tm.ngx_tm_mday,
501 months[tm.ngx_tm_mon - 1],
502 tm.ngx_tm_year,
503 tm.ngx_tm_hour,
504 tm.ngx_tm_min);
506 if (alcf->exact_size) {
507 if (entry[i].dir) {
508 b->last = ngx_cpymem(b->last, " -",
509 sizeof(" -") - 1);
510 } else {
511 b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
514 } else {
515 if (entry[i].dir) {
516 b->last = ngx_cpymem(b->last, " -",
517 sizeof(" -") - 1);
519 } else {
520 length = entry[i].size;
522 if (length > 1024 * 1024 * 1024 - 1) {
523 size = (ngx_int_t) (length / (1024 * 1024 * 1024));
524 if ((length % (1024 * 1024 * 1024))
525 > (1024 * 1024 * 1024 / 2 - 1))
527 size++;
529 scale = 'G';
531 } else if (length > 1024 * 1024 - 1) {
532 size = (ngx_int_t) (length / (1024 * 1024));
533 if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
534 size++;
536 scale = 'M';
538 } else if (length > 9999) {
539 size = (ngx_int_t) (length / 1024);
540 if (length % 1024 > 511) {
541 size++;
543 scale = 'K';
545 } else {
546 size = (ngx_int_t) length;
547 scale = '\0';
550 if (scale) {
551 b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
553 } else {
554 b->last = ngx_sprintf(b->last, " %6i", size);
559 *b->last++ = CR;
560 *b->last++ = LF;
563 /* TODO: free temporary pool */
565 b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
567 b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
569 if (r == r->main) {
570 b->last_buf = 1;
573 b->last_in_chain = 1;
575 out.buf = b;
576 out.next = NULL;
578 return ngx_http_output_filter(r, &out);
582 static int ngx_libc_cdecl
583 ngx_http_autoindex_cmp_entries(const void *one, const void *two)
585 ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
586 ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
588 if (first->dir && !second->dir) {
589 /* move the directories to the start */
590 return -1;
593 if (!first->dir && second->dir) {
594 /* move the directories to the start */
595 return 1;
598 return (int) ngx_strcmp(first->name.data, second->name.data);
602 #if 0
604 static ngx_buf_t *
605 ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
607 ngx_chain_t *cl;
609 if (ctx->buf) {
611 if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
612 return ctx->buf;
615 ctx->size += ctx->buf->last - ctx->buf->pos;
618 ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
619 if (ctx->buf == NULL) {
620 return NULL;
623 cl = ngx_alloc_chain_link(ctx->pool);
624 if (cl == NULL) {
625 return NULL;
628 cl->buf = ctx->buf;
629 cl->next = NULL;
631 *ctx->last_out = cl;
632 ctx->last_out = &cl->next;
634 return ctx->buf;
637 #endif
640 static ngx_int_t
641 ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
643 if (ngx_close_dir(dir) == NGX_ERROR) {
644 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
645 ngx_close_dir_n " \"%V\" failed", name);
648 return NGX_HTTP_INTERNAL_SERVER_ERROR;
652 static void *
653 ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
655 ngx_http_autoindex_loc_conf_t *conf;
657 conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
658 if (conf == NULL) {
659 return NULL;
662 conf->enable = NGX_CONF_UNSET;
663 conf->localtime = NGX_CONF_UNSET;
664 conf->exact_size = NGX_CONF_UNSET;
666 return conf;
670 static char *
671 ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
673 ngx_http_autoindex_loc_conf_t *prev = parent;
674 ngx_http_autoindex_loc_conf_t *conf = child;
676 ngx_conf_merge_value(conf->enable, prev->enable, 0);
677 ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
678 ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
680 return NGX_CONF_OK;
684 static ngx_int_t
685 ngx_http_autoindex_init(ngx_conf_t *cf)
687 ngx_http_handler_pt *h;
688 ngx_http_core_main_conf_t *cmcf;
690 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
692 h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
693 if (h == NULL) {
694 return NGX_ERROR;
697 *h = ngx_http_autoindex_handler;
699 return NGX_OK;