src/http/modules/ngx_http_autoindex_module.c - nginx-1.31.4 nginx/ @ 8d9666701

Global variables defined

Data types defined

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>


  8. #if 0

  9. typedef struct {
  10.     ngx_buf_t     *buf;
  11.     size_t         size;
  12.     ngx_pool_t    *pool;
  13.     size_t         alloc_size;
  14.     ngx_chain_t  **last_out;
  15. } ngx_http_autoindex_ctx_t;

  16. #endif


  17. typedef struct {
  18.     ngx_str_t      name;
  19.     size_t         utf_len;
  20.     size_t         escape;
  21.     size_t         escape_html;

  22.     unsigned       dir:1;
  23.     unsigned       file:1;

  24.     time_t         mtime;
  25.     off_t          size;
  26. } ngx_http_autoindex_entry_t;


  27. typedef struct {
  28.     ngx_flag_t     enable;
  29.     ngx_uint_t     format;
  30.     ngx_flag_t     localtime;
  31.     ngx_flag_t     exact_size;
  32. } ngx_http_autoindex_loc_conf_t;


  33. #define NGX_HTTP_AUTOINDEX_HTML         0
  34. #define NGX_HTTP_AUTOINDEX_JSON         1
  35. #define NGX_HTTP_AUTOINDEX_JSONP        2
  36. #define NGX_HTTP_AUTOINDEX_XML          3

  37. #define NGX_HTTP_AUTOINDEX_PREALLOCATE  50

  38. #define NGX_HTTP_AUTOINDEX_NAME_LEN     50


  39. static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r,
  40.     ngx_array_t *entries);
  41. static ngx_buf_t *ngx_http_autoindex_json(ngx_http_request_t *r,
  42.     ngx_array_t *entries, ngx_str_t *callback);
  43. static ngx_int_t ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r,
  44.     ngx_str_t *callback);
  45. static ngx_buf_t *ngx_http_autoindex_xml(ngx_http_request_t *r,
  46.     ngx_array_t *entries);

  47. static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
  48.     const void *two);

  49. static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
  50. static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
  51. static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
  52.     void *parent, void *child);


  53. static ngx_conf_enum_t  ngx_http_autoindex_format[] = {
  54.     { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
  55.     { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
  56.     { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
  57.     { ngx_string("xml"), NGX_HTTP_AUTOINDEX_XML },
  58.     { ngx_null_string, 0 }
  59. };


  60. static ngx_command_t  ngx_http_autoindex_commands[] = {

  61.     { ngx_string("autoindex"),
  62.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  63.       ngx_conf_set_flag_slot,
  64.       NGX_HTTP_LOC_CONF_OFFSET,
  65.       offsetof(ngx_http_autoindex_loc_conf_t, enable),
  66.       NULL },

  67.     { ngx_string("autoindex_format"),
  68.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  69.       ngx_conf_set_enum_slot,
  70.       NGX_HTTP_LOC_CONF_OFFSET,
  71.       offsetof(ngx_http_autoindex_loc_conf_t, format),
  72.       &ngx_http_autoindex_format },

  73.     { ngx_string("autoindex_localtime"),
  74.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  75.       ngx_conf_set_flag_slot,
  76.       NGX_HTTP_LOC_CONF_OFFSET,
  77.       offsetof(ngx_http_autoindex_loc_conf_t, localtime),
  78.       NULL },

  79.     { ngx_string("autoindex_exact_size"),
  80.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  81.       ngx_conf_set_flag_slot,
  82.       NGX_HTTP_LOC_CONF_OFFSET,
  83.       offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
  84.       NULL },

  85.       ngx_null_command
  86. };


  87. static ngx_http_module_t  ngx_http_autoindex_module_ctx = {
  88.     NULL,                                  /* preconfiguration */
  89.     ngx_http_autoindex_init,               /* postconfiguration */

  90.     NULL,                                  /* create main configuration */
  91.     NULL,                                  /* init main configuration */

  92.     NULL,                                  /* create server configuration */
  93.     NULL,                                  /* merge server configuration */

  94.     ngx_http_autoindex_create_loc_conf,    /* create location configuration */
  95.     ngx_http_autoindex_merge_loc_conf      /* merge location configuration */
  96. };


  97. ngx_module_t  ngx_http_autoindex_module = {
  98.     NGX_MODULE_V1,
  99.     &ngx_http_autoindex_module_ctx,        /* module context */
  100.     ngx_http_autoindex_commands,           /* module directives */
  101.     NGX_HTTP_MODULE,                       /* module type */
  102.     NULL,                                  /* init master */
  103.     NULL,                                  /* init module */
  104.     NULL,                                  /* init process */
  105.     NULL,                                  /* init thread */
  106.     NULL,                                  /* exit thread */
  107.     NULL,                                  /* exit process */
  108.     NULL,                                  /* exit master */
  109.     NGX_MODULE_V1_PADDING
  110. };


  111. static ngx_int_t
  112. ngx_http_autoindex_handler(ngx_http_request_t *r)
  113. {
  114.     u_char                         *last, *filename;
  115.     size_t                          len, allocated, root;
  116.     ngx_err_t                       err;
  117.     ngx_buf_t                      *b;
  118.     ngx_int_t                       rc;
  119.     ngx_str_t                       path, callback;
  120.     ngx_dir_t                       dir;
  121.     ngx_uint_t                      level, format;
  122.     ngx_pool_t                     *pool;
  123.     ngx_chain_t                     out;
  124.     ngx_array_t                     entries;
  125.     ngx_http_autoindex_entry_t     *entry;
  126.     ngx_http_autoindex_loc_conf_t  *alcf;

  127.     if (r->uri.data[r->uri.len - 1] != '/') {
  128.         return NGX_DECLINED;
  129.     }

  130.     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
  131.         return NGX_DECLINED;
  132.     }

  133.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);

  134.     if (!alcf->enable) {
  135.         return NGX_DECLINED;
  136.     }

  137.     rc = ngx_http_discard_request_body(r);

  138.     if (rc != NGX_OK) {
  139.         return rc;
  140.     }

  141.     last = ngx_http_map_uri_to_path(r, &path, &root,
  142.                                     NGX_HTTP_AUTOINDEX_PREALLOCATE);
  143.     if (last == NULL) {
  144.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  145.     }

  146.     allocated = path.len;
  147.     path.len = last - path.data;
  148.     if (path.len > 1) {
  149.         path.len--;
  150.     }
  151.     path.data[path.len] = '\0';

  152.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  153.                    "http autoindex: \"%s\"", path.data);

  154.     format = alcf->format;

  155.     if (format == NGX_HTTP_AUTOINDEX_JSONP) {
  156.         if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
  157.             return NGX_HTTP_BAD_REQUEST;
  158.         }

  159.         if (callback.len == 0) {
  160.             format = NGX_HTTP_AUTOINDEX_JSON;
  161.         }
  162.     }

  163.     if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
  164.         err = ngx_errno;

  165.         if (err == NGX_ENOENT
  166.             || err == NGX_ENOTDIR
  167.             || err == NGX_ENAMETOOLONG)
  168.         {
  169.             level = NGX_LOG_ERR;
  170.             rc = NGX_HTTP_NOT_FOUND;

  171.         } else if (err == NGX_EACCES) {
  172.             level = NGX_LOG_ERR;
  173.             rc = NGX_HTTP_FORBIDDEN;

  174.         } else {
  175.             level = NGX_LOG_CRIT;
  176.             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
  177.         }

  178.         ngx_log_error(level, r->connection->log, err,
  179.                       ngx_open_dir_n " \"%s\" failed", path.data);

  180.         return rc;
  181.     }

  182. #if (NGX_SUPPRESS_WARN)

  183.     /* MSVC thinks 'entries' may be used without having been initialized */
  184.     ngx_memzero(&entries, sizeof(ngx_array_t));

  185. #endif

  186.     r->headers_out.status = NGX_HTTP_OK;

  187.     switch (format) {

  188.     case NGX_HTTP_AUTOINDEX_JSON:
  189.         ngx_str_set(&r->headers_out.content_type, "application/json");
  190.         break;

  191.     case NGX_HTTP_AUTOINDEX_JSONP:
  192.         ngx_str_set(&r->headers_out.content_type, "application/javascript");
  193.         break;

  194.     case NGX_HTTP_AUTOINDEX_XML:
  195.         ngx_str_set(&r->headers_out.content_type, "text/xml");
  196.         ngx_str_set(&r->headers_out.charset, "utf-8");
  197.         break;

  198.     default: /* NGX_HTTP_AUTOINDEX_HTML */
  199.         ngx_str_set(&r->headers_out.content_type, "text/html");
  200.         break;
  201.     }

  202.     r->headers_out.content_type_len = r->headers_out.content_type.len;
  203.     r->headers_out.content_type_lowcase = NULL;

  204.     rc = ngx_http_send_header(r);

  205.     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  206.         if (ngx_close_dir(&dir) == NGX_ERROR) {
  207.             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  208.                           ngx_close_dir_n " \"%V\" failed", &path);
  209.         }

  210.         return rc;
  211.     }

  212.     pool = ngx_create_pool(4096, r->connection->log);
  213.     if (pool == NULL) {
  214.         goto failed;
  215.     }

  216.     if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
  217.         != NGX_OK)
  218.     {
  219.         goto failed;
  220.     }

  221.     filename = path.data;
  222.     filename[path.len] = '/';

  223.     for ( ;; ) {
  224.         ngx_set_errno(0);

  225.         if (ngx_read_dir(&dir) == NGX_ERROR) {
  226.             err = ngx_errno;

  227.             if (err != NGX_ENOMOREFILES) {
  228.                 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
  229.                               ngx_read_dir_n " \"%V\" failed", &path);
  230.                 goto failed;
  231.             }

  232.             break;
  233.         }

  234.         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  235.                        "http autoindex file: \"%s\"", ngx_de_name(&dir));

  236.         len = ngx_de_namelen(&dir);

  237.         if (ngx_de_name(&dir)[0] == '.') {
  238.             continue;
  239.         }

  240.         if (!dir.valid_info) {

  241.             /* 1 byte for '/' and 1 byte for terminating '\0' */

  242.             if (path.len + 1 + len + 1 > allocated) {
  243.                 allocated = path.len + 1 + len + 1
  244.                                      + NGX_HTTP_AUTOINDEX_PREALLOCATE;

  245.                 filename = ngx_pnalloc(pool, allocated);
  246.                 if (filename == NULL) {
  247.                     goto failed;
  248.                 }

  249.                 last = ngx_cpystrn(filename, path.data, path.len + 1);
  250.                 *last++ = '/';
  251.             }

  252.             ngx_cpystrn(last, ngx_de_name(&dir), len + 1);

  253.             if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
  254.                 err = ngx_errno;

  255.                 if (err != NGX_ENOENT && err != NGX_ELOOP) {
  256.                     ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
  257.                                   ngx_de_info_n " \"%s\" failed", filename);

  258.                     if (err == NGX_EACCES) {
  259.                         continue;
  260.                     }

  261.                     goto failed;
  262.                 }

  263.                 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
  264.                     ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
  265.                                   ngx_de_link_info_n " \"%s\" failed",
  266.                                   filename);
  267.                     goto failed;
  268.                 }
  269.             }
  270.         }

  271.         entry = ngx_array_push(&entries);
  272.         if (entry == NULL) {
  273.             goto failed;
  274.         }

  275.         entry->name.len = len;

  276.         entry->name.data = ngx_pnalloc(pool, len + 1);
  277.         if (entry->name.data == NULL) {
  278.             goto failed;
  279.         }

  280.         ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);

  281.         entry->dir = ngx_de_is_dir(&dir);
  282.         entry->file = ngx_de_is_file(&dir);
  283.         entry->mtime = ngx_de_mtime(&dir);
  284.         entry->size = ngx_de_size(&dir);
  285.     }

  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.     if (entries.nelts > 1) {
  291.         ngx_qsort(entries.elts, (size_t) entries.nelts,
  292.                   sizeof(ngx_http_autoindex_entry_t),
  293.                   ngx_http_autoindex_cmp_entries);
  294.     }

  295.     switch (format) {

  296.     case NGX_HTTP_AUTOINDEX_JSON:
  297.         b = ngx_http_autoindex_json(r, &entries, NULL);
  298.         break;

  299.     case NGX_HTTP_AUTOINDEX_JSONP:
  300.         b = ngx_http_autoindex_json(r, &entries, &callback);
  301.         break;

  302.     case NGX_HTTP_AUTOINDEX_XML:
  303.         b = ngx_http_autoindex_xml(r, &entries);
  304.         break;

  305.     default: /* NGX_HTTP_AUTOINDEX_HTML */
  306.         b = ngx_http_autoindex_html(r, &entries);
  307.         break;
  308.     }

  309.     ngx_destroy_pool(pool);

  310.     if (b == NULL) {
  311.         return NGX_ERROR;
  312.     }

  313.     if (r == r->main) {
  314.         b->last_buf = 1;
  315.     }

  316.     b->last_in_chain = 1;

  317.     out.buf = b;
  318.     out.next = NULL;

  319.     return ngx_http_output_filter(r, &out);

  320. failed:

  321.     if (pool != NULL) {
  322.         ngx_destroy_pool(pool);
  323.     }

  324.     if (ngx_close_dir(&dir) == NGX_ERROR) {
  325.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  326.                       ngx_close_dir_n " \"%V\" failed", &path);
  327.     }

  328.     return NGX_ERROR;
  329. }


  330. static ngx_buf_t *
  331. ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
  332. {
  333.     u_char                         *last, scale;
  334.     off_t                           length;
  335.     size_t                          len, entry_len, char_len, escape_html;
  336.     ngx_tm_t                        tm;
  337.     ngx_buf_t                      *b;
  338.     ngx_int_t                       size;
  339.     ngx_uint_t                      i, utf8;
  340.     ngx_time_t                     *tp;
  341.     ngx_http_autoindex_entry_t     *entry;
  342.     ngx_http_autoindex_loc_conf_t  *alcf;

  343.     static u_char  title[] =
  344.         "<html>" CRLF
  345.         "<head><title>Index of "
  346.     ;

  347.     static u_char  header[] =
  348.         "</title></head>" CRLF
  349.         "<body>" CRLF
  350.         "<h1>Index of "
  351.     ;

  352.     static u_char  tail[] =
  353.         "</body>" CRLF
  354.         "</html>" CRLF
  355.     ;

  356.     static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  357.                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

  358.     if (r->headers_out.charset.len == 5
  359.         && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
  360.            == 0)
  361.     {
  362.         utf8 = 1;

  363.     } else {
  364.         utf8 = 0;
  365.     }

  366.     escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);

  367.     len = sizeof(title) - 1
  368.           + r->uri.len + escape_html
  369.           + sizeof(header) - 1
  370.           + r->uri.len + escape_html
  371.           + sizeof("</h1>") - 1
  372.           + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
  373.           + sizeof("</pre><hr>") - 1
  374.           + sizeof(tail) - 1;

  375.     entry = entries->elts;
  376.     for (i = 0; i < entries->nelts; i++) {
  377.         entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
  378.                                              entry[i].name.len,
  379.                                              NGX_ESCAPE_URI_COMPONENT);

  380.         entry[i].escape_html = ngx_escape_html(NULL, entry[i].name.data,
  381.                                                entry[i].name.len);

  382.         if (utf8) {
  383.             entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
  384.                                                entry[i].name.len);
  385.         } else {
  386.             entry[i].utf_len = entry[i].name.len;
  387.         }

  388.         entry_len = sizeof("<a href=\"") - 1
  389.                   + entry[i].name.len + entry[i].escape
  390.                   + 1                                    /* 1 is for "/" */
  391.                   + sizeof("\">") - 1
  392.                   + entry[i].name.len - entry[i].utf_len
  393.                   + entry[i].escape_html
  394.                   + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
  395.                   + sizeof("</a>") - 1
  396.                   + sizeof(" 28-Sep-1970 12:00 ") - 1
  397.                   + 20                                   /* the file size */
  398.                   + 2;

  399.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  400.             return NULL;
  401.         }

  402.         len += entry_len;
  403.     }

  404.     b = ngx_create_temp_buf(r->pool, len);
  405.     if (b == NULL) {
  406.         return NULL;
  407.     }

  408.     b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);

  409.     if (escape_html) {
  410.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
  411.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  412.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);

  413.     } else {
  414.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  415.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  416.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  417.     }

  418.     b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);

  419.     b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
  420.                          sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);

  421.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
  422.     tp = ngx_timeofday();

  423.     for (i = 0; i < entries->nelts; i++) {
  424.         b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);

  425.         if (entry[i].escape) {
  426.             ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
  427.                            NGX_ESCAPE_URI_COMPONENT);

  428.             b->last += entry[i].name.len + entry[i].escape;

  429.         } else {
  430.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  431.                                  entry[i].name.len);
  432.         }

  433.         if (entry[i].dir) {
  434.             *b->last++ = '/';
  435.         }

  436.         *b->last++ = '"';
  437.         *b->last++ = '>';

  438.         len = entry[i].utf_len;

  439.         if (entry[i].name.len != len) {
  440.             if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  441.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;

  442.             } else {
  443.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
  444.             }

  445.             last = b->last;
  446.             b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
  447.                                        char_len, entry[i].name.len + 1);

  448.             if (entry[i].escape_html) {
  449.                 b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
  450.                                                      b->last - last);
  451.             }

  452.             last = b->last;

  453.         } else {
  454.             if (entry[i].escape_html) {
  455.                 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  456.                     char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;

  457.                 } else {
  458.                     char_len = len;
  459.                 }

  460.                 b->last = (u_char *) ngx_escape_html(b->last,
  461.                                                   entry[i].name.data, char_len);
  462.                 last = b->last;

  463.             } else {
  464.                 b->last = ngx_cpystrn(b->last, entry[i].name.data,
  465.                                       NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
  466.                 last = b->last - 3;
  467.             }
  468.         }

  469.         if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  470.             b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);

  471.         } else {
  472.             if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  473.                 *b->last++ = '/';
  474.                 len++;
  475.             }

  476.             b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);

  477.             if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  478.                 ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
  479.                 b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
  480.             }
  481.         }

  482.         *b->last++ = ' ';

  483.         ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);

  484.         b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
  485.                               tm.ngx_tm_mday,
  486.                               months[tm.ngx_tm_mon - 1],
  487.                               tm.ngx_tm_year,
  488.                               tm.ngx_tm_hour,
  489.                               tm.ngx_tm_min);

  490.         if (alcf->exact_size) {
  491.             if (entry[i].dir) {
  492.                 b->last = ngx_cpymem(b->last,  "                  -",
  493.                                      sizeof("                  -") - 1);
  494.             } else {
  495.                 b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
  496.             }

  497.         } else {
  498.             if (entry[i].dir) {
  499.                 b->last = ngx_cpymem(b->last,  "      -",
  500.                                      sizeof("      -") - 1);

  501.             } else {
  502.                 length = entry[i].size;

  503.                 if (length > 1024 * 1024 * 1024 - 1) {
  504.                     size = (ngx_int_t) (length / (1024 * 1024 * 1024));
  505.                     if ((length % (1024 * 1024 * 1024))
  506.                                                 > (1024 * 1024 * 1024 / 2 - 1))
  507.                     {
  508.                         size++;
  509.                     }
  510.                     scale = 'G';

  511.                 } else if (length > 1024 * 1024 - 1) {
  512.                     size = (ngx_int_t) (length / (1024 * 1024));
  513.                     if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
  514.                         size++;
  515.                     }
  516.                     scale = 'M';

  517.                 } else if (length > 9999) {
  518.                     size = (ngx_int_t) (length / 1024);
  519.                     if (length % 1024 > 511) {
  520.                         size++;
  521.                     }
  522.                     scale = 'K';

  523.                 } else {
  524.                     size = (ngx_int_t) length;
  525.                     scale = '\0';
  526.                 }

  527.                 if (scale) {
  528.                     b->last = ngx_sprintf(b->last, "%6i%c", size, scale);

  529.                 } else {
  530.                     b->last = ngx_sprintf(b->last, " %6i", size);
  531.                 }
  532.             }
  533.         }

  534.         *b->last++ = CR;
  535.         *b->last++ = LF;
  536.     }

  537.     b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);

  538.     b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);

  539.     return b;
  540. }


  541. static ngx_buf_t *
  542. ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
  543.     ngx_str_t *callback)
  544. {
  545.     size_t                       len, entry_len;
  546.     ngx_buf_t                   *b;
  547.     ngx_uint_t                   i;
  548.     ngx_http_autoindex_entry_t  *entry;

  549.     len = sizeof("[" CRLF CRLF "]") - 1;

  550.     if (callback) {
  551.         len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
  552.     }

  553.     entry = entries->elts;

  554.     for (i = 0; i < entries->nelts; i++) {
  555.         entry[i].escape = ngx_escape_json(NULL, entry[i].name.data,
  556.                                           entry[i].name.len);

  557.         entry_len = sizeof("{  }," CRLF) - 1
  558.                   + sizeof("\"name\":\"\"") - 1
  559.                   + entry[i].name.len + entry[i].escape
  560.                   + sizeof(", \"type\":\"directory\"") - 1
  561.                   + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;

  562.         if (entry[i].file) {
  563.             entry_len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
  564.         }

  565.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  566.             return NULL;
  567.         }

  568.         len += entry_len;
  569.     }

  570.     b = ngx_create_temp_buf(r->pool, len);
  571.     if (b == NULL) {
  572.         return NULL;
  573.     }

  574.     if (callback) {
  575.         b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
  576.                              sizeof("/* callback */" CRLF) - 1);

  577.         b->last = ngx_cpymem(b->last, callback->data, callback->len);

  578.         *b->last++ = '(';
  579.     }

  580.     *b->last++ = '[';

  581.     for (i = 0; i < entries->nelts; i++) {
  582.         b->last = ngx_cpymem(b->last, CRLF "{ \"name\":\"",
  583.                              sizeof(CRLF "{ \"name\":\"") - 1);

  584.         if (entry[i].escape) {
  585.             b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
  586.                                                  entry[i].name.len);
  587.         } else {
  588.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  589.                                  entry[i].name.len);
  590.         }

  591.         b->last = ngx_cpymem(b->last, "\", \"type\":\"",
  592.                              sizeof("\", \"type\":\"") - 1);

  593.         if (entry[i].dir) {
  594.             b->last = ngx_cpymem(b->last, "directory", sizeof("directory") - 1);

  595.         } else if (entry[i].file) {
  596.             b->last = ngx_cpymem(b->last, "file", sizeof("file") - 1);

  597.         } else {
  598.             b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
  599.         }

  600.         b->last = ngx_cpymem(b->last, "\", \"mtime\":\"",
  601.                              sizeof("\", \"mtime\":\"") - 1);

  602.         b->last = ngx_http_time(b->last, entry[i].mtime);

  603.         if (entry[i].file) {
  604.             b->last = ngx_cpymem(b->last, "\", \"size\":",
  605.                                  sizeof("\", \"size\":") - 1);
  606.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);

  607.         } else {
  608.             *b->last++ = '"';
  609.         }

  610.         b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
  611.     }

  612.     if (i > 0) {
  613.         b->last--;  /* strip last comma */
  614.     }

  615.     b->last = ngx_cpymem(b->last, CRLF "]", sizeof(CRLF "]") - 1);

  616.     if (callback) {
  617.         *b->last++ = ')'; *b->last++ = ';';
  618.     }

  619.     return b;
  620. }


  621. static ngx_int_t
  622. ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
  623. {
  624.     u_char      *p, c, ch;
  625.     ngx_uint_t   i;

  626.     if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
  627.         callback->len = 0;
  628.         return NGX_OK;
  629.     }

  630.     if (callback->len > 128) {
  631.         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
  632.                       "client sent too long callback name: \"%V\"", callback);
  633.         return NGX_DECLINED;
  634.     }

  635.     p = callback->data;

  636.     for (i = 0; i < callback->len; i++) {
  637.         ch = p[i];

  638.         c = (u_char) (ch | 0x20);
  639.         if (c >= 'a' && c <= 'z') {
  640.             continue;
  641.         }

  642.         if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
  643.             continue;
  644.         }

  645.         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
  646.                       "client sent invalid callback name: \"%V\"", callback);

  647.         return NGX_DECLINED;
  648.     }

  649.     return NGX_OK;
  650. }


  651. static ngx_buf_t *
  652. ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
  653. {
  654.     size_t                          len, entry_len;
  655.     ngx_tm_t                        tm;
  656.     ngx_buf_t                      *b;
  657.     ngx_str_t                       type;
  658.     ngx_uint_t                      i;
  659.     ngx_http_autoindex_entry_t     *entry;

  660.     static u_char  head[] = "<?xml version=\"1.0\"?>" CRLF "<list>" CRLF;
  661.     static u_char  tail[] = "</list>" CRLF;

  662.     len = sizeof(head) - 1 + sizeof(tail) - 1;

  663.     entry = entries->elts;

  664.     for (i = 0; i < entries->nelts; i++) {
  665.         entry[i].escape = ngx_escape_html(NULL, entry[i].name.data,
  666.                                           entry[i].name.len);

  667.         entry_len = sizeof("<directory></directory>" CRLF) - 1
  668.                   + entry[i].name.len + entry[i].escape
  669.                   + sizeof(" mtime=\"1986-12-31T10:00:00Z\"") - 1;

  670.         if (entry[i].file) {
  671.             entry_len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
  672.         }

  673.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  674.             return NULL;
  675.         }

  676.         len += entry_len;
  677.     }

  678.     b = ngx_create_temp_buf(r->pool, len);
  679.     if (b == NULL) {
  680.         return NULL;
  681.     }

  682.     b->last = ngx_cpymem(b->last, head, sizeof(head) - 1);

  683.     for (i = 0; i < entries->nelts; i++) {
  684.         *b->last++ = '<';

  685.         if (entry[i].dir) {
  686.             ngx_str_set(&type, "directory");

  687.         } else if (entry[i].file) {
  688.             ngx_str_set(&type, "file");

  689.         } else {
  690.             ngx_str_set(&type, "other");
  691.         }

  692.         b->last = ngx_cpymem(b->last, type.data, type.len);

  693.         b->last = ngx_cpymem(b->last, " mtime=\"", sizeof(" mtime=\"") - 1);

  694.         ngx_gmtime(entry[i].mtime, &tm);

  695.         b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
  696.                               tm.ngx_tm_year, tm.ngx_tm_mon,
  697.                               tm.ngx_tm_mday, tm.ngx_tm_hour,
  698.                               tm.ngx_tm_min, tm.ngx_tm_sec);

  699.         if (entry[i].file) {
  700.             b->last = ngx_cpymem(b->last, "\" size=\"",
  701.                                  sizeof("\" size=\"") - 1);
  702.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);
  703.         }

  704.         *b->last++ = '"'; *b->last++ = '>';

  705.         if (entry[i].escape) {
  706.             b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
  707.                                                  entry[i].name.len);
  708.         } else {
  709.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  710.                                  entry[i].name.len);
  711.         }

  712.         *b->last++ = '<'; *b->last++ = '/';

  713.         b->last = ngx_cpymem(b->last, type.data, type.len);

  714.         *b->last++ = '>';

  715.         *b->last++ = CR; *b->last++ = LF;
  716.     }

  717.     b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);

  718.     return b;
  719. }


  720. static int ngx_libc_cdecl
  721. ngx_http_autoindex_cmp_entries(const void *one, const void *two)
  722. {
  723.     ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
  724.     ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;

  725.     if (first->dir && !second->dir) {
  726.         /* move the directories to the start */
  727.         return -1;
  728.     }

  729.     if (!first->dir && second->dir) {
  730.         /* move the directories to the start */
  731.         return 1;
  732.     }

  733.     return (int) ngx_strcmp(first->name.data, second->name.data);
  734. }


  735. #if 0

  736. static ngx_buf_t *
  737. ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
  738. {
  739.     ngx_chain_t  *cl;

  740.     if (ctx->buf) {

  741.         if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
  742.             return ctx->buf;
  743.         }

  744.         ctx->size += ctx->buf->last - ctx->buf->pos;
  745.     }

  746.     ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
  747.     if (ctx->buf == NULL) {
  748.         return NULL;
  749.     }

  750.     cl = ngx_alloc_chain_link(ctx->pool);
  751.     if (cl == NULL) {
  752.         return NULL;
  753.     }

  754.     cl->buf = ctx->buf;
  755.     cl->next = NULL;

  756.     *ctx->last_out = cl;
  757.     ctx->last_out = &cl->next;

  758.     return ctx->buf;
  759. }

  760. #endif



  761. static void *
  762. ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
  763. {
  764.     ngx_http_autoindex_loc_conf_t  *conf;

  765.     conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
  766.     if (conf == NULL) {
  767.         return NULL;
  768.     }

  769.     conf->enable = NGX_CONF_UNSET;
  770.     conf->format = NGX_CONF_UNSET_UINT;
  771.     conf->localtime = NGX_CONF_UNSET;
  772.     conf->exact_size = NGX_CONF_UNSET;

  773.     return conf;
  774. }


  775. static char *
  776. ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  777. {
  778.     ngx_http_autoindex_loc_conf_t *prev = parent;
  779.     ngx_http_autoindex_loc_conf_t *conf = child;

  780.     ngx_conf_merge_value(conf->enable, prev->enable, 0);
  781.     ngx_conf_merge_uint_value(conf->format, prev->format,
  782.                               NGX_HTTP_AUTOINDEX_HTML);
  783.     ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
  784.     ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);

  785.     return NGX_CONF_OK;
  786. }


  787. static ngx_int_t
  788. ngx_http_autoindex_init(ngx_conf_t *cf)
  789. {
  790.     ngx_http_handler_pt        *h;
  791.     ngx_http_core_main_conf_t  *cmcf;

  792.     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

  793.     h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
  794.     if (h == NULL) {
  795.         return NGX_ERROR;
  796.     }

  797.     *h = ngx_http_autoindex_handler;

  798.     return NGX_OK;
  799. }