├── .gitattributes ├── .gitignore ├── src ├── ngx_http_array_var_util.h ├── ddebug.h ├── ngx_http_array_var_util.c └── ngx_http_array_var_module.c ├── util └── build.sh ├── config ├── .travis.yml ├── valgrind.suppress ├── t └── sanity.t └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | reindex 2 | .libs 3 | *.swp 4 | *.slo 5 | *.la 6 | *.swo 7 | *.lo 8 | *~ 9 | *.o 10 | print.txt 11 | .rsync 12 | *.tar.gz 13 | dist 14 | build[789] 15 | build 16 | tags 17 | update-readme 18 | *.tmp 19 | test/Makefile 20 | test/blib 21 | test.sh 22 | t.sh 23 | t/t.sh 24 | t/servroot/ 25 | releng 26 | reset 27 | *.t_ 28 | genmobi.sh 29 | *.mobi 30 | misc/chunked 31 | ctags 32 | src/module.c 33 | src/util.c 34 | src/util.h 35 | all 36 | go 37 | build1[0-9] 38 | buildroot/ 39 | work/ 40 | Makefile 41 | nginx 42 | *.plist 43 | -------------------------------------------------------------------------------- /src/ngx_http_array_var_util.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_HTTP_ARRAY_VAR_UTIL_H 2 | #define NGX_HTTP_ARRAY_VAR_UTIL_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | ngx_int_t ngx_http_array_var_add_variable(ngx_conf_t *cf, ngx_str_t *name); 11 | 12 | u_char *ngx_http_array_var_strlstrn(u_char *s1, u_char *last, u_char *s2, 13 | size_t n); 14 | 15 | ndk_set_var_value_pt ngx_http_array_var_get_func_from_cmd(u_char *name, 16 | size_t name_len); 17 | 18 | 19 | #ifndef ngx_str3cmp 20 | 21 | # define ngx_str3cmp(m, c0, c1, c2) \ 22 | m[0] == c0 && m[1] == c1 && m[2] == c2 23 | 24 | #endif /* ngx_str3cmp */ 25 | 26 | #endif /* NGX_HTTP_ARRAY_VAR_UTIL_H */ 27 | 28 | -------------------------------------------------------------------------------- /util/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is mostly meant to be used by the author himself. 4 | 5 | root=`pwd` 6 | home=~ 7 | version=$1 8 | force=$2 9 | 10 | ngx-build $force $version \ 11 | --without-mail_pop3_module \ 12 | --without-mail_imap_module \ 13 | --without-mail_smtp_module \ 14 | --without-http_upstream_ip_hash_module \ 15 | --without-http_empty_gif_module \ 16 | --without-http_memcached_module \ 17 | --without-http_referer_module \ 18 | --without-http_autoindex_module \ 19 | --without-http_auth_basic_module \ 20 | --without-http_userid_module \ 21 | --add-module=$root/../echo-nginx-module \ 22 | --add-module=$root/../ndk-nginx-module \ 23 | --add-module=$root/../set-misc-nginx-module \ 24 | --add-module=$root $opts \ 25 | --with-debug || exit 1 26 | #--add-module=$home/work/ndk \ 27 | #--without-http_ssi_module # we cannot disable ssi because echo_location_async depends on it (i dunno why?!) 28 | 29 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | if test -n "$ngx_module_link"; then 2 | if test -n "$NDK_SRCS"; then 3 | echo "found ngx_devel_kit for ngx_array_var; looks good." 4 | else 5 | echo "error: ngx_devel_kit is required to build ngx_array_var; please put it before ngx_array_var." 1>&2 6 | exit 1 7 | fi 8 | else 9 | if echo $HTTP_MODULES | grep " ndk_http_module" > /dev/null; then 10 | echo "found ngx_devel_kit for ngx_array_var; looks good." 11 | else 12 | echo "error: ngx_devel_kit is required to build ngx_array_var; please put it before ngx_array_var." 1>&2 13 | exit 1 14 | fi 15 | fi 16 | 17 | ngx_addon_name=ngx_http_array_var_module 18 | 19 | HTTP_ARRAY_VAR_SRCS=" \ 20 | $ngx_addon_dir/src/ngx_http_array_var_module.c \ 21 | $ngx_addon_dir/src/ngx_http_array_var_util.c \ 22 | " 23 | 24 | HTTP_ARRAY_VAR_DEPS=" \ 25 | $ngx_addon_dir/src/ddebug.h \ 26 | $ngx_addon_dir/src/ngx_http_array_var_util.h \ 27 | " 28 | 29 | if test -n "$ngx_module_link"; then 30 | ngx_module_type=HTTP 31 | ngx_module_name=$ngx_addon_name 32 | ngx_module_incs= 33 | ngx_module_deps="$HTTP_ARRAY_VAR_DEPS" 34 | ngx_module_srcs="$HTTP_ARRAY_VAR_SRCS" 35 | ngx_module_libs= 36 | 37 | . auto/module 38 | else 39 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 40 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $HTTP_ARRAY_VAR_SRCS" 41 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $HTTP_ARRAY_VAR_DEPS" 42 | fi 43 | 44 | CFLAGS="$CFLAGS -DNDK_SET_VAR" 45 | -------------------------------------------------------------------------------- /src/ddebug.h: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG_H 2 | #define DDEBUG_H 3 | 4 | #include 5 | #include 6 | 7 | #if defined(DDEBUG) && (DDEBUG) 8 | 9 | # if (NGX_HAVE_VARIADIC_MACROS) 10 | 11 | # define dd(...) fprintf(stderr, "array_var *** "); \ 12 | fprintf(stderr, __VA_ARGS__); \ 13 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) 14 | 15 | # else 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | static ngx_inline void 23 | dd(const char* fmt, ...) { 24 | } 25 | 26 | # endif 27 | 28 | #else 29 | 30 | # if (NGX_HAVE_VARIADIC_MACROS) 31 | 32 | # define dd(...) 33 | 34 | # else 35 | 36 | #include 37 | 38 | static ngx_inline void 39 | dd(const char* fmt, ...) { 40 | } 41 | 42 | # endif 43 | 44 | #endif 45 | 46 | #if defined(DDEBUG) && (DDEBUG) 47 | 48 | #define dd_check_read_event_handler(r) \ 49 | dd("r->read_event_handler = %s", \ 50 | r->read_event_handler == ngx_http_block_reading ? \ 51 | "ngx_http_block_reading" : \ 52 | r->read_event_handler == ngx_http_test_reading ? \ 53 | "ngx_http_test_reading" : \ 54 | r->read_event_handler == ngx_http_request_empty_handler ? \ 55 | "ngx_http_request_empty_handler" : "UNKNOWN") 56 | 57 | #define dd_check_write_event_handler(r) \ 58 | dd("r->write_event_handler = %s", \ 59 | r->write_event_handler == ngx_http_handler ? \ 60 | "ngx_http_handler" : \ 61 | r->write_event_handler == ngx_http_core_run_phases ? \ 62 | "ngx_http_core_run_phases" : \ 63 | r->write_event_handler == ngx_http_request_empty_handler ? \ 64 | "ngx_http_request_empty_handler" : "UNKNOWN") 65 | 66 | #else 67 | 68 | #define dd_check_read_event_handler(r) 69 | #define dd_check_write_event_handler(r) 70 | 71 | #endif 72 | 73 | #endif /* DDEBUG_H */ 74 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | 15 | env: 16 | global: 17 | - LUAJIT_PREFIX=/opt/luajit21 18 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 19 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 20 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 21 | - LUA_INCLUDE_DIR=$LUAJIT_INC 22 | - LUA_CMODULE_DIR=/lib 23 | - JOBS=3 24 | - NGX_BUILD_JOBS=$JOBS 25 | - TEST_NGINX_SLEEP=0.006 26 | matrix: 27 | - NGINX_VERSION=1.29.2 28 | 29 | addons: 30 | apt: 31 | packages: 32 | - axel 33 | - cpanminus 34 | - libtest-base-perl 35 | - libtext-diff-perl 36 | - liburi-perl 37 | - libwww-perl 38 | - libtest-longstring-perl 39 | - liblist-moreutils-perl 40 | - libgd-dev 41 | 42 | cache: 43 | apt: true 44 | directories: 45 | - download-cache 46 | 47 | install: 48 | - git clone https://github.com/openresty/openresty-devel-utils.git 49 | - git clone https://github.com/openresty/openresty.git ../openresty 50 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 51 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 52 | - git clone https://github.com/openresty/test-nginx.git 53 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 54 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 55 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 56 | - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 57 | 58 | script: 59 | - cd luajit2 60 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1) 61 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 62 | - cd ../test-nginx && sudo cpanm . && cd .. 63 | - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH 64 | - export NGX_BUILD_CC=$CC 65 | - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) 66 | - nginx -V 67 | - prove -I. -r t 68 | -------------------------------------------------------------------------------- /src/ngx_http_array_var_util.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_array_var_util.h" 14 | 15 | 16 | static ngx_int_t ngx_http_array_var_variable_not_found(ngx_http_request_t *r, 17 | ngx_http_variable_value_t *v, uintptr_t data); 18 | 19 | 20 | /* Modified from the ngx_strlcasestrn function in ngx_string.h 21 | * Copyright (C) by Igor Sysoev */ 22 | u_char * 23 | ngx_http_array_var_strlstrn(u_char *s1, u_char *last, u_char *s2, size_t n) 24 | { 25 | ngx_uint_t c1, c2; 26 | 27 | c2 = (ngx_uint_t) *s2++; 28 | last -= n; 29 | 30 | do { 31 | do { 32 | if (s1 >= last) { 33 | return NULL; 34 | } 35 | 36 | c1 = (ngx_uint_t) *s1++; 37 | 38 | } while (c1 != c2); 39 | 40 | } while (ngx_strncmp(s1, s2, n) != 0); 41 | 42 | return --s1; 43 | } 44 | 45 | 46 | ndk_set_var_value_pt 47 | ngx_http_array_var_get_func_from_cmd(u_char *name, size_t name_len) 48 | { 49 | ndk_set_var_t *filter; 50 | ngx_uint_t i; 51 | ngx_module_t **modules; 52 | ngx_module_t *module; 53 | ngx_command_t *cmd; 54 | 55 | #if defined(nginx_version) && nginx_version >= 1009011 56 | modules = ngx_cycle->modules; 57 | #else 58 | modules = ngx_modules; 59 | #endif 60 | 61 | for (i = 0; modules[i]; i++) { 62 | module = modules[i]; 63 | if (module->type != NGX_HTTP_MODULE) { 64 | continue; 65 | } 66 | 67 | cmd = modules[i]->commands; 68 | if (cmd == NULL) { 69 | continue; 70 | } 71 | 72 | for ( /* void */ ; cmd->name.len; cmd++) { 73 | if (cmd->set != ndk_set_var_value) { 74 | continue; 75 | } 76 | 77 | filter = cmd->post; 78 | if (filter == NULL) { 79 | continue; 80 | } 81 | 82 | if (cmd->name.len != name_len 83 | || ngx_strncmp(cmd->name.data, name, name_len) != 0) 84 | { 85 | continue; 86 | } 87 | 88 | return (ndk_set_var_value_pt) filter->func; 89 | } 90 | } 91 | 92 | return NULL; 93 | } 94 | 95 | 96 | ngx_int_t 97 | ngx_http_array_var_add_variable(ngx_conf_t *cf, ngx_str_t *name) 98 | { 99 | ngx_http_variable_t *v; 100 | 101 | v = ngx_http_add_variable(cf, name, NGX_HTTP_VAR_CHANGEABLE); 102 | if (v == NULL) { 103 | return NGX_ERROR; 104 | } 105 | 106 | v->get_handler = ngx_http_array_var_variable_not_found; 107 | 108 | return ngx_http_get_variable_index(cf, name); 109 | } 110 | 111 | 112 | static ngx_int_t 113 | ngx_http_array_var_variable_not_found(ngx_http_request_t *r, 114 | ngx_http_variable_value_t *v, uintptr_t data) 115 | { 116 | v->not_found = 1; 117 | return NGX_OK; 118 | } 119 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | socketcall.sendmsg(msg.msg_iov[i]) 5 | fun:sendmsg 6 | fun:ngx_write_channel 7 | fun:ngx_signal_worker_processes 8 | fun:ngx_master_process_cycle 9 | fun:main 10 | } 11 | { 12 | 13 | Memcheck:Addr4 14 | fun:ngx_init_cycle 15 | fun:ngx_master_process_cycle 16 | fun:main 17 | } 18 | { 19 | 20 | Memcheck:Param 21 | socketcall.sendmsg(msg.msg_iov[i]) 22 | fun:__sendmsg_nocancel 23 | fun:ngx_write_channel 24 | fun:ngx_signal_worker_processes 25 | fun:ngx_master_process_cycle 26 | fun:main 27 | } 28 | { 29 | 30 | Memcheck:Leak 31 | fun:malloc 32 | fun:ngx_alloc 33 | fun:ngx_event_process_init 34 | } 35 | { 36 | 37 | Memcheck:Param 38 | socketcall.sendmsg(msg.msg_iov[i]) 39 | fun:__sendmsg_nocancel 40 | fun:ngx_write_channel 41 | fun:ngx_signal_worker_processes 42 | fun:ngx_master_process_cycle 43 | fun:main 44 | } 45 | { 46 | 47 | Memcheck:Param 48 | socketcall.sendmsg(msg.msg_iov[i]) 49 | fun:__sendmsg_nocancel 50 | fun:ngx_write_channel 51 | fun:ngx_master_process_cycle 52 | fun:main 53 | } 54 | { 55 | 56 | Memcheck:Param 57 | socketcall.sendmsg(msg.msg_iov[i]) 58 | fun:__sendmsg_nocancel 59 | fun:ngx_write_channel 60 | fun:ngx_pass_open_channel 61 | fun:ngx_start_worker_processes 62 | fun:ngx_master_process_cycle 63 | fun:main 64 | } 65 | { 66 | 67 | Memcheck:Param 68 | epoll_ctl(event) 69 | fun:epoll_ctl 70 | } 71 | { 72 | nginx-core-process-init 73 | Memcheck:Leak 74 | fun:malloc 75 | fun:ngx_alloc 76 | fun:ngx_event_process_init 77 | fun:ngx_single_process_cycle 78 | fun:main 79 | } 80 | { 81 | nginx-core-crc32-init 82 | Memcheck:Leak 83 | fun:malloc 84 | fun:ngx_alloc 85 | fun:ngx_crc32_table_init 86 | fun:main 87 | } 88 | { 89 | palloc_large_for_init_request 90 | Memcheck:Leak 91 | fun:malloc 92 | fun:ngx_alloc 93 | fun:ngx_palloc_large 94 | fun:ngx_palloc 95 | fun:ngx_pcalloc 96 | fun:ngx_http_init_request 97 | fun:ngx_epoll_process_events 98 | fun:ngx_process_events_and_timers 99 | fun:ngx_single_process_cycle 100 | fun:main 101 | } 102 | { 103 | palloc_large_for_create_temp_buf 104 | Memcheck:Leak 105 | fun:malloc 106 | fun:ngx_alloc 107 | fun:ngx_palloc_large 108 | fun:ngx_palloc 109 | fun:ngx_create_temp_buf 110 | fun:ngx_http_init_request 111 | fun:ngx_epoll_process_events 112 | fun:ngx_process_events_and_timers 113 | fun:ngx_single_process_cycle 114 | fun:main 115 | } 116 | { 117 | accept_create_pool 118 | Memcheck:Leak 119 | fun:memalign 120 | fun:posix_memalign 121 | fun:ngx_memalign 122 | fun:ngx_create_pool 123 | fun:ngx_event_accept 124 | fun:ngx_epoll_process_events 125 | fun:ngx_process_events_and_timers 126 | fun:ngx_single_process_cycle 127 | fun:main 128 | } 129 | { 130 | create_pool_for_init_req 131 | Memcheck:Leak 132 | fun:memalign 133 | fun:posix_memalign 134 | fun:ngx_memalign 135 | fun:ngx_create_pool 136 | fun:ngx_http_init_request 137 | fun:ngx_epoll_process_events 138 | fun:ngx_process_events_and_timers 139 | fun:ngx_single_process_cycle 140 | fun:main 141 | } 142 | { 143 | 144 | Memcheck:Cond 145 | fun:index 146 | fun:expand_dynamic_string_token 147 | fun:_dl_map_object 148 | fun:map_doit 149 | fun:_dl_catch_error 150 | fun:do_preload 151 | fun:dl_main 152 | } 153 | { 154 | 155 | Memcheck:Leak 156 | match-leak-kinds: definite 157 | fun:malloc 158 | fun:ngx_alloc 159 | fun:ngx_set_environment 160 | fun:ngx_single_process_cycle 161 | } 162 | { 163 | 164 | Memcheck:Leak 165 | match-leak-kinds: definite 166 | fun:malloc 167 | fun:ngx_alloc 168 | fun:ngx_set_environment 169 | fun:ngx_worker_process_init 170 | fun:ngx_worker_process_cycle 171 | } 172 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #repeat_each(3); 7 | repeat_each(1); 8 | 9 | plan tests => repeat_each() * 2 * blocks(); 10 | 11 | no_long_string(); 12 | #no_shuffle(); 13 | 14 | run_tests(); 15 | 16 | #no_diff(); 17 | 18 | __DATA__ 19 | 20 | === TEST 1: array split/join 21 | --- config 22 | location /foo { 23 | array_split ',' $arg_names to=$names; 24 | array_join '+' $names; 25 | echo $names; 26 | } 27 | --- request 28 | GET /foo?names=Bob,Marry,John 29 | --- response_body 30 | Bob+Marry+John 31 | 32 | 33 | 34 | === TEST 2: array split/join (non-empty sep with a limit) 35 | --- config 36 | location /foo { 37 | array_split ',' $arg_names 2 to=$names; 38 | array_join '+' $names; 39 | echo $names; 40 | } 41 | --- request 42 | GET /foo?names=Bob,Marry,John 43 | --- response_body 44 | Bob+Marry,John 45 | 46 | 47 | 48 | === TEST 3: array split/join (non-empty sep with a ZERO limit) 49 | --- config 50 | location /foo { 51 | array_split ',' $arg_names 0 to=$names; 52 | array_join '+' $names; 53 | echo $names; 54 | } 55 | --- request 56 | GET /foo?names=Bob,Marry,John 57 | --- response_body 58 | Bob+Marry+John 59 | 60 | 61 | 62 | === TEST 4: array split/join (emtpy sep with a limit) 63 | --- config 64 | location /foo { 65 | array_split '' $arg_names 2 to=$names; 66 | array_join '+' $names; 67 | echo $names; 68 | } 69 | --- request 70 | GET /foo?names=Bob 71 | --- response_body 72 | B+ob 73 | 74 | 75 | 76 | === TEST 5: array split/join (emtpy sep with a ZERO limit) 77 | --- config 78 | location /foo { 79 | array_split '' $arg_names 0 to=$names; 80 | array_join '+' $names; 81 | echo $names; 82 | } 83 | --- request 84 | GET /foo?names=Bob 85 | --- response_body 86 | B+o+b 87 | 88 | 89 | 90 | === TEST 6: array split (empty split sep) 91 | --- config 92 | location /foo { 93 | array_split '' $arg_names to=$names; 94 | array_join '+' $names; 95 | echo $names; 96 | } 97 | --- request 98 | GET /foo?names=Bob 99 | --- response_body 100 | B+o+b 101 | 102 | 103 | 104 | === TEST 7: array split (empty split/join sep) 105 | --- config 106 | location /foo { 107 | array_split '' $arg_names to=$names; 108 | array_join '' $names; 109 | echo [$names]; 110 | } 111 | --- request 112 | GET /foo?names=Bob 113 | --- response_body 114 | [Bob] 115 | 116 | 117 | 118 | === TEST 8: array split (empty split + empty input) 119 | --- config 120 | location /foo { 121 | array_split '' $arg_names to=$names; 122 | array_join '+' $names; 123 | echo [$names]; 124 | } 125 | --- request 126 | GET /foo? 127 | --- response_body 128 | [] 129 | 130 | 131 | 132 | === TEST 9: array split/join (single item) 133 | --- config 134 | location /foo { 135 | array_split ',' $arg_names to=$names; 136 | array_join '+' $names; 137 | echo $names; 138 | } 139 | --- request 140 | GET /foo?names=nomas 141 | --- response_body 142 | nomas 143 | 144 | 145 | 146 | === TEST 10: array split/join (empty array) 147 | --- config 148 | location /foo { 149 | array_split ',' $arg_names to=$names; 150 | array_join '+' $names; 151 | echo "[$names]"; 152 | } 153 | --- request 154 | GET /foo? 155 | --- response_body 156 | [] 157 | 158 | 159 | 160 | === TEST 11: array split/join (multi-char sep) 161 | --- config 162 | location /foo { 163 | array_split '->' $arg_names to=$names; 164 | array_join '(+)' $names; 165 | echo "$names"; 166 | } 167 | --- request 168 | GET /foo?names=a->b->c 169 | --- response_body 170 | a(+)b(+)c 171 | 172 | 173 | 174 | === TEST 12: array split/join (list of empty values) 175 | --- config 176 | location /foo { 177 | array_split ',' $arg_names to=$names; 178 | array_join '+' $names; 179 | echo "[$names]"; 180 | } 181 | --- request 182 | GET /foo?names=,,, 183 | --- response_body 184 | [+++] 185 | 186 | 187 | 188 | === TEST 13: array map 189 | --- config 190 | location /foo { 191 | array_split ',' $arg_names to=$names; 192 | array_map 'hi' $names; 193 | array_join '+' $names; 194 | echo "[$names]"; 195 | } 196 | --- request 197 | GET /foo?names=,,, 198 | --- response_body 199 | [hi+hi+hi+hi] 200 | 201 | 202 | 203 | === TEST 14: array map (in-place) 204 | --- config 205 | location /foo { 206 | array_split ',' $arg_names to=$names; 207 | array_map '[$array_it]' $names; 208 | array_join '+' $names; 209 | echo "$names"; 210 | } 211 | --- request 212 | GET /foo?names=bob,marry,nomas 213 | --- response_body 214 | [bob]+[marry]+[nomas] 215 | 216 | 217 | 218 | === TEST 15: array map (copy) 219 | --- config 220 | location /foo { 221 | array_split ',' $arg_names to=$names; 222 | array_map '[$array_it]' $names to=$names2; 223 | array_join '+' $names; 224 | array_join '+' $names2; 225 | echo "$names"; 226 | echo "$names2"; 227 | } 228 | --- request 229 | GET /foo?names=bob,marry,nomas 230 | --- response_body 231 | bob+marry+nomas 232 | [bob]+[marry]+[nomas] 233 | 234 | 235 | 236 | === TEST 16: array map (empty values) 237 | --- config 238 | location /foo { 239 | array_split ',' $arg_names to=$names; 240 | array_map '[$array_it]' $names; 241 | array_join '+' $names; 242 | echo "$names"; 243 | } 244 | --- request 245 | GET /foo?names=,marry,nomas 246 | --- response_body 247 | []+[marry]+[nomas] 248 | 249 | 250 | 251 | === TEST 17: non-in-place join 252 | --- config 253 | location /foo { 254 | array_split ',' $arg_names to=$names; 255 | array_join '+' $names to=$res; 256 | array_join '-' $names to=$res2; 257 | echo $res; 258 | echo $res2; 259 | } 260 | --- request 261 | GET /foo?names=bob,marry,nomas 262 | --- response_body 263 | bob+marry+nomas 264 | bob-marry-nomas 265 | 266 | 267 | 268 | === TEST 18: non-in-place join 269 | --- config 270 | location /foo { 271 | array_split ',' $arg_names to=$names; 272 | array_join '+' $names to=$res; 273 | array_join '-' $names to=$res2; 274 | echo $res; 275 | echo $res2; 276 | } 277 | --- request 278 | GET /foo?names=bob,marry,nomas 279 | --- response_body 280 | bob+marry+nomas 281 | bob-marry-nomas 282 | 283 | 284 | 285 | === TEST 19: map op (in-place) 286 | --- config 287 | location /foo { 288 | array_split ',' $arg_names to=$names; 289 | array_map_op set_quote_sql_str $names; 290 | array_join '+' $names to=$res; 291 | echo $res; 292 | } 293 | --- request 294 | GET /foo?names=bob,marry,nomas 295 | --- response_body 296 | 'bob'+'marry'+'nomas' 297 | 298 | 299 | 300 | === TEST 20: map op (copy) 301 | --- config 302 | location /foo { 303 | array_split ',' $arg_names to=$names; 304 | array_map_op set_quote_sql_str $names to=$list; 305 | array_join '+' $list to=$res; 306 | echo $res; 307 | } 308 | --- request 309 | GET /foo?names=bob,marry,nomas 310 | --- response_body 311 | 'bob'+'marry'+'nomas' 312 | 313 | 314 | 315 | === TEST 21: map op (quote special chars) 316 | --- config 317 | location /foo { 318 | array_split ',' $arg_names to=$names; 319 | array_map_op set_quote_sql_str $names; 320 | array_join '+' $names to=$res; 321 | echo $res; 322 | } 323 | --- request 324 | GET /foo?names=',\ 325 | --- response_body 326 | '\''+'\\' 327 | 328 | 329 | 330 | === TEST 22: $array_it gets cleared after array map 331 | --- config 332 | location /foo { 333 | array_split ',' $arg_names to=$names; 334 | array_map '[$array_it]' $names; 335 | echo "[$array_it]"; 336 | } 337 | --- request 338 | GET /foo?names=bob,marry,nomas 339 | --- response_body 340 | [] 341 | 342 | 343 | 344 | === TEST 23: map op (copy) on set_quote_pgsql_str 345 | --- config 346 | location /foo { 347 | array_split ',' $arg_names to=$names; 348 | array_map_op set_quote_pgsql_str $names to=$list; 349 | array_join '+' $list to=$res; 350 | echo $res; 351 | } 352 | --- request 353 | GET /foo?names=bob,marry,nomas 354 | --- response_body 355 | E'bob'+E'marry'+E'nomas' 356 | 357 | 358 | 359 | === TEST 24: map op (copy) on set_quote_json_str 360 | --- config 361 | location /foo { 362 | array_split ',' $arg_names to=$names; 363 | array_map_op set_quote_json_str $names to=$list; 364 | array_join '+' $list to=$res; 365 | echo $res; 366 | } 367 | --- request 368 | GET /foo?names=bob,marry,nomas 369 | --- response_body 370 | "bob"+"marry"+"nomas" 371 | 372 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | array-var-nginx-module - Add support for array-typed variables to nginx config files 5 | 6 | *This module is not distributed with the Nginx source.* See the 7 | installation instructions. 8 | 9 | Table of Contents 10 | ================= 11 | 12 | * [Name](#name) 13 | * [Status](#status) 14 | * [Synopsis](#synopsis) 15 | * [Description](#description) 16 | * [Directives](#directives) 17 | * [array_split](#array_split) 18 | * [array_join](#array_join) 19 | * [array_map](#array_map) 20 | * [array_map_op](#array_map_op) 21 | * [Installation](#installation) 22 | * [Building as a dynamic module](#building-as-a-dynamic-module) 23 | * [Compatibility](#compatibility) 24 | * [Source Repository](#source-repository) 25 | * [Getting involved](#getting-involved) 26 | * [Author](#author) 27 | * [Copyright & License](#copyright--license) 28 | * [See Also](#see-also) 29 | 30 | Status 31 | ====== 32 | 33 | This module is production ready. 34 | 35 | Synopsis 36 | ======== 37 | 38 | ```nginx 39 | location /foo { 40 | array_split ',' $arg_files to=$array; 41 | 42 | # use the set_quote_sql_str directive in the ngx_set_misc 43 | # module to map to each element in the array $array: 44 | array_map_op set_quote_sql_str $array; 45 | 46 | array_map "name = $array_it" $array; 47 | 48 | array_join ' or ' $array to=$sql_condition; 49 | 50 | # well, we could feed it to ngx_drizzle to talk to MySQL, for example ;) 51 | echo "select * from files where $sql_condition"; 52 | } 53 | ``` 54 | 55 | Description 56 | =========== 57 | 58 | This module provides array typed nginx variables to `nginx.conf`. 59 | 60 | Under the hood, this module just "abuses" the nginx string values to hold binary pointers 61 | to C data structures (NGINX core's `ngx_array_t` struct on the C land). 62 | 63 | The array type gives `nginx.onf` wonderful capabilities of handling value lists. Nowadays, however, 64 | you are highly recommended to use the [ngx_lua](https://github.com/openresty/lua-nginx-module) module 65 | so as to have the full scripting power provided by the Lua language in nginx. 66 | 67 | [Back to TOC](#table-of-contents) 68 | 69 | Directives 70 | ========== 71 | 72 | [Back to TOC](#table-of-contents) 73 | 74 | array_split 75 | ----------- 76 | **syntax:** *array_split <separator> <subject> to=$target_variable* 77 | 78 | **default:** *no* 79 | 80 | **context:** *http, server, server if, location, location if* 81 | 82 | Splits the string value in the `subject` argument with the separator string specified by the 83 | `separator` argument. The result is an array-typed value saved to the nginx variable specified by the `to=VAR` option. 84 | 85 | For example, 86 | 87 | ```nginx 88 | array_split "," $arg_names to=$names; 89 | ``` 90 | 91 | will split the string values in the URI query argument `names` into an array-typed value saved to the custom nginx variable 92 | `$names`. 93 | 94 | This directive creates an array-typed variable. Array-typed variables cannot be used outside 95 | the directives offered by this module. If you want to use the values in an array-typed variable 96 | in other contexts, 97 | you must use the [array_join](#array_join) directive to produce a normal string value. 98 | 99 | [Back to TOC](#table-of-contents) 100 | 101 | array_join 102 | ---------- 103 | **syntax:** *array_split <separator> $array_var* 104 | 105 | **default:** *no* 106 | 107 | **context:** *http, server, server if, location, location if* 108 | 109 | Joins the elements in the array-typed nginx variable (`$array_var`) into a single string value 110 | with the separator specified by the first argument. 111 | 112 | For example, 113 | 114 | ```nginx 115 | location /foo { 116 | array_split ',' $arg_names to=$names; 117 | array_join '+' $names; 118 | echo $names; 119 | } 120 | ``` 121 | 122 | Then request `GET /foo?names=Bob,Marry,John` will yield the response body 123 | 124 | ``` 125 | Bob+Marry+John 126 | ``` 127 | 128 | In the example above, we use the [ngx_echo](https://github.com/openresty/echo-nginx-module) module's [echo](https://github.com/openresty/echo-nginx-module#echo) directive to output 129 | the final result. 130 | 131 | [Back to TOC](#table-of-contents) 132 | 133 | array_map 134 | --------- 135 | **syntax:** *array_map <template> $array_var* 136 | 137 | **syntax:** *array_map <template> $array_var to=$new_array_var* 138 | 139 | **default:** *no* 140 | 141 | **context:** *http, server, server if, location, location if* 142 | 143 | Maps the string template to each element in the array-typed nginx variable specified. Within 144 | the string template, you can use the special iterator variable `$array_it` to reference the current 145 | array element in the array being mapped. 146 | 147 | For example, 148 | 149 | ```nginx 150 | array_map "[$array_it]" $names; 151 | ``` 152 | 153 | will change each element in the array variable `$names` by putting the square brackets around 154 | each element's string value. The modification is in-place in this case. 155 | 156 | If you do not want in-place modifications, you can use the `to=$var` option to specify a new nginx variable to hold the results. For instance, 157 | 158 | ```nginx 159 | array_map "[$array_it]" $names to=$new_names; 160 | ``` 161 | 162 | where the results are saved into another (array-typed) nginx variable named `$new_names` while 163 | the `$names` variable keeps intact. 164 | 165 | Below is a complete example for this: 166 | 167 | ```nginx 168 | location /foo { 169 | array_split ',' $arg_names to=$names; 170 | array_map '[$array_it]' $names; 171 | array_join '+' $names; 172 | echo "$names"; 173 | } 174 | ``` 175 | 176 | Then request `GET /foo?names=bob,marry,nomas` will yield the response body 177 | 178 | ``` 179 | [bob]+[marry]+[nomas] 180 | ``` 181 | 182 | [Back to TOC](#table-of-contents) 183 | 184 | array_map_op 185 | ------------ 186 | **syntax:** *array_map_op <directive> $array_var* 187 | 188 | **syntax:** *array_map_op <directive> $array_var to=$new_array_var* 189 | 190 | **default:** *no* 191 | 192 | **context:** *http, server, server if, location, location if* 193 | 194 | Similar to the [array_map](#array_map) directive but maps the specified nginx configuration directive instead of 195 | a string template to each element in the array-typed nginx variable specified. The result 196 | of applying the specified configuration directive becomes the result of the mapping. 197 | 198 | The nginx configuration directive being used as the iterator must be implemented by [Nginx Devel Kit](https://github.com/simpl/ngx_devel_kit) (NDK)'s set_var submodule's `ndk_set_var_value`. 199 | For example, the following [set-misc-nginx-module](http://github.com/openresty/set-misc-nginx-module) directives can be invoked this way: 200 | 201 | * [set_quote_sql_str](http://github.com/openresty/set-misc-nginx-module#set_quote_sql_str) 202 | * [set_quote_pgsql_str](http://github.com/openresty/set-misc-nginx-module#set_quote_pgsql_str) 203 | * [set_quote_json_str](http://github.com/openresty/set-misc-nginx-module#set_quote_json_str) 204 | * [set_unescape_uri](http://github.com/openresty/set-misc-nginx-module#set_unescape_uri) 205 | * [set_escape_uri](http://github.com/openresty/set-misc-nginx-module#set_escape_uri) 206 | * [set_encode_base32](http://github.com/openresty/set-misc-nginx-module#set_encode_base32) 207 | * [set_decode_base32](http://github.com/openresty/set-misc-nginx-module#set_decode_base32) 208 | * [set_encode_base64](http://github.com/openresty/set-misc-nginx-module#set_encode_base64) 209 | * [set_decode_base64](http://github.com/openresty/set-misc-nginx-module#set_decode_base64) 210 | * [set_encode_hex](http://github.com/openresty/set-misc-nginx-module#set_encode_base64) 211 | * [set_decode_hex](http://github.com/openresty/set-misc-nginx-module#set_decode_base64) 212 | * [set_sha1](http://github.com/openresty/set-misc-nginx-module#set_encode_base64) 213 | * [set_md5](http://github.com/openresty/set-misc-nginx-module#set_decode_base64) 214 | 215 | This is a higher-order operation where other nginx configuration directives can be used 216 | as arguments for this `map_array_op` directive. 217 | 218 | Consider the following example, 219 | 220 | ```nginx 221 | array_map_op set_quote_sql_str $names; 222 | ``` 223 | 224 | This line changes each element in the array-typed nginx variable `$names` by applying the 225 | [set_quote_sql_str](https://github.com/openresty/set-misc-nginx-module#set_quote_sql_str) 226 | directive provided by the [ngx_set_misc](https://github.com/openresty/set-misc-nginx-module) 227 | module one by one. The result is that each element in the array `$names` has been escaped as SQL string literal values. 228 | 229 | You can also specify the `to=$var` option if you do not want in-place modifications of the input arrays. For instance, 230 | 231 | ```nginx 232 | array_map_op set_quote_sql_str $names to=$quoted_names; 233 | ``` 234 | 235 | will save the escaped elements into a new (array-typed) nginx variable named `$quoted_names` with `$names` intact. 236 | 237 | The following is a relatively complete example: 238 | 239 | ```nginx 240 | location /foo { 241 | array_split ',' $arg_names to=$names; 242 | array_map_op set_quote_sql_str $names; 243 | array_join '+' $names to=$res; 244 | echo $res; 245 | } 246 | ``` 247 | 248 | Then request `GET /foo?names=bob,marry,nomas` will yield the response body 249 | 250 | ``` 251 | 'bob'+'marry'+'nomas' 252 | ``` 253 | 254 | Pretty cool, huh? 255 | 256 | [Back to TOC](#table-of-contents) 257 | 258 | Installation 259 | ============ 260 | 261 | You're recommended to install this module (as well as the Nginx core and many other goodies) via the [OpenResty bundle](http://openresty.org). See [the detailed instructions](http://openresty.org/#Installation) for downloading and installing OpenResty into your system. This is the easiest and most safe way to set things up. 262 | 263 | Alternatively, you can install this module manually with the Nginx source: 264 | 265 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, 266 | the version 1.13.6 (see [nginx compatibility](#compatibility)), and then build the source with this module: 267 | 268 | ```bash 269 | wget 'http://nginx.org/download/nginx-1.13.6.tar.gz' 270 | tar -xzvf nginx-1.13.6.tar.gz 271 | cd nginx-1.13.6/ 272 | 273 | # Here we assume you would install you nginx under /opt/nginx/. 274 | ./configure --prefix=/opt/nginx \ 275 | --add-module=/path/to/ngx_devel_kit \ 276 | --add-module=/path/to/array-var-nginx-module 277 | 278 | make -j2 279 | make install 280 | ``` 281 | 282 | Download the latest version of the release tarball of this module from [array-var-nginx-module file list](https://github.com/openresty/array-var-nginx-module/tags), and the latest tarball for [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) from its [file list](https://github.com/simplresty/ngx_devel_kit/tags). 283 | 284 | Also, this module is included and enabled by default in the [OpenResty bundle](http://openresty.org). 285 | 286 | [Back to TOC](#table-of-contents) 287 | 288 | Building as a dynamic module 289 | ---------------------------- 290 | 291 | Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the `--add-dynamic-module=PATH` option instead of `--add-module=PATH` on the 292 | `./configure` command line above. And then you can explicitly load the module in your `nginx.conf` via the [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) 293 | directive, for example, 294 | 295 | ```nginx 296 | load_module /path/to/modules/ndk_http_module.so; # assuming NDK is built as a dynamic module too 297 | load_module /path/to/modules/ngx_http_array_var_module.so; 298 | ``` 299 | 300 | [Back to TOC](#table-of-contents) 301 | 302 | Compatibility 303 | ============== 304 | 305 | The following versions of Nginx should work with this module: 306 | 307 | * **1.13.x** (last tested: 1.13.6) 308 | * **1.12.x** 309 | * **1.11.x** (last tested: 1.11.2) 310 | * **1.10.x** 311 | * **1.9.x** (last tested: 1.9.7) 312 | * **1.8.x** 313 | * **1.7.x** (last tested: 1.7.10) 314 | * **1.6.x** 315 | * **1.5.x** (last tested: 1.5.12) 316 | * **1.4.x** (last tested: 1.4.2) 317 | * **1.2.x** (last tested: 1.2.9) 318 | * **1.1.x** (last tested: 1.1.5) 319 | * **1.0.x** (last tested: 1.0.8) 320 | * **0.9.x** (last tested: 0.9.4) 321 | * **0.8.x** (last tested: 0.8.54) 322 | * **0.7.x >= 0.7.44** (last tested: 0.7.68) 323 | 324 | Earlier versions of Nginx like 0.6.x and 0.5.x will *not* work. 325 | 326 | If you find that any particular version of Nginx above 0.7.44 does not 327 | work with this module, please consider reporting a bug. 328 | 329 | [Back to TOC](#table-of-contents) 330 | 331 | Source Repository 332 | ================= 333 | 334 | Available on github at [openresty/array-var-nginx-module](https://github.com/openresty/array-var-nginx-module). 335 | 336 | [Back to TOC](#table-of-contents) 337 | 338 | Getting involved 339 | ================ 340 | 341 | You'll be very welcomed to submit patches to the author or just ask for 342 | a commit bit to the source repository on GitHub. 343 | 344 | [Back to TOC](#table-of-contents) 345 | 346 | Author 347 | ====== 348 | 349 | Yichun "agentzh" Zhang (章亦春) <agentzh@gmail.com>, CloudFlare Inc. 350 | 351 | [Back to TOC](#table-of-contents) 352 | 353 | Copyright & License 354 | =================== 355 | 356 | Copyright (c) 2009-2016, Yichun Zhang (agentzh) <agentzh@gmail.com>, CloudFlare Inc. 357 | 358 | This module is licensed under the terms of the BSD license. 359 | 360 | Redistribution and use in source and binary forms, with or without 361 | modification, are permitted provided that the following conditions are 362 | met: 363 | 364 | * Redistributions of source code must retain the above copyright 365 | notice, this list of conditions and the following disclaimer. 366 | * Redistributions in binary form must reproduce the above copyright 367 | notice, this list of conditions and the following disclaimer in the 368 | documentation and/or other materials provided with the distribution. 369 | 370 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 371 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 372 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 373 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 374 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 375 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 376 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 377 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 378 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 379 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 380 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 381 | 382 | [Back to TOC](#table-of-contents) 383 | 384 | See Also 385 | ======== 386 | 387 | * [NDK](https://github.com/simpl/ngx_devel_kit) 388 | * [ngx_lua](https://github.com/openresty/lua-nginx-module) 389 | * [ngx_set_misc](https://github.com/openresty/set-misc-nginx-module) 390 | 391 | [Back to TOC](#table-of-contents) 392 | 393 | -------------------------------------------------------------------------------- /src/ngx_http_array_var_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_array_var_util.h" 14 | #include 15 | 16 | 17 | static ngx_str_t ngx_http_array_it_key = ngx_string("array_it"); 18 | 19 | 20 | typedef struct { 21 | ngx_uint_t nargs; 22 | } ngx_http_array_split_data_t; 23 | 24 | 25 | typedef struct { 26 | unsigned in_place; 27 | ngx_http_complex_value_t *template; 28 | ngx_int_t array_it_index; 29 | } ngx_http_array_map_data_t; 30 | 31 | 32 | typedef struct { 33 | unsigned in_place; 34 | } ngx_http_array_map_op_data_t; 35 | 36 | 37 | static char *ngx_http_array_split(ngx_conf_t *cf, ngx_command_t *cmd, 38 | void *conf); 39 | static char *ngx_http_array_map(ngx_conf_t *cf, ngx_command_t *cmd, 40 | void *conf); 41 | static char *ngx_http_array_map_op(ngx_conf_t *cf, ngx_command_t *cmd, 42 | void *conf); 43 | static char *ngx_http_array_join(ngx_conf_t *cf, ngx_command_t *cmd, 44 | void *conf); 45 | static ngx_int_t ngx_http_array_var_split(ngx_http_request_t *r, 46 | ngx_str_t *res, ngx_http_variable_value_t *v, void *data); 47 | static ngx_int_t ngx_http_array_var_map(ngx_http_request_t *r, 48 | ngx_str_t *res, ngx_http_variable_value_t *v, void *data); 49 | static ngx_int_t ngx_http_array_var_map_op(ngx_http_request_t *r, 50 | ngx_str_t *res, ngx_http_variable_value_t *v, void *data); 51 | static ngx_int_t ngx_http_array_var_join(ngx_http_request_t *r, 52 | ngx_str_t *res, ngx_http_variable_value_t *v); 53 | 54 | 55 | static ngx_command_t ngx_http_array_var_commands[] = { 56 | { 57 | ngx_string ("array_split"), 58 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF 59 | |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_2MORE, 60 | ngx_http_array_split, 61 | 0, 62 | 0, 63 | NULL 64 | }, 65 | { 66 | ngx_string ("array_map"), 67 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF 68 | |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 69 | |NGX_CONF_TAKE23, 70 | ngx_http_array_map, 71 | 0, 72 | 0, 73 | NULL 74 | }, 75 | { 76 | ngx_string ("array_map_op"), 77 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF 78 | |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 79 | |NGX_CONF_TAKE23, 80 | ngx_http_array_map_op, 81 | 0, 82 | 0, 83 | (void *) ngx_http_array_var_map_op 84 | }, 85 | { 86 | ngx_string("array_join"), 87 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF 88 | |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 89 | |NGX_CONF_TAKE23, 90 | ngx_http_array_join, 91 | 0, 92 | 0, 93 | (void *) ngx_http_array_var_join 94 | }, 95 | 96 | ngx_null_command 97 | }; 98 | 99 | 100 | static ngx_http_module_t ngx_http_array_var_module_ctx = { 101 | NULL, /* preconfiguration */ 102 | NULL, /* postconfiguration */ 103 | 104 | NULL, /* create main configuration */ 105 | NULL, /* init main configuration */ 106 | 107 | NULL, /* create server configuration */ 108 | NULL, /* merge server configuration */ 109 | 110 | NULL, /* create location configuration */ 111 | NULL, /* merge location configuration */ 112 | }; 113 | 114 | 115 | ngx_module_t ngx_http_array_var_module = { 116 | NGX_MODULE_V1, 117 | &ngx_http_array_var_module_ctx, /* module context */ 118 | ngx_http_array_var_commands, /* module directives */ 119 | NGX_HTTP_MODULE, /* module type */ 120 | NULL, /* init master */ 121 | NULL, /* init module */ 122 | NULL, /* init process */ 123 | NULL, /* init thread */ 124 | NULL, /* exit thread */ 125 | NULL, /* exit process */ 126 | NULL, /* exit master */ 127 | NGX_MODULE_V1_PADDING 128 | }; 129 | 130 | 131 | static char * 132 | ngx_http_array_split(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 133 | { 134 | ndk_set_var_t filter; 135 | ngx_str_t target; 136 | ngx_str_t *value; 137 | ngx_str_t *bad_arg; 138 | ngx_http_array_split_data_t *data = NULL; 139 | 140 | data = ngx_palloc(cf->pool, sizeof(ngx_http_array_split_data_t)); 141 | if (data == NULL) { 142 | return NGX_CONF_ERROR; 143 | } 144 | 145 | filter.type = NDK_SET_VAR_MULTI_VALUE_DATA; 146 | filter.func = (void *) ngx_http_array_var_split; 147 | filter.data = data; 148 | 149 | value = cf->args->elts; 150 | 151 | if (cf->args->nelts == 2 + 1) { 152 | dd("array_split $sep $var"); 153 | data->nargs = filter.size = 2; 154 | target = value[2]; 155 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 156 | } 157 | 158 | /* cf->args->nelts >= 3 + 1 */ 159 | 160 | if (value[3].len >= sizeof("to=") - 1 161 | && ngx_str3cmp(value[3].data, 't', 'o', '=')) 162 | { 163 | dd("array_split $sep $str to=$array"); 164 | data->nargs = filter.size = 2; 165 | 166 | target.data = value[3].data + sizeof("to=") - 1; 167 | target.len = value[3].len - (sizeof("to=") - 1); 168 | dd("split target: %.*s", (int) target.len, target.data); 169 | 170 | if (cf->args->nelts > 3 + 1) { 171 | bad_arg = &value[4]; 172 | goto unexpected_arg; 173 | } 174 | 175 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 176 | } 177 | 178 | /* the 3rd argument is max_items */ 179 | 180 | if (cf->args->nelts > 4 + 1) { 181 | bad_arg = &value[5]; 182 | goto unexpected_arg; 183 | } 184 | 185 | if (cf->args->nelts == 4 + 1) { 186 | /* array_split $sep $str $max to=$array */ 187 | 188 | if (value[4].len < sizeof("to=") - 1 189 | || ! (ngx_str3cmp(value[4].data, 't', 'o', '='))) 190 | { 191 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, 192 | "%V: expecting the \"to\" option at the " 193 | "4th argument: \"%V\"", 194 | &cmd->name, &value[4]); 195 | 196 | return NGX_CONF_ERROR; 197 | } 198 | 199 | data->nargs = filter.size = 3; 200 | 201 | target.data = value[4].data + sizeof("to=") - 1; 202 | target.len = value[4].len - (sizeof("to=") - 1); 203 | 204 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 205 | } 206 | 207 | /* cf->args->nelts == 3 + 1 */ 208 | 209 | /* array_split $sep $var $max */ 210 | 211 | target = value[2]; 212 | data->nargs = filter.size = 3; 213 | 214 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 215 | 216 | unexpected_arg: 217 | 218 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V: unexpected argument \"%V\"", 219 | &cmd->name, bad_arg); 220 | 221 | return NGX_CONF_ERROR; 222 | } 223 | 224 | 225 | static char * 226 | ngx_http_array_map(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 227 | { 228 | ndk_set_var_t filter; 229 | ngx_str_t target; 230 | ngx_str_t *value; 231 | ngx_http_array_map_data_t *data; 232 | ngx_http_compile_complex_value_t ccv; 233 | 234 | data = ngx_palloc(cf->pool, sizeof(ngx_http_array_map_data_t)); 235 | if (data == NULL) { 236 | return NGX_CONF_ERROR; 237 | } 238 | 239 | data->template = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 240 | if (data->template == NULL) { 241 | return NGX_CONF_ERROR; 242 | } 243 | 244 | value = cf->args->elts; 245 | 246 | if (value[1].len == 0) { 247 | ngx_memzero(data->template, sizeof(ngx_http_complex_value_t)); 248 | 249 | } else { 250 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 251 | 252 | ccv.cf = cf; 253 | ccv.value = &value[1]; 254 | ccv.complex_value = data->template; 255 | 256 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 257 | return NGX_CONF_ERROR; 258 | } 259 | } 260 | 261 | filter.type = NDK_SET_VAR_VALUE_DATA; 262 | filter.func = (void *) ngx_http_array_var_map; 263 | filter.data = data; 264 | filter.size = 1; 265 | 266 | data->array_it_index = ngx_http_array_var_add_variable(cf, 267 | &ngx_http_array_it_key); 268 | 269 | if (data->array_it_index == NGX_ERROR) { 270 | return NGX_CONF_ERROR; 271 | } 272 | 273 | if (cf->args->nelts == 2 + 1) { 274 | /* array_map $template $array */ 275 | data->in_place = 1; 276 | target = value[2]; 277 | 278 | } else { 279 | /* cf->args->nelts == 3 + 1 */ 280 | 281 | if (value[3].len < sizeof("to=") - 1 282 | || ! (ngx_str3cmp(value[3].data, 't', 'o', '='))) 283 | { 284 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 285 | "%V: expecting the \"to\" option at " 286 | "the 3rd argument: \"%V\"", 287 | &cmd->name, &value[3]); 288 | 289 | return NGX_CONF_ERROR; 290 | } 291 | 292 | target.data = value[3].data + sizeof("to=") - 1; 293 | target.len = value[3].len - (sizeof("to=") - 1); 294 | data->in_place = 0; 295 | } 296 | 297 | return ndk_set_var_value_core(cf, &target, &value[2], &filter); 298 | } 299 | 300 | 301 | static char * 302 | ngx_http_array_map_op(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 303 | { 304 | ngx_http_array_map_op_data_t *data; 305 | ndk_set_var_t filter; 306 | ngx_str_t target; 307 | ngx_str_t *value; 308 | ngx_str_t *bad_arg; 309 | 310 | data = ngx_palloc(cf->pool, sizeof(ngx_http_array_map_op_data_t)); 311 | if (data == NULL) { 312 | return NGX_CONF_ERROR; 313 | } 314 | 315 | filter.type = NDK_SET_VAR_MULTI_VALUE_DATA; 316 | filter.func = cmd->post; 317 | filter.data = data; 318 | 319 | value = cf->args->elts; 320 | 321 | if (cf->args->nelts == 2 + 1) { 322 | dd("array_join $sep $var"); 323 | 324 | filter.size = 2; 325 | data->in_place = 1; 326 | 327 | target = value[2]; 328 | 329 | dd("array join target: %.*s", (int) target.len, target.data); 330 | 331 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 332 | } 333 | 334 | /* cf->args->nelts == 3 + 1 */ 335 | 336 | if (value[3].len >= sizeof("to=") - 1 337 | && ngx_str3cmp(value[3].data, 't', 'o', '=')) 338 | { 339 | /* array_join $sep $str to=$array */ 340 | filter.size = 2; 341 | data->in_place = 0; 342 | 343 | target.data = value[3].data + sizeof("to=") - 1; 344 | target.len = value[3].len - (sizeof("to=") - 1); 345 | 346 | if (cf->args->nelts > 3 + 1) { 347 | bad_arg = &value[4]; 348 | 349 | } else { 350 | return ndk_set_var_multi_value_core(cf, &target, &value[1], 351 | &filter); 352 | } 353 | 354 | } else { 355 | bad_arg = &value[3]; 356 | } 357 | 358 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 359 | "%V: unexpected argument \"%V\"", 360 | &cmd->name, bad_arg); 361 | 362 | return NGX_CONF_ERROR; 363 | } 364 | 365 | 366 | static char * 367 | ngx_http_array_join(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 368 | { 369 | ndk_set_var_t filter; 370 | ngx_str_t target; 371 | ngx_str_t *value; 372 | ngx_str_t *bad_arg; 373 | 374 | filter.type = NDK_SET_VAR_MULTI_VALUE; 375 | filter.func = cmd->post; 376 | 377 | value = cf->args->elts; 378 | 379 | if (cf->args->nelts == 2 + 1) { 380 | dd("array_join $sep $var"); 381 | 382 | filter.size = 2; 383 | target = value[2]; 384 | 385 | dd("array join target: %.*s", (int) target.len, target.data); 386 | 387 | return ndk_set_var_multi_value_core(cf, &target, &value[1], &filter); 388 | } 389 | 390 | /* cf->args->nelts == 3 + 1 */ 391 | 392 | if (value[3].len >= sizeof("to=") - 1 393 | && ngx_str3cmp(value[3].data, 't', 'o', '=')) 394 | { 395 | /* array_join $sep $str to=$array */ 396 | filter.size = 2; 397 | 398 | target.data = value[3].data + sizeof("to=") - 1; 399 | target.len = value[3].len - (sizeof("to=") - 1); 400 | 401 | if (cf->args->nelts > 3 + 1) { 402 | bad_arg = &value[4]; 403 | } else { 404 | return ndk_set_var_multi_value_core(cf, &target, &value[1], 405 | &filter); 406 | } 407 | 408 | } else { 409 | bad_arg = &value[3]; 410 | } 411 | 412 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 413 | "%V: unexpected argument \"%V\"", 414 | &cmd->name, bad_arg); 415 | 416 | return NGX_CONF_ERROR; 417 | } 418 | 419 | 420 | static ngx_int_t 421 | ngx_http_array_var_split(ngx_http_request_t *r, ngx_str_t *res, 422 | ngx_http_variable_value_t *v, void *data) 423 | { 424 | ngx_http_array_split_data_t *conf = data; 425 | ngx_http_variable_value_t *sep, *str; 426 | ngx_str_t *s; 427 | u_char *pos, *end, *last = NULL; 428 | ssize_t max, i, len = 4; 429 | ngx_array_t *array; 430 | 431 | if (conf->nargs == 3) { 432 | max = ngx_atosz(v[2].data, v[2].len); 433 | if (max == NGX_ERROR) { 434 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 435 | "array_split: invalid max items: \"%V\"", 436 | &v[2]); 437 | 438 | return NGX_ERROR; 439 | } 440 | } else { 441 | max = 0; 442 | } 443 | 444 | if (max) { 445 | len = max; 446 | } 447 | 448 | array = ngx_array_create(r->pool, len, sizeof(ngx_str_t)); 449 | if (array == NULL) { 450 | return NGX_ERROR; 451 | } 452 | 453 | sep = &v[0]; 454 | str = &v[1]; 455 | 456 | pos = str->data; 457 | end = str->data + str->len; 458 | 459 | i = 0; 460 | 461 | if (sep->len == 0) { 462 | /* split each char into an array elem */ 463 | 464 | while (i != max - 1 && pos < end - 1) { 465 | s = ngx_array_push(array); 466 | if (s == NULL) { 467 | return NGX_ERROR; 468 | } 469 | 470 | s->data = pos; 471 | s->len = 1; 472 | 473 | pos++; 474 | i++; 475 | } 476 | 477 | goto done; 478 | } 479 | 480 | while (i != max - 1) { 481 | last = ngx_http_array_var_strlstrn(pos, end, sep->data, 482 | sep->len - 1); 483 | if (last == NULL) { 484 | break; 485 | } 486 | 487 | s = ngx_array_push(array); 488 | if (s == NULL) { 489 | return NGX_ERROR; 490 | } 491 | 492 | s->data = pos; 493 | s->len = last - pos; 494 | 495 | dd("split item %.*s", (int) s->len, s->data); 496 | 497 | pos = last + sep->len; 498 | i++; 499 | } 500 | 501 | done: 502 | 503 | dd("pos %p, last %p, end %p", pos, last, end); 504 | 505 | s = ngx_array_push(array); 506 | if (s == NULL) { 507 | return NGX_ERROR; 508 | } 509 | 510 | s->data = pos; 511 | s->len = end - pos; 512 | 513 | dd("split item %.*s", (int) s->len, s->data); 514 | 515 | dd("split: array size: %d", (int) array->nelts); 516 | dd("split array ptr: %p", array); 517 | 518 | res->data = (u_char *) array; 519 | res->len = sizeof(ngx_array_t); 520 | 521 | return NGX_OK; 522 | } 523 | 524 | 525 | static ngx_int_t 526 | ngx_http_array_var_map(ngx_http_request_t *r, ngx_str_t *res, 527 | ngx_http_variable_value_t *v, void *data) 528 | { 529 | ngx_http_array_map_data_t *conf = data; 530 | ngx_http_variable_value_t *array_it; 531 | ngx_uint_t i; 532 | ngx_str_t *value, *new_value; 533 | ngx_array_t *array, *new_array; 534 | 535 | dd("entered array var map"); 536 | 537 | if (conf->template == NULL) { 538 | dd("template empty"); 539 | 540 | return NGX_OK; 541 | } 542 | 543 | if (v[0].len != sizeof(ngx_array_t)) { 544 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 545 | "array_join: invalid array variable value in the 2nd " 546 | "argument: \"%.*s\"", &v[0]); 547 | 548 | return NGX_ERROR; 549 | } 550 | 551 | array = (ngx_array_t *) v[0].data; 552 | 553 | value = array->elts; 554 | 555 | array_it = ngx_http_get_indexed_variable(r, conf->array_it_index); 556 | if (array_it == NULL) { 557 | return NGX_ERROR; 558 | } 559 | 560 | if (conf->in_place) { 561 | new_array = array; 562 | 563 | } else { 564 | new_array = ngx_array_create(r->pool, array->nelts, 565 | sizeof(ngx_str_t)); 566 | if (new_array == NULL) { 567 | return NGX_ERROR; 568 | } 569 | } 570 | 571 | dd("array var map: array size: %d", (int) array->nelts); 572 | 573 | array_it->not_found = 0; 574 | array_it->valid = 1; 575 | 576 | for (i = 0; i < array->nelts; i++) { 577 | array_it->data = value[i].data; 578 | array_it->len = value[i].len; 579 | 580 | dd("array it: %.*s", array_it->len, array_it->data); 581 | 582 | if (conf->in_place) { 583 | new_value = &value[i]; 584 | 585 | } else { 586 | new_value = ngx_array_push(new_array); 587 | if (new_value == NULL) { 588 | return NGX_ERROR; 589 | } 590 | } 591 | 592 | if (ngx_http_complex_value(r, conf->template, new_value) != NGX_OK) { 593 | return NGX_ERROR; 594 | } 595 | 596 | dd("array var map: new item: %.*s", (int) new_value->len, 597 | new_value->data); 598 | } 599 | 600 | array_it->not_found = 1; 601 | array_it->valid = 0; 602 | 603 | res->data = (u_char *) new_array; 604 | res->len = sizeof(ngx_array_t); 605 | 606 | return NGX_OK; 607 | } 608 | 609 | 610 | static ngx_int_t 611 | ngx_http_array_var_map_op(ngx_http_request_t *r, 612 | ngx_str_t *res, ngx_http_variable_value_t *v, void *data) 613 | { 614 | ngx_http_variable_value_t arg; 615 | ngx_http_array_map_op_data_t *conf = data; 616 | ndk_set_var_value_pt func; 617 | ngx_int_t rc; 618 | ngx_uint_t i; 619 | ngx_str_t *value; 620 | ngx_str_t *new_value; 621 | ngx_array_t *array; 622 | ngx_array_t *new_array; 623 | 624 | func = ngx_http_array_var_get_func_from_cmd(v[0].data, v[0].len); 625 | 626 | if (func == NULL) { 627 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 628 | "array_map_op: directive \"%v\" not found " 629 | "or does not use ndk_set_var_value", 630 | &v[0]); 631 | 632 | return NGX_ERROR; 633 | } 634 | 635 | if (v[1].len != sizeof(ngx_array_t)) { 636 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 637 | "array_map_op: invalid array variable value in the 2nd " 638 | "argument: \"%.*s\"", &v[0]); 639 | 640 | return NGX_ERROR; 641 | } 642 | 643 | array = (ngx_array_t *) v[1].data; 644 | 645 | value = array->elts; 646 | 647 | if (conf->in_place) { 648 | new_array = array; 649 | 650 | } else { 651 | new_array = ngx_array_create(r->pool, array->nelts, 652 | sizeof(ngx_str_t)); 653 | if (new_array == NULL) { 654 | return NGX_ERROR; 655 | } 656 | } 657 | 658 | for (i = 0; i < array->nelts; i++) { 659 | arg.data = value[i].data; 660 | arg.len = value[i].len; 661 | arg.valid = 1; 662 | arg.not_found = 0; 663 | 664 | if (conf->in_place) { 665 | new_value = &value[i]; 666 | 667 | } else { 668 | new_value = ngx_array_push(new_array); 669 | if (new_value == NULL) { 670 | return NGX_ERROR; 671 | } 672 | } 673 | 674 | rc = func(r, new_value, &arg); 675 | if (rc != NGX_OK) { 676 | return NGX_ERROR; 677 | } 678 | } 679 | 680 | res->data = (u_char *) new_array; 681 | res->len = sizeof(ngx_array_t); 682 | 683 | return NGX_OK; 684 | } 685 | 686 | 687 | static ngx_int_t 688 | ngx_http_array_var_join(ngx_http_request_t *r, 689 | ngx_str_t *res, ngx_http_variable_value_t *v) 690 | { 691 | ngx_http_variable_value_t *sep; 692 | ngx_array_t *array; 693 | size_t len; 694 | ngx_str_t *value; 695 | ngx_uint_t i; 696 | u_char *p; 697 | 698 | sep = &v[0]; 699 | 700 | dd("sep %.*s", sep->len, sep->data); 701 | 702 | if (v[1].len != sizeof(ngx_array_t)) { 703 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 704 | "array_join: invalid array variable value in the " 705 | "2nd argument: \"%V\"", &v[1]); 706 | 707 | return NGX_ERROR; 708 | } 709 | 710 | array = (ngx_array_t *) v[1].data; 711 | 712 | dd("join array ptr %p", array); 713 | dd("array->nelts: %d", (int) array->nelts); 714 | 715 | if (array->nelts == 0) { 716 | res->data = NULL; 717 | res->len = 0; 718 | return NGX_OK; 719 | } 720 | 721 | value = array->elts; 722 | 723 | len = sep->len * (array->nelts - 1); 724 | 725 | for (i = 0; i < array->nelts; i++) { 726 | len += value[i].len; 727 | } 728 | 729 | dd("buf len %d", (int) len); 730 | 731 | res->data = ngx_palloc(r->pool, len); 732 | if (res->data == NULL) { 733 | return NGX_ERROR; 734 | } 735 | 736 | res->len = len; 737 | 738 | p = res->data; 739 | 740 | for (i = 0; i < array->nelts; i++) { 741 | dd("copying elem of size %d", (int) value[i].len); 742 | p = ngx_copy(p, value[i].data, value[i].len); 743 | if (i < array->nelts - 1) { 744 | p = ngx_copy(p, sep->data, sep->len); 745 | } 746 | } 747 | 748 | if (p != res->data + res->len) { 749 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 750 | "array_join: buffer error"); 751 | 752 | return NGX_ERROR; 753 | } 754 | 755 | return NGX_OK; 756 | } 757 | --------------------------------------------------------------------------------