src/http/modules/ngx_http_autoindex_module.c - nginx-1.7.10

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.     /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */

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

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

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

  153.     format = alcf->format;

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

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

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

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

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

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

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

  179.         return rc;
  180.     }

  181. #if (NGX_SUPPRESS_WARN)

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

  184. #endif

  185.     /* TODO: pool should be temporary pool */
  186.     pool = r->pool;

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

  192.     r->headers_out.status = NGX_HTTP_OK;

  193.     switch (format) {

  194.     case NGX_HTTP_AUTOINDEX_JSON:
  195.         ngx_str_set(&r->headers_out.content_type, "application/json");
  196.         break;

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

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

  204.     default: /* NGX_HTTP_AUTOINDEX_HTML */
  205.         ngx_str_set(&r->headers_out.content_type, "text/html");
  206.         break;
  207.     }

  208.     r->headers_out.content_type_len = r->headers_out.content_type.len;
  209.     r->headers_out.content_type_lowcase = NULL;

  210.     rc = ngx_http_send_header(r);

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

  216.         return rc;
  217.     }

  218.     filename = path.data;
  219.     filename[path.len] = '/';

  220.     for ( ;; ) {
  221.         ngx_set_errno(0);

  222.         if (ngx_read_dir(&dir) == NGX_ERROR) {
  223.             err = ngx_errno;

  224.             if (err != NGX_ENOMOREFILES) {
  225.                 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
  226.                               ngx_read_dir_n " \"%V\" failed", &path);
  227.                 return ngx_http_autoindex_error(r, &dir, &path);
  228.             }

  229.             break;
  230.         }

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

  233.         len = ngx_de_namelen(&dir);

  234.         if (ngx_de_name(&dir)[0] == '.') {
  235.             continue;
  236.         }

  237.         if (!dir.valid_info) {

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

  239.             if (path.len + 1 + len + 1 > allocated) {
  240.                 allocated = path.len + 1 + len + 1
  241.                                      + NGX_HTTP_AUTOINDEX_PREALLOCATE;

  242.                 filename = ngx_pnalloc(pool, allocated);
  243.                 if (filename == NULL) {
  244.                     return ngx_http_autoindex_error(r, &dir, &path);
  245.                 }

  246.                 last = ngx_cpystrn(filename, path.data, path.len + 1);
  247.                 *last++ = '/';
  248.             }

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

  250.             if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
  251.                 err = ngx_errno;

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

  255.                     if (err == NGX_EACCES) {
  256.                         continue;
  257.                     }

  258.                     return ngx_http_autoindex_error(r, &dir, &path);
  259.                 }

  260.                 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
  261.                     ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
  262.                                   ngx_de_link_info_n " \"%s\" failed",
  263.                                   filename);
  264.                     return ngx_http_autoindex_error(r, &dir, &path);
  265.                 }
  266.             }
  267.         }

  268.         entry = ngx_array_push(&entries);
  269.         if (entry == NULL) {
  270.             return ngx_http_autoindex_error(r, &dir, &path);
  271.         }

  272.         entry->name.len = len;

  273.         entry->name.data = ngx_pnalloc(pool, len + 1);
  274.         if (entry->name.data == NULL) {
  275.             return ngx_http_autoindex_error(r, &dir, &path);
  276.         }

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

  278.         entry->dir = ngx_de_is_dir(&dir);
  279.         entry->file = ngx_de_is_file(&dir);
  280.         entry->mtime = ngx_de_mtime(&dir);
  281.         entry->size = ngx_de_size(&dir);
  282.     }

  283.     if (ngx_close_dir(&dir) == NGX_ERROR) {
  284.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  285.                       ngx_close_dir_n " \"%V\" failed", &path);
  286.     }

  287.     if (entries.nelts > 1) {
  288.         ngx_qsort(entries.elts, (size_t) entries.nelts,
  289.                   sizeof(ngx_http_autoindex_entry_t),
  290.                   ngx_http_autoindex_cmp_entries);
  291.     }

  292.     switch (format) {

  293.     case NGX_HTTP_AUTOINDEX_JSON:
  294.         b = ngx_http_autoindex_json(r, &entries, NULL);
  295.         break;

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

  299.     case NGX_HTTP_AUTOINDEX_XML:
  300.         b = ngx_http_autoindex_xml(r, &entries);
  301.         break;

  302.     default: /* NGX_HTTP_AUTOINDEX_HTML */
  303.         b = ngx_http_autoindex_html(r, &entries);
  304.         break;
  305.     }

  306.     if (b == NULL) {
  307.         return NGX_ERROR;
  308.     }

  309.     /* TODO: free temporary pool */

  310.     if (r == r->main) {
  311.         b->last_buf = 1;
  312.     }

  313.     b->last_in_chain = 1;

  314.     out.buf = b;
  315.     out.next = NULL;

  316.     return ngx_http_output_filter(r, &out);
  317. }


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

  331.     static u_char  title[] =
  332.         "<html>" CRLF
  333.         "<head><title>Index of "
  334.     ;

  335.     static u_char  header[] =
  336.         "</title></head>" CRLF
  337.         "<body bgcolor=\"white\">" CRLF
  338.         "<h1>Index of "
  339.     ;

  340.     static u_char  tail[] =
  341.         "</body>" CRLF
  342.         "</html>" CRLF
  343.     ;

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

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

  351.     } else {
  352.         utf8 = 0;
  353.     }

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

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

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

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

  370.         if (utf8) {
  371.             entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
  372.                                                entry[i].name.len);
  373.         } else {
  374.             entry[i].utf_len = entry[i].name.len;
  375.         }

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

  388.     b = ngx_create_temp_buf(r->pool, len);
  389.     if (b == NULL) {
  390.         return NULL;
  391.     }

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

  393.     if (escape_html) {
  394.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
  395.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  396.         b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);

  397.     } else {
  398.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  399.         b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
  400.         b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
  401.     }

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

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

  405.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
  406.     tp = ngx_timeofday();

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

  409.         if (entry[i].escape) {
  410.             ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
  411.                            NGX_ESCAPE_URI_COMPONENT);

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

  413.         } else {
  414.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  415.                                  entry[i].name.len);
  416.         }

  417.         if (entry[i].dir) {
  418.             *b->last++ = '/';
  419.         }

  420.         *b->last++ = '"';
  421.         *b->last++ = '>';

  422.         len = entry[i].utf_len;

  423.         if (entry[i].name.len != len) {
  424.             if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  425.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;

  426.             } else {
  427.                 char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
  428.             }

  429.             last = b->last;
  430.             b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
  431.                                        char_len, entry[i].name.len + 1);

  432.             if (entry[i].escape_html) {
  433.                 b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
  434.                                                      b->last - last);
  435.             }

  436.             last = b->last;

  437.         } else {
  438.             if (entry[i].escape_html) {
  439.                 if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
  440.                     char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;

  441.                 } else {
  442.                     char_len = len;
  443.                 }

  444.                 b->last = (u_char *) ngx_escape_html(b->last,
  445.                                                   entry[i].name.data, char_len);
  446.                 last = b->last;

  447.             } else {
  448.                 b->last = ngx_cpystrn(b->last, entry[i].name.data,
  449.                                       NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
  450.                 last = b->last - 3;
  451.             }
  452.         }

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

  455.         } else {
  456.             if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  457.                 *b->last++ = '/';
  458.                 len++;
  459.             }

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

  461.             if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
  462.                 ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
  463.                 b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
  464.             }
  465.         }

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

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

  468.         b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
  469.                               tm.ngx_tm_mday,
  470.                               months[tm.ngx_tm_mon - 1],
  471.                               tm.ngx_tm_year,
  472.                               tm.ngx_tm_hour,
  473.                               tm.ngx_tm_min);

  474.         if (alcf->exact_size) {
  475.             if (entry[i].dir) {
  476.                 b->last = ngx_cpymem(b->last,  "                  -",
  477.                                      sizeof("                  -") - 1);
  478.             } else {
  479.                 b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
  480.             }

  481.         } else {
  482.             if (entry[i].dir) {
  483.                 b->last = ngx_cpymem(b->last,  "      -",
  484.                                      sizeof("      -") - 1);

  485.             } else {
  486.                 length = entry[i].size;

  487.                 if (length > 1024 * 1024 * 1024 - 1) {
  488.                     size = (ngx_int_t) (length / (1024 * 1024 * 1024));
  489.                     if ((length % (1024 * 1024 * 1024))
  490.                                                 > (1024 * 1024 * 1024 / 2 - 1))
  491.                     {
  492.                         size++;
  493.                     }
  494.                     scale = 'G';

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

  501.                 } else if (length > 9999) {
  502.                     size = (ngx_int_t) (length / 1024);
  503.                     if (length % 1024 > 511) {
  504.                         size++;
  505.                     }
  506.                     scale = 'K';

  507.                 } else {
  508.                     size = (ngx_int_t) length;
  509.                     scale = '\0';
  510.                 }

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

  513.                 } else {
  514.                     b->last = ngx_sprintf(b->last, " %6i", size);
  515.                 }
  516.             }
  517.         }

  518.         *b->last++ = CR;
  519.         *b->last++ = LF;
  520.     }

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

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

  523.     return b;
  524. }


  525. static ngx_buf_t *
  526. ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
  527.     ngx_str_t *callback)
  528. {
  529.     size_t                       len;
  530.     ngx_buf_t                   *b;
  531.     ngx_uint_t                   i;
  532.     ngx_http_autoindex_entry_t  *entry;

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

  534.     if (callback) {
  535.         len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
  536.     }

  537.     entry = entries->elts;

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

  541.         len += sizeof("{  }," CRLF) - 1
  542.             + sizeof("\"name\":\"\"") - 1
  543.             + entry[i].name.len + entry[i].escape
  544.             + sizeof(", \"type\":\"directory\"") - 1
  545.             + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;

  546.         if (entry[i].file) {
  547.             len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
  548.         }
  549.     }

  550.     b = ngx_create_temp_buf(r->pool, len);
  551.     if (b == NULL) {
  552.         return NULL;
  553.     }

  554.     if (callback) {
  555.         b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
  556.                              sizeof("/* callback */" CRLF) - 1);

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

  558.         *b->last++ = '(';
  559.     }

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

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

  564.         if (entry[i].escape) {
  565.             b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
  566.                                                  entry[i].name.len);
  567.         } else {
  568.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  569.                                  entry[i].name.len);
  570.         }

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

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

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

  577.         } else {
  578.             b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
  579.         }

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

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

  583.         if (entry[i].file) {
  584.             b->last = ngx_cpymem(b->last, "\", \"size\":",
  585.                                  sizeof("\", \"size\":") - 1);
  586.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);

  587.         } else {
  588.             *b->last++ = '"';
  589.         }

  590.         b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
  591.     }

  592.     if (i > 0) {
  593.         b->last--;  /* strip last comma */
  594.     }

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

  596.     if (callback) {
  597.         *b->last++ = ')'; *b->last++ = ';';
  598.     }

  599.     return b;
  600. }


  601. static ngx_int_t
  602. ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
  603. {
  604.     u_char      *p, c, ch;
  605.     ngx_uint_t   i;

  606.     if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
  607.         callback->len = 0;
  608.         return NGX_OK;
  609.     }

  610.     if (callback->len > 128) {
  611.         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
  612.                       "client sent too long callback name: \"%V\"", callback);
  613.         return NGX_DECLINED;
  614.     }

  615.     p = callback->data;

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

  618.         c = (u_char) (ch | 0x20);
  619.         if (c >= 'a' && c <= 'z') {
  620.             continue;
  621.         }

  622.         if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
  623.             continue;
  624.         }

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

  627.         return NGX_DECLINED;
  628.     }

  629.     return NGX_OK;
  630. }


  631. static ngx_buf_t *
  632. ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
  633. {
  634.     size_t                          len;
  635.     ngx_tm_t                        tm;
  636.     ngx_buf_t                      *b;
  637.     ngx_str_t                       type;
  638.     ngx_uint_t                      i;
  639.     ngx_http_autoindex_entry_t     *entry;

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

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

  643.     entry = entries->elts;

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

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

  650.         if (entry[i].file) {
  651.             len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
  652.         }
  653.     }

  654.     b = ngx_create_temp_buf(r->pool, len);
  655.     if (b == NULL) {
  656.         return NULL;
  657.     }

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

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

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

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

  665.         } else {
  666.             ngx_str_set(&type, "other");
  667.         }

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

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

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

  671.         b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
  672.                               tm.ngx_tm_year, tm.ngx_tm_mon,
  673.                               tm.ngx_tm_mday, tm.ngx_tm_hour,
  674.                               tm.ngx_tm_min, tm.ngx_tm_sec);

  675.         if (entry[i].file) {
  676.             b->last = ngx_cpymem(b->last, "\" size=\"",
  677.                                  sizeof("\" size=\"") - 1);
  678.             b->last = ngx_sprintf(b->last, "%O", entry[i].size);
  679.         }

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

  681.         if (entry[i].escape) {
  682.             b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
  683.                                                  entry[i].name.len);
  684.         } else {
  685.             b->last = ngx_cpymem(b->last, entry[i].name.data,
  686.                                  entry[i].name.len);
  687.         }

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

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

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

  691.         *b->last++ = CR; *b->last++ = LF;
  692.     }

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

  694.     return b;
  695. }


  696. static int ngx_libc_cdecl
  697. ngx_http_autoindex_cmp_entries(const void *one, const void *two)
  698. {
  699.     ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
  700.     ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;

  701.     if (first->dir && !second->dir) {
  702.         /* move the directories to the start */
  703.         return -1;
  704.     }

  705.     if (!first->dir && second->dir) {
  706.         /* move the directories to the start */
  707.         return 1;
  708.     }

  709.     return (int) ngx_strcmp(first->name.data, second->name.data);
  710. }


  711. #if 0

  712. static ngx_buf_t *
  713. ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
  714. {
  715.     ngx_chain_t  *cl;

  716.     if (ctx->buf) {

  717.         if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
  718.             return ctx->buf;
  719.         }

  720.         ctx->size += ctx->buf->last - ctx->buf->pos;
  721.     }

  722.     ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
  723.     if (ctx->buf == NULL) {
  724.         return NULL;
  725.     }

  726.     cl = ngx_alloc_chain_link(ctx->pool);
  727.     if (cl == NULL) {
  728.         return NULL;
  729.     }

  730.     cl->buf = ctx->buf;
  731.     cl->next = NULL;

  732.     *ctx->last_out = cl;
  733.     ctx->last_out = &cl->next;

  734.     return ctx->buf;
  735. }

  736. #endif


  737. static ngx_int_t
  738. ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
  739. {
  740.     if (ngx_close_dir(dir) == NGX_ERROR) {
  741.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
  742.                       ngx_close_dir_n " \"%V\" failed", name);
  743.     }

  744.     return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
  745. }


  746. static void *
  747. ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
  748. {
  749.     ngx_http_autoindex_loc_conf_t  *conf;

  750.     conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
  751.     if (conf == NULL) {
  752.         return NULL;
  753.     }

  754.     conf->enable = NGX_CONF_UNSET;
  755.     conf->format = NGX_CONF_UNSET_UINT;
  756.     conf->localtime = NGX_CONF_UNSET;
  757.     conf->exact_size = NGX_CONF_UNSET;

  758.     return conf;
  759. }


  760. static char *
  761. ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  762. {
  763.     ngx_http_autoindex_loc_conf_t *prev = parent;
  764.     ngx_http_autoindex_loc_conf_t *conf = child;

  765.     ngx_conf_merge_value(conf->enable, prev->enable, 0);
  766.     ngx_conf_merge_uint_value(conf->format, prev->format,
  767.                               NGX_HTTP_AUTOINDEX_HTML);
  768.     ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
  769.     ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);

  770.     return NGX_CONF_OK;
  771. }


  772. static ngx_int_t
  773. ngx_http_autoindex_init(ngx_conf_t *cf)
  774. {
  775.     ngx_http_handler_pt        *h;
  776.     ngx_http_core_main_conf_t  *cmcf;

  777.     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

  778.     h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
  779.     if (h == NULL) {
  780.         return NGX_ERROR;
  781.     }

  782.     *h = ngx_http_autoindex_handler;

  783.     return NGX_OK;
  784. }