├── .gitattributes ├── .gitignore ├── .travis.yml ├── README.markdown ├── config ├── src ├── ddebug.h ├── ngx_http_replace_filter_module.c ├── ngx_http_replace_filter_module.h ├── ngx_http_replace_parse.c ├── ngx_http_replace_parse.h ├── ngx_http_replace_script.c ├── ngx_http_replace_script.h ├── ngx_http_replace_util.c └── ngx_http_replace_util.h ├── t ├── 01-sanity.t ├── 02-max-buffered.t ├── 03-var.t ├── 04-capturing.t ├── 05-capturing-max-buffered.t ├── 06-if.t ├── 07-multi.t ├── 08-gzip.t ├── 09-unused.t ├── 10-last-modified.t └── 11-skip.t ├── util └── build.sh └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mobi 2 | genmobi.sh 3 | .libs 4 | *.swp 5 | *.slo 6 | *.la 7 | *.swo 8 | *.lo 9 | *~ 10 | *.o 11 | print.txt 12 | .rsync 13 | *.tar.gz 14 | dist 15 | build[789] 16 | build 17 | build10 18 | tags 19 | update-readme 20 | *.tmp 21 | go 22 | t/t.sh 23 | releng 24 | reset 25 | *.t_ 26 | ctags 27 | src/stream.h 28 | nginx 29 | keepalive 30 | reindex 31 | src/module.[ch] 32 | all 33 | t/servroot/ 34 | buildroot/ 35 | build1[0-9] 36 | re 37 | *.plist 38 | Makefile 39 | src/script.[ch] 40 | src/parse.[ch] 41 | src/util.[ch] 42 | *.patch 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | compiler: 9 | - gcc 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - axel 15 | - cpanminus 16 | - libgd-dev 17 | - libpcre3-dev 18 | 19 | cache: 20 | apt: true 21 | 22 | env: 23 | global: 24 | - JOBS=3 25 | - NGX_BUILD_JOBS=$JOBS 26 | - LUAJIT_PREFIX=/opt/luajit21 27 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 28 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 29 | - LUA_INCLUDE_DIR=$LUAJIT_INC 30 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 31 | jobs: 32 | - NGINX_VERSION=1.21.4 33 | - NGINX_VERSION=1.25.1 NGX_EXTRA_OPT=--without-pcre2 34 | 35 | before_install: 36 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 37 | 38 | install: 39 | - git clone https://github.com/openresty/openresty.git ../openresty 40 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 41 | - git clone https://github.com/openresty/openresty-devel-utils.git 42 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 43 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 44 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 45 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 46 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 47 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 48 | - git clone https://github.com/openresty/sregex.git 49 | 50 | script: 51 | - cd sregex && sudo make PREFIX=/usr install && cd .. 52 | - cd luajit2/ 53 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) 54 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 55 | - cd .. 56 | - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH 57 | - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) 58 | - nginx -V 59 | - prove -r t 60 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | ngx_replace_filter - Streaming regular expression replacement in response bodies. 5 | 6 | *This module is not distributed with the Nginx source.* See [the installation instructions](#installation). 7 | 8 | Table of Contents 9 | ================= 10 | 11 | * [Name](#name) 12 | * [Status](#status) 13 | * [Synopsis](#synopsis) 14 | * [Description](#description) 15 | * [Directives](#directives) 16 | * [replace_filter](#replace_filter) 17 | * [replace_filter_types](#replace_filter_types) 18 | * [replace_filter_max_buffered_size](#replace_filter_max_buffered_size) 19 | * [replace_filter_last_modified](#replace_filter_last_modified) 20 | * [replace_filter_skip](#replace_filter_skip) 21 | * [Installation](#installation) 22 | * [Trouble Shooting](#trouble-shooting) 23 | * [TODO](#todo) 24 | * [Community](#community) 25 | * [English Mailing List](#english-mailing-list) 26 | * [Chinese Mailing List](#chinese-mailing-list) 27 | * [Bugs and Patches](#bugs-and-patches) 28 | * [Author](#author) 29 | * [Copyright and License](#copyright-and-license) 30 | * [See Also](#see-also) 31 | 32 | Status 33 | ====== 34 | 35 | This module is already quite usable though still at the early phase of development 36 | and is considered experimental. 37 | 38 | Synopsis 39 | ======== 40 | 41 | ```nginx 42 | location /t { 43 | default_type text/html; 44 | echo abc; 45 | replace_filter 'ab|abc' X; 46 | } 47 | 48 | location / { 49 | # proxy_pass/fastcgi_pass/... 50 | 51 | # caseless global substitution: 52 | replace_filter '\d+' 'blah blah' 'ig'; 53 | replace_filter_types text/plain text/css; 54 | } 55 | 56 | location /a { 57 | # proxy_pass/fastcgi_pass/root/... 58 | 59 | # remove line-leading spaces and line-trailing spaces, 60 | # as well as blank lines: 61 | replace_filter '^\s+|\s+$' '' g; 62 | } 63 | 64 | location /b { 65 | # proxy_pass/fastcgi_pass/root/... 66 | 67 | # only remove line-leading spaces and line-trailing spaces: 68 | replace_filter '^[ \f\t]+|[ \f\t]+$' '' g; 69 | } 70 | 71 | location ~ '\.cpp$' { 72 | # proxy_pass/fastcgi_pass/root/... 73 | 74 | replace_filter_types text/plain; 75 | 76 | # skip C/C++ string literals: 77 | replace_filter "'(?:\\\\[^\n]|[^'\n])*'" $& g; 78 | replace_filter '"(?:\\\\[^\n]|[^"\n])*"' $& g; 79 | 80 | # remove all those ugly C/C++ comments: 81 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 82 | } 83 | ``` 84 | 85 | Description 86 | =========== 87 | 88 | This Nginx output filter module tries to do regular expression substitutions in 89 | a non-buffered manner wherever possible. 90 | 91 | This module does *not* use traditional backtracking regular expression engines like PCRE, rather, 92 | it uses the new [sregex](https://github.com/agentzh/sregex) library implemented by the author himself, which was designed with streaming processing in mind from the very beginning: 93 | 94 | A good common subset of Perl 5 regular expressions is supported by `sregex`. For the complete 95 | feature list, check out sregex's documentation: 96 | 97 | https://github.com/agentzh/sregex#syntax-supported 98 | 99 | Response body data is only buffered when absolutely necessary, like facing an incomplete capture that belongs to a possible match near the data chunk boundaries. 100 | 101 | [Back to TOC](#table-of-contents) 102 | 103 | Directives 104 | ========== 105 | 106 | [Back to TOC](#table-of-contents) 107 | 108 | replace_filter 109 | -------------- 110 | **syntax:** *replace_filter <regex> <replace>* 111 | 112 | **syntax:** *replace_filter <regex> <replace> <options>* 113 | 114 | **default:** *no* 115 | 116 | **context:** *http, server, location, location if* 117 | 118 | **phase:** *output body filter* 119 | 120 | Specifies the regex pattern and text to be replaced, with optional regex flags. 121 | 122 | By default, the filter stops matching after the first match is found. This behavior can be changed by specifying the `g` regex option. 123 | 124 | The following regex options are supported: 125 | 126 | * `g` 127 | 128 | for global search and substitution (default off) 129 | * `i` 130 | 131 | for case-insensitive matching (default off) 132 | 133 | Multiple options can be combined in a single string argument, for example: 134 | 135 | ```nginx 136 | replace_filter hello hiya ig; 137 | ``` 138 | 139 | Nginx variables can be interpolated into the text to be replaced, for example: 140 | 141 | ```nginx 142 | replace_filter \w+ "[$foo,$bar]"; 143 | ``` 144 | 145 | If you want to use the literal dollar sign character (`$`), use the `$$` sequence for that, 146 | for instance: 147 | 148 | ```nginx 149 | replace_filter \w "$$"; 150 | ``` 151 | 152 | Use of submatch capturing variables like `$&`, `$1`, `$2`, and etc are also supported, for example, 153 | 154 | ```nginx 155 | replace_filter [bc]|d [$&-$1-$2] g; 156 | ``` 157 | 158 | The semantics of the submatch capturing variables is exactly the same as in the Perl 5 language. 159 | 160 | Multiple `replace_filter` directives in the same scope is also supported. 161 | All the patterns will be applied at the same time as in a tokenizer. 162 | We will *not* use the longest token match semantics, but rather, patterns will be prioritized according to their order in 163 | the configure file. 164 | 165 | Here is an example for removing all the C/C++ comments from a C/C++ source code file: 166 | 167 | ```nginx 168 | replace_filter "'(?:\\\\[^\n]|[^'\n])*'" $& g; 169 | replace_filter '"(?:\\\\[^\n]|[^"\n])*"' $& g; 170 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 171 | ``` 172 | 173 | When the `Content-Encoding` response header is not empty (like `gzip`), the response 174 | body will always remain intact. So usually you want to disable the gzip compression 175 | in your backend servers' responses by adding the following line to your `nginx.conf` 176 | if you are the ngx_proxy module: 177 | 178 | ```nginx 179 | proxy_set_header Accept-Encoding ''; 180 | ``` 181 | 182 | Your responses can still be gzip compressed on the Nginx server level though. 183 | 184 | [Back to TOC](#table-of-contents) 185 | 186 | replace_filter_types 187 | -------------------- 188 | 189 | **syntax:** *replace_filter_types <mime-type> ...* 190 | 191 | **default:** *replace_filter_types text/html* 192 | 193 | **context:** *http, server, location, location if* 194 | 195 | **phase:** *output body filter* 196 | 197 | Specify one or more MIME types (in the `Content-Type` response header) to be processed. 198 | 199 | By default, only `text/html` typed responses are processed. 200 | 201 | [Back to TOC](#table-of-contents) 202 | 203 | replace_filter_max_buffered_size 204 | --------------------------------- 205 | **syntax:** *replace_filter_max_buffered_size <size>* 206 | 207 | **default:** *replace_filter_max_buffered_size 8k* 208 | 209 | **context:** *http, server, location, location if* 210 | 211 | **phase:** *output body filter* 212 | 213 | Limits the total size of the data buffered by the module at runtime. Default to `8k`. 214 | 215 | When the limit is reached, `replace_filter` will immediately stop processing and 216 | leave all the remaining response body data intact. 217 | 218 | [Back to TOC](#table-of-contents) 219 | 220 | replace_filter_last_modified 221 | ---------------------------- 222 | 223 | **syntax:** *replace_filter_last_modifiled keep | clear* 224 | 225 | **default:** *replace_filter_last_modified clear* 226 | 227 | **context:** *http, server, location, location if* 228 | 229 | **phase:** *output body filter* 230 | 231 | Controls how to deal with the existing Last-Modified response header. 232 | 233 | By default, this module will clear the `Last-Modified` response header if there is any. You can specify 234 | 235 | ```nginx 236 | replace_filter_last_modified keep; 237 | ``` 238 | 239 | to always keep the original `Last-Modified` response header. 240 | 241 | [Back to TOC](#table-of-contents) 242 | 243 | replace_filter_skip 244 | ------------------- 245 | 246 | **syntax:** *replace_filter_skip $var* 247 | 248 | **default:** *no* 249 | 250 | **context:** *http, server, location, location if* 251 | 252 | **phase:** *output header filter* 253 | 254 | This directive controls whether to skip all the `replace_filter` rules on a per-request basis. 255 | 256 | Both constant values or strings containing NGINX variables are supported. 257 | 258 | When the value is evaluated to an empty value ("") or the value "0" in the request output header phase, no `replace_filter` rules will be skipped for the current request. Otherwise all the `replace_filter` rules will be skipped for the current request. 259 | 260 | Below is a trivial example for this: 261 | 262 | ```nginx 263 | set $skip ''; 264 | location /t { 265 | content_by_lua ' 266 | ngx.var.skip = 1 267 | ngx.say("abcabd") 268 | '; 269 | replace_filter_skip $skip; 270 | replace_filter abcabd X; 271 | } 272 | ``` 273 | 274 | [Back to TOC](#table-of-contents) 275 | 276 | Installation 277 | ============ 278 | 279 | You need to install the sregex library first: 280 | 281 | https://github.com/agentzh/sregex 282 | 283 | And then rebuild your Nginx like this: 284 | 285 | ```bash 286 | ./configure --add-module=/path/to/replace-filter-nginx-module 287 | ``` 288 | 289 | If sregex is not installed to the default prefix (i.e., `/usr/local`), then 290 | you should specify the locations of your sregex installation via 291 | the `SREGEX_INC` and `SREGEX_LIB` environments before running the 292 | `./configure` script, as in 293 | 294 | ```bash 295 | export SREGEX_INC=/opt/sregex/include 296 | export SREGEX_LIB=/opt/sregex/lib 297 | ``` 298 | 299 | assuming that your sregex is installed to the prefix `/opt/sregex`. 300 | 301 | 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 302 | `./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) 303 | directive, for example, 304 | 305 | ```nginx 306 | load_module /path/to/modules/ngx_http_replace_filter_module.so; 307 | ``` 308 | 309 | [Back to TOC](#table-of-contents) 310 | 311 | Trouble Shooting 312 | ================ 313 | 314 | * If you are seeing the error "error while loading shared libraries: libsregex.so.0: cannot open shared object file: No such file or directory" 315 | while starting nginx, then it means that the installation path of your libsregex library 316 | is not in your system's default library search path. You can solve this issue by passing the option `--with-ld-opt='-Wl,-rpath,/usr/local/lib'` to nginx's `./configure` command. Alternatively, you can just add the path of your libsregex.so.0 to the `LD_LIBRARY_PATH` environment value before starting your nginx server. 317 | 318 | [Back to TOC](#table-of-contents) 319 | 320 | TODO 321 | ==== 322 | 323 | * optimize the special case for verbatim substitutions, i.e., `replace_filter $&;`. 324 | * implement the `replace_filter_skip $var` directive to control whether to enable the filter on the fly. 325 | * reduce the amount of data that has to be buffered for when an partial match is already found. 326 | * recycle the memory blocks used to buffer the pending capture data and "complex values" for replacement. 327 | * allow use of inlined Lua code as the `replacement` argument of the `replace_filter` directive to generate the text to be replaced on-the-fly. 328 | 329 | [Back to TOC](#table-of-contents) 330 | 331 | Community 332 | ========= 333 | 334 | [Back to TOC](#table-of-contents) 335 | 336 | English Mailing List 337 | -------------------- 338 | 339 | The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. 340 | 341 | [Back to TOC](#table-of-contents) 342 | 343 | Chinese Mailing List 344 | -------------------- 345 | 346 | The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. 347 | 348 | [Back to TOC](#table-of-contents) 349 | 350 | Bugs and Patches 351 | ================ 352 | 353 | Please submit bug reports, wishlists, or patches by 354 | 355 | 1. creating a ticket on the [GitHub Issue Tracker](http://github.com/agentzh/replace-filter-nginx-module/issues), 356 | 1. or posting to the [OpenResty community](http://wiki.nginx.org/HttpLuaModule#Community). 357 | 358 | [Back to TOC](#table-of-contents) 359 | 360 | Author 361 | ====== 362 | 363 | Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 364 | 365 | [Back to TOC](#table-of-contents) 366 | 367 | Copyright and License 368 | ===================== 369 | 370 | This module is licensed under the BSD license. 371 | 372 | Copyright (C) 2012-2017, by Yichun "agentzh" Zhang (章亦春), OpenResty Inc. 373 | 374 | All rights reserved. 375 | 376 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 377 | 378 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 379 | 380 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 381 | 382 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 383 | 384 | [Back to TOC](#table-of-contents) 385 | 386 | See Also 387 | ======== 388 | 389 | * agentzh's sregex library: https://github.com/agentzh/sregex 390 | * The standard ngx_sub_filter module: http://nginx.org/en/docs/http/ngx_http_sub_module.html 391 | * Slides for my talk "sregex: matching Perl 5 regexes on data streams": http://agentzh.org/misc/slides/yapc-na-2013-sregex.pdf 392 | [Back to TOC](#table-of-contents) 393 | 394 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_feature="agentzh's sregex library" 2 | ngx_feature_libs="-lsregex" 3 | ngx_feature_name= 4 | ngx_feature_run=no 5 | ngx_feature_incs="#include " 6 | ngx_feature_path= 7 | ngx_feature_test="sre_regex_t *re; 8 | sre_program_t *prog; 9 | sre_pool_t *pool; 10 | u_char s[] = {'a', 'b', 'c'}; 11 | sre_int_t rc, err_offset, ovector; 12 | sre_int_t *pending_matched; 13 | sre_uint_t ncaps; 14 | sre_vm_pike_ctx_t *pctx; 15 | pool = sre_create_pool(1024); 16 | re = sre_regex_parse(pool, s, &ncaps, 0, &err_offset); 17 | prog = sre_regex_compile(pool, re); 18 | pctx = sre_vm_pike_create_ctx(pool, prog, &ovector, 0); 19 | rc = sre_vm_pike_exec(pctx, s, 32, 1, &pending_matched); 20 | sre_destroy_pool(pool)" 21 | 22 | if [ -n "$SREGEX_INC" -o -n "$SREGEX_LIB" ]; then 23 | # explicitly set sregex lib path 24 | ngx_feature="agentzh's sregex library in $SREGEX_LIB and $SREGEX_INC (specified by the SREGEX_LIB and SREGEX_INC env)" 25 | ngx_feature_path="$SREGEX_INC" 26 | if [ $NGX_RPATH = YES ]; then 27 | ngx_feature_libs="-R$SREGEX_LIB -L$SREGEX_LIB -lsregex" 28 | else 29 | ngx_feature_libs="-L$SREGEX_LIB -lsregex" 30 | fi 31 | 32 | . auto/feature 33 | 34 | if [ $ngx_found = no ]; then 35 | cat << END 36 | $0: error: ngx_http_replace_filter_module requires agentzh's sregex library and SREGEX_LIB is defined as $SREGEX_LIB and SREGEX_INC (path for sregex/sregex.h) $SREGEX_INC, but we cannot find sregex there. 37 | END 38 | exit 1 39 | fi 40 | 41 | else 42 | # auto-discovery 43 | ngx_feature="agentzh's sregex library" 44 | ngx_feature_libs="-lsregex" 45 | . auto/feature 46 | 47 | if [ $ngx_found = no ]; then 48 | # default installation prefix in sregex 49 | ngx_feature="agentzh's sregex library in /usr/local/" 50 | ngx_feature_path="/usr/local/include" 51 | if [ $NGX_RPATH = YES ]; then 52 | ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lsregex" 53 | else 54 | ngx_feature_libs="-L/usr/local/lib -lsregex" 55 | fi 56 | . auto/feature 57 | fi 58 | fi 59 | 60 | if [ $ngx_found = no ]; then 61 | cat << END 62 | $0: error: ngx_http_replace_filter_module requires agentzh's sregex library. 63 | END 64 | exit 1 65 | fi 66 | 67 | REPLACE_FILTER_SRCS="$ngx_addon_dir/src/ngx_http_replace_filter_module.c \ 68 | $ngx_addon_dir/src/ngx_http_replace_script.c \ 69 | $ngx_addon_dir/src/ngx_http_replace_parse.c \ 70 | $ngx_addon_dir/src/ngx_http_replace_util.c" 71 | REPLACE_FILTER_DEPS="$ngx_addon_dir/src/ngx_http_replace_filter_module.h \ 72 | $ngx_addon_dir/src/ngx_http_replace_script.h \ 73 | $ngx_addon_dir/src/ngx_http_replace_parse.h \ 74 | $ngx_addon_dir/src/ngx_http_replace_util.h" 75 | 76 | ngx_addon_name=ngx_http_replace_filter_module 77 | if test -n "$ngx_module_link"; then 78 | ngx_module_type=HTTP_AUX_FILTER 79 | ngx_module_name=$ngx_addon_name 80 | ngx_module_srcs="$REPLACE_FILTER_SRCS" 81 | ngx_module_deps="$REPLACE_FILTER_DEPS" 82 | ngx_module_incs=$ngx_feature_path 83 | ngx_module_libs=$ngx_feature_libs 84 | 85 | . auto/module 86 | 87 | else 88 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name" 89 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $REPLACE_FILTER_SRCS" 90 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $REPLACE_FILTER_DEPS" 91 | CORE_INCS="$CORE_INCS $ngx_feature_path" 92 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 93 | fi 94 | -------------------------------------------------------------------------------- /src/ddebug.h: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG_H 2 | #define DDEBUG_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #if defined(DDEBUG) && (DDEBUG) 11 | 12 | # if (NGX_HAVE_VARIADIC_MACROS) 13 | 14 | # define dd(...) fprintf(stderr, "replace_filter *** %s: ", __func__); \ 15 | fprintf(stderr, __VA_ARGS__); \ 16 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) 17 | 18 | # else 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | static void dd(const char * fmt, ...) { 26 | } 27 | 28 | # endif 29 | 30 | # if DDEBUG > 1 31 | 32 | # define dd_enter() dd_enter_helper(r, __func__) 33 | 34 | static void dd_enter_helper(ngx_http_request_t *r, const char *func) { 35 | ngx_http_posted_request_t *pr; 36 | 37 | fprintf(stderr, ">enter %s %.*s %.*s?%.*s c:%d m:%p r:%p ar:%p pr:%p", 38 | func, 39 | (int) r->method_name.len, r->method_name.data, 40 | (int) r->uri.len, r->uri.data, 41 | (int) r->args.len, r->args.data, 42 | 0/*(int) r->main->count*/, r->main, 43 | r, r->connection->data, r->parent); 44 | 45 | if (r->posted_requests) { 46 | fprintf(stderr, " posted:"); 47 | 48 | for (pr = r->posted_requests; pr; pr = pr->next) { 49 | fprintf(stderr, "%p,", pr); 50 | } 51 | } 52 | 53 | fprintf(stderr, "\n"); 54 | } 55 | 56 | # else 57 | 58 | # define dd_enter() 59 | 60 | # endif 61 | 62 | #else 63 | 64 | # if (NGX_HAVE_VARIADIC_MACROS) 65 | 66 | # define dd(...) 67 | 68 | # define dd_enter() 69 | 70 | # else 71 | 72 | #include 73 | 74 | static void dd(const char * fmt, ...) { 75 | } 76 | 77 | static void dd_enter() { 78 | } 79 | 80 | # endif 81 | 82 | #endif 83 | 84 | #if defined(DDEBUG) && (DDEBUG) 85 | 86 | #define dd_check_read_event_handler(r) \ 87 | dd("r->read_event_handler = %s", \ 88 | r->read_event_handler == ngx_http_block_reading ? \ 89 | "ngx_http_block_reading" : \ 90 | r->read_event_handler == ngx_http_test_reading ? \ 91 | "ngx_http_test_reading" : \ 92 | r->read_event_handler == ngx_http_request_empty_handler ? \ 93 | "ngx_http_request_empty_handler" : "UNKNOWN") 94 | 95 | #define dd_check_write_event_handler(r) \ 96 | dd("r->write_event_handler = %s", \ 97 | r->write_event_handler == ngx_http_handler ? \ 98 | "ngx_http_handler" : \ 99 | r->write_event_handler == ngx_http_core_run_phases ? \ 100 | "ngx_http_core_run_phases" : \ 101 | r->write_event_handler == ngx_http_request_empty_handler ? \ 102 | "ngx_http_request_empty_handler" : "UNKNOWN") 103 | 104 | #else 105 | 106 | #define dd_check_read_event_handler(r) 107 | #define dd_check_write_event_handler(r) 108 | 109 | #endif 110 | 111 | #endif /* DDEBUG_H */ 112 | 113 | -------------------------------------------------------------------------------- /src/ngx_http_replace_filter_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | * Copyright (C) Igor Sysoev 5 | * Copyright (C) Nginx, Inc. 6 | */ 7 | 8 | 9 | #ifndef DDEBUG 10 | #define DDEBUG 0 11 | #endif 12 | #include "ddebug.h" 13 | 14 | 15 | #include "ngx_http_replace_filter_module.h" 16 | #include "ngx_http_replace_parse.h" 17 | #include "ngx_http_replace_script.h" 18 | #include "ngx_http_replace_util.h" 19 | 20 | 21 | enum { 22 | SREGEX_COMPILER_POOL_SIZE = 4096 23 | }; 24 | 25 | 26 | static ngx_int_t ngx_http_replace_output(ngx_http_request_t *r, 27 | ngx_http_replace_ctx_t *ctx); 28 | static char *ngx_http_replace_filter(ngx_conf_t *cf, ngx_command_t *cmd, 29 | void *conf); 30 | static void *ngx_http_replace_create_loc_conf(ngx_conf_t *cf); 31 | static char *ngx_http_replace_merge_loc_conf(ngx_conf_t *cf, 32 | void *parent, void *child); 33 | static ngx_int_t ngx_http_replace_filter_init(ngx_conf_t *cf); 34 | static void ngx_http_replace_cleanup_pool(void *data); 35 | static void *ngx_http_replace_create_main_conf(ngx_conf_t *cf); 36 | 37 | 38 | #define ngx_http_replace_regex_is_disabled(ctx) \ 39 | ((ctx)->disabled[(ctx)->regex_id / 8] & (1 << ((ctx)->regex_id % 8))) 40 | 41 | 42 | #define ngx_http_replace_regex_set_disabled(ctx) \ 43 | (ctx)->disabled[(ctx)->regex_id / 8] |= (1 << ((ctx)->regex_id % 8)) 44 | 45 | 46 | static volatile ngx_cycle_t *ngx_http_replace_prev_cycle = NULL; 47 | 48 | 49 | #define NGX_HTTP_REPLACE_CLEAR_LAST_MODIFIED 0 50 | #define NGX_HTTP_REPLACE_KEEP_LAST_MODIFIED 1 51 | 52 | 53 | static ngx_conf_enum_t ngx_http_replace_filter_last_modified[] = { 54 | { ngx_string("clear"), NGX_HTTP_REPLACE_CLEAR_LAST_MODIFIED }, 55 | { ngx_string("keep"), NGX_HTTP_REPLACE_KEEP_LAST_MODIFIED }, 56 | { ngx_null_string, 0 } 57 | }; 58 | 59 | 60 | static ngx_command_t ngx_http_replace_filter_commands[] = { 61 | 62 | { ngx_string("replace_filter"), 63 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 64 | |NGX_CONF_TAKE23, 65 | ngx_http_replace_filter, 66 | NGX_HTTP_LOC_CONF_OFFSET, 67 | 0, 68 | NULL }, 69 | 70 | { ngx_string("replace_filter_types"), 71 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 72 | |NGX_CONF_1MORE, 73 | ngx_http_types_slot, 74 | NGX_HTTP_LOC_CONF_OFFSET, 75 | offsetof(ngx_http_replace_loc_conf_t, types_keys), 76 | &ngx_http_html_default_types[0] }, 77 | 78 | { ngx_string("replace_filter_max_buffered_size"), 79 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 80 | |NGX_CONF_TAKE1, 81 | ngx_conf_set_size_slot, 82 | NGX_HTTP_LOC_CONF_OFFSET, 83 | offsetof(ngx_http_replace_loc_conf_t, max_buffered_size), 84 | NULL }, 85 | 86 | { ngx_string("replace_filter_last_modified"), 87 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 88 | |NGX_CONF_1MORE, 89 | ngx_conf_set_enum_slot, 90 | NGX_HTTP_LOC_CONF_OFFSET, 91 | offsetof(ngx_http_replace_loc_conf_t, last_modified), 92 | &ngx_http_replace_filter_last_modified }, 93 | 94 | { ngx_string("replace_filter_skip"), 95 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 96 | |NGX_CONF_TAKE1, 97 | ngx_http_set_complex_value_slot, 98 | NGX_HTTP_LOC_CONF_OFFSET, 99 | offsetof(ngx_http_replace_loc_conf_t, skip), 100 | NULL }, 101 | 102 | ngx_null_command 103 | }; 104 | 105 | 106 | static ngx_http_module_t ngx_http_replace_filter_module_ctx = { 107 | NULL, /* preconfiguration */ 108 | ngx_http_replace_filter_init, /* postconfiguration */ 109 | 110 | ngx_http_replace_create_main_conf, /* create main configuration */ 111 | NULL, /* init main configuration */ 112 | 113 | NULL, /* create server configuration */ 114 | NULL, /* merge server configuration */ 115 | 116 | ngx_http_replace_create_loc_conf, /* create location configuration */ 117 | ngx_http_replace_merge_loc_conf /* merge location configuration */ 118 | }; 119 | 120 | 121 | ngx_module_t ngx_http_replace_filter_module = { 122 | NGX_MODULE_V1, 123 | &ngx_http_replace_filter_module_ctx, /* module context */ 124 | ngx_http_replace_filter_commands, /* module directives */ 125 | NGX_HTTP_MODULE, /* module type */ 126 | NULL, /* init master */ 127 | NULL, /* init module */ 128 | NULL, /* init process */ 129 | NULL, /* init thread */ 130 | NULL, /* exit thread */ 131 | NULL, /* exit process */ 132 | NULL, /* exit master */ 133 | NGX_MODULE_V1_PADDING 134 | }; 135 | 136 | 137 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 138 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 139 | 140 | 141 | static ngx_int_t 142 | ngx_http_replace_header_filter(ngx_http_request_t *r) 143 | { 144 | size_t size; 145 | ngx_str_t skip; 146 | ngx_pool_cleanup_t *cln; 147 | ngx_http_replace_ctx_t *ctx; 148 | ngx_http_replace_loc_conf_t *rlcf; 149 | 150 | rlcf = ngx_http_get_module_loc_conf(r, ngx_http_replace_filter_module); 151 | 152 | dd("replace header filter"); 153 | 154 | if (rlcf->regexes.nelts == 0 155 | || r->headers_out.content_length_n == 0 156 | || (r->headers_out.content_encoding 157 | && r->headers_out.content_encoding->value.len) 158 | || ngx_http_test_content_type(r, &rlcf->types) == NULL) 159 | { 160 | return ngx_http_next_header_filter(r); 161 | } 162 | 163 | dd("skip: %p", rlcf->skip); 164 | 165 | if (rlcf->skip != NULL) { 166 | if (ngx_http_complex_value(r, rlcf->skip, &skip) != NGX_OK) { 167 | return NGX_ERROR; 168 | } 169 | 170 | if (skip.len && (skip.len != 1 || skip.data[0] != '0')) { 171 | return ngx_http_next_header_filter(r); 172 | } 173 | } 174 | 175 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_replace_ctx_t)); 176 | if (ctx == NULL) { 177 | return NGX_ERROR; 178 | } 179 | 180 | ctx->last_special = &ctx->special; 181 | ctx->last_pending = &ctx->pending; 182 | ctx->last_pending2 = &ctx->pending2; 183 | ctx->last_captured = &ctx->captured; 184 | 185 | ctx->sub = ngx_pcalloc(r->pool, 186 | rlcf->multi_replace.nelts * sizeof(ngx_str_t)); 187 | if (ctx->sub == NULL) { 188 | return NGX_ERROR; 189 | } 190 | 191 | ctx->ovector = ngx_palloc(r->pool, rlcf->ovecsize); 192 | if (ctx->ovector == NULL) { 193 | return NGX_ERROR; 194 | } 195 | 196 | size = ngx_align(rlcf->regexes.nelts, 8) / 8; 197 | ctx->disabled = ngx_pcalloc(r->pool, size); 198 | if (ctx->disabled == NULL) { 199 | return NGX_ERROR; 200 | } 201 | 202 | ctx->vm_pool = sre_create_pool(1024); 203 | if (ctx->vm_pool == NULL) { 204 | return NGX_ERROR; 205 | } 206 | 207 | dd("created vm pool %p", ctx->vm_pool); 208 | 209 | cln = ngx_pool_cleanup_add(r->pool, 0); 210 | if (cln == NULL) { 211 | sre_destroy_pool(ctx->vm_pool); 212 | return NGX_ERROR; 213 | } 214 | 215 | cln->data = ctx->vm_pool; 216 | cln->handler = ngx_http_replace_cleanup_pool; 217 | 218 | ctx->vm_ctx = sre_vm_pike_create_ctx(ctx->vm_pool, rlcf->program, 219 | ctx->ovector, rlcf->ovecsize); 220 | if (ctx->vm_ctx == NULL) { 221 | return NGX_ERROR; 222 | } 223 | 224 | ngx_http_set_ctx(r, ctx, ngx_http_replace_filter_module); 225 | 226 | ctx->last_out = &ctx->out; 227 | 228 | r->filter_need_in_memory = 1; 229 | 230 | if (r == r->main) { 231 | ngx_http_clear_content_length(r); 232 | 233 | if (rlcf->last_modified == NGX_HTTP_REPLACE_CLEAR_LAST_MODIFIED) { 234 | ngx_http_clear_last_modified(r); 235 | } 236 | } 237 | 238 | return ngx_http_next_header_filter(r); 239 | } 240 | 241 | 242 | static void 243 | ngx_http_replace_cleanup_pool(void *data) 244 | { 245 | sre_pool_t *pool = data; 246 | 247 | if (pool) { 248 | dd("destroy sre pool %p", pool); 249 | sre_destroy_pool(pool); 250 | } 251 | } 252 | 253 | 254 | static ngx_int_t 255 | ngx_http_replace_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 256 | { 257 | ngx_int_t rc; 258 | ngx_buf_t *b; 259 | ngx_str_t *sub; 260 | ngx_chain_t *cl, *cur = NULL, *rematch = NULL; 261 | 262 | ngx_http_replace_ctx_t *ctx; 263 | ngx_http_replace_loc_conf_t *rlcf; 264 | 265 | rlcf = ngx_http_get_module_loc_conf(r, ngx_http_replace_filter_module); 266 | 267 | ctx = ngx_http_get_module_ctx(r, ngx_http_replace_filter_module); 268 | 269 | if (ctx == NULL) { 270 | return ngx_http_next_body_filter(r, in); 271 | } 272 | 273 | if ((in == NULL 274 | && ctx->buf == NULL 275 | && ctx->in == NULL 276 | && ctx->busy == NULL)) 277 | { 278 | return ngx_http_next_body_filter(r, in); 279 | } 280 | 281 | if ((ctx->once || ctx->vm_done) && (ctx->buf == NULL || ctx->in == NULL)) { 282 | 283 | if (ctx->busy) { 284 | if (ngx_http_replace_output(r, ctx) == NGX_ERROR) { 285 | return NGX_ERROR; 286 | } 287 | } 288 | 289 | return ngx_http_next_body_filter(r, in); 290 | } 291 | 292 | /* add the incoming chain to the chain ctx->in */ 293 | 294 | if (in) { 295 | if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { 296 | return NGX_ERROR; 297 | } 298 | } 299 | 300 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 301 | "http sub filter \"%V\"", &r->uri); 302 | 303 | while (ctx->in || ctx->buf) { 304 | 305 | if (ctx->buf == NULL) { 306 | cur = ctx->in; 307 | ctx->buf = cur->buf; 308 | ctx->in = cur->next; 309 | 310 | ctx->pos = ctx->buf->pos; 311 | ctx->special_buf = ngx_buf_special(ctx->buf); 312 | ctx->last_buf = (ctx->buf->last_buf || ctx->buf->last_in_chain); 313 | 314 | dd("=== new incoming buf: size=%d, special=%u, last=%u", 315 | (int) ngx_buf_size(ctx->buf), ctx->special_buf, 316 | ctx->last_buf); 317 | } 318 | 319 | b = NULL; 320 | 321 | while (ctx->pos < ctx->buf->last 322 | || (ctx->special_buf && ctx->last_buf)) 323 | { 324 | rc = rlcf->parse_buf(r, ctx, rematch); 325 | 326 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 327 | "replace filter parse: %d, %p-%p", 328 | rc, ctx->copy_start, ctx->copy_end); 329 | 330 | if (rc == NGX_ERROR) { 331 | return rc; 332 | } 333 | 334 | if (rc == NGX_DECLINED) { 335 | 336 | if (ctx->pending) { 337 | *ctx->last_out = ctx->pending; 338 | ctx->last_out = ctx->last_pending; 339 | 340 | ctx->pending = NULL; 341 | ctx->last_pending = &ctx->pending; 342 | } 343 | 344 | if (!ctx->special_buf) { 345 | ctx->copy_start = ctx->pos; 346 | ctx->copy_end = ctx->buf->last; 347 | ctx->pos = ctx->buf->last; 348 | 349 | } else { 350 | ctx->copy_start = NULL; 351 | ctx->copy_end = NULL; 352 | } 353 | 354 | sre_reset_pool(ctx->vm_pool); 355 | ctx->vm_done = 1; 356 | } 357 | 358 | dd("copy_end - copy_start: %d, special: %u", 359 | (int) (ctx->copy_end - ctx->copy_start), ctx->special_buf); 360 | 361 | if (ctx->copy_start != ctx->copy_end && !ctx->special_buf) { 362 | dd("copy: %.*s", (int) (ctx->copy_end - ctx->copy_start), 363 | ctx->copy_start); 364 | 365 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 366 | if (cl == NULL) { 367 | return NGX_ERROR; 368 | } 369 | 370 | b = cl->buf; 371 | 372 | b->memory = 1; 373 | b->pos = ctx->copy_start; 374 | b->last = ctx->copy_end; 375 | 376 | *ctx->last_out = cl; 377 | ctx->last_out = &cl->next; 378 | } 379 | 380 | if (rc == NGX_AGAIN) { 381 | if (ctx->special_buf && ctx->last_buf) { 382 | break; 383 | } 384 | 385 | continue; 386 | } 387 | 388 | if (rc == NGX_DECLINED) { 389 | break; 390 | } 391 | 392 | /* rc == NGX_OK || rc == NGX_BUSY */ 393 | 394 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 395 | if (cl == NULL) { 396 | return NGX_ERROR; 397 | } 398 | 399 | b = cl->buf; 400 | 401 | dd("free data buf: %p", b); 402 | 403 | sub = &ctx->sub[ctx->regex_id]; 404 | 405 | if (sub->data == NULL 406 | || rlcf->parse_buf == ngx_http_replace_capturing_parse) 407 | { 408 | ngx_http_replace_complex_value_t *cv; 409 | 410 | if (ngx_http_replace_regex_is_disabled(ctx)) { 411 | cv = &rlcf->verbatim; 412 | 413 | } else { 414 | cv = rlcf->multi_replace.elts; 415 | cv = &cv[ctx->regex_id]; 416 | } 417 | 418 | if (ngx_http_replace_complex_value(r, ctx->captured, 419 | rlcf->ncaps, 420 | ctx->ovector, 421 | cv, sub) 422 | != NGX_OK) 423 | { 424 | return NGX_ERROR; 425 | } 426 | 427 | /* release ctx->captured */ 428 | if (ctx->captured) { 429 | dd("release ctx captured: %p", ctx->captured); 430 | *ctx->last_captured = ctx->free; 431 | ctx->free = ctx->captured; 432 | 433 | ctx->captured = NULL; 434 | ctx->last_captured = &ctx->captured; 435 | } 436 | } 437 | 438 | dd("emit replaced value: \"%.*s\"", (int) sub->len, sub->data); 439 | 440 | if (sub->len) { 441 | b->memory = 1; 442 | b->pos = sub->data; 443 | b->last = sub->data + sub->len; 444 | 445 | } else { 446 | b->sync = 1; 447 | } 448 | 449 | cl->buf = b; 450 | cl->next = NULL; 451 | 452 | *ctx->last_out = cl; 453 | ctx->last_out = &cl->next; 454 | 455 | if (!ctx->once && !ngx_http_replace_regex_is_disabled(ctx)) { 456 | uint8_t *once; 457 | 458 | once = rlcf->multi_once.elts; 459 | 460 | if (rlcf->regexes.nelts == 1) { 461 | ctx->once = once[0]; 462 | 463 | } else { 464 | if (once[ctx->regex_id]) { 465 | ngx_http_replace_regex_set_disabled(ctx); 466 | if (!rlcf->seen_global 467 | && ++ctx->disabled_count == rlcf->regexes.nelts) 468 | { 469 | ctx->once = 1; 470 | } 471 | } 472 | } 473 | } 474 | 475 | if (rc == NGX_BUSY) { 476 | dd("goto rematch"); 477 | goto rematch; 478 | } 479 | 480 | if (ctx->special_buf) { 481 | break; 482 | } 483 | 484 | continue; 485 | } 486 | 487 | if ((ctx->buf->flush || ctx->last_buf || ngx_buf_in_memory(ctx->buf)) 488 | && cur) 489 | { 490 | if (b == NULL) { 491 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 492 | if (cl == NULL) { 493 | return NGX_ERROR; 494 | } 495 | 496 | b = cl->buf; 497 | b->sync = 1; 498 | 499 | *ctx->last_out = cl; 500 | ctx->last_out = &cl->next; 501 | } 502 | 503 | dd("setting shadow and last buf: %d", (int) ctx->buf->last_buf); 504 | b->last_buf = ctx->buf->last_buf; 505 | b->last_in_chain = ctx->buf->last_in_chain; 506 | b->flush = ctx->buf->flush; 507 | b->shadow = ctx->buf; 508 | b->recycled = ctx->buf->recycled; 509 | } 510 | 511 | if (!ctx->special_buf) { 512 | ctx->stream_pos += ctx->buf->last - ctx->buf->pos; 513 | } 514 | 515 | if (rematch) { 516 | rematch->next = ctx->free; 517 | ctx->free = rematch; 518 | rematch = NULL; 519 | } 520 | 521 | rematch: 522 | 523 | dd("ctx->rematch: %p", ctx->rematch); 524 | 525 | if (ctx->rematch == NULL) { 526 | ctx->buf = NULL; 527 | cur = NULL; 528 | 529 | } else { 530 | 531 | if (cur) { 532 | ctx->in = cur; 533 | cur = NULL; 534 | } 535 | 536 | ctx->buf = ctx->rematch->buf; 537 | 538 | dd("ctx->buf set to rematch buf %p, len=%d, next=%p", 539 | ctx->buf, (int) ngx_buf_size(ctx->buf), ctx->rematch->next); 540 | 541 | rematch = ctx->rematch; 542 | ctx->rematch = rematch->next; 543 | 544 | ctx->pos = ctx->buf->pos; 545 | ctx->special_buf = ngx_buf_special(ctx->buf); 546 | ctx->last_buf = (ctx->buf->last_buf || ctx->buf->last_in_chain); 547 | ctx->stream_pos = ctx->buf->file_pos; 548 | } 549 | 550 | #if (DDEBUG) 551 | /* 552 | ngx_http_replace_dump_chain("ctx->pending", &ctx->pending, 553 | ctx->last_pending); 554 | ngx_http_replace_dump_chain("ctx->pending2", &ctx->pending2, 555 | ctx->last_pending2); 556 | */ 557 | #endif 558 | } /* while */ 559 | 560 | if (ctx->out == NULL && ctx->busy == NULL) { 561 | return NGX_OK; 562 | } 563 | 564 | return ngx_http_replace_output(r, ctx); 565 | } 566 | 567 | 568 | static ngx_int_t 569 | ngx_http_replace_output(ngx_http_request_t *r, ngx_http_replace_ctx_t *ctx) 570 | { 571 | ngx_int_t rc; 572 | ngx_buf_t *b; 573 | ngx_chain_t *cl; 574 | 575 | #if (DDEBUG) 576 | b = NULL; 577 | for (cl = ctx->out; cl; cl = cl->next) { 578 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 579 | "replace out: %p %p", cl->buf, cl->buf->pos); 580 | if (cl->buf == b) { 581 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 582 | "the same buf was used in sub"); 583 | ngx_debug_point(); 584 | return NGX_ERROR; 585 | } 586 | b = cl->buf; 587 | } 588 | 589 | /* ngx_http_replace_dump_chain("ctx->out", &ctx->out, ctx->last_out); */ 590 | #endif 591 | 592 | rc = ngx_http_next_body_filter(r, ctx->out); 593 | 594 | /* we are essentially duplicating the logic of 595 | * ngx_chain_update_chains below, 596 | * with our own optimizations */ 597 | 598 | if (ctx->busy == NULL) { 599 | ctx->busy = ctx->out; 600 | 601 | } else { 602 | for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ } 603 | cl->next = ctx->out; 604 | } 605 | 606 | ctx->out = NULL; 607 | ctx->last_out = &ctx->out; 608 | 609 | while (ctx->busy) { 610 | 611 | cl = ctx->busy; 612 | b = cl->buf; 613 | 614 | if (ngx_buf_size(b) != 0) { 615 | break; 616 | } 617 | 618 | if (cl->buf->tag != (ngx_buf_tag_t) &ngx_http_replace_filter_module) { 619 | ctx->busy = cl->next; 620 | ngx_free_chain(r->pool, cl); 621 | continue; 622 | } 623 | 624 | if (b->shadow) { 625 | b->shadow->pos = b->shadow->last; 626 | b->shadow->file_pos = b->shadow->file_last; 627 | } 628 | 629 | ctx->busy = cl->next; 630 | 631 | if (ngx_buf_special(b)) { 632 | 633 | /* collect special bufs to ctx->special, which may still be busy */ 634 | 635 | cl->next = NULL; 636 | *ctx->last_special = cl; 637 | ctx->last_special = &cl->next; 638 | 639 | } else { 640 | 641 | /* add ctx->special to ctx->free because they cannot be busy at 642 | * this point */ 643 | 644 | *ctx->last_special = ctx->free; 645 | ctx->free = ctx->special; 646 | ctx->special = NULL; 647 | ctx->last_special = &ctx->special; 648 | 649 | #if 0 650 | /* free the temporary buf's data block if it is big enough */ 651 | if (b->temporary 652 | && b->start != NULL 653 | && b->end - b->start > (ssize_t) r->pool->max) 654 | { 655 | ngx_pfree(r->pool, b->start); 656 | } 657 | #endif 658 | 659 | /* add the data buf itself to the free buf chain */ 660 | 661 | cl->next = ctx->free; 662 | ctx->free = cl; 663 | } 664 | } 665 | 666 | if (ctx->in || ctx->buf) { 667 | r->buffered |= NGX_HTTP_SUB_BUFFERED; 668 | 669 | } else { 670 | r->buffered &= ~NGX_HTTP_SUB_BUFFERED; 671 | } 672 | 673 | return rc; 674 | } 675 | 676 | 677 | static char * 678 | ngx_http_replace_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 679 | { 680 | ngx_http_replace_loc_conf_t *rlcf = conf; 681 | ngx_http_replace_main_conf_t *rmcf; 682 | 683 | int *flags; 684 | u_char *p, **re; 685 | ngx_str_t *value; 686 | ngx_uint_t i; 687 | uint8_t *once; 688 | 689 | ngx_pool_cleanup_t *cln; 690 | ngx_http_replace_complex_value_t *cv; 691 | ngx_http_replace_compile_complex_value_t ccv; 692 | 693 | value = cf->args->elts; 694 | 695 | re = ngx_array_push(&rlcf->regexes); 696 | if (re == NULL) { 697 | return NGX_CONF_ERROR; 698 | } 699 | 700 | *re = value[1].data; 701 | 702 | cv = ngx_array_push(&rlcf->multi_replace); 703 | if (cv == NULL) { 704 | return NGX_CONF_ERROR; 705 | } 706 | 707 | ngx_memzero(cv, sizeof(ngx_http_replace_complex_value_t)); 708 | ngx_memzero(&ccv, sizeof(ngx_http_replace_compile_complex_value_t)); 709 | 710 | ccv.cf = cf; 711 | ccv.value = &value[2]; 712 | ccv.complex_value = cv; 713 | 714 | if (ngx_http_replace_compile_complex_value(&ccv) != NGX_OK) { 715 | return NGX_CONF_ERROR; 716 | } 717 | 718 | /* check variable usage in the "replace" argument */ 719 | 720 | if (cv->capture_variables) { 721 | rlcf->parse_buf = ngx_http_replace_capturing_parse; 722 | 723 | } else if (rlcf->parse_buf == NULL) { 724 | rlcf->parse_buf = ngx_http_replace_non_capturing_parse; 725 | } 726 | 727 | #if 0 728 | rlcf->parse_buf = ngx_http_replace_capturing_parse; 729 | #endif 730 | 731 | flags = ngx_array_push(&rlcf->multi_flags); 732 | if (flags == NULL) { 733 | return NGX_CONF_ERROR; 734 | } 735 | *flags = 0; 736 | 737 | once = ngx_array_push(&rlcf->multi_once); 738 | if (once == NULL) { 739 | return NGX_CONF_ERROR; 740 | } 741 | *once = 1; /* default to once */ 742 | 743 | if (cf->args->nelts == 4) { 744 | /* 3 user args */ 745 | 746 | p = value[3].data; 747 | 748 | for (i = 0; i < value[3].len; i++) { 749 | switch (p[i]) { 750 | case 'i': 751 | *flags |= SRE_REGEX_CASELESS; 752 | break; 753 | 754 | case 'g': 755 | *once = 0; 756 | break; 757 | 758 | default: 759 | return "specifies an unrecognized regex flag"; 760 | } 761 | } 762 | } 763 | 764 | if (*once) { 765 | rlcf->seen_once = 1; 766 | 767 | } else { 768 | rlcf->seen_global = 1; 769 | } 770 | 771 | if (rlcf->seen_once && rlcf->regexes.nelts > 1) { 772 | rlcf->parse_buf = ngx_http_replace_capturing_parse; 773 | 774 | if (rlcf->verbatim.value.data == NULL) { 775 | ngx_str_t v = ngx_string("$&"); 776 | 777 | ngx_memzero(&ccv, sizeof(ngx_http_replace_compile_complex_value_t)); 778 | 779 | ccv.cf = cf; 780 | ccv.value = &v; 781 | ccv.complex_value = &rlcf->verbatim; 782 | 783 | if (ngx_http_replace_compile_complex_value(&ccv) != NGX_OK) { 784 | return NGX_CONF_ERROR; 785 | } 786 | } 787 | } 788 | 789 | rmcf = 790 | ngx_http_conf_get_module_main_conf(cf, ngx_http_replace_filter_module); 791 | 792 | if (rmcf->compiler_pool == NULL) { 793 | rmcf->compiler_pool = sre_create_pool(SREGEX_COMPILER_POOL_SIZE); 794 | if (rmcf->compiler_pool == NULL) { 795 | return NGX_CONF_ERROR; 796 | } 797 | 798 | cln = ngx_pool_cleanup_add(cf->pool, 0); 799 | if (cln == NULL) { 800 | sre_destroy_pool(rmcf->compiler_pool); 801 | rmcf->compiler_pool = NULL; 802 | return NGX_CONF_ERROR; 803 | } 804 | 805 | cln->data = rmcf->compiler_pool; 806 | cln->handler = ngx_http_replace_cleanup_pool; 807 | } 808 | 809 | return NGX_CONF_OK; 810 | } 811 | 812 | 813 | static void * 814 | ngx_http_replace_create_loc_conf(ngx_conf_t *cf) 815 | { 816 | ngx_http_replace_loc_conf_t *conf; 817 | 818 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_replace_loc_conf_t)); 819 | if (conf == NULL) { 820 | return NULL; 821 | } 822 | 823 | /* 824 | * set by ngx_pcalloc(): 825 | * 826 | * conf->types = { NULL }; 827 | * conf->types_keys = NULL; 828 | * conf->program = NULL; 829 | * conf->ncaps = 0; 830 | * conf->ovecsize = 0; 831 | * conf->parse_buf = NULL; 832 | * conf->verbatim = { {0, NULL}, NULL, NULL, 0 }; 833 | * conf->seen_once = 0; 834 | * conf->seen_global = 0; 835 | * conf->skip = NULL; 836 | */ 837 | 838 | conf->max_buffered_size = NGX_CONF_UNSET_SIZE; 839 | conf->last_modified = NGX_CONF_UNSET_UINT; 840 | 841 | ngx_array_init(&conf->multi_replace, cf->pool, 4, 842 | sizeof(ngx_http_replace_complex_value_t)); 843 | 844 | ngx_array_init(&conf->multi_flags, cf->pool, 4, sizeof(int)); 845 | 846 | ngx_array_init(&conf->regexes, cf->pool, 4, sizeof(u_char *)); 847 | 848 | ngx_array_init(&conf->multi_once, cf->pool, 4, sizeof(uint8_t)); 849 | 850 | return conf; 851 | } 852 | 853 | 854 | static char * 855 | ngx_http_replace_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 856 | { 857 | u_char **value; 858 | sre_int_t err_offset, err_regex_id; 859 | ngx_str_t prefix, suffix; 860 | sre_pool_t *ppool; /* parser pool */ 861 | sre_regex_t *re; 862 | sre_program_t *prog; 863 | 864 | ngx_http_replace_main_conf_t *rmcf; 865 | 866 | ngx_http_replace_loc_conf_t *prev = parent; 867 | ngx_http_replace_loc_conf_t *conf = child; 868 | 869 | ngx_conf_merge_size_value(conf->max_buffered_size, 870 | prev->max_buffered_size, 871 | 8192); 872 | 873 | ngx_conf_merge_uint_value(conf->last_modified, 874 | prev->last_modified, 875 | NGX_HTTP_REPLACE_CLEAR_LAST_MODIFIED); 876 | 877 | if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, 878 | &prev->types_keys, &prev->types, 879 | ngx_http_html_default_types) 880 | != NGX_OK) 881 | { 882 | return NGX_CONF_ERROR; 883 | } 884 | 885 | if (conf->skip == NULL) { 886 | conf->skip = prev->skip; 887 | } 888 | 889 | if (conf->regexes.nelts > 0 && conf->program == NULL) { 890 | 891 | dd("parsing and compiling %d regexes", (int) conf->regexes.nelts); 892 | 893 | ppool = sre_create_pool(1024); 894 | if (ppool == NULL) { 895 | return NGX_CONF_ERROR; 896 | } 897 | 898 | value = conf->regexes.elts; 899 | 900 | re = sre_regex_parse_multi(ppool, value, conf->regexes.nelts, 901 | &conf->ncaps, conf->multi_flags.elts, 902 | &err_offset, &err_regex_id); 903 | 904 | if (re == NULL) { 905 | 906 | if (err_offset >= 0) { 907 | prefix.data = value[err_regex_id]; 908 | prefix.len = err_offset; 909 | 910 | suffix.data = value[err_regex_id] + err_offset; 911 | suffix.len = ngx_strlen(value[err_regex_id]) - err_offset; 912 | 913 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 914 | "failed to parse regex at offset %i: " 915 | "syntax error; marked by <-- HERE in " 916 | "\"%V <-- HERE %V\"", 917 | (ngx_int_t) err_offset, &prefix, &suffix); 918 | 919 | } else { 920 | 921 | if (err_regex_id >= 0) { 922 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 923 | "failed to parse regex \"%s\"", 924 | value[err_regex_id]); 925 | 926 | } else { 927 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 928 | "failed to parse regex \"%s\" " 929 | "and its siblings", 930 | value[0]); 931 | } 932 | } 933 | 934 | sre_destroy_pool(ppool); 935 | return NGX_CONF_ERROR; 936 | } 937 | 938 | rmcf = ngx_http_conf_get_module_main_conf(cf, 939 | ngx_http_replace_filter_module); 940 | 941 | prog = sre_regex_compile(rmcf->compiler_pool, re); 942 | 943 | sre_destroy_pool(ppool); 944 | 945 | if (prog == NULL) { 946 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 947 | "failed to compile regex \"%s\" and its " 948 | "siblings", value[0]); 949 | 950 | return NGX_CONF_ERROR; 951 | } 952 | 953 | conf->program = prog; 954 | conf->ovecsize = 2 * (conf->ncaps + 1) * sizeof(sre_int_t); 955 | 956 | } else { 957 | 958 | conf->regexes = prev->regexes; 959 | conf->multi_once = prev->multi_once; 960 | conf->multi_flags = prev->multi_flags; 961 | conf->multi_replace = prev->multi_replace; 962 | conf->parse_buf = prev->parse_buf; 963 | conf->verbatim = prev->verbatim; 964 | conf->program = prev->program; 965 | conf->ncaps = prev->ncaps; 966 | conf->ovecsize = prev->ovecsize; 967 | conf->seen_once = prev->seen_once; 968 | conf->seen_global = prev->seen_global; 969 | } 970 | 971 | return NGX_CONF_OK; 972 | } 973 | 974 | 975 | static ngx_int_t 976 | ngx_http_replace_filter_init(ngx_conf_t *cf) 977 | { 978 | int multi_http_blocks; 979 | ngx_http_replace_main_conf_t *rmcf; 980 | 981 | rmcf = 982 | ngx_http_conf_get_module_main_conf(cf, ngx_http_replace_filter_module); 983 | 984 | if (ngx_http_replace_prev_cycle != ngx_cycle) { 985 | ngx_http_replace_prev_cycle = ngx_cycle; 986 | multi_http_blocks = 0; 987 | 988 | } else { 989 | multi_http_blocks = 1; 990 | } 991 | 992 | if (multi_http_blocks || rmcf->compiler_pool != NULL) { 993 | ngx_http_next_header_filter = ngx_http_top_header_filter; 994 | ngx_http_top_header_filter = ngx_http_replace_header_filter; 995 | 996 | ngx_http_next_body_filter = ngx_http_top_body_filter; 997 | ngx_http_top_body_filter = ngx_http_replace_body_filter; 998 | 999 | return NGX_OK; 1000 | } 1001 | 1002 | return NGX_OK; 1003 | } 1004 | 1005 | 1006 | static void * 1007 | ngx_http_replace_create_main_conf(ngx_conf_t *cf) 1008 | { 1009 | ngx_http_replace_main_conf_t *rmcf; 1010 | 1011 | rmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_replace_main_conf_t)); 1012 | if (rmcf == NULL) { 1013 | return NULL; 1014 | } 1015 | 1016 | /* set by ngx_pcalloc: 1017 | * rmcf->compiler_pool = NULL; 1018 | */ 1019 | 1020 | return rmcf; 1021 | } 1022 | -------------------------------------------------------------------------------- /src/ngx_http_replace_filter_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_REPLACE_FILTER_MODULE_H_INCLUDED_ 2 | #define _NGX_HTTP_REPLACE_FILTER_MODULE_H_INCLUDED_ 3 | 4 | 5 | #include "ngx_http_replace_script.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | extern ngx_module_t ngx_http_replace_filter_module; 13 | 14 | 15 | typedef struct { 16 | sre_int_t regex_id; 17 | sre_int_t stream_pos; 18 | sre_int_t *ovector; 19 | sre_pool_t *vm_pool; 20 | sre_vm_pike_ctx_t *vm_ctx; 21 | 22 | ngx_chain_t *pending; /* pending data before the 23 | pending matched capture */ 24 | ngx_chain_t **last_pending; 25 | 26 | ngx_chain_t *pending2; /* pending data after the pending 27 | matched capture */ 28 | ngx_chain_t **last_pending2; 29 | 30 | ngx_buf_t *buf; 31 | 32 | ngx_str_t *sub; 33 | 34 | u_char *pos; 35 | u_char *copy_start; 36 | u_char *copy_end; 37 | 38 | ngx_chain_t *in; 39 | ngx_chain_t *out; 40 | ngx_chain_t **last_out; 41 | ngx_chain_t *busy; 42 | ngx_chain_t *free; 43 | ngx_chain_t *special; 44 | ngx_chain_t **last_special; 45 | ngx_chain_t *rematch; 46 | ngx_chain_t *captured; 47 | ngx_chain_t **last_captured; 48 | uint8_t *disabled; 49 | sre_uint_t disabled_count; 50 | 51 | size_t total_buffered; 52 | 53 | unsigned once:1; 54 | unsigned vm_done:1; 55 | unsigned special_buf:1; 56 | unsigned last_buf:1; 57 | } ngx_http_replace_ctx_t; 58 | 59 | 60 | typedef ngx_int_t (*ngx_http_replace_parse_buf_pt)(ngx_http_request_t *r, 61 | ngx_http_replace_ctx_t *ctx, ngx_chain_t *rematch); 62 | 63 | 64 | typedef struct { 65 | sre_pool_t *compiler_pool; 66 | } ngx_http_replace_main_conf_t; 67 | 68 | 69 | typedef struct { 70 | sre_uint_t ncaps; 71 | size_t ovecsize; 72 | 73 | ngx_array_t multi_once; /* of uint8_t */ 74 | ngx_array_t regexes; /* of u_char* */ 75 | ngx_array_t multi_flags; /* of int */ 76 | ngx_array_t multi_replace; 77 | /* of ngx_http_replace_complex_value_t */ 78 | 79 | sre_program_t *program; 80 | 81 | ngx_hash_t types; 82 | ngx_array_t *types_keys; 83 | 84 | size_t max_buffered_size; 85 | 86 | ngx_uint_t last_modified; 87 | /* replace_filter_last_modified */ 88 | 89 | ngx_http_replace_parse_buf_pt parse_buf; 90 | ngx_http_replace_complex_value_t verbatim; 91 | 92 | ngx_http_complex_value_t *skip; 93 | 94 | unsigned seen_once; /* :1 */ 95 | unsigned seen_global; /* :1 */ 96 | } ngx_http_replace_loc_conf_t; 97 | 98 | 99 | #endif /* _NGX_HTTP_REPLACE_FILTER_MODULE_H_INCLUDED_ */ 100 | -------------------------------------------------------------------------------- /src/ngx_http_replace_parse.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | * Copyright (C) Igor Sysoev 5 | * Copyright (C) Nginx, Inc. 6 | */ 7 | 8 | 9 | #ifndef DDEBUG 10 | #define DDEBUG 0 11 | #endif 12 | #include "ddebug.h" 13 | 14 | 15 | #include "ngx_http_replace_parse.h" 16 | #include "ngx_http_replace_util.h" 17 | 18 | 19 | static void ngx_http_replace_check_total_buffered(ngx_http_request_t *r, 20 | ngx_http_replace_ctx_t *ctx, sre_int_t len, sre_int_t mlen); 21 | 22 | 23 | ngx_int_t 24 | ngx_http_replace_capturing_parse(ngx_http_request_t *r, 25 | ngx_http_replace_ctx_t *ctx, ngx_chain_t *rematch) 26 | { 27 | sre_int_t ret, from, to; 28 | ngx_int_t rc; 29 | ngx_chain_t *new_rematch = NULL; 30 | ngx_chain_t *cl; 31 | ngx_chain_t **last_rematch, **last; 32 | size_t len; 33 | 34 | dd("replace capturing parse"); 35 | 36 | if (ctx->once || ctx->vm_done) { 37 | ctx->copy_start = ctx->pos; 38 | ctx->copy_end = ctx->buf->last; 39 | ctx->pos = ctx->buf->last; 40 | 41 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "once"); 42 | 43 | return NGX_AGAIN; 44 | } 45 | 46 | len = ctx->buf->last - ctx->pos; 47 | 48 | dd("=== process data chunk %p len=%d, pos=%u, special=%u, " 49 | "last=%u, \"%.*s\"", ctx->buf, (int) (ctx->buf->last - ctx->pos), 50 | (int) (ctx->pos - ctx->buf->pos + ctx->stream_pos), 51 | ctx->special_buf, ctx->last_buf, 52 | (int) (ctx->buf->last - ctx->pos), ctx->pos); 53 | 54 | ret = sre_vm_pike_exec(ctx->vm_ctx, ctx->pos, len, ctx->last_buf, NULL); 55 | 56 | dd("vm pike exec: %d", (int) ret); 57 | 58 | if (ret >= 0) { 59 | ctx->regex_id = ret; 60 | ctx->total_buffered = 0; 61 | 62 | from = ctx->ovector[0]; 63 | to = ctx->ovector[1]; 64 | 65 | dd("pike vm ok: (%d, %d)", (int) from, (int) to); 66 | 67 | if (from >= ctx->stream_pos) { 68 | /* the match is completely on the current buf */ 69 | 70 | if (ctx->pending) { 71 | *ctx->last_out = ctx->pending; 72 | ctx->last_out = ctx->last_pending; 73 | 74 | ctx->pending = NULL; 75 | ctx->last_pending = &ctx->pending; 76 | } 77 | 78 | /* prepare ctx->captured */ 79 | 80 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 81 | if (cl == NULL) { 82 | return NGX_ERROR; 83 | } 84 | 85 | cl->buf->pos = ctx->buf->pos; 86 | cl->buf->last = ctx->buf->last; 87 | cl->buf->memory = 1; 88 | cl->buf->file_pos = ctx->stream_pos; 89 | cl->buf->file_last = ctx->stream_pos 90 | + (cl->buf->last - cl->buf->pos); 91 | 92 | *ctx->last_captured = cl; 93 | ctx->last_captured = &cl->next; 94 | 95 | dd("ctx captured: %p", ctx->captured); 96 | 97 | /* prepare copy-out data */ 98 | 99 | ctx->copy_start = ctx->pos; 100 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 101 | 102 | dd("copy len: %d", (int) (ctx->copy_end - ctx->copy_start)); 103 | 104 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 105 | return NGX_OK; 106 | } 107 | 108 | /* from < ctx->stream_pos */ 109 | 110 | if (ctx->pending) { 111 | 112 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending, 113 | &ctx->last_pending, from, 114 | &cl, &last, 1) 115 | != NGX_OK) 116 | { 117 | return NGX_ERROR; 118 | } 119 | 120 | if (ctx->pending) { 121 | *ctx->last_out = ctx->pending; 122 | ctx->last_out = ctx->last_pending; 123 | 124 | ctx->pending = NULL; 125 | ctx->last_pending = &ctx->pending; 126 | } 127 | 128 | if (cl) { 129 | if (to >= ctx->stream_pos) { 130 | /* no pending data to be rematched */ 131 | 132 | if (to == ctx->stream_pos) { 133 | *ctx->last_captured = cl; 134 | ctx->last_captured = &cl->next; 135 | 136 | } else { 137 | *ctx->last_captured = cl; 138 | ctx->last_captured = last; 139 | 140 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 141 | if (cl == NULL) { 142 | return NGX_ERROR; 143 | } 144 | 145 | cl->buf->pos = ctx->buf->pos; 146 | cl->buf->last = ctx->buf->last; 147 | cl->buf->memory = 1; 148 | cl->buf->file_pos = ctx->stream_pos; 149 | cl->buf->file_last = ctx->stream_pos 150 | + (cl->buf->last - cl->buf->pos); 151 | 152 | *ctx->last_captured = cl; 153 | ctx->last_captured = &cl->next; 154 | } 155 | 156 | } else { 157 | /* there's pending data to be rematched */ 158 | 159 | if (ngx_http_replace_split_chain(r, ctx, &cl, 160 | &last, 161 | to, &new_rematch, 162 | &last_rematch, 1) 163 | != NGX_OK) 164 | { 165 | return NGX_ERROR; 166 | } 167 | 168 | if (cl) { 169 | *ctx->last_captured = cl; 170 | ctx->last_captured = last; 171 | } 172 | 173 | if (new_rematch) { 174 | if (rematch) { 175 | ctx->rematch = rematch; 176 | } 177 | 178 | /* prepend cl to ctx->rematch */ 179 | *last_rematch = ctx->rematch; 180 | ctx->rematch = new_rematch; 181 | } 182 | } 183 | } 184 | } 185 | 186 | #if (DDEBUG) 187 | ngx_http_replace_dump_chain("ctx->rematch", &ctx->rematch, NULL); 188 | #endif 189 | 190 | ctx->copy_start = NULL; 191 | ctx->copy_end = NULL; 192 | 193 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 194 | 195 | return new_rematch ? NGX_BUSY : NGX_OK; 196 | } 197 | 198 | switch (ret) { 199 | case SRE_AGAIN: 200 | from = ctx->ovector[0]; 201 | to = ctx->ovector[1]; 202 | 203 | dd("pike vm again: (%d, %d)", (int) from, (int) to); 204 | 205 | if (from == -1) { 206 | from = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 207 | } 208 | 209 | if (to == -1) { 210 | to = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 211 | } 212 | 213 | dd("pike vm again (adjusted): stream pos:%d, (%d, %d)", 214 | (int) ctx->stream_pos, (int) from, (int) to); 215 | 216 | if (from > to) { 217 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 218 | "invalid capture range: %i > %i", (ngx_int_t) from, 219 | (ngx_int_t) to); 220 | return NGX_ERROR; 221 | } 222 | 223 | if (from == to) { 224 | if (ctx->pending) { 225 | ctx->total_buffered = 0; 226 | 227 | dd("output pending"); 228 | *ctx->last_out = ctx->pending; 229 | ctx->last_out = ctx->last_pending; 230 | 231 | ctx->pending = NULL; 232 | ctx->last_pending = &ctx->pending; 233 | } 234 | 235 | ctx->copy_start = ctx->pos; 236 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 237 | ctx->pos = ctx->copy_end; 238 | 239 | return NGX_AGAIN; 240 | } 241 | 242 | /* 243 | * append the existing ctx->pending data right before 244 | * the $& capture to ctx->out. 245 | */ 246 | 247 | if (from >= ctx->stream_pos) { 248 | /* the match is completely on the current buf */ 249 | 250 | ctx->copy_start = ctx->pos; 251 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 252 | 253 | if (ctx->pending) { 254 | ctx->total_buffered = 0; 255 | 256 | *ctx->last_out = ctx->pending; 257 | ctx->last_out = ctx->last_pending; 258 | 259 | ctx->pending = NULL; 260 | ctx->last_pending = &ctx->pending; 261 | } 262 | 263 | dd("create ctx->pending as (%ld, %ld)", (long) from, (long) to); 264 | rc = ngx_http_replace_new_pending_buf(r, ctx, from, to, &cl); 265 | if (rc == NGX_ERROR) { 266 | return NGX_ERROR; 267 | } 268 | 269 | #if 1 270 | if (rc == NGX_BUSY) { 271 | dd("stop processing because of buffer size limit reached"); 272 | ctx->once = 1; 273 | ctx->copy_start = ctx->pos; 274 | ctx->copy_end = ctx->buf->last; 275 | ctx->pos = ctx->buf->last; 276 | return NGX_AGAIN; 277 | } 278 | #endif 279 | 280 | *ctx->last_pending = cl; 281 | ctx->last_pending = &cl->next; 282 | 283 | ctx->pos = ctx->buf->last; 284 | 285 | return NGX_AGAIN; 286 | } 287 | 288 | dd("from < ctx->stream_pos"); 289 | 290 | if (ctx->pending) { 291 | /* split ctx->pending into ctx->out and ctx->pending */ 292 | 293 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending, 294 | &ctx->last_pending, from, &cl, 295 | &last, 1) 296 | != NGX_OK) 297 | { 298 | return NGX_ERROR; 299 | } 300 | 301 | if (ctx->pending) { 302 | dd("adjust pending: pos=%d, from=%d", 303 | (int) ctx->pending->buf->file_pos, (int) from); 304 | 305 | ctx->total_buffered -= (size_t) 306 | (from - ctx->pending->buf->file_pos); 307 | 308 | *ctx->last_out = ctx->pending; 309 | ctx->last_out = ctx->last_pending; 310 | 311 | ctx->pending = NULL; 312 | ctx->last_pending = &ctx->pending; 313 | } 314 | 315 | if (cl) { 316 | dd("splitted ctx->pending into ctx->out and ctx->pending: %d", 317 | (int) ctx->total_buffered); 318 | 319 | ctx->pending = cl; 320 | ctx->last_pending = last; 321 | } 322 | } 323 | 324 | /* new pending data to buffer to ctx->pending */ 325 | 326 | rc = ngx_http_replace_new_pending_buf(r, ctx, ctx->pos 327 | - ctx->buf->pos 328 | + ctx->stream_pos, to, &cl); 329 | if (rc == NGX_ERROR) { 330 | return NGX_ERROR; 331 | } 332 | 333 | #if 1 334 | if (rc == NGX_BUSY) { 335 | ctx->once = 1; 336 | 337 | if (ctx->pending) { 338 | *ctx->last_out = ctx->pending; 339 | ctx->last_out = ctx->last_pending; 340 | 341 | ctx->pending = NULL; 342 | ctx->last_pending = &ctx->pending; 343 | } 344 | 345 | ctx->copy_start = ctx->pos; 346 | ctx->copy_end = ctx->buf->last; 347 | ctx->pos = ctx->buf->last; 348 | 349 | return NGX_AGAIN; 350 | } 351 | #endif 352 | 353 | *ctx->last_pending = cl; 354 | ctx->last_pending = &cl->next; 355 | 356 | ctx->copy_start = NULL; 357 | ctx->copy_end = NULL; 358 | 359 | ctx->pos = ctx->buf->last; 360 | 361 | return NGX_AGAIN; 362 | 363 | case SRE_DECLINED: 364 | ctx->total_buffered = 0; 365 | 366 | return NGX_DECLINED; 367 | 368 | default: 369 | /* SRE_ERROR */ 370 | return NGX_ERROR; 371 | } 372 | 373 | /* cannot reach here */ 374 | } 375 | 376 | 377 | ngx_int_t 378 | ngx_http_replace_non_capturing_parse(ngx_http_request_t *r, 379 | ngx_http_replace_ctx_t *ctx, ngx_chain_t *rematch) 380 | { 381 | sre_int_t ret, from, to, mfrom = -1, mto = -1; 382 | ngx_int_t rc; 383 | ngx_chain_t *new_rematch = NULL; 384 | ngx_chain_t *cl; 385 | ngx_chain_t **last_rematch, **last; 386 | size_t len; 387 | sre_int_t *pending_matched; 388 | 389 | dd("replace non capturing parse"); 390 | 391 | if (ctx->once || ctx->vm_done) { 392 | ctx->copy_start = ctx->pos; 393 | ctx->copy_end = ctx->buf->last; 394 | ctx->pos = ctx->buf->last; 395 | 396 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "once"); 397 | 398 | return NGX_AGAIN; 399 | } 400 | 401 | len = ctx->buf->last - ctx->pos; 402 | 403 | dd("=== process data chunk %p len=%d, pos=%u, special=%u, " 404 | "last=%u, \"%.*s\"", ctx->buf, (int) (ctx->buf->last - ctx->pos), 405 | (int) (ctx->pos - ctx->buf->pos + ctx->stream_pos), 406 | ctx->special_buf, ctx->last_buf, 407 | (int) (ctx->buf->last - ctx->pos), ctx->pos); 408 | 409 | ret = sre_vm_pike_exec(ctx->vm_ctx, ctx->pos, len, ctx->last_buf, 410 | &pending_matched); 411 | 412 | dd("vm pike exec: %d", (int) ret); 413 | 414 | if (ret >= 0) { 415 | ctx->regex_id = ret; 416 | ctx->total_buffered = 0; 417 | 418 | from = ctx->ovector[0]; 419 | to = ctx->ovector[1]; 420 | 421 | dd("pike vm ok: (%d, %d)", (int) from, (int) to); 422 | 423 | if (from >= ctx->stream_pos) { 424 | /* the match is completely on the current buf */ 425 | 426 | if (ctx->pending) { 427 | *ctx->last_out = ctx->pending; 428 | ctx->last_out = ctx->last_pending; 429 | 430 | ctx->pending = NULL; 431 | ctx->last_pending = &ctx->pending; 432 | } 433 | 434 | if (ctx->pending2) { 435 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 436 | "assertion failed: ctx->pending2 is not NULL " 437 | "when the match is completely on the current " 438 | "buf"); 439 | return NGX_ERROR; 440 | } 441 | 442 | ctx->copy_start = ctx->pos; 443 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 444 | 445 | dd("copy len: %d", (int) (ctx->copy_end - ctx->copy_start)); 446 | 447 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 448 | return NGX_OK; 449 | } 450 | 451 | /* from < ctx->stream_pos */ 452 | 453 | if (ctx->pending) { 454 | 455 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending, 456 | &ctx->last_pending, from, 457 | &cl, &last, 0) 458 | != NGX_OK) 459 | { 460 | return NGX_ERROR; 461 | } 462 | 463 | if (ctx->pending) { 464 | *ctx->last_out = ctx->pending; 465 | ctx->last_out = ctx->last_pending; 466 | 467 | ctx->pending = NULL; 468 | ctx->last_pending = &ctx->pending; 469 | } 470 | 471 | if (cl) { 472 | *last = ctx->free; 473 | ctx->free = cl; 474 | } 475 | } 476 | 477 | if (ctx->pending2) { 478 | 479 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending2, 480 | &ctx->last_pending2, 481 | to, &new_rematch, &last_rematch, 1) 482 | != NGX_OK) 483 | { 484 | return NGX_ERROR; 485 | } 486 | 487 | if (ctx->pending2) { 488 | *ctx->last_pending2 = ctx->free; 489 | ctx->free = ctx->pending2; 490 | 491 | ctx->pending2 = NULL; 492 | ctx->last_pending2 = &ctx->pending2; 493 | } 494 | 495 | if (new_rematch) { 496 | if (rematch) { 497 | ctx->rematch = rematch; 498 | } 499 | 500 | /* prepend cl to ctx->rematch */ 501 | *last_rematch = ctx->rematch; 502 | ctx->rematch = new_rematch; 503 | } 504 | } 505 | 506 | #if (DDEBUG) 507 | ngx_http_replace_dump_chain("ctx->rematch", &ctx->rematch, NULL); 508 | #endif 509 | 510 | ctx->copy_start = NULL; 511 | ctx->copy_end = NULL; 512 | 513 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 514 | 515 | return new_rematch ? NGX_BUSY : NGX_OK; 516 | } 517 | 518 | switch (ret) { 519 | case SRE_AGAIN: 520 | from = ctx->ovector[0]; 521 | to = ctx->ovector[1]; 522 | 523 | dd("pike vm again: (%d, %d)", (int) from, (int) to); 524 | 525 | if (from == -1) { 526 | from = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 527 | } 528 | 529 | if (to == -1) { 530 | to = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 531 | } 532 | 533 | dd("pike vm again (adjusted): stream pos:%d, (%d, %d)", 534 | (int) ctx->stream_pos, (int) from, (int) to); 535 | 536 | if (from > to) { 537 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 538 | "invalid capture range: %i > %i", (ngx_int_t) from, 539 | (ngx_int_t) to); 540 | return NGX_ERROR; 541 | } 542 | 543 | if (pending_matched) { 544 | mfrom = pending_matched[0]; 545 | mto = pending_matched[1]; 546 | 547 | dd("pending matched: (%ld, %ld)", (long) mfrom, (long) mto); 548 | } 549 | 550 | if (from == to) { 551 | if (ctx->pending) { 552 | ctx->total_buffered = 0; 553 | 554 | dd("output pending"); 555 | *ctx->last_out = ctx->pending; 556 | ctx->last_out = ctx->last_pending; 557 | 558 | ctx->pending = NULL; 559 | ctx->last_pending = &ctx->pending; 560 | } 561 | 562 | ctx->copy_start = ctx->pos; 563 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 564 | ctx->pos = ctx->copy_end; 565 | 566 | ngx_http_replace_check_total_buffered(r, ctx, to - from, 567 | mto - mfrom); 568 | return NGX_AGAIN; 569 | } 570 | 571 | /* 572 | * append the existing ctx->pending data right before 573 | * the $& capture to ctx->out. 574 | */ 575 | 576 | if (from >= ctx->stream_pos) { 577 | /* the match is completely on the current buf */ 578 | 579 | ctx->copy_start = ctx->pos; 580 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 581 | 582 | if (ctx->pending) { 583 | ctx->total_buffered = 0; 584 | 585 | *ctx->last_out = ctx->pending; 586 | ctx->last_out = ctx->last_pending; 587 | 588 | ctx->pending = NULL; 589 | ctx->last_pending = &ctx->pending; 590 | } 591 | 592 | if (ctx->pending2) { 593 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 594 | "assertion failed: ctx->pending2 is not NULL " 595 | "when the match is completely on the current " 596 | "buf"); 597 | return NGX_ERROR; 598 | } 599 | 600 | if (pending_matched) { 601 | 602 | if (from < mfrom) { 603 | /* create ctx->pending as (from, mfrom) */ 604 | 605 | rc = ngx_http_replace_new_pending_buf(r, ctx, from, mfrom, 606 | &cl); 607 | if (rc == NGX_ERROR) { 608 | return NGX_ERROR; 609 | } 610 | 611 | if (rc == NGX_BUSY) { 612 | dd("stop processing because of buffer size limit " 613 | "reached"); 614 | ctx->once = 1; 615 | ctx->copy_start = ctx->pos; 616 | ctx->copy_end = ctx->buf->last; 617 | ctx->pos = ctx->buf->last; 618 | return NGX_AGAIN; 619 | } 620 | 621 | *ctx->last_pending = cl; 622 | ctx->last_pending = &cl->next; 623 | } 624 | 625 | if (mto < to) { 626 | /* create ctx->pending2 as (mto, to) */ 627 | rc = ngx_http_replace_new_pending_buf(r, ctx, mto, to, &cl); 628 | if (rc == NGX_ERROR) { 629 | return NGX_ERROR; 630 | } 631 | 632 | #if 1 633 | if (rc == NGX_BUSY) { 634 | dd("stop processing because of buffer size limit " 635 | "reached"); 636 | ctx->once = 1; 637 | ctx->copy_start = ctx->pos; 638 | ctx->copy_end = ctx->buf->last; 639 | ctx->pos = ctx->buf->last; 640 | return NGX_AGAIN; 641 | } 642 | #endif 643 | 644 | *ctx->last_pending2 = cl; 645 | ctx->last_pending2 = &cl->next; 646 | } 647 | 648 | } else { 649 | dd("create ctx->pending as (%ld, %ld)", (long) from, (long) to); 650 | rc = ngx_http_replace_new_pending_buf(r, ctx, from, to, &cl); 651 | if (rc == NGX_ERROR) { 652 | return NGX_ERROR; 653 | } 654 | 655 | #if 1 656 | if (rc == NGX_BUSY) { 657 | dd("stop processing because of buffer size limit reached"); 658 | ctx->once = 1; 659 | ctx->copy_start = ctx->pos; 660 | ctx->copy_end = ctx->buf->last; 661 | ctx->pos = ctx->buf->last; 662 | return NGX_AGAIN; 663 | } 664 | #endif 665 | 666 | *ctx->last_pending = cl; 667 | ctx->last_pending = &cl->next; 668 | } 669 | 670 | ctx->pos = ctx->buf->last; 671 | 672 | ngx_http_replace_check_total_buffered(r, ctx, to - from, 673 | mto - mfrom); 674 | 675 | return NGX_AGAIN; 676 | } 677 | 678 | dd("from < ctx->stream_pos"); 679 | 680 | if (ctx->pending) { 681 | /* split ctx->pending into ctx->out and ctx->pending */ 682 | 683 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending, 684 | &ctx->last_pending, from, &cl, 685 | &last, 1) 686 | != NGX_OK) 687 | { 688 | return NGX_ERROR; 689 | } 690 | 691 | if (ctx->pending) { 692 | dd("adjust pending: pos=%d, from=%d", 693 | (int) ctx->pending->buf->file_pos, (int) from); 694 | 695 | ctx->total_buffered -= (size_t) 696 | (from - ctx->pending->buf->file_pos); 697 | 698 | *ctx->last_out = ctx->pending; 699 | ctx->last_out = ctx->last_pending; 700 | 701 | ctx->pending = NULL; 702 | ctx->last_pending = &ctx->pending; 703 | } 704 | 705 | if (cl) { 706 | dd("splitted ctx->pending into ctx->out and ctx->pending: %d", 707 | (int) ctx->total_buffered); 708 | ctx->pending = cl; 709 | ctx->last_pending = last; 710 | } 711 | 712 | if (pending_matched && !ctx->pending2 && mto >= ctx->stream_pos) { 713 | dd("splitting ctx->pending into ctx->pending and ctx->free"); 714 | 715 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending, 716 | &ctx->last_pending, mfrom, &cl, 717 | &last, 0) 718 | != NGX_OK) 719 | { 720 | return NGX_ERROR; 721 | } 722 | 723 | if (cl) { 724 | ctx->total_buffered -= (size_t) (ctx->stream_pos - mfrom); 725 | 726 | dd("splitted ctx->pending into ctx->pending and ctx->free"); 727 | *last = ctx->free; 728 | ctx->free = cl; 729 | } 730 | } 731 | } 732 | 733 | if (ctx->pending2) { 734 | 735 | if (pending_matched) { 736 | dd("splitting ctx->pending2 into ctx->free and ctx->pending2"); 737 | 738 | if (ngx_http_replace_split_chain(r, ctx, &ctx->pending2, 739 | &ctx->last_pending2, 740 | mto, &cl, &last, 1) 741 | != NGX_OK) 742 | { 743 | return NGX_ERROR; 744 | } 745 | 746 | if (ctx->pending2) { 747 | 748 | dd("total buffered reduced by %d (was %d)", 749 | (int) (mto - ctx->pending2->buf->file_pos), 750 | (int) ctx->total_buffered); 751 | 752 | ctx->total_buffered -= (size_t) 753 | (mto - ctx->pending2->buf->file_pos); 754 | 755 | *ctx->last_pending2 = ctx->free; 756 | ctx->free = ctx->pending2; 757 | 758 | ctx->pending2 = NULL; 759 | ctx->last_pending2 = &ctx->pending2; 760 | } 761 | 762 | if (cl) { 763 | ctx->pending2 = cl; 764 | ctx->last_pending2 = last; 765 | } 766 | } 767 | 768 | if (mto < to) { 769 | dd("new pending data to buffer to ctx->pending2: (%ld, %ld)", 770 | (long) mto, (long) to); 771 | 772 | rc = ngx_http_replace_new_pending_buf(r, ctx, mto, to, &cl); 773 | if (rc == NGX_ERROR) { 774 | return NGX_ERROR; 775 | } 776 | 777 | #if 1 778 | if (rc == NGX_BUSY) { 779 | ctx->once = 1; 780 | 781 | if (ctx->pending) { 782 | *ctx->last_out = ctx->pending; 783 | ctx->last_out = ctx->last_pending; 784 | 785 | ctx->pending = NULL; 786 | ctx->last_pending = &ctx->pending; 787 | } 788 | 789 | ctx->copy_start = NULL; 790 | ctx->copy_end = NULL; 791 | 792 | if (ctx->pending2) { 793 | new_rematch = ctx->pending2; 794 | last_rematch = ctx->last_pending2; 795 | 796 | if (rematch) { 797 | ctx->rematch = rematch; 798 | } 799 | 800 | /* prepend cl to ctx->rematch */ 801 | *last_rematch = ctx->rematch; 802 | ctx->rematch = new_rematch; 803 | 804 | ctx->pending2 = NULL; 805 | ctx->last_pending2 = &ctx->pending2; 806 | } 807 | 808 | ctx->pos = ctx->buf->pos + (mto - ctx->stream_pos); 809 | return new_rematch ? NGX_BUSY : NGX_OK; 810 | } 811 | #endif 812 | 813 | *ctx->last_pending2 = cl; 814 | ctx->last_pending2 = &cl->next; 815 | } 816 | 817 | ctx->copy_start = NULL; 818 | ctx->copy_end = NULL; 819 | 820 | ctx->pos = ctx->buf->last; 821 | 822 | ngx_http_replace_check_total_buffered(r, ctx, to - from, 823 | mto - mfrom); 824 | 825 | return NGX_AGAIN; 826 | } 827 | 828 | /* ctx->pending2 == NULL */ 829 | 830 | if (pending_matched) { 831 | 832 | if (mto < to) { 833 | /* new pending data to buffer to ctx->pending2 */ 834 | rc = ngx_http_replace_new_pending_buf(r, ctx, mto, to, &cl); 835 | if (rc == NGX_ERROR) { 836 | return NGX_ERROR; 837 | } 838 | 839 | if (rc == NGX_BUSY) { 840 | ctx->once = 1; 841 | 842 | if (ctx->pending) { 843 | *ctx->last_out = ctx->pending; 844 | ctx->last_out = ctx->last_pending; 845 | 846 | ctx->pending = NULL; 847 | ctx->last_pending = &ctx->pending; 848 | } 849 | 850 | ctx->copy_start = NULL; 851 | ctx->copy_end = NULL; 852 | ctx->pos = ctx->buf->pos + mto - ctx->stream_pos; 853 | 854 | return NGX_OK; 855 | } 856 | 857 | *ctx->last_pending2 = cl; 858 | ctx->last_pending2 = &cl->next; 859 | } 860 | 861 | /* otherwise no new data to buffer */ 862 | 863 | } else { 864 | 865 | /* new pending data to buffer to ctx->pending */ 866 | rc = ngx_http_replace_new_pending_buf(r, ctx, ctx->pos 867 | - ctx->buf->pos 868 | + ctx->stream_pos, to, &cl); 869 | if (rc == NGX_ERROR) { 870 | return NGX_ERROR; 871 | } 872 | 873 | #if 1 874 | if (rc == NGX_BUSY) { 875 | ctx->once = 1; 876 | 877 | if (ctx->pending) { 878 | *ctx->last_out = ctx->pending; 879 | ctx->last_out = ctx->last_pending; 880 | 881 | ctx->pending = NULL; 882 | ctx->last_pending = &ctx->pending; 883 | } 884 | 885 | ctx->copy_start = ctx->pos; 886 | ctx->copy_end = ctx->buf->last; 887 | ctx->pos = ctx->buf->last; 888 | 889 | return NGX_AGAIN; 890 | } 891 | #endif 892 | 893 | *ctx->last_pending = cl; 894 | ctx->last_pending = &cl->next; 895 | } 896 | 897 | ctx->copy_start = NULL; 898 | ctx->copy_end = NULL; 899 | 900 | ctx->pos = ctx->buf->last; 901 | 902 | ngx_http_replace_check_total_buffered(r, ctx, to - from, 903 | mto - mfrom); 904 | 905 | return NGX_AGAIN; 906 | 907 | case SRE_DECLINED: 908 | ctx->total_buffered = 0; 909 | 910 | return NGX_DECLINED; 911 | 912 | default: 913 | /* SRE_ERROR */ 914 | return NGX_ERROR; 915 | } 916 | 917 | /* cannot reach here */ 918 | } 919 | 920 | 921 | static void 922 | ngx_http_replace_check_total_buffered(ngx_http_request_t *r, 923 | ngx_http_replace_ctx_t *ctx, sre_int_t len, sre_int_t mlen) 924 | { 925 | dd("total buffered: %d", (int) ctx->total_buffered); 926 | 927 | if ((ssize_t) ctx->total_buffered != len - mlen) { 928 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 929 | "replace filter: ctx->total_buffered out of " 930 | "sync: it is %i but should be %uz", 931 | ctx->total_buffered, (ngx_int_t) (len - mlen)); 932 | 933 | #if (DDEBUG) 934 | assert(0); 935 | #endif 936 | } 937 | } 938 | -------------------------------------------------------------------------------- /src/ngx_http_replace_parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_REPLACE_PARSE_H_INCLUDED_ 2 | #define _NGX_HTTP_REPLACE_PARSE_H_INCLUDED_ 3 | 4 | 5 | #include "ngx_http_replace_filter_module.h" 6 | 7 | 8 | ngx_int_t ngx_http_replace_non_capturing_parse(ngx_http_request_t *r, 9 | ngx_http_replace_ctx_t *ctx, ngx_chain_t *rematch); 10 | ngx_int_t ngx_http_replace_capturing_parse(ngx_http_request_t *r, 11 | ngx_http_replace_ctx_t *ctx, ngx_chain_t *rematch); 12 | 13 | 14 | #endif /* _NGX_HTTP_REPLACE_PARSE_H_INCLUDED_ */ 15 | -------------------------------------------------------------------------------- /src/ngx_http_replace_script.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_replace_script.h" 14 | 15 | 16 | static void *ngx_http_replace_script_add_code(ngx_array_t *codes, size_t size); 17 | static size_t ngx_http_replace_script_copy_len_code( 18 | ngx_http_replace_script_engine_t *e); 19 | static size_t 20 | ngx_http_replace_script_copy_code(ngx_http_replace_script_engine_t *e); 21 | static ngx_int_t ngx_http_replace_script_add_copy_code( 22 | ngx_http_replace_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last); 23 | static ngx_int_t 24 | ngx_http_replace_script_compile(ngx_http_replace_script_compile_t *sc); 25 | static ngx_int_t ngx_http_replace_script_add_capture_code( 26 | ngx_http_replace_script_compile_t *sc, ngx_uint_t n); 27 | static size_t ngx_http_replace_script_copy_capture_len_code( 28 | ngx_http_replace_script_engine_t *e); 29 | static size_t ngx_http_replace_script_copy_capture_code( 30 | ngx_http_replace_script_engine_t *e); 31 | static ngx_int_t 32 | ngx_http_replace_script_done(ngx_http_replace_script_compile_t *sc); 33 | static ngx_int_t ngx_http_replace_script_init_arrays( 34 | ngx_http_replace_script_compile_t *sc); 35 | static ngx_int_t 36 | ngx_http_replace_script_add_var_code(ngx_http_replace_script_compile_t *sc, 37 | ngx_str_t *name); 38 | static size_t 39 | ngx_http_replace_script_copy_var_len_code( 40 | ngx_http_replace_script_engine_t *e); 41 | static size_t 42 | ngx_http_replace_script_copy_var_code(ngx_http_replace_script_engine_t *e); 43 | static void ngx_http_replace_count_variables(u_char *src, size_t len, 44 | ngx_uint_t *ngxvars, ngx_uint_t *capvars); 45 | 46 | 47 | ngx_int_t 48 | ngx_http_replace_compile_complex_value( 49 | ngx_http_replace_compile_complex_value_t *ccv) 50 | { 51 | ngx_str_t *v; 52 | ngx_uint_t n, ngxvars, capvars; 53 | ngx_array_t lengths, values, *pl, *pv; 54 | 55 | ngx_http_replace_script_compile_t sc; 56 | 57 | v = ccv->value; 58 | 59 | ngx_http_replace_count_variables(v->data, v->len, &ngxvars, &capvars); 60 | 61 | ccv->complex_value->value = *v; 62 | ccv->complex_value->lengths = NULL; 63 | ccv->complex_value->values = NULL; 64 | 65 | if (capvars == 0 && ngxvars == 0) { 66 | return NGX_OK; 67 | } 68 | 69 | n = capvars * (2 * sizeof(ngx_http_replace_script_copy_code_t) 70 | + sizeof(ngx_http_replace_script_capture_code_t)) 71 | + ngxvars * (2 * sizeof(ngx_http_replace_script_copy_code_t) 72 | + sizeof(ngx_http_replace_script_var_code_t)) 73 | + sizeof(uintptr_t); 74 | 75 | if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) { 76 | return NGX_ERROR; 77 | } 78 | 79 | n = capvars * (2 * sizeof(ngx_http_replace_script_copy_code_t) 80 | + sizeof(ngx_http_replace_script_capture_code_t)) 81 | + ngxvars * (2 * sizeof(ngx_http_replace_script_var_code_t) 82 | + sizeof(ngx_http_replace_script_var_code_t)) 83 | + sizeof(uintptr_t); 84 | 85 | if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) { 86 | return NGX_ERROR; 87 | } 88 | 89 | pl = &lengths; 90 | pv = &values; 91 | 92 | ngx_memzero(&sc, sizeof(ngx_http_replace_script_compile_t)); 93 | 94 | sc.cf = ccv->cf; 95 | sc.source = v; 96 | sc.lengths = &pl; 97 | sc.values = &pv; 98 | 99 | if (ngx_http_replace_script_compile(&sc) != NGX_OK) { 100 | ngx_array_destroy(&lengths); 101 | ngx_array_destroy(&values); 102 | return NGX_ERROR; 103 | } 104 | 105 | ccv->complex_value->lengths = lengths.elts; 106 | ccv->complex_value->values = values.elts; 107 | ccv->complex_value->capture_variables = sc.capture_variables; 108 | 109 | return NGX_OK; 110 | } 111 | 112 | 113 | ngx_int_t 114 | ngx_http_replace_complex_value(ngx_http_request_t *r, 115 | ngx_chain_t *captured, sre_uint_t ncaps, sre_int_t *cap, 116 | ngx_http_replace_complex_value_t *val, ngx_str_t *value) 117 | { 118 | size_t len; 119 | ngx_http_replace_script_code_pt code; 120 | ngx_http_replace_script_len_code_pt lcode; 121 | ngx_http_replace_script_engine_t e; 122 | 123 | if (val->lengths == NULL) { 124 | *value = val->value; 125 | return NGX_OK; 126 | } 127 | 128 | ngx_memzero(&e, sizeof(ngx_http_replace_script_engine_t)); 129 | 130 | e.request = r; 131 | e.ncaptures = (ncaps + 1) * 2; 132 | e.captures_data = captured; 133 | e.captures = cap; 134 | e.ip = val->lengths; 135 | 136 | len = 0; 137 | 138 | while (*(uintptr_t *) e.ip) { 139 | lcode = *(ngx_http_replace_script_len_code_pt *) e.ip; 140 | len += lcode(&e); 141 | } 142 | 143 | value->len = len; 144 | value->data = ngx_pnalloc(r->pool, len); 145 | if (value->data == NULL) { 146 | return NGX_ERROR; 147 | } 148 | 149 | e.ip = val->values; 150 | e.pos = value->data; 151 | 152 | while (*(uintptr_t *) e.ip) { 153 | code = *(ngx_http_replace_script_code_pt *) e.ip; 154 | code((ngx_http_replace_script_engine_t *) &e); 155 | } 156 | 157 | return NGX_OK; 158 | } 159 | 160 | 161 | static ngx_int_t 162 | ngx_http_replace_script_compile(ngx_http_replace_script_compile_t *sc) 163 | { 164 | u_char ch; 165 | ngx_str_t name; 166 | ngx_uint_t i, bracket; 167 | unsigned num_var; 168 | ngx_uint_t n = 0; 169 | 170 | if (ngx_http_replace_script_init_arrays(sc) != NGX_OK) { 171 | return NGX_ERROR; 172 | } 173 | 174 | for (i = 0; i < sc->source->len; /* void */ ) { 175 | 176 | name.len = 0; 177 | 178 | if (sc->source->data[i] == '$') { 179 | 180 | if (++i == sc->source->len) { 181 | goto invalid_variable; 182 | } 183 | 184 | if (sc->source->data[i] == '$') { 185 | name.data = &sc->source->data[i]; 186 | i++; 187 | name.len++; 188 | sc->size += name.len; 189 | 190 | if (ngx_http_replace_script_add_copy_code(sc, &name, 191 | (i == sc->source->len)) 192 | != NGX_OK) 193 | { 194 | return NGX_ERROR; 195 | } 196 | 197 | continue; 198 | } 199 | 200 | if ((sc->source->data[i] >= '1' && sc->source->data[i] <= '9') 201 | || sc->source->data[i] == '&') 202 | { 203 | num_var = 1; 204 | n = 0; 205 | 206 | } else { 207 | num_var = 0; 208 | } 209 | 210 | if (sc->source->data[i] == '{') { 211 | bracket = 1; 212 | 213 | if (++i == sc->source->len) { 214 | goto invalid_variable; 215 | } 216 | 217 | if ((sc->source->data[i] >= '1' && sc->source->data[i] <= '9') 218 | || sc->source->data[i] == '&') 219 | { 220 | num_var = 1; 221 | n = 0; 222 | } 223 | 224 | name.data = &sc->source->data[i]; 225 | 226 | } else { 227 | bracket = 0; 228 | name.data = &sc->source->data[i]; 229 | } 230 | 231 | for ( /* void */ ; i < sc->source->len; i++, name.len++) { 232 | ch = sc->source->data[i]; 233 | 234 | if (ch == '}' && bracket) { 235 | i++; 236 | bracket = 0; 237 | break; 238 | } 239 | 240 | if (num_var) { 241 | if (ch >= '0' && ch <= '9') { 242 | n = n * 10 + (ch - '0'); 243 | continue; 244 | } 245 | 246 | if (ch == '&') { 247 | i++; 248 | name.len++; 249 | } 250 | 251 | break; 252 | } 253 | 254 | /* not a number variable like $1, $2, etc */ 255 | 256 | if ((ch >= 'A' && ch <= 'Z') 257 | || (ch >= 'a' && ch <= 'z') 258 | || (ch >= '0' && ch <= '9') 259 | || ch == '_') 260 | { 261 | continue; 262 | } 263 | 264 | break; 265 | } 266 | 267 | if (bracket) { 268 | ngx_log_error(NGX_LOG_ERR, sc->cf->log, 0, 269 | "the closing bracket in \"%V\" " 270 | "variable is missing", &name); 271 | return NGX_ERROR; 272 | } 273 | 274 | if (name.len == 0) { 275 | goto invalid_variable; 276 | } 277 | 278 | if (num_var) { 279 | dd("found numbered capturing variable \"%.*s\"", 280 | (int) name.len, name.data); 281 | 282 | sc->capture_variables++; 283 | 284 | if (ngx_http_replace_script_add_capture_code(sc, n) != NGX_OK) { 285 | return NGX_ERROR; 286 | } 287 | 288 | } else { 289 | sc->nginx_variables++; 290 | 291 | if (ngx_http_replace_script_add_var_code(sc, &name) != NGX_OK) { 292 | return NGX_ERROR; 293 | } 294 | } 295 | 296 | continue; 297 | } 298 | 299 | name.data = &sc->source->data[i]; 300 | 301 | while (i < sc->source->len) { 302 | 303 | if (sc->source->data[i] == '$') { 304 | break; 305 | } 306 | 307 | i++; 308 | name.len++; 309 | } 310 | 311 | sc->size += name.len; 312 | 313 | if (ngx_http_replace_script_add_copy_code(sc, &name, 314 | (i == sc->source->len)) 315 | != NGX_OK) 316 | { 317 | return NGX_ERROR; 318 | } 319 | } 320 | 321 | return ngx_http_replace_script_done(sc); 322 | 323 | invalid_variable: 324 | 325 | ngx_log_error(NGX_LOG_ERR, sc->cf->log, 0, 326 | "replace script: invalid capturing variable name found " 327 | "in \"%V\"", sc->source); 328 | 329 | return NGX_ERROR; 330 | } 331 | 332 | 333 | static ngx_int_t 334 | ngx_http_replace_script_add_copy_code(ngx_http_replace_script_compile_t *sc, 335 | ngx_str_t *value, ngx_uint_t last) 336 | { 337 | size_t size, len; 338 | ngx_http_replace_script_copy_code_t *code; 339 | 340 | len = value->len; 341 | 342 | code = ngx_http_replace_script_add_code(*sc->lengths, 343 | sizeof(ngx_http_replace_script_copy_code_t)); 344 | if (code == NULL) { 345 | return NGX_ERROR; 346 | } 347 | 348 | code->code = (ngx_http_replace_script_code_pt) 349 | ngx_http_replace_script_copy_len_code; 350 | code->len = len; 351 | 352 | size = (sizeof(ngx_http_replace_script_copy_code_t) + len + 353 | sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); 354 | 355 | code = ngx_http_replace_script_add_code(*sc->values, size); 356 | if (code == NULL) { 357 | return NGX_ERROR; 358 | } 359 | 360 | code->code = (ngx_http_replace_script_code_pt) 361 | ngx_http_replace_script_copy_code; 362 | code->len = len; 363 | 364 | ngx_memcpy((u_char *) code + sizeof(ngx_http_replace_script_copy_code_t), 365 | value->data, value->len); 366 | 367 | return NGX_OK; 368 | } 369 | 370 | 371 | static size_t 372 | ngx_http_replace_script_copy_len_code(ngx_http_replace_script_engine_t *e) 373 | { 374 | ngx_http_replace_script_copy_code_t *code; 375 | 376 | code = (ngx_http_replace_script_copy_code_t *) e->ip; 377 | 378 | e->ip += sizeof(ngx_http_replace_script_copy_code_t); 379 | 380 | return code->len; 381 | } 382 | 383 | 384 | static size_t 385 | ngx_http_replace_script_copy_code(ngx_http_replace_script_engine_t *e) 386 | { 387 | u_char *p; 388 | 389 | ngx_http_replace_script_copy_code_t *code; 390 | 391 | code = (ngx_http_replace_script_copy_code_t *) e->ip; 392 | 393 | p = e->pos; 394 | 395 | if (!e->skip) { 396 | e->pos = ngx_copy(p, e->ip 397 | + sizeof(ngx_http_replace_script_copy_code_t), 398 | code->len); 399 | } 400 | 401 | e->ip += sizeof(ngx_http_replace_script_copy_code_t) 402 | + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1)); 403 | 404 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, 405 | "replace script copy: \"%*s\"", e->pos - p, p); 406 | 407 | return 0; 408 | } 409 | 410 | 411 | static ngx_int_t 412 | ngx_http_replace_script_add_capture_code(ngx_http_replace_script_compile_t *sc, 413 | ngx_uint_t n) 414 | { 415 | ngx_http_replace_script_capture_code_t *code; 416 | 417 | code = ngx_http_replace_script_add_code(*sc->lengths, 418 | sizeof(ngx_http_replace_script_capture_code_t)); 419 | if (code == NULL) { 420 | return NGX_ERROR; 421 | } 422 | 423 | code->code = (ngx_http_replace_script_code_pt) 424 | ngx_http_replace_script_copy_capture_len_code; 425 | code->n = 2 * n; 426 | 427 | code = ngx_http_replace_script_add_code(*sc->values, 428 | sizeof(ngx_http_replace_script_capture_code_t)); 429 | if (code == NULL) { 430 | return NGX_ERROR; 431 | } 432 | 433 | code->code = (ngx_http_replace_script_code_pt) 434 | ngx_http_replace_script_copy_capture_code; 435 | code->n = 2 * n; 436 | 437 | return NGX_OK; 438 | } 439 | 440 | 441 | static size_t 442 | ngx_http_replace_script_copy_capture_len_code( 443 | ngx_http_replace_script_engine_t *e) 444 | { 445 | sre_int_t *cap; 446 | ngx_uint_t n; 447 | 448 | ngx_http_replace_script_capture_code_t *code; 449 | 450 | code = (ngx_http_replace_script_capture_code_t *) e->ip; 451 | 452 | e->ip += sizeof(ngx_http_replace_script_capture_code_t); 453 | 454 | n = code->n; 455 | 456 | dd("group index: %d, ncaptures: %d", (int) n, (int) e->ncaptures); 457 | 458 | if (n + 1 < e->ncaptures) { 459 | cap = e->captures; 460 | return cap[n + 1] - cap[n]; 461 | } 462 | 463 | return 0; 464 | } 465 | 466 | 467 | static size_t 468 | ngx_http_replace_script_copy_capture_code(ngx_http_replace_script_engine_t *e) 469 | { 470 | sre_int_t *cap, from, to, len; 471 | u_char *p; 472 | #if (NGX_DEBUG) 473 | u_char *pos; 474 | #endif 475 | ngx_uint_t n; 476 | ngx_chain_t *cl; 477 | 478 | ngx_http_replace_script_capture_code_t *code; 479 | 480 | code = (ngx_http_replace_script_capture_code_t *) e->ip; 481 | 482 | e->ip += sizeof(ngx_http_replace_script_capture_code_t); 483 | 484 | n = code->n; 485 | 486 | #if (NGX_DEBUG) 487 | pos = e->pos; 488 | #endif 489 | 490 | if (n < e->ncaptures) { 491 | 492 | cap = e->captures; 493 | from = cap[n]; 494 | to = cap[n + 1]; 495 | 496 | dd("captures data: %p", e->captures_data); 497 | 498 | for (cl = e->captures_data; cl; cl = cl->next) { 499 | 500 | if (from >= cl->buf->file_last) { 501 | continue; 502 | } 503 | 504 | /* from < cl->buf->file_last */ 505 | 506 | if (to <= cl->buf->file_pos) { 507 | break; 508 | } 509 | 510 | p = cl->buf->pos + (from - cl->buf->file_pos); 511 | len = ngx_min(cl->buf->file_last, to) - from; 512 | e->pos = ngx_copy(e->pos, p, len); 513 | from += len; 514 | } 515 | } 516 | 517 | #if (NGX_DEBUG) 518 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, 519 | "replace script capture: \"%*s\"", e->pos - pos, pos); 520 | #endif 521 | 522 | return 0; 523 | } 524 | 525 | 526 | static ngx_int_t 527 | ngx_http_replace_script_init_arrays(ngx_http_replace_script_compile_t *sc) 528 | { 529 | ngx_uint_t n; 530 | 531 | if (*sc->lengths == NULL) { 532 | n = sc->capture_variables 533 | * (2 * sizeof(ngx_http_replace_script_copy_code_t) 534 | + sizeof(ngx_http_replace_script_capture_code_t)) 535 | + sc->nginx_variables 536 | * (2 * sizeof(ngx_http_replace_script_copy_code_t) 537 | + sizeof(ngx_http_replace_script_var_code_t)) 538 | + sizeof(uintptr_t); 539 | 540 | *sc->lengths = ngx_array_create(sc->cf->pool, n, 1); 541 | if (*sc->lengths == NULL) { 542 | return NGX_ERROR; 543 | } 544 | } 545 | 546 | if (*sc->values == NULL) { 547 | n = sc->capture_variables 548 | * (2 * sizeof(ngx_http_replace_script_copy_code_t) 549 | + sizeof(ngx_http_replace_script_capture_code_t)) 550 | + sc->nginx_variables 551 | * (2 * sizeof(ngx_http_replace_script_copy_code_t) 552 | + sizeof(ngx_http_replace_script_var_code_t)) 553 | + sizeof(uintptr_t); 554 | 555 | *sc->values = ngx_array_create(sc->cf->pool, n, 1); 556 | if (*sc->values == NULL) { 557 | return NGX_ERROR; 558 | } 559 | } 560 | 561 | sc->nginx_variables = 0; 562 | sc->capture_variables = 0; 563 | 564 | return NGX_OK; 565 | } 566 | 567 | 568 | static ngx_int_t 569 | ngx_http_replace_script_done(ngx_http_replace_script_compile_t *sc) 570 | { 571 | uintptr_t *code; 572 | 573 | code = ngx_http_replace_script_add_code(*sc->lengths, 574 | sizeof(uintptr_t)); 575 | if (code == NULL) { 576 | return NGX_ERROR; 577 | } 578 | 579 | *code = (uintptr_t) NULL; 580 | 581 | code = ngx_http_replace_script_add_code(*sc->values, sizeof(uintptr_t)); 582 | if (code == NULL) { 583 | return NGX_ERROR; 584 | } 585 | 586 | *code = (uintptr_t) NULL; 587 | 588 | return NGX_OK; 589 | } 590 | 591 | 592 | static void * 593 | ngx_http_replace_script_add_code(ngx_array_t *codes, size_t size) 594 | { 595 | return ngx_array_push_n(codes, size); 596 | } 597 | 598 | 599 | static ngx_int_t 600 | ngx_http_replace_script_add_var_code(ngx_http_replace_script_compile_t *sc, 601 | ngx_str_t *name) 602 | { 603 | ngx_int_t index; 604 | ngx_http_replace_script_var_code_t *code; 605 | 606 | index = ngx_http_get_variable_index(sc->cf, name); 607 | 608 | if (index == NGX_ERROR) { 609 | return NGX_ERROR; 610 | } 611 | 612 | code = ngx_http_replace_script_add_code(*sc->lengths, 613 | sizeof(ngx_http_replace_script_var_code_t)); 614 | if (code == NULL) { 615 | return NGX_ERROR; 616 | } 617 | 618 | code->code = (ngx_http_replace_script_code_pt) 619 | ngx_http_replace_script_copy_var_len_code; 620 | 621 | code->index = (uintptr_t) index; 622 | 623 | code = ngx_http_replace_script_add_code(*sc->values, 624 | sizeof(ngx_http_replace_script_var_code_t)); 625 | if (code == NULL) { 626 | return NGX_ERROR; 627 | } 628 | 629 | code->code = (ngx_http_replace_script_code_pt) 630 | ngx_http_replace_script_copy_var_code; 631 | code->index = (uintptr_t) index; 632 | 633 | return NGX_OK; 634 | } 635 | 636 | 637 | static size_t 638 | ngx_http_replace_script_copy_var_len_code(ngx_http_replace_script_engine_t *e) 639 | { 640 | ngx_http_variable_value_t *value; 641 | ngx_http_replace_script_var_code_t *code; 642 | 643 | code = (ngx_http_replace_script_var_code_t *) e->ip; 644 | 645 | e->ip += sizeof(ngx_http_replace_script_var_code_t); 646 | 647 | value = ngx_http_get_indexed_variable(e->request, code->index); 648 | 649 | if (value && !value->not_found) { 650 | return value->len; 651 | } 652 | 653 | return 0; 654 | } 655 | 656 | 657 | static size_t 658 | ngx_http_replace_script_copy_var_code(ngx_http_replace_script_engine_t *e) 659 | { 660 | u_char *p; 661 | ngx_http_variable_value_t *value; 662 | ngx_http_replace_script_var_code_t *code; 663 | 664 | code = (ngx_http_replace_script_var_code_t *) e->ip; 665 | 666 | e->ip += sizeof(ngx_http_replace_script_var_code_t); 667 | 668 | if (!e->skip) { 669 | 670 | value = ngx_http_get_indexed_variable(e->request, code->index); 671 | 672 | if (value && !value->not_found) { 673 | p = e->pos; 674 | e->pos = ngx_copy(p, value->data, value->len); 675 | 676 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, 677 | e->request->connection->log, 0, 678 | "http replace script var: \"%*s\"", e->pos - p, p); 679 | } 680 | } 681 | 682 | return 0; 683 | } 684 | 685 | 686 | static void 687 | ngx_http_replace_count_variables(u_char *src, size_t len, 688 | ngx_uint_t *ngxvars, ngx_uint_t *capvars) 689 | { 690 | ngx_uint_t i; 691 | unsigned var = 0; 692 | u_char c; 693 | 694 | *ngxvars = 0; 695 | *capvars = 0; 696 | 697 | for (i = 0; i < len; i++) { 698 | c = src[i]; 699 | 700 | if (c == '$') { 701 | if (var) { 702 | var = 0; 703 | 704 | } else { 705 | var = 1; 706 | } 707 | 708 | } else if (var) { 709 | if ((c >= '1' && c <= '9') || c == '&') { 710 | (*capvars)++; 711 | 712 | } else { 713 | (*ngxvars)++; 714 | } 715 | 716 | var = 0; 717 | } 718 | } 719 | } 720 | 721 | /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ 722 | -------------------------------------------------------------------------------- /src/ngx_http_replace_script.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef _NGX_HTTP_REPLACE_SCRIPT_H_INCLUDED_ 8 | #define _NGX_HTTP_REPLACE_SCRIPT_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | typedef struct { 19 | ngx_conf_t *cf; 20 | ngx_str_t *source; 21 | 22 | ngx_array_t **lengths; 23 | ngx_array_t **values; 24 | 25 | ngx_uint_t capture_variables; /* captures $1, $2, etc */ 26 | ngx_uint_t nginx_variables; /* nginx variables */ 27 | ngx_uint_t size; 28 | } ngx_http_replace_script_compile_t; 29 | 30 | 31 | typedef struct { 32 | ngx_str_t value; 33 | void *lengths; 34 | void *values; 35 | ngx_uint_t capture_variables; 36 | } ngx_http_replace_complex_value_t; 37 | 38 | 39 | typedef struct { 40 | ngx_conf_t *cf; 41 | ngx_str_t *value; 42 | 43 | ngx_http_replace_complex_value_t *complex_value; 44 | } ngx_http_replace_compile_complex_value_t; 45 | 46 | 47 | typedef struct { 48 | u_char *ip; 49 | u_char *pos; 50 | 51 | ngx_str_t buf; 52 | 53 | sre_int_t *captures; 54 | ngx_uint_t ncaptures; 55 | ngx_chain_t *captures_data; 56 | 57 | unsigned skip:1; 58 | 59 | ngx_http_request_t *request; 60 | } ngx_http_replace_script_engine_t; 61 | 62 | 63 | typedef size_t (*ngx_http_replace_script_code_pt) 64 | (ngx_http_replace_script_engine_t *e); 65 | 66 | typedef size_t (*ngx_http_replace_script_len_code_pt) 67 | (ngx_http_replace_script_engine_t *e); 68 | 69 | 70 | typedef struct { 71 | ngx_http_replace_script_code_pt code; 72 | uintptr_t len; 73 | } ngx_http_replace_script_copy_code_t; 74 | 75 | 76 | typedef struct { 77 | ngx_http_replace_script_code_pt code; 78 | uintptr_t n; 79 | } ngx_http_replace_script_capture_code_t; 80 | 81 | 82 | typedef struct { 83 | ngx_http_replace_script_code_pt code; 84 | uintptr_t index; 85 | } ngx_http_replace_script_var_code_t; 86 | 87 | 88 | ngx_int_t ngx_http_replace_compile_complex_value( 89 | ngx_http_replace_compile_complex_value_t *ccv); 90 | ngx_int_t ngx_http_replace_complex_value(ngx_http_request_t *r, 91 | ngx_chain_t *captured, sre_uint_t ncaps, sre_int_t *cap, 92 | ngx_http_replace_complex_value_t *val, ngx_str_t *value); 93 | 94 | 95 | #endif /* _NGX_HTTP_REPLACE_SCRIPT_H_INCLUDED_ */ 96 | 97 | /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ 98 | -------------------------------------------------------------------------------- /src/ngx_http_replace_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_replace_util.h" 14 | 15 | 16 | ngx_chain_t * 17 | ngx_http_replace_get_free_buf(ngx_pool_t *p, ngx_chain_t **free) 18 | { 19 | ngx_chain_t *cl; 20 | 21 | cl = ngx_chain_get_free_buf(p, free); 22 | if (cl == NULL) { 23 | return cl; 24 | } 25 | 26 | ngx_memzero(cl->buf, sizeof(ngx_buf_t)); 27 | 28 | cl->buf->tag = (ngx_buf_tag_t) &ngx_http_replace_filter_module; 29 | 30 | return cl; 31 | } 32 | 33 | 34 | ngx_int_t 35 | ngx_http_replace_split_chain(ngx_http_request_t *r, ngx_http_replace_ctx_t *ctx, 36 | ngx_chain_t **pa, ngx_chain_t ***plast_a, sre_int_t split, ngx_chain_t **pb, 37 | ngx_chain_t ***plast_b, unsigned b_sane) 38 | { 39 | sre_int_t file_last; 40 | ngx_chain_t *cl, *newcl, **ll; 41 | 42 | #if 0 43 | b_sane = 0; 44 | #endif 45 | 46 | ll = pa; 47 | for (cl = *pa; cl; ll = &cl->next, cl = cl->next) { 48 | if (cl->buf->file_last > split) { 49 | /* found an overlap */ 50 | 51 | if (cl->buf->file_pos < split) { 52 | 53 | dd("adjust cl buf (b_sane=%d): \"%.*s\"", b_sane, 54 | (int) ngx_buf_size(cl->buf), cl->buf->pos); 55 | 56 | file_last = cl->buf->file_last; 57 | cl->buf->last -= file_last - split; 58 | cl->buf->file_last = split; 59 | 60 | dd("adjusted cl buf (next=%p): %.*s", 61 | cl->next, 62 | (int) ngx_buf_size(cl->buf), cl->buf->pos); 63 | 64 | /* build the b chain */ 65 | if (b_sane) { 66 | newcl = ngx_http_replace_get_free_buf(r->pool, 67 | &ctx->free); 68 | if (newcl == NULL) { 69 | return NGX_ERROR; 70 | } 71 | 72 | newcl->buf->memory = 1; 73 | newcl->buf->pos = cl->buf->last; 74 | newcl->buf->last = cl->buf->last + file_last - split; 75 | newcl->buf->file_pos = split; 76 | newcl->buf->file_last = file_last; 77 | 78 | newcl->next = cl->next; 79 | 80 | *pb = newcl; 81 | if (plast_b) { 82 | if (cl->next) { 83 | *plast_b = *plast_a; 84 | 85 | } else { 86 | *plast_b = &newcl->next; 87 | } 88 | } 89 | 90 | } else { 91 | *pb = cl->next; 92 | if (plast_b) { 93 | *plast_b = *plast_a; 94 | } 95 | } 96 | 97 | /* truncate the a chain */ 98 | *plast_a = &cl->next; 99 | cl->next = NULL; 100 | 101 | return NGX_OK; 102 | } 103 | 104 | /* build the b chain */ 105 | *pb = cl; 106 | if (plast_b) { 107 | *plast_b = *plast_a; 108 | } 109 | 110 | /* truncate the a chain */ 111 | *plast_a = ll; 112 | *ll = NULL; 113 | 114 | return NGX_OK; 115 | } 116 | } 117 | 118 | /* missed */ 119 | 120 | *pb = NULL; 121 | if (plast_b) { 122 | *plast_b = pb; 123 | } 124 | 125 | return NGX_OK; 126 | } 127 | 128 | 129 | ngx_int_t 130 | ngx_http_replace_new_pending_buf(ngx_http_request_t *r, 131 | ngx_http_replace_ctx_t *ctx, sre_int_t from, sre_int_t to, 132 | ngx_chain_t **out) 133 | { 134 | size_t len; 135 | ngx_buf_t *b; 136 | ngx_chain_t *cl; 137 | 138 | ngx_http_replace_loc_conf_t *rlcf; 139 | 140 | if (from < ctx->stream_pos) { 141 | from = ctx->stream_pos; 142 | } 143 | 144 | len = (size_t) (to - from); 145 | if (len == 0) { 146 | return NGX_ERROR; 147 | } 148 | 149 | ctx->total_buffered += len; 150 | 151 | rlcf = ngx_http_get_module_loc_conf(r, ngx_http_replace_filter_module); 152 | 153 | if (ctx->total_buffered > rlcf->max_buffered_size) { 154 | #if 1 155 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 156 | "replace filter: exceeding " 157 | "replace_filter_max_buffered_size (%uz): %uz", 158 | rlcf->max_buffered_size, ctx->total_buffered); 159 | return NGX_BUSY; 160 | #endif 161 | } 162 | 163 | cl = ngx_http_replace_get_free_buf(r->pool, &ctx->free); 164 | if (cl == NULL) { 165 | return NGX_ERROR; 166 | } 167 | 168 | b = cl->buf; 169 | b->temporary = 1; 170 | 171 | /* abuse the file_pos and file_last fields here */ 172 | b->file_pos = from; 173 | b->file_last = to; 174 | 175 | b->start = ngx_palloc(r->pool, len); 176 | if (b->start == NULL) { 177 | return NGX_ERROR; 178 | } 179 | b->end = b->start + len; 180 | 181 | b->pos = b->start; 182 | b->last = ngx_copy(b->pos, ctx->buf->pos + from - ctx->stream_pos, len); 183 | 184 | dd("buffered pending data: stream_pos=%ld (%ld, %ld): %.*s", 185 | (long) ctx->stream_pos, (long) from, (long) to, 186 | (int) len, ctx->buf->pos + from - ctx->stream_pos); 187 | 188 | *out = cl; 189 | return NGX_OK; 190 | } 191 | 192 | 193 | #if (DDEBUG) 194 | void 195 | ngx_http_replace_dump_chain(const char *prefix, ngx_chain_t **pcl, 196 | ngx_chain_t **last) 197 | { 198 | ngx_chain_t *cl; 199 | 200 | if (*pcl == NULL) { 201 | dd("%s buf empty", prefix); 202 | if (last && last != pcl) { 203 | dd("BAD last %s", prefix); 204 | assert(0); 205 | } 206 | } 207 | 208 | for (cl = *pcl; cl; cl = cl->next) { 209 | dd("%s buf: \"%.*s\"", prefix, (int) ngx_buf_size(cl->buf), 210 | cl->buf->pos); 211 | 212 | if (cl->next == NULL) { 213 | if (last && last != &cl->next) { 214 | dd("BAD last %s", prefix); 215 | assert(0); 216 | } 217 | } 218 | } 219 | } 220 | #endif /* DDEBUG */ 221 | -------------------------------------------------------------------------------- /src/ngx_http_replace_util.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_REPLACE_UTIL_H_INCLUDED_ 2 | #define _NGX_HTTP_REPLACE_UTIL_H_INCLUDED_ 3 | 4 | 5 | #include "ngx_http_replace_filter_module.h" 6 | 7 | 8 | ngx_chain_t *ngx_http_replace_get_free_buf(ngx_pool_t *p, 9 | ngx_chain_t **free); 10 | ngx_int_t ngx_http_replace_split_chain(ngx_http_request_t *r, 11 | ngx_http_replace_ctx_t *ctx, ngx_chain_t **pa, ngx_chain_t ***plast_a, 12 | sre_int_t split, ngx_chain_t **pb, ngx_chain_t ***plast_b, unsigned b_sane); 13 | ngx_int_t ngx_http_replace_new_pending_buf(ngx_http_request_t *r, 14 | ngx_http_replace_ctx_t *ctx, sre_int_t from, sre_int_t to, 15 | ngx_chain_t **out); 16 | #if (DDEBUG) 17 | void ngx_http_replace_dump_chain(const char *prefix, ngx_chain_t **pcl, 18 | ngx_chain_t **last); 19 | #endif 20 | 21 | 22 | #endif /* _NGX_HTTP_REPLACE_UTIL_H_INCLUDED_ */ 23 | -------------------------------------------------------------------------------- /t/01-sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4 + 1); 16 | 17 | our $StapOutputChains = <<'_EOC_'; 18 | global active 19 | 20 | F(ngx_http_handler) { 21 | active = 1 22 | } 23 | 24 | /* 25 | F(ngx_http_write_filter) { 26 | if (active && pid() == target()) { 27 | printf("http writer filter: %s\n", ngx_chain_dump($in)) 28 | } 29 | } 30 | */ 31 | 32 | F(ngx_http_chunked_body_filter) { 33 | if (active && pid() == target()) { 34 | printf("http chunked filter: %s\n", ngx_chain_dump($in)) 35 | } 36 | } 37 | 38 | F(ngx_http_replace_output) { 39 | if (active && pid() == target()) { 40 | printf("http replace output: %s\n", ngx_chain_dump($ctx->out)) 41 | } 42 | } 43 | 44 | probe syscall.writev { 45 | if (active && pid() == target()) { 46 | printf("writev(%s)", ngx_iovec_dump($vec, $vlen)) 47 | /* 48 | for (i = 0; i < $vlen; i++) { 49 | printf(" %p [%s]", $vec[i]->iov_base, text_str(user_string_n($vec[i]->iov_base, $vec[i]->iov_len))) 50 | } 51 | */ 52 | } 53 | } 54 | 55 | probe syscall.writev.return { 56 | if (active && pid() == target()) { 57 | printf(" = %s\n", retstr) 58 | } 59 | } 60 | 61 | _EOC_ 62 | 63 | #no_diff(); 64 | #no_long_string(); 65 | run_tests(); 66 | 67 | __DATA__ 68 | 69 | === TEST 1: ambiguous pattern 70 | --- config 71 | default_type text/html; 72 | replace_filter_max_buffered_size 0; 73 | location /t { 74 | echo abcabcabde; 75 | replace_filter abcabd X; 76 | } 77 | --- request 78 | GET /t 79 | 80 | --- stap 81 | F(ngx_http_replace_non_capturing_parse) { 82 | println("non capturing parse") 83 | } 84 | 85 | F(ngx_http_replace_capturing_parse) { 86 | println("capturing parse") 87 | } 88 | 89 | F(ngx_http_replace_complex_value) { 90 | println("complex value") 91 | } 92 | 93 | --- stap_out_like chop 94 | ^(non capturing parse\n)+$ 95 | 96 | --- response_body 97 | abcXe 98 | --- no_error_log 99 | [alert] 100 | [error] 101 | 102 | 103 | 104 | === TEST 2: ambiguous pattern 105 | --- config 106 | default_type text/html; 107 | replace_filter_max_buffered_size 0; 108 | location /t { 109 | echo -n ababac; 110 | replace_filter abac X; 111 | } 112 | --- request 113 | GET /t 114 | --- response_body chop 115 | abX 116 | --- no_error_log 117 | [alert] 118 | [error] 119 | 120 | 121 | 122 | === TEST 3: alt 123 | --- config 124 | default_type text/html; 125 | replace_filter_max_buffered_size 0; 126 | location /t { 127 | echo abc; 128 | replace_filter 'ab|abc' X; 129 | } 130 | --- request 131 | GET /t 132 | --- response_body 133 | Xc 134 | --- no_error_log 135 | [alert] 136 | [error] 137 | 138 | 139 | 140 | === TEST 4: caseless 141 | --- config 142 | default_type text/html; 143 | replace_filter_max_buffered_size 0; 144 | location /t { 145 | echo abcabcaBde; 146 | replace_filter abCabd X i; 147 | } 148 | --- request 149 | GET /t 150 | --- response_body 151 | abcXe 152 | --- no_error_log 153 | [alert] 154 | [error] 155 | 156 | 157 | 158 | === TEST 5: case sensitive (no match) 159 | --- config 160 | default_type text/html; 161 | replace_filter_max_buffered_size 0; 162 | location /t { 163 | echo abcabcaBde; 164 | replace_filter abCabd X; 165 | } 166 | --- request 167 | GET /t 168 | --- response_body 169 | abcabcaBde 170 | --- no_error_log 171 | [alert] 172 | [error] 173 | 174 | 175 | 176 | === TEST 6: 1-byte chain bufs 177 | --- config 178 | default_type text/html; 179 | replace_filter_max_buffered_size 3; 180 | 181 | location = /t { 182 | echo -n a; 183 | echo -n b; 184 | echo -n a; 185 | echo -n b; 186 | echo -n a; 187 | echo -n c; 188 | echo d; 189 | replace_filter abac X; 190 | } 191 | --- request 192 | GET /t 193 | --- stap2 eval: $::StapOutputChains 194 | --- stap3 195 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1413") { 196 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 197 | print_ubacktrace() 198 | } 199 | 200 | --- response_body 201 | abXd 202 | --- no_error_log 203 | [alert] 204 | [error] 205 | 206 | 207 | 208 | === TEST 7: 2-byte chain bufs 209 | --- config 210 | default_type text/html; 211 | replace_filter_max_buffered_size 2; 212 | 213 | location = /t { 214 | echo -n ab; 215 | echo -n ab; 216 | echo -n ac; 217 | echo d; 218 | replace_filter abac X; 219 | } 220 | --- request 221 | GET /t 222 | --- stap2 eval: $::StapOutputChains 223 | --- response_body 224 | abXd 225 | --- no_error_log 226 | [alert] 227 | [error] 228 | 229 | 230 | 231 | === TEST 8: 3-byte chain bufs 232 | --- config 233 | default_type text/html; 234 | replace_filter_max_buffered_size 3; 235 | 236 | location = /t { 237 | echo -n aba; 238 | echo -n bac; 239 | echo d; 240 | replace_filter abac X; 241 | } 242 | --- request 243 | GET /t 244 | --- stap2 eval: $::StapOutputChains 245 | --- response_body 246 | abXd 247 | --- no_error_log 248 | [alert] 249 | [error] 250 | 251 | 252 | 253 | === TEST 9: 3-byte chain bufs (more) 254 | --- config 255 | default_type text/html; 256 | replace_filter_max_buffered_size 4; 257 | 258 | location = /t { 259 | echo -n aba; 260 | echo -n bac; 261 | echo d; 262 | replace_filter abacd X; 263 | } 264 | --- request 265 | GET /t 266 | --- stap2 eval: $::StapOutputChains 267 | --- response_body 268 | abX 269 | --- no_error_log 270 | [alert] 271 | [error] 272 | 273 | 274 | 275 | === TEST 10: once by default (1st char matched) 276 | --- config 277 | replace_filter_max_buffered_size 0; 278 | default_type text/html; 279 | location /t { 280 | echo abcabcabde; 281 | replace_filter a X; 282 | } 283 | --- request 284 | GET /t 285 | --- response_body 286 | Xbcabcabde 287 | --- no_error_log 288 | [alert] 289 | [error] 290 | 291 | 292 | 293 | === TEST 11: once by default (2nd char matched) 294 | --- config 295 | default_type text/html; 296 | replace_filter_max_buffered_size 0; 297 | location /t { 298 | echo abcabcabde; 299 | replace_filter b X; 300 | } 301 | --- request 302 | GET /t 303 | --- response_body 304 | aXcabcabde 305 | --- no_error_log 306 | [alert] 307 | [error] 308 | 309 | 310 | 311 | === TEST 12: global substitution 312 | --- config 313 | default_type text/html; 314 | replace_filter_max_buffered_size 0; 315 | location /t { 316 | echo bbc; 317 | replace_filter b X g; 318 | } 319 | --- request 320 | GET /t 321 | --- response_body 322 | XXc 323 | --- no_error_log 324 | [alert] 325 | [error] 326 | 327 | 328 | 329 | === TEST 13: global substitution 330 | --- config 331 | default_type text/html; 332 | replace_filter_max_buffered_size 0; 333 | location /t { 334 | echo abcabcabde; 335 | replace_filter b X g; 336 | } 337 | --- request 338 | GET /t 339 | --- response_body 340 | aXcaXcaXde 341 | --- no_error_log 342 | [alert] 343 | [error] 344 | 345 | 346 | 347 | === TEST 14: global substitution (empty captures) 348 | --- config 349 | default_type text/html; 350 | replace_filter_max_buffered_size 0; 351 | location /t { 352 | echo -n abcabcabde; 353 | replace_filter [0-9]* X g; 354 | } 355 | --- request 356 | GET /t 357 | --- response_body chop 358 | XaXbXcXaXbXcXaXbXdXeX 359 | --- no_error_log 360 | [alert] 361 | [error] 362 | 363 | 364 | 365 | === TEST 15: global substitution (empty captures, splitted) 366 | --- config 367 | default_type text/html; 368 | replace_filter_max_buffered_size 0; 369 | location /t { 370 | echo -n ab; 371 | echo -n cab; 372 | echo -n c; 373 | echo -n abde; 374 | replace_filter [0-9]* X g; 375 | } 376 | --- request 377 | GET /t 378 | --- response_body chop 379 | XaXbXcXaXbXcXaXbXdXeX 380 | --- no_error_log 381 | [alert] 382 | [error] 383 | 384 | 385 | 386 | === TEST 16: global substitution (\d+) 387 | --- config 388 | default_type text/html; 389 | replace_filter_max_buffered_size 0; 390 | location /t { 391 | echo "hello1234, 56 world"; 392 | replace_filter \d+ X g; 393 | } 394 | --- request 395 | GET /t 396 | --- response_body 397 | helloX, X world 398 | --- no_error_log 399 | [alert] 400 | [error] 401 | 402 | 403 | 404 | === TEST 17: replace_filter_types default to text/html 405 | --- config 406 | default_type text/plain; 407 | location /t { 408 | echo abc; 409 | replace_filter b X; 410 | } 411 | --- request 412 | GET /t 413 | --- response_body 414 | abc 415 | --- no_error_log 416 | [alert] 417 | [error] 418 | 419 | 420 | 421 | === TEST 18: custom replace_filter_types 422 | --- config 423 | default_type text/plain; 424 | location /t { 425 | echo abc; 426 | replace_filter b X; 427 | replace_filter_types text/plain; 428 | } 429 | --- request 430 | GET /t 431 | --- response_body 432 | aXc 433 | --- no_error_log 434 | [alert] 435 | [error] 436 | 437 | 438 | 439 | === TEST 19: multiple replace_filter_types settings 440 | --- config 441 | default_type text/plain; 442 | location /t { 443 | echo abc; 444 | replace_filter b X; 445 | replace_filter_types text/css text/plain; 446 | } 447 | --- request 448 | GET /t 449 | --- response_body 450 | aXc 451 | --- no_error_log 452 | [alert] 453 | [error] 454 | 455 | 456 | 457 | === TEST 20: trim leading spaces 458 | --- config 459 | replace_filter_max_buffered_size 0; 460 | default_type text/html; 461 | location /a.html { 462 | replace_filter '^\s+' '' g; 463 | } 464 | --- user_files 465 | >>> a.html 466 | hello, world 467 | blah yeah 468 | hello 469 | baby! 470 | 471 | abc 472 | --- request 473 | GET /a.html 474 | --- response_body 475 | hello, world 476 | blah yeah 477 | hello 478 | baby! 479 | abc 480 | --- no_error_log 481 | [alert] 482 | [error] 483 | 484 | 485 | 486 | === TEST 21: trim trailing spaces 487 | --- config 488 | default_type text/html; 489 | replace_filter_max_buffered_size 0; 490 | location /a.html { 491 | replace_filter '\s+$' '' g; 492 | } 493 | --- user_files 494 | >>> a.html 495 | hello, world 496 | blah yeah 497 | hello 498 | baby! 499 | 500 | abc 501 | --- request 502 | GET /a.html 503 | --- response_body chop 504 | hello, world 505 | blah yeah 506 | hello 507 | baby! 508 | abc 509 | --- no_error_log 510 | [alert] 511 | [error] 512 | 513 | 514 | 515 | === TEST 22: trim both leading and trailing spaces 516 | --- config 517 | replace_filter_max_buffered_size 0; 518 | default_type text/html; 519 | location /a.html { 520 | replace_filter '^\s+|\s+$' '' g; 521 | } 522 | --- user_files 523 | >>> a.html 524 | hello, world 525 | blah yeah 526 | hello 527 | baby! 528 | 529 | abc 530 | --- request 531 | GET /a.html 532 | --- response_body chop 533 | hello, world 534 | blah yeah 535 | hello 536 | baby! 537 | abc 538 | --- no_error_log 539 | [alert] 540 | [error] 541 | 542 | 543 | 544 | === TEST 23: pure flush buf in the stream (no data) 545 | --- config 546 | replace_filter_max_buffered_size 0; 547 | default_type text/html; 548 | location = /t { 549 | echo_flush; 550 | replace_filter 'a' 'X' g; 551 | } 552 | --- request 553 | GET /t 554 | --- response_body chop 555 | --- no_error_log 556 | [alert] 557 | [error] 558 | 559 | 560 | 561 | === TEST 24: pure flush buf in the stream (with data) 562 | --- config 563 | replace_filter_max_buffered_size 0; 564 | default_type text/html; 565 | location = /t { 566 | echo a; 567 | echo_flush; 568 | replace_filter 'a' 'X' g; 569 | } 570 | --- request 571 | GET /t 572 | --- stap3 eval: $::StapOutputChains 573 | --- stap2 574 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:539") { 575 | printf("chain: %s", ngx_chain_dump($ctx->busy)) 576 | //print_ubacktrace() 577 | } 578 | --- response_body 579 | X 580 | --- no_error_log 581 | [alert] 582 | [error] 583 | 584 | 585 | 586 | === TEST 25: trim both leading and trailing spaces (1 byte at a time) 587 | --- config 588 | default_type text/html; 589 | replace_filter_max_buffered_size 1; 590 | location = /t { 591 | echo -n 'a'; 592 | echo ' '; 593 | echo "b"; 594 | replace_filter '^\s+|\s+$' '' g; 595 | } 596 | 597 | --- stap2 598 | F(ngx_palloc) { 599 | if ($size < 0) { 600 | print_ubacktrace() 601 | exit() 602 | } 603 | } 604 | --- stap3 eval: $::StapOutputChains 605 | --- request 606 | GET /t 607 | --- response_body chop 608 | a 609 | b 610 | 611 | --- no_error_log 612 | [alert] 613 | [error] 614 | 615 | 616 | 617 | === TEST 26: trim both leading and trailing spaces (1 byte at a time), no \s for $ 618 | --- config 619 | replace_filter_max_buffered_size 1; 620 | default_type text/html; 621 | location = /t { 622 | echo -n 'a'; 623 | echo ' '; 624 | echo "b"; 625 | replace_filter '^\s+| +$' '' g; 626 | } 627 | 628 | --- stap2 629 | F(ngx_palloc) { 630 | if ($size < 0) { 631 | print_ubacktrace() 632 | exit() 633 | } 634 | } 635 | --- stap3 eval: $::StapOutputChains 636 | --- request 637 | GET /t 638 | --- response_body 639 | a 640 | b 641 | 642 | --- no_error_log 643 | [alert] 644 | [error] 645 | 646 | 647 | 648 | === TEST 27: trim both leading and trailing spaces (1 byte at a time) 649 | --- config 650 | replace_filter_max_buffered_size 4; 651 | default_type text/html; 652 | location /a.html { 653 | internal; 654 | } 655 | 656 | location = /t { 657 | content_by_lua ' 658 | local res = ngx.location.capture("/a.html") 659 | local txt = res.body 660 | for i = 1, string.len(txt) do 661 | ngx.print(string.sub(txt, i, i)) 662 | ngx.flush(true) 663 | end 664 | '; 665 | replace_filter '^\s+|\s+$' '' g; 666 | } 667 | --- user_files 668 | >>> a.html 669 | hello, world 670 | blah yeah 671 | hello 672 | baby! 673 | 674 | abc 675 | 676 | --- stap2 677 | F(ngx_palloc) { 678 | if ($size < 0) { 679 | print_ubacktrace() 680 | exit() 681 | } 682 | } 683 | --- stap3 684 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1438") { 685 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 686 | print_ubacktrace() 687 | exit() 688 | } 689 | 690 | --- request 691 | GET /t 692 | --- response_body chop 693 | hello, world 694 | blah yeah 695 | hello 696 | baby! 697 | abc 698 | --- no_error_log 699 | [alert] 700 | [error] 701 | 702 | 703 | 704 | === TEST 28: \b at the border 705 | --- config 706 | default_type text/html; 707 | replace_filter_max_buffered_size 0; 708 | location /t { 709 | echo -n a; 710 | echo b; 711 | replace_filter '\bb|a' X g; 712 | } 713 | --- request 714 | GET /t 715 | --- response_body 716 | Xb 717 | --- no_error_log 718 | [alert] 719 | [error] 720 | 721 | 722 | 723 | === TEST 29: \B at the border 724 | --- config 725 | replace_filter_max_buffered_size 0; 726 | default_type text/html; 727 | location /t { 728 | echo -n a; 729 | echo ','; 730 | replace_filter '\B,|a' X g; 731 | } 732 | --- request 733 | GET /t 734 | --- response_body 735 | X, 736 | --- no_error_log 737 | [alert] 738 | [error] 739 | 740 | 741 | 742 | === TEST 30: \A at the border 743 | --- config 744 | default_type text/html; 745 | replace_filter_max_buffered_size 0; 746 | location /t { 747 | echo -n a; 748 | echo 'b'; 749 | replace_filter '\Ab|a' X g; 750 | } 751 | --- request 752 | GET /t 753 | --- response_body 754 | Xb 755 | --- no_error_log 756 | [alert] 757 | [error] 758 | 759 | 760 | 761 | === TEST 31: memory bufs with last_buf=1 762 | --- config 763 | default_type text/html; 764 | replace_filter_max_buffered_size 0; 765 | location /t { 766 | return 200 "abc"; 767 | replace_filter \w+ X; 768 | } 769 | --- request 770 | GET /t 771 | --- stap2 eval: $::StapOutputChains 772 | --- response_body chop 773 | X 774 | --- no_error_log 775 | [alert] 776 | [error] 777 | 778 | 779 | 780 | === TEST 32: trim both leading and trailing spaces (2 bytes at a time) 781 | --- config 782 | default_type text/html; 783 | replace_filter_max_buffered_size 4; 784 | location /a.html { 785 | internal; 786 | } 787 | 788 | location = /t { 789 | content_by_lua ' 790 | local res = ngx.location.capture("/a.html") 791 | local txt = res.body 792 | local len = string.len(txt) 793 | i = 1 794 | while i <= len do 795 | if i == len then 796 | ngx.print(string.sub(txt, i, i)) 797 | i = i + 1 798 | else 799 | ngx.print(string.sub(txt, i, i + 1)) 800 | i = i + 2 801 | end 802 | ngx.flush(true) 803 | end 804 | '; 805 | replace_filter '^\s+|\s+$' '' g; 806 | } 807 | --- user_files 808 | >>> a.html 809 | hello, world 810 | blah yeah 811 | hello 812 | baby! 813 | 814 | abc 815 | 816 | --- stap2 eval: $::StapOutputChains 817 | --- request 818 | GET /t 819 | --- response_body chop 820 | hello, world 821 | blah yeah 822 | hello 823 | baby! 824 | abc 825 | --- no_error_log 826 | [alert] 827 | [error] 828 | 829 | 830 | 831 | === TEST 33: trim both leading and trailing spaces (3 bytes at a time) 832 | --- config 833 | replace_filter_max_buffered_size 2; 834 | default_type text/html; 835 | location /a.html { 836 | internal; 837 | } 838 | 839 | location = /t { 840 | content_by_lua ' 841 | local res = ngx.location.capture("/a.html") 842 | local txt = res.body 843 | local len = string.len(txt) 844 | i = 1 845 | while i <= len do 846 | if i == len then 847 | ngx.print(string.sub(txt, i, i)) 848 | i = i + 1 849 | elseif i == len - 1 then 850 | ngx.print(string.sub(txt, i, i + 1)) 851 | i = i + 2 852 | else 853 | ngx.print(string.sub(txt, i, i + 2)) 854 | i = i + 3 855 | end 856 | ngx.flush(true) 857 | end 858 | '; 859 | replace_filter '^\s+|\s+$' '' g; 860 | } 861 | --- user_files 862 | >>> a.html 863 | hello, world 864 | blah yeah 865 | hello 866 | baby! 867 | 868 | abc 869 | 870 | --- stap2 eval: $::StapOutputChains 871 | --- request 872 | GET /t 873 | --- response_body chop 874 | hello, world 875 | blah yeah 876 | hello 877 | baby! 878 | abc 879 | --- no_error_log 880 | [alert] 881 | [error] 882 | 883 | 884 | 885 | === TEST 34: github issue #2: error "general look-ahead not supported" 886 | --- config 887 | replace_filter_max_buffered_size 3; 888 | location /t { 889 | charset utf-8; 890 | default_type text/html; 891 | echo "ABCabcABCabc"; 892 | #replace_filter_types text/plain; 893 | replace_filter "a.+a" "X" "ig"; 894 | } 895 | --- request 896 | GET /t 897 | 898 | --- stap2 899 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1492") { 900 | print_ubacktrace() 901 | } 902 | 903 | --- response_body 904 | Xbc 905 | --- no_error_log 906 | [alert] 907 | [error] 908 | 909 | 910 | 911 | === TEST 35: backtrack to the middle of a pending capture (pending: output|capture + rematch) 912 | --- config 913 | replace_filter_max_buffered_size 2; 914 | default_type text/html; 915 | location = /t { 916 | echo -n ab; 917 | echo -n c; 918 | echo d; 919 | replace_filter 'abce|b' 'X' g; 920 | } 921 | 922 | --- stap2 923 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1501") { 924 | print_ubacktrace() 925 | } 926 | 927 | --- stap3 eval: $::StapOutputChains 928 | --- request 929 | GET /t 930 | --- response_body 931 | aXcd 932 | 933 | --- no_error_log 934 | [alert] 935 | [error] 936 | 937 | 938 | 939 | === TEST 36: backtrack to the middle of a pending capture (pending: output + capture|rematch 940 | --- config 941 | replace_filter_max_buffered_size 2; 942 | default_type text/html; 943 | location = /t { 944 | echo -n a; 945 | echo -n bc; 946 | echo d; 947 | replace_filter 'abce|b' 'X' g; 948 | } 949 | 950 | --- stap2 951 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1501") { 952 | print_ubacktrace() 953 | } 954 | 955 | --- stap3 eval: $::StapOutputChains 956 | --- request 957 | GET /t 958 | --- response_body 959 | aXcd 960 | 961 | --- no_error_log 962 | [alert] 963 | [error] 964 | 965 | 966 | 967 | === TEST 37: backtrack to the middle of a pending capture (pending: output + capture + rematch 968 | --- config 969 | replace_filter_max_buffered_size 2; 970 | default_type text/html; 971 | location = /t { 972 | echo -n a; 973 | echo -n b; 974 | echo -n c; 975 | echo d; 976 | replace_filter 'abce|b' 'X' g; 977 | } 978 | 979 | --- stap2 980 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1522") { 981 | print_ubacktrace() 982 | } 983 | 984 | --- stap3 eval: $::StapOutputChains 985 | --- request 986 | GET /t 987 | --- response_body 988 | aXcd 989 | 990 | --- no_error_log 991 | [alert] 992 | [error] 993 | 994 | 995 | 996 | === TEST 38: backtrack to the middle of a pending capture (pending: output|capture|rematch 997 | --- config 998 | replace_filter_max_buffered_size 2; 999 | default_type text/html; 1000 | location = /t { 1001 | echo -n abc; 1002 | echo d; 1003 | replace_filter 'abce|b' 'X' g; 1004 | } 1005 | 1006 | --- stap2 1007 | F(ngx_palloc) { 1008 | if ($size < 0) { 1009 | print_ubacktrace() 1010 | exit() 1011 | } 1012 | } 1013 | --- stap3 eval: $::StapOutputChains 1014 | --- request 1015 | GET /t 1016 | --- response_body 1017 | aXcd 1018 | 1019 | --- no_error_log 1020 | [alert] 1021 | [error] 1022 | 1023 | 1024 | 1025 | === TEST 39: backtrack to the middle of a pending capture (pending: output|capture|rematch(2) 1026 | --- config 1027 | replace_filter_max_buffered_size 3; 1028 | default_type text/html; 1029 | location = /t { 1030 | echo -n abcc; 1031 | echo d; 1032 | replace_filter 'abcce|b' 'X' g; 1033 | } 1034 | 1035 | --- stap2 1036 | F(ngx_palloc) { 1037 | if ($size < 0) { 1038 | print_ubacktrace() 1039 | exit() 1040 | } 1041 | } 1042 | --- stap3 eval: $::StapOutputChains 1043 | --- request 1044 | GET /t 1045 | --- response_body 1046 | aXccd 1047 | 1048 | --- no_error_log 1049 | [alert] 1050 | [error] 1051 | 1052 | 1053 | 1054 | === TEST 40: backtrack to the middle of a pending capture (pending: output|capture(2)|rematch 1055 | --- config 1056 | replace_filter_max_buffered_size 2; 1057 | default_type text/html; 1058 | location = /t { 1059 | echo -n abbc; 1060 | echo d; 1061 | replace_filter 'abbce|bb' 'X' g; 1062 | } 1063 | 1064 | --- stap2 1065 | F(ngx_palloc) { 1066 | if ($size < 0) { 1067 | print_ubacktrace() 1068 | exit() 1069 | } 1070 | } 1071 | --- stap3 eval: $::StapOutputChains 1072 | --- request 1073 | GET /t 1074 | --- response_body 1075 | aXcd 1076 | 1077 | --- no_error_log 1078 | [alert] 1079 | [error] 1080 | 1081 | 1082 | 1083 | === TEST 41: backtrack to the middle of a pending capture (pending: output(2)|capture|rematch 1084 | --- config 1085 | replace_filter_max_buffered_size 3; 1086 | default_type text/html; 1087 | location = /t { 1088 | echo -n aabc; 1089 | echo d; 1090 | replace_filter 'aabce|b' 'X' g; 1091 | } 1092 | 1093 | --- stap2 1094 | F(ngx_palloc) { 1095 | if ($size < 0) { 1096 | print_ubacktrace() 1097 | exit() 1098 | } 1099 | } 1100 | --- stap3 eval: $::StapOutputChains 1101 | --- request 1102 | GET /t 1103 | --- response_body 1104 | aaXcd 1105 | 1106 | --- no_error_log 1107 | [alert] 1108 | [error] 1109 | 1110 | 1111 | 1112 | === TEST 42: backtrack to the beginning of a pending capture (pending: output + capture|rematch(2) 1113 | --- config 1114 | replace_filter_max_buffered_size 3; 1115 | default_type text/html; 1116 | location = /t { 1117 | echo -n a; 1118 | echo -n bcc; 1119 | echo d; 1120 | replace_filter 'abcce|b' 'X' g; 1121 | } 1122 | 1123 | --- stap2 1124 | F(ngx_palloc) { 1125 | if ($size < 0) { 1126 | print_ubacktrace() 1127 | exit() 1128 | } 1129 | } 1130 | --- stap3 eval: $::StapOutputChains 1131 | --- request 1132 | GET /t 1133 | --- response_body 1134 | aXccd 1135 | 1136 | --- no_error_log 1137 | [alert] 1138 | [error] 1139 | 1140 | 1141 | 1142 | === TEST 43: backtrack to the beginning of a pending capture (pending: output + capture(2)|rematch 1143 | --- config 1144 | replace_filter_max_buffered_size 2; 1145 | default_type text/html; 1146 | location = /t { 1147 | echo -n a; 1148 | echo -n bbc; 1149 | echo d; 1150 | replace_filter 'abbce|bb' 'X' g; 1151 | } 1152 | 1153 | --- stap2 1154 | F(ngx_palloc) { 1155 | if ($size < 0) { 1156 | print_ubacktrace() 1157 | exit() 1158 | } 1159 | } 1160 | --- stap3 eval: $::StapOutputChains 1161 | --- request 1162 | GET /t 1163 | --- response_body 1164 | aXcd 1165 | 1166 | --- no_error_log 1167 | [alert] 1168 | [error] 1169 | 1170 | 1171 | 1172 | === TEST 44: backtrack to the middle of a pending capture (pending: output(2) + capture|rematch 1173 | --- config 1174 | replace_filter_max_buffered_size 3; 1175 | default_type text/html; 1176 | location = /t { 1177 | echo -n aa; 1178 | echo -n bc; 1179 | echo d; 1180 | replace_filter 'aabce|b' 'X' g; 1181 | } 1182 | 1183 | --- stap2 1184 | F(ngx_palloc) { 1185 | if ($size < 0) { 1186 | print_ubacktrace() 1187 | exit() 1188 | } 1189 | } 1190 | --- stap3 eval: $::StapOutputChains 1191 | --- request 1192 | GET /t 1193 | --- response_body 1194 | aaXcd 1195 | 1196 | --- no_error_log 1197 | [alert] 1198 | [error] 1199 | 1200 | 1201 | 1202 | === TEST 45: assertions across AGAIN 1203 | --- config 1204 | replace_filter_max_buffered_size 2; 1205 | default_type text/html; 1206 | location = /t { 1207 | echo -n a; 1208 | echo -n "\n"; 1209 | echo b; 1210 | replace_filter 'a\n^b' 'X' g; 1211 | } 1212 | 1213 | --- stap2 1214 | F(ngx_palloc) { 1215 | if ($size < 0) { 1216 | print_ubacktrace() 1217 | exit() 1218 | } 1219 | } 1220 | --- stap3 eval: $::StapOutputChains 1221 | --- request 1222 | GET /t 1223 | --- response_body 1224 | X 1225 | 1226 | --- no_error_log 1227 | [alert] 1228 | [error] 1229 | 1230 | 1231 | 1232 | === TEST 46: assertions when capture backtracking happens 1233 | --- config 1234 | replace_filter_max_buffered_size 3; 1235 | default_type text/html; 1236 | location = /t { 1237 | echo -n a; 1238 | echo -n b; 1239 | echo -n c; 1240 | echo -n d; 1241 | echo f; 1242 | #echo abcdf; 1243 | replace_filter 'abcde|b|\bc' 'X' g; 1244 | } 1245 | 1246 | --- stap2 1247 | F(ngx_palloc) { 1248 | if ($size < 0) { 1249 | print_ubacktrace() 1250 | exit() 1251 | } 1252 | } 1253 | --- stap3 eval: $::StapOutputChains 1254 | --- request 1255 | GET /t 1256 | --- response_body 1257 | aXcdf 1258 | 1259 | --- no_error_log 1260 | [alert] 1261 | [error] 1262 | 1263 | 1264 | 1265 | === TEST 47: assertions when capture backtracking happens (2 pending matches) 1266 | --- config 1267 | replace_filter_max_buffered_size 3; 1268 | default_type text/html; 1269 | location = /t { 1270 | echo -n a; 1271 | echo -n b; 1272 | echo -n ' '; 1273 | echo -n d; 1274 | echo f; 1275 | #echo ab df; 1276 | replace_filter 'ab de|b|b |\b ' 'X' g; 1277 | } 1278 | 1279 | --- stap2 1280 | F(ngx_palloc) { 1281 | if ($size < 0) { 1282 | print_ubacktrace() 1283 | exit() 1284 | } 1285 | } 1286 | --- stap3 eval: $::StapOutputChains 1287 | --- request 1288 | GET /t 1289 | --- response_body 1290 | aXXdf 1291 | 1292 | --- no_error_log 1293 | [alert] 1294 | [error] 1295 | 1296 | 1297 | 1298 | === TEST 48: github issue #2: error "general look-ahead not supported", no "g" 1299 | --- config 1300 | replace_filter_max_buffered_size 3; 1301 | location /t { 1302 | charset utf-8; 1303 | default_type text/html; 1304 | echo "ABCabcABCabc"; 1305 | #replace_filter_types text/plain; 1306 | replace_filter "a.+a" "X" "i"; 1307 | } 1308 | --- request 1309 | GET /t 1310 | --- stap2 eval: $::StapOutputChains 1311 | --- response_body 1312 | Xbc 1313 | --- no_error_log 1314 | [alert] 1315 | [error] 1316 | 1317 | 1318 | 1319 | === TEST 49: nested rematch bufs 1320 | --- config 1321 | replace_filter_max_buffered_size 4; 1322 | location /t { 1323 | default_type text/html; 1324 | echo -n a; 1325 | echo -n b; 1326 | echo -n c; 1327 | echo -n d; 1328 | echo -n e; 1329 | echo g; 1330 | #echo abcdeg; 1331 | replace_filter 'abcdef|b|cdf|c' X g; 1332 | } 1333 | --- request 1334 | GET /t 1335 | --- stap2 eval: $::StapOutputChains 1336 | --- response_body 1337 | aXXdeg 1338 | --- no_error_log 1339 | [alert] 1340 | [error] 1341 | 1342 | 1343 | 1344 | === TEST 50: nested rematch bufs (splitting pending buf) 1345 | --- config 1346 | replace_filter_max_buffered_size 6; 1347 | location /t { 1348 | default_type text/html; 1349 | echo -n a; 1350 | echo -n b; 1351 | echo -n cd; 1352 | echo -n e; 1353 | echo -n f; 1354 | echo -n g; 1355 | echo i; 1356 | #echo abcdefh; 1357 | replace_filter 'abcdefgh|b|cdeg|d' X g; 1358 | } 1359 | --- request 1360 | GET /t 1361 | --- stap2 eval: $::StapOutputChains 1362 | --- response_body 1363 | aXcXefgi 1364 | --- no_error_log 1365 | [alert] 1366 | [error] 1367 | 1368 | 1369 | 1370 | === TEST 51: remove C/C++ comments (1 byte at a time) 1371 | --- config 1372 | replace_filter_max_buffered_size 42; 1373 | default_type text/html; 1374 | location /a.html { 1375 | internal; 1376 | } 1377 | 1378 | location = /t { 1379 | content_by_lua ' 1380 | local res = ngx.location.capture("/a.html") 1381 | local txt = res.body 1382 | for i = 1, string.len(txt) do 1383 | ngx.print(string.sub(txt, i, i)) 1384 | ngx.flush(true) 1385 | end 1386 | '; 1387 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 1388 | } 1389 | --- user_files 1390 | >>> a.html 1391 | i don't know // hello // world /* */ 1392 | hello world /** abc * b/c /* 1393 | hello ** // world 1394 | * 1395 | */ 1396 | blah /* hi */ */ b 1397 | // 1398 | ///hi 1399 | --- stap2 1400 | F(ngx_palloc) { 1401 | if ($size < 0) { 1402 | print_ubacktrace() 1403 | exit() 1404 | } 1405 | } 1406 | --- request 1407 | GET /t 1408 | --- response_body eval 1409 | " i don't know 1410 | hello world 1411 | blah */ b 1412 | 1413 | 1414 | " 1415 | --- no_error_log 1416 | [alert] 1417 | [error] 1418 | 1419 | 1420 | 1421 | === TEST 52: remove C/C++ comments (all at a time) 1422 | --- config 1423 | default_type text/html; 1424 | replace_filter_max_buffered_size 0; 1425 | 1426 | location /a.html { 1427 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 1428 | } 1429 | 1430 | --- user_files 1431 | >>> a.html 1432 | i don't know // hello // world /* */ 1433 | hello world /** abc * b/c /* 1434 | hello ** // world 1435 | * 1436 | */ 1437 | blah /* hi */ */ b 1438 | // 1439 | ///hi 1440 | --- stap2 1441 | F(ngx_palloc) { 1442 | if ($size < 0) { 1443 | print_ubacktrace() 1444 | exit() 1445 | } 1446 | } 1447 | --- request 1448 | GET /a.html 1449 | --- response_body eval 1450 | " i don't know 1451 | hello world 1452 | blah */ b 1453 | 1454 | 1455 | " 1456 | --- no_error_log 1457 | [alert] 1458 | [error] 1459 | 1460 | 1461 | 1462 | === TEST 53: remove C/C++ comments (all at a time) - server-level config 1463 | --- config 1464 | replace_filter_max_buffered_size 0; 1465 | default_type text/html; 1466 | 1467 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 1468 | 1469 | --- user_files 1470 | >>> a.html 1471 | i don't know // hello // world /* */ 1472 | hello world /** abc * b/c /* 1473 | hello ** // world 1474 | * 1475 | */ 1476 | blah /* hi */ */ b 1477 | // 1478 | ///hi 1479 | --- stap2 1480 | F(ngx_palloc) { 1481 | if ($size < 0) { 1482 | print_ubacktrace() 1483 | exit() 1484 | } 1485 | } 1486 | --- request 1487 | GET /a.html 1488 | --- response_body eval 1489 | " i don't know 1490 | hello world 1491 | blah */ b 1492 | 1493 | 1494 | " 1495 | --- no_error_log 1496 | [alert] 1497 | [error] 1498 | 1499 | 1500 | 1501 | === TEST 54: multiple replace_filter_types settings (server level) 1502 | --- config 1503 | replace_filter_max_buffered_size 0; 1504 | default_type text/plain; 1505 | replace_filter_types text/css text/plain; 1506 | location /t { 1507 | echo abc; 1508 | replace_filter b X; 1509 | } 1510 | --- request 1511 | GET /t 1512 | --- response_body 1513 | aXc 1514 | --- no_error_log 1515 | [alert] 1516 | [error] 1517 | 1518 | 1519 | 1520 | === TEST 55: multiple replace_filter_types settings (server level, but overridding in location) 1521 | --- config 1522 | replace_filter_max_buffered_size 0; 1523 | default_type text/plain; 1524 | replace_filter_types text/css text/plain; 1525 | location /t { 1526 | echo abc; 1527 | replace_filter_types text/javascript; 1528 | replace_filter b X; 1529 | } 1530 | --- request 1531 | GET /t 1532 | --- response_body 1533 | abc 1534 | --- no_error_log 1535 | [alert] 1536 | [error] 1537 | 1538 | 1539 | 1540 | === TEST 56: do not use replace_filter at all 1541 | --- config 1542 | replace_filter_max_buffered_size 0; 1543 | default_type text/plain; 1544 | replace_filter_types text/css text/plain; 1545 | location /t { 1546 | echo abc; 1547 | replace_filter_types text/css; 1548 | } 1549 | --- request 1550 | GET /t 1551 | --- response_body 1552 | abc 1553 | --- no_error_log 1554 | [alert] 1555 | [error] 1556 | 1557 | 1558 | 1559 | === TEST 57: bad regex 1560 | --- config 1561 | default_type text/html; 1562 | location /t { 1563 | echo abc; 1564 | replace_filter '(a+b' ''; 1565 | } 1566 | --- request 1567 | GET /t 1568 | --- response_body 1569 | abc 1570 | --- no_error_log 1571 | [alert] 1572 | [error] 1573 | --- SKIP 1574 | 1575 | 1576 | 1577 | === TEST 58: github issue #3: data lost in particular situation 1578 | --- config 1579 | replace_filter_max_buffered_size 4; 1580 | default_type text/html; 1581 | location /t { 1582 | default_type text/html; 1583 | echo "ABCabcABC"; 1584 | echo "ABCabcABC"; 1585 | #echo "ABCabcABC\nABCabcABC"; 1586 | replace_filter "(a.+?c){2}" "X" "ig"; 1587 | } 1588 | --- request 1589 | GET /t 1590 | --- response_body 1591 | XXABC 1592 | --- no_error_log 1593 | [alert] 1594 | [error] 1595 | 1596 | 1597 | 1598 | === TEST 59: variation 1599 | --- config 1600 | replace_filter_max_buffered_size 5; 1601 | default_type text/html; 1602 | location /t { 1603 | default_type text/html; 1604 | #echo "ABCabcABC"; 1605 | #echo "ABCabcABC"; 1606 | echo "ACacAC ACacAC"; 1607 | replace_filter "(a.+?c){2}" "X" "ig"; 1608 | } 1609 | --- request 1610 | GET /t 1611 | --- response_body 1612 | XacAC 1613 | --- no_error_log 1614 | [alert] 1615 | [error] 1616 | 1617 | 1618 | 1619 | === TEST 60: nested pending matched 1620 | --- config 1621 | replace_filter_max_buffered_size 4; 1622 | default_type text/html; 1623 | location /t { 1624 | default_type text/html; 1625 | echo -n a; 1626 | echo -n b; 1627 | echo -n c; 1628 | echo -n def; 1629 | echo -n gh; 1630 | echo -n i; 1631 | echo k; 1632 | #echo abcdefig; 1633 | replace_filter "abcdefghij|bcdefg|cd" "X" "ig"; 1634 | } 1635 | --- request 1636 | GET /t 1637 | --- response_body 1638 | aXhik 1639 | --- no_error_log 1640 | [alert] 1641 | [error] 1642 | 1643 | 1644 | 1645 | === TEST 61: test split chain with b_sane=1, next=NULL 1646 | --- config 1647 | replace_filter_max_buffered_size 4; 1648 | default_type text/html; 1649 | 1650 | location = /t { 1651 | echo -n aba; 1652 | echo -n ba; 1653 | echo -n bac; 1654 | echo d; 1655 | #echo abababacd; 1656 | replace_filter abacd X; 1657 | } 1658 | --- request 1659 | GET /t 1660 | --- stap2 eval: $::StapOutputChains 1661 | --- stap3 1662 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1217") { 1663 | print_ubacktrace() 1664 | } 1665 | --- response_body 1666 | ababX 1667 | --- no_error_log 1668 | [alert] 1669 | [error] 1670 | 1671 | 1672 | 1673 | === TEST 62: test split chain with b_sane=1, next not NULL 1674 | --- config 1675 | replace_filter_max_buffered_size 6; 1676 | default_type text/html; 1677 | 1678 | location = /t { 1679 | echo -n aba; 1680 | echo -n ba; 1681 | echo -n ba; 1682 | echo -n bac; 1683 | echo d; 1684 | #echo abababacd; 1685 | replace_filter ababacd X; 1686 | } 1687 | --- request 1688 | GET /t 1689 | --- stap2 eval: $::StapOutputChains 1690 | --- stap3 1691 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1217") { 1692 | print_ubacktrace() 1693 | } 1694 | --- response_body 1695 | ababX 1696 | --- no_error_log 1697 | [alert] 1698 | [error] 1699 | 1700 | 1701 | 1702 | === TEST 63: trim leading spaces (1 byte at a time) 1703 | --- config 1704 | replace_filter_max_buffered_size 0; 1705 | default_type text/html; 1706 | location /a.html { 1707 | } 1708 | 1709 | location = /t { 1710 | content_by_lua ' 1711 | local res = ngx.location.capture("/a.html") 1712 | local txt = res.body 1713 | for i = 1, string.len(txt) do 1714 | ngx.print(string.sub(txt, i, i)) 1715 | ngx.flush(true) 1716 | end 1717 | '; 1718 | replace_filter '^\s+' '' g; 1719 | } 1720 | 1721 | --- user_files 1722 | >>> a.html 1723 | hello, world 1724 | blah yeah 1725 | hello 1726 | baby! 1727 | 1728 | abc 1729 | --- request 1730 | GET /t 1731 | --- response_body 1732 | hello, world 1733 | blah yeah 1734 | hello 1735 | baby! 1736 | abc 1737 | --- no_error_log 1738 | [alert] 1739 | [error] 1740 | 1741 | 1742 | 1743 | === TEST 64: split ctx->pending into ctx->pending and ctx->free 1744 | --- config 1745 | replace_filter_max_buffered_size 3; 1746 | default_type text/html; 1747 | 1748 | location = /t { 1749 | #echo "abc\nd"; 1750 | echo -n a; 1751 | echo -n b; 1752 | echo -n c; 1753 | echo -n "\n"; 1754 | echo d; 1755 | replace_filter "abcd|bc\ne|c$" X; 1756 | } 1757 | --- request 1758 | GET /t 1759 | --- stap2 eval: $::StapOutputChains 1760 | --- stap3 1761 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1482") { 1762 | print_ubacktrace() 1763 | } 1764 | --- response_body 1765 | abX 1766 | d 1767 | --- no_error_log 1768 | [alert] 1769 | [error] 1770 | 1771 | 1772 | 1773 | === TEST 65: trim both leading and trailing spaces (1 byte at a time) 1774 | --- config 1775 | default_type text/html; 1776 | replace_filter_max_buffered_size 2; 1777 | location /t { 1778 | echo -n 'a'; 1779 | echo_sleep 0.001; 1780 | echo ' '; 1781 | echo_sleep 0.001; 1782 | echo ''; 1783 | echo_sleep 0.001; 1784 | echo ' '; 1785 | echo_sleep 0.001; 1786 | echo "b"; 1787 | echo_sleep 0.001; 1788 | echo " "; 1789 | replace_filter '^\s+|\s+$' '' g; 1790 | } 1791 | 1792 | location = /main { 1793 | echo_location_async /t1; 1794 | echo_location_async /t2; 1795 | echo_location_async /t3; 1796 | echo_location_async /t4; 1797 | echo_location_async /t5; 1798 | echo_location_async /t6; 1799 | } 1800 | 1801 | --- stap3 eval: $::StapOutputChains 1802 | --- request 1803 | GET /main 1804 | --- response_body 1805 | a 1806 | b 1807 | a 1808 | b 1809 | a 1810 | b 1811 | a 1812 | b 1813 | a 1814 | b 1815 | a 1816 | b 1817 | 1818 | --- no_error_log 1819 | [alert] 1820 | [error] 1821 | 1822 | -------------------------------------------------------------------------------- /t/02-max-buffered.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4); 16 | 17 | our $StapOutputChains = <<'_EOC_'; 18 | global active 19 | 20 | F(ngx_http_handler) { 21 | active = 1 22 | } 23 | 24 | /* 25 | F(ngx_http_write_filter) { 26 | if (active && pid() == target()) { 27 | printf("http writer filter: %s\n", ngx_chain_dump($in)) 28 | } 29 | } 30 | */ 31 | 32 | F(ngx_http_chunked_body_filter) { 33 | if (active && pid() == target()) { 34 | printf("http chunked filter: %s\n", ngx_chain_dump($in)) 35 | } 36 | } 37 | 38 | F(ngx_http_replace_output) { 39 | if (active && pid() == target()) { 40 | printf("http replace output: %s\n", ngx_chain_dump($ctx->out)) 41 | } 42 | } 43 | 44 | probe syscall.writev { 45 | if (active && pid() == target()) { 46 | printf("writev(%s)", ngx_iovec_dump($vec, $vlen)) 47 | /* 48 | for (i = 0; i < $vlen; i++) { 49 | printf(" %p [%s]", $vec[i]->iov_base, text_str(user_string_n($vec[i]->iov_base, $vec[i]->iov_len))) 50 | } 51 | */ 52 | } 53 | } 54 | 55 | probe syscall.writev.return { 56 | if (active && pid() == target()) { 57 | printf(" = %s\n", retstr) 58 | } 59 | } 60 | 61 | _EOC_ 62 | 63 | #no_diff(); 64 | no_long_string(); 65 | run_tests(); 66 | 67 | __DATA__ 68 | 69 | === TEST 1: 1-byte chain bufs (0) 70 | --- config 71 | default_type text/html; 72 | replace_filter_max_buffered_size 0; 73 | 74 | location = /t { 75 | echo -n a; 76 | echo -n b; 77 | echo -n a; 78 | echo -n b; 79 | echo -n a; 80 | echo -n c; 81 | echo d; 82 | replace_filter abac X; 83 | } 84 | --- request 85 | GET /t 86 | --- stap2 eval: $::StapOutputChains 87 | --- stap3 88 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1413") { 89 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 90 | print_ubacktrace() 91 | } 92 | 93 | --- response_body 94 | ababacd 95 | --- error_log 96 | replace filter: exceeding replace_filter_max_buffered_size (0): 1 97 | --- no_error_log 98 | [error] 99 | 100 | 101 | 102 | === TEST 2: 1-byte chain bufs (1) 103 | --- config 104 | default_type text/html; 105 | replace_filter_max_buffered_size 1; 106 | 107 | location = /t { 108 | echo -n a; 109 | echo -n b; 110 | echo -n a; 111 | echo -n b; 112 | echo -n a; 113 | echo -n c; 114 | echo d; 115 | replace_filter abac X; 116 | } 117 | --- request 118 | GET /t 119 | --- stap2 eval: $::StapOutputChains 120 | --- stap3 121 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1439") { 122 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 123 | print_ubacktrace() 124 | exit() 125 | } 126 | 127 | --- response_body 128 | ababacd 129 | --- error_log 130 | replace filter: exceeding replace_filter_max_buffered_size (1): 2 131 | --- no_error_log 132 | [error] 133 | 134 | 135 | 136 | === TEST 3: trim both leading and trailing spaces (1 byte at a time) (2) 137 | --- config 138 | replace_filter_max_buffered_size 2; 139 | default_type text/html; 140 | location /a.html { 141 | internal; 142 | } 143 | 144 | location = /t { 145 | content_by_lua ' 146 | local res = ngx.location.capture("/a.html") 147 | local txt = res.body 148 | for i = 1, string.len(txt) do 149 | ngx.print(string.sub(txt, i, i)) 150 | ngx.flush(true) 151 | end 152 | '; 153 | replace_filter '^\s+|\s+$' '' g; 154 | } 155 | --- user_files 156 | >>> a.html 157 | hello, world 158 | blah yeah 159 | hello 160 | baby! 161 | 162 | abc 163 | 164 | --- stap2 165 | F(ngx_palloc) { 166 | if ($size < 0) { 167 | print_ubacktrace() 168 | exit() 169 | } 170 | } 171 | --- stap3 172 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1438") { 173 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 174 | print_ubacktrace() 175 | exit() 176 | } 177 | 178 | --- request 179 | GET /t 180 | --- response_body 181 | hello, world 182 | blah yeah 183 | hello 184 | baby! 185 | 186 | abc 187 | 188 | --- error_log 189 | replace filter: exceeding replace_filter_max_buffered_size (2): 3 190 | --- no_error_log 191 | [error] 192 | 193 | 194 | 195 | === TEST 4: github issue #2: error "general look-ahead not supported" 196 | --- config 197 | replace_filter_max_buffered_size 0; 198 | location /t { 199 | charset utf-8; 200 | default_type text/html; 201 | echo "ABCabcABCabc"; 202 | #replace_filter_types text/plain; 203 | replace_filter "a.+a" "X" "ig"; 204 | } 205 | --- request 206 | GET /t 207 | 208 | --- stap3 209 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1481") { 210 | print_ubacktrace() 211 | } 212 | 213 | --- response_body 214 | ABCabcABCabc 215 | --- error_log 216 | replace filter: exceeding replace_filter_max_buffered_size (0): 2 217 | --- no_error_log 218 | [error] 219 | 220 | 221 | 222 | === TEST 5: backtrack to the middle of a pending capture (pending: output|capture + rematch) (0) 223 | --- config 224 | replace_filter_max_buffered_size 0; 225 | default_type text/html; 226 | location = /t { 227 | echo -n ab; 228 | echo -n c; 229 | echo d; 230 | replace_filter 'abce|b' 'X' g; 231 | } 232 | 233 | --- stap2 234 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1492") { 235 | print_ubacktrace() 236 | } 237 | 238 | --- stap3 eval: $::StapOutputChains 239 | --- request 240 | GET /t 241 | --- response_body 242 | abcd 243 | 244 | --- error_log 245 | replace filter: exceeding replace_filter_max_buffered_size (0): 1 246 | --- no_error_log 247 | [error] 248 | 249 | 250 | 251 | === TEST 6: backtrack to the middle of a pending capture (pending: output|capture + rematch) (1) 252 | --- config 253 | replace_filter_max_buffered_size 1; 254 | default_type text/html; 255 | location = /t { 256 | echo -n ab; 257 | echo -n c; 258 | echo d; 259 | replace_filter 'abce|b' 'X' g; 260 | } 261 | 262 | --- stap2 263 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1501") { 264 | print_ubacktrace() 265 | } 266 | 267 | --- stap3 eval: $::StapOutputChains 268 | --- request 269 | GET /t 270 | --- response_body 271 | aXcd 272 | 273 | --- error_log 274 | replace filter: exceeding replace_filter_max_buffered_size (1): 2 275 | --- no_error_log 276 | [error] 277 | 278 | -------------------------------------------------------------------------------- /t/03-var.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4 + 3); 16 | 17 | our $StapOutputChains = <<'_EOC_'; 18 | global active 19 | 20 | F(ngx_http_handler) { 21 | active = 1 22 | } 23 | 24 | /* 25 | F(ngx_http_write_filter) { 26 | if (active && pid() == target()) { 27 | printf("http writer filter: %s\n", ngx_chain_dump($in)) 28 | } 29 | } 30 | */ 31 | 32 | F(ngx_http_chunked_body_filter) { 33 | if (active && pid() == target()) { 34 | printf("http chunked filter: %s\n", ngx_chain_dump($in)) 35 | } 36 | } 37 | 38 | F(ngx_http_replace_output) { 39 | if (active && pid() == target()) { 40 | printf("http replace output: %s\n", ngx_chain_dump($ctx->out)) 41 | } 42 | } 43 | 44 | probe syscall.writev { 45 | if (active && pid() == target()) { 46 | printf("writev(%s)", ngx_iovec_dump($vec, $vlen)) 47 | /* 48 | for (i = 0; i < $vlen; i++) { 49 | printf(" %p [%s]", $vec[i]->iov_base, text_str(user_string_n($vec[i]->iov_base, $vec[i]->iov_len))) 50 | } 51 | */ 52 | } 53 | } 54 | 55 | probe syscall.writev.return { 56 | if (active && pid() == target()) { 57 | printf(" = %s\n", retstr) 58 | } 59 | } 60 | 61 | _EOC_ 62 | 63 | #no_diff(); 64 | #no_long_string(); 65 | run_tests(); 66 | 67 | __DATA__ 68 | 69 | === TEST 1: nginx vars (global) 70 | --- config 71 | default_type text/html; 72 | replace_filter_max_buffered_size 0; 73 | location /t { 74 | set $foo X; 75 | echo abc; 76 | replace_filter . $foo g; 77 | } 78 | --- request 79 | GET /t 80 | 81 | --- stap 82 | F(ngx_http_replace_non_capturing_parse) { 83 | println("non capturing parse") 84 | } 85 | 86 | F(ngx_http_replace_capturing_parse) { 87 | println("capturing parse") 88 | } 89 | 90 | --- stap_out_like chop 91 | ^(non capturing parse\n)+$ 92 | 93 | --- response_body chop 94 | XXXX 95 | --- no_error_log 96 | [alert] 97 | [error] 98 | 99 | 100 | 101 | === TEST 2: nginx vars (non-global) 102 | --- config 103 | default_type text/html; 104 | replace_filter_max_buffered_size 0; 105 | location /t { 106 | set $foo X; 107 | echo abc; 108 | replace_filter . $foo; 109 | } 110 | --- request 111 | GET /t 112 | 113 | --- stap 114 | F(ngx_http_replace_non_capturing_parse) { 115 | println("non capturing parse") 116 | } 117 | 118 | F(ngx_http_replace_capturing_parse) { 119 | println("capturing parse") 120 | } 121 | 122 | --- stap_out_like chop 123 | ^(non capturing parse\n)+$ 124 | 125 | --- response_body 126 | Xbc 127 | --- no_error_log 128 | [alert] 129 | [error] 130 | 131 | 132 | 133 | === TEST 3: undefined nginx vars 134 | --- config 135 | default_type text/html; 136 | replace_filter_max_buffered_size 0; 137 | location /t { 138 | echo abc; 139 | replace_filter . $foo; 140 | } 141 | --- request 142 | GET /t 143 | --- response_body 144 | Xbc 145 | --- no_error_log 146 | [alert] 147 | [error] 148 | --- SKIP 149 | 150 | 151 | 152 | === TEST 4: use of capturing variables 153 | --- config 154 | default_type text/html; 155 | replace_filter_max_buffered_size 0; 156 | location /t { 157 | echo abc; 158 | replace_filter . $1; 159 | } 160 | --- request 161 | GET /t 162 | --- response_body 163 | Xbc 164 | --- no_error_log 165 | [alert] 166 | [error] 167 | --- SKIP 168 | 169 | 170 | 171 | === TEST 5: more contexts 172 | --- config 173 | default_type text/html; 174 | replace_filter_max_buffered_size 0; 175 | location /t { 176 | set $foo X; 177 | echo abc; 178 | replace_filter . "[$foo]"; 179 | } 180 | --- request 181 | GET /t 182 | --- response_body 183 | [X]bc 184 | --- no_error_log 185 | [alert] 186 | [error] 187 | 188 | 189 | 190 | === TEST 6: more nginx vars 191 | --- config 192 | default_type text/html; 193 | replace_filter_max_buffered_size 0; 194 | location /t { 195 | set $foo X; 196 | set $bar Y; 197 | echo abc; 198 | replace_filter . "[$foo,$bar]"; 199 | } 200 | --- request 201 | GET /t 202 | --- response_body 203 | [X,Y]bc 204 | --- no_error_log 205 | [alert] 206 | [error] 207 | 208 | 209 | 210 | === TEST 7: various lengths of nginx var values 211 | --- config 212 | default_type text/html; 213 | replace_filter_max_buffered_size 0; 214 | location /t { 215 | set $foo XYZ; 216 | set $bar ""; 217 | echo abc; 218 | replace_filter . "[$foo,$bar]"; 219 | } 220 | --- request 221 | GET /t 222 | --- response_body 223 | [XYZ,]bc 224 | --- no_error_log 225 | [alert] 226 | [error] 227 | 228 | 229 | 230 | === TEST 8: escaping the dollar sign 231 | --- config 232 | default_type text/html; 233 | replace_filter_max_buffered_size 0; 234 | location /t { 235 | set $foo X; 236 | set $bar Y; 237 | echo abc; 238 | replace_filter . "[$foo,$$bar]"; 239 | } 240 | --- request 241 | GET /t 242 | --- response_body 243 | [X,$bar]bc 244 | --- no_error_log 245 | [alert] 246 | [error] 247 | 248 | 249 | 250 | === TEST 9: \ is not an escaping sequence 251 | --- config 252 | default_type text/html; 253 | replace_filter_max_buffered_size 0; 254 | location /t { 255 | set $foo X; 256 | set $bar Y; 257 | echo abc; 258 | replace_filter . "[\$foo,\$bar]"; 259 | } 260 | --- request 261 | GET /t 262 | --- response_body 263 | [\X,\Y]bc 264 | --- no_error_log 265 | [alert] 266 | [error] 267 | 268 | 269 | 270 | === TEST 10: cached subs values 271 | --- config 272 | default_type text/html; 273 | replace_filter_max_buffered_size 0; 274 | location /t { 275 | set $foo X; 276 | echo abc; 277 | replace_filter . "$foo" g; 278 | } 279 | --- request 280 | GET /t 281 | --- response_body chop 282 | XXXX 283 | 284 | --- stap 285 | F(ngx_http_replace_complex_value) { 286 | println("complex value") 287 | } 288 | 289 | --- stap_out 290 | complex value 291 | 292 | --- no_error_log 293 | [alert] 294 | [error] 295 | 296 | -------------------------------------------------------------------------------- /t/05-capturing-max-buffered.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4); 16 | 17 | our $StapOutputChains = <<'_EOC_'; 18 | global active 19 | 20 | F(ngx_http_handler) { 21 | active = 1 22 | } 23 | 24 | /* 25 | F(ngx_http_write_filter) { 26 | if (active && pid() == target()) { 27 | printf("http writer filter: %s\n", ngx_chain_dump($in)) 28 | } 29 | } 30 | */ 31 | 32 | F(ngx_http_chunked_body_filter) { 33 | if (active && pid() == target()) { 34 | printf("http chunked filter: %s\n", ngx_chain_dump($in)) 35 | } 36 | } 37 | 38 | F(ngx_http_replace_output) { 39 | if (active && pid() == target()) { 40 | printf("http replace output: %s\n", ngx_chain_dump($ctx->out)) 41 | } 42 | } 43 | 44 | probe syscall.writev { 45 | if (active && pid() == target()) { 46 | printf("writev(%s)", ngx_iovec_dump($vec, $vlen)) 47 | /* 48 | for (i = 0; i < $vlen; i++) { 49 | printf(" %p [%s]", $vec[i]->iov_base, text_str(user_string_n($vec[i]->iov_base, $vec[i]->iov_len))) 50 | } 51 | */ 52 | } 53 | } 54 | 55 | probe syscall.writev.return { 56 | if (active && pid() == target()) { 57 | printf(" = %s\n", retstr) 58 | } 59 | } 60 | 61 | _EOC_ 62 | 63 | #no_diff(); 64 | no_long_string(); 65 | run_tests(); 66 | 67 | __DATA__ 68 | 69 | === TEST 1: 1-byte chain bufs (0) 70 | --- config 71 | default_type text/html; 72 | replace_filter_max_buffered_size 0; 73 | 74 | location = /t { 75 | echo -n a; 76 | echo -n b; 77 | echo -n a; 78 | echo -n b; 79 | echo -n a; 80 | echo -n c; 81 | echo d; 82 | replace_filter abac [$&]; 83 | } 84 | --- request 85 | GET /t 86 | --- stap2 eval: $::StapOutputChains 87 | --- stap3 88 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1413") { 89 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 90 | print_ubacktrace() 91 | } 92 | 93 | --- response_body 94 | ababacd 95 | --- error_log 96 | replace filter: exceeding replace_filter_max_buffered_size (0): 1 97 | --- no_error_log 98 | [error] 99 | 100 | 101 | 102 | === TEST 2: 1-byte chain bufs (1) 103 | --- config 104 | default_type text/html; 105 | replace_filter_max_buffered_size 1; 106 | 107 | location = /t { 108 | echo -n a; 109 | echo -n b; 110 | echo -n a; 111 | echo -n b; 112 | echo -n a; 113 | echo -n c; 114 | echo d; 115 | replace_filter abac [$&]; 116 | } 117 | --- request 118 | GET /t 119 | --- stap2 eval: $::StapOutputChains 120 | --- stap3 121 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1439") { 122 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 123 | print_ubacktrace() 124 | exit() 125 | } 126 | 127 | --- response_body 128 | ababacd 129 | --- error_log 130 | replace filter: exceeding replace_filter_max_buffered_size (1): 2 131 | --- no_error_log 132 | [error] 133 | 134 | 135 | 136 | === TEST 3: 1-byte chain bufs (2) 137 | --- config 138 | default_type text/html; 139 | replace_filter_max_buffered_size 2; 140 | 141 | location = /t { 142 | echo -n a; 143 | echo -n b; 144 | echo -n a; 145 | echo -n b; 146 | echo -n a; 147 | echo -n c; 148 | echo d; 149 | replace_filter abac [$&]; 150 | } 151 | --- request 152 | GET /t 153 | --- stap2 eval: $::StapOutputChains 154 | --- stap3 155 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1439") { 156 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 157 | print_ubacktrace() 158 | exit() 159 | } 160 | 161 | --- response_body 162 | ababacd 163 | --- error_log 164 | replace filter: exceeding replace_filter_max_buffered_size (2): 3 165 | --- no_error_log 166 | [error] 167 | 168 | 169 | 170 | === TEST 4: trim both leading and trailing spaces (1 byte at a time) (6) 171 | --- config 172 | replace_filter_max_buffered_size 6; 173 | default_type text/html; 174 | location /a.html { 175 | internal; 176 | } 177 | 178 | location = /t { 179 | content_by_lua ' 180 | local res = ngx.location.capture("/a.html") 181 | local txt = res.body 182 | for i = 1, string.len(txt) do 183 | ngx.print(string.sub(txt, i, i)) 184 | ngx.flush(true) 185 | end 186 | '; 187 | replace_filter '^\s+|\s+$' '[$&]' g; 188 | } 189 | --- user_files 190 | >>> a.html 191 | hello, world 192 | blah yeah 193 | hello 194 | baby! 195 | 196 | abc 197 | 198 | --- stap2 199 | F(ngx_palloc) { 200 | if ($size < 0) { 201 | print_ubacktrace() 202 | exit() 203 | } 204 | } 205 | --- stap3 206 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1438") { 207 | //printf("chain: %s", ngx_chain_dump($ctx->busy)) 208 | print_ubacktrace() 209 | exit() 210 | } 211 | 212 | --- request 213 | GET /t 214 | --- response_body 215 | [ ]hello, world[ ] 216 | blah yeah 217 | hello[ ] 218 | [ ]baby! 219 | 220 | abc 221 | 222 | --- error_log 223 | replace filter: exceeding replace_filter_max_buffered_size (6): 7 224 | --- no_error_log 225 | [error] 226 | 227 | 228 | 229 | === TEST 5: github issue #2: error "general look-ahead not supported" 230 | --- config 231 | replace_filter_max_buffered_size 0; 232 | location /t { 233 | charset utf-8; 234 | default_type text/html; 235 | echo "ABCabcABCabc"; 236 | #replace_filter_types text/plain; 237 | replace_filter "a.+a" "[$&]" "ig"; 238 | } 239 | --- request 240 | GET /t 241 | 242 | --- stap3 243 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1481") { 244 | print_ubacktrace() 245 | } 246 | 247 | --- response_body 248 | ABCabcABCabc 249 | --- error_log 250 | replace filter: exceeding replace_filter_max_buffered_size (0): 12 251 | --- no_error_log 252 | [error] 253 | 254 | 255 | 256 | === TEST 6: backtrack to the middle of a pending capture (pending: output|capture + rematch) (0) 257 | --- config 258 | replace_filter_max_buffered_size 0; 259 | default_type text/html; 260 | location = /t { 261 | echo -n ab; 262 | echo -n c; 263 | echo d; 264 | replace_filter 'abce|b' '[$&]' g; 265 | } 266 | 267 | --- stap2 268 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1492") { 269 | print_ubacktrace() 270 | } 271 | 272 | --- stap3 eval: $::StapOutputChains 273 | --- request 274 | GET /t 275 | --- response_body 276 | abcd 277 | 278 | --- error_log 279 | replace filter: exceeding replace_filter_max_buffered_size (0): 2 280 | --- no_error_log 281 | [error] 282 | 283 | 284 | 285 | === TEST 7: backtrack to the middle of a pending capture (pending: output|capture + rematch) (1) 286 | --- config 287 | replace_filter_max_buffered_size 1; 288 | default_type text/html; 289 | location = /t { 290 | echo -n ab; 291 | echo -n c; 292 | echo d; 293 | replace_filter 'abce|b' '[$&]' g; 294 | } 295 | 296 | --- stap2 297 | probe process("nginx").statement("*@ngx_http_replace_filter_module.c:1501") { 298 | print_ubacktrace() 299 | } 300 | 301 | --- stap3 eval: $::StapOutputChains 302 | --- request 303 | GET /t 304 | --- response_body 305 | abcd 306 | 307 | --- error_log 308 | replace filter: exceeding replace_filter_max_buffered_size (1): 2 309 | --- no_error_log 310 | [error] 311 | 312 | -------------------------------------------------------------------------------- /t/06-if.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4); 16 | 17 | #no_diff(); 18 | #no_long_string(); 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: local if hit 24 | --- config 25 | location /t { 26 | default_type text/plain; 27 | echo abcabcabde; 28 | 29 | if ($arg_disable = "") { 30 | replace_filter_types text/plain; 31 | replace_filter_max_buffered_size 0; 32 | replace_filter abcabd X; 33 | } 34 | } 35 | --- request 36 | GET /t 37 | --- response_body 38 | abcXe 39 | --- no_error_log 40 | [alert] 41 | [error] 42 | 43 | 44 | 45 | === TEST 2: local if miss 46 | --- config 47 | replace_filter_max_buffered_size 0; 48 | location /t { 49 | default_type text/plain; 50 | echo abcabcabde; 51 | 52 | if ($arg_disable = "") { 53 | replace_filter_types text/plain; 54 | replace_filter_max_buffered_size 0; 55 | replace_filter abcabd X; 56 | } 57 | } 58 | --- request 59 | GET /t?disable=1 60 | --- response_body 61 | abcabcabde 62 | --- no_error_log 63 | [alert] 64 | [error] 65 | 66 | -------------------------------------------------------------------------------- /t/07-multi.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4 + 5); 16 | 17 | our $StapOutputChains = <<'_EOC_'; 18 | global active 19 | 20 | F(ngx_http_handler) { 21 | active = 1 22 | } 23 | 24 | /* 25 | F(ngx_http_write_filter) { 26 | if (active && pid() == target()) { 27 | printf("http writer filter: %s\n", ngx_chain_dump($in)) 28 | } 29 | } 30 | */ 31 | 32 | F(ngx_http_chunked_body_filter) { 33 | if (active && pid() == target()) { 34 | printf("http chunked filter: %s\n", ngx_chain_dump($in)) 35 | } 36 | } 37 | 38 | F(ngx_http_replace_output) { 39 | if (active && pid() == target()) { 40 | printf("http replace output: %s\n", ngx_chain_dump($ctx->out)) 41 | } 42 | } 43 | 44 | probe syscall.writev { 45 | if (active && pid() == target()) { 46 | printf("writev(%s)", ngx_iovec_dump($vec, $vlen)) 47 | /* 48 | for (i = 0; i < $vlen; i++) { 49 | printf(" %p [%s]", $vec[i]->iov_base, text_str(user_string_n($vec[i]->iov_base, $vec[i]->iov_len))) 50 | } 51 | */ 52 | } 53 | } 54 | 55 | probe syscall.writev.return { 56 | if (active && pid() == target()) { 57 | printf(" = %s\n", retstr) 58 | } 59 | } 60 | 61 | _EOC_ 62 | 63 | #no_diff(); 64 | #no_long_string(); 65 | run_tests(); 66 | 67 | __DATA__ 68 | 69 | === TEST 1: once patterns 70 | --- config 71 | default_type text/html; 72 | replace_filter_max_buffered_size 0; 73 | location /t { 74 | echo 'hello world world hello'; 75 | replace_filter world "<$&>"; 76 | replace_filter hello "[$&]"; 77 | } 78 | --- request 79 | GET /t 80 | --- response_body 81 | [hello] world hello 82 | 83 | --- stap 84 | F(ngx_http_replace_non_capturing_parse) { 85 | println("non capturing parse") 86 | } 87 | 88 | F(ngx_http_replace_capturing_parse) { 89 | println("capturing parse") 90 | } 91 | 92 | --- stap_out_like chop 93 | ^(capturing parse\n)+$ 94 | 95 | --- stap2 eval: $::StapOutputChains 96 | --- no_error_log 97 | [alert] 98 | [error] 99 | 100 | 101 | 102 | === TEST 2: once patterns 103 | --- config 104 | default_type text/html; 105 | replace_filter_max_buffered_size 0; 106 | location /t { 107 | echo 'Hello world Hello world'; 108 | replace_filter world "<$&>"; 109 | replace_filter hello "[$&]"; 110 | } 111 | --- request 112 | GET /t 113 | --- response_body 114 | Hello Hello world 115 | 116 | --- stap2 eval: $::StapOutputChains 117 | --- no_error_log 118 | [alert] 119 | [error] 120 | 121 | 122 | 123 | === TEST 3: case-insensitive patterns 124 | --- config 125 | default_type text/html; 126 | replace_filter_max_buffered_size 0; 127 | location /t { 128 | echo 'Hello world WORLD HELLO'; 129 | replace_filter world "<$&>"; 130 | replace_filter hello "[$&]" i; 131 | } 132 | --- request 133 | GET /t 134 | --- response_body 135 | [Hello] WORLD HELLO 136 | 137 | --- stap2 eval: $::StapOutputChains 138 | --- no_error_log 139 | [alert] 140 | [error] 141 | 142 | 143 | 144 | === TEST 4: global subs 145 | --- config 146 | default_type text/html; 147 | replace_filter_max_buffered_size 0; 148 | location /t { 149 | echo 'hello world world hello'; 150 | replace_filter world "<$&>" g; 151 | replace_filter hello "[$&]" g; 152 | } 153 | --- request 154 | GET /t 155 | --- response_body 156 | [hello] [hello] 157 | 158 | --- stap2 eval: $::StapOutputChains 159 | --- no_error_log 160 | [alert] 161 | [error] 162 | 163 | 164 | 165 | === TEST 5: global subs (case sensitive) 166 | --- config 167 | default_type text/html; 168 | replace_filter_max_buffered_size 0; 169 | location /t { 170 | echo 'Hello World worlD hellO'; 171 | replace_filter world "<$&>" g; 172 | replace_filter hello "[$&]" g; 173 | } 174 | --- request 175 | GET /t 176 | --- response_body 177 | Hello World worlD hellO 178 | 179 | --- stap2 eval: $::StapOutputChains 180 | --- no_error_log 181 | [alert] 182 | [error] 183 | 184 | 185 | 186 | === TEST 6: global subs (case insensitive) 187 | --- config 188 | default_type text/html; 189 | replace_filter_max_buffered_size 0; 190 | location /t { 191 | echo 'Hello World worlD hellO'; 192 | replace_filter world "<$&>" ig; 193 | replace_filter hello "[$&]" g; 194 | } 195 | --- request 196 | GET /t 197 | --- response_body 198 | Hello hellO 199 | 200 | --- stap2 eval: $::StapOutputChains 201 | --- no_error_log 202 | [alert] 203 | [error] 204 | 205 | 206 | 207 | === TEST 7: global subs (case insensitive) (2) 208 | --- config 209 | default_type text/html; 210 | replace_filter_max_buffered_size 0; 211 | location /t { 212 | echo 'Hello World worlD hellO'; 213 | replace_filter world "<$&>" g; 214 | replace_filter hello "[$&]" ig; 215 | } 216 | --- request 217 | GET /t 218 | --- response_body 219 | [Hello] World worlD [hellO] 220 | 221 | --- stap2 eval: $::StapOutputChains 222 | --- no_error_log 223 | [alert] 224 | [error] 225 | 226 | 227 | 228 | === TEST 8: global subs (case insensitive) (3) 229 | --- config 230 | default_type text/html; 231 | replace_filter_max_buffered_size 0; 232 | location /t { 233 | echo 'Hello World worlD hellO'; 234 | replace_filter world "<$&>" gi; 235 | replace_filter hello "[$&]" ig; 236 | } 237 | --- request 238 | GET /t 239 | --- response_body 240 | [Hello] [hellO] 241 | 242 | --- stap 243 | F(ngx_http_replace_non_capturing_parse) { 244 | println("non capturing parse") 245 | } 246 | 247 | F(ngx_http_replace_capturing_parse) { 248 | println("capturing parse") 249 | } 250 | 251 | --- stap_out_like chop 252 | ^(capturing parse\n)+$ 253 | 254 | --- no_error_log 255 | [alert] 256 | [error] 257 | 258 | 259 | 260 | === TEST 9: global subs (case insensitive) - non-capturing 261 | --- config 262 | default_type text/html; 263 | replace_filter_max_buffered_size 0; 264 | location /t { 265 | echo 'Hello World'; 266 | replace_filter world "<>" gi; 267 | replace_filter hello "[]" ig; 268 | } 269 | --- request 270 | GET /t 271 | --- response_body 272 | [] <> 273 | 274 | --- stap 275 | F(ngx_http_replace_non_capturing_parse) { 276 | println("non capturing parse") 277 | } 278 | 279 | F(ngx_http_replace_capturing_parse) { 280 | println("capturing parse") 281 | } 282 | 283 | --- stap_out_like chop 284 | ^(non capturing parse\n)+$ 285 | 286 | --- stap2 eval: $::StapOutputChains 287 | --- no_error_log 288 | [alert] 289 | [error] 290 | 291 | 292 | 293 | === TEST 10: working as a tokenizer 294 | --- config 295 | default_type text/html; 296 | replace_filter_max_buffered_size 0; 297 | location /t { 298 | echo -n a; 299 | echo b; 300 | replace_filter a "[$&]" g; 301 | replace_filter ab "<$&>" g; 302 | } 303 | --- request 304 | GET /t 305 | --- response_body 306 | [a]b 307 | 308 | --- stap2 eval: $::StapOutputChains 309 | --- no_error_log 310 | [alert] 311 | [error] 312 | 313 | 314 | 315 | === TEST 11: working as a tokenizer (2) 316 | --- config 317 | default_type text/html; 318 | replace_filter_max_buffered_size 1; 319 | location /t { 320 | echo -n a; 321 | echo b; 322 | replace_filter ab "<$&>" g; 323 | replace_filter a "[$&]" g; 324 | } 325 | --- request 326 | GET /t 327 | --- response_body 328 | 329 | 330 | --- stap2 eval: $::StapOutputChains 331 | --- no_error_log 332 | [alert] 333 | [error] 334 | 335 | 336 | 337 | === TEST 12: on server level 338 | --- config 339 | default_type text/html; 340 | replace_filter_max_buffered_size 1; 341 | replace_filter ab "<$&>" g; 342 | replace_filter a "[$&]" g; 343 | 344 | location /t { 345 | echo -n a; 346 | echo b; 347 | } 348 | --- request 349 | GET /t 350 | --- response_body 351 | 352 | 353 | --- stap2 eval: $::StapOutputChains 354 | --- no_error_log 355 | [alert] 356 | [error] 357 | 358 | 359 | 360 | === TEST 13: mixing once and global patterns 361 | --- config 362 | default_type text/html; 363 | replace_filter_max_buffered_size 1; 364 | location /t { 365 | echo hello world hiya hiya world hello; 366 | replace_filter hello "<$&>"; 367 | replace_filter hiya "{$&}"; 368 | replace_filter world "[$&]" g; 369 | } 370 | --- request 371 | GET /t 372 | --- response_body 373 | [world] {hiya} hiya [world] hello 374 | 375 | --- stap2 eval: $::StapOutputChains 376 | --- no_error_log 377 | [alert] 378 | [error] 379 | 380 | 381 | 382 | === TEST 14: all once 383 | --- config 384 | default_type text/html; 385 | replace_filter_max_buffered_size 1; 386 | location /t { 387 | echo hello world hiya hiya world hello; 388 | replace_filter hello "<$&>"; 389 | replace_filter hiya "{$&}"; 390 | replace_filter world "[$&]"; 391 | } 392 | --- request 393 | GET /t 394 | --- response_body 395 | [world] {hiya} hiya world hello 396 | 397 | --- stap 398 | F(ngx_http_replace_complex_value) { 399 | println("complex value") 400 | } 401 | 402 | --- stap_out 403 | complex value 404 | complex value 405 | complex value 406 | 407 | --- stap2 eval: $::StapOutputChains 408 | --- no_error_log 409 | [alert] 410 | [error] 411 | 412 | 413 | 414 | === TEST 15: all once (server level) 415 | --- config 416 | default_type text/html; 417 | replace_filter_max_buffered_size 1; 418 | replace_filter hello "<$&>"; 419 | replace_filter hiya "{$&}"; 420 | replace_filter world "[$&]"; 421 | 422 | location /t { 423 | echo hello world hiya hiya world hello; 424 | } 425 | --- request 426 | GET /t 427 | --- response_body 428 | [world] {hiya} hiya world hello 429 | 430 | --- stap 431 | F(ngx_http_replace_complex_value) { 432 | println("complex value") 433 | } 434 | 435 | --- stap_out 436 | complex value 437 | complex value 438 | complex value 439 | 440 | --- stap2 eval: $::StapOutputChains 441 | --- no_error_log 442 | [alert] 443 | [error] 444 | 445 | 446 | 447 | === TEST 16: remove C/C++ comments (1 byte at a time) 448 | --- config 449 | replace_filter_max_buffered_size 50; 450 | default_type text/html; 451 | location /a.html { 452 | internal; 453 | } 454 | 455 | location = /t { 456 | content_by_lua ' 457 | local res = ngx.location.capture("/a.html") 458 | local txt = res.body 459 | for i = 1, string.len(txt) do 460 | ngx.print(string.sub(txt, i, i)) 461 | ngx.flush(true) 462 | end 463 | '; 464 | replace_filter "'(?:\\\\[^\n]|[^'\n])*'" $& g; 465 | replace_filter '"(?:\\\\[^\n]|[^"\n])*"' $& g; 466 | replace_filter '/\*.*?\*/|//[^\n]*' '' g; 467 | } 468 | --- user_files 469 | >>> a.html 470 | b = '"'; /* blah */ c = '"' 471 | a = "h\"/* */"; 472 | i don't know // hello // world /* */ 473 | hello world /** abc * b/c /* 474 | hello ** // world 475 | * 476 | */ 477 | blah /* hi */ */ b 478 | // 479 | ///hi 480 | --- stap2 481 | F(ngx_palloc) { 482 | if ($size < 0) { 483 | print_ubacktrace() 484 | exit() 485 | } 486 | } 487 | --- request 488 | GET /t 489 | --- response_body eval 490 | qq{b = '"'; c = '"' 491 | a = "h\\"/* */"; 492 | i don't know 493 | hello world 494 | blah */ b 495 | 496 | 497 | } 498 | --- no_error_log 499 | [alert] 500 | [error] 501 | 502 | 503 | 504 | === TEST 17: more patterns 505 | --- config 506 | default_type text/html; 507 | replace_filter_max_buffered_size 1; 508 | location /t { 509 | #echo hello world hiya hiya world hello; 510 | replace_filter a A; 511 | replace_filter b B; 512 | replace_filter c C; 513 | replace_filter d D; 514 | replace_filter e E; 515 | replace_filter f F; 516 | replace_filter g G; 517 | replace_filter h H; 518 | replace_filter i I; 519 | replace_filter j J; 520 | replace_filter k K; 521 | replace_filter l L; 522 | replace_filter m M; 523 | replace_filter n N; 524 | replace_filter o O; 525 | replace_filter p P; 526 | replace_filter q Q; 527 | replace_filter r R; 528 | replace_filter s S; 529 | replace_filter t T; 530 | replace_filter u U; 531 | replace_filter v V; 532 | replace_filter w W; 533 | replace_filter x X; 534 | replace_filter y Y; 535 | replace_filter z Z; 536 | } 537 | --- request 538 | GET /t 539 | --- user_files 540 | >>> t 541 | It'll be officially possible when the timer_by_lua directive is 542 | implemented in ngx_lua :) 543 | 544 | For now, people have been using some tricks to do something like that, 545 | i.e., using detached long-running requests (by calling ngx.eof early). 546 | See the related documentation for details: 547 | 548 | --- response_body 549 | IT'Ll BE OFfICiAllY PoSsible WHeN the tiMeR_by_lUa DirectiVe is 550 | implemented in nGX_lua :) 551 | 552 | For now, people have been using some tricKs to do something like that, 553 | i.e., using detached long-running reQuests (by calling ngx.eof early). 554 | See the related documentation for details: 555 | 556 | --- stap2 eval: $::StapOutputChains 557 | --- no_error_log 558 | [alert] 559 | [error] 560 | 561 | -------------------------------------------------------------------------------- /t/08-gzip.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 3); 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: once patterns 22 | --- config 23 | default_type text/html; 24 | replace_filter_max_buffered_size 0; 25 | location /t { 26 | content_by_lua ' 27 | ngx.header.content_encoding = "gzip" 28 | ngx.say("hello world world hello"); 29 | '; 30 | replace_filter hello "[$&]"; 31 | replace_filter world "<$&>"; 32 | } 33 | --- request 34 | GET /t 35 | --- response_body 36 | hello world world hello 37 | --- no_error_log 38 | [error] 39 | 40 | -------------------------------------------------------------------------------- /t/09-unused.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 5); 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: used 22 | --- config 23 | default_type text/html; 24 | replace_filter_max_buffered_size 0; 25 | location /t { 26 | echo abcabcabde; 27 | replace_filter abcabd X; 28 | } 29 | --- request 30 | GET /t 31 | 32 | --- stap 33 | F(ngx_http_replace_header_filter) { 34 | println("replace header filter") 35 | } 36 | 37 | F(ngx_http_replace_body_filter) { 38 | println("replace body filter") 39 | } 40 | 41 | --- stap_out 42 | replace header filter 43 | replace body filter 44 | replace body filter 45 | 46 | --- response_body 47 | abcXe 48 | --- no_error_log 49 | [alert] 50 | [error] 51 | 52 | 53 | 54 | === TEST 2: unused 55 | --- config 56 | default_type text/html; 57 | replace_filter_max_buffered_size 0; 58 | location /t { 59 | echo abcabcabde; 60 | #replace_filter abcabd X; 61 | } 62 | --- request 63 | GET /t 64 | 65 | --- stap 66 | F(ngx_http_replace_header_filter) { 67 | println("replace header filter") 68 | } 69 | 70 | F(ngx_http_replace_body_filter) { 71 | println("replace body filter") 72 | } 73 | 74 | --- stap_out 75 | 76 | --- response_body 77 | abcabcabde 78 | --- no_error_log 79 | [alert] 80 | [error] 81 | 82 | 83 | 84 | === TEST 3: used (multi http {} blocks) 85 | This test case won't run with nginx 1.9.3+ since duplicate http {} blocks 86 | have been prohibited since then. 87 | --- SKIP 88 | --- config 89 | default_type text/html; 90 | replace_filter_max_buffered_size 0; 91 | location /t { 92 | echo abcabcabde; 93 | replace_filter abcabd X; 94 | } 95 | --- post_main_config 96 | http { 97 | } 98 | 99 | --- request 100 | GET /t 101 | 102 | --- stap 103 | F(ngx_http_replace_header_filter) { 104 | println("replace header filter") 105 | } 106 | 107 | F(ngx_http_replace_body_filter) { 108 | println("replace body filter") 109 | } 110 | 111 | --- stap_out 112 | replace header filter 113 | replace body filter 114 | replace body filter 115 | 116 | --- response_body 117 | abcXe 118 | --- no_error_log 119 | [alert] 120 | [error] 121 | 122 | -------------------------------------------------------------------------------- /t/10-last-modified.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 5); 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: replace_filter_last_modified clear 22 | --- config 23 | default_type text/html; 24 | replace_filter_last_modified clear; 25 | location /t { 26 | content_by_lua ' 27 | ngx.header["Last-Modified"] = "Wed, 20 Nov 2013 05:30:35 GMT" 28 | ngx.say("ok") 29 | '; 30 | replace_filter abcabd X; 31 | } 32 | --- request 33 | GET /t 34 | 35 | --- response_body 36 | ok 37 | --- response_headers 38 | !Last-Modified 39 | --- no_error_log 40 | [alert] 41 | [error] 42 | 43 | 44 | 45 | === TEST 2: replace_filter_last_modified keep 46 | --- config 47 | default_type text/html; 48 | replace_filter_last_modified keep; 49 | location /t { 50 | content_by_lua ' 51 | ngx.header["Last-Modified"] = "Wed, 20 Nov 2013 05:30:35 GMT" 52 | ngx.say("ok") 53 | '; 54 | replace_filter abcabd X; 55 | } 56 | --- request 57 | GET /t 58 | 59 | --- response_body 60 | ok 61 | --- response_headers 62 | Last-Modified: Wed, 20 Nov 2013 05:30:35 GMT 63 | --- no_error_log 64 | [alert] 65 | [error] 66 | 67 | 68 | 69 | === TEST 3: replace_filter_last_modified default to clear 70 | --- config 71 | default_type text/html; 72 | #replace_filter_last_modified clear; 73 | location /t { 74 | content_by_lua ' 75 | ngx.header["Last-Modified"] = "Wed, 20 Nov 2013 05:30:35 GMT" 76 | ngx.say("ok") 77 | '; 78 | replace_filter abcabd X; 79 | } 80 | --- request 81 | GET /t 82 | 83 | --- response_body 84 | ok 85 | --- response_headers 86 | !Last-Modified 87 | --- no_error_log 88 | [alert] 89 | [error] 90 | 91 | -------------------------------------------------------------------------------- /t/11-skip.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #worker_connections(1014); 7 | #master_on(); 8 | #workers(2); 9 | #log_level('warn'); 10 | 11 | repeat_each(2); 12 | 13 | #no_shuffle(); 14 | 15 | plan tests => repeat_each() * (blocks() * 4); 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: skip true (constant) 22 | --- config 23 | default_type text/html; 24 | replace_filter_skip 1; 25 | location /t { 26 | content_by_lua ' 27 | ngx.say("abcabd") 28 | '; 29 | replace_filter abcabd X; 30 | } 31 | --- request 32 | GET /t 33 | 34 | --- response_body 35 | abcabd 36 | --- no_error_log 37 | [alert] 38 | [error] 39 | 40 | 41 | 42 | === TEST 2: skip false (constant 0) 43 | --- config 44 | default_type text/html; 45 | replace_filter_skip 0; 46 | location /t { 47 | content_by_lua ' 48 | ngx.say("abcabd") 49 | '; 50 | replace_filter abcabd X; 51 | } 52 | --- request 53 | GET /t 54 | 55 | --- response_body 56 | X 57 | --- no_error_log 58 | [alert] 59 | [error] 60 | 61 | 62 | 63 | === TEST 3: skip false (constant "") 64 | --- config 65 | default_type text/html; 66 | replace_filter_skip ""; 67 | location /t { 68 | content_by_lua ' 69 | ngx.say("abcabd") 70 | '; 71 | replace_filter abcabd X; 72 | } 73 | --- request 74 | GET /t 75 | 76 | --- response_body 77 | X 78 | --- no_error_log 79 | [alert] 80 | [error] 81 | 82 | 83 | 84 | === TEST 4: skip true (constant, random strings) 85 | --- config 86 | default_type text/html; 87 | replace_filter_skip ab; 88 | location /t { 89 | content_by_lua ' 90 | ngx.say("abcabd") 91 | '; 92 | replace_filter abcabd X; 93 | } 94 | --- request 95 | GET /t 96 | 97 | --- response_body 98 | abcabd 99 | --- no_error_log 100 | [alert] 101 | [error] 102 | 103 | 104 | 105 | === TEST 5: skip variable (1) 106 | --- config 107 | default_type text/html; 108 | set $skip ''; 109 | replace_filter_skip $skip; 110 | location /t { 111 | content_by_lua ' 112 | ngx.var.skip = 1 113 | ngx.say("abcabd") 114 | '; 115 | replace_filter abcabd X; 116 | } 117 | --- request 118 | GET /t 119 | 120 | --- response_body 121 | abcabd 122 | --- no_error_log 123 | [alert] 124 | [error] 125 | 126 | 127 | 128 | === TEST 6: skip variable (0) 129 | --- config 130 | default_type text/html; 131 | set $skip ''; 132 | replace_filter_skip $skip; 133 | location /t { 134 | content_by_lua ' 135 | ngx.var.skip = 0 136 | ngx.say("abcabd") 137 | '; 138 | replace_filter abcabd X; 139 | } 140 | --- request 141 | GET /t 142 | 143 | --- response_body 144 | X 145 | --- no_error_log 146 | [alert] 147 | [error] 148 | 149 | 150 | 151 | === TEST 7: skip variable ("") 152 | --- config 153 | default_type text/html; 154 | set $skip ''; 155 | replace_filter_skip $skip; 156 | location /t { 157 | content_by_lua ' 158 | ngx.var.skip = "" 159 | ngx.say("abcabd") 160 | '; 161 | replace_filter abcabd X; 162 | } 163 | --- request 164 | GET /t 165 | 166 | --- response_body 167 | X 168 | --- no_error_log 169 | [alert] 170 | [error] 171 | 172 | 173 | 174 | === TEST 8: skip variable (nil) 175 | --- config 176 | default_type text/html; 177 | set $skip ''; 178 | replace_filter_skip $skip; 179 | location /t { 180 | content_by_lua ' 181 | ngx.var.skip = nil 182 | ngx.say("abcabd") 183 | '; 184 | replace_filter abcabd X; 185 | } 186 | --- request 187 | GET /t 188 | 189 | --- response_body 190 | X 191 | --- no_error_log 192 | [alert] 193 | [error] 194 | 195 | -------------------------------------------------------------------------------- /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 | $NGX_EXTRA_OPT \ 12 | --with-cc-opt="-I$PCRE_INC -I$OPENSSL_INC" \ 13 | --with-ld-opt="-L$PCRE_LIB -L$OPENSSL_LIB -Wl,-rpath,$SREGEX_LIB:$PCRE_LIB:$LIBDRIZZLE_LIB:$OPENSSL_LIB" \ 14 | --with-http_ssl_module \ 15 | --without-mail_pop3_module \ 16 | --without-mail_imap_module \ 17 | --without-mail_smtp_module \ 18 | --without-http_upstream_ip_hash_module \ 19 | --without-http_empty_gif_module \ 20 | --without-http_memcached_module \ 21 | --without-http_referer_module \ 22 | --without-http_autoindex_module \ 23 | --without-http_auth_basic_module \ 24 | --without-http_userid_module \ 25 | --add-module=$root $opts \ 26 | --add-module=$root/../ndk-nginx-module \ 27 | --add-module=$root/../lua-nginx-module \ 28 | --add-module=$root/../echo-nginx-module \ 29 | --with-debug 30 | #--with-cc-opt="-g3 -O0" 31 | #--without-http_ssi_module # we cannot disable ssi because echo_location_async depends on it (i dunno why?!) 32 | 33 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Addr1 4 | fun:ngx_init_cycle 5 | fun:ngx_master_process_cycle 6 | fun:main 7 | } 8 | { 9 | 10 | Memcheck:Addr4 11 | fun:ngx_init_cycle 12 | fun:ngx_master_process_cycle 13 | fun:main 14 | } 15 | { 16 | 17 | Memcheck:Cond 18 | fun:ngx_vslprintf 19 | fun:ngx_snprintf 20 | fun:ngx_sock_ntop 21 | fun:ngx_event_accept 22 | fun:ngx_epoll_process_events 23 | fun:ngx_process_events_and_timers 24 | } 25 | { 26 | 27 | Memcheck:Cond 28 | fun:ngx_vslprintf 29 | fun:ngx_snprintf 30 | fun:ngx_sock_ntop 31 | fun:ngx_event_accept 32 | fun:ngx_epoll_process_events 33 | fun:ngx_process_events_and_timers 34 | } 35 | { 36 | 37 | Memcheck:Addr1 38 | fun:ngx_vslprintf 39 | fun:ngx_snprintf 40 | fun:ngx_sock_ntop 41 | fun:ngx_event_accept 42 | } 43 | { 44 | 45 | Memcheck:Leak 46 | fun:malloc 47 | fun:ngx_alloc 48 | obj:* 49 | } 50 | { 51 | 52 | exp-sgcheck:SorG 53 | fun:ngx_http_lua_ndk_set_var_get 54 | } 55 | { 56 | 57 | exp-sgcheck:SorG 58 | fun:ngx_http_variables_init_vars 59 | fun:ngx_http_block 60 | } 61 | { 62 | 63 | exp-sgcheck:SorG 64 | fun:ngx_conf_parse 65 | } 66 | { 67 | 68 | exp-sgcheck:SorG 69 | fun:ngx_vslprintf 70 | fun:ngx_log_error_core 71 | } 72 | { 73 | 74 | Memcheck:Leak 75 | fun:malloc 76 | fun:ngx_alloc 77 | fun:ngx_calloc 78 | fun:ngx_event_process_init 79 | } 80 | { 81 | 82 | Memcheck:Param 83 | epoll_ctl(event) 84 | fun:epoll_ctl 85 | } 86 | { 87 | 88 | Memcheck:Leak 89 | fun:malloc 90 | fun:ngx_alloc 91 | fun:ngx_event_process_init 92 | } 93 | { 94 | 95 | Memcheck:Cond 96 | fun:ngx_conf_flush_files 97 | fun:ngx_single_process_cycle 98 | } 99 | { 100 | 101 | Memcheck:Cond 102 | fun:memcpy 103 | fun:ngx_vslprintf 104 | fun:ngx_log_error_core 105 | fun:ngx_http_charset_header_filter 106 | } 107 | { 108 | 109 | Memcheck:Param 110 | socketcall.setsockopt(optval) 111 | fun:setsockopt 112 | fun:drizzle_state_connect 113 | } 114 | { 115 | 116 | Memcheck:Leak 117 | fun:malloc 118 | fun:ngx_alloc 119 | fun:ngx_pool_cleanup_add 120 | } 121 | { 122 | 123 | Memcheck:Cond 124 | fun:ngx_conf_flush_files 125 | fun:ngx_single_process_cycle 126 | fun:main 127 | } 128 | { 129 | 130 | Memcheck:Cond 131 | fun:index 132 | fun:expand_dynamic_string_token 133 | fun:_dl_map_object 134 | fun:map_doit 135 | fun:_dl_catch_error 136 | fun:do_preload 137 | fun:dl_main 138 | } 139 | { 140 | 141 | Memcheck:Leak 142 | match-leak-kinds: definite 143 | fun:malloc 144 | fun:ngx_alloc 145 | fun:ngx_set_environment 146 | fun:ngx_single_process_cycle 147 | } 148 | { 149 | 150 | Memcheck:Leak 151 | match-leak-kinds: definite 152 | fun:malloc 153 | fun:ngx_alloc 154 | fun:ngx_set_environment 155 | fun:ngx_worker_process_init 156 | fun:ngx_worker_process_cycle 157 | } 158 | --------------------------------------------------------------------------------