1 /*
2 * Author: Weibin Yao(yaoweibin@gmail.com)
3 *
4 * Licence: This module could be distributed under the
5 * same terms as Nginx itself.
6 */
7
8
9 #include <ngx_config.h>
10 #include <ngx_core.h>
11 #include <ngx_http.h>
12 #include <nginx.h>
13
14
15 #if (NGX_DEBUG)
16 #define SUBS_DEBUG 1
17 #else
18 #define SUBS_DEBUG 0
19 #endif
20
21
22 #ifndef NGX_HTTP_MAX_CAPTURES
23 #define NGX_HTTP_MAX_CAPTURES 9
24 #endif
25
26
27 #define ngx_buffer_init(b) b->pos = b->last = b->start;
28
29
30 typedef struct {
31 ngx_flag_t once;
32 ngx_flag_t regex;
33 ngx_flag_t insensitive;
34
35 /* If it has captured variables? */
36 ngx_flag_t has_captured;
37
38 ngx_str_t match;
39 ngx_array_t *match_lengths;
40 ngx_array_t *match_values;
41 #if (NGX_PCRE)
42 ngx_regex_t *match_regex;
43 int *captures;
44 ngx_int_t ncaptures;
45 #endif
46
47 ngx_str_t sub;
48 ngx_array_t *sub_lengths;
49 ngx_array_t *sub_values;
50
51 unsigned matched;
52 } sub_pair_t;
53
54
55 typedef struct {
56 ngx_hash_t types;
57 ngx_array_t *sub_pairs; /* array of sub_pair_t */
58 ngx_array_t *types_keys; /* array of ngx_hash_key_t */
59 ngx_array_t *bypass; /* array of ngx_http_complex_value_t */
60 size_t line_buffer_size;
61 ngx_bufs_t bufs;
62 } ngx_http_subs_loc_conf_t;
63
64
65 typedef struct {
66 ngx_array_t *sub_pairs; /* array of sub_pair_t */
67
68 ngx_chain_t *in;
69
70 /* the line input buffer before substitution */
71 ngx_buf_t *line_in;
72 /* the line destination buffer after substitution */
73 ngx_buf_t *line_dst;
74
75 /* the last output buffer */
76 ngx_buf_t *out_buf;
77 /* point to the last output chain's next chain */
78 ngx_chain_t **last_out;
79 ngx_chain_t *out;
80
81 ngx_chain_t *busy;
82
83 /* the freed chain buffers. */
84 ngx_chain_t *free;
85
86 ngx_int_t bufs;
87
88 unsigned last;
89
90 } ngx_http_subs_ctx_t;
91
92
93 static ngx_int_t ngx_http_subs_header_filter(ngx_http_request_t *r);
94 static ngx_int_t ngx_http_subs_init_context(ngx_http_request_t *r);
95
96 static ngx_int_t ngx_http_subs_body_filter(ngx_http_request_t *r,
97 ngx_chain_t *in);
98 static ngx_int_t ngx_http_subs_body_filter_init_context(ngx_http_request_t *r,
99 ngx_chain_t *in);
100 static ngx_int_t ngx_http_subs_body_filter_process_buffer(ngx_http_request_t *r,
101 ngx_buf_t *b);
102 static ngx_int_t ngx_http_subs_match(ngx_http_request_t *r,
103 ngx_http_subs_ctx_t *ctx);
104 #if (NGX_PCRE)
105 static ngx_int_t ngx_http_subs_match_regex_substituion(ngx_http_request_t *r,
106 sub_pair_t *pair, ngx_buf_t *b, ngx_buf_t *dst);
107 #endif
108 static ngx_int_t ngx_http_subs_match_fix_substituion(ngx_http_request_t *r,
109 sub_pair_t *pair, ngx_buf_t *b, ngx_buf_t *dst);
110 static ngx_buf_t * buffer_append_string(ngx_buf_t *b, u_char *s, size_t len,
111 ngx_pool_t *pool);
112 static ngx_int_t ngx_http_subs_out_chain_append(ngx_http_request_t *r,
113 ngx_http_subs_ctx_t *ctx, ngx_buf_t *b);
114 static ngx_int_t ngx_http_subs_get_chain_buf(ngx_http_request_t *r,
115 ngx_http_subs_ctx_t *ctx);
116 static ngx_int_t ngx_http_subs_output(ngx_http_request_t *r,
117 ngx_http_subs_ctx_t *ctx, ngx_chain_t *in);
118
119 static char * ngx_http_subs_filter(ngx_conf_t *cf, ngx_command_t *cmd,
120 void *conf);
121 static ngx_int_t ngx_http_subs_filter_regex_compile(sub_pair_t *pair,
122 ngx_http_script_compile_t *sc, ngx_conf_t *cf);
123
124
125 static void *ngx_http_subs_create_conf(ngx_conf_t *cf);
126 static char *ngx_http_subs_merge_conf(ngx_conf_t *cf, void *parent,
127 void *child);
128
129 static ngx_int_t ngx_http_subs_filter_init(ngx_conf_t *cf);
130
131 #if (NGX_PCRE)
132 static ngx_int_t ngx_http_subs_regex_capture_count(ngx_regex_t *re);
133 #endif
134
135
136 static ngx_command_t ngx_http_subs_filter_commands[] = {
137
138 { ngx_string("subs_filter"),
139 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
140 ngx_http_subs_filter,
141 NGX_HTTP_LOC_CONF_OFFSET,
142 0,
143 NULL },
144
145 { ngx_string("subs_filter_bypass"),
146 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
147 ngx_http_set_predicate_slot,
148 NGX_HTTP_LOC_CONF_OFFSET,
149 offsetof(ngx_http_subs_loc_conf_t, bypass),
150 NULL },
151
152 { ngx_string("subs_filter_types"),
153 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
154 ngx_http_types_slot,
155 NGX_HTTP_LOC_CONF_OFFSET,
156 offsetof(ngx_http_subs_loc_conf_t, types_keys),
157 &ngx_http_html_default_types[0] },
158
159 { ngx_string("subs_line_buffer_size"),
160 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
161 ngx_conf_set_size_slot,
162 NGX_HTTP_LOC_CONF_OFFSET,
163 offsetof(ngx_http_subs_loc_conf_t, line_buffer_size),
164 NULL },
165
166 { ngx_string("subs_buffers"),
167 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
168 ngx_conf_set_bufs_slot,
169 NGX_HTTP_LOC_CONF_OFFSET,
170 offsetof(ngx_http_subs_loc_conf_t, bufs),
171 NULL },
172
173 ngx_null_command
174 };
175
176
177 static ngx_http_module_t ngx_http_subs_filter_module_ctx = {
178 NULL, /* preconfiguration */
179 ngx_http_subs_filter_init, /* postconfiguration */
180
181 NULL, /* create main configuration */
182 NULL, /* init main configuration */
183
184 NULL, /* create server configuration */
185 NULL, /* merge server configuration */
186
187 ngx_http_subs_create_conf, /* create location configuration */
188 ngx_http_subs_merge_conf /* merge location configuration */
189 };
190
191
192 ngx_module_t ngx_http_subs_filter_module = {
193 NGX_MODULE_V1,
194 &ngx_http_subs_filter_module_ctx, /* module context */
195 ngx_http_subs_filter_commands, /* module directives */
196 NGX_HTTP_MODULE, /* module type */
197 NULL, /* init master */
198 NULL, /* init module */
199 NULL, /* init process */
200 NULL, /* init thread */
201 NULL, /* exit thread */
202 NULL, /* exit process */
203 NULL, /* exit master */
204 NGX_MODULE_V1_PADDING
205 };
206
207
208 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
209 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
210
211 extern volatile ngx_cycle_t *ngx_cycle;
212
213
214 static ngx_int_t
ngx_http_subs_header_filter(ngx_http_request_t * r)215 ngx_http_subs_header_filter(ngx_http_request_t *r)
216 {
217 ngx_http_subs_loc_conf_t *slcf;
218
219 slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
220
221 if (slcf->sub_pairs->nelts == 0
222 || r->header_only
223 || r->headers_out.content_type.len == 0
224 || r->headers_out.content_length_n == 0)
225 {
226 return ngx_http_next_header_filter(r);
227 }
228
229 if (ngx_http_test_content_type(r, &slcf->types) == NULL) {
230 return ngx_http_next_header_filter(r);
231 }
232
233 switch (ngx_http_test_predicates(r, slcf->bypass)) {
234
235 case NGX_ERROR:
236 /*pass through*/
237
238 case NGX_DECLINED:
239
240 return ngx_http_next_header_filter(r);
241
242 default: /* NGX_OK */
243 break;
244 }
245
246 /* Don't do substitution with the compressed content */
247 if (r->headers_out.content_encoding
248 && r->headers_out.content_encoding->value.len
249 && ngx_strncasecmp(r->headers_out.content_encoding->value.data,
250 (u_char *) "identity", 8) != 0) {
251
252 ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
253 "http subs filter header ignored, this may be a "
254 "compressed response.");
255
256 return ngx_http_next_header_filter(r);
257 }
258
259 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
260 "http subs filter header \"%V\"", &r->uri);
261
262 if (ngx_http_subs_init_context(r) == NGX_ERROR) {
263 return NGX_ERROR;
264 }
265
266 r->filter_need_in_memory = 1;
267
268 if (r == r->main) {
269 ngx_http_clear_content_length(r);
270 ngx_http_clear_last_modified(r);
271 }
272
273 return ngx_http_next_header_filter(r);
274 }
275
276
277 static ngx_int_t
ngx_http_subs_init_context(ngx_http_request_t * r)278 ngx_http_subs_init_context(ngx_http_request_t *r)
279 {
280 ngx_uint_t i;
281 sub_pair_t *src_pair, *dst_pair;
282 ngx_http_subs_ctx_t *ctx;
283 ngx_http_subs_loc_conf_t *slcf;
284
285 slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
286
287 /* Everything in ctx is NULL or 0. */
288 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_subs_ctx_t));
289 if (ctx == NULL) {
290 return NGX_ERROR;
291 }
292
293 ngx_http_set_ctx(r, ctx, ngx_http_subs_filter_module);
294
295 ctx->sub_pairs = ngx_array_create(r->pool, slcf->sub_pairs->nelts,
296 sizeof(sub_pair_t));
297 if (slcf->sub_pairs == NULL) {
298 return NGX_ERROR;
299 }
300
301 /* Deep copy sub_pairs from slcf to ctx, matched and captures need it */
302 src_pair = (sub_pair_t *) slcf->sub_pairs->elts;
303
304 for (i = 0; i < slcf->sub_pairs->nelts; i++) {
305
306 dst_pair = ngx_array_push(ctx->sub_pairs);
307 if (dst_pair == NULL) {
308 return NGX_ERROR;
309 }
310
311 ngx_memcpy(dst_pair, src_pair + i, sizeof(sub_pair_t));
312 }
313
314 if (ctx->line_in == NULL) {
315
316 ctx->line_in = ngx_create_temp_buf(r->pool, slcf->line_buffer_size);
317 if (ctx->line_in == NULL) {
318 return NGX_ERROR;
319 }
320 }
321
322 if (ctx->line_dst == NULL) {
323
324 ctx->line_dst = ngx_create_temp_buf(r->pool, slcf->line_buffer_size);
325 if (ctx->line_dst == NULL) {
326 return NGX_ERROR;
327 }
328 }
329
330 return NGX_OK;
331 }
332
333
334 static ngx_int_t
ngx_http_subs_body_filter(ngx_http_request_t * r,ngx_chain_t * in)335 ngx_http_subs_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
336 {
337 ngx_int_t rc;
338 ngx_log_t *log;
339 ngx_chain_t *cl, *temp;
340 ngx_http_subs_ctx_t *ctx;
341 ngx_http_subs_loc_conf_t *slcf;
342
343 log = r->connection->log;
344
345 slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
346 if (slcf == NULL) {
347 return ngx_http_next_body_filter(r, in);
348 }
349
350 ctx = ngx_http_get_module_ctx(r, ngx_http_subs_filter_module);
351 if (ctx == NULL) {
352 return ngx_http_next_body_filter(r, in);
353 }
354
355 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
356 "http subs filter \"%V\"", &r->uri);
357
358 if (in == NULL && ctx->busy == NULL) {
359 return ngx_http_next_body_filter(r, in);
360 }
361
362 if (ngx_http_subs_body_filter_init_context(r, in) != NGX_OK){
363 goto failed;
364 }
365
366 for (cl = ctx->in; cl; cl = cl->next) {
367
368 if (cl->buf->last_buf || cl->buf->last_in_chain){
369 ctx->last = 1;
370 }
371
372 /* TODO: check the flush flag */
373 rc = ngx_http_subs_body_filter_process_buffer(r, cl->buf);
374
375 if (rc == NGX_DECLINED) {
376 continue;
377 } else if (rc == NGX_ERROR) {
378 goto failed;
379 }
380
381 if (cl->next != NULL) {
382 continue;
383 }
384
385 if (ctx->last) {
386
387 /* copy line_in to ctx->out. */
388 if (ngx_buf_size(ctx->line_in) > 0) {
389
390 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
391 "[subs_filter] Lost last linefeed, output anyway.");
392
393 if (ngx_http_subs_out_chain_append(r, ctx, ctx->line_in)
394 != NGX_OK) {
395 goto failed;
396 }
397 }
398
399 if (ctx->out_buf == NULL) {
400
401 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
402 "[subs_filter] The last buffer is zero size.");
403
404 /*
405 * This is a zero buffer, it should not be set the temporary
406 * or memory flag
407 * */
408 ctx->out_buf = ngx_calloc_buf(r->pool);
409 if (ctx->out_buf == NULL) {
410 goto failed;
411 }
412
413 ctx->out_buf->sync = 1;
414
415 temp = ngx_alloc_chain_link(r->pool);
416 if (temp == NULL) {
417 goto failed;
418 }
419
420 temp->buf = ctx->out_buf;
421 temp->next = NULL;
422
423 *ctx->last_out = temp;
424 ctx->last_out = &temp->next;
425 }
426
427 ctx->out_buf->last_buf = (r == r->main) ? 1 : 0;
428 ctx->out_buf->last_in_chain = cl->buf->last_in_chain;
429
430 break;
431 }
432 }
433
434 /* It doesn't output anything, return */
435 if ((ctx->out == NULL) && (ctx->busy == NULL)) {
436 return NGX_OK;
437 }
438
439 return ngx_http_subs_output(r, ctx, in);
440
441 failed:
442
443 ngx_log_error(NGX_LOG_ERR, log, 0,
444 "[subs_filter] ngx_http_subs_body_filter error.");
445
446 return NGX_ERROR;
447 }
448
449
450 static ngx_int_t
ngx_http_subs_body_filter_init_context(ngx_http_request_t * r,ngx_chain_t * in)451 ngx_http_subs_body_filter_init_context(ngx_http_request_t *r, ngx_chain_t *in)
452 {
453 ngx_http_subs_ctx_t *ctx;
454
455 ctx = ngx_http_get_module_ctx(r, ngx_http_subs_filter_module);
456
457 r->connection->buffered |= NGX_HTTP_SUB_BUFFERED;
458
459 ctx->in = NULL;
460
461 if (in) {
462 if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
463 return NGX_ERROR;
464 }
465 }
466
467 #if SUBS_DEBUG
468 if (ngx_buf_size(ctx->line_in) > 0) {
469 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
470 "subs line in buffer: %p, size:%uz",
471 ctx->line_in, ngx_buf_size(ctx->line_in));
472 }
473 #endif
474
475 #if SUBS_DEBUG
476 ngx_chain_t *cl;
477
478 for (cl = ctx->in; cl; cl = cl->next) {
479 if (cl->buf) {
480 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
481 "subs in buffer:%p, size:%uz, "
482 "flush:%d, last_buf:%d",
483 cl->buf, ngx_buf_size(cl->buf),
484 cl->buf->flush, cl->buf->last_buf);
485 }
486 }
487 #endif
488
489 ctx->last_out = &ctx->out;
490 ctx->out_buf = NULL;
491
492 return NGX_OK;
493 }
494
495
496 static ngx_int_t
ngx_http_subs_body_filter_process_buffer(ngx_http_request_t * r,ngx_buf_t * b)497 ngx_http_subs_body_filter_process_buffer(ngx_http_request_t *r, ngx_buf_t *b)
498 {
499 u_char *p, *last, *linefeed;
500 ngx_int_t len, rc;
501 ngx_http_subs_ctx_t *ctx;
502
503 ctx = ngx_http_get_module_ctx(r, ngx_http_subs_filter_module);
504
505 if (b == NULL) {
506 return NGX_DECLINED;
507 }
508
509 p = b->pos;
510 last = b->last;
511 b->pos = b->last;
512
513 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
514 "subs process in buffer: %p %uz, line_in buffer: %p %uz",
515 b, last - p,
516 ctx->line_in, ngx_buf_size(ctx->line_in));
517
518 if ((last - p) == 0 && ngx_buf_size(ctx->line_in) == 0){
519 return NGX_OK;
520 }
521
522 if ((last - p) == 0 && ngx_buf_size(ctx->line_in) && ctx->last) {
523
524 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
525 "the last zero buffer, try to do substitution");
526
527 rc = ngx_http_subs_match(r, ctx);
528 if (rc < 0) {
529 return NGX_ERROR;
530 }
531
532 return NGX_OK;
533 }
534
535 while (p < last) {
536
537 linefeed = memchr(p, LF, last - p);
538
539 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "find linefeed: %p",
540 linefeed);
541
542 if (linefeed == NULL) {
543
544 if (ctx->last) {
545 linefeed = last - 1;
546 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
547 "the last buffer, not find linefeed");
548 }
549 }
550
551 if (linefeed) {
552
553 len = linefeed - p + 1;
554
555 if (buffer_append_string(ctx->line_in, p, len, r->pool) == NULL) {
556 return NGX_ERROR;
557 }
558
559 p += len;
560
561 rc = ngx_http_subs_match(r, ctx);
562 if (rc < 0) {
563 return NGX_ERROR;
564 }
565
566 } else {
567
568 /* Not find linefeed in this chain, save the left data to line_in */
569 if (buffer_append_string(ctx->line_in, p, last - p, r->pool)
570 == NULL) {
571 return NGX_ERROR;
572 }
573
574 break;
575 }
576 }
577
578 return NGX_OK;
579 }
580
581
582 /*
583 * Do the substitutions from ctx->line_in
584 * and output the chain buffers to ctx->out
585 * */
586 static ngx_int_t
ngx_http_subs_match(ngx_http_request_t * r,ngx_http_subs_ctx_t * ctx)587 ngx_http_subs_match(ngx_http_request_t *r, ngx_http_subs_ctx_t *ctx)
588 {
589 ngx_buf_t *src, *dst, *temp;
590 ngx_log_t *log;
591 ngx_int_t count, match_count;
592 sub_pair_t *pairs, *pair;
593 ngx_uint_t i;
594
595 count = 0;
596 match_count = 0;
597
598 log = r->connection->log;
599
600 src = ctx->line_in;
601 dst = ctx->line_dst;
602
603 pairs = (sub_pair_t *) ctx->sub_pairs->elts;
604 for (i = 0; i < ctx->sub_pairs->nelts; i++) {
605
606 pair = &pairs[i];
607
608 if (!pair->has_captured) {
609 if (pair->sub.data == NULL) {
610 if (ngx_http_script_run(r, &pair->sub, pair->sub_lengths->elts,
611 0, pair->sub_values->elts) == NULL)
612 {
613 goto failed;
614 }
615 }
616
617 } else {
618 pair->sub.data = NULL;
619 pair->sub.len = 0;
620 }
621
622 /* exchange the src and dst buffer */
623 if (dst->pos != dst->last) {
624
625 temp = src;
626 src = dst;
627 dst = temp;
628
629 ngx_buffer_init(dst);
630 }
631
632 if ((!pair->regex)
633 && ((ngx_uint_t)(src->last - src->pos) < pair->match.len)) {
634 continue;
635 }
636
637 if (pair->once && pair->matched) {
638 continue;
639 }
640
641 if (pair->sub.data == NULL && !pair->has_captured) {
642
643 if (ngx_http_script_run(r, &pair->sub, pair->sub_lengths->elts, 0,
644 pair->sub_values->elts) == NULL)
645 {
646 goto failed;
647 }
648 }
649
650 /* regex substitution */
651 if (pair->regex) {
652 #if (NGX_PCRE)
653 count = ngx_http_subs_match_regex_substituion(r, pair, src, dst);
654 if (count == NGX_ERROR) {
655 goto failed;
656 }
657 #endif
658 } else {
659
660 if (pair->match.data == NULL) {
661 if (ngx_http_script_run(r, &pair->match,
662 pair->match_lengths->elts, 0,
663 pair->match_values->elts) == NULL)
664 {
665 goto failed;
666 }
667 }
668
669 /* fixed string substituion */
670 count = ngx_http_subs_match_fix_substituion(r, pair, src, dst);
671 if (count == NGX_ERROR) {
672 goto failed;
673 }
674 }
675
676 /* no match. */
677 if (count == 0){
678 continue;
679 }
680
681 if (src->pos < src->last) {
682
683 if (buffer_append_string(dst, src->pos, src->last - src->pos,
684 r->pool) == NULL) {
685 goto failed;
686 }
687
688 src->pos = src->last;
689 }
690
691 /* match */
692 match_count += count;
693 }
694
695 /* no match last time */
696 if (dst->pos == dst->last){
697 dst = src;
698 }
699
700 if (ngx_http_subs_out_chain_append(r, ctx, dst) != NGX_OK) {
701 goto failed;
702 }
703
704 ngx_buffer_init(ctx->line_in);
705 ngx_buffer_init(ctx->line_dst);
706
707 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "match counts: %i", match_count);
708
709 return match_count;
710
711 failed:
712
713 ngx_log_error(NGX_LOG_ERR, log, 0,
714 "[subs_filter] ngx_http_subs_match error.");
715
716 return -1;
717 }
718
719
720 #if (NGX_PCRE)
721 static ngx_int_t
ngx_http_subs_match_regex_substituion(ngx_http_request_t * r,sub_pair_t * pair,ngx_buf_t * b,ngx_buf_t * dst)722 ngx_http_subs_match_regex_substituion(ngx_http_request_t *r, sub_pair_t *pair,
723 ngx_buf_t *b, ngx_buf_t *dst)
724 {
725 ngx_str_t line;
726 ngx_log_t *log;
727 ngx_int_t rc, count = 0;
728
729 log = r->connection->log;
730
731 if (pair->captures == NULL || pair->ncaptures == 0) {
732 pair->ncaptures = (NGX_HTTP_MAX_CAPTURES + 1) * 3;
733 pair->captures = ngx_palloc(r->pool, pair->ncaptures * sizeof(int));
734 if (pair->captures == NULL) {
735 return NGX_ERROR;
736 }
737 }
738
739 while (b->pos < b->last) {
740
741 if (pair->once && pair->matched) {
742 break;
743 }
744
745 line.data = b->pos;
746 line.len = b->last - b->pos;
747
748 rc = ngx_regex_exec(pair->match_regex, &line,
749 (int *) pair->captures, pair->ncaptures);
750
751 if (rc == NGX_REGEX_NO_MATCHED) {
752 break;
753
754 } else if(rc < 0) {
755 ngx_log_error(NGX_LOG_ERR, log, 0,
756 ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
757 rc, &line, &pair->match);
758
759 return NGX_ERROR;
760 }
761
762 pair->matched++;
763 count++;
764
765 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0,
766 "regex match:%i, start:%d, end:%d ",
767 rc, pair->captures[0], pair->captures[1]);
768
769 if (pair->has_captured) {
770 r->captures = pair->captures;
771 r->ncaptures = pair->ncaptures;
772 r->captures_data = line.data;
773
774 if (ngx_http_script_run(r, &pair->sub, pair->sub_lengths->elts, 0,
775 pair->sub_values->elts) == NULL)
776 {
777 ngx_log_error(NGX_LOG_ALERT, log, 0,
778 "[subs_filter] ngx_http_script_run error.");
779 return NGX_ERROR;
780 }
781 }
782
783 if (buffer_append_string(dst, b->pos, pair->captures[0],
784 r->pool) == NULL) {
785 return NGX_ERROR;
786 }
787
788 if (buffer_append_string(dst, pair->sub.data, pair->sub.len,
789 r->pool) == NULL) {
790 return NGX_ERROR;
791 }
792
793 b->pos = b->pos + pair->captures[1];
794 }
795
796 return count;
797 }
798 #endif
799
800
801 /*
802 * Thanks to Laurent Ghigonis
803 * Taken from FreeBSD
804 * Find the first occurrence of the byte string s in byte string l.
805 */
806 static void *
subs_memmem(const void * l,size_t l_len,const void * s,size_t s_len)807 subs_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
808 {
809 register char *cur, *last;
810 const char *cl = (const char *)l;
811 const char *cs = (const char *)s;
812
813 /* we need something to compare */
814 if (l_len == 0 || s_len == 0) {
815 return NULL;
816 }
817
818 /* "s" must be smaller or equal to "l" */
819 if (l_len < s_len) {
820 return NULL;
821 }
822
823 /* special case where s_len == 1 */
824 if (s_len == 1) {
825 return memchr(l, (int)*cs, l_len);
826 }
827
828 /* the last position where its possible to find "s" in "l" */
829 last = (char *)cl + l_len - s_len;
830
831 for (cur = (char *)cl; cur <= last; cur++) {
832 if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) {
833 return cur;
834 }
835 }
836
837 return NULL;
838 }
839
840
841 static ngx_int_t
ngx_http_subs_match_fix_substituion(ngx_http_request_t * r,sub_pair_t * pair,ngx_buf_t * b,ngx_buf_t * dst)842 ngx_http_subs_match_fix_substituion(ngx_http_request_t *r,
843 sub_pair_t *pair, ngx_buf_t *b, ngx_buf_t *dst)
844 {
845 u_char *sub_start;
846 ngx_int_t count = 0;
847
848 while(b->pos < b->last)
849 {
850 if (pair->once && pair->matched) {
851 break;
852 }
853 if (pair->insensitive) {
854 sub_start = ngx_strlcasestrn(b->pos, b->last,
855 pair->match.data, pair->match.len - 1);
856 } else {
857 sub_start = subs_memmem(b->pos, b->last - b->pos,
858 pair->match.data, pair->match.len);
859 }
860 if (sub_start == NULL) {
861 break;
862 }
863
864 pair->matched++;
865 count++;
866
867 if (buffer_append_string(dst, b->pos, sub_start - b->pos,
868 r->pool) == NULL) {
869 return NGX_ERROR;
870 }
871
872 if (buffer_append_string(dst, pair->sub.data, pair->sub.len,
873 r->pool) == NULL) {
874 return NGX_ERROR;
875 }
876
877 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
878 "fixed string match: %p", sub_start);
879
880 b->pos = sub_start + pair->match.len;
881
882 if ((ngx_uint_t)(b->last - b->pos) < pair->match.len)
883 break;
884 }
885
886 return count;
887 }
888
889
890 static ngx_buf_t *
buffer_append_string(ngx_buf_t * b,u_char * s,size_t len,ngx_pool_t * pool)891 buffer_append_string(ngx_buf_t *b, u_char *s, size_t len, ngx_pool_t *pool)
892 {
893 u_char *p;
894 ngx_uint_t capacity, size;
895
896 if (len > (size_t) (b->end - b->last)) {
897
898 size = b->last - b->pos;
899
900 capacity = b->end - b->start;
901 capacity <<= 1;
902
903 if (capacity < (size + len)) {
904 capacity = size + len;
905 }
906
907 p = ngx_palloc(pool, capacity);
908 if (p == NULL) {
909 return NULL;
910 }
911
912 b->last = ngx_copy(p, b->pos, size);
913
914 b->start = b->pos = p;
915 b->end = p + capacity;
916 }
917
918 b->last = ngx_copy(b->last, s, len);
919
920 return b;
921 }
922
923
924 static ngx_int_t
ngx_http_subs_out_chain_append(ngx_http_request_t * r,ngx_http_subs_ctx_t * ctx,ngx_buf_t * b)925 ngx_http_subs_out_chain_append(ngx_http_request_t *r,
926 ngx_http_subs_ctx_t *ctx, ngx_buf_t *b)
927 {
928 size_t len, capcity;
929
930 if (b == NULL || ngx_buf_size(b) == 0) {
931 return NGX_OK;
932 }
933
934 if (ctx->out_buf == NULL) {
935 if (ngx_http_subs_get_chain_buf(r, ctx) != NGX_OK) {
936 return NGX_ERROR;
937 }
938 }
939
940 while (1) {
941
942 len = (size_t) ngx_buf_size(b);
943 if (len == 0) {
944 break;
945 }
946
947 capcity = ctx->out_buf->end - ctx->out_buf->last;
948
949 if (len <= capcity) {
950 ctx->out_buf->last = ngx_copy(ctx->out_buf->last, b->pos, len);
951 b->pos += len;
952 break;
953
954 } else {
955 ctx->out_buf->last = ngx_copy(ctx->out_buf->last,
956 b->pos, capcity);
957 }
958
959 b->pos += capcity;
960
961 /* get more buffers */
962 if (ngx_http_subs_get_chain_buf(r, ctx) != NGX_OK) {
963 return NGX_ERROR;
964 }
965 }
966
967 return NGX_OK;
968 }
969
970
971 static ngx_int_t
ngx_http_subs_get_chain_buf(ngx_http_request_t * r,ngx_http_subs_ctx_t * ctx)972 ngx_http_subs_get_chain_buf(ngx_http_request_t *r,
973 ngx_http_subs_ctx_t *ctx)
974 {
975 ngx_chain_t *temp;
976 ngx_http_subs_loc_conf_t *slcf;
977
978 slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
979
980 if (ctx->free) {
981 temp = ctx->free;
982 ctx->free = ctx->free->next;
983
984 } else {
985 temp = ngx_alloc_chain_link(r->pool);
986 if (temp == NULL) {
987 return NGX_ERROR;
988 }
989
990 temp->buf = ngx_create_temp_buf(r->pool, slcf->bufs.size);
991 if (temp->buf == NULL) {
992 return NGX_ERROR;
993 }
994
995 temp->buf->tag = (ngx_buf_tag_t) &ngx_http_subs_filter_module;
996 temp->buf->recycled = 1;
997
998 /* TODO: limit the buffer number */
999 ctx->bufs++;
1000 }
1001
1002 temp->next = NULL;
1003
1004 ctx->out_buf = temp->buf;
1005 *ctx->last_out = temp;
1006 ctx->last_out = &temp->next;
1007
1008 return NGX_OK;
1009 }
1010
1011
1012 static ngx_int_t
ngx_http_subs_output(ngx_http_request_t * r,ngx_http_subs_ctx_t * ctx,ngx_chain_t * in)1013 ngx_http_subs_output(ngx_http_request_t *r, ngx_http_subs_ctx_t *ctx,
1014 ngx_chain_t *in)
1015 {
1016 ngx_int_t rc;
1017
1018 #if SUBS_DEBUG
1019 ngx_buf_t *b;
1020 ngx_chain_t *cl;
1021
1022 for (cl = ctx->out; cl; cl = cl->next) {
1023
1024 b = cl->buf;
1025
1026 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1027 "subs out buffer:%p, size:%uz, t:%d, l:%d",
1028 b, ngx_buf_size(b), b->temporary, b->last_buf);
1029 }
1030 #endif
1031
1032 /* ctx->out may not output all the data */
1033 rc = ngx_http_next_body_filter(r, ctx->out);
1034 if (rc == NGX_ERROR) {
1035 return NGX_ERROR;
1036 }
1037
1038 #if SUBS_DEBUG
1039 for (cl = ctx->out; cl; cl = cl->next) {
1040 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1041 "subs out end: %p %uz", cl->buf, ngx_buf_size(cl->buf));
1042 }
1043 #endif
1044
1045 #if defined(nginx_version) && (nginx_version >= 1001004)
1046 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
1047 (ngx_buf_tag_t) &ngx_http_subs_filter_module);
1048 #else
1049 ngx_chain_update_chains(&ctx->free, &ctx->busy, &ctx->out,
1050 (ngx_buf_tag_t) &ngx_http_subs_filter_module);
1051 #endif
1052
1053 if (ctx->last) {
1054 r->connection->buffered &= ~NGX_HTTP_SUB_BUFFERED;
1055 }
1056
1057 return rc;
1058 }
1059
1060
1061 static char *
ngx_http_subs_filter(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1062 ngx_http_subs_filter( ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1063 {
1064 ngx_int_t n;
1065 ngx_uint_t i;
1066 ngx_str_t *value;
1067 ngx_str_t *option;
1068 sub_pair_t *pair;
1069 ngx_http_subs_loc_conf_t *slcf = conf;
1070 ngx_http_script_compile_t sc;
1071
1072 value = cf->args->elts;
1073
1074 if (slcf->sub_pairs == NULL) {
1075 slcf->sub_pairs = ngx_array_create(cf->pool, 4, sizeof(sub_pair_t));
1076 if (slcf->sub_pairs == NULL) {
1077 return NGX_CONF_ERROR;
1078 }
1079 }
1080
1081 pair = ngx_array_push(slcf->sub_pairs);
1082 if (pair == NULL) {
1083 return NGX_CONF_ERROR;
1084 }
1085 ngx_memzero(pair, sizeof(sub_pair_t));
1086
1087 // judge the mode first
1088 if (cf->args->nelts > 3) {
1089
1090 option = &value[3];
1091 for(i = 0; i < option->len; i++) {
1092
1093 switch (option->data[i]) {
1094 case 'i':
1095 pair->insensitive = 1;
1096 break;
1097
1098 case 'o':
1099 pair->once = 1;
1100 break;
1101
1102 case 'r':
1103 pair->regex = 1;
1104 break;
1105
1106 case 'g':
1107 default:
1108 continue;
1109 }
1110 }
1111 }
1112
1113 n = ngx_http_script_variables_count(&value[1]);
1114 if (n != 0) {
1115 if (pair->regex) {
1116 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1117 "match part cannot contain variable "
1118 "during regex mode");
1119 return NGX_CONF_ERROR;
1120 }
1121 ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
1122
1123 sc.cf = cf;
1124 sc.source = &value[1];
1125 sc.lengths = &pair->match_lengths;
1126 sc.values = &pair->match_values;
1127 sc.variables = n;
1128 sc.complete_lengths = 1;
1129 sc.complete_values = 1;
1130
1131 if (ngx_http_script_compile(&sc) != NGX_OK) {
1132 return NGX_CONF_ERROR;
1133 }
1134 } else {
1135 pair->match = value[1];
1136 }
1137
1138 n = ngx_http_script_variables_count(&value[2]);
1139 if (n != 0) {
1140 ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
1141
1142 sc.cf = cf;
1143 sc.source = &value[2];
1144 sc.lengths = &pair->sub_lengths;
1145 sc.values = &pair->sub_values;
1146 sc.variables = n;
1147 sc.complete_lengths = 1;
1148 sc.complete_values = 1;
1149
1150 if (ngx_http_script_compile(&sc) != NGX_OK) {
1151 return NGX_CONF_ERROR;
1152 }
1153
1154 /* Dirty hack, if it has captured variables */
1155 if (sc.captures_mask) {
1156 pair->has_captured = 1;
1157 }
1158
1159 } else {
1160 pair->sub = value[2];
1161 }
1162
1163
1164 if (pair->regex) {
1165 if (ngx_http_subs_filter_regex_compile(pair, &sc, cf) == NGX_ERROR) {
1166 return NGX_CONF_ERROR;
1167 }
1168 }
1169
1170 return NGX_CONF_OK;
1171 }
1172
1173
1174 static ngx_int_t
ngx_http_subs_filter_regex_compile(sub_pair_t * pair,ngx_http_script_compile_t * sc,ngx_conf_t * cf)1175 ngx_http_subs_filter_regex_compile(sub_pair_t *pair,
1176 ngx_http_script_compile_t *sc, ngx_conf_t *cf)
1177 {
1178 /* Caseless match can only be implemented in regex. */
1179 #if (NGX_PCRE)
1180 u_char errstr[NGX_MAX_CONF_ERRSTR];
1181 ngx_int_t n, options;
1182 ngx_str_t err, *value;
1183 ngx_uint_t mask;
1184
1185 value = cf->args->elts;
1186
1187 err.len = NGX_MAX_CONF_ERRSTR;
1188 err.data = errstr;
1189
1190 options = (pair->insensitive ? NGX_REGEX_CASELESS : 0);
1191
1192 /* make nginx-0.8.25+ happy */
1193 #if defined(nginx_version) && nginx_version >= 8025
1194 ngx_regex_compile_t rc;
1195
1196 rc.pattern = pair->match;
1197 rc.pool = cf->pool;
1198 rc.err = err;
1199 rc.options = options;
1200
1201 if (ngx_regex_compile(&rc) != NGX_OK) {
1202 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
1203 return NGX_ERROR;
1204 }
1205
1206 pair->match_regex = rc.regex;
1207
1208 #else
1209 pair->match_regex = ngx_regex_compile(&pair->match, options,
1210 cf->pool, &err);
1211 #endif
1212
1213 if (pair->match_regex == NULL) {
1214 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &err);
1215 return NGX_ERROR;
1216 }
1217
1218 n = ngx_http_subs_regex_capture_count(pair->match_regex);
1219
1220 if (pair->has_captured) {
1221 mask = ((1 << (n + 1)) - 1);
1222 if ( mask < sc->captures_mask ) {
1223 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1224 "You want to capture too many regex substrings, "
1225 "more than %i in \"%V\"",
1226 n, &value[2]);
1227
1228 return NGX_ERROR;
1229 }
1230 }
1231 #else
1232 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1233 "the using of the regex \"%V\" requires PCRE library",
1234 &pair->match);
1235
1236 return NGX_ERROR;
1237 #endif
1238
1239 return NGX_OK;
1240 }
1241
1242
1243 #if (NGX_PCRE)
1244 static ngx_int_t
ngx_http_subs_regex_capture_count(ngx_regex_t * re)1245 ngx_http_subs_regex_capture_count(ngx_regex_t *re)
1246 {
1247 int rc, n;
1248
1249 n = 0;
1250
1251 #if defined(nginx_version) && nginx_version >= 1002002
1252 rc = pcre_fullinfo(re->code, NULL, PCRE_INFO_CAPTURECOUNT, &n);
1253 #elif defined(nginx_version) && nginx_version >= 1001012
1254 rc = pcre_fullinfo(re->pcre, NULL, PCRE_INFO_CAPTURECOUNT, &n);
1255 #else
1256 rc = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &n);
1257 #endif
1258
1259 if (rc < 0) {
1260 return (ngx_int_t) rc;
1261 }
1262
1263 return (ngx_int_t) n;
1264 }
1265 #endif
1266
1267
1268 static void *
ngx_http_subs_create_conf(ngx_conf_t * cf)1269 ngx_http_subs_create_conf(ngx_conf_t *cf)
1270 {
1271 ngx_http_subs_loc_conf_t *conf;
1272
1273 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_subs_loc_conf_t));
1274 if (conf == NULL) {
1275 return NGX_CONF_ERROR;
1276 }
1277
1278 /*
1279 * set by ngx_pcalloc():
1280 *
1281 * conf->sub_pairs = NULL;
1282 * conf->types = {NULL, 0};
1283 * conf->types_keys = NULL;
1284 * conf->bufs.num = 0;
1285 */
1286
1287 conf->line_buffer_size = NGX_CONF_UNSET_SIZE;
1288 conf->bypass = NGX_CONF_UNSET_PTR;
1289
1290 return conf;
1291 }
1292
1293
1294 static char *
ngx_http_subs_merge_conf(ngx_conf_t * cf,void * parent,void * child)1295 ngx_http_subs_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1296 {
1297 ngx_http_subs_loc_conf_t *prev = parent;
1298 ngx_http_subs_loc_conf_t *conf = child;
1299
1300 if (conf->sub_pairs == NULL) {
1301 if (prev->sub_pairs == NULL) {
1302 conf->sub_pairs = ngx_array_create(cf->pool, 4, sizeof(sub_pair_t));
1303 if (conf->sub_pairs == NULL) {
1304 return NGX_CONF_ERROR;
1305 }
1306 } else {
1307 conf->sub_pairs = prev->sub_pairs;
1308 }
1309 }
1310
1311 ngx_conf_merge_ptr_value(conf->bypass,
1312 prev->bypass, NULL);
1313
1314 if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
1315 &prev->types_keys, &prev->types,
1316 ngx_http_html_default_types)
1317 != NGX_OK)
1318 {
1319 return NGX_CONF_ERROR;
1320 }
1321
1322 ngx_conf_merge_size_value(conf->line_buffer_size,
1323 prev->line_buffer_size, 8 * ngx_pagesize);
1324
1325 /* Default total buffer size is 128k */
1326 ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
1327 (128 * 1024) / ngx_pagesize, ngx_pagesize);
1328
1329 return NGX_CONF_OK;
1330 }
1331
1332
1333 static ngx_int_t
ngx_http_subs_filter_init(ngx_conf_t * cf)1334 ngx_http_subs_filter_init(ngx_conf_t *cf)
1335 {
1336 ngx_http_next_header_filter = ngx_http_top_header_filter;
1337 ngx_http_top_header_filter = ngx_http_subs_header_filter;
1338
1339 ngx_http_next_body_filter = ngx_http_top_body_filter;
1340 ngx_http_top_body_filter = ngx_http_subs_body_filter;
1341
1342 return NGX_OK;
1343 }
1344