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 unsigned file:1;
34
35 time_t mtime;
36 off_t size;
37 } ngx_http_autoindex_entry_t;
38
39
40 typedef struct {
41 ngx_flag_t enable;
42 ngx_uint_t format;
43 ngx_flag_t localtime;
44 ngx_flag_t exact_size;
45 } ngx_http_autoindex_loc_conf_t;
46
47
48 #define NGX_HTTP_AUTOINDEX_HTML 0
49 #define NGX_HTTP_AUTOINDEX_JSON 1
50 #define NGX_HTTP_AUTOINDEX_JSONP 2
51 #define NGX_HTTP_AUTOINDEX_XML 3
52
53 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
54
55 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
56
57
58 static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r,
59 ngx_array_t *entries);
60 static ngx_buf_t *ngx_http_autoindex_json(ngx_http_request_t *r,
61 ngx_array_t *entries, ngx_str_t *callback);
62 static ngx_int_t ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r,
63 ngx_str_t *callback);
64 static ngx_buf_t *ngx_http_autoindex_xml(ngx_http_request_t *r,
65 ngx_array_t *entries);
66
67 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
68 const void *two);
69 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
70 ngx_dir_t *dir, ngx_str_t *name);
71
72 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
73 static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
74 static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
75 void *parent, void *child);
76
77
78 static ngx_conf_enum_t ngx_http_autoindex_format[] = {
79 { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
80 { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
81 { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
82 { ngx_string("xml"), NGX_HTTP_AUTOINDEX_XML },
83 { ngx_null_string, 0 }
84 };
85
86
87 static ngx_command_t ngx_http_autoindex_commands[] = {
88
89 { ngx_string("autoindex"),
90 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
91 ngx_conf_set_flag_slot,
92 NGX_HTTP_LOC_CONF_OFFSET,
93 offsetof(ngx_http_autoindex_loc_conf_t, enable),
94 NULL },
95
96 { ngx_string("autoindex_format"),
97 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
98 ngx_conf_set_enum_slot,
99 NGX_HTTP_LOC_CONF_OFFSET,
100 offsetof(ngx_http_autoindex_loc_conf_t, format),
101 &ngx_http_autoindex_format },
102
103 { ngx_string("autoindex_localtime"),
104 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
105 ngx_conf_set_flag_slot,
106 NGX_HTTP_LOC_CONF_OFFSET,
107 offsetof(ngx_http_autoindex_loc_conf_t, localtime),
108 NULL },
109
110 { ngx_string("autoindex_exact_size"),
111 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
112 ngx_conf_set_flag_slot,
113 NGX_HTTP_LOC_CONF_OFFSET,
114 offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
115 NULL },
116
117 ngx_null_command
118 };
119
120
121 static ngx_http_module_t ngx_http_autoindex_module_ctx = {
122 NULL, /* preconfiguration */
123 ngx_http_autoindex_init, /* postconfiguration */
124
125 NULL, /* create main configuration */
126 NULL, /* init main configuration */
127
128 NULL, /* create server configuration */
129 NULL, /* merge server configuration */
130
131 ngx_http_autoindex_create_loc_conf, /* create location configuration */
132 ngx_http_autoindex_merge_loc_conf /* merge location configuration */
133 };
134
135
136 ngx_module_t ngx_http_autoindex_module = {
137 NGX_MODULE_V1,
138 &ngx_http_autoindex_module_ctx, /* module context */
139 ngx_http_autoindex_commands, /* module directives */
140 NGX_HTTP_MODULE, /* module type */
141 NULL, /* init master */
142 NULL, /* init module */
143 NULL, /* init process */
144 NULL, /* init thread */
145 NULL, /* exit thread */
146 NULL, /* exit process */
147 NULL, /* exit master */
148 NGX_MODULE_V1_PADDING
149 };
150
151
152 static ngx_int_t
ngx_http_autoindex_handler(ngx_http_request_t * r)153 ngx_http_autoindex_handler(ngx_http_request_t *r)
154 {
155 u_char *last, *filename;
156 size_t len, allocated, root;
157 ngx_err_t err;
158 ngx_buf_t *b;
159 ngx_int_t rc;
160 ngx_str_t path, callback;
161 ngx_dir_t dir;
162 ngx_uint_t level, format;
163 ngx_pool_t *pool;
164 ngx_chain_t out;
165 ngx_array_t entries;
166 ngx_http_autoindex_entry_t *entry;
167 ngx_http_autoindex_loc_conf_t *alcf;
168
169 if (r->uri.data[r->uri.len - 1] != '/') {
170 return NGX_DECLINED;
171 }
172
173 if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
174 return NGX_DECLINED;
175 }
176
177 alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
178
179 if (!alcf->enable) {
180 return NGX_DECLINED;
181 }
182
183 rc = ngx_http_discard_request_body(r);
184
185 if (rc != NGX_OK) {
186 return rc;
187 }
188
189 last = ngx_http_map_uri_to_path(r, &path, &root,
190 NGX_HTTP_AUTOINDEX_PREALLOCATE);
191 if (last == NULL) {
192 return NGX_HTTP_INTERNAL_SERVER_ERROR;
193 }
194
195 allocated = path.len;
196 path.len = last - path.data;
197 if (path.len > 1) {
198 path.len--;
199 }
200 path.data[path.len] = '\0';
201
202 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
203 "http autoindex: \"%s\"", path.data);
204
205 format = alcf->format;
206
207 if (format == NGX_HTTP_AUTOINDEX_JSONP) {
208 if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
209 return NGX_HTTP_BAD_REQUEST;
210 }
211
212 if (callback.len == 0) {
213 format = NGX_HTTP_AUTOINDEX_JSON;
214 }
215 }
216
217 if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
218 err = ngx_errno;
219
220 if (err == NGX_ENOENT
221 || err == NGX_ENOTDIR
222 || err == NGX_ENAMETOOLONG)
223 {
224 level = NGX_LOG_ERR;
225 rc = NGX_HTTP_NOT_FOUND;
226
227 } else if (err == NGX_EACCES) {
228 level = NGX_LOG_ERR;
229 rc = NGX_HTTP_FORBIDDEN;
230
231 } else {
232 level = NGX_LOG_CRIT;
233 rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
234 }
235
236 ngx_log_error(level, r->connection->log, err,
237 ngx_open_dir_n " \"%s\" failed", path.data);
238
239 return rc;
240 }
241
242 #if (NGX_SUPPRESS_WARN)
243
244 /* MSVC thinks 'entries' may be used without having been initialized */
245 ngx_memzero(&entries, sizeof(ngx_array_t));
246
247 #endif
248
249 /* TODO: pool should be temporary pool */
250 pool = r->pool;
251
252 if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
253 != NGX_OK)
254 {
255 return ngx_http_autoindex_error(r, &dir, &path);
256 }
257
258 r->headers_out.status = NGX_HTTP_OK;
259
260 switch (format) {
261
262 case NGX_HTTP_AUTOINDEX_JSON:
263 ngx_str_set(&r->headers_out.content_type, "application/json");
264 break;
265
266 case NGX_HTTP_AUTOINDEX_JSONP:
267 ngx_str_set(&r->headers_out.content_type, "application/javascript");
268 break;
269
270 case NGX_HTTP_AUTOINDEX_XML:
271 ngx_str_set(&r->headers_out.content_type, "text/xml");
272 ngx_str_set(&r->headers_out.charset, "utf-8");
273 break;
274
275 default: /* NGX_HTTP_AUTOINDEX_HTML */
276 ngx_str_set(&r->headers_out.content_type, "text/html");
277 break;
278 }
279
280 r->headers_out.content_type_len = r->headers_out.content_type.len;
281 r->headers_out.content_type_lowcase = NULL;
282
283 rc = ngx_http_send_header(r);
284
285 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
286 if (ngx_close_dir(&dir) == NGX_ERROR) {
287 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
288 ngx_close_dir_n " \"%V\" failed", &path);
289 }
290
291 return rc;
292 }
293
294 filename = path.data;
295 filename[path.len] = '/';
296
297 for ( ;; ) {
298 ngx_set_errno(0);
299
300 if (ngx_read_dir(&dir) == NGX_ERROR) {
301 err = ngx_errno;
302
303 if (err != NGX_ENOMOREFILES) {
304 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
305 ngx_read_dir_n " \"%V\" failed", &path);
306 return ngx_http_autoindex_error(r, &dir, &path);
307 }
308
309 break;
310 }
311
312 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
313 "http autoindex file: \"%s\"", ngx_de_name(&dir));
314
315 len = ngx_de_namelen(&dir);
316
317 if (ngx_de_name(&dir)[0] == '.') {
318 continue;
319 }
320
321 if (!dir.valid_info) {
322
323 /* 1 byte for '/' and 1 byte for terminating '\0' */
324
325 if (path.len + 1 + len + 1 > allocated) {
326 allocated = path.len + 1 + len + 1
327 + NGX_HTTP_AUTOINDEX_PREALLOCATE;
328
329 filename = ngx_pnalloc(pool, allocated);
330 if (filename == NULL) {
331 return ngx_http_autoindex_error(r, &dir, &path);
332 }
333
334 last = ngx_cpystrn(filename, path.data, path.len + 1);
335 *last++ = '/';
336 }
337
338 ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
339
340 if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
341 err = ngx_errno;
342
343 if (err != NGX_ENOENT && err != NGX_ELOOP) {
344 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
345 ngx_de_info_n " \"%s\" failed", filename);
346
347 if (err == NGX_EACCES) {
348 continue;
349 }
350
351 return ngx_http_autoindex_error(r, &dir, &path);
352 }
353
354 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
355 ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
356 ngx_de_link_info_n " \"%s\" failed",
357 filename);
358 return ngx_http_autoindex_error(r, &dir, &path);
359 }
360 }
361 }
362
363 entry = ngx_array_push(&entries);
364 if (entry == NULL) {
365 return ngx_http_autoindex_error(r, &dir, &path);
366 }
367
368 entry->name.len = len;
369
370 entry->name.data = ngx_pnalloc(pool, len + 1);
371 if (entry->name.data == NULL) {
372 return ngx_http_autoindex_error(r, &dir, &path);
373 }
374
375 ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
376
377 entry->dir = ngx_de_is_dir(&dir);
378 entry->file = ngx_de_is_file(&dir);
379 entry->mtime = ngx_de_mtime(&dir);
380 entry->size = ngx_de_size(&dir);
381 }
382
383 if (ngx_close_dir(&dir) == NGX_ERROR) {
384 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
385 ngx_close_dir_n " \"%V\" failed", &path);
386 }
387
388 if (entries.nelts > 1) {
389 ngx_qsort(entries.elts, (size_t) entries.nelts,
390 sizeof(ngx_http_autoindex_entry_t),
391 ngx_http_autoindex_cmp_entries);
392 }
393
394 switch (format) {
395
396 case NGX_HTTP_AUTOINDEX_JSON:
397 b = ngx_http_autoindex_json(r, &entries, NULL);
398 break;
399
400 case NGX_HTTP_AUTOINDEX_JSONP:
401 b = ngx_http_autoindex_json(r, &entries, &callback);
402 break;
403
404 case NGX_HTTP_AUTOINDEX_XML:
405 b = ngx_http_autoindex_xml(r, &entries);
406 break;
407
408 default: /* NGX_HTTP_AUTOINDEX_HTML */
409 b = ngx_http_autoindex_html(r, &entries);
410 break;
411 }
412
413 if (b == NULL) {
414 return NGX_ERROR;
415 }
416
417 /* TODO: free temporary pool */
418
419 if (r == r->main) {
420 b->last_buf = 1;
421 }
422
423 b->last_in_chain = 1;
424
425 out.buf = b;
426 out.next = NULL;
427
428 return ngx_http_output_filter(r, &out);
429 }
430
431
432 static ngx_buf_t *
ngx_http_autoindex_html(ngx_http_request_t * r,ngx_array_t * entries)433 ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
434 {
435 u_char *last, scale;
436 off_t length;
437 size_t len, entry_len, char_len, escape_html;
438 ngx_tm_t tm;
439 ngx_buf_t *b;
440 ngx_int_t size;
441 ngx_uint_t i, utf8;
442 ngx_time_t *tp;
443 ngx_http_autoindex_entry_t *entry;
444 ngx_http_autoindex_loc_conf_t *alcf;
445
446 static u_char title[] =
447 "<html>" CRLF
448 "<head><title>Index of "
449 ;
450
451 static u_char header[] =
452 "</title></head>" CRLF
453 "<body>" CRLF
454 "<h1>Index of "
455 ;
456
457 static u_char tail[] =
458 "</body>" CRLF
459 "</html>" CRLF
460 ;
461
462 static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
463 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
464
465 if (r->headers_out.charset.len == 5
466 && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
467 == 0)
468 {
469 utf8 = 1;
470
471 } else {
472 utf8 = 0;
473 }
474
475 escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
476
477 len = sizeof(title) - 1
478 + r->uri.len + escape_html
479 + sizeof(header) - 1
480 + r->uri.len + escape_html
481 + sizeof("</h1>") - 1
482 + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
483 + sizeof("</pre><hr>") - 1
484 + sizeof(tail) - 1;
485
486 entry = entries->elts;
487 for (i = 0; i < entries->nelts; i++) {
488 entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
489 entry[i].name.len,
490 NGX_ESCAPE_URI_COMPONENT);
491
492 entry[i].escape_html = ngx_escape_html(NULL, entry[i].name.data,
493 entry[i].name.len);
494
495 if (utf8) {
496 entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
497 entry[i].name.len);
498 } else {
499 entry[i].utf_len = entry[i].name.len;
500 }
501
502 entry_len = sizeof("<a href=\"") - 1
503 + entry[i].name.len + entry[i].escape
504 + 1 /* 1 is for "/" */
505 + sizeof("\">") - 1
506 + entry[i].name.len - entry[i].utf_len
507 + entry[i].escape_html
508 + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof(">") - 2
509 + sizeof("</a>") - 1
510 + sizeof(" 28-Sep-1970 12:00 ") - 1
511 + 20 /* the file size */
512 + 2;
513
514 if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
515 return NULL;
516 }
517
518 len += entry_len;
519 }
520
521 b = ngx_create_temp_buf(r->pool, len);
522 if (b == NULL) {
523 return NULL;
524 }
525
526 b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
527
528 if (escape_html) {
529 b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
530 b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
531 b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
532
533 } else {
534 b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
535 b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
536 b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
537 }
538
539 b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
540
541 b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
542 sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
543
544 alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
545 tp = ngx_timeofday();
546
547 for (i = 0; i < entries->nelts; i++) {
548 b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
549
550 if (entry[i].escape) {
551 ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
552 NGX_ESCAPE_URI_COMPONENT);
553
554 b->last += entry[i].name.len + entry[i].escape;
555
556 } else {
557 b->last = ngx_cpymem(b->last, entry[i].name.data,
558 entry[i].name.len);
559 }
560
561 if (entry[i].dir) {
562 *b->last++ = '/';
563 }
564
565 *b->last++ = '"';
566 *b->last++ = '>';
567
568 len = entry[i].utf_len;
569
570 if (entry[i].name.len != len) {
571 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
572 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
573
574 } else {
575 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
576 }
577
578 last = b->last;
579 b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
580 char_len, entry[i].name.len + 1);
581
582 if (entry[i].escape_html) {
583 b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
584 b->last - last);
585 }
586
587 last = b->last;
588
589 } else {
590 if (entry[i].escape_html) {
591 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
592 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
593
594 } else {
595 char_len = len;
596 }
597
598 b->last = (u_char *) ngx_escape_html(b->last,
599 entry[i].name.data, char_len);
600 last = b->last;
601
602 } else {
603 b->last = ngx_cpystrn(b->last, entry[i].name.data,
604 NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
605 last = b->last - 3;
606 }
607 }
608
609 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
610 b->last = ngx_cpymem(last, "..></a>", sizeof("..></a>") - 1);
611
612 } else {
613 if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
614 *b->last++ = '/';
615 len++;
616 }
617
618 b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
619
620 if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
621 ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
622 b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
623 }
624 }
625
626 *b->last++ = ' ';
627
628 ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
629
630 b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
631 tm.ngx_tm_mday,
632 months[tm.ngx_tm_mon - 1],
633 tm.ngx_tm_year,
634 tm.ngx_tm_hour,
635 tm.ngx_tm_min);
636
637 if (alcf->exact_size) {
638 if (entry[i].dir) {
639 b->last = ngx_cpymem(b->last, " -",
640 sizeof(" -") - 1);
641 } else {
642 b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
643 }
644
645 } else {
646 if (entry[i].dir) {
647 b->last = ngx_cpymem(b->last, " -",
648 sizeof(" -") - 1);
649
650 } else {
651 length = entry[i].size;
652
653 if (length > 1024 * 1024 * 1024 - 1) {
654 size = (ngx_int_t) (length / (1024 * 1024 * 1024));
655 if ((length % (1024 * 1024 * 1024))
656 > (1024 * 1024 * 1024 / 2 - 1))
657 {
658 size++;
659 }
660 scale = 'G';
661
662 } else if (length > 1024 * 1024 - 1) {
663 size = (ngx_int_t) (length / (1024 * 1024));
664 if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
665 size++;
666 }
667 scale = 'M';
668
669 } else if (length > 9999) {
670 size = (ngx_int_t) (length / 1024);
671 if (length % 1024 > 511) {
672 size++;
673 }
674 scale = 'K';
675
676 } else {
677 size = (ngx_int_t) length;
678 scale = '\0';
679 }
680
681 if (scale) {
682 b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
683
684 } else {
685 b->last = ngx_sprintf(b->last, " %6i", size);
686 }
687 }
688 }
689
690 *b->last++ = CR;
691 *b->last++ = LF;
692 }
693
694 b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
695
696 b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
697
698 return b;
699 }
700
701
702 static ngx_buf_t *
ngx_http_autoindex_json(ngx_http_request_t * r,ngx_array_t * entries,ngx_str_t * callback)703 ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
704 ngx_str_t *callback)
705 {
706 size_t len, entry_len;
707 ngx_buf_t *b;
708 ngx_uint_t i;
709 ngx_http_autoindex_entry_t *entry;
710
711 len = sizeof("[" CRLF CRLF "]") - 1;
712
713 if (callback) {
714 len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
715 }
716
717 entry = entries->elts;
718
719 for (i = 0; i < entries->nelts; i++) {
720 entry[i].escape = ngx_escape_json(NULL, entry[i].name.data,
721 entry[i].name.len);
722
723 entry_len = sizeof("{ }," CRLF) - 1
724 + sizeof("\"name\":\"\"") - 1
725 + entry[i].name.len + entry[i].escape
726 + sizeof(", \"type\":\"directory\"") - 1
727 + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;
728
729 if (entry[i].file) {
730 entry_len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
731 }
732
733 if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
734 return NULL;
735 }
736
737 len += entry_len;
738 }
739
740 b = ngx_create_temp_buf(r->pool, len);
741 if (b == NULL) {
742 return NULL;
743 }
744
745 if (callback) {
746 b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
747 sizeof("/* callback */" CRLF) - 1);
748
749 b->last = ngx_cpymem(b->last, callback->data, callback->len);
750
751 *b->last++ = '(';
752 }
753
754 *b->last++ = '[';
755
756 for (i = 0; i < entries->nelts; i++) {
757 b->last = ngx_cpymem(b->last, CRLF "{ \"name\":\"",
758 sizeof(CRLF "{ \"name\":\"") - 1);
759
760 if (entry[i].escape) {
761 b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
762 entry[i].name.len);
763 } else {
764 b->last = ngx_cpymem(b->last, entry[i].name.data,
765 entry[i].name.len);
766 }
767
768 b->last = ngx_cpymem(b->last, "\", \"type\":\"",
769 sizeof("\", \"type\":\"") - 1);
770
771 if (entry[i].dir) {
772 b->last = ngx_cpymem(b->last, "directory", sizeof("directory") - 1);
773
774 } else if (entry[i].file) {
775 b->last = ngx_cpymem(b->last, "file", sizeof("file") - 1);
776
777 } else {
778 b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
779 }
780
781 b->last = ngx_cpymem(b->last, "\", \"mtime\":\"",
782 sizeof("\", \"mtime\":\"") - 1);
783
784 b->last = ngx_http_time(b->last, entry[i].mtime);
785
786 if (entry[i].file) {
787 b->last = ngx_cpymem(b->last, "\", \"size\":",
788 sizeof("\", \"size\":") - 1);
789 b->last = ngx_sprintf(b->last, "%O", entry[i].size);
790
791 } else {
792 *b->last++ = '"';
793 }
794
795 b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
796 }
797
798 if (i > 0) {
799 b->last--; /* strip last comma */
800 }
801
802 b->last = ngx_cpymem(b->last, CRLF "]", sizeof(CRLF "]") - 1);
803
804 if (callback) {
805 *b->last++ = ')'; *b->last++ = ';';
806 }
807
808 return b;
809 }
810
811
812 static ngx_int_t
ngx_http_autoindex_jsonp_callback(ngx_http_request_t * r,ngx_str_t * callback)813 ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
814 {
815 u_char *p, c, ch;
816 ngx_uint_t i;
817
818 if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
819 callback->len = 0;
820 return NGX_OK;
821 }
822
823 if (callback->len > 128) {
824 ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
825 "client sent too long callback name: \"%V\"", callback);
826 return NGX_DECLINED;
827 }
828
829 p = callback->data;
830
831 for (i = 0; i < callback->len; i++) {
832 ch = p[i];
833
834 c = (u_char) (ch | 0x20);
835 if (c >= 'a' && c <= 'z') {
836 continue;
837 }
838
839 if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
840 continue;
841 }
842
843 ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
844 "client sent invalid callback name: \"%V\"", callback);
845
846 return NGX_DECLINED;
847 }
848
849 return NGX_OK;
850 }
851
852
853 static ngx_buf_t *
ngx_http_autoindex_xml(ngx_http_request_t * r,ngx_array_t * entries)854 ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
855 {
856 size_t len, entry_len;
857 ngx_tm_t tm;
858 ngx_buf_t *b;
859 ngx_str_t type;
860 ngx_uint_t i;
861 ngx_http_autoindex_entry_t *entry;
862
863 static u_char head[] = "<?xml version=\"1.0\"?>" CRLF "<list>" CRLF;
864 static u_char tail[] = "</list>" CRLF;
865
866 len = sizeof(head) - 1 + sizeof(tail) - 1;
867
868 entry = entries->elts;
869
870 for (i = 0; i < entries->nelts; i++) {
871 entry[i].escape = ngx_escape_html(NULL, entry[i].name.data,
872 entry[i].name.len);
873
874 entry_len = sizeof("<directory></directory>" CRLF) - 1
875 + entry[i].name.len + entry[i].escape
876 + sizeof(" mtime=\"1986-12-31T10:00:00Z\"") - 1;
877
878 if (entry[i].file) {
879 entry_len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
880 }
881
882 if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
883 return NULL;
884 }
885
886 len += entry_len;
887 }
888
889 b = ngx_create_temp_buf(r->pool, len);
890 if (b == NULL) {
891 return NULL;
892 }
893
894 b->last = ngx_cpymem(b->last, head, sizeof(head) - 1);
895
896 for (i = 0; i < entries->nelts; i++) {
897 *b->last++ = '<';
898
899 if (entry[i].dir) {
900 ngx_str_set(&type, "directory");
901
902 } else if (entry[i].file) {
903 ngx_str_set(&type, "file");
904
905 } else {
906 ngx_str_set(&type, "other");
907 }
908
909 b->last = ngx_cpymem(b->last, type.data, type.len);
910
911 b->last = ngx_cpymem(b->last, " mtime=\"", sizeof(" mtime=\"") - 1);
912
913 ngx_gmtime(entry[i].mtime, &tm);
914
915 b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
916 tm.ngx_tm_year, tm.ngx_tm_mon,
917 tm.ngx_tm_mday, tm.ngx_tm_hour,
918 tm.ngx_tm_min, tm.ngx_tm_sec);
919
920 if (entry[i].file) {
921 b->last = ngx_cpymem(b->last, "\" size=\"",
922 sizeof("\" size=\"") - 1);
923 b->last = ngx_sprintf(b->last, "%O", entry[i].size);
924 }
925
926 *b->last++ = '"'; *b->last++ = '>';
927
928 if (entry[i].escape) {
929 b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
930 entry[i].name.len);
931 } else {
932 b->last = ngx_cpymem(b->last, entry[i].name.data,
933 entry[i].name.len);
934 }
935
936 *b->last++ = '<'; *b->last++ = '/';
937
938 b->last = ngx_cpymem(b->last, type.data, type.len);
939
940 *b->last++ = '>';
941
942 *b->last++ = CR; *b->last++ = LF;
943 }
944
945 b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
946
947 return b;
948 }
949
950
951 static int ngx_libc_cdecl
ngx_http_autoindex_cmp_entries(const void * one,const void * two)952 ngx_http_autoindex_cmp_entries(const void *one, const void *two)
953 {
954 ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
955 ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
956
957 if (first->dir && !second->dir) {
958 /* move the directories to the start */
959 return -1;
960 }
961
962 if (!first->dir && second->dir) {
963 /* move the directories to the start */
964 return 1;
965 }
966
967 return (int) ngx_strcmp(first->name.data, second->name.data);
968 }
969
970
971 #if 0
972
973 static ngx_buf_t *
974 ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
975 {
976 ngx_chain_t *cl;
977
978 if (ctx->buf) {
979
980 if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
981 return ctx->buf;
982 }
983
984 ctx->size += ctx->buf->last - ctx->buf->pos;
985 }
986
987 ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
988 if (ctx->buf == NULL) {
989 return NULL;
990 }
991
992 cl = ngx_alloc_chain_link(ctx->pool);
993 if (cl == NULL) {
994 return NULL;
995 }
996
997 cl->buf = ctx->buf;
998 cl->next = NULL;
999
1000 *ctx->last_out = cl;
1001 ctx->last_out = &cl->next;
1002
1003 return ctx->buf;
1004 }
1005
1006 #endif
1007
1008
1009 static ngx_int_t
ngx_http_autoindex_error(ngx_http_request_t * r,ngx_dir_t * dir,ngx_str_t * name)1010 ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
1011 {
1012 if (ngx_close_dir(dir) == NGX_ERROR) {
1013 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
1014 ngx_close_dir_n " \"%V\" failed", name);
1015 }
1016
1017 return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
1018 }
1019
1020
1021 static void *
ngx_http_autoindex_create_loc_conf(ngx_conf_t * cf)1022 ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
1023 {
1024 ngx_http_autoindex_loc_conf_t *conf;
1025
1026 conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
1027 if (conf == NULL) {
1028 return NULL;
1029 }
1030
1031 conf->enable = NGX_CONF_UNSET;
1032 conf->format = NGX_CONF_UNSET_UINT;
1033 conf->localtime = NGX_CONF_UNSET;
1034 conf->exact_size = NGX_CONF_UNSET;
1035
1036 return conf;
1037 }
1038
1039
1040 static char *
ngx_http_autoindex_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1041 ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1042 {
1043 ngx_http_autoindex_loc_conf_t *prev = parent;
1044 ngx_http_autoindex_loc_conf_t *conf = child;
1045
1046 ngx_conf_merge_value(conf->enable, prev->enable, 0);
1047 ngx_conf_merge_uint_value(conf->format, prev->format,
1048 NGX_HTTP_AUTOINDEX_HTML);
1049 ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
1050 ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
1051
1052 return NGX_CONF_OK;
1053 }
1054
1055
1056 static ngx_int_t
ngx_http_autoindex_init(ngx_conf_t * cf)1057 ngx_http_autoindex_init(ngx_conf_t *cf)
1058 {
1059 ngx_http_handler_pt *h;
1060 ngx_http_core_main_conf_t *cmcf;
1061
1062 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
1063
1064 h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
1065 if (h == NULL) {
1066 return NGX_ERROR;
1067 }
1068
1069 *h = ngx_http_autoindex_handler;
1070
1071 return NGX_OK;
1072 }
1073