upstream 0.7.33
[nginx.git] / nginx / src / http / modules / ngx_http_sub_filter_module.c
1
2 /*
3  * Copyright (C) Igor Sysoev
4  */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 typedef struct {
13     ngx_str_t      match;
14     ngx_str_t      sub;
15
16     ngx_hash_t     types;
17
18     ngx_array_t   *sub_lengths;
19     ngx_array_t   *sub_values;
20
21     ngx_flag_t     once;
22
23     ngx_array_t   *types_keys;
24 } ngx_http_sub_loc_conf_t;
25
26
27 typedef enum {
28     sub_start_state = 0,
29     sub_match_state,
30 } ngx_http_sub_state_e;
31
32
33 typedef struct {
34     ngx_str_t      match;
35
36     ngx_uint_t     once;   /* unsigned  once:1 */
37
38     ngx_buf_t     *buf;
39
40     u_char        *pos;
41     u_char        *copy_start;
42     u_char        *copy_end;
43
44     ngx_chain_t   *in;
45     ngx_chain_t   *out;
46     ngx_chain_t  **last_out;
47     ngx_chain_t   *busy;
48     ngx_chain_t   *free;
49
50     ngx_str_t      sub;
51
52     ngx_uint_t     state;
53     size_t         saved;
54     size_t         looked;
55 } ngx_http_sub_ctx_t;
56
57
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);
62
63 static char * ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd,
64     void *conf);
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);
69
70
71 static ngx_command_t  ngx_http_sub_filter_commands[] = {
72
73     { ngx_string("sub_filter"),
74       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
75       ngx_http_sub_filter,
76       NGX_HTTP_LOC_CONF_OFFSET,
77       0,
78       NULL },
79
80     { ngx_string("sub_filter_types"),
81       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
82       ngx_http_types_slot,
83       NGX_HTTP_LOC_CONF_OFFSET,
84       offsetof(ngx_http_sub_loc_conf_t, types_keys),
85       &ngx_http_html_default_types[0] },
86
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),
92       NULL },
93
94       ngx_null_command
95 };
96
97
98 static ngx_http_module_t  ngx_http_sub_filter_module_ctx = {
99     NULL,                                  /* preconfiguration */
100     ngx_http_sub_filter_init,              /* postconfiguration */
101
102     NULL,                                  /* create main configuration */
103     NULL,                                  /* init main configuration */
104
105     NULL,                                  /* create server configuration */
106     NULL,                                  /* merge server configuration */
107
108     ngx_http_sub_create_conf,              /* create location configuration */
109     ngx_http_sub_merge_conf                /* merge location configuration */
110 };
111
112
113 ngx_module_t  ngx_http_sub_filter_module = {
114     NGX_MODULE_V1,
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
126 };
127
128
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;
131
132
133 static ngx_int_t
134 ngx_http_sub_header_filter(ngx_http_request_t *r)
135 {
136     ngx_http_sub_ctx_t        *ctx;
137     ngx_http_sub_loc_conf_t  *slcf;
138
139     slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
140
141     if (slcf->match.len == 0
142         || r->headers_out.content_length_n == 0
143         || ngx_http_test_content_type(r, &slcf->types) == NULL)
144     {
145         return ngx_http_next_header_filter(r);
146     }
147
148     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_ctx_t));
149     if (ctx == NULL) {
150         return NGX_ERROR;
151     }
152
153     ngx_http_set_ctx(r, ctx, ngx_http_sub_filter_module);
154
155     ctx->match = slcf->match;
156     ctx->last_out = &ctx->out;
157     ctx->sub = slcf->sub;
158
159     r->filter_need_in_memory = 1;
160
161     if (r == r->main) {
162         ngx_http_clear_content_length(r);
163         ngx_http_clear_last_modified(r);
164     }
165
166     return ngx_http_next_header_filter(r);
167 }
168
169
170 static ngx_int_t
171 ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
172 {
173     ngx_int_t                  rc;
174     ngx_buf_t                 *b;
175     ngx_chain_t               *cl;
176     ngx_http_sub_ctx_t        *ctx;
177     ngx_http_sub_loc_conf_t   *slcf;
178
179     ctx = ngx_http_get_module_ctx(r, ngx_http_sub_filter_module);
180
181     if (ctx == NULL) {
182         return ngx_http_next_body_filter(r, in);
183     }
184
185     if ((in == NULL
186          && ctx->buf == NULL
187          && ctx->in == NULL
188          && ctx->busy == NULL))
189     {
190         return ngx_http_next_body_filter(r, in);
191     }
192
193     if (ctx->once && (ctx->buf == NULL || ctx->in == NULL)) {
194
195         if (ctx->busy) {
196             if (ngx_http_sub_output(r, ctx) == NGX_ERROR) {
197                 return NGX_ERROR;
198             }
199         }
200
201         return ngx_http_next_body_filter(r, in);
202     }
203
204     /* add the incoming chain to the chain ctx->in */
205
206     if (in) {
207         if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
208             return NGX_ERROR;
209         }
210     }
211
212     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
213                    "http sub filter \"%V\"", &r->uri);
214
215     while (ctx->in || ctx->buf) {
216
217         if (ctx->buf == NULL ){
218             ctx->buf = ctx->in->buf;
219             ctx->in = ctx->in->next;
220             ctx->pos = ctx->buf->pos;
221         }
222
223         if (ctx->state == sub_start_state) {
224             ctx->copy_start = ctx->pos;
225             ctx->copy_end = ctx->pos;
226         }
227
228         b = NULL;
229
230         while (ctx->pos < ctx->buf->last) {
231
232             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
233                            "saved: %d state: %d", ctx->saved, ctx->state);
234
235             rc = ngx_http_sub_parse(r, ctx);
236
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);
240
241             if (rc == NGX_ERROR) {
242                 return rc;
243             }
244
245             if (ctx->copy_start != ctx->copy_end) {
246
247                 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
248                                "saved: %d", ctx->saved);
249
250                 if (ctx->saved) {
251
252                     if (ctx->free) {
253                         cl = ctx->free;
254                         ctx->free = ctx->free->next;
255                         b = cl->buf;
256                         ngx_memzero(b, sizeof(ngx_buf_t));
257
258                     } else {
259                         b = ngx_calloc_buf(r->pool);
260                         if (b == NULL) {
261                             return NGX_ERROR;
262                         }
263
264                         cl = ngx_alloc_chain_link(r->pool);
265                         if (cl == NULL) {
266                             return NGX_ERROR;
267                         }
268
269                         cl->buf = b;
270                     }
271
272                     b->memory = 1;
273                     b->pos = ctx->match.data;
274                     b->last = ctx->match.data + ctx->saved;
275
276                     *ctx->last_out = cl;
277                     ctx->last_out = &cl->next;
278
279                     ctx->saved = 0;
280                 }
281
282                 if (ctx->free) {
283                     cl = ctx->free;
284                     ctx->free = ctx->free->next;
285                     b = cl->buf;
286
287                 } else {
288                     b = ngx_alloc_buf(r->pool);
289                     if (b == NULL) {
290                         return NGX_ERROR;
291                     }
292
293                     cl = ngx_alloc_chain_link(r->pool);
294                     if (cl == NULL) {
295                         return NGX_ERROR;
296                     }
297
298                     cl->buf = b;
299                 }
300
301                 ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
302
303                 b->pos = ctx->copy_start;
304                 b->last = ctx->copy_end;
305                 b->shadow = NULL;
306                 b->last_buf = 0;
307                 b->recycled = 0;
308
309                 if (b->in_file) {
310                     b->file_last = b->file_pos + (b->last - ctx->buf->pos);
311                     b->file_pos += b->pos - ctx->buf->pos;
312                 }
313
314                 cl->next = NULL;
315                 *ctx->last_out = cl;
316                 ctx->last_out = &cl->next;
317             }
318
319             if (ctx->state == sub_start_state) {
320                 ctx->copy_start = ctx->pos;
321                 ctx->copy_end = ctx->pos;
322
323             } else {
324                 ctx->copy_start = NULL;
325                 ctx->copy_end = NULL;
326             }
327
328             if (rc == NGX_AGAIN) {
329                 continue;
330             }
331
332
333             /* rc == NGX_OK */
334
335             b = ngx_calloc_buf(r->pool);
336             if (b == NULL) {
337                 return NGX_ERROR;
338             }
339
340             cl = ngx_alloc_chain_link(r->pool);
341             if (cl == NULL) {
342                 return NGX_ERROR;
343             }
344
345             slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
346
347             if (ctx->sub.data == NULL) {
348
349                 if (ngx_http_script_run(r, &ctx->sub, slcf->sub_lengths->elts,
350                                         0, slcf->sub_values->elts)
351                     == NULL)
352                 {
353                     return NGX_ERROR;
354                 }
355             }
356
357             if (ctx->sub.len) {
358                 b->memory = 1;
359                 b->pos = ctx->sub.data;
360                 b->last = ctx->sub.data + ctx->sub.len;
361
362             } else {
363                 b->sync = 1;
364             }
365
366             cl->buf = b;
367             cl->next = NULL;
368             *ctx->last_out = cl;
369             ctx->last_out = &cl->next;
370
371             ctx->once = slcf->once;
372
373             continue;
374         }
375
376         if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
377             if (b == NULL) {
378                 if (ctx->free) {
379                     cl = ctx->free;
380                     ctx->free = ctx->free->next;
381                     b = cl->buf;
382                     ngx_memzero(b, sizeof(ngx_buf_t));
383
384                 } else {
385                     b = ngx_calloc_buf(r->pool);
386                     if (b == NULL) {
387                         return NGX_ERROR;
388                     }
389
390                     cl = ngx_alloc_chain_link(r->pool);
391                     if (cl == NULL) {
392                         return NGX_ERROR;
393                     }
394
395                     cl->buf = b;
396                 }
397
398                 b->sync = 1;
399
400                 cl->next = NULL;
401                 *ctx->last_out = cl;
402                 ctx->last_out = &cl->next;
403             }
404
405             b->last_buf = ctx->buf->last_buf;
406             b->shadow = ctx->buf;
407
408             b->recycled = ctx->buf->recycled;
409         }
410
411         ctx->buf = NULL;
412
413         ctx->saved = ctx->looked;
414     }
415
416     if (ctx->out == NULL && ctx->busy == NULL) {
417         return NGX_OK;
418     }
419
420     return ngx_http_sub_output(r, ctx);
421 }
422
423
424 static ngx_int_t
425 ngx_http_sub_output(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
426 {
427     ngx_int_t     rc;
428     ngx_buf_t    *b;
429     ngx_chain_t  *cl;
430
431 #if 1
432     b = NULL;
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);
436         if (cl->buf == b) {
437             ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
438                           "the same buf was used in sub");
439             ngx_debug_point();
440             return NGX_ERROR;
441         }
442         b = cl->buf;
443     }
444 #endif
445
446     rc = ngx_http_next_body_filter(r, ctx->out);
447
448     if (ctx->busy == NULL) {
449         ctx->busy = ctx->out;
450
451     } else {
452         for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
453         cl->next = ctx->out;
454     }
455
456     ctx->out = NULL;
457     ctx->last_out = &ctx->out;
458
459     while (ctx->busy) {
460
461         cl = ctx->busy;
462         b = cl->buf;
463
464         if (ngx_buf_size(b) != 0) {
465             break;
466         }
467
468         if (b->shadow) {
469             b->shadow->pos = b->shadow->last;
470         }
471
472         ctx->busy = cl->next;
473
474         if (ngx_buf_in_memory(b) || b->in_file) {
475             /* add data bufs only to the free buf chain */
476
477             cl->next = ctx->free;
478             ctx->free = cl;
479         }
480     }
481
482     if (ctx->in || ctx->buf) {
483         r->buffered |= NGX_HTTP_SUB_BUFFERED;
484
485     } else {
486         r->buffered &= ~NGX_HTTP_SUB_BUFFERED;
487     }
488
489     return rc;
490 }
491
492
493 static ngx_int_t
494 ngx_http_sub_parse(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
495 {
496     u_char                *p, *last, *copy_end, ch, match;
497     size_t                 looked;
498     ngx_http_sub_state_e   state;
499
500     if (ctx->once) {
501         ctx->copy_start = ctx->pos;
502         ctx->copy_end = ctx->buf->last;
503         ctx->pos = ctx->buf->last;
504         ctx->looked = 0;
505
506         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "once");
507
508         return NGX_AGAIN;
509     }
510
511     state = ctx->state;
512     looked = ctx->looked;
513     last = ctx->buf->last;
514     copy_end = ctx->copy_end;
515
516     for (p = ctx->pos; p < last; p++) {
517
518         ch = *p;
519         ch = ngx_tolower(ch);
520
521         if (state == sub_start_state) {
522
523             /* the tight loop */
524
525             match = ctx->match.data[0];
526
527             for ( ;; ) {
528                 if (ch == match) {
529                     copy_end = p;
530                     looked = 1;
531                     state = sub_match_state;
532
533                     goto match_started;
534                 }
535
536                 if (++p == last) {
537                     break;
538                 }
539
540                 ch = *p;
541                 ch = ngx_tolower(ch);
542             }
543
544             ctx->state = state;
545             ctx->pos = p;
546             ctx->looked = looked;
547             ctx->copy_end = p;
548
549             if (ctx->copy_start == NULL) {
550                 ctx->copy_start = ctx->buf->pos;
551             }
552
553             return NGX_AGAIN;
554
555         match_started:
556
557             continue;
558         }
559
560         /* state == sub_match_state */
561
562         if (ch == ctx->match.data[looked]) {
563             looked++;
564
565             if (looked == ctx->match.len) {
566                 if ((size_t) (p - ctx->pos) < looked) {
567                     ctx->saved = 0;
568                 }
569
570                 ctx->state = sub_start_state;
571                 ctx->pos = p + 1;
572                 ctx->looked = 0;
573                 ctx->copy_end = copy_end;
574
575                 if (ctx->copy_start == NULL && copy_end) {
576                     ctx->copy_start = ctx->buf->pos;
577                 }
578
579                 return NGX_OK;
580             }
581
582         } else if (ch == ctx->match.data[0]) {
583             copy_end = p;
584             looked = 1;
585
586         } else {
587             copy_end = p;
588             looked = 0;
589             state = sub_start_state;
590         }
591     }
592
593     ctx->state = state;
594     ctx->pos = p;
595     ctx->looked = looked;
596
597     ctx->copy_end = (state == sub_start_state) ? p : copy_end;
598
599     if (ctx->copy_start == NULL && ctx->copy_end) {
600         ctx->copy_start = ctx->buf->pos;
601     }
602
603     return NGX_AGAIN;
604 }
605
606
607 static char *
608 ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
609 {
610     ngx_http_sub_loc_conf_t *slcf = conf;
611
612     ngx_str_t                  *value;
613     ngx_int_t                   n;
614     ngx_http_script_compile_t   sc;
615
616     if (slcf->match.len) {
617         return "is duplicate";
618     }
619
620     value = cf->args->elts;
621
622     ngx_strlow(value[1].data, value[1].data, value[1].len);
623
624     slcf->match = value[1];
625
626     n = ngx_http_script_variables_count(&value[2]);
627
628     if (n == 0) {
629         slcf->sub = value[2];
630         return NGX_CONF_OK;
631     }
632
633     ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
634
635     sc.cf = cf;
636     sc.source = &value[2];
637     sc.lengths = &slcf->sub_lengths;
638     sc.values = &slcf->sub_values;
639     sc.variables = n;
640     sc.complete_lengths = 1;
641     sc.complete_values = 1;
642
643     if (ngx_http_script_compile(&sc) != NGX_OK) {
644         return NGX_CONF_ERROR;
645     }
646
647     return NGX_CONF_OK;
648 }
649
650
651 static void *
652 ngx_http_sub_create_conf(ngx_conf_t *cf)
653 {
654     ngx_http_sub_loc_conf_t  *slcf;
655
656     slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sub_loc_conf_t));
657     if (slcf == NULL) {
658         return NGX_CONF_ERROR;
659     }
660
661     /*
662      * set by ngx_pcalloc():
663      *
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;
670      */
671
672     slcf->once = NGX_CONF_UNSET;
673
674     return slcf;
675 }
676
677
678 static char *
679 ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child)
680 {
681     ngx_http_sub_loc_conf_t *prev = parent;
682     ngx_http_sub_loc_conf_t *conf = child;
683
684     ngx_conf_merge_value(conf->once, prev->once, 1);
685     ngx_conf_merge_str_value(conf->match, prev->match, "");
686
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;
691     }
692
693     if (ngx_http_merge_types(cf, conf->types_keys, &conf->types,
694                              prev->types_keys, &prev->types,
695                              ngx_http_html_default_types)
696         != NGX_OK)
697     {
698         return NGX_CONF_ERROR;
699     }
700
701     return NGX_CONF_OK;
702 }
703
704
705 static ngx_int_t
706 ngx_http_sub_filter_init(ngx_conf_t *cf)
707 {
708     ngx_http_next_header_filter = ngx_http_top_header_filter;
709     ngx_http_top_header_filter = ngx_http_sub_header_filter;
710
711     ngx_http_next_body_filter = ngx_http_top_body_filter;
712     ngx_http_top_body_filter = ngx_http_sub_body_filter;
713
714     return NGX_OK;
715 }