Groonga 3.0.9 Source Code Document
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_autoindex_module.c
Go to the documentation of this file.
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 
13 #if 0
14 
15 typedef struct {
16  ngx_buf_t *buf;
17  size_t size;
18  ngx_pool_t *pool;
19  size_t alloc_size;
20  ngx_chain_t **last_out;
21 } ngx_http_autoindex_ctx_t;
22 
23 #endif
24 
25 
26 typedef struct {
28  size_t utf_len;
29  size_t escape;
30  size_t escape_html;
31 
32  unsigned dir:1;
33 
34  time_t mtime;
35  off_t size;
37 
38 
39 typedef struct {
44 
45 
46 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
47 
48 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
49 
50 
51 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
52  const void *two);
53 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
54  ngx_dir_t *dir, ngx_str_t *name);
55 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
56 static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
57 static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
58  void *parent, void *child);
59 
60 
61 static ngx_command_t ngx_http_autoindex_commands[] = {
62 
63  { ngx_string("autoindex"),
67  offsetof(ngx_http_autoindex_loc_conf_t, enable),
68  NULL },
69 
70  { ngx_string("autoindex_localtime"),
74  offsetof(ngx_http_autoindex_loc_conf_t, localtime),
75  NULL },
76 
77  { ngx_string("autoindex_exact_size"),
81  offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
82  NULL },
83 
85 };
86 
87 
88 static ngx_http_module_t ngx_http_autoindex_module_ctx = {
89  NULL, /* preconfiguration */
90  ngx_http_autoindex_init, /* postconfiguration */
91 
92  NULL, /* create main configuration */
93  NULL, /* init main configuration */
94 
95  NULL, /* create server configuration */
96  NULL, /* merge server configuration */
97 
98  ngx_http_autoindex_create_loc_conf, /* create location configuration */
99  ngx_http_autoindex_merge_loc_conf /* merge location configuration */
100 };
101 
102 
105  &ngx_http_autoindex_module_ctx, /* module context */
106  ngx_http_autoindex_commands, /* module directives */
107  NGX_HTTP_MODULE, /* module type */
108  NULL, /* init master */
109  NULL, /* init module */
110  NULL, /* init process */
111  NULL, /* init thread */
112  NULL, /* exit thread */
113  NULL, /* exit process */
114  NULL, /* exit master */
116 };
117 
118 
119 static u_char title[] =
120 "<html>" CRLF
121 "<head><title>Index of "
122 ;
123 
124 
125 static u_char header[] =
126 "</title></head>" CRLF
127 "<body bgcolor=\"white\">" CRLF
128 "<h1>Index of "
129 ;
130 
131 static u_char tail[] =
132 "</body>" CRLF
133 "</html>" CRLF
134 ;
135 
136 
137 static ngx_int_t
138 ngx_http_autoindex_handler(ngx_http_request_t *r)
139 {
140  u_char *last, *filename, scale;
141  off_t length;
142  size_t len, char_len, escape_html, allocated, root;
143  ngx_tm_t tm;
144  ngx_err_t err;
145  ngx_buf_t *b;
146  ngx_int_t rc, size;
147  ngx_str_t path;
148  ngx_dir_t dir;
149  ngx_uint_t i, level, utf8;
150  ngx_pool_t *pool;
151  ngx_time_t *tp;
152  ngx_chain_t out;
153  ngx_array_t entries;
156 
157  static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
158  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
159 
160  if (r->uri.data[r->uri.len - 1] != '/') {
161  return NGX_DECLINED;
162  }
163 
164  if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
165  return NGX_DECLINED;
166  }
167 
168  alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
169 
170  if (!alcf->enable) {
171  return NGX_DECLINED;
172  }
173 
174  /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
175 
176  last = ngx_http_map_uri_to_path(r, &path, &root,
178  if (last == NULL) {
180  }
181 
182  allocated = path.len;
183  path.len = last - path.data;
184  if (path.len > 1) {
185  path.len--;
186  }
187  path.data[path.len] = '\0';
188 
190  "http autoindex: \"%s\"", path.data);
191 
192  if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
193  err = ngx_errno;
194 
195  if (err == NGX_ENOENT
196  || err == NGX_ENOTDIR
197  || err == NGX_ENAMETOOLONG)
198  {
199  level = NGX_LOG_ERR;
200  rc = NGX_HTTP_NOT_FOUND;
201 
202  } else if (err == NGX_EACCES) {
203  level = NGX_LOG_ERR;
204  rc = NGX_HTTP_FORBIDDEN;
205 
206  } else {
207  level = NGX_LOG_CRIT;
209  }
210 
211  ngx_log_error(level, r->connection->log, err,
212  ngx_open_dir_n " \"%s\" failed", path.data);
213 
214  return rc;
215  }
216 
217 #if (NGX_SUPPRESS_WARN)
218 
219  /* MSVC thinks 'entries' may be used without having been initialized */
220  ngx_memzero(&entries, sizeof(ngx_array_t));
221 
222 #endif
223 
224  /* TODO: pool should be temporary pool */
225  pool = r->pool;
226 
227  if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
228  != NGX_OK)
229  {
230  return ngx_http_autoindex_error(r, &dir, &path);
231  }
232 
234  r->headers_out.content_type_len = sizeof("text/html") - 1;
235  ngx_str_set(&r->headers_out.content_type, "text/html");
236 
237  rc = ngx_http_send_header(r);
238 
239  if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
240  if (ngx_close_dir(&dir) == NGX_ERROR) {
242  ngx_close_dir_n " \"%V\" failed", &path);
243  }
244 
245  return rc;
246  }
247 
248  filename = path.data;
249  filename[path.len] = '/';
250 
251  if (r->headers_out.charset.len == 5
252  && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
253  == 0)
254  {
255  utf8 = 1;
256 
257  } else {
258  utf8 = 0;
259  }
260 
261  for ( ;; ) {
262  ngx_set_errno(0);
263 
264  if (ngx_read_dir(&dir) == NGX_ERROR) {
265  err = ngx_errno;
266 
267  if (err != NGX_ENOMOREFILES) {
269  ngx_read_dir_n " \"%V\" failed", &path);
270  return ngx_http_autoindex_error(r, &dir, &path);
271  }
272 
273  break;
274  }
275 
277  "http autoindex file: \"%s\"", ngx_de_name(&dir));
278 
279  len = ngx_de_namelen(&dir);
280 
281  if (ngx_de_name(&dir)[0] == '.') {
282  continue;
283  }
284 
285  if (!dir.valid_info) {
286 
287  /* 1 byte for '/' and 1 byte for terminating '\0' */
288 
289  if (path.len + 1 + len + 1 > allocated) {
290  allocated = path.len + 1 + len + 1
292 
293  filename = ngx_pnalloc(pool, allocated);
294  if (filename == NULL) {
295  return ngx_http_autoindex_error(r, &dir, &path);
296  }
297 
298  last = ngx_cpystrn(filename, path.data, path.len + 1);
299  *last++ = '/';
300  }
301 
302  ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
303 
304  if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
305  err = ngx_errno;
306 
307  if (err != NGX_ENOENT) {
309  ngx_de_info_n " \"%s\" failed", filename);
310 
311  if (err == NGX_EACCES) {
312  continue;
313  }
314 
315  return ngx_http_autoindex_error(r, &dir, &path);
316  }
317 
318  if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
320  ngx_de_link_info_n " \"%s\" failed",
321  filename);
322  return ngx_http_autoindex_error(r, &dir, &path);
323  }
324  }
325  }
326 
327  entry = ngx_array_push(&entries);
328  if (entry == NULL) {
329  return ngx_http_autoindex_error(r, &dir, &path);
330  }
331 
332  entry->name.len = len;
333 
334  entry->name.data = ngx_pnalloc(pool, len + 1);
335  if (entry->name.data == NULL) {
336  return ngx_http_autoindex_error(r, &dir, &path);
337  }
338 
339  ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
340 
341  entry->escape = 2 * ngx_escape_uri(NULL, ngx_de_name(&dir), len,
343 
344  entry->escape_html = ngx_escape_html(NULL, entry->name.data,
345  entry->name.len);
346 
347  if (utf8) {
348  entry->utf_len = ngx_utf8_length(entry->name.data, entry->name.len);
349  } else {
350  entry->utf_len = len;
351  }
352 
353  entry->dir = ngx_de_is_dir(&dir);
354  entry->mtime = ngx_de_mtime(&dir);
355  entry->size = ngx_de_size(&dir);
356  }
357 
358  if (ngx_close_dir(&dir) == NGX_ERROR) {
360  ngx_close_dir_n " \"%s\" failed", &path);
361  }
362 
363  escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
364 
365  len = sizeof(title) - 1
366  + r->uri.len + escape_html
367  + sizeof(header) - 1
368  + r->uri.len + escape_html
369  + sizeof("</h1>") - 1
370  + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
371  + sizeof("</pre><hr>") - 1
372  + sizeof(tail) - 1;
373 
374  entry = entries.elts;
375  for (i = 0; i < entries.nelts; i++) {
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 
389  b = ngx_create_temp_buf(r->pool, len);
390  if (b == NULL) {
392  }
393 
394  if (entries.nelts > 1) {
395  ngx_qsort(entry, (size_t) entries.nelts,
397  ngx_http_autoindex_cmp_entries);
398  }
399 
400  b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
401 
402  if (escape_html) {
403  b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
404  b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
405  b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
406 
407  } else {
408  b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
409  b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
410  b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
411  }
412 
413  b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
414 
415  b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
416  sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
417 
418  tp = ngx_timeofday();
419 
420  for (i = 0; i < entries.nelts; i++) {
421  b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
422 
423  if (entry[i].escape) {
424  ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
426 
427  b->last += entry[i].name.len + entry[i].escape;
428 
429  } else {
430  b->last = ngx_cpymem(b->last, entry[i].name.data,
431  entry[i].name.len);
432  }
433 
434  if (entry[i].dir) {
435  *b->last++ = '/';
436  }
437 
438  *b->last++ = '"';
439  *b->last++ = '>';
440 
441  len = entry[i].utf_len;
442 
443  if (entry[i].name.len != len) {
444  if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
445  char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
446 
447  } else {
448  char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
449  }
450 
451  last = b->last;
452  b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
453  char_len, entry[i].name.len + 1);
454 
455  if (entry[i].escape_html) {
456  b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
457  b->last - last);
458  }
459 
460  last = b->last;
461 
462  } else {
463  if (entry[i].escape_html) {
464  if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
465  char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
466 
467  } else {
468  char_len = len;
469  }
470 
471  b->last = (u_char *) ngx_escape_html(b->last,
472  entry[i].name.data, char_len);
473  last = b->last;
474 
475  } else {
476  b->last = ngx_cpystrn(b->last, entry[i].name.data,
478  last = b->last - 3;
479  }
480  }
481 
482  if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
483  b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
484 
485  } else {
486  if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
487  *b->last++ = '/';
488  len++;
489  }
490 
491  b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
492 
493  if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
495  b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
496  }
497  }
498 
499  *b->last++ = ' ';
500 
501  ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
502 
503  b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
504  tm.ngx_tm_mday,
505  months[tm.ngx_tm_mon - 1],
506  tm.ngx_tm_year,
507  tm.ngx_tm_hour,
508  tm.ngx_tm_min);
509 
510  if (alcf->exact_size) {
511  if (entry[i].dir) {
512  b->last = ngx_cpymem(b->last, " -",
513  sizeof(" -") - 1);
514  } else {
515  b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
516  }
517 
518  } else {
519  if (entry[i].dir) {
520  b->last = ngx_cpymem(b->last, " -",
521  sizeof(" -") - 1);
522 
523  } else {
524  length = entry[i].size;
525 
526  if (length > 1024 * 1024 * 1024 - 1) {
527  size = (ngx_int_t) (length / (1024 * 1024 * 1024));
528  if ((length % (1024 * 1024 * 1024))
529  > (1024 * 1024 * 1024 / 2 - 1))
530  {
531  size++;
532  }
533  scale = 'G';
534 
535  } else if (length > 1024 * 1024 - 1) {
536  size = (ngx_int_t) (length / (1024 * 1024));
537  if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
538  size++;
539  }
540  scale = 'M';
541 
542  } else if (length > 9999) {
543  size = (ngx_int_t) (length / 1024);
544  if (length % 1024 > 511) {
545  size++;
546  }
547  scale = 'K';
548 
549  } else {
550  size = (ngx_int_t) length;
551  scale = '\0';
552  }
553 
554  if (scale) {
555  b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
556 
557  } else {
558  b->last = ngx_sprintf(b->last, " %6i", size);
559  }
560  }
561  }
562 
563  *b->last++ = CR;
564  *b->last++ = LF;
565  }
566 
567  /* TODO: free temporary pool */
568 
569  b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
570 
571  b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
572 
573  if (r == r->main) {
574  b->last_buf = 1;
575  }
576 
577  b->last_in_chain = 1;
578 
579  out.buf = b;
580  out.next = NULL;
581 
582  return ngx_http_output_filter(r, &out);
583 }
584 
585 
586 static int ngx_libc_cdecl
587 ngx_http_autoindex_cmp_entries(const void *one, const void *two)
588 {
591 
592  if (first->dir && !second->dir) {
593  /* move the directories to the start */
594  return -1;
595  }
596 
597  if (!first->dir && second->dir) {
598  /* move the directories to the start */
599  return 1;
600  }
601 
602  return (int) ngx_strcmp(first->name.data, second->name.data);
603 }
604 
605 
606 #if 0
607 
608 static ngx_buf_t *
609 ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
610 {
611  ngx_chain_t *cl;
612 
613  if (ctx->buf) {
614 
615  if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
616  return ctx->buf;
617  }
618 
619  ctx->size += ctx->buf->last - ctx->buf->pos;
620  }
621 
622  ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
623  if (ctx->buf == NULL) {
624  return NULL;
625  }
626 
627  cl = ngx_alloc_chain_link(ctx->pool);
628  if (cl == NULL) {
629  return NULL;
630  }
631 
632  cl->buf = ctx->buf;
633  cl->next = NULL;
634 
635  *ctx->last_out = cl;
636  ctx->last_out = &cl->next;
637 
638  return ctx->buf;
639 }
640 
641 #endif
642 
643 
644 static ngx_int_t
645 ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
646 {
647  if (ngx_close_dir(dir) == NGX_ERROR) {
649  ngx_close_dir_n " \"%V\" failed", name);
650  }
651 
653 }
654 
655 
656 static void *
657 ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
658 {
660 
661  conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
662  if (conf == NULL) {
663  return NULL;
664  }
665 
666  conf->enable = NGX_CONF_UNSET;
667  conf->localtime = NGX_CONF_UNSET;
668  conf->exact_size = NGX_CONF_UNSET;
669 
670  return conf;
671 }
672 
673 
674 static char *
675 ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
676 {
677  ngx_http_autoindex_loc_conf_t *prev = parent;
678  ngx_http_autoindex_loc_conf_t *conf = child;
679 
680  ngx_conf_merge_value(conf->enable, prev->enable, 0);
681  ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
682  ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
683 
684  return NGX_CONF_OK;
685 }
686 
687 
688 static ngx_int_t
689 ngx_http_autoindex_init(ngx_conf_t *cf)
690 {
693 
695 
697  if (h == NULL) {
698  return NGX_ERROR;
699  }
700 
701  *h = ngx_http_autoindex_handler;
702 
703  return NGX_OK;
704 }