├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── .gitmodules ├── LICENSE.md ├── README.md ├── config ├── config.make └── ngx_http_brotli_filter_module.c /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | schedule: 9 | - cron: '0 0 * * *' 10 | name: Semgrep config 11 | jobs: 12 | semgrep: 13 | name: semgrep/ci 14 | runs-on: ubuntu-latest 15 | env: 16 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 17 | SEMGREP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 20 | container: 21 | image: returntocorp/semgrep 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "brotli"] 2 | path = brotli 3 | url = https://github.com/google/brotli.git 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | /* 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx_brotli_module 2 | 3 | This NGINX module enables the brotli compression for Accept-Encoding:"br". 4 | 5 | Brotli is a recent compression format developed by Google. 6 | 7 | https://tools.ietf.org/html/rfc7932 8 | 9 | Use the "--add-module=" when configuring NGINX to enable the module. 10 | 11 | Config options: 12 | 13 | brotli on/off - enable the module. When brotli is enabled, it takes 14 | precendence over gzip if Accept-Encoding has both gzip and 15 | brotli. 16 | brotli_comp_level num - the compression level used 1-11 17 | brotli_min_length num - the minimal size of the resource to be compressed. 18 | Brotli will compress only resources larger than this 19 | value. If it is smaller it will let gzip to compress. 20 | 21 | Currently tested only on Linux. 22 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | 2 | ngx_addon_name=ngx_http_brotli_filter_module 3 | 4 | HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_brotli_filter_module" 5 | 6 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_brotli_filter_module.c" 7 | 8 | CORE_INCS="$CORE_INCS $ngx_addon_dir/brotli/include/" 9 | 10 | CORE_DEPS="$CORE_DEPS brotli" 11 | 12 | CORE_LIBS="$CORE_LIBS -lm" 13 | 14 | CORE_LINK="$CORE_LINK $ngx_addon_dir/brotli/bin/obj/enc/backward_references.o \ 15 | $ngx_addon_dir/brotli/bin/obj/enc/bit_cost.o \ 16 | $ngx_addon_dir/brotli/bin/obj/enc/block_splitter.o \ 17 | $ngx_addon_dir/brotli/bin/obj/enc/brotli_bit_stream.o \ 18 | $ngx_addon_dir/brotli/bin/obj/enc/cluster.o \ 19 | $ngx_addon_dir/brotli/bin/obj/enc/compress_fragment.o \ 20 | $ngx_addon_dir/brotli/bin/obj/enc/compress_fragment_two_pass.o \ 21 | $ngx_addon_dir/brotli/bin/obj/enc/encode.o \ 22 | $ngx_addon_dir/brotli/bin/obj/enc/entropy_encode.o \ 23 | $ngx_addon_dir/brotli/bin/obj/enc/histogram.o \ 24 | $ngx_addon_dir/brotli/bin/obj/enc/literal_cost.o \ 25 | $ngx_addon_dir/brotli/bin/obj/enc/memory.o \ 26 | $ngx_addon_dir/brotli/bin/obj/enc/metablock.o \ 27 | $ngx_addon_dir/brotli/bin/obj/enc/static_dict.o \ 28 | $ngx_addon_dir/brotli/bin/obj/enc/utf8_util.o \ 29 | $ngx_addon_dir/brotli/bin/obj/common/dictionary.o" 30 | 31 | have=NGX_HTTP_GZIP . auto/have 32 | -------------------------------------------------------------------------------- /config.make: -------------------------------------------------------------------------------- 1 | cat << END >> $NGX_MAKEFILE 2 | 3 | brotli: $NGX_MAKEFILE 4 | cd $ngx_addon_dir/brotli \\ 5 | && \$(MAKE) 6 | 7 | END 8 | 9 | -------------------------------------------------------------------------------- /ngx_http_brotli_filter_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) CloudFlare, Inc. 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | 14 | typedef struct { 15 | ngx_flag_t enable; 16 | ngx_hash_t types; 17 | ngx_bufs_t bufs; 18 | ngx_int_t level; 19 | ssize_t min_length; 20 | ngx_array_t *types_keys; 21 | } ngx_http_brotli_conf_t; 22 | 23 | 24 | typedef struct { 25 | ngx_chain_t *in; 26 | ngx_chain_t *free; 27 | ngx_chain_t *busy; 28 | ngx_chain_t *out; 29 | ngx_chain_t **last_out; 30 | 31 | ngx_chain_t *copied; 32 | ngx_chain_t *copy_buf; 33 | 34 | ngx_buf_t *in_buf; 35 | ngx_buf_t *out_buf; 36 | ngx_int_t bufs; 37 | 38 | BrotliEncoderState *bro; 39 | 40 | uint8_t *input; 41 | uint8_t *output; 42 | uint8_t *next_in; 43 | uint8_t *next_out; 44 | size_t available_in; 45 | size_t available_out; 46 | 47 | ngx_http_request_t *request; 48 | 49 | BrotliEncoderOperation flush:2; 50 | unsigned redo:1; 51 | unsigned done:1; 52 | unsigned nomem:1; 53 | } ngx_http_brotli_ctx_t; 54 | 55 | 56 | static ngx_int_t ngx_http_brotli_filter_start(ngx_http_request_t *r, 57 | ngx_http_brotli_ctx_t *ctx); 58 | static ngx_int_t ngx_http_brotli_filter_add_data(ngx_http_request_t *r, 59 | ngx_http_brotli_ctx_t *ctx); 60 | static ngx_int_t ngx_http_brotli_filter_get_buf(ngx_http_request_t *r, 61 | ngx_http_brotli_ctx_t *ctx); 62 | static ngx_int_t ngx_http_brotli_filter_compress(ngx_http_request_t *r, 63 | ngx_http_brotli_ctx_t *ctx); 64 | static ngx_int_t ngx_http_brotli_filter_end(ngx_http_request_t *r, 65 | ngx_http_brotli_ctx_t *ctx); 66 | static void ngx_http_brotli_filter_free_copy_buf(ngx_http_request_t *r, 67 | ngx_http_brotli_ctx_t *ctx); 68 | 69 | static ngx_int_t ngx_http_brotli_filter_init(ngx_conf_t *cf); 70 | static void *ngx_http_brotli_create_conf(ngx_conf_t *cf); 71 | static char *ngx_http_brotli_merge_conf(ngx_conf_t *cf, 72 | void *parent, void *child); 73 | 74 | static ngx_conf_num_bounds_t ngx_http_brotli_comp_level_bounds = { 75 | ngx_conf_check_num_bounds, 1, 11 76 | }; 77 | 78 | static ngx_command_t ngx_http_brotli_filter_commands[] = { 79 | 80 | { ngx_string("brotli"), 81 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 82 | |NGX_CONF_FLAG, 83 | ngx_conf_set_flag_slot, 84 | NGX_HTTP_LOC_CONF_OFFSET, 85 | offsetof(ngx_http_brotli_conf_t, enable), 86 | NULL }, 87 | 88 | { ngx_string("brotli_types"), 89 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, 90 | ngx_http_types_slot, 91 | NGX_HTTP_LOC_CONF_OFFSET, 92 | offsetof(ngx_http_brotli_conf_t, types_keys), 93 | &ngx_http_html_default_types[0] }, 94 | 95 | { ngx_string("brotli_comp_level"), 96 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 97 | ngx_conf_set_num_slot, 98 | NGX_HTTP_LOC_CONF_OFFSET, 99 | offsetof(ngx_http_brotli_conf_t, level), 100 | &ngx_http_brotli_comp_level_bounds }, 101 | 102 | { ngx_string("brotli_min_length"), 103 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 104 | ngx_conf_set_size_slot, 105 | NGX_HTTP_LOC_CONF_OFFSET, 106 | offsetof(ngx_http_brotli_conf_t, min_length), 107 | NULL }, 108 | 109 | ngx_null_command 110 | }; 111 | 112 | 113 | static ngx_http_module_t ngx_http_brotli_filter_module_ctx = { 114 | NULL, /* preconfiguration */ 115 | ngx_http_brotli_filter_init, /* postconfiguration */ 116 | 117 | NULL, /* create main configuration */ 118 | NULL, /* init main configuration */ 119 | 120 | NULL, /* create server configuration */ 121 | NULL, /* merge server configuration */ 122 | 123 | ngx_http_brotli_create_conf, /* create location configuration */ 124 | ngx_http_brotli_merge_conf /* merge location configuration */ 125 | }; 126 | 127 | 128 | ngx_module_t ngx_http_brotli_filter_module = { 129 | NGX_MODULE_V1, 130 | &ngx_http_brotli_filter_module_ctx, /* module context */ 131 | ngx_http_brotli_filter_commands, /* module directives */ 132 | NGX_HTTP_MODULE, /* module type */ 133 | NULL, /* init master */ 134 | NULL, /* init module */ 135 | NULL, /* init process */ 136 | NULL, /* init thread */ 137 | NULL, /* exit thread */ 138 | NULL, /* exit process */ 139 | NULL, /* exit master */ 140 | NGX_MODULE_V1_PADDING 141 | }; 142 | 143 | 144 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 145 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 146 | 147 | static ngx_int_t 148 | accept_br(ngx_table_elt_t *ae) 149 | { 150 | size_t len; 151 | unsigned char *ptr; 152 | 153 | if (!ae) { 154 | return NGX_DECLINED; 155 | } 156 | 157 | if (ae->value.len < 2) { 158 | return NGX_DECLINED; 159 | } 160 | 161 | ptr = ae->value.data; 162 | len = ae->value.len; 163 | 164 | while (len >= 2) { 165 | 166 | len--; 167 | 168 | if (*ptr++ != 'b') { 169 | continue; 170 | } 171 | 172 | if (*ptr == 'r') { 173 | if (len == 1) { 174 | return NGX_OK; 175 | } 176 | 177 | if (*(ptr + 1) == ',' || *(ptr + 1) == ';' || *(ptr + 1) == ' ') { 178 | return NGX_OK; 179 | } 180 | } 181 | } 182 | 183 | return NGX_DECLINED; 184 | } 185 | 186 | 187 | static ngx_int_t 188 | ngx_http_brotli_header_filter(ngx_http_request_t *r) 189 | { 190 | ngx_table_elt_t *h; 191 | ngx_http_brotli_ctx_t *ctx; 192 | ngx_http_brotli_conf_t *conf; 193 | 194 | conf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module); 195 | 196 | if (!conf->enable 197 | || (r->headers_out.status != NGX_HTTP_OK 198 | && r->headers_out.status != NGX_HTTP_FORBIDDEN 199 | && r->headers_out.status != NGX_HTTP_NOT_FOUND) 200 | || (r->headers_out.content_length_n != -1 201 | && r->headers_out.content_length_n < conf->min_length) 202 | || ngx_http_test_content_type(r, &conf->types) == NULL 203 | || r->header_only) 204 | { 205 | return ngx_http_next_header_filter(r); 206 | } 207 | 208 | if (r->headers_out.content_encoding 209 | && r->headers_out.content_encoding->value.len) 210 | { 211 | return ngx_http_next_header_filter(r); 212 | } 213 | 214 | /* Check that brotli is supported. We do not check possible q value 215 | * if brotli is supported it takes precendence over gzip if size > 216 | * brotli_min_length */ 217 | if (accept_br(r->headers_in.accept_encoding) != NGX_OK) { 218 | return ngx_http_next_header_filter(r); 219 | } 220 | 221 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_brotli_ctx_t)); 222 | if (ctx == NULL) { 223 | return NGX_ERROR; 224 | } 225 | 226 | #if (NGX_HTTP_GZIP) 227 | r->gzip_vary = 1; 228 | /* Make sure gzip does not execute */ 229 | r->gzip_tested = 1; 230 | r->gzip_ok = 0; 231 | #endif 232 | 233 | ngx_http_set_ctx(r, ctx, ngx_http_brotli_filter_module); 234 | 235 | ctx->request = r; 236 | 237 | h = ngx_list_push(&r->headers_out.headers); 238 | if (h == NULL) { 239 | return NGX_ERROR; 240 | } 241 | 242 | h->hash = 1; 243 | ngx_str_set(&h->key, "Content-Encoding"); 244 | ngx_str_set(&h->value, "br"); 245 | r->headers_out.content_encoding = h; 246 | 247 | r->main_filter_need_in_memory = 1; 248 | 249 | ngx_http_clear_content_length(r); 250 | ngx_http_clear_accept_ranges(r); 251 | ngx_http_weak_etag(r); 252 | 253 | return ngx_http_next_header_filter(r); 254 | } 255 | 256 | 257 | /* The brotli body is almost identical to gzip body */ 258 | static ngx_int_t 259 | ngx_http_brotli_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 260 | { 261 | int rc; 262 | ngx_uint_t flush; 263 | ngx_chain_t *cl; 264 | ngx_http_brotli_ctx_t *ctx; 265 | 266 | ctx = ngx_http_get_module_ctx(r, ngx_http_brotli_filter_module); 267 | 268 | if (ctx == NULL || ctx->done || r->header_only) { 269 | return ngx_http_next_body_filter(r, in); 270 | } 271 | 272 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 273 | "http brotli filter"); 274 | 275 | if (ctx->bro == NULL) { 276 | if (ngx_http_brotli_filter_start(r, ctx) != NGX_OK) { 277 | goto failed; 278 | } 279 | } 280 | 281 | if (in) { 282 | if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { 283 | goto failed; 284 | } 285 | r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED; 286 | } 287 | 288 | if (ctx->nomem) { 289 | /* flush busy buffers */ 290 | if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) { 291 | goto failed; 292 | } 293 | 294 | cl = NULL; 295 | 296 | ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl, 297 | (ngx_buf_tag_t) &ngx_http_brotli_filter_module); 298 | ctx->nomem = 0; 299 | flush = 0; 300 | 301 | } else { 302 | flush = ctx->busy ? 1 : 0; 303 | } 304 | 305 | for ( ;; ) { 306 | 307 | /* cycle while we can write to a client */ 308 | 309 | for ( ;; ) { 310 | 311 | /* cycle while there is data to feed botli and ... */ 312 | 313 | rc = ngx_http_brotli_filter_add_data(r, ctx); 314 | 315 | if (rc == NGX_DECLINED) { 316 | break; 317 | } 318 | 319 | if (rc == NGX_AGAIN) { 320 | continue; 321 | } 322 | 323 | 324 | /* ... there are buffers to write brotli output */ 325 | 326 | rc = ngx_http_brotli_filter_get_buf(r, ctx); 327 | 328 | if (rc == NGX_DECLINED) { 329 | break; 330 | } 331 | 332 | if (rc == NGX_ERROR) { 333 | goto failed; 334 | } 335 | 336 | 337 | rc = ngx_http_brotli_filter_compress(r, ctx); 338 | 339 | if (rc == NGX_OK) { 340 | break; 341 | } 342 | 343 | if (rc == NGX_ERROR) { 344 | goto failed; 345 | } 346 | 347 | /* rc == NGX_AGAIN */ 348 | } 349 | 350 | if (ctx->out == NULL && !flush) { 351 | ngx_http_brotli_filter_free_copy_buf(r, ctx); 352 | return ctx->busy ? NGX_AGAIN : NGX_OK; 353 | } 354 | 355 | rc = ngx_http_next_body_filter(r, ctx->out); 356 | 357 | if (rc == NGX_ERROR) { 358 | goto failed; 359 | } 360 | 361 | ngx_http_brotli_filter_free_copy_buf(r, ctx); 362 | 363 | ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out, 364 | (ngx_buf_tag_t) &ngx_http_brotli_filter_module); 365 | ctx->last_out = &ctx->out; 366 | 367 | ctx->nomem = 0; 368 | flush = 0; 369 | 370 | if (ctx->done) { 371 | return rc; 372 | } 373 | } 374 | 375 | /* unreachable */ 376 | 377 | failed: 378 | 379 | ctx->done = 1; 380 | 381 | ngx_http_brotli_filter_free_copy_buf(r, ctx); 382 | BrotliEncoderDestroyInstance(ctx->bro); 383 | ctx->bro = NULL; 384 | 385 | return NGX_ERROR; 386 | } 387 | 388 | 389 | static ngx_int_t 390 | ngx_http_brotli_filter_start(ngx_http_request_t *r, 391 | ngx_http_brotli_ctx_t *ctx) 392 | { 393 | ngx_http_brotli_conf_t *conf; 394 | 395 | conf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module); 396 | 397 | ctx->bro = BrotliEncoderCreateInstance(NULL, NULL, NULL); 398 | if (ctx->bro == NULL) { 399 | return NGX_ERROR; 400 | } 401 | 402 | ctx->last_out = &ctx->out; 403 | BrotliEncoderSetParameter(ctx->bro, BROTLI_PARAM_QUALITY, conf->level); 404 | BrotliEncoderSetParameter(ctx->bro, BROTLI_PARAM_LGWIN, BROTLI_DEFAULT_WINDOW); 405 | ctx->input = NULL; 406 | ctx->output = NULL; 407 | ctx->next_in = NULL; 408 | ctx->next_out = NULL; 409 | ctx->available_in = 0; 410 | ctx->available_out = 0; 411 | 412 | return NGX_OK; 413 | } 414 | 415 | 416 | static ngx_int_t 417 | ngx_http_brotli_filter_add_data(ngx_http_request_t *r, ngx_http_brotli_ctx_t *ctx) 418 | { 419 | if (ctx->available_in || ctx->flush != BROTLI_OPERATION_PROCESS || ctx->redo) { 420 | return NGX_OK; 421 | } 422 | 423 | if (ctx->in == NULL) { 424 | return NGX_DECLINED; 425 | } 426 | 427 | if (ctx->copy_buf) { 428 | ctx->copy_buf->next = ctx->copied; 429 | ctx->copied = ctx->copy_buf; 430 | ctx->copy_buf = NULL; 431 | } 432 | 433 | ctx->in_buf = ctx->in->buf; 434 | 435 | if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_brotli_filter_module) { 436 | ctx->copy_buf = ctx->in; 437 | } 438 | 439 | ctx->in = ctx->in->next; 440 | 441 | ctx->next_in = ctx->in_buf->pos; 442 | ctx->available_in = ctx->in_buf->last - ctx->in_buf->pos; 443 | 444 | if (ctx->in_buf->last_buf) { 445 | ctx->flush = BROTLI_OPERATION_FINISH; 446 | 447 | } else if (ctx->in_buf->flush) { 448 | ctx->flush = BROTLI_OPERATION_FLUSH; 449 | } 450 | 451 | if (!ctx->available_in && ctx->flush == BROTLI_OPERATION_PROCESS) { 452 | return NGX_AGAIN; 453 | } 454 | 455 | return NGX_OK; 456 | } 457 | 458 | 459 | static ngx_int_t 460 | ngx_http_brotli_filter_get_buf(ngx_http_request_t *r, ngx_http_brotli_ctx_t *ctx) 461 | { 462 | ngx_http_brotli_conf_t *conf; 463 | 464 | if (ctx->available_out) { 465 | return NGX_OK; 466 | } 467 | 468 | conf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module); 469 | 470 | if (ctx->free) { 471 | ctx->out_buf = ctx->free->buf; 472 | ctx->free = ctx->free->next; 473 | 474 | } else if (ctx->bufs < conf->bufs.num) { 475 | 476 | ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size); 477 | if (ctx->out_buf == NULL) { 478 | return NGX_ERROR; 479 | } 480 | 481 | ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_brotli_filter_module; 482 | ctx->out_buf->recycled = 1; 483 | ctx->bufs++; 484 | 485 | } else { 486 | ctx->nomem = 1; 487 | return NGX_DECLINED; 488 | } 489 | 490 | ctx->next_out = ctx->out_buf->pos; 491 | ctx->available_out = conf->bufs.size; 492 | 493 | return NGX_OK; 494 | } 495 | 496 | 497 | static ngx_int_t 498 | ngx_http_brotli_filter_compress(ngx_http_request_t *r, ngx_http_brotli_ctx_t *ctx) 499 | { 500 | BROTLI_BOOL rc; 501 | ngx_buf_t *b; 502 | ngx_chain_t *cl; 503 | 504 | rc = BrotliEncoderCompressStream(ctx->bro, ctx->flush, &ctx->available_in, (const uint8_t **)&ctx->next_in, &ctx->available_out, &ctx->next_out, NULL); 505 | 506 | if (rc != BROTLI_TRUE) { 507 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 508 | "compress() failed: %d, %d", ctx->flush, rc); 509 | return NGX_ERROR; 510 | } 511 | 512 | if (ctx->next_in) { 513 | ctx->in_buf->pos = ctx->next_in; 514 | 515 | if (ctx->available_in == 0) { 516 | ctx->next_in = NULL; 517 | } 518 | } 519 | 520 | ctx->out_buf->last = ctx->next_out; 521 | 522 | if (ctx->available_out == 0) { 523 | 524 | /* brotli wants to output some more compressed data */ 525 | 526 | cl = ngx_alloc_chain_link(r->pool); 527 | if (cl == NULL) { 528 | return NGX_ERROR; 529 | } 530 | 531 | cl->buf = ctx->out_buf; 532 | cl->next = NULL; 533 | *ctx->last_out = cl; 534 | ctx->last_out = &cl->next; 535 | 536 | ctx->redo = 1; 537 | 538 | return NGX_AGAIN; 539 | } 540 | 541 | ctx->redo = 0; 542 | 543 | if (ctx->flush == BROTLI_OPERATION_FLUSH) { 544 | 545 | ctx->flush = BROTLI_OPERATION_PROCESS; 546 | 547 | cl = ngx_alloc_chain_link(r->pool); 548 | if (cl == NULL) { 549 | return NGX_ERROR; 550 | } 551 | 552 | b = ctx->out_buf; 553 | 554 | if (ngx_buf_size(b) == 0) { 555 | 556 | b = ngx_calloc_buf(ctx->request->pool); 557 | if (b == NULL) { 558 | return NGX_ERROR; 559 | } 560 | 561 | } else { 562 | ctx->available_out = 0; 563 | } 564 | 565 | b->flush = 1; 566 | 567 | cl->buf = b; 568 | cl->next = NULL; 569 | *ctx->last_out = cl; 570 | ctx->last_out = &cl->next; 571 | 572 | r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED; 573 | 574 | return NGX_OK; 575 | } 576 | 577 | if (BrotliEncoderIsFinished(ctx->bro)) { 578 | if (ngx_http_brotli_filter_end(r, ctx) != NGX_OK) { 579 | return NGX_ERROR; 580 | } 581 | return NGX_OK; 582 | } 583 | 584 | return NGX_AGAIN; 585 | } 586 | 587 | 588 | static ngx_int_t 589 | ngx_http_brotli_filter_end(ngx_http_request_t *r, 590 | ngx_http_brotli_ctx_t *ctx) 591 | { 592 | ngx_chain_t *cl; 593 | 594 | BrotliEncoderDestroyInstance(ctx->bro); 595 | ctx->bro = NULL; 596 | cl = ngx_alloc_chain_link(r->pool); 597 | 598 | if (cl == NULL) { 599 | return NGX_ERROR; 600 | } 601 | 602 | cl->buf = ctx->out_buf; 603 | cl->next = NULL; 604 | *ctx->last_out = cl; 605 | ctx->last_out = &cl->next; 606 | ctx->out_buf->last_buf = 1; 607 | 608 | ctx->done = 1; 609 | 610 | r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED; 611 | 612 | return NGX_OK; 613 | } 614 | 615 | 616 | static void 617 | ngx_http_brotli_filter_free_copy_buf(ngx_http_request_t *r, 618 | ngx_http_brotli_ctx_t *ctx) 619 | { 620 | ngx_chain_t *cl; 621 | 622 | for (cl = ctx->copied; cl; cl = cl->next) { 623 | ngx_pfree(r->pool, cl->buf->start); 624 | } 625 | 626 | ctx->copied = NULL; 627 | } 628 | 629 | 630 | static void * 631 | ngx_http_brotli_create_conf(ngx_conf_t *cf) 632 | { 633 | ngx_http_brotli_conf_t *conf; 634 | 635 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_brotli_conf_t)); 636 | if (conf == NULL) { 637 | return NULL; 638 | } 639 | 640 | /* 641 | * set by ngx_pcalloc(): 642 | * 643 | * conf->bufs.num = 0; 644 | * conf->types = { NULL }; 645 | * conf->types_keys = NULL; 646 | */ 647 | 648 | conf->enable = NGX_CONF_UNSET; 649 | conf->level = NGX_CONF_UNSET; 650 | conf->min_length = NGX_CONF_UNSET; 651 | 652 | return conf; 653 | } 654 | 655 | 656 | static char * 657 | ngx_http_brotli_merge_conf(ngx_conf_t *cf, void *parent, void *child) 658 | { 659 | ngx_http_brotli_conf_t *prev = parent; 660 | ngx_http_brotli_conf_t *conf = child; 661 | 662 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 663 | ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 664 | (128 * 1024) / ngx_pagesize, ngx_pagesize); 665 | 666 | ngx_conf_merge_value(conf->level, prev->level, 6); 667 | ngx_conf_merge_value(conf->min_length, prev->min_length, 2048); 668 | 669 | if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, 670 | &prev->types_keys, &prev->types, 671 | ngx_http_html_default_types) 672 | != NGX_OK) 673 | { 674 | return NGX_CONF_ERROR; 675 | } 676 | 677 | return NGX_CONF_OK; 678 | } 679 | 680 | 681 | static ngx_int_t 682 | ngx_http_brotli_filter_init(ngx_conf_t *cf) 683 | { 684 | ngx_http_next_header_filter = ngx_http_top_header_filter; 685 | ngx_http_top_header_filter = ngx_http_brotli_header_filter; 686 | 687 | ngx_http_next_body_filter = ngx_http_top_body_filter; 688 | ngx_http_top_body_filter = ngx_http_brotli_body_filter; 689 | 690 | return NGX_OK; 691 | } 692 | --------------------------------------------------------------------------------