upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / experimental / mod_charset_lite.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * simple hokey charset recoding configuration module
19  *
20  * See mod_ebcdic and mod_charset for more thought-out examples.  This
21  * one is just so Jeff can learn how a module works and experiment with
22  * basic character set recoding configuration.
23  *
24  * !!!This is an extremely cheap ripoff of mod_charset.c from Russian Apache!!!
25  */
26
27 #include "httpd.h"
28 #include "http_config.h"
29 #define CORE_PRIVATE
30 #include "http_core.h"
31 #include "http_log.h"
32 #include "http_main.h"
33 #include "http_protocol.h"
34 #include "http_request.h"
35 #include "util_charset.h"
36 #include "apr_buckets.h"
37 #include "util_filter.h"
38 #include "apr_strings.h"
39 #include "apr_lib.h"
40 #include "apr_xlate.h"
41 #define APR_WANT_STRFUNC
42 #include "apr_want.h"
43
44 #define OUTPUT_XLATE_BUF_SIZE (16*1024) /* size of translation buffer used on output */
45 #define INPUT_XLATE_BUF_SIZE  (8*1024)  /* size of translation buffer used on input */
46
47 #define XLATE_MIN_BUFF_LEFT 128  /* flush once there is no more than this much
48                                   * space left in the translation buffer 
49                                   */
50
51 #define FATTEST_CHAR  8          /* we don't handle chars wider than this that straddle 
52                                   * two buckets
53                                   */
54
55 /* extended error status codes; this is used in addition to an apr_status_t to
56  * track errors in the translation filter
57  */
58 typedef enum {
59     EES_INIT = 0,   /* no error info yet; value must be 0 for easy init */
60     EES_LIMIT,      /* built-in restriction encountered */
61     EES_INCOMPLETE_CHAR, /* incomplete multi-byte char at end of content */
62     EES_BUCKET_READ,
63     EES_DOWNSTREAM, /* something bad happened in a filter below xlate */
64     EES_BAD_INPUT   /* input data invalid */
65 } ees_t;
66
67 /* registered name of the output translation filter */
68 #define XLATEOUT_FILTER_NAME "XLATEOUT"
69 /* registered name of input translation filter */
70 #define XLATEIN_FILTER_NAME  "XLATEIN" 
71
72 typedef struct charset_dir_t {
73     /** debug level; -1 means uninitialized, 0 means no debug */
74     int debug;
75     const char *charset_source; /* source encoding */
76     const char *charset_default; /* how to ship on wire */
77     /** module does ap_add_*_filter()? */    
78     enum {IA_INIT, IA_IMPADD, IA_NOIMPADD} implicit_add; 
79 } charset_dir_t;
80
81 /* charset_filter_ctx_t is created for each filter instance; because the same
82  * filter code is used for translating in both directions, we need this context
83  * data to tell the filter which translation handle to use; it also can hold a
84  * character which was split between buckets
85  */
86 typedef struct charset_filter_ctx_t {
87     apr_xlate_t *xlate;
88     charset_dir_t *dc;
89     ees_t ees;              /* extended error status */
90     apr_size_t saved;
91     char buf[FATTEST_CHAR]; /* we want to be able to build a complete char here */
92     int ran;                /* has filter instance run before? */
93     int noop;               /* should we pass brigades through unchanged? */
94     char *tmp;              /* buffer for input filtering */
95     apr_bucket_brigade *bb; /* input buckets we couldn't finish translating */
96 } charset_filter_ctx_t;
97
98 /* charset_req_t is available via r->request_config if any translation is
99  * being performed
100  */
101 typedef struct charset_req_t {
102     charset_dir_t *dc;
103     charset_filter_ctx_t *output_ctx, *input_ctx;
104 } charset_req_t;
105
106 /* debug level definitions */
107 #define DBGLVL_GORY           9 /* gory details */
108 #define DBGLVL_FLOW           4 /* enough messages to see what happens on
109                                  * each request */
110 #define DBGLVL_PMC            2 /* messages about possible misconfiguration */
111
112 module AP_MODULE_DECLARE_DATA charset_lite_module;
113
114 static void *create_charset_dir_conf(apr_pool_t *p,char *dummy)
115 {
116     charset_dir_t *dc = (charset_dir_t *)apr_pcalloc(p,sizeof(charset_dir_t));
117
118     dc->debug = -1;
119     return dc;
120 }
121
122 static void *merge_charset_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
123 {
124     charset_dir_t *a = (charset_dir_t *)apr_pcalloc (p, sizeof(charset_dir_t));
125     charset_dir_t *base = (charset_dir_t *)basev,
126         *over = (charset_dir_t *)overridesv;
127
128     /* If it is defined in the current container, use it.  Otherwise, use the one
129      * from the enclosing container. 
130      */
131
132     a->debug = 
133         over->debug != -1 ? over->debug : base->debug;
134     a->charset_default = 
135         over->charset_default ? over->charset_default : base->charset_default;
136     a->charset_source = 
137         over->charset_source ? over->charset_source : base->charset_source;
138     a->implicit_add =
139         over->implicit_add != IA_INIT ? over->implicit_add : base->implicit_add;
140     return a;
141 }
142
143 /* CharsetSourceEnc charset
144  */
145 static const char *add_charset_source(cmd_parms *cmd, void *in_dc,
146                                       const char *name)
147 {
148     charset_dir_t *dc = in_dc;
149
150     dc->charset_source = name;
151     return NULL;
152 }
153
154 /* CharsetDefault charset
155  */
156 static const char *add_charset_default(cmd_parms *cmd, void *in_dc, 
157                                        const char *name)
158 {
159     charset_dir_t *dc = in_dc;
160
161     dc->charset_default = name;
162     return NULL;
163 }
164
165 /* CharsetOptions optionflag...
166  */
167 static const char *add_charset_options(cmd_parms *cmd, void *in_dc, 
168                                        const char *flag)
169 {
170     charset_dir_t *dc = in_dc;
171
172     if (!strcasecmp(flag, "ImplicitAdd")) {
173         dc->implicit_add = IA_IMPADD;
174     }
175     else if (!strcasecmp(flag, "NoImplicitAdd")) {
176         dc->implicit_add = IA_NOIMPADD;
177     }
178     else if (!strncasecmp(flag, "DebugLevel=", 11)) {
179         dc->debug = atoi(flag + 11);
180     }
181     else {
182         return apr_pstrcat(cmd->temp_pool, 
183                            "Invalid CharsetOptions option: ",
184                            flag,
185                            NULL);
186     }
187
188     return NULL;
189 }
190
191 /* find_code_page() is a fixup hook that decides if translation should be
192  * enabled; if so, it sets up request data for use by the filter registration
193  * hook so that it knows what to do
194  */
195 static int find_code_page(request_rec *r)
196 {
197     charset_dir_t *dc = ap_get_module_config(r->per_dir_config, 
198                                              &charset_lite_module);
199     charset_req_t *reqinfo;
200     charset_filter_ctx_t *input_ctx, *output_ctx;
201     apr_status_t rv;
202     const char *mime_type;
203
204     if (dc->debug >= DBGLVL_FLOW) {
205         ap_log_rerror(APLOG_MARK,APLOG_DEBUG, 0, r,
206                       "uri: %s file: %s method: %d "
207                       "imt: %s flags: %s%s%s %s->%s",
208                       r->uri, r->filename, r->method_number,
209                       r->content_type ? r->content_type : "(unknown)",
210                       r->main     ? "S" : "",    /* S if subrequest */
211                       r->prev     ? "R" : "",    /* R if redirect */
212                       r->proxyreq ? "P" : "",    /* P if proxy */
213                       dc->charset_source, dc->charset_default);
214     }
215
216     /* If we don't have a full directory configuration, bail out.
217      */
218     if (!dc->charset_source || !dc->charset_default) {
219         if (dc->debug >= DBGLVL_PMC) {
220             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
221                           "incomplete configuration: src %s, dst %s",
222                           dc->charset_source ? dc->charset_source : "unspecified",
223                           dc->charset_default ? dc->charset_default : "unspecified");
224         }
225         return DECLINED;
226     }
227
228     /* catch proxy requests */
229     if (r->proxyreq) return DECLINED;
230     /* mod_rewrite indicators */
231     if (!strncmp(r->filename, "redirect:", 9)) return DECLINED; 
232     if (!strncmp(r->filename, "gone:", 5)) return DECLINED; 
233     if (!strncmp(r->filename, "passthrough:", 12)) return DECLINED; 
234     if (!strncmp(r->filename, "forbidden:", 10)) return DECLINED; 
235     
236     mime_type = r->content_type ? r->content_type : ap_default_type(r);
237
238     /* If mime type isn't text or message, bail out.
239      */
240
241 /* XXX When we handle translation of the request body, watch out here as
242  *     1.3 allowed additional mime types: multipart and 
243  *     application/x-www-form-urlencoded
244  */
245              
246     if (strncasecmp(mime_type, "text/", 5) &&
247 #if APR_CHARSET_EBCDIC || AP_WANT_DIR_TRANSLATION
248         /* On an EBCDIC machine, be willing to translate mod_autoindex-
249          * generated output.  Otherwise, it doesn't look too cool.
250          *
251          * XXX This isn't a perfect fix because this doesn't trigger us
252          * to convert from the charset of the source code to ASCII.  The
253          * general solution seems to be to allow a generator to set an
254          * indicator in the r specifying that the body is coded in the
255          * implementation character set (i.e., the charset of the source
256          * code).  This would get several different types of documents
257          * translated properly: mod_autoindex output, mod_status output,
258          * mod_info output, hard-coded error documents, etc.
259          */
260         strcmp(mime_type, DIR_MAGIC_TYPE) &&
261 #endif
262         strncasecmp(mime_type, "message/", 8)) {
263         if (dc->debug >= DBGLVL_GORY) {
264             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
265                           "mime type is %s; no translation selected",
266                           mime_type);
267         }
268         return DECLINED;
269     }
270
271     if (dc->debug >= DBGLVL_GORY) {
272         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
273                       "charset_source: %s charset_default: %s",
274                       dc && dc->charset_source ? dc->charset_source : "(none)",
275                       dc && dc->charset_default ? dc->charset_default : "(none)");
276     }
277
278     /* Get storage for the request data and the output filter context.
279      * We rarely need the input filter context, so allocate that separately.
280      */
281     reqinfo = (charset_req_t *)apr_pcalloc(r->pool, 
282                                            sizeof(charset_req_t) + 
283                                            sizeof(charset_filter_ctx_t));
284     output_ctx = (charset_filter_ctx_t *)(reqinfo + 1);
285
286     reqinfo->dc = dc;
287     output_ctx->dc = dc;
288     ap_set_module_config(r->request_config, &charset_lite_module, reqinfo);
289
290     reqinfo->output_ctx = output_ctx;
291     rv = apr_xlate_open(&output_ctx->xlate, 
292                         dc->charset_default, dc->charset_source, r->pool);
293     if (rv != APR_SUCCESS) {
294         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
295                       "can't open translation %s->%s",
296                       dc->charset_source, dc->charset_default);
297         return HTTP_INTERNAL_SERVER_ERROR;
298     }
299
300     switch (r->method_number) {
301     case M_PUT:
302     case M_POST:
303         /* Set up input translation.  Note: A request body can be included 
304          * with the OPTIONS method, but for now we don't set up translation 
305          * of it.
306          */
307         input_ctx = apr_pcalloc(r->pool, sizeof(charset_filter_ctx_t));
308         input_ctx->bb = apr_brigade_create(r->pool,
309                                            r->connection->bucket_alloc);
310         input_ctx->tmp = apr_palloc(r->pool, INPUT_XLATE_BUF_SIZE);
311         input_ctx->dc = dc;
312         reqinfo->input_ctx = input_ctx;
313         rv = apr_xlate_open(&input_ctx->xlate, dc->charset_source, 
314                             dc->charset_default, r->pool);
315         if (rv != APR_SUCCESS) {
316             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
317                           "can't open translation %s->%s",
318                           dc->charset_default, dc->charset_source);
319             return HTTP_INTERNAL_SERVER_ERROR;
320         }
321     }
322
323     return DECLINED;
324 }
325
326 static int configured_in_list(request_rec *r, const char *filter_name,
327                               struct ap_filter_t *filter_list)
328 {
329     struct ap_filter_t *filter = filter_list;
330
331     while (filter) {
332         if (!strcasecmp(filter_name, filter->frec->name)) {
333             return 1;
334         }
335         filter = filter->next;
336     }
337     return 0;
338 }
339
340 static int configured_on_input(request_rec *r, const char *filter_name)
341 {
342     return configured_in_list(r, filter_name, r->input_filters);
343 }
344
345 static int configured_on_output(request_rec *r, const char *filter_name)
346 {
347     return configured_in_list(r, filter_name, r->output_filters);
348 }
349
350 /* xlate_insert_filter() is a filter hook which decides whether or not
351  * to insert a translation filter for the current request.
352  */
353 static void xlate_insert_filter(request_rec *r)
354 {
355     /* Hey... don't be so quick to use reqinfo->dc here; reqinfo may be NULL */
356     charset_req_t *reqinfo = ap_get_module_config(r->request_config, 
357                                                   &charset_lite_module);
358     charset_dir_t *dc = ap_get_module_config(r->per_dir_config, 
359                                              &charset_lite_module);
360
361     if (reqinfo) {
362         if (reqinfo->output_ctx && !configured_on_output(r, XLATEOUT_FILTER_NAME)) {
363             ap_add_output_filter(XLATEOUT_FILTER_NAME, reqinfo->output_ctx, r, 
364                                  r->connection);
365         }
366         else if (dc->debug >= DBGLVL_FLOW) {
367             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
368                           "xlate output filter not added implicitly because %s",
369                           !reqinfo->output_ctx ? 
370                           "no output configuration available" :
371                           "another module added the filter");
372         }
373
374         if (reqinfo->input_ctx && !configured_on_input(r, XLATEIN_FILTER_NAME)) {
375             ap_add_input_filter(XLATEIN_FILTER_NAME, reqinfo->input_ctx, r,
376                                 r->connection);
377         }
378         else if (dc->debug >= DBGLVL_FLOW) {
379             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
380                           "xlate input filter not added implicitly because %s",
381                           !reqinfo->input_ctx ?
382                           "no input configuration available" :
383                           "another module added the filter");
384         }
385     }
386 }
387
388 /* stuff that sucks that I know of:
389  *
390  * bucket handling:
391  *  why create an eos bucket when we see it come down the stream?  just send the one
392  *  passed as input...  news flash: this will be fixed when xlate_out_filter() starts
393  *  using the more generic xlate_brigade()
394  *
395  * translation mechanics:
396  *   we don't handle characters that straddle more than two buckets; an error
397  *   will be generated
398  */
399
400 /* send_downstream() is passed the translated data; it puts it in a single-
401  * bucket brigade and passes the brigade to the next filter
402  */
403 static apr_status_t send_downstream(ap_filter_t *f, const char *tmp, apr_size_t len)
404 {
405     request_rec *r = f->r;
406     conn_rec *c = r->connection;
407     apr_bucket_brigade *bb;
408     apr_bucket *b;
409     charset_filter_ctx_t *ctx = f->ctx;
410     apr_status_t rv;
411
412     bb = apr_brigade_create(r->pool, c->bucket_alloc);
413     b = apr_bucket_transient_create(tmp, len, c->bucket_alloc);
414     APR_BRIGADE_INSERT_TAIL(bb, b);
415     rv = ap_pass_brigade(f->next, bb);
416     if (rv != APR_SUCCESS) {
417         ctx->ees = EES_DOWNSTREAM;
418     }
419     return rv;
420 }
421
422 static apr_status_t send_eos(ap_filter_t *f)
423 {
424     request_rec *r = f->r;
425     conn_rec *c = r->connection;
426     apr_bucket_brigade *bb;
427     apr_bucket *b;
428     charset_filter_ctx_t *ctx = f->ctx;
429     apr_status_t rv;
430
431     bb = apr_brigade_create(r->pool, c->bucket_alloc);
432     b = apr_bucket_eos_create(c->bucket_alloc);
433     APR_BRIGADE_INSERT_TAIL(bb, b);
434     rv = ap_pass_brigade(f->next, bb);
435     if (rv != APR_SUCCESS) {
436         ctx->ees = EES_DOWNSTREAM;
437     }
438     return rv;
439 }
440
441 static apr_status_t set_aside_partial_char(charset_filter_ctx_t *ctx, 
442                                            const char *partial,
443                                            apr_size_t partial_len)
444 {
445     apr_status_t rv;
446
447     if (sizeof(ctx->buf) > partial_len) {
448         ctx->saved = partial_len;
449         memcpy(ctx->buf, partial, partial_len);
450         rv = APR_SUCCESS;
451     }
452     else {
453         rv = APR_INCOMPLETE;
454         ctx->ees = EES_LIMIT; /* we don't handle chars this wide which straddle 
455                                * buckets 
456                                */
457     }
458     return rv;
459 }
460
461 static apr_status_t finish_partial_char(charset_filter_ctx_t *ctx,
462                                         /* input buffer: */
463                                         const char **cur_str, 
464                                         apr_size_t *cur_len,
465                                         /* output buffer: */
466                                         char **out_str,
467                                         apr_size_t *out_len)
468 {
469     apr_status_t rv;
470     apr_size_t tmp_input_len;
471
472     /* Keep adding bytes from the input string to the saved string until we
473      *    1) finish the input char
474      *    2) get an error
475      * or 3) run out of bytes to add
476      */
477
478     do {
479         ctx->buf[ctx->saved] = **cur_str;
480         ++ctx->saved;
481         ++*cur_str;
482         --*cur_len;
483         tmp_input_len = ctx->saved;
484         rv = apr_xlate_conv_buffer(ctx->xlate,
485                                    ctx->buf,
486                                    &tmp_input_len,
487                                    *out_str,
488                                    out_len);
489     } while (rv == APR_INCOMPLETE && *cur_len);
490
491     if (rv == APR_SUCCESS) {
492         ctx->saved = 0;
493     }
494     else {
495         ctx->ees = EES_LIMIT; /* code isn't smart enough to handle chars
496                                * straddling more than two buckets
497                                */
498     }
499
500     return rv;
501 }
502
503 static void log_xlate_error(ap_filter_t *f, apr_status_t rv)
504 {
505     charset_filter_ctx_t *ctx = f->ctx;
506     const char *msg;
507     char msgbuf[100];
508     int cur;
509
510     switch(ctx->ees) {
511     case EES_LIMIT:
512         rv = 0;
513         msg = "xlate filter - a built-in restriction was encountered";
514         break;
515     case EES_BAD_INPUT:
516         rv = 0;
517         msg = "xlate filter - an input character was invalid";
518         break;
519     case EES_BUCKET_READ:
520         rv = 0;
521         msg = "xlate filter - bucket read routine failed";
522         break;
523     case EES_INCOMPLETE_CHAR:
524         rv = 0;
525         strcpy(msgbuf, "xlate filter - incomplete char at end of input - ");
526         cur = 0;
527         while ((apr_size_t)cur < ctx->saved) {
528             apr_snprintf(msgbuf + strlen(msgbuf), sizeof(msgbuf) - strlen(msgbuf), 
529                          "%02X", (unsigned)ctx->buf[cur]);
530             ++cur;
531         }
532         msg = msgbuf;
533         break;
534     case EES_DOWNSTREAM:
535         msg = "xlate filter - an error occurred in a lower filter";
536         break;
537     default:
538         msg = "xlate filter - returning error";
539     }
540     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
541                   "%s", msg);
542 }
543
544 /* chk_filter_chain() is called once per filter instance; it tries to
545  * determine if the current filter instance should be disabled because
546  * its translation is incompatible with the translation of an existing
547  * instance of the translate filter
548  *
549  * Example bad scenario:
550  *
551  *   configured filter chain for the request:
552  *     INCLUDES XLATEOUT(8859-1->UTS-16)
553  *   configured filter chain for the subrequest:
554  *     XLATEOUT(8859-1->UTS-16)
555  *
556  *   When the subrequest is processed, the filter chain will be
557  *     XLATEOUT(8859-1->UTS-16) XLATEOUT(8859-1->UTS-16)
558  *   This makes no sense, so the instance of XLATEOUT added for the
559  *   subrequest will be noop-ed.
560  *
561  * Example good scenario:
562  *
563  *   configured filter chain for the request:
564  *     INCLUDES XLATEOUT(8859-1->UTS-16)
565  *   configured filter chain for the subrequest:
566  *     XLATEOUT(IBM-1047->8859-1)
567  *
568  *   When the subrequest is processed, the filter chain will be
569  *     XLATEOUT(IBM-1047->8859-1) XLATEOUT(8859-1->UTS-16)
570  *   This makes sense, so the instance of XLATEOUT added for the
571  *   subrequest will be left alone and it will translate from
572  *   IBM-1047->8859-1.
573  */
574 static void chk_filter_chain(ap_filter_t *f)
575 {
576     ap_filter_t *curf;
577     charset_filter_ctx_t *curctx, *last_xlate_ctx = NULL,
578         *ctx = f->ctx;
579     int debug = ctx->dc->debug;
580     int output = !strcasecmp(f->frec->name, XLATEOUT_FILTER_NAME);
581
582     if (ctx->noop) {
583         return;
584     }
585
586     /* walk the filter chain; see if it makes sense for our filter to
587      * do any translation
588      */
589     curf = output ? f->r->output_filters : f->r->input_filters;
590     while (curf) {
591         if (!strcasecmp(curf->frec->name, f->frec->name) &&
592             curf->ctx) {
593             curctx = (charset_filter_ctx_t *)curf->ctx;
594             if (!last_xlate_ctx) {
595                 last_xlate_ctx = curctx;
596             }
597             else {
598                 if (strcmp(last_xlate_ctx->dc->charset_default,
599                            curctx->dc->charset_source)) {
600                     /* incompatible translation 
601                      * if our filter instance is incompatible with an instance
602                      * already in place, noop our instance
603                      * Notes: 
604                      * . We are only willing to noop our own instance.
605                      * . It is possible to noop another instance which has not
606                      *   yet run, but this is not currently implemented.
607                      *   Hopefully it will not be needed.
608                      * . It is not possible to noop an instance which has 
609                      *   already run.
610                      */
611                     if (last_xlate_ctx == f->ctx) {
612                         last_xlate_ctx->noop = 1;
613                         if (debug >= DBGLVL_PMC) {
614                             const char *symbol = output ? "->" : "<-";
615
616                             ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
617                                           0, f->r,
618                                           "%s %s - disabling "
619                                           "translation %s%s%s; existing "
620                                           "translation %s%s%s",
621                                           f->r->uri ? "uri" : "file",
622                                           f->r->uri ? f->r->uri : f->r->filename,
623                                           last_xlate_ctx->dc->charset_source,
624                                           symbol,
625                                           last_xlate_ctx->dc->charset_default,
626                                           curctx->dc->charset_source,
627                                           symbol,
628                                           curctx->dc->charset_default);
629                         }
630                     }
631                     else {
632                         const char *symbol = output ? "->" : "<-";
633
634                         ap_log_rerror(APLOG_MARK, APLOG_ERR,
635                                       0, f->r,
636                                       "chk_filter_chain() - can't disable "
637                                       "translation %s%s%s; existing "
638                                       "translation %s%s%s",
639                                       last_xlate_ctx->dc->charset_source,
640                                       symbol,
641                                       last_xlate_ctx->dc->charset_default,
642                                       curctx->dc->charset_source,
643                                       symbol,
644                                       curctx->dc->charset_default);
645                     }
646                     break;
647                 }
648             }
649         }
650         curf = curf->next;
651     }
652 }
653
654 /* xlate_brigade() is used to filter request and response bodies
655  *
656  * we'll stop when one of the following occurs:
657  * . we run out of buckets
658  * . we run out of space in the output buffer
659  * . we hit an error
660  *
661  * inputs:
662  *   bb:               brigade to process
663  *   buffer:           storage to hold the translated characters
664  *   buffer_size:      size of buffer
665  *   (and a few more uninteresting parms)
666  *
667  * outputs:
668  *   return value:     APR_SUCCESS or some error code
669  *   bb:               we've removed any buckets representing the
670  *                     translated characters; the eos bucket, if
671  *                     present, will be left in the brigade
672  *   buffer:           filled in with translated characters
673  *   buffer_size:      updated with the bytes remaining
674  *   hit_eos:          did we hit an EOS bucket?
675  */
676 static apr_status_t xlate_brigade(charset_filter_ctx_t *ctx,
677                                   apr_bucket_brigade *bb,
678                                   char *buffer, 
679                                   apr_size_t *buffer_avail,
680                                   int *hit_eos)
681 {
682     apr_bucket *b = NULL; /* set to NULL only to quiet some gcc */
683     apr_bucket *consumed_bucket;
684     const char *bucket;
685     apr_size_t bytes_in_bucket; /* total bytes read from current bucket */
686     apr_size_t bucket_avail;    /* bytes left in current bucket */
687     apr_status_t rv = APR_SUCCESS;
688
689     *hit_eos = 0;
690     bucket_avail = 0;
691     consumed_bucket = NULL;
692     while (1) {
693         if (!bucket_avail) { /* no bytes left to process in the current bucket... */
694             if (consumed_bucket) {
695                 apr_bucket_delete(consumed_bucket);
696                 consumed_bucket = NULL;
697             }
698             b = APR_BRIGADE_FIRST(bb);
699             if (b == APR_BRIGADE_SENTINEL(bb) ||
700                 APR_BUCKET_IS_EOS(b)) {
701                 break;
702             }
703             rv = apr_bucket_read(b, &bucket, &bytes_in_bucket, APR_BLOCK_READ);
704             if (rv != APR_SUCCESS) {
705                 ctx->ees = EES_BUCKET_READ;
706                 break;
707             }
708             bucket_avail = bytes_in_bucket;
709             consumed_bucket = b;   /* for axing when we're done reading it */
710         }
711         if (bucket_avail) {
712             /* We've got data, so translate it. */
713             if (ctx->saved) {
714                 /* Rats... we need to finish a partial character from the previous
715                  * bucket.
716                  *
717                  * Strangely, finish_partial_char() increments the input buffer
718                  * pointer but does not increment the output buffer pointer.
719                  */
720                 apr_size_t old_buffer_avail = *buffer_avail;
721                 rv = finish_partial_char(ctx,
722                                          &bucket, &bucket_avail,
723                                          &buffer, buffer_avail);
724                 buffer += old_buffer_avail - *buffer_avail;
725             }
726             else {
727                 apr_size_t old_buffer_avail = *buffer_avail;
728                 apr_size_t old_bucket_avail = bucket_avail;
729                 rv = apr_xlate_conv_buffer(ctx->xlate,
730                                            bucket, &bucket_avail,
731                                            buffer,
732                                            buffer_avail);
733                 buffer  += old_buffer_avail - *buffer_avail;
734                 bucket  += old_bucket_avail - bucket_avail;
735                 
736                 if (rv == APR_INCOMPLETE) { /* partial character at end of input */
737                     /* We need to save the final byte(s) for next time; we can't
738                      * convert it until we look at the next bucket.
739                      */
740                     rv = set_aside_partial_char(ctx, bucket, bucket_avail);
741                     bucket_avail = 0;
742                 }
743             }
744             if (rv != APR_SUCCESS) {
745                 /* bad input byte or partial char too big to store */
746                 break;
747             }
748             if (*buffer_avail < XLATE_MIN_BUFF_LEFT) {
749                 /* if any data remains in the current bucket, split there */
750                 if (bucket_avail) {
751                     apr_bucket_split(b, bytes_in_bucket - bucket_avail);
752                 }
753                 apr_bucket_delete(b);
754                 break;
755             }
756         }
757     }
758
759     if (!APR_BRIGADE_EMPTY(bb)) {
760         b = APR_BRIGADE_FIRST(bb);
761         if (APR_BUCKET_IS_EOS(b)) {
762             /* Leave the eos bucket in the brigade for reporting to
763              * subsequent filters.
764              */
765             *hit_eos = 1;
766             if (ctx->saved) {
767                 /* Oops... we have a partial char from the previous bucket
768                  * that won't be completed because there's no more data.
769                  */
770                 rv = APR_INCOMPLETE;
771                 ctx->ees = EES_INCOMPLETE_CHAR;
772             }
773         }
774     }
775
776     return rv;
777 }
778
779 /* xlate_out_filter() handles (almost) arbitrary conversions from one charset 
780  * to another...
781  * translation is determined in the fixup hook (find_code_page), which is
782  * where the filter's context data is set up... the context data gives us
783  * the translation handle
784  */
785 static apr_status_t xlate_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
786 {
787     charset_req_t *reqinfo = ap_get_module_config(f->r->request_config,
788                                                   &charset_lite_module);
789     charset_dir_t *dc = ap_get_module_config(f->r->per_dir_config,
790                                              &charset_lite_module);
791     charset_filter_ctx_t *ctx = f->ctx;
792     apr_bucket *dptr, *consumed_bucket;
793     const char *cur_str;
794     apr_size_t cur_len, cur_avail;
795     char tmp[OUTPUT_XLATE_BUF_SIZE];
796     apr_size_t space_avail;
797     int done;
798     apr_status_t rv = APR_SUCCESS;
799
800     if (!ctx) { 
801         /* this is SetOutputFilter path; grab the preallocated context,
802          * if any; note that if we decided not to do anything in an earlier
803          * handler, we won't even have a reqinfo
804          */
805         if (reqinfo) {
806             ctx = f->ctx = reqinfo->output_ctx;
807             reqinfo->output_ctx = NULL; /* prevent SNAFU if user coded us twice
808                                          * in the filter chain; we can't have two
809                                          * instances using the same context
810                                          */
811         }
812         if (!ctx) {                   /* no idea how to translate; don't do anything */
813             ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(charset_filter_ctx_t));
814             ctx->dc = dc;
815             ctx->noop = 1;
816         }
817     }
818
819     if (dc->debug >= DBGLVL_GORY) {
820         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
821                      "xlate_out_filter() - "
822                      "charset_source: %s charset_default: %s",
823                      dc && dc->charset_source ? dc->charset_source : "(none)",
824                      dc && dc->charset_default ? dc->charset_default : "(none)");
825     }
826
827     if (!ctx->ran) {  /* filter never ran before */
828         chk_filter_chain(f);
829         ctx->ran = 1;
830     }
831
832     if (ctx->noop) {
833         return ap_pass_brigade(f->next, bb);
834     }
835
836     dptr = APR_BRIGADE_FIRST(bb);
837     done = 0;
838     cur_len = 0;
839     space_avail = sizeof(tmp);
840     consumed_bucket = NULL;
841     while (!done) {
842         if (!cur_len) { /* no bytes left to process in the current bucket... */
843             if (consumed_bucket) {
844                 apr_bucket_delete(consumed_bucket);
845                 consumed_bucket = NULL;
846             }
847             if (dptr == APR_BRIGADE_SENTINEL(bb)) {
848                 done = 1;
849                 break;
850             }
851             if (APR_BUCKET_IS_EOS(dptr)) {
852                 done = 1;
853                 cur_len = -1; /* XXX yuck, but that tells us to send
854                                  * eos down; when we minimize our bb construction
855                                  * we'll fix this crap */
856                 if (ctx->saved) {
857                     /* Oops... we have a partial char from the previous bucket
858                      * that won't be completed because there's no more data.
859                      */
860                     rv = APR_INCOMPLETE;
861                     ctx->ees = EES_INCOMPLETE_CHAR;
862                 }
863                 break;
864             }
865             rv = apr_bucket_read(dptr, &cur_str, &cur_len, APR_BLOCK_READ);
866             if (rv != APR_SUCCESS) {
867                 done = 1;
868                 ctx->ees = EES_BUCKET_READ;
869                 break;
870             }
871             consumed_bucket = dptr; /* for axing when we're done reading it */
872             dptr = APR_BUCKET_NEXT(dptr); /* get ready for when we access the 
873                                           * next bucket */
874         }
875         /* Try to fill up our tmp buffer with translated data. */
876         cur_avail = cur_len;
877
878         if (cur_len) { /* maybe we just hit the end of a pipe (len = 0) ? */
879             if (ctx->saved) {
880                 /* Rats... we need to finish a partial character from the previous
881                  * bucket.
882                  */
883                 char *tmp_tmp;
884                 
885                 tmp_tmp = tmp + sizeof(tmp) - space_avail;
886                 rv = finish_partial_char(ctx,
887                                          &cur_str, &cur_len,
888                                          &tmp_tmp, &space_avail);
889             }
890             else {
891                 rv = apr_xlate_conv_buffer(ctx->xlate,
892                                            cur_str, &cur_avail,
893                                            tmp + sizeof(tmp) - space_avail, &space_avail);
894                 
895                 /* Update input ptr and len after consuming some bytes */
896                 cur_str += cur_len - cur_avail;
897                 cur_len = cur_avail;
898                 
899                 if (rv == APR_INCOMPLETE) { /* partial character at end of input */
900                     /* We need to save the final byte(s) for next time; we can't
901                      * convert it until we look at the next bucket.
902                      */
903                     rv = set_aside_partial_char(ctx, cur_str, cur_len);
904                     cur_len = 0;
905                 }
906             }
907         }
908
909         if (rv != APR_SUCCESS) {
910             /* bad input byte or partial char too big to store */
911             done = 1;
912         }
913
914         if (space_avail < XLATE_MIN_BUFF_LEFT) {
915             /* It is time to flush, as there is not enough space left in the
916              * current output buffer to bother with converting more data.
917              */
918             rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
919             if (rv != APR_SUCCESS) {
920                 done = 1;
921             }
922             
923             /* tmp is now empty */
924             space_avail = sizeof(tmp);
925         }
926     }
927
928     if (rv == APR_SUCCESS) {
929         if (space_avail < sizeof(tmp)) { /* gotta write out what we converted */
930             rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
931         }
932     }
933     if (rv == APR_SUCCESS) {
934         if (cur_len == -1) {
935             rv = send_eos(f);
936         }
937     }
938     else {
939         log_xlate_error(f, rv);
940     }
941
942     return rv;
943 }
944
945 static int xlate_in_filter(ap_filter_t *f, apr_bucket_brigade *bb, 
946                            ap_input_mode_t mode, apr_read_type_e block,
947                            apr_off_t readbytes)
948 {
949     apr_status_t rv;
950     charset_req_t *reqinfo = ap_get_module_config(f->r->request_config,
951                                                   &charset_lite_module);
952     charset_dir_t *dc = ap_get_module_config(f->r->per_dir_config,
953                                              &charset_lite_module);
954     charset_filter_ctx_t *ctx = f->ctx;
955     apr_size_t buffer_size;
956     int hit_eos;
957
958     if (!ctx) { 
959         /* this is SetInputFilter path; grab the preallocated context,
960          * if any; note that if we decided not to do anything in an earlier
961          * handler, we won't even have a reqinfo
962          */
963         if (reqinfo) {
964             ctx = f->ctx = reqinfo->input_ctx;
965             reqinfo->input_ctx = NULL; /* prevent SNAFU if user coded us twice
966                                         * in the filter chain; we can't have two
967                                         * instances using the same context
968                                         */
969         }
970         if (!ctx) {                   /* no idea how to translate; don't do anything */
971             ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(charset_filter_ctx_t));
972             ctx->dc = dc;
973             ctx->noop = 1;
974         }
975     }
976
977     if (dc->debug >= DBGLVL_GORY) {
978         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
979                      "xlate_in_filter() - "
980                      "charset_source: %s charset_default: %s",
981                      dc && dc->charset_source ? dc->charset_source : "(none)",
982                      dc && dc->charset_default ? dc->charset_default : "(none)");
983     }
984
985     if (!ctx->ran) {  /* filter never ran before */
986         chk_filter_chain(f);
987         ctx->ran = 1;
988     }
989
990     if (ctx->noop) {
991         return ap_get_brigade(f->next, bb, mode, block, readbytes);
992     }
993
994     if (APR_BRIGADE_EMPTY(ctx->bb)) {
995         if ((rv = ap_get_brigade(f->next, bb, mode, block, 
996                                  readbytes)) != APR_SUCCESS) {
997             return rv;
998         }
999     }
1000     else {
1001         APR_BRIGADE_PREPEND(bb, ctx->bb); /* first use the leftovers */
1002     }
1003
1004     buffer_size = INPUT_XLATE_BUF_SIZE;
1005     rv = xlate_brigade(ctx, bb, ctx->tmp, &buffer_size, &hit_eos);
1006     if (rv == APR_SUCCESS) {
1007         if (!hit_eos) {
1008             /* move anything leftover into our context for next time;
1009              * we don't currently "set aside" since the data came from
1010              * down below, but I suspect that for long-term we need to
1011              * do that
1012              */
1013             APR_BRIGADE_CONCAT(ctx->bb, bb);
1014         }
1015         if (buffer_size < INPUT_XLATE_BUF_SIZE) { /* do we have output? */
1016             apr_bucket *e;
1017
1018             e = apr_bucket_heap_create(ctx->tmp, 
1019                                        INPUT_XLATE_BUF_SIZE - buffer_size,
1020                                        NULL, f->r->connection->bucket_alloc);
1021             /* make sure we insert at the head, because there may be
1022              * an eos bucket already there, and the eos bucket should 
1023              * come after the data
1024              */
1025             APR_BRIGADE_INSERT_HEAD(bb, e);
1026         }
1027         else {
1028             /* XXX need to get some more data... what if the last brigade
1029              * we got had only the first byte of a multibyte char?  we need
1030              * to grab more data from the network instead of returning an
1031              * empty brigade
1032              */
1033         }
1034     }
1035     else {
1036         log_xlate_error(f, rv);
1037     }
1038
1039     return rv;
1040 }
1041
1042 static const command_rec cmds[] =
1043 {
1044     AP_INIT_TAKE1("CharsetSourceEnc",
1045                   add_charset_source,
1046                   NULL,
1047                   OR_FILEINFO,
1048                   "source (html,cgi,ssi) file charset"),
1049     AP_INIT_TAKE1("CharsetDefault", 
1050                   add_charset_default,
1051                   NULL,
1052                   OR_FILEINFO, 
1053                   "name of default charset"),
1054     AP_INIT_ITERATE("CharsetOptions",
1055                     add_charset_options,
1056                     NULL,
1057                     OR_FILEINFO,
1058                     "valid options: ImplicitAdd, NoImplicitAdd, DebugLevel=n"),
1059     {NULL}
1060 };
1061
1062 static void charset_register_hooks(apr_pool_t *p)
1063 {
1064     ap_hook_fixups(find_code_page, NULL, NULL, APR_HOOK_MIDDLE);
1065     ap_hook_insert_filter(xlate_insert_filter, NULL, NULL, APR_HOOK_REALLY_LAST);
1066     ap_register_output_filter(XLATEOUT_FILTER_NAME, xlate_out_filter, NULL,
1067                               AP_FTYPE_RESOURCE);
1068     ap_register_input_filter(XLATEIN_FILTER_NAME, xlate_in_filter, NULL,
1069                              AP_FTYPE_RESOURCE);
1070 }
1071
1072 module AP_MODULE_DECLARE_DATA charset_lite_module =
1073 {
1074     STANDARD20_MODULE_STUFF,
1075     create_charset_dir_conf,
1076     merge_charset_dir_conf,
1077     NULL, 
1078     NULL,
1079     cmds,
1080     charset_register_hooks
1081 };
1082