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