upstream nginx-0.7.43
[nginx.git] / nginx / src / http / modules / ngx_http_charset_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 #define NGX_HTTP_NO_CHARSET    -2
13 #define NGX_HTTP_CHARSET_VAR   0x10000
14
15 /* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */
16 #define NGX_UTF_LEN             4
17
18 #define NGX_HTML_ENTITY_LEN     (sizeof("&#1114111;") - 1)
19
20
21 typedef struct {
22     u_char                    **tables;
23     ngx_str_t                   name;
24
25     unsigned                    length:16;
26     unsigned                    utf8:1;
27 } ngx_http_charset_t;
28
29
30 typedef struct {
31     ngx_int_t                   src;
32     ngx_int_t                   dst;
33 } ngx_http_charset_recode_t;
34
35
36 typedef struct {
37     ngx_int_t                   src;
38     ngx_int_t                   dst;
39     u_char                     *src2dst;
40     u_char                     *dst2src;
41 } ngx_http_charset_tables_t;
42
43
44 typedef struct {
45     ngx_array_t                 charsets;       /* ngx_http_charset_t */
46     ngx_array_t                 tables;         /* ngx_http_charset_tables_t */
47     ngx_array_t                 recodes;        /* ngx_http_charset_recode_t */
48 } ngx_http_charset_main_conf_t;
49
50
51 typedef struct {
52     ngx_int_t                   charset;
53     ngx_int_t                   source_charset;
54     ngx_flag_t                  override_charset;
55
56     ngx_hash_t                  types;
57     ngx_array_t                *types_keys;
58 } ngx_http_charset_loc_conf_t;
59
60
61 typedef struct {
62     u_char                     *table;
63     ngx_int_t                   charset;
64
65     ngx_chain_t                *busy;
66     ngx_chain_t                *free_bufs;
67     ngx_chain_t                *free_buffers;
68
69     size_t                      saved_len;
70     u_char                      saved[NGX_UTF_LEN];
71
72     unsigned                    length:16;
73     unsigned                    from_utf8:1;
74     unsigned                    to_utf8:1;
75 } ngx_http_charset_ctx_t;
76
77
78 typedef struct {
79     ngx_http_charset_tables_t  *table;
80     ngx_http_charset_t         *charset;
81     ngx_uint_t                  characters;
82 } ngx_http_charset_conf_ctx_t;
83
84
85 static ngx_int_t ngx_http_charset_get_charset(ngx_http_charset_t *charsets,
86     ngx_uint_t n, ngx_str_t *charset);
87 static ngx_int_t ngx_http_charset_set_charset(ngx_http_request_t *r,
88     ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset);
89 static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
90 static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool,
91     ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
92 static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool,
93     ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
94
95 static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool,
96     ngx_http_charset_ctx_t *ctx);
97 static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool,
98     ngx_http_charset_ctx_t *ctx, size_t size);
99
100 static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
101     void *conf);
102 static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy,
103     void *conf);
104
105 static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
106     void *conf);
107 static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name);
108
109 static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
110 static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf);
111 static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
112     void *parent, void *child);
113 static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf);
114
115
116 ngx_str_t  ngx_http_charset_default_types[] = {
117     ngx_string("text/html"),
118     ngx_string("text/xml"),
119     ngx_string("text/plain"),
120     ngx_string("text/vnd.wap.wml"),
121     ngx_string("application/x-javascript"),
122     ngx_string("application/rss+xml"),
123     ngx_null_string
124 };
125
126
127 static ngx_command_t  ngx_http_charset_filter_commands[] = {
128
129     { ngx_string("charset"),
130       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
131                         |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
132       ngx_http_set_charset_slot,
133       NGX_HTTP_LOC_CONF_OFFSET,
134       offsetof(ngx_http_charset_loc_conf_t, charset),
135       NULL },
136
137     { ngx_string("source_charset"),
138       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
139                         |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
140       ngx_http_set_charset_slot,
141       NGX_HTTP_LOC_CONF_OFFSET,
142       offsetof(ngx_http_charset_loc_conf_t, source_charset),
143       NULL },
144
145     { ngx_string("override_charset"),
146       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
147                         |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
148       ngx_conf_set_flag_slot,
149       NGX_HTTP_LOC_CONF_OFFSET,
150       offsetof(ngx_http_charset_loc_conf_t, override_charset),
151       NULL },
152
153     { ngx_string("charset_types"),
154       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
155       ngx_http_types_slot,
156       NGX_HTTP_LOC_CONF_OFFSET,
157       offsetof(ngx_http_charset_loc_conf_t, types_keys),
158       &ngx_http_charset_default_types[0] },
159
160     { ngx_string("charset_map"),
161       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
162       ngx_http_charset_map_block,
163       NGX_HTTP_MAIN_CONF_OFFSET,
164       0,
165       NULL },
166
167       ngx_null_command
168 };
169
170
171 static ngx_http_module_t  ngx_http_charset_filter_module_ctx = {
172     NULL,                                  /* preconfiguration */
173     ngx_http_charset_postconfiguration,    /* postconfiguration */
174
175     ngx_http_charset_create_main_conf,     /* create main configuration */
176     NULL,                                  /* init main configuration */
177
178     NULL,                                  /* create server configuration */
179     NULL,                                  /* merge server configuration */
180
181     ngx_http_charset_create_loc_conf,      /* create location configuration */
182     ngx_http_charset_merge_loc_conf        /* merge location configuration */
183 };
184
185
186 ngx_module_t  ngx_http_charset_filter_module = {
187     NGX_MODULE_V1,
188     &ngx_http_charset_filter_module_ctx,   /* module context */
189     ngx_http_charset_filter_commands,      /* module directives */
190     NGX_HTTP_MODULE,                       /* module type */
191     NULL,                                  /* init master */
192     NULL,                                  /* init module */
193     NULL,                                  /* init process */
194     NULL,                                  /* init thread */
195     NULL,                                  /* exit thread */
196     NULL,                                  /* exit process */
197     NULL,                                  /* exit master */
198     NGX_MODULE_V1_PADDING
199 };
200
201
202 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
203 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
204
205
206 static ngx_int_t
207 ngx_http_charset_header_filter(ngx_http_request_t *r)
208 {
209     ngx_int_t                      charset, source_charset;
210     ngx_str_t                     *mc, *from, *to, s;
211     ngx_uint_t                     n;
212     ngx_http_charset_t            *charsets;
213     ngx_http_charset_ctx_t        *ctx;
214     ngx_http_variable_value_t     *vv;
215     ngx_http_charset_loc_conf_t   *lcf, *mlcf;
216     ngx_http_charset_main_conf_t  *mcf;
217
218     mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
219
220     charsets = mcf->charsets.elts;
221     n = mcf->charsets.nelts;
222
223     /* destination charset */
224
225     if (r == r->main) {
226
227         if (r->headers_out.content_encoding
228             && r->headers_out.content_encoding->value.len)
229         {
230             return ngx_http_next_header_filter(r);
231         }
232
233         if (r->headers_out.content_type.len == 0) {
234             return ngx_http_next_header_filter(r);
235         }
236
237         if (r->headers_out.override_charset
238             && r->headers_out.override_charset->len)
239         {
240             charset = ngx_http_charset_get_charset(charsets, n,
241                                               r->headers_out.override_charset);
242
243             if (charset == NGX_HTTP_NO_CHARSET) {
244                 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
245                               "unknown charset \"%V\" to override",
246                               r->headers_out.override_charset);
247
248                 return ngx_http_next_header_filter(r);
249             }
250
251         } else {
252             mlcf = ngx_http_get_module_loc_conf(r,
253                                                 ngx_http_charset_filter_module);
254             charset = mlcf->charset;
255
256             if (charset == NGX_HTTP_NO_CHARSET) {
257                 return ngx_http_next_header_filter(r);
258             }
259
260             if (r->headers_out.charset.len) {
261                 if (mlcf->override_charset == 0) {
262                     return ngx_http_next_header_filter(r);
263                 }
264
265             } else {
266                 if (ngx_http_test_content_type(r, &mlcf->types) == NULL) {
267                     return ngx_http_next_header_filter(r);
268                 }
269             }
270
271             if (charset >= NGX_HTTP_CHARSET_VAR) {
272                 vv = ngx_http_get_indexed_variable(r,
273                                                charset - NGX_HTTP_CHARSET_VAR);
274
275                 if (vv == NULL || vv->not_found) {
276                     return NGX_ERROR;
277                 }
278
279                 s.len = vv->len;
280                 s.data = vv->data;
281
282                 charset = ngx_http_charset_get_charset(charsets, n, &s);
283             }
284         }
285
286     } else {
287         ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module);
288
289         if (ctx == NULL) {
290
291             mc = &r->main->headers_out.charset;
292
293             if (mc->len == 0) {
294                 return ngx_http_next_header_filter(r);
295             }
296
297             ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
298             if (ctx == NULL) {
299                 return NGX_ERROR;
300             }
301
302             ngx_http_set_ctx(r->main, ctx, ngx_http_charset_filter_module);
303
304             charset = ngx_http_charset_get_charset(charsets, n, mc);
305
306             ctx->charset = charset;
307
308         } else {
309             charset = ctx->charset;
310         }
311     }
312
313     /* source charset */
314
315     if (r->headers_out.charset.len == 0) {
316         lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
317
318         source_charset = lcf->source_charset;
319
320         if (source_charset >= NGX_HTTP_CHARSET_VAR) {
321             vv = ngx_http_get_indexed_variable(r,
322                                         source_charset - NGX_HTTP_CHARSET_VAR);
323
324             if (vv == NULL || vv->not_found) {
325                 return NGX_ERROR;
326             }
327
328             s.len = vv->len;
329             s.data = vv->data;
330
331             source_charset = ngx_http_charset_get_charset(charsets, n, &s);
332         }
333
334         if (charset != NGX_HTTP_NO_CHARSET) {
335             return ngx_http_charset_set_charset(r, mcf->charsets.elts, charset,
336                                                 source_charset);
337         }
338
339         if (source_charset == NGX_CONF_UNSET) {
340             return ngx_http_next_header_filter(r);
341         }
342
343         from = &charsets[source_charset].name;
344         to = &r->main->headers_out.charset;
345
346         goto no_charset_map;
347     }
348
349     source_charset = ngx_http_charset_get_charset(charsets, n,
350                                                   &r->headers_out.charset);
351
352     if (charset == NGX_HTTP_NO_CHARSET
353         || source_charset == NGX_HTTP_NO_CHARSET)
354     {
355         if (charset != source_charset
356             || ngx_strcasecmp(r->main->headers_out.charset.data,
357                               r->headers_out.charset.data)
358                 != 0)
359         {
360             from = &r->headers_out.charset;
361             to = (charset == NGX_HTTP_NO_CHARSET) ?
362                                            &r->main->headers_out.charset:
363                                            &charsets[charset].name;
364
365             goto no_charset_map;
366         }
367
368         return ngx_http_next_header_filter(r);
369     }
370
371     if (source_charset != charset
372         && (charsets[source_charset].tables == NULL
373             || charsets[source_charset].tables[charset] == NULL))
374     {
375         from = &charsets[source_charset].name;
376         to = &charsets[charset].name;
377
378         goto no_charset_map;
379     }
380
381     r->headers_out.content_type.len = r->headers_out.content_type_len;
382
383     return ngx_http_charset_set_charset(r, mcf->charsets.elts, charset,
384                                         source_charset);
385
386 no_charset_map:
387
388     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
389                   "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
390                   from, to);
391
392     return ngx_http_next_header_filter(r);
393 }
394
395
396 static ngx_int_t
397 ngx_http_charset_get_charset(ngx_http_charset_t *charsets, ngx_uint_t n,
398     ngx_str_t *charset)
399 {
400     ngx_uint_t  i;
401
402     for (i = 0; i < n; i++) {
403         if (charsets[i].name.len != charset->len) {
404             continue;
405         }
406
407         if (ngx_strncasecmp(charsets[i].name.data, charset->data, charset->len)
408             == 0)
409         {
410             return i;
411         }
412     }
413
414     return NGX_HTTP_NO_CHARSET;
415 }
416
417
418 static ngx_int_t
419 ngx_http_charset_set_charset(ngx_http_request_t *r,
420     ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset)
421 {
422     ngx_http_charset_ctx_t  *ctx;
423
424     if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
425         || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
426     {
427         /*
428          * do not set charset for the redirect because NN 4.x
429          * use this charset instead of the next page charset
430          */
431
432         r->headers_out.charset.len = 0;
433
434         return ngx_http_next_header_filter(r);
435     }
436
437     r->headers_out.charset = charsets[charset].name;
438     r->utf8 = charsets[charset].utf8;
439
440     if (source_charset == NGX_CONF_UNSET || source_charset == charset) {
441         return ngx_http_next_header_filter(r);
442     }
443
444     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
445     if (ctx == NULL) {
446         return NGX_ERROR;
447     }
448
449     ngx_http_set_ctx(r, ctx, ngx_http_charset_filter_module);
450
451     ctx->table = charsets[source_charset].tables[charset];
452     ctx->charset = charset;
453     ctx->length = charsets[charset].length;
454     ctx->from_utf8 = charsets[source_charset].utf8;
455     ctx->to_utf8 = charsets[charset].utf8;
456
457     r->filter_need_in_memory = 1;
458
459     if ((ctx->to_utf8 || ctx->from_utf8) && r == r->main) {
460         ngx_http_clear_content_length(r);
461
462     } else {
463         r->filter_need_temporary = 1;
464     }
465
466     return ngx_http_next_header_filter(r);
467 }
468
469
470 static ngx_int_t
471 ngx_http_charset_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
472 {
473     ngx_int_t                rc;
474     ngx_buf_t               *b;
475     ngx_chain_t             *cl, *out, **ll;
476     ngx_http_charset_ctx_t  *ctx;
477
478     ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
479
480     if (ctx == NULL || ctx->table == NULL) {
481         return ngx_http_next_body_filter(r, in);
482     }
483
484     if ((ctx->to_utf8 || ctx->from_utf8) || ctx->busy) {
485
486         out = NULL;
487         ll = &out;
488
489         for (cl = in; cl; cl = cl->next) {
490             b = cl->buf;
491
492             if (ngx_buf_size(b) == 0) {
493
494                 *ll = ngx_alloc_chain_link(r->pool);
495                 if (*ll == NULL) {
496                     return NGX_ERROR;
497                 }
498
499                 (*ll)->buf = b;
500                 (*ll)->next = NULL;
501
502                 ll = &(*ll)->next;
503
504                 continue;
505             }
506
507             if (ctx->to_utf8) {
508                 *ll = ngx_http_charset_recode_to_utf8(r->pool, b, ctx);
509
510             } else {
511                 *ll = ngx_http_charset_recode_from_utf8(r->pool, b, ctx);
512             }
513
514             if (*ll == NULL) {
515                 return NGX_ERROR;
516             }
517
518             while (*ll) {
519                 ll = &(*ll)->next;
520             }
521         }
522
523         rc = ngx_http_next_body_filter(r, out);
524
525         if (out) {
526             if (ctx->busy == NULL) {
527                 ctx->busy = out;
528
529             } else {
530                 for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
531                 cl->next = out;
532             }
533         }
534
535         while (ctx->busy) {
536
537             cl = ctx->busy;
538             b = cl->buf;
539
540             if (ngx_buf_size(b) != 0) {
541                 break;
542             }
543
544             ctx->busy = cl->next;
545
546             if (b->tag != (ngx_buf_tag_t) &ngx_http_charset_filter_module) {
547                 continue;
548             }
549
550             if (b->shadow) {
551                 b->shadow->pos = b->shadow->last;
552             }
553
554             if (b->pos) {
555                 cl->next = ctx->free_buffers;
556                 ctx->free_buffers = cl;
557                 continue;
558             }
559
560             cl->next = ctx->free_bufs;
561             ctx->free_bufs = cl;
562         }
563
564         return rc;
565     }
566
567     for (cl = in; cl; cl = cl->next) {
568         (void) ngx_http_charset_recode(cl->buf, ctx->table);
569     }
570
571     return ngx_http_next_body_filter(r, in);
572 }
573
574
575 static ngx_uint_t
576 ngx_http_charset_recode(ngx_buf_t *b, u_char *table)
577 {
578     u_char  *p, *last;
579
580     last = b->last;
581
582     for (p = b->pos; p < last; p++) {
583
584         if (*p != table[*p]) {
585             goto recode;
586         }
587     }
588
589     return 0;
590
591 recode:
592
593     do {
594         if (*p != table[*p]) {
595             *p = table[*p];
596         }
597
598         p++;
599
600     } while (p < last);
601
602     b->in_file = 0;
603
604     return 1;
605 }
606
607
608 static ngx_chain_t *
609 ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
610     ngx_http_charset_ctx_t *ctx)
611 {
612     size_t        len, size;
613     u_char        c, *p, *src, *dst, *saved, **table;
614     uint32_t      n;
615     ngx_buf_t    *b;
616     ngx_uint_t    i;
617     ngx_chain_t  *out, *cl, **ll;
618
619     src = buf->pos;
620
621     if (ctx->saved_len == 0) {
622
623         for ( /* void */ ; src < buf->last; src++) {
624
625             if (*src < 0x80) {
626                 continue;
627             }
628
629             len = src - buf->pos;
630
631             if (len > 512) {
632                 out = ngx_http_charset_get_buf(pool, ctx);
633                 if (out == NULL) {
634                     return NULL;
635                 }
636
637                 b = out->buf;
638
639                 b->temporary = buf->temporary;
640                 b->memory = buf->memory;
641                 b->mmap = buf->mmap;
642                 b->flush = buf->flush;
643
644                 b->pos = buf->pos;
645                 b->last = src;
646
647                 out->buf = b;
648                 out->next = NULL;
649
650                 size = buf->last - src;
651
652                 saved = src;
653                 n = ngx_utf8_decode(&saved, size);
654
655                 if (n == 0xfffffffe) {
656                     /* incomplete UTF-8 symbol */
657
658                     ngx_memcpy(ctx->saved, src, size);
659                     ctx->saved_len = size;
660
661                     b->shadow = buf;
662
663                     return out;
664                 }
665
666             } else {
667                 out = NULL;
668                 size = len + buf->last - src;
669                 src = buf->pos;
670             }
671
672             if (size < NGX_HTML_ENTITY_LEN) {
673                 size += NGX_HTML_ENTITY_LEN;
674             }
675
676             cl = ngx_http_charset_get_buffer(pool, ctx, size);
677             if (cl == NULL) {
678                 return NULL;
679             }
680
681             if (out) {
682                 out->next = cl;
683
684             } else {
685                 out = cl;
686             }
687
688             b = cl->buf;
689             dst = b->pos;
690
691             goto recode;
692         }
693
694         out = ngx_alloc_chain_link(pool);
695         if (out == NULL) {
696             return NULL;
697         }
698
699         out->buf = buf;
700         out->next = NULL;
701
702         return out;
703     }
704
705     /* process incomplete UTF sequence from previous buffer */
706
707     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0,
708                    "http charset utf saved: %z", ctx->saved_len);
709
710     p = src;
711
712     for (i = ctx->saved_len; i < NGX_UTF_LEN; i++) {
713         ctx->saved[i] = *p++;
714
715         if (p == buf->last) {
716             break;
717         }
718     }
719
720     saved = ctx->saved;
721     n = ngx_utf8_decode(&saved, i);
722
723     c = '\0';
724
725     if (n < 0x10000) {
726         table = (u_char **) ctx->table;
727         p = table[n >> 8];
728
729         if (p) {
730             c = p[n & 0xff];
731         }
732
733     } else if (n == 0xfffffffe) {
734
735         /* incomplete UTF-8 symbol */
736
737         if (i < NGX_UTF_LEN) {
738             out = ngx_http_charset_get_buf(pool, ctx);
739             if (out == NULL) {
740                 return NULL;
741             }
742
743             b = out->buf;
744
745             b->pos = buf->pos;
746             b->last = buf->last;
747             b->sync = 1;
748             b->shadow = buf;
749
750             ngx_memcpy(&ctx->saved[ctx->saved_len], src, i);
751             ctx->saved_len += i;
752
753             return out;
754         }
755     }
756
757     size = buf->last - buf->pos;
758
759     if (size < NGX_HTML_ENTITY_LEN) {
760         size += NGX_HTML_ENTITY_LEN;
761     }
762
763     cl = ngx_http_charset_get_buffer(pool, ctx, size);
764     if (cl == NULL) {
765         return NULL;
766     }
767
768     out = cl;
769
770     b = cl->buf;
771     dst = b->pos;
772
773     if (c) {
774         *dst++ = c;
775
776     } else if (n == 0xfffffffe) {
777         *dst++ = '?';
778
779         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
780                        "http charset invalid utf 0");
781
782         saved = &ctx->saved[NGX_UTF_LEN];
783
784     } else if (n > 0x10ffff) {
785         *dst++ = '?';
786
787         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
788                        "http charset invalid utf 1");
789
790     } else {
791         dst = ngx_sprintf(dst, "&#%uD;", n);
792     }
793
794     src += (saved - ctx->saved) - ctx->saved_len;
795     ctx->saved_len = 0;
796
797 recode:
798
799     ll = &cl->next;
800
801     table = (u_char **) ctx->table;
802
803     while (src < buf->last) {
804
805         if ((size_t) (b->end - dst) < NGX_HTML_ENTITY_LEN) {
806             b->last = dst;
807
808             size = buf->last - src + NGX_HTML_ENTITY_LEN;
809
810             cl = ngx_http_charset_get_buffer(pool, ctx, size);
811             if (cl == NULL) {
812                 return NULL;
813             }
814
815             *ll = cl;
816             ll = &cl->next;
817
818             b = cl->buf;
819             dst = b->pos;
820         }
821
822         if (*src < 0x80) {
823             *dst++ = *src++;
824             continue;
825         }
826
827         len = buf->last - src;
828
829         n = ngx_utf8_decode(&src, len);
830
831         if (n < 0x10000) {
832
833             p = table[n >> 8];
834
835             if (p) {
836                 c = p[n & 0xff];
837
838                 if (c) {
839                     *dst++ = c;
840                     continue;
841                 }
842             }
843
844             dst = ngx_sprintf(dst, "&#%uD;", n);
845
846             continue;
847         }
848
849         if (n == 0xfffffffe) {
850             /* incomplete UTF-8 symbol */
851
852             ngx_memcpy(ctx->saved, src, len);
853             ctx->saved_len = len;
854
855             if (b->pos == dst) {
856                 b->sync = 1;
857                 b->temporary = 0;
858             }
859
860             break;
861         }
862
863         if (n > 0x10ffff) {
864             *dst++ = '?';
865
866             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
867                            "http charset invalid utf 2");
868
869             continue;
870         }
871
872         /* n > 0xffff */
873
874         dst = ngx_sprintf(dst, "&#%uD;", n);
875     }
876
877     b->last = dst;
878
879     b->last_buf = buf->last_buf;
880     b->last_in_chain = buf->last_in_chain;
881     b->flush = buf->flush;
882
883     b->shadow = buf;
884
885     return out;
886 }
887
888
889 static ngx_chain_t *
890 ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
891     ngx_http_charset_ctx_t *ctx)
892 {
893     size_t        len, size;
894     u_char       *p, *src, *dst, *table;
895     ngx_buf_t    *b;
896     ngx_chain_t  *out, *cl, **ll;
897
898     table = ctx->table;
899
900     for (src = buf->pos; src < buf->last; src++) {
901         if (table[*src * NGX_UTF_LEN] == '\1') {
902             continue;
903         }
904
905         goto recode;
906     }
907
908     out = ngx_alloc_chain_link(pool);
909     if (out == NULL) {
910         return NULL;
911     }
912
913     out->buf = buf;
914     out->next = NULL;
915
916     return out;
917
918 recode:
919
920     /*
921      * we assume that there are about half of characters to be recoded,
922      * so we preallocate "size / 2 + size / 2 * ctx->length"
923      */
924
925     len = src - buf->pos;
926
927     if (len > 512) {
928         out = ngx_http_charset_get_buf(pool, ctx);
929         if (out == NULL) {
930             return NULL;
931         }
932
933         b = out->buf;
934
935         b->temporary = buf->temporary;
936         b->memory = buf->memory;
937         b->mmap = buf->mmap;
938         b->flush = buf->flush;
939
940         b->pos = buf->pos;
941         b->last = src;
942
943         out->buf = b;
944         out->next = NULL;
945
946         size = buf->last - src;
947         size = size / 2 + size / 2 * ctx->length;
948
949     } else {
950         out = NULL;
951
952         size = buf->last - src;
953         size = len + size / 2 + size / 2 * ctx->length;
954
955         src = buf->pos;
956     }
957
958     cl = ngx_http_charset_get_buffer(pool, ctx, size);
959     if (cl == NULL) {
960         return NULL;
961     }
962
963     if (out) {
964         out->next = cl;
965
966     } else {
967         out = cl;
968     }
969
970     ll = &cl->next;
971
972     b = cl->buf;
973     dst = b->pos;
974
975     while (src < buf->last) {
976
977         p = &table[*src++ * NGX_UTF_LEN];
978         len = *p++;
979
980         if ((size_t) (b->end - dst) < len) {
981             b->last = dst;
982
983             size = buf->last - src;
984             size = len + size / 2 + size / 2 * ctx->length;
985
986             cl = ngx_http_charset_get_buffer(pool, ctx, size);
987             if (cl == NULL) {
988                 return NULL;
989             }
990
991             *ll = cl;
992             ll = &cl->next;
993
994             b = cl->buf;
995             dst = b->pos;
996         }
997
998         while (len) {
999             *dst++ = *p++;
1000             len--;
1001         }
1002     }
1003
1004     b->last = dst;
1005
1006     b->last_buf = buf->last_buf;
1007     b->last_in_chain = buf->last_in_chain;
1008     b->flush = buf->flush;
1009
1010     b->shadow = buf;
1011
1012     return out;
1013 }
1014
1015
1016 static ngx_chain_t *
1017 ngx_http_charset_get_buf(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx)
1018 {
1019     ngx_chain_t  *cl;
1020
1021     cl = ctx->free_bufs;
1022
1023     if (cl) {
1024         ctx->free_bufs = cl->next;
1025
1026         cl->buf->shadow = NULL;
1027         cl->next = NULL;
1028
1029         return cl;
1030     }
1031
1032     cl = ngx_alloc_chain_link(pool);
1033     if (cl == NULL) {
1034         return NULL;
1035     }
1036
1037     cl->buf = ngx_calloc_buf(pool);
1038     if (cl->buf == NULL) {
1039         return NULL;
1040     }
1041
1042     cl->next = NULL;
1043
1044     cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
1045
1046     return cl;
1047 }
1048
1049
1050 static ngx_chain_t *
1051 ngx_http_charset_get_buffer(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx,
1052     size_t size)
1053 {
1054     ngx_buf_t    *b;
1055     ngx_chain_t  *cl, **ll;
1056
1057     for (ll = &ctx->free_buffers, cl = ctx->free_buffers;
1058          cl;
1059          ll = &cl->next, cl = cl->next)
1060     {
1061         b = cl->buf;
1062
1063         if ((size_t) (b->end - b->start) >= size) {
1064             *ll = cl->next;
1065             cl->next = NULL;
1066
1067             b->pos = b->start;
1068             b->temporary = 1;
1069             b->shadow = NULL;
1070
1071             return cl;
1072         }
1073     }
1074
1075     cl = ngx_alloc_chain_link(pool);
1076     if (cl == NULL) {
1077         return NULL;
1078     }
1079
1080     cl->buf = ngx_create_temp_buf(pool, size);
1081     if (cl->buf == NULL) {
1082         return NULL;
1083     }
1084
1085     cl->next = NULL;
1086
1087     cl->buf->temporary = 1;
1088     cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
1089
1090     return cl;
1091 }
1092
1093
1094 static char *
1095 ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1096 {
1097     ngx_http_charset_main_conf_t  *mcf = conf;
1098
1099     char                         *rv;
1100     u_char                       *p, *dst2src, **pp;
1101     ngx_int_t                     src, dst;
1102     ngx_uint_t                    i, n;
1103     ngx_str_t                    *value;
1104     ngx_conf_t                    pvcf;
1105     ngx_http_charset_t           *charset;
1106     ngx_http_charset_tables_t    *table;
1107     ngx_http_charset_conf_ctx_t   ctx;
1108
1109     value = cf->args->elts;
1110
1111     src = ngx_http_add_charset(&mcf->charsets, &value[1]);
1112     if (src == NGX_ERROR) {
1113         return NGX_CONF_ERROR;
1114     }
1115
1116     dst = ngx_http_add_charset(&mcf->charsets, &value[2]);
1117     if (dst == NGX_ERROR) {
1118         return NGX_CONF_ERROR;
1119     }
1120
1121     if (src == dst) {
1122         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1123                            "\"charset_map\" between the same charsets "
1124                            "\"%V\" and \"%V\"", &value[1], &value[2]);
1125         return NGX_CONF_ERROR;
1126     }
1127
1128     table = mcf->tables.elts;
1129     for (i = 0; i < mcf->tables.nelts; i++) {
1130         if ((src == table->src && dst == table->dst)
1131              || (src == table->dst && dst == table->src))
1132         {
1133             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1134                                "duplicate \"charset_map\" between "
1135                                "\"%V\" and \"%V\"", &value[1], &value[2]);
1136             return NGX_CONF_ERROR;
1137         }
1138     }
1139
1140     table = ngx_array_push(&mcf->tables);
1141     if (table == NULL) {
1142         return NGX_CONF_ERROR;
1143     }
1144
1145     table->src = src;
1146     table->dst = dst;
1147
1148     if (ngx_strcasecmp(value[2].data, (u_char *) "utf-8") == 0) {
1149         table->src2dst = ngx_pcalloc(cf->pool, 256 * NGX_UTF_LEN);
1150         if (table->src2dst == NULL) {
1151             return NGX_CONF_ERROR;
1152         }
1153
1154         table->dst2src = ngx_pcalloc(cf->pool, 256 * sizeof(void *));
1155         if (table->dst2src == NULL) {
1156             return NGX_CONF_ERROR;
1157         }
1158
1159         dst2src = ngx_pcalloc(cf->pool, 256);
1160         if (dst2src == NULL) {
1161             return NGX_CONF_ERROR;
1162         }
1163
1164         pp = (u_char **) &table->dst2src[0];
1165         pp[0] = dst2src;
1166
1167         for (i = 0; i < 128; i++) {
1168             p = &table->src2dst[i * NGX_UTF_LEN];
1169             p[0] = '\1';
1170             p[1] = (u_char) i;
1171             dst2src[i] = (u_char) i;
1172         }
1173
1174         for (/* void */; i < 256; i++) {
1175             p = &table->src2dst[i * NGX_UTF_LEN];
1176             p[0] = '\1';
1177             p[1] = '?';
1178         }
1179
1180     } else {
1181         table->src2dst = ngx_palloc(cf->pool, 256);
1182         if (table->src2dst == NULL) {
1183             return NGX_CONF_ERROR;
1184         }
1185
1186         table->dst2src = ngx_palloc(cf->pool, 256);
1187         if (table->dst2src == NULL) {
1188             return NGX_CONF_ERROR;
1189         }
1190
1191         for (i = 0; i < 128; i++) {
1192             table->src2dst[i] = (u_char) i;
1193             table->dst2src[i] = (u_char) i;
1194         }
1195
1196         for (/* void */; i < 256; i++) {
1197             table->src2dst[i] = '?';
1198             table->dst2src[i] = '?';
1199         }
1200     }
1201
1202     charset = mcf->charsets.elts;
1203
1204     ctx.table = table;
1205     ctx.charset = &charset[dst];
1206     ctx.characters = 0;
1207
1208     pvcf = *cf;
1209     cf->ctx = &ctx;
1210     cf->handler = ngx_http_charset_map;
1211     cf->handler_conf = conf;
1212
1213     rv = ngx_conf_parse(cf, NULL);
1214
1215     *cf = pvcf;
1216
1217     if (ctx.characters) {
1218         n = ctx.charset->length;
1219         ctx.charset->length /= ctx.characters;
1220
1221         if (((n * 10) / ctx.characters) % 10 > 4) {
1222             ctx.charset->length++;
1223         }
1224     }
1225
1226     return rv;
1227 }
1228
1229
1230 static char *
1231 ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
1232 {
1233     u_char                       *p, *dst2src, **pp;
1234     uint32_t                      n;
1235     ngx_int_t                     src, dst;
1236     ngx_str_t                    *value;
1237     ngx_uint_t                    i;
1238     ngx_http_charset_tables_t    *table;
1239     ngx_http_charset_conf_ctx_t  *ctx;
1240
1241     if (cf->args->nelts != 2) {
1242         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number");
1243         return NGX_CONF_ERROR;
1244     }
1245
1246     value = cf->args->elts;
1247
1248     src = ngx_hextoi(value[0].data, value[0].len);
1249     if (src == NGX_ERROR || src > 255) {
1250         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1251                            "invalid value \"%V\"", &value[0]);
1252         return NGX_CONF_ERROR;
1253     }
1254
1255     ctx = cf->ctx;
1256     table = ctx->table;
1257
1258     if (ctx->charset->utf8) {
1259         p = &table->src2dst[src * NGX_UTF_LEN];
1260
1261         *p++ = (u_char) (value[1].len / 2);
1262
1263         for (i = 0; i < value[1].len; i += 2) {
1264             dst = ngx_hextoi(&value[1].data[i], 2);
1265             if (dst == NGX_ERROR || dst > 255) {
1266                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1267                                    "invalid value \"%V\"", &value[1]);
1268                 return NGX_CONF_ERROR;
1269             }
1270
1271             *p++ = (u_char) dst;
1272         }
1273
1274         i /= 2;
1275
1276         ctx->charset->length += i;
1277         ctx->characters++;
1278
1279         p = &table->src2dst[src * NGX_UTF_LEN] + 1;
1280
1281         n = ngx_utf8_decode(&p, i);
1282
1283         if (n > 0xffff) {
1284             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1285                                "invalid value \"%V\"", &value[1]);
1286             return NGX_CONF_ERROR;
1287         }
1288
1289         pp = (u_char **) &table->dst2src[0];
1290
1291         dst2src = pp[n >> 8];
1292
1293         if (dst2src == NULL) {
1294             dst2src = ngx_pcalloc(cf->pool, 256);
1295             if (dst2src == NULL) {
1296                 return NGX_CONF_ERROR;
1297             }
1298
1299             pp[n >> 8] = dst2src;
1300         }
1301
1302         dst2src[n & 0xff] = (u_char) src;
1303
1304     } else {
1305         dst = ngx_hextoi(value[1].data, value[1].len);
1306         if (dst == NGX_ERROR || dst > 255) {
1307             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1308                                "invalid value \"%V\"", &value[1]);
1309             return NGX_CONF_ERROR;
1310         }
1311
1312         table->src2dst[src] = (u_char) dst;
1313         table->dst2src[dst] = (u_char) src;
1314     }
1315
1316     return NGX_CONF_OK;
1317 }
1318
1319
1320 static char *
1321 ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1322 {
1323     char  *p = conf;
1324
1325     ngx_int_t                     *cp;
1326     ngx_str_t                     *value, var;
1327     ngx_http_charset_main_conf_t  *mcf;
1328
1329     cp = (ngx_int_t *) (p + cmd->offset);
1330
1331     if (*cp != NGX_CONF_UNSET) {
1332         return "is duplicate";
1333     }
1334
1335     value = cf->args->elts;
1336
1337     if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, charset)
1338         && ngx_strcmp(value[1].data, "off") == 0)
1339     {
1340         *cp = NGX_HTTP_NO_CHARSET;
1341         return NGX_CONF_OK;
1342     }
1343
1344
1345     if (value[1].data[0] == '$') {
1346         var.len = value[1].len - 1;
1347         var.data = value[1].data + 1;
1348
1349         *cp = ngx_http_get_variable_index(cf, &var);
1350
1351         if (*cp == NGX_ERROR) {
1352             return NGX_CONF_ERROR;
1353         }
1354
1355         *cp += NGX_HTTP_CHARSET_VAR;
1356
1357         return NGX_CONF_OK;
1358     }
1359
1360     mcf = ngx_http_conf_get_module_main_conf(cf,
1361                                              ngx_http_charset_filter_module);
1362
1363     *cp = ngx_http_add_charset(&mcf->charsets, &value[1]);
1364     if (*cp == NGX_ERROR) {
1365         return NGX_CONF_ERROR;
1366     }
1367
1368     return NGX_CONF_OK;
1369 }
1370
1371
1372 static ngx_int_t
1373 ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name)
1374 {
1375     ngx_uint_t           i;
1376     ngx_http_charset_t  *c;
1377
1378     c = charsets->elts;
1379     for (i = 0; i < charsets->nelts; i++) {
1380         if (name->len != c[i].name.len) {
1381             continue;
1382         }
1383
1384         if (ngx_strcasecmp(name->data, c[i].name.data) == 0) {
1385             break;
1386         }
1387     }
1388
1389     if (i < charsets->nelts) {
1390         return i;
1391     }
1392
1393     c = ngx_array_push(charsets);
1394     if (c == NULL) {
1395         return NGX_ERROR;
1396     }
1397
1398     c->tables = NULL;
1399     c->name = *name;
1400     c->length = 0;
1401
1402     if (ngx_strcasecmp(name->data, (u_char *) "utf-8") == 0) {
1403         c->utf8 = 1;
1404
1405     } else {
1406         c->utf8 = 0;
1407     }
1408
1409     return i;
1410 }
1411
1412
1413 static void *
1414 ngx_http_charset_create_main_conf(ngx_conf_t *cf)
1415 {
1416     ngx_http_charset_main_conf_t  *mcf;
1417
1418     mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t));
1419     if (mcf == NULL) {
1420         return NGX_CONF_ERROR;
1421     }
1422
1423     if (ngx_array_init(&mcf->charsets, cf->pool, 2, sizeof(ngx_http_charset_t))
1424         != NGX_OK)
1425     {
1426         return NGX_CONF_ERROR;
1427     }
1428
1429     if (ngx_array_init(&mcf->tables, cf->pool, 1,
1430                        sizeof(ngx_http_charset_tables_t))
1431         != NGX_OK)
1432     {
1433         return NGX_CONF_ERROR;
1434     }
1435
1436     if (ngx_array_init(&mcf->recodes, cf->pool, 2,
1437                        sizeof(ngx_http_charset_recode_t))
1438         != NGX_OK)
1439     {
1440         return NGX_CONF_ERROR;
1441     }
1442
1443     return mcf;
1444 }
1445
1446
1447 static void *
1448 ngx_http_charset_create_loc_conf(ngx_conf_t *cf)
1449 {
1450     ngx_http_charset_loc_conf_t  *lcf;
1451
1452     lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t));
1453     if (lcf == NULL) {
1454         return NGX_CONF_ERROR;
1455     }
1456
1457     /*
1458      * set by ngx_pcalloc():
1459      *
1460      *     lcf->types = { NULL };
1461      *     lcf->types_keys = NULL;
1462      */
1463
1464     lcf->charset = NGX_CONF_UNSET;
1465     lcf->source_charset = NGX_CONF_UNSET;
1466     lcf->override_charset = NGX_CONF_UNSET;
1467
1468     return lcf;
1469 }
1470
1471
1472 static char *
1473 ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1474 {
1475     ngx_http_charset_loc_conf_t *prev = parent;
1476     ngx_http_charset_loc_conf_t *conf = child;
1477
1478     ngx_uint_t                     i;
1479     ngx_http_charset_recode_t     *recode;
1480     ngx_http_charset_main_conf_t  *mcf;
1481
1482     if (ngx_http_merge_types(cf, conf->types_keys, &conf->types,
1483                              prev->types_keys, &prev->types,
1484                              ngx_http_charset_default_types)
1485         != NGX_OK)
1486     {
1487         return NGX_CONF_ERROR;
1488     }
1489
1490     ngx_conf_merge_value(conf->override_charset, prev->override_charset, 0);
1491     ngx_conf_merge_value(conf->charset, prev->charset, NGX_HTTP_NO_CHARSET);
1492
1493     if (conf->source_charset == NGX_CONF_UNSET) {
1494         conf->source_charset = prev->source_charset;
1495     }
1496
1497     if (conf->charset == NGX_HTTP_NO_CHARSET
1498         || conf->source_charset == NGX_CONF_UNSET
1499         || conf->charset == conf->source_charset)
1500     {
1501         return NGX_CONF_OK;
1502     }
1503
1504     if (conf->source_charset >= NGX_HTTP_CHARSET_VAR
1505         || conf->charset >= NGX_HTTP_CHARSET_VAR)
1506     {
1507         return NGX_CONF_OK;
1508     }
1509
1510     mcf = ngx_http_conf_get_module_main_conf(cf,
1511                                              ngx_http_charset_filter_module);
1512     recode = mcf->recodes.elts;
1513     for (i = 0; i < mcf->recodes.nelts; i++) {
1514         if (conf->source_charset == recode[i].src
1515             && conf->charset == recode[i].dst)
1516         {
1517             return NGX_CONF_OK;
1518         }
1519     }
1520
1521     recode = ngx_array_push(&mcf->recodes);
1522     if (recode == NULL) {
1523         return NGX_CONF_ERROR;
1524     }
1525
1526     recode->src = conf->source_charset;
1527     recode->dst = conf->charset;
1528
1529     return NGX_CONF_OK;
1530 }
1531
1532
1533 static ngx_int_t
1534 ngx_http_charset_postconfiguration(ngx_conf_t *cf)
1535 {
1536     u_char                       **src, **dst;
1537     ngx_int_t                      c;
1538     ngx_uint_t                     i, t;
1539     ngx_http_charset_t            *charset;
1540     ngx_http_charset_recode_t     *recode;
1541     ngx_http_charset_tables_t     *tables;
1542     ngx_http_charset_main_conf_t  *mcf;
1543
1544     mcf = ngx_http_conf_get_module_main_conf(cf,
1545                                              ngx_http_charset_filter_module);
1546
1547     recode = mcf->recodes.elts;
1548     tables = mcf->tables.elts;
1549     charset = mcf->charsets.elts;
1550
1551     for (i = 0; i < mcf->recodes.nelts; i++) {
1552
1553         c = recode[i].src;
1554
1555         for (t = 0; t < mcf->tables.nelts; t++) {
1556
1557             if (c == tables[t].src && recode[i].dst == tables[t].dst) {
1558                 goto next;
1559             }
1560
1561             if (c == tables[t].dst && recode[i].dst == tables[t].src) {
1562                 goto next;
1563             }
1564         }
1565
1566         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
1567                    "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
1568                    &charset[c].name, &charset[recode[i].dst].name);
1569         return NGX_ERROR;
1570
1571     next:
1572         continue;
1573     }
1574
1575
1576     for (t = 0; t < mcf->tables.nelts; t++) {
1577
1578         src = charset[tables[t].src].tables;
1579
1580         if (src == NULL) {
1581             src = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
1582             if (src == NULL) {
1583                 return NGX_ERROR;
1584             }
1585
1586             charset[tables[t].src].tables = src;
1587         }
1588
1589         dst = charset[tables[t].dst].tables;
1590
1591         if (dst == NULL) {
1592             dst = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
1593             if (dst == NULL) {
1594                 return NGX_ERROR;
1595             }
1596
1597             charset[tables[t].dst].tables = dst;
1598         }
1599
1600         src[tables[t].dst] = tables[t].src2dst;
1601         dst[tables[t].src] = tables[t].dst2src;
1602     }
1603
1604     ngx_http_next_header_filter = ngx_http_top_header_filter;
1605     ngx_http_top_header_filter = ngx_http_charset_header_filter;
1606
1607     ngx_http_next_body_filter = ngx_http_top_body_filter;
1608     ngx_http_top_body_filter = ngx_http_charset_body_filter;
1609
1610     return NGX_OK;
1611 }