3 * Copyright (C) Igor Sysoev
7 #include <ngx_config.h>
18 ngx_array_t *sub_lengths;
19 ngx_array_t *sub_values;
23 ngx_array_t *types_keys;
24 } ngx_http_sub_loc_conf_t;
30 } ngx_http_sub_state_e;
36 ngx_uint_t once; /* unsigned once:1 */
46 ngx_chain_t **last_out;
58 static ngx_int_t ngx_http_sub_output(ngx_http_request_t *r,
59 ngx_http_sub_ctx_t *ctx);
60 static ngx_int_t ngx_http_sub_parse(ngx_http_request_t *r,
61 ngx_http_sub_ctx_t *ctx);
63 static char * ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd,
65 static void *ngx_http_sub_create_conf(ngx_conf_t *cf);
66 static char *ngx_http_sub_merge_conf(ngx_conf_t *cf,
67 void *parent, void *child);
68 static ngx_int_t ngx_http_sub_filter_init(ngx_conf_t *cf);
71 static ngx_command_t ngx_http_sub_filter_commands[] = {
73 { ngx_string("sub_filter"),
74 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
76 NGX_HTTP_LOC_CONF_OFFSET,
80 { ngx_string("sub_filter_types"),
81 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
83 NGX_HTTP_LOC_CONF_OFFSET,
84 offsetof(ngx_http_sub_loc_conf_t, types_keys),
85 &ngx_http_html_default_types[0] },
87 { ngx_string("sub_filter_once"),
88 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
89 ngx_conf_set_flag_slot,
90 NGX_HTTP_LOC_CONF_OFFSET,
91 offsetof(ngx_http_sub_loc_conf_t, once),
98 static ngx_http_module_t ngx_http_sub_filter_module_ctx = {
99 NULL, /* preconfiguration */
100 ngx_http_sub_filter_init, /* postconfiguration */
102 NULL, /* create main configuration */
103 NULL, /* init main configuration */
105 NULL, /* create server configuration */
106 NULL, /* merge server configuration */
108 ngx_http_sub_create_conf, /* create location configuration */
109 ngx_http_sub_merge_conf /* merge location configuration */
113 ngx_module_t ngx_http_sub_filter_module = {
115 &ngx_http_sub_filter_module_ctx, /* module context */
116 ngx_http_sub_filter_commands, /* module directives */
117 NGX_HTTP_MODULE, /* module type */
118 NULL, /* init master */
119 NULL, /* init module */
120 NULL, /* init process */
121 NULL, /* init thread */
122 NULL, /* exit thread */
123 NULL, /* exit process */
124 NULL, /* exit master */
125 NGX_MODULE_V1_PADDING
129 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
130 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
134 ngx_http_sub_header_filter(ngx_http_request_t *r)
136 ngx_http_sub_ctx_t *ctx;
137 ngx_http_sub_loc_conf_t *slcf;
139 slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
141 if (slcf->match.len == 0
142 || r->headers_out.content_length_n == 0
143 || ngx_http_test_content_type(r, &slcf->types) == NULL)
145 return ngx_http_next_header_filter(r);
148 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_ctx_t));
153 ngx_http_set_ctx(r, ctx, ngx_http_sub_filter_module);
155 ctx->match = slcf->match;
156 ctx->last_out = &ctx->out;
157 ctx->sub = slcf->sub;
159 r->filter_need_in_memory = 1;
162 ngx_http_clear_content_length(r);
163 ngx_http_clear_last_modified(r);
166 return ngx_http_next_header_filter(r);
171 ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
176 ngx_http_sub_ctx_t *ctx;
177 ngx_http_sub_loc_conf_t *slcf;
179 ctx = ngx_http_get_module_ctx(r, ngx_http_sub_filter_module);
182 return ngx_http_next_body_filter(r, in);
188 && ctx->busy == NULL))
190 return ngx_http_next_body_filter(r, in);
193 if (ctx->once && (ctx->buf == NULL || ctx->in == NULL)) {
196 if (ngx_http_sub_output(r, ctx) == NGX_ERROR) {
201 return ngx_http_next_body_filter(r, in);
204 /* add the incoming chain to the chain ctx->in */
207 if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
212 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
213 "http sub filter \"%V\"", &r->uri);
215 while (ctx->in || ctx->buf) {
217 if (ctx->buf == NULL ){
218 ctx->buf = ctx->in->buf;
219 ctx->in = ctx->in->next;
220 ctx->pos = ctx->buf->pos;
223 if (ctx->state == sub_start_state) {
224 ctx->copy_start = ctx->pos;
225 ctx->copy_end = ctx->pos;
230 while (ctx->pos < ctx->buf->last) {
232 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
233 "saved: %d state: %d", ctx->saved, ctx->state);
235 rc = ngx_http_sub_parse(r, ctx);
237 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
238 "parse: %d, looked: %d %p-%p",
239 rc, ctx->looked, ctx->copy_start, ctx->copy_end);
241 if (rc == NGX_ERROR) {
245 if (ctx->copy_start != ctx->copy_end) {
247 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
248 "saved: %d", ctx->saved);
254 ctx->free = ctx->free->next;
256 ngx_memzero(b, sizeof(ngx_buf_t));
259 b = ngx_calloc_buf(r->pool);
264 cl = ngx_alloc_chain_link(r->pool);
273 b->pos = ctx->match.data;
274 b->last = ctx->match.data + ctx->saved;
277 ctx->last_out = &cl->next;
284 ctx->free = ctx->free->next;
288 b = ngx_alloc_buf(r->pool);
293 cl = ngx_alloc_chain_link(r->pool);
301 ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
303 b->pos = ctx->copy_start;
304 b->last = ctx->copy_end;
310 b->file_last = b->file_pos + (b->last - ctx->buf->pos);
311 b->file_pos += b->pos - ctx->buf->pos;
316 ctx->last_out = &cl->next;
319 if (ctx->state == sub_start_state) {
320 ctx->copy_start = ctx->pos;
321 ctx->copy_end = ctx->pos;
324 ctx->copy_start = NULL;
325 ctx->copy_end = NULL;
328 if (rc == NGX_AGAIN) {
335 b = ngx_calloc_buf(r->pool);
340 cl = ngx_alloc_chain_link(r->pool);
345 slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
347 if (ctx->sub.data == NULL) {
349 if (ngx_http_script_run(r, &ctx->sub, slcf->sub_lengths->elts,
350 0, slcf->sub_values->elts)
359 b->pos = ctx->sub.data;
360 b->last = ctx->sub.data + ctx->sub.len;
369 ctx->last_out = &cl->next;
371 ctx->once = slcf->once;
376 if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
380 ctx->free = ctx->free->next;
382 ngx_memzero(b, sizeof(ngx_buf_t));
385 b = ngx_calloc_buf(r->pool);
390 cl = ngx_alloc_chain_link(r->pool);
402 ctx->last_out = &cl->next;
405 b->last_buf = ctx->buf->last_buf;
406 b->shadow = ctx->buf;
408 b->recycled = ctx->buf->recycled;
413 ctx->saved = ctx->looked;
416 if (ctx->out == NULL && ctx->busy == NULL) {
420 return ngx_http_sub_output(r, ctx);
425 ngx_http_sub_output(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
433 for (cl = ctx->out; cl; cl = cl->next) {
434 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
435 "sub out: %p %p", cl->buf, cl->buf->pos);
437 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
438 "the same buf was used in sub");
446 rc = ngx_http_next_body_filter(r, ctx->out);
448 if (ctx->busy == NULL) {
449 ctx->busy = ctx->out;
452 for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
457 ctx->last_out = &ctx->out;
464 if (ngx_buf_size(b) != 0) {
469 b->shadow->pos = b->shadow->last;
472 ctx->busy = cl->next;
474 if (ngx_buf_in_memory(b) || b->in_file) {
475 /* add data bufs only to the free buf chain */
477 cl->next = ctx->free;
482 if (ctx->in || ctx->buf) {
483 r->buffered |= NGX_HTTP_SUB_BUFFERED;
486 r->buffered &= ~NGX_HTTP_SUB_BUFFERED;
494 ngx_http_sub_parse(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
496 u_char *p, *last, *copy_end, ch, match;
498 ngx_http_sub_state_e state;
501 ctx->copy_start = ctx->pos;
502 ctx->copy_end = ctx->buf->last;
503 ctx->pos = ctx->buf->last;
506 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "once");
512 looked = ctx->looked;
513 last = ctx->buf->last;
514 copy_end = ctx->copy_end;
516 for (p = ctx->pos; p < last; p++) {
519 ch = ngx_tolower(ch);
521 if (state == sub_start_state) {
525 match = ctx->match.data[0];
531 state = sub_match_state;
541 ch = ngx_tolower(ch);
546 ctx->looked = looked;
549 if (ctx->copy_start == NULL) {
550 ctx->copy_start = ctx->buf->pos;
560 /* state == sub_match_state */
562 if (ch == ctx->match.data[looked]) {
565 if (looked == ctx->match.len) {
566 if ((size_t) (p - ctx->pos) < looked) {
570 ctx->state = sub_start_state;
573 ctx->copy_end = copy_end;
575 if (ctx->copy_start == NULL && copy_end) {
576 ctx->copy_start = ctx->buf->pos;
582 } else if (ch == ctx->match.data[0]) {
589 state = sub_start_state;
595 ctx->looked = looked;
597 ctx->copy_end = (state == sub_start_state) ? p : copy_end;
599 if (ctx->copy_start == NULL && ctx->copy_end) {
600 ctx->copy_start = ctx->buf->pos;
608 ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
610 ngx_http_sub_loc_conf_t *slcf = conf;
614 ngx_http_script_compile_t sc;
616 if (slcf->match.len) {
617 return "is duplicate";
620 value = cf->args->elts;
622 ngx_strlow(value[1].data, value[1].data, value[1].len);
624 slcf->match = value[1];
626 n = ngx_http_script_variables_count(&value[2]);
629 slcf->sub = value[2];
633 ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
636 sc.source = &value[2];
637 sc.lengths = &slcf->sub_lengths;
638 sc.values = &slcf->sub_values;
640 sc.complete_lengths = 1;
641 sc.complete_values = 1;
643 if (ngx_http_script_compile(&sc) != NGX_OK) {
644 return NGX_CONF_ERROR;
652 ngx_http_sub_create_conf(ngx_conf_t *cf)
654 ngx_http_sub_loc_conf_t *slcf;
656 slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sub_loc_conf_t));
658 return NGX_CONF_ERROR;
662 * set by ngx_pcalloc():
664 * conf->match = { 0, NULL };
665 * conf->sub = { 0, NULL };
666 * conf->sub_lengths = NULL;
667 * conf->sub_values = NULL;
668 * conf->types = { NULL };
669 * conf->types_keys = NULL;
672 slcf->once = NGX_CONF_UNSET;
679 ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child)
681 ngx_http_sub_loc_conf_t *prev = parent;
682 ngx_http_sub_loc_conf_t *conf = child;
684 ngx_conf_merge_value(conf->once, prev->once, 1);
685 ngx_conf_merge_str_value(conf->match, prev->match, "");
687 if (conf->sub.data == NULL && conf->sub_lengths == NULL) {
688 conf->sub = prev->sub;
689 conf->sub_lengths = prev->sub_lengths;
690 conf->sub_values = prev->sub_values;
693 if (ngx_http_merge_types(cf, conf->types_keys, &conf->types,
694 prev->types_keys, &prev->types,
695 ngx_http_html_default_types)
698 return NGX_CONF_ERROR;
706 ngx_http_sub_filter_init(ngx_conf_t *cf)
708 ngx_http_next_header_filter = ngx_http_top_header_filter;
709 ngx_http_top_header_filter = ngx_http_sub_header_filter;
711 ngx_http_next_body_filter = ngx_http_top_body_filter;
712 ngx_http_top_body_filter = ngx_http_sub_body_filter;