src/http/modules/ngx_http_autoindex_module.c - nginx source code

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_error(ngx_http_request_t *r,
  50.     ngx_dir_t *dir, ngx_str_t *name);

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


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


  62. static ngx_command_t  ngx_http_autoindex_commands[] = {

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

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

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

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

  87.       ngx_null_command
  88. };


  89. static ngx_http_module_t  ngx_http_autoindex_module_ctx = {
  90.     NULL,                                  /* preconfiguration */
  91.     ngx_http_autoindex_init,               /* postconfiguration */

  92.     NULL,                                  /* create main configuration */
  93.     NULL,                                  /* init main configuration */

  94.     NULL,                                  /* create server configuration */
  95.     NULL,                                  /* merge server configuration */

  96.     ngx_http_autoindex_create_loc_conf,    /* create location configuration */
  97.     ngx_http_autoindex_merge_loc_conf      /* merge location configuration */
  98. };


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


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

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

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

  135.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);

  136.     if (!alcf->enable) {
  137.         return NGX_DECLINED;
  138.     }

  139.     rc = ngx_http_discard_request_body(r);

  140.     if (rc != NGX_OK) {
  141.         return rc;
  142.     }

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

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

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

  156.     format = alcf->format;

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

  161.         if (callback.len == 0) {
  162.             format = NGX_HTTP_AUTOINDEX_JSON;
  163.         }
  164.     }

  165.     if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
  166.         err = ngx_errno;

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

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

  176.         } else {
  177.             level = NGX_LOG_CRIT;
  178.             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
  179.         }

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

  182.         return rc;
  183.     }

  184. #if (NGX_SUPPRESS_WARN)

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

  187. #endif

  188.     /* TODO: pool should be temporary pool */
  189.     pool = r->pool;

  190.     if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
  191.         != NGX_OK)
  192.     {
  193.         return ngx_http_autoindex_error(r, &dir, &path);
  194.     }

  195.     r->headers_out.status = NGX_HTTP_OK;

  196.     switch (format) {

  197.     case NGX_HTTP_AUTOINDEX_JSON:
  198.         ngx_str_set(&r->headers_out.content_type, "application/json");
  199.         break;

  200.     case NGX_HTTP_AUTOINDEX_JSONP:
  201.         ngx_str_set(&r->headers_out.content_type, "application/javascript");
  202.         break;

  203.     case NGX_HTTP_AUTOINDEX_XML:
  204.         ngx_str_set(&r->headers_out.content_type, "text/xml");
  205.         ngx_str_set(&r->headers_out.charset, "utf-8");
  206.         break;

  207.     default: /* NGX_HTTP_AUTOINDEX_HTML */
  208.         ngx_str_set(&r->headers_out.content_type, "text/html");
  209.         break;
  210.     }

  211.     r->headers_out.content_type_len = r->headers_out.content_type.len;
  212.     r->headers_out.content_type_lowcase = NULL;

  213.     rc = ngx_http_send_header(r);

  214.     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  215.         if (ngx_close_dir(&dir) == NGX_ERROR) {
  216.             ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  217.                           ngx_close_dir_n " \"%V\" failed", &path);
  218.         }

  219.         return rc;
  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.                 return ngx_http_autoindex_error(r, &dir, &path);
  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.                     return ngx_http_autoindex_error(r, &dir, &path);
  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.                     return ngx_http_autoindex_error(r, &dir, &path);
  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.                     return ngx_http_autoindex_error(r, &dir, &path);
  268.                 }
  269.             }
  270.         }

  271.         entry = ngx_array_push(&entries);
  272.         if (entry == NULL) {
  273.             return ngx_http_autoindex_error(r, &dir, &path);
  274.         }

  275.         entry->name.len = len;

  276.         entry->name.data = ngx_pnalloc(pool, len + 1);
  277.         if (entry->name.data == NULL) {
  278.             return ngx_http_autoindex_error(r, &dir, &path);
  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.     if (b == NULL) {
  310.         return NGX_ERROR;
  311.     }

  312.     /* TODO: free temporary pool */

  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. }


  321. static ngx_buf_t *
  322. ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
  323. {
  324.     u_char                         *last, scale;
  325.     off_t                           length;
  326.     size_t                          len, entry_len, char_len, escape_html;
  327.     ngx_tm_t                        tm;
  328.     ngx_buf_t                      *b;
  329.     ngx_int_t                       size;
  330.     ngx_uint_t                      i, utf8;
  331.     ngx_time_t                     *tp;
  332.     ngx_http_autoindex_entry_t     *entry;
  333.     ngx_http_autoindex_loc_conf_t  *alcf;

  334.     static u_char  title[] =
  335.         "<html>" CRLF
  336.         "<head><title>Index of "
  337.     ;

  338.     static u_char  header[] =
  339.         "</title></head>" CRLF
  340.         "<body>" CRLF
  341.         "<h1>Index of "
  342.     ;

  343.     static u_char  tail[] =
  344.         "</body>" CRLF
  345.         "</html>" CRLF
  346.     ;

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

  349.     if (r->headers_out.charset.len == 5
  350.         && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
  351.            == 0)
  352.     {
  353.         utf8 = 1;

  354.     } else {
  355.         utf8 = 0;
  356.     }

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

  358.     len = sizeof(title) - 1
  359.           + r->uri.len + escape_html
  360.           + sizeof(header) - 1
  361.           + r->uri.len + escape_html
  362.           + sizeof("</h1>") - 1
  363.           + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
  364.           + sizeof("</pre><hr>") - 1
  365.           + sizeof(tail) - 1;

  366.     entry = entries->elts;
  367.     for (i = 0; i < entries->nelts; i++) {
  368.         entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
  369.                                              entry[i].name.len,
  370.                                              NGX_ESCAPE_URI_COMPONENT);

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

  373.         if (utf8) {
  374.             entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
  375.                                                entry[i].name.len);
  376.         } else {
  377.             entry[i].utf_len = entry[i].name.len;
  378.         }

  379.         entry_len = sizeof("<a href=\"") - 1
  380.                   + entry[i].name.len + entry[i].escape
  381.                   + 1                                    /* 1 is for "/" */
  382.                   + sizeof("\">") - 1
  383.                   + entry[i].name.len - entry[i].utf_len
  384.                   + entry[i].escape_html
  385.                   + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
  386.                   + sizeof("</a>") - 1
  387.                   + sizeof(" 28-Sep-1970 12:00 ") - 1
  388.                   + 20                                   /* the file size */
  389.                   + 2;

  390.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  391.             return NULL;
  392.         }

  393.         len += entry_len;
  394.     }

  395.     b = ngx_create_temp_buf(r->pool, len);
  396.     if (b == NULL) {
  397.         return NULL;
  398.     }

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

  400.     if (escape_html) {
  401.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
  402.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  403.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);

  404.     } else {
  405.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  406.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  407.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  408.     }

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

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

  412.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
  413.     tp = ngx_timeofday();

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

  416.         if (entry[i].escape) {
  417.             ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
  418.                            NGX_ESCAPE_URI_COMPONENT);

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

  420.         } else {
  421.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  422.                                  entry[i].name.len);
  423.         }

  424.         if (entry[i].dir) {
  425.             *b->last++ = '/';
  426.         }

  427.         *b->last++ = '"';
  428.         *b->last++ = '>';

  429.         len = entry[i].utf_len;

  430.         if (entry[i].name.len != len) {
  431.             if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  432.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;

  433.             } else {
  434.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
  435.             }

  436.             last = b->last;
  437.             b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
  438.                                        char_len, entry[i].name.len + 1);

  439.             if (entry[i].escape_html) {
  440.                 b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
  441.                                                      b->last - last);
  442.             }

  443.             last = b->last;

  444.         } else {
  445.             if (entry[i].escape_html) {
  446.                 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  447.                     char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;

  448.                 } else {
  449.                     char_len = len;
  450.                 }

  451.                 b->last = (u_char *) ngx_escape_html(b->last,
  452.                                                   entry[i].name.data, char_len);
  453.                 last = b->last;

  454.             } else {
  455.                 b->last = ngx_cpystrn(b->last, entry[i].name.data,
  456.                                       NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
  457.                 last = b->last - 3;
  458.             }
  459.         }

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

  462.         } else {
  463.             if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  464.                 *b->last++ = '/';
  465.                 len++;
  466.             }

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

  468.             if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  469.                 ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
  470.                 b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
  471.             }
  472.         }

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

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

  475.         b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
  476.                               tm.ngx_tm_mday,
  477.                               months[tm.ngx_tm_mon - 1],
  478.                               tm.ngx_tm_year,
  479.                               tm.ngx_tm_hour,
  480.                               tm.ngx_tm_min);

  481.         if (alcf->exact_size) {
  482.             if (entry[i].dir) {
  483.                 b->last = ngx_cpymem(b->last,  "                  -",
  484.                                      sizeof("                  -") - 1);
  485.             } else {
  486.                 b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
  487.             }

  488.         } else {
  489.             if (entry[i].dir) {
  490.                 b->last = ngx_cpymem(b->last,  "      -",
  491.                                      sizeof("      -") - 1);

  492.             } else {
  493.                 length = entry[i].size;

  494.                 if (length > 1024 * 1024 * 1024 - 1) {
  495.                     size = (ngx_int_t) (length / (1024 * 1024 * 1024));
  496.                     if ((length % (1024 * 1024 * 1024))
  497.                                                 > (1024 * 1024 * 1024 / 2 - 1))
  498.                     {
  499.                         size++;
  500.                     }
  501.                     scale = 'G';

  502.                 } else if (length > 1024 * 1024 - 1) {
  503.                     size = (ngx_int_t) (length / (1024 * 1024));
  504.                     if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
  505.                         size++;
  506.                     }
  507.                     scale = 'M';

  508.                 } else if (length > 9999) {
  509.                     size = (ngx_int_t) (length / 1024);
  510.                     if (length % 1024 > 511) {
  511.                         size++;
  512.                     }
  513.                     scale = 'K';

  514.                 } else {
  515.                     size = (ngx_int_t) length;
  516.                     scale = '\0';
  517.                 }

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

  520.                 } else {
  521.                     b->last = ngx_sprintf(b->last, " %6i", size);
  522.                 }
  523.             }
  524.         }

  525.         *b->last++ = CR;
  526.         *b->last++ = LF;
  527.     }

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

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

  530.     return b;
  531. }


  532. static ngx_buf_t *
  533. ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
  534.     ngx_str_t *callback)
  535. {
  536.     size_t                       len, entry_len;
  537.     ngx_buf_t                   *b;
  538.     ngx_uint_t                   i;
  539.     ngx_http_autoindex_entry_t  *entry;

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

  541.     if (callback) {
  542.         len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
  543.     }

  544.     entry = entries->elts;

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

  548.         entry_len = sizeof("{  }," CRLF) - 1
  549.                   + sizeof("\"name\":\"\"") - 1
  550.                   + entry[i].name.len + entry[i].escape
  551.                   + sizeof(", \"type\":\"directory\"") - 1
  552.                   + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;

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

  556.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  557.             return NULL;
  558.         }

  559.         len += entry_len;
  560.     }

  561.     b = ngx_create_temp_buf(r->pool, len);
  562.     if (b == NULL) {
  563.         return NULL;
  564.     }

  565.     if (callback) {
  566.         b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
  567.                              sizeof("/* callback */" CRLF) - 1);

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

  569.         *b->last++ = '(';
  570.     }

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

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

  575.         if (entry[i].escape) {
  576.             b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
  577.                                                  entry[i].name.len);
  578.         } else {
  579.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  580.                                  entry[i].name.len);
  581.         }

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

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

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

  588.         } else {
  589.             b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
  590.         }

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

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

  594.         if (entry[i].file) {
  595.             b->last = ngx_cpymem(b->last, "\", \"size\":",
  596.                                  sizeof("\", \"size\":") - 1);
  597.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);

  598.         } else {
  599.             *b->last++ = '"';
  600.         }

  601.         b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
  602.     }

  603.     if (i > 0) {
  604.         b->last--;  /* strip last comma */
  605.     }

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

  607.     if (callback) {
  608.         *b->last++ = ')'; *b->last++ = ';';
  609.     }

  610.     return b;
  611. }


  612. static ngx_int_t
  613. ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
  614. {
  615.     u_char      *p, c, ch;
  616.     ngx_uint_t   i;

  617.     if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
  618.         callback->len = 0;
  619.         return NGX_OK;
  620.     }

  621.     if (callback->len > 128) {
  622.         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
  623.                       "client sent too long callback name: \"%V\"", callback);
  624.         return NGX_DECLINED;
  625.     }

  626.     p = callback->data;

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

  629.         c = (u_char) (ch | 0x20);
  630.         if (c >= 'a' && c <= 'z') {
  631.             continue;
  632.         }

  633.         if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
  634.             continue;
  635.         }

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

  638.         return NGX_DECLINED;
  639.     }

  640.     return NGX_OK;
  641. }


  642. static ngx_buf_t *
  643. ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
  644. {
  645.     size_t                          len, entry_len;
  646.     ngx_tm_t                        tm;
  647.     ngx_buf_t                      *b;
  648.     ngx_str_t                       type;
  649.     ngx_uint_t                      i;
  650.     ngx_http_autoindex_entry_t     *entry;

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

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

  654.     entry = entries->elts;

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

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

  661.         if (entry[i].file) {
  662.             entry_len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
  663.         }

  664.         if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
  665.             return NULL;
  666.         }

  667.         len += entry_len;
  668.     }

  669.     b = ngx_create_temp_buf(r->pool, len);
  670.     if (b == NULL) {
  671.         return NULL;
  672.     }

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

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

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

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

  680.         } else {
  681.             ngx_str_set(&type, "other");
  682.         }

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

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

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

  686.         b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
  687.                               tm.ngx_tm_year, tm.ngx_tm_mon,
  688.                               tm.ngx_tm_mday, tm.ngx_tm_hour,
  689.                               tm.ngx_tm_min, tm.ngx_tm_sec);

  690.         if (entry[i].file) {
  691.             b->last = ngx_cpymem(b->last, "\" size=\"",
  692.                                  sizeof("\" size=\"") - 1);
  693.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);
  694.         }

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

  696.         if (entry[i].escape) {
  697.             b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
  698.                                                  entry[i].name.len);
  699.         } else {
  700.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  701.                                  entry[i].name.len);
  702.         }

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

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

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

  706.         *b->last++ = CR; *b->last++ = LF;
  707.     }

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

  709.     return b;
  710. }


  711. static int ngx_libc_cdecl
  712. ngx_http_autoindex_cmp_entries(const void *one, const void *two)
  713. {
  714.     ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
  715.     ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;

  716.     if (first->dir && !second->dir) {
  717.         /* move the directories to the start */
  718.         return -1;
  719.     }

  720.     if (!first->dir && second->dir) {
  721.         /* move the directories to the start */
  722.         return 1;
  723.     }

  724.     return (int) ngx_strcmp(first->name.data, second->name.data);
  725. }


  726. #if 0

  727. static ngx_buf_t *
  728. ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
  729. {
  730.     ngx_chain_t  *cl;

  731.     if (ctx->buf) {

  732.         if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
  733.             return ctx->buf;
  734.         }

  735.         ctx->size += ctx->buf->last - ctx->buf->pos;
  736.     }

  737.     ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
  738.     if (ctx->buf == NULL) {
  739.         return NULL;
  740.     }

  741.     cl = ngx_alloc_chain_link(ctx->pool);
  742.     if (cl == NULL) {
  743.         return NULL;
  744.     }

  745.     cl->buf = ctx->buf;
  746.     cl->next = NULL;

  747.     *ctx->last_out = cl;
  748.     ctx->last_out = &cl->next;

  749.     return ctx->buf;
  750. }

  751. #endif


  752. static ngx_int_t
  753. ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
  754. {
  755.     if (ngx_close_dir(dir) == NGX_ERROR) {
  756.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  757.                       ngx_close_dir_n " \"%V\" failed", name);
  758.     }

  759.     return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
  760. }


  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. }