3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
8 #include <ngx_config.h>
20 ngx_chain_t
**last_out
;
21 } ngx_http_autoindex_ctx_t
;
36 } ngx_http_autoindex_entry_t
;
42 ngx_flag_t exact_size
;
43 } ngx_http_autoindex_loc_conf_t
;
46 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
48 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
51 static int ngx_libc_cdecl
ngx_http_autoindex_cmp_entries(const void *one
,
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
);
61 static ngx_command_t ngx_http_autoindex_commands
[] = {
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
),
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
),
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
),
88 static ngx_http_module_t ngx_http_autoindex_module_ctx
= {
89 NULL
, /* preconfiguration */
90 ngx_http_autoindex_init
, /* postconfiguration */
92 NULL
, /* create main configuration */
93 NULL
, /* init main configuration */
95 NULL
, /* create server configuration */
96 NULL
, /* merge server configuration */
98 ngx_http_autoindex_create_loc_conf
, /* create location configuration */
99 ngx_http_autoindex_merge_loc_conf
/* merge location configuration */
103 ngx_module_t ngx_http_autoindex_module
= {
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
119 static u_char title
[] =
121 "<head><title>Index of "
125 static u_char header
[] =
126 "</title></head>" CRLF
127 "<body bgcolor=\"white\">" CRLF
131 static u_char tail
[] =
138 ngx_http_autoindex_handler(ngx_http_request_t
*r
)
140 u_char
*last
, *filename
, scale
;
142 size_t len
, char_len
, escape_html
, allocated
, root
;
149 ngx_uint_t i
, level
, utf8
;
154 ngx_http_autoindex_entry_t
*entry
;
155 ngx_http_autoindex_loc_conf_t
*alcf
;
157 static char *months
[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
158 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
160 if (r
->uri
.data
[r
->uri
.len
- 1] != '/') {
164 if (!(r
->method
& (NGX_HTTP_GET
|NGX_HTTP_HEAD
))) {
168 alcf
= ngx_http_get_module_loc_conf(r
, ngx_http_autoindex_module
);
174 /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
176 last
= ngx_http_map_uri_to_path(r
, &path
, &root
,
177 NGX_HTTP_AUTOINDEX_PREALLOCATE
);
179 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
182 allocated
= path
.len
;
183 path
.len
= last
- path
.data
;
187 path
.data
[path
.len
] = '\0';
189 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
190 "http autoindex: \"%s\"", path
.data
);
192 if (ngx_open_dir(&path
, &dir
) == NGX_ERROR
) {
195 if (err
== NGX_ENOENT
196 || err
== NGX_ENOTDIR
197 || err
== NGX_ENAMETOOLONG
)
200 rc
= NGX_HTTP_NOT_FOUND
;
202 } else if (err
== NGX_EACCES
) {
204 rc
= NGX_HTTP_FORBIDDEN
;
207 level
= NGX_LOG_CRIT
;
208 rc
= NGX_HTTP_INTERNAL_SERVER_ERROR
;
211 ngx_log_error(level
, r
->connection
->log
, err
,
212 ngx_open_dir_n
" \"%s\" failed", path
.data
);
217 #if (NGX_SUPPRESS_WARN)
219 /* MSVC thinks 'entries' may be used without having been initialized */
220 ngx_memzero(&entries
, sizeof(ngx_array_t
));
224 /* TODO: pool should be temporary pool */
227 if (ngx_array_init(&entries
, pool
, 40, sizeof(ngx_http_autoindex_entry_t
))
230 return ngx_http_autoindex_error(r
, &dir
, &path
);
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");
237 rc
= ngx_http_send_header(r
);
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
);
248 filename
= path
.data
;
249 filename
[path
.len
] = '/';
251 if (r
->headers_out
.charset
.len
== 5
252 && ngx_strncasecmp(r
->headers_out
.charset
.data
, (u_char
*) "utf-8", 5)
264 if (ngx_read_dir(&dir
) == NGX_ERROR
) {
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
);
276 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
277 "http autoindex file: \"%s\"", ngx_de_name(&dir
));
279 len
= ngx_de_namelen(&dir
);
281 if (ngx_de_name(&dir
)[0] == '.') {
285 if (!dir
.valid_info
) {
287 /* 1 byte for '/' and 1 byte for terminating '\0' */
289 if (path
.len
+ 1 + len
+ 1 > allocated
) {
290 allocated
= path
.len
+ 1 + len
+ 1
291 + NGX_HTTP_AUTOINDEX_PREALLOCATE
;
293 filename
= ngx_pnalloc(pool
, allocated
);
294 if (filename
== NULL
) {
295 return ngx_http_autoindex_error(r
, &dir
, &path
);
298 last
= ngx_cpystrn(filename
, path
.data
, path
.len
+ 1);
302 ngx_cpystrn(last
, ngx_de_name(&dir
), len
+ 1);
304 if (ngx_de_info(filename
, &dir
) == NGX_FILE_ERROR
) {
307 if (err
!= NGX_ENOENT
) {
308 ngx_log_error(NGX_LOG_CRIT
, r
->connection
->log
, err
,
309 ngx_de_info_n
" \"%s\" failed", filename
);
311 if (err
== NGX_EACCES
) {
315 return ngx_http_autoindex_error(r
, &dir
, &path
);
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",
322 return ngx_http_autoindex_error(r
, &dir
, &path
);
327 entry
= ngx_array_push(&entries
);
329 return ngx_http_autoindex_error(r
, &dir
, &path
);
332 entry
->name
.len
= len
;
334 entry
->name
.data
= ngx_pnalloc(pool
, len
+ 1);
335 if (entry
->name
.data
== NULL
) {
336 return ngx_http_autoindex_error(r
, &dir
, &path
);
339 ngx_cpystrn(entry
->name
.data
, ngx_de_name(&dir
), len
+ 1);
341 entry
->escape
= 2 * ngx_escape_uri(NULL
, ngx_de_name(&dir
), len
,
342 NGX_ESCAPE_URI_COMPONENT
);
344 entry
->escape_html
= ngx_escape_html(NULL
, entry
->name
.data
,
348 entry
->utf_len
= ngx_utf8_length(entry
->name
.data
, entry
->name
.len
);
350 entry
->utf_len
= len
;
353 entry
->dir
= ngx_de_is_dir(&dir
);
354 entry
->mtime
= ngx_de_mtime(&dir
);
355 entry
->size
= ngx_de_size(&dir
);
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
);
363 escape_html
= ngx_escape_html(NULL
, r
->uri
.data
, r
->uri
.len
);
365 len
= sizeof(title
) - 1
366 + r
->uri
.len
+ escape_html
368 + r
->uri
.len
+ escape_html
369 + sizeof("</h1>") - 1
370 + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF
) - 1
371 + sizeof("</pre><hr>") - 1
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 "/" */
380 + entry
[i
].name
.len
- entry
[i
].utf_len
381 + entry
[i
].escape_html
382 + NGX_HTTP_AUTOINDEX_NAME_LEN
+ sizeof(">") - 2
384 + sizeof(" 28-Sep-1970 12:00 ") - 1
385 + 20 /* the file size */
389 b
= ngx_create_temp_buf(r
->pool
, len
);
391 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
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
);
400 b
->last
= ngx_cpymem(b
->last
, title
, sizeof(title
) - 1);
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
);
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
);
413 b
->last
= ngx_cpymem(b
->last
, "</h1>", sizeof("</h1>") - 1);
415 b
->last
= ngx_cpymem(b
->last
, "<hr><pre><a href=\"../\">../</a>" CRLF
,
416 sizeof("<hr><pre><a href=\"../\">../</a>" CRLF
) - 1);
418 tp
= ngx_timeofday();
420 for (i
= 0; i
< entries
.nelts
; i
++) {
421 b
->last
= ngx_cpymem(b
->last
, "<a href=\"", sizeof("<a href=\"") - 1);
423 if (entry
[i
].escape
) {
424 ngx_escape_uri(b
->last
, entry
[i
].name
.data
, entry
[i
].name
.len
,
425 NGX_ESCAPE_URI_COMPONENT
);
427 b
->last
+= entry
[i
].name
.len
+ entry
[i
].escape
;
430 b
->last
= ngx_cpymem(b
->last
, entry
[i
].name
.data
,
441 len
= entry
[i
].utf_len
;
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;
448 char_len
= NGX_HTTP_AUTOINDEX_NAME_LEN
+ 1;
452 b
->last
= ngx_utf8_cpystrn(b
->last
, entry
[i
].name
.data
,
453 char_len
, entry
[i
].name
.len
+ 1);
455 if (entry
[i
].escape_html
) {
456 b
->last
= (u_char
*) ngx_escape_html(last
, entry
[i
].name
.data
,
463 if (entry
[i
].escape_html
) {
464 if (len
> NGX_HTTP_AUTOINDEX_NAME_LEN
) {
465 char_len
= NGX_HTTP_AUTOINDEX_NAME_LEN
- 3;
471 b
->last
= (u_char
*) ngx_escape_html(b
->last
,
472 entry
[i
].name
.data
, char_len
);
476 b
->last
= ngx_cpystrn(b
->last
, entry
[i
].name
.data
,
477 NGX_HTTP_AUTOINDEX_NAME_LEN
+ 1);
482 if (len
> NGX_HTTP_AUTOINDEX_NAME_LEN
) {
483 b
->last
= ngx_cpymem(last
, "..></a>", sizeof("..></a>") - 1);
486 if (entry
[i
].dir
&& NGX_HTTP_AUTOINDEX_NAME_LEN
- len
> 0) {
491 b
->last
= ngx_cpymem(b
->last
, "</a>", sizeof("</a>") - 1);
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
;
501 ngx_gmtime(entry
[i
].mtime
+ tp
->gmtoff
* 60 * alcf
->localtime
, &tm
);
503 b
->last
= ngx_sprintf(b
->last
, "%02d-%s-%d %02d:%02d ",
505 months
[tm
.ngx_tm_mon
- 1],
510 if (alcf
->exact_size
) {
512 b
->last
= ngx_cpymem(b
->last
, " -",
515 b
->last
= ngx_sprintf(b
->last
, "%19O", entry
[i
].size
);
520 b
->last
= ngx_cpymem(b
->last
, " -",
524 length
= entry
[i
].size
;
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))
535 } else if (length
> 1024 * 1024 - 1) {
536 size
= (ngx_int_t
) (length
/ (1024 * 1024));
537 if ((length
% (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
542 } else if (length
> 9999) {
543 size
= (ngx_int_t
) (length
/ 1024);
544 if (length
% 1024 > 511) {
550 size
= (ngx_int_t
) length
;
555 b
->last
= ngx_sprintf(b
->last
, "%6i%c", size
, scale
);
558 b
->last
= ngx_sprintf(b
->last
, " %6i", size
);
567 /* TODO: free temporary pool */
569 b
->last
= ngx_cpymem(b
->last
, "</pre><hr>", sizeof("</pre><hr>") - 1);
571 b
->last
= ngx_cpymem(b
->last
, tail
, sizeof(tail
) - 1);
577 b
->last_in_chain
= 1;
582 return ngx_http_output_filter(r
, &out
);
586 static int ngx_libc_cdecl
587 ngx_http_autoindex_cmp_entries(const void *one
, const void *two
)
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
;
592 if (first
->dir
&& !second
->dir
) {
593 /* move the directories to the start */
597 if (!first
->dir
&& second
->dir
) {
598 /* move the directories to the start */
602 return (int) ngx_strcmp(first
->name
.data
, second
->name
.data
);
609 ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t
*ctx
, size_t size
)
615 if ((size_t) (ctx
->buf
->end
- ctx
->buf
->last
) >= size
) {
619 ctx
->size
+= ctx
->buf
->last
- ctx
->buf
->pos
;
622 ctx
->buf
= ngx_create_temp_buf(ctx
->pool
, ctx
->alloc_size
);
623 if (ctx
->buf
== NULL
) {
627 cl
= ngx_alloc_chain_link(ctx
->pool
);
636 ctx
->last_out
= &cl
->next
;
645 ngx_http_autoindex_error(ngx_http_request_t
*r
, ngx_dir_t
*dir
, ngx_str_t
*name
)
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
);
652 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
657 ngx_http_autoindex_create_loc_conf(ngx_conf_t
*cf
)
659 ngx_http_autoindex_loc_conf_t
*conf
;
661 conf
= ngx_palloc(cf
->pool
, sizeof(ngx_http_autoindex_loc_conf_t
));
666 conf
->enable
= NGX_CONF_UNSET
;
667 conf
->localtime
= NGX_CONF_UNSET
;
668 conf
->exact_size
= NGX_CONF_UNSET
;
675 ngx_http_autoindex_merge_loc_conf(ngx_conf_t
*cf
, void *parent
, void *child
)
677 ngx_http_autoindex_loc_conf_t
*prev
= parent
;
678 ngx_http_autoindex_loc_conf_t
*conf
= child
;
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);
689 ngx_http_autoindex_init(ngx_conf_t
*cf
)
691 ngx_http_handler_pt
*h
;
692 ngx_http_core_main_conf_t
*cmcf
;
694 cmcf
= ngx_http_conf_get_module_main_conf(cf
, ngx_http_core_module
);
696 h
= ngx_array_push(&cmcf
->phases
[NGX_HTTP_CONTENT_PHASE
].handlers
);
701 *h
= ngx_http_autoindex_handler
;