3 * Copyright (C) Igor Sysoev
7 #include <ngx_config.h>
19 ngx_chain_t
**last_out
;
20 } ngx_http_autoindex_ctx_t
;
35 } ngx_http_autoindex_entry_t
;
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
,
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
),
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
),
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
),
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
= {
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
[] =
120 "<head><title>Index of "
124 static u_char header
[] =
125 "</title></head>" CRLF
126 "<body bgcolor=\"white\">" CRLF
130 static u_char tail
[] =
137 ngx_http_autoindex_handler(ngx_http_request_t
*r
)
139 u_char
*last
, *filename
, scale
;
141 size_t len
, char_len
, escape_html
, allocated
, root
;
148 ngx_uint_t i
, level
, utf8
;
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] != '/') {
163 if (!(r
->method
& (NGX_HTTP_GET
|NGX_HTTP_HEAD
))) {
167 alcf
= ngx_http_get_module_loc_conf(r
, ngx_http_autoindex_module
);
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
);
178 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
181 allocated
= path
.len
;
182 path
.len
= last
- path
.data
;
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
) {
194 if (err
== NGX_ENOENT
195 || err
== NGX_ENOTDIR
196 || err
== NGX_ENAMETOOLONG
)
199 rc
= NGX_HTTP_NOT_FOUND
;
201 } else if (err
== NGX_EACCES
) {
203 rc
= NGX_HTTP_FORBIDDEN
;
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
);
216 #if (NGX_SUPPRESS_WARN)
218 /* MSVC thinks 'entries' may be used without having been initialized */
219 ngx_memzero(&entries
, sizeof(ngx_array_t
));
223 /* TODO: pool should be temporary pool */
226 if (ngx_array_init(&entries
, pool
, 40, sizeof(ngx_http_autoindex_entry_t
))
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
);
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)
263 if (ngx_read_dir(&dir
) == NGX_ERROR
) {
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
);
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] == '.') {
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);
301 ngx_cpystrn(last
, ngx_de_name(&dir
), len
+ 1);
303 if (ngx_de_info(filename
, &dir
) == NGX_FILE_ERROR
) {
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
) {
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",
321 return ngx_http_autoindex_error(r
, &dir
, &path
);
326 entry
= ngx_array_push(&entries
);
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
,
347 entry
->utf_len
= ngx_utf8_length(entry
->name
.data
, entry
->name
.len
);
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
367 + r
->uri
.len
+ escape_html
368 + sizeof("</h1>") - 1
369 + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF
) - 1
370 + sizeof("</pre><hr>") - 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 "/" */
379 + entry
[i
].name
.len
- entry
[i
].utf_len
380 + entry
[i
].escape_html
381 + NGX_HTTP_AUTOINDEX_NAME_LEN
+ sizeof(">") - 2
383 + sizeof(" 28-Sep-1970 12:00 ") - 1
384 + 20 /* the file size */
388 b
= ngx_create_temp_buf(r
->pool
, len
);
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);
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
);
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
;
429 b
->last
= ngx_cpymem(b
->last
, entry
[i
].name
.data
,
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;
447 char_len
= NGX_HTTP_AUTOINDEX_NAME_LEN
+ 1;
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
,
462 if (entry
[i
].escape_html
) {
463 if (len
> NGX_HTTP_AUTOINDEX_NAME_LEN
) {
464 char_len
= NGX_HTTP_AUTOINDEX_NAME_LEN
- 3;
470 b
->last
= (u_char
*) ngx_escape_html(b
->last
,
471 entry
[i
].name
.data
, char_len
);
475 b
->last
= ngx_cpystrn(b
->last
, entry
[i
].name
.data
,
476 NGX_HTTP_AUTOINDEX_NAME_LEN
+ 1);
481 if (len
> NGX_HTTP_AUTOINDEX_NAME_LEN
) {
482 b
->last
= ngx_cpymem(last
, "..></a>", sizeof("..></a>") - 1);
485 if (entry
[i
].dir
&& NGX_HTTP_AUTOINDEX_NAME_LEN
- len
> 0) {
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
;
497 ngx_gmtime(entry
[i
].mtime
+ tp
->gmtoff
* 60 * alcf
->localtime
, &tm
);
499 b
->last
= ngx_sprintf(b
->last
, "%02d-%s-%d %02d:%02d ",
501 months
[tm
.ngx_tm_mon
- 1],
506 if (alcf
->exact_size
) {
508 b
->last
= ngx_cpymem(b
->last
, " -",
511 b
->last
= ngx_sprintf(b
->last
, "%19O", entry
[i
].size
);
516 b
->last
= ngx_cpymem(b
->last
, " -",
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))
531 } else if (length
> 1024 * 1024 - 1) {
532 size
= (ngx_int_t
) (length
/ (1024 * 1024));
533 if ((length
% (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
538 } else if (length
> 9999) {
539 size
= (ngx_int_t
) (length
/ 1024);
540 if (length
% 1024 > 511) {
546 size
= (ngx_int_t
) length
;
551 b
->last
= ngx_sprintf(b
->last
, "%6i%c", size
, scale
);
554 b
->last
= ngx_sprintf(b
->last
, " %6i", size
);
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);
573 b
->last_in_chain
= 1;
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 */
593 if (!first
->dir
&& second
->dir
) {
594 /* move the directories to the start */
598 return (int) ngx_strcmp(first
->name
.data
, second
->name
.data
);
605 ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t
*ctx
, size_t size
)
611 if ((size_t) (ctx
->buf
->end
- ctx
->buf
->last
) >= size
) {
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
) {
623 cl
= ngx_alloc_chain_link(ctx
->pool
);
632 ctx
->last_out
= &cl
->next
;
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
;
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
));
662 conf
->enable
= NGX_CONF_UNSET
;
663 conf
->localtime
= NGX_CONF_UNSET
;
664 conf
->exact_size
= NGX_CONF_UNSET
;
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);
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
);
697 *h
= ngx_http_autoindex_handler
;