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