comparison src/http/modules/ngx_http_autoindex_module.c @ 5944:33c08d7e2915

Autoindex: implemented JSON output format.
author Valentin Bartenev <vbart@nginx.com>
date Fri, 12 Dec 2014 20:25:35 +0300
parents 631dee7bfd4e
children 99751fe3bc3b
comparison
equal deleted inserted replaced
5943:631dee7bfd4e 5944:33c08d7e2915
28 size_t utf_len; 28 size_t utf_len;
29 size_t escape; 29 size_t escape;
30 size_t escape_html; 30 size_t escape_html;
31 31
32 unsigned dir:1; 32 unsigned dir:1;
33 unsigned file:1;
33 34
34 time_t mtime; 35 time_t mtime;
35 off_t size; 36 off_t size;
36 } ngx_http_autoindex_entry_t; 37 } ngx_http_autoindex_entry_t;
37 38
38 39
39 typedef struct { 40 typedef struct {
40 ngx_flag_t enable; 41 ngx_flag_t enable;
42 ngx_uint_t format;
41 ngx_flag_t localtime; 43 ngx_flag_t localtime;
42 ngx_flag_t exact_size; 44 ngx_flag_t exact_size;
43 } ngx_http_autoindex_loc_conf_t; 45 } ngx_http_autoindex_loc_conf_t;
44 46
45 47
48 #define NGX_HTTP_AUTOINDEX_HTML 0
49 #define NGX_HTTP_AUTOINDEX_JSON 1
50 #define NGX_HTTP_AUTOINDEX_JSONP 2
51
46 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50 52 #define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
47 53
48 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50 54 #define NGX_HTTP_AUTOINDEX_NAME_LEN 50
49 55
50 56
51 static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r, 57 static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r,
52 ngx_array_t *entries); 58 ngx_array_t *entries);
59 static ngx_buf_t *ngx_http_autoindex_json(ngx_http_request_t *r,
60 ngx_array_t *entries, ngx_str_t *callback);
61 static ngx_int_t ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r,
62 ngx_str_t *callback);
63
53 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one, 64 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
54 const void *two); 65 const void *two);
55 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r, 66 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
56 ngx_dir_t *dir, ngx_str_t *name); 67 ngx_dir_t *dir, ngx_str_t *name);
68
57 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf); 69 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
58 static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf); 70 static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
59 static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, 71 static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
60 void *parent, void *child); 72 void *parent, void *child);
73
74
75 static ngx_conf_enum_t ngx_http_autoindex_format[] = {
76 { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
77 { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
78 { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
79 { ngx_null_string, 0 }
80 };
61 81
62 82
63 static ngx_command_t ngx_http_autoindex_commands[] = { 83 static ngx_command_t ngx_http_autoindex_commands[] = {
64 84
65 { ngx_string("autoindex"), 85 { ngx_string("autoindex"),
66 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 86 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
67 ngx_conf_set_flag_slot, 87 ngx_conf_set_flag_slot,
68 NGX_HTTP_LOC_CONF_OFFSET, 88 NGX_HTTP_LOC_CONF_OFFSET,
69 offsetof(ngx_http_autoindex_loc_conf_t, enable), 89 offsetof(ngx_http_autoindex_loc_conf_t, enable),
70 NULL }, 90 NULL },
91
92 { ngx_string("autoindex_format"),
93 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
94 ngx_conf_set_enum_slot,
95 NGX_HTTP_LOC_CONF_OFFSET,
96 offsetof(ngx_http_autoindex_loc_conf_t, format),
97 &ngx_http_autoindex_format },
71 98
72 { ngx_string("autoindex_localtime"), 99 { ngx_string("autoindex_localtime"),
73 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 100 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
74 ngx_conf_set_flag_slot, 101 ngx_conf_set_flag_slot,
75 NGX_HTTP_LOC_CONF_OFFSET, 102 NGX_HTTP_LOC_CONF_OFFSET,
124 u_char *last, *filename; 151 u_char *last, *filename;
125 size_t len, allocated, root; 152 size_t len, allocated, root;
126 ngx_err_t err; 153 ngx_err_t err;
127 ngx_buf_t *b; 154 ngx_buf_t *b;
128 ngx_int_t rc; 155 ngx_int_t rc;
129 ngx_str_t path; 156 ngx_str_t path, callback;
130 ngx_dir_t dir; 157 ngx_dir_t dir;
131 ngx_uint_t level; 158 ngx_uint_t level, format;
132 ngx_pool_t *pool; 159 ngx_pool_t *pool;
133 ngx_chain_t out; 160 ngx_chain_t out;
134 ngx_array_t entries; 161 ngx_array_t entries;
135 ngx_http_autoindex_entry_t *entry; 162 ngx_http_autoindex_entry_t *entry;
136 ngx_http_autoindex_loc_conf_t *alcf; 163 ngx_http_autoindex_loc_conf_t *alcf;
164 } 191 }
165 path.data[path.len] = '\0'; 192 path.data[path.len] = '\0';
166 193
167 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 194 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
168 "http autoindex: \"%s\"", path.data); 195 "http autoindex: \"%s\"", path.data);
196
197 format = alcf->format;
198
199 if (format == NGX_HTTP_AUTOINDEX_JSONP) {
200 if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
201 return NGX_HTTP_BAD_REQUEST;
202 }
203
204 if (callback.len == 0) {
205 format = NGX_HTTP_AUTOINDEX_JSON;
206 }
207 }
169 208
170 if (ngx_open_dir(&path, &dir) == NGX_ERROR) { 209 if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
171 err = ngx_errno; 210 err = ngx_errno;
172 211
173 if (err == NGX_ENOENT 212 if (err == NGX_ENOENT
207 { 246 {
208 return ngx_http_autoindex_error(r, &dir, &path); 247 return ngx_http_autoindex_error(r, &dir, &path);
209 } 248 }
210 249
211 r->headers_out.status = NGX_HTTP_OK; 250 r->headers_out.status = NGX_HTTP_OK;
212 r->headers_out.content_type_len = sizeof("text/html") - 1; 251
213 ngx_str_set(&r->headers_out.content_type, "text/html"); 252 switch (format) {
253
254 case NGX_HTTP_AUTOINDEX_JSON:
255 ngx_str_set(&r->headers_out.content_type, "application/json");
256 break;
257
258 case NGX_HTTP_AUTOINDEX_JSONP:
259 ngx_str_set(&r->headers_out.content_type, "application/javascript");
260 break;
261
262 default: /* NGX_HTTP_AUTOINDEX_HTML */
263 ngx_str_set(&r->headers_out.content_type, "text/html");
264 break;
265 }
266
267 r->headers_out.content_type_len = r->headers_out.content_type.len;
214 r->headers_out.content_type_lowcase = NULL; 268 r->headers_out.content_type_lowcase = NULL;
215 269
216 rc = ngx_http_send_header(r); 270 rc = ngx_http_send_header(r);
217 271
218 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 272 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
306 } 360 }
307 361
308 ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1); 362 ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
309 363
310 entry->dir = ngx_de_is_dir(&dir); 364 entry->dir = ngx_de_is_dir(&dir);
365 entry->file = ngx_de_is_file(&dir);
311 entry->mtime = ngx_de_mtime(&dir); 366 entry->mtime = ngx_de_mtime(&dir);
312 entry->size = ngx_de_size(&dir); 367 entry->size = ngx_de_size(&dir);
313 } 368 }
314 369
315 if (ngx_close_dir(&dir) == NGX_ERROR) { 370 if (ngx_close_dir(&dir) == NGX_ERROR) {
321 ngx_qsort(entries.elts, (size_t) entries.nelts, 376 ngx_qsort(entries.elts, (size_t) entries.nelts,
322 sizeof(ngx_http_autoindex_entry_t), 377 sizeof(ngx_http_autoindex_entry_t),
323 ngx_http_autoindex_cmp_entries); 378 ngx_http_autoindex_cmp_entries);
324 } 379 }
325 380
326 b = ngx_http_autoindex_html(r, &entries); 381 switch (format) {
382
383 case NGX_HTTP_AUTOINDEX_JSON:
384 b = ngx_http_autoindex_json(r, &entries, NULL);
385 break;
386
387 case NGX_HTTP_AUTOINDEX_JSONP:
388 b = ngx_http_autoindex_json(r, &entries, &callback);
389 break;
390
391 default: /* NGX_HTTP_AUTOINDEX_HTML */
392 b = ngx_http_autoindex_html(r, &entries);
393 break;
394 }
327 395
328 if (b == NULL) { 396 if (b == NULL) {
329 return NGX_ERROR; 397 return NGX_ERROR;
330 } 398 }
331 399
606 674
607 return b; 675 return b;
608 } 676 }
609 677
610 678
679 static ngx_buf_t *
680 ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
681 ngx_str_t *callback)
682 {
683 size_t len;
684 ngx_buf_t *b;
685 ngx_uint_t i;
686 ngx_http_autoindex_entry_t *entry;
687
688 len = sizeof("[" CRLF CRLF "]") - 1;
689
690 if (callback) {
691 len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
692 }
693
694 entry = entries->elts;
695
696 for (i = 0; i < entries->nelts; i++) {
697 entry[i].escape = ngx_escape_json(NULL, entry[i].name.data,
698 entry[i].name.len);
699
700 len += sizeof("{ }," CRLF) - 1
701 + sizeof("\"name\":\"\"") - 1
702 + entry[i].name.len + entry[i].escape
703 + sizeof(", \"type\":\"directory\"") - 1
704 + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;
705
706 if (entry[i].file) {
707 len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
708 }
709 }
710
711 b = ngx_create_temp_buf(r->pool, len);
712 if (b == NULL) {
713 return NULL;
714 }
715
716 if (callback) {
717 b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
718 sizeof("/* callback */" CRLF) - 1);
719
720 b->last = ngx_cpymem(b->last, callback->data, callback->len);
721
722 *b->last++ = '(';
723 }
724
725 *b->last++ = '[';
726
727 for (i = 0; i < entries->nelts; i++) {
728 b->last = ngx_cpymem(b->last, CRLF "{ \"name\":\"",
729 sizeof(CRLF "{ \"name\":\"") - 1);
730
731 if (entry[i].escape) {
732 b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
733 entry[i].name.len);
734 } else {
735 b->last = ngx_cpymem(b->last, entry[i].name.data,
736 entry[i].name.len);
737 }
738
739 b->last = ngx_cpymem(b->last, "\", \"type\":\"",
740 sizeof("\", \"type\":\"") - 1);
741
742 if (entry[i].dir) {
743 b->last = ngx_cpymem(b->last, "directory", sizeof("directory") - 1);
744
745 } else if (entry[i].file) {
746 b->last = ngx_cpymem(b->last, "file", sizeof("file") - 1);
747
748 } else {
749 b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
750 }
751
752 b->last = ngx_cpymem(b->last, "\", \"mtime\":\"",
753 sizeof("\", \"mtime\":\"") - 1);
754
755 b->last = ngx_http_time(b->last, entry[i].mtime);
756
757 if (entry[i].file) {
758 b->last = ngx_cpymem(b->last, "\", \"size\":",
759 sizeof("\", \"size\":") - 1);
760 b->last = ngx_sprintf(b->last, "%O", entry[i].size);
761
762 } else {
763 *b->last++ = '"';
764 }
765
766 b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
767 }
768
769 if (i > 0) {
770 b->last--; /* strip last comma */
771 }
772
773 b->last = ngx_cpymem(b->last, CRLF "]", sizeof(CRLF "]") - 1);
774
775 if (callback) {
776 *b->last++ = ')'; *b->last++ = ';';
777 }
778
779 return b;
780 }
781
782
783 static ngx_int_t
784 ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
785 {
786 u_char *p, c, ch;
787 ngx_uint_t i;
788
789 if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
790 callback->len = 0;
791 return NGX_OK;
792 }
793
794 if (callback->len > 128) {
795 ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
796 "client sent too long callback name: \"%V\"", callback);
797 return NGX_DECLINED;
798 }
799
800 p = callback->data;
801
802 for (i = 0; i < callback->len; i++) {
803 ch = p[i];
804
805 c = (u_char) (ch | 0x20);
806 if (c >= 'a' && c <= 'z') {
807 continue;
808 }
809
810 if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
811 continue;
812 }
813
814 ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
815 "client sent invalid callback name: \"%V\"", callback);
816
817 return NGX_DECLINED;
818 }
819
820 return NGX_OK;
821 }
822
823
611 static int ngx_libc_cdecl 824 static int ngx_libc_cdecl
612 ngx_http_autoindex_cmp_entries(const void *one, const void *two) 825 ngx_http_autoindex_cmp_entries(const void *one, const void *two)
613 { 826 {
614 ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one; 827 ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
615 ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two; 828 ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
687 if (conf == NULL) { 900 if (conf == NULL) {
688 return NULL; 901 return NULL;
689 } 902 }
690 903
691 conf->enable = NGX_CONF_UNSET; 904 conf->enable = NGX_CONF_UNSET;
905 conf->format = NGX_CONF_UNSET_UINT;
692 conf->localtime = NGX_CONF_UNSET; 906 conf->localtime = NGX_CONF_UNSET;
693 conf->exact_size = NGX_CONF_UNSET; 907 conf->exact_size = NGX_CONF_UNSET;
694 908
695 return conf; 909 return conf;
696 } 910 }
701 { 915 {
702 ngx_http_autoindex_loc_conf_t *prev = parent; 916 ngx_http_autoindex_loc_conf_t *prev = parent;
703 ngx_http_autoindex_loc_conf_t *conf = child; 917 ngx_http_autoindex_loc_conf_t *conf = child;
704 918
705 ngx_conf_merge_value(conf->enable, prev->enable, 0); 919 ngx_conf_merge_value(conf->enable, prev->enable, 0);
920 ngx_conf_merge_uint_value(conf->format, prev->format,
921 NGX_HTTP_AUTOINDEX_HTML);
706 ngx_conf_merge_value(conf->localtime, prev->localtime, 0); 922 ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
707 ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1); 923 ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
708 924
709 return NGX_CONF_OK; 925 return NGX_CONF_OK;
710 } 926 }