view src/http/modules/ngx_http_autoindex_handler.c @ 34:aab2ea7c0458 NGINX_0_1_17

nginx 0.1.17 *) Change: the ngx_http_rewrite_module was rewritten from the scratch. Now it is possible to redirect, to return the error codes, to check the variables and referrers. The directives can be used inside locations. The redirect directive was canceled. *) Feature: the ngx_http_geo_module. *) Feature: the proxy_set_x_var and fastcgi_set_var directives. *) Bugfix: the location configuration with "=" modifier may be used in another location. *) Bugfix: the correct content type was set only for requests that use small caps letters in extension. *) Bugfix: if the proxy_pass or fastcgi_pass directives were set in the location, and access was denied, and the error was redirected to a static page, then the segmentation fault occurred. *) Bugfix: if in a proxied "Location" header was a relative URL, then a host name and a slash were added to them; bug appeared in 0.1.14. *) Bugfix: the system error message was not logged on Linux.
author Igor Sysoev <http://sysoev.ru>
date Thu, 03 Feb 2005 00:00:00 +0300
parents 8b6db3bda591
children 2879cd3a40cb
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


#if 0

typedef struct {
    ngx_buf_t     *buf;
    size_t         size;
    ngx_pool_t    *pool;
    size_t         alloc_size;
    ngx_chain_t  **last_out;
} ngx_http_autoindex_ctx_t;

#endif


typedef struct {
    ngx_str_t      name;
    ngx_uint_t     escape;
    ngx_uint_t     dir;
    time_t         mtime;
    off_t          size;
} ngx_http_autoindex_entry_t;


typedef struct {
    ngx_flag_t     enable;
} ngx_http_autoindex_loc_conf_t;


#define NGX_HTTP_AUTOINDEX_NAME_LEN  50


static int ngx_http_autoindex_cmp_entries(const void *one, const void *two);
static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir,
                                          u_char *name);
static ngx_int_t ngx_http_autoindex_init(ngx_cycle_t *cycle);
static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
                                               void *parent, void *child);


static ngx_command_t  ngx_http_autoindex_commands[] = {

    { ngx_string("autoindex"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_autoindex_loc_conf_t, enable),
      NULL },

      ngx_null_command
};


ngx_http_module_t  ngx_http_autoindex_module_ctx = {
    NULL,                                  /* pre conf */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_autoindex_create_loc_conf,    /* create location configration */
    ngx_http_autoindex_merge_loc_conf      /* merge location configration */
};


ngx_module_t  ngx_http_autoindex_module = {
    NGX_MODULE,
    &ngx_http_autoindex_module_ctx,        /* module context */ 
    ngx_http_autoindex_commands,           /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    ngx_http_autoindex_init,               /* init module */
    NULL                                   /* init child */
};


static u_char title[] =
"<html>" CRLF
"<head><title>Index of "
;


static u_char header[] =
"</title></head>" CRLF
"<body bgcolor=\"white\">" CRLF
"<h1>Index of "
;

static u_char tail[] =
"</body>" CRLF
"</html>" CRLF
;


static ngx_int_t ngx_http_autoindex_handler(ngx_http_request_t *r)
{
    u_char                         *last;
    size_t                          len;
    ngx_tm_t                        tm;
    ngx_int_t                       rc;
    ngx_uint_t                      i, level;
    ngx_err_t                       err;
    ngx_buf_t                      *b;
    ngx_chain_t                     out;
    ngx_str_t                       dname, fname;
    ngx_dir_t                       dir;
    ngx_pool_t                     *pool;
    ngx_array_t                     entries;
    ngx_http_core_loc_conf_t       *clcf;
    ngx_http_autoindex_entry_t     *entry;
    ngx_http_autoindex_loc_conf_t  *alcf;

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

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

    alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);

    if (!alcf->enable) {
        return NGX_DECLINED;
    }

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

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    if (clcf->alias) {
        dname.data = ngx_palloc(pool, clcf->root.len + r->uri.len
                                                     + NGX_DIR_MASK_LEN
                                                     - clcf->name.len);
        if (dname.data == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    
        last = ngx_cpymem(dname.data, clcf->root.data, clcf->root.len);
        last = ngx_cpystrn(last, r->uri.data + clcf->name.len,
                           r->uri.len - clcf->name.len);

    } else {
        dname.data = ngx_palloc(pool, clcf->root.len + r->uri.len
                                                     + NGX_DIR_MASK_LEN);
        if (dname.data == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        last = ngx_cpymem(dname.data, clcf->root.data, clcf->root.len);
        last = ngx_cpystrn(last, r->uri.data, r->uri.len);

    }

    dname.len = last - dname.data;

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


    if (ngx_open_dir(&dname, &dir) == NGX_ERROR) {
        err = ngx_errno;

        if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
            level = NGX_LOG_ERR;
            rc = NGX_HTTP_NOT_FOUND;

        } else if (err == NGX_EACCES) {
            level = NGX_LOG_ERR;
            rc = NGX_HTTP_FORBIDDEN;

        } else {
            level = NGX_LOG_CRIT;
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

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

        return rc;
    }

#if (NGX_SUPPRESS_WARN)
    /* MSVC thinks 'entries' may be used without having been initialized */
    ngx_memzero(&entries, sizeof(ngx_array_t));
#endif

    if (ngx_array_init(&entries, pool, 50, sizeof(ngx_http_autoindex_entry_t))
                                                                  == NGX_ERROR)
    {
        return ngx_http_autoindex_error(r, &dir, dname.data);
    }

    fname.len = 0;
#if (NGX_SUPPRESS_WARN)
    fname.data = NULL;
#endif

    for ( ;; ) {
        ngx_set_errno(0);

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

            if (err == NGX_ENOMOREFILES) {
                rc = NGX_OK;

            } else {
                ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
                              ngx_read_dir_n " \"%s\" failed", dname.data);
                return ngx_http_autoindex_error(r, &dir, dname.data);
            }

            break; 
        }

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

        len = ngx_de_namelen(&dir);

        if (len == 1 && ngx_de_name(&dir)[0] == '.') {
            continue;
        }

        if (len == 2
            && ngx_de_name(&dir)[0] == '.'
            && ngx_de_name(&dir)[0] == '.')
        {
            continue;
        }

        if (!dir.valid_info) {

            if (dname.len + 1 + len > fname.len) {
                fname.len = dname.len + 1 + len + 32;

                if (!(fname.data = ngx_palloc(pool, fname.len))) {
                    return ngx_http_autoindex_error(r, &dir, dname.data);
                }

                last = ngx_cpystrn(fname.data, dname.data,
                                   dname.len + 1);
                *last++ = '/';
            }

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

            if (ngx_de_info(fname.data, &dir) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
                              ngx_de_info_n " \"%s\" failed", fname.data);
                return ngx_http_autoindex_error(r, &dir, dname.data);
            }
        }

        if (!(entry = ngx_array_push(&entries))) {
            return ngx_http_autoindex_error(r, &dir, dname.data);
        }

        entry->name.len = len;        
        entry->escape = 2 * ngx_escape_uri(NULL, ngx_de_name(&dir), len,
                                           NGX_ESCAPE_HTML);

        if (!(entry->name.data = ngx_palloc(pool, len + entry->escape + 1))) {
            return ngx_http_autoindex_error(r, &dir, dname.data);
        }

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

        entry->dir = ngx_de_is_dir(&dir);
        entry->mtime = ngx_de_mtime(&dir);
        entry->size = ngx_de_size(&dir);
    }

    if (ngx_close_dir(&dir) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
                      ngx_close_dir_n " \"%s\" failed", dname.data);
    }

    len = sizeof(title) - 1
          + r->uri.len
          + sizeof(header) - 1
          + r->uri.len
          + sizeof("</h1>") - 1
          + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
          + sizeof("</pre><hr>") - 1
          + sizeof(tail) - 1;

    entry = entries.elts;
    for (i = 0; i < entries.nelts; i++) {
        len += sizeof("<a href=\"") - 1
               + 1                                          /* 1 is for "/" */
               + entry[i].name.len + entry[i].escape
               + sizeof("\">") - 1
               + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
               + sizeof("</a>") - 1
               + sizeof(" 28-Sep-1970 12:00 ") - 1
               + 19
               + 2;
    }

    if (!(b = ngx_create_temp_buf(r->pool, len))) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (entries.nelts > 1) {
        ngx_qsort(entry, (size_t) entries.nelts,
                  sizeof(ngx_http_autoindex_entry_t),
                  ngx_http_autoindex_cmp_entries);
    }

    b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
    b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
    b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
    b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
    b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);

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

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

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

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

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

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

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

        b->last = ngx_cpystrn(b->last, entry[i].name.data,
                              NGX_HTTP_AUTOINDEX_NAME_LEN + 1);

        len = entry[i].name.len;

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

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

            b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
            ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
            b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
        }

        *b->last++ = ' ';

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

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

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

        } else {
            b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
        }

        *b->last++ = CR;
        *b->last++ = LF;
    }

    /* TODO: free temporary pool */

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

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

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    r->headers_out.content_type = ngx_list_push(&r->headers_out.headers);
    if (r->headers_out.content_type == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->headers_out.content_type->key.len = 0;
    r->headers_out.content_type->key.data = NULL;
    r->headers_out.content_type->value.len = sizeof("text/html") - 1;
    r->headers_out.content_type->value.data = (u_char *) "text/html";

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    if (!r->main) {
        b->last_buf = 1;
    }

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

    return ngx_http_output_filter(r, &out);
}


static int ngx_http_autoindex_cmp_entries(const void *one, const void *two)
{
    ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
    ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;

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

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

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


#if 0

static ngx_buf_t *ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx,
                                           size_t size)
{
    ngx_chain_t  *cl;

    if (ctx->buf) {

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

        ctx->size += ctx->buf->last - ctx->buf->pos;
    }

    if (!(ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size))) {
        return NULL;
    }

    if (!(cl = ngx_alloc_chain_link(ctx->pool))) {
        return NULL;
    }

    cl->buf = ctx->buf;
    cl->next = NULL;

    *ctx->last_out = cl;
    ctx->last_out = &cl->next;

    return ctx->buf;
}

#endif


static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir,
                                          u_char *name)
{
    if (ngx_close_dir(dir) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
                      ngx_close_dir_n " \"%s\" failed", name);
    }

    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}


static ngx_int_t ngx_http_autoindex_init(ngx_cycle_t *cycle)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);

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

    *h = ngx_http_autoindex_handler;

    return NGX_OK;
}


static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_autoindex_loc_conf_t  *conf;

    conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->enable = NGX_CONF_UNSET;

    return conf;
}


static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
                                               void *parent, void *child)
{
    ngx_http_autoindex_loc_conf_t *prev = parent;
    ngx_http_autoindex_loc_conf_t *conf = child;

    ngx_conf_merge_value(conf->enable, prev->enable, 0);

    return NGX_CONF_OK;
}