virtual log4cxx_status_t decode(ByteBuffer& in,
     LogString& out) {
     enum { BUFSIZE = 256 };
     logchar buf[BUFSIZE];
     const apr_size_t initial_outbytes_left = BUFSIZE * sizeof(logchar);
     apr_status_t stat = APR_SUCCESS;
     if (in.remaining() == 0) {
       size_t outbytes_left = initial_outbytes_left;
       {
         synchronized sync(mutex);
         stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
           NULL, NULL, (char*) buf, &outbytes_left);
       }
       out.append(buf, (initial_outbytes_left - outbytes_left)/sizeof(logchar));
     } else {
       while(in.remaining() > 0 && stat == APR_SUCCESS) {
         size_t inbytes_left = in.remaining();
         size_t initial_inbytes_left = inbytes_left;
         size_t pos = in.position();
         apr_size_t outbytes_left = initial_outbytes_left;
         {
           synchronized sync(mutex);
           stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
              in.data() + pos,
              &inbytes_left,
              (char*) buf,
              &outbytes_left);
         }
         out.append(buf, (initial_outbytes_left - outbytes_left)/sizeof(logchar));
         in.position(pos + (initial_inbytes_left - inbytes_left));
       }
     }
     return stat;
 }
static bool xlate_string(apr_xlate_t *xlate, const char *input, char *output) {
    apr_size_t input_left = strlen(input);
    apr_size_t output_left = XLATE_BUFFER_SIZE;
    if (apr_xlate_conv_buffer(xlate, input, &input_left, output, &output_left) != APR_SUCCESS
        || input_left != 0
        || apr_xlate_conv_buffer(xlate, NULL, NULL, output, &output_left) != APR_SUCCESS
        || output_left < 1) {
        return false;
    }
    return true;
}
Beispiel #3
0
static apr_status_t apr_xml_parser_convert_elem(apr_xml_elem *e,
        apr_xlate_t *convset)
{
    apr_xml_attr *a;
    apr_xml_elem *ec;
    apr_text *t;
    apr_size_t inbytes_left, outbytes_left;
    apr_status_t status;

    inbytes_left = outbytes_left = strlen(e->name);
    status = apr_xlate_conv_buffer(convset, e->name,  &inbytes_left, (char *) e->name, &outbytes_left);
    if (status) {
        return status;
    }

    for (t = e->first_cdata.first; t != NULL; t = t->next) {
        inbytes_left = outbytes_left = strlen(t->text);
        status = apr_xlate_conv_buffer(convset, t->text, &inbytes_left, (char *) t->text, &outbytes_left);
        if (status) {
            return status;
        }
    }

    for (t = e->following_cdata.first;  t != NULL; t = t->next) {
        inbytes_left = outbytes_left = strlen(t->text);
        status = apr_xlate_conv_buffer(convset, t->text, &inbytes_left, (char *) t->text, &outbytes_left);
        if (status) {
            return status;
        }
    }

    for (a = e->attr; a != NULL; a = a->next) {
        inbytes_left = outbytes_left = strlen(a->name);
        status = apr_xlate_conv_buffer(convset, a->name, &inbytes_left, (char *) a->name, &outbytes_left);
        if (status) {
            return status;
        }
        inbytes_left = outbytes_left = strlen(a->value);
        status = apr_xlate_conv_buffer(convset, a->value, &inbytes_left, (char *) a->value, &outbytes_left);
        if (status) {
            return status;
        }
    }

    for (ec = e->first_child; ec != NULL; ec = ec->next) {
        status = apr_xml_parser_convert_elem(ec, convset);
        if (status) {
            return status;
        }
    }
    return APR_SUCCESS;
}
Beispiel #4
0
/* convert the whole document to EBCDIC */
APU_DECLARE(apr_status_t) apr_xml_parser_convert_doc(apr_pool_t *pool,
        apr_xml_doc *pdoc,
        apr_xlate_t *convset)
{
    apr_status_t status;
    /* Don't convert the namespaces: they are constant! */
    if (pdoc->namespaces != NULL) {
        int i;
        apr_array_header_t *namespaces;
        namespaces = apr_array_make(pool, pdoc->namespaces->nelts, sizeof(const char *));
        if (namespaces == NULL)
            return APR_ENOMEM;
        for (i = 0; i < pdoc->namespaces->nelts; i++) {
            apr_size_t inbytes_left, outbytes_left;
            char *ptr = (char *) APR_XML_GET_URI_ITEM(pdoc->namespaces, i);
            ptr = apr_pstrdup(pool, ptr);
            if ( ptr == NULL)
                return APR_ENOMEM;
            inbytes_left = outbytes_left = strlen(ptr);
            status = apr_xlate_conv_buffer(convset, ptr, &inbytes_left, ptr, &outbytes_left);
            if (status) {
                return status;
            }
            apr_xml_insert_uri(namespaces, ptr);
        }
        pdoc->namespaces = namespaces;
    }
    return apr_xml_parser_convert_elem(pdoc->root, convset);
}
Beispiel #5
0
void ap_xlate_proto_from_ascii(char *buffer, apr_size_t len)
{
    apr_size_t inbytes_left, outbytes_left;

    inbytes_left = outbytes_left = len;
    apr_xlate_conv_buffer(ap_hdrs_from_ascii, buffer, &inbytes_left,
                          buffer, &outbytes_left);
}
Beispiel #6
0
static apr_status_t finish_partial_char(charset_filter_ctx_t *ctx,
                                        /* input buffer: */
                                        const char **cur_str,
                                        apr_size_t *cur_len,
                                        /* output buffer: */
                                        char **out_str,
                                        apr_size_t *out_len)
{
    apr_status_t rv;
    apr_size_t tmp_input_len;

    /* Keep adding bytes from the input string to the saved string until we
     *    1) finish the input char
     *    2) get an error
     * or 3) run out of bytes to add
     */

    do {
        ctx->buf[ctx->saved] = **cur_str;
        ++ctx->saved;
        ++*cur_str;
        --*cur_len;
        tmp_input_len = ctx->saved;
        rv = apr_xlate_conv_buffer(ctx->xlate,
                                   ctx->buf,
                                   &tmp_input_len,
                                   *out_str,
                                   out_len);
    } while (rv == APR_INCOMPLETE && *cur_len);

    if (rv == APR_SUCCESS) {
        ctx->saved = 0;
    }
    else {
        ctx->ees = EES_LIMIT; /* code isn't smart enough to handle chars
                               * straddling more than two buckets
                               */
    }

    return rv;
}
Beispiel #7
0
static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
                               apr_table_t *table, apr_file_t *file)
{
    char w[MAX_STRING_LEN];
    char *l;
    int p;
    apr_status_t rv;

    while (1) {

        /* ### What about APR_EOF? */
        rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Premature end of cache headers.");
            return rv;
        }

        /* Delete terminal (CR?)LF */

        p = strlen(w);
        /* Indeed, the host's '\n':
           '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
           -- whatever the script generates.
        */
        if (p > 0 && w[p - 1] == '\n') {
            if (p > 1 && w[p - 2] == CR) {
                w[p - 2] = '\0';
            }
            else {
                w[p - 1] = '\0';
            }
        }

        /* If we've finished reading the headers, break out of the loop. */
        if (w[0] == '\0') {
            break;
        }

#if APR_CHARSET_EBCDIC
        /* Chances are that we received an ASCII header text instead of
         * the expected EBCDIC header lines. Try to auto-detect:
         */
        if (!(l = strchr(w, ':'))) {
            int maybeASCII = 0, maybeEBCDIC = 0;
            unsigned char *cp, native;
            apr_size_t inbytes_left, outbytes_left;

            for (cp = w; *cp != '\0'; ++cp) {
                native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
                if (apr_isprint(*cp) && !apr_isprint(native))
                    ++maybeEBCDIC;
                if (!apr_isprint(*cp) && apr_isprint(native))
                    ++maybeASCII;
            }
            if (maybeASCII > maybeEBCDIC) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
                             "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
                             r->filename);
                inbytes_left = outbytes_left = cp - w;
                apr_xlate_conv_buffer(ap_hdrs_from_ascii,
                                      w, &inbytes_left, w, &outbytes_left);
            }
        }
#endif /*APR_CHARSET_EBCDIC*/

        /* if we see a bogus header don't ignore it. Shout and scream */
        if (!(l = strchr(w, ':'))) {
            return APR_EGENERAL;
        }

        *l++ = '\0';
        while (*l && apr_isspace(*l)) {
            ++l;
        }

        apr_table_add(table, w, l);
    }

    return APR_SUCCESS;
}
Beispiel #8
0
AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer,
                                       int (*getsfunc) (char *, int, void *),
                                       void *getsfunc_data)
{
    char x[MAX_STRING_LEN];
    char *w, *l;
    int p;
    int cgi_status = HTTP_UNSET;
    apr_table_t *merge;
    apr_table_t *cookie_table;

    if (buffer) {
        *buffer = '\0';
    }
    w = buffer ? buffer : x;

    /* temporary place to hold headers to merge in later */
    merge = apr_table_make(r->pool, 10);

    /* The HTTP specification says that it is legal to merge duplicate
     * headers into one.  Some browsers that support Cookies don't like
     * merged headers and prefer that each Set-Cookie header is sent
     * separately.  Lets humour those browsers by not merging.
     * Oh what a pain it is.
     */
    cookie_table = apr_table_make(r->pool, 2);
    apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, "Set-Cookie", NULL);

    while (1) {

        int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data);
        if (rv == 0) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
                          "Premature end of script headers: %s",
                          apr_filepath_name_get(r->filename));
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        else if (rv == -1) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
                          "Script timed out before returning headers: %s",
                          apr_filepath_name_get(r->filename));
            return HTTP_GATEWAY_TIME_OUT;
        }

        /* Delete terminal (CR?)LF */

        p = strlen(w);
             /* Indeed, the host's '\n':
                '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
                 -- whatever the script generates.
             */
        if (p > 0 && w[p - 1] == '\n') {
            if (p > 1 && w[p - 2] == CR) {
                w[p - 2] = '\0';
            }
            else {
                w[p - 1] = '\0';
            }
        }

        /*
         * If we've finished reading the headers, check to make sure any
         * HTTP/1.1 conditions are met.  If so, we're done; normal processing
         * will handle the script's output.  If not, just return the error.
         * The appropriate thing to do would be to send the script process a
         * SIGPIPE to let it know we're ignoring it, close the channel to the
         * script process, and *then* return the failed-to-meet-condition
         * error.  Otherwise we'd be waiting for the script to finish
         * blithering before telling the client the output was no good.
         * However, we don't have the information to do that, so we have to
         * leave it to an upper layer.
         */
        if (w[0] == '\0') {
            int cond_status = OK;

           /* PR#38070: This fails because it gets confused when a
            * CGI Status header overrides ap_meets_conditions.
            * 
            * We can fix that by dropping ap_meets_conditions when
            * Status has been set.  Since this is the only place
            * cgi_status gets used, let's test it explicitly.
            *
            * The alternative would be to ignore CGI Status when
            * ap_meets_conditions returns anything interesting.
            * That would be safer wrt HTTP, but would break CGI.
            */
            if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) {
                cond_status = ap_meets_conditions(r);
            }
            apr_table_overlap(r->err_headers_out, merge,
                APR_OVERLAP_TABLES_MERGE);
            if (!apr_is_empty_table(cookie_table)) {
                /* the cookies have already been copied to the cookie_table */
                apr_table_unset(r->err_headers_out, "Set-Cookie");
                r->err_headers_out = apr_table_overlay(r->pool,
                    r->err_headers_out, cookie_table);
            }
            return cond_status;
        }

        /* if we see a bogus header don't ignore it. Shout and scream */

#if APR_CHARSET_EBCDIC
            /* Chances are that we received an ASCII header text instead of
             * the expected EBCDIC header lines. Try to auto-detect:
             */
        if (!(l = strchr(w, ':'))) {
            int maybeASCII = 0, maybeEBCDIC = 0;
            unsigned char *cp, native;
            apr_size_t inbytes_left, outbytes_left;

            for (cp = w; *cp != '\0'; ++cp) {
                native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
                if (apr_isprint(*cp) && !apr_isprint(native))
                    ++maybeEBCDIC;
                if (!apr_isprint(*cp) && apr_isprint(native))
                    ++maybeASCII;
            }
            if (maybeASCII > maybeEBCDIC) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
                             "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
                             r->filename);
                inbytes_left = outbytes_left = cp - w;
                apr_xlate_conv_buffer(ap_hdrs_from_ascii,
                                      w, &inbytes_left, w, &outbytes_left);
            }
        }
#endif /*APR_CHARSET_EBCDIC*/
        if (!(l = strchr(w, ':'))) {
            char malformed[(sizeof MALFORMED_MESSAGE) + 1
                           + MALFORMED_HEADER_LENGTH_TO_SHOW];

            strcpy(malformed, MALFORMED_MESSAGE);
            strncat(malformed, w, MALFORMED_HEADER_LENGTH_TO_SHOW);

            if (!buffer) {
                /* Soak up all the script output - may save an outright kill */
                while ((*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data)) {
                    continue;
                }
            }

            ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
                          "%s: %s", malformed,
                          apr_filepath_name_get(r->filename));
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        *l++ = '\0';
        while (*l && apr_isspace(*l)) {
            ++l;
        }

        if (!strcasecmp(w, "Content-type")) {
            char *tmp;

            /* Nuke trailing whitespace */

            char *endp = l + strlen(l) - 1;
            while (endp > l && apr_isspace(*endp)) {
                *endp-- = '\0';
            }

            tmp = apr_pstrdup(r->pool, l);
            ap_content_type_tolower(tmp);
            ap_set_content_type(r, tmp);
        }
        /*
         * If the script returned a specific status, that's what
         * we'll use - otherwise we assume 200 OK.
         */
        else if (!strcasecmp(w, "Status")) {
            r->status = cgi_status = atoi(l);
            r->status_line = apr_pstrdup(r->pool, l);
        }
        else if (!strcasecmp(w, "Location")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Content-Length")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Content-Range")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Transfer-Encoding")) {
            apr_table_set(r->headers_out, w, l);
        }
        /*
         * If the script gave us a Last-Modified header, we can't just
         * pass it on blindly because of restrictions on future values.
         */
        else if (!strcasecmp(w, "Last-Modified")) {
            ap_update_mtime(r, apr_date_parse_http(l));
            ap_set_last_modified(r);
        }
        else if (!strcasecmp(w, "Set-Cookie")) {
            apr_table_add(cookie_table, w, l);
        }
        else {
            apr_table_add(merge, w, l);
        }
    }

    return OK;
}
Beispiel #9
0
/* xlate_out_filter() handles (almost) arbitrary conversions from one charset
 * to another...
 * translation is determined in the fixup hook (find_code_page), which is
 * where the filter's context data is set up... the context data gives us
 * the translation handle
 */
static apr_status_t xlate_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    charset_req_t *reqinfo = ap_get_module_config(f->r->request_config,
                                                  &charset_lite_module);
    charset_dir_t *dc = ap_get_module_config(f->r->per_dir_config,
                                             &charset_lite_module);
    charset_filter_ctx_t *ctx = f->ctx;
    apr_bucket *dptr, *consumed_bucket;
    const char *cur_str;
    apr_size_t cur_len, cur_avail;
    char tmp[OUTPUT_XLATE_BUF_SIZE];
    apr_size_t space_avail;
    int done;
    apr_status_t rv = APR_SUCCESS;

    if (!ctx) {
        /* this is SetOutputFilter path; grab the preallocated context,
         * if any; note that if we decided not to do anything in an earlier
         * handler, we won't even have a reqinfo
         */
        if (reqinfo) {
            ctx = f->ctx = reqinfo->output_ctx;
            reqinfo->output_ctx = NULL; /* prevent SNAFU if user coded us twice
                                         * in the filter chain; we can't have two
                                         * instances using the same context
                                         */
        }
        if (!ctx) {                   /* no idea how to translate; don't do anything */
            ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(charset_filter_ctx_t));
            ctx->dc = dc;
            ctx->noop = 1;
        }
    }

    /* Check the mime type to see if translation should be performed.
     */
    if (!ctx->noop && ctx->xlate == NULL) {
        const char *mime_type = f->r->content_type;

        if (mime_type && (strncasecmp(mime_type, "text/", 5) == 0 ||
#if APR_CHARSET_EBCDIC
        /* On an EBCDIC machine, be willing to translate mod_autoindex-
         * generated output.  Otherwise, it doesn't look too cool.
         *
         * XXX This isn't a perfect fix because this doesn't trigger us
         * to convert from the charset of the source code to ASCII.  The
         * general solution seems to be to allow a generator to set an
         * indicator in the r specifying that the body is coded in the
         * implementation character set (i.e., the charset of the source
         * code).  This would get several different types of documents
         * translated properly: mod_autoindex output, mod_status output,
         * mod_info output, hard-coded error documents, etc.
         */
            strcmp(mime_type, DIR_MAGIC_TYPE) == 0 ||
#endif
            strncasecmp(mime_type, "message/", 8) == 0 ||
            dc->force_xlate == FX_FORCE)) {

            rv = apr_xlate_open(&ctx->xlate,
                                dc->charset_default, dc->charset_source, f->r->pool);
            if (rv != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01453)
                              "can't open translation %s->%s",
                              dc->charset_source, dc->charset_default);
                ctx->noop = 1;
            }
            else {
                if (apr_xlate_sb_get(ctx->xlate, &ctx->is_sb) != APR_SUCCESS) {
                    ctx->is_sb = 0;
                }
            }
        }
        else {
            ctx->noop = 1;
            if (mime_type) {
                ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r,
                              "mime type is %s; no translation selected",
                              mime_type);
            }
        }
    }

    ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r,
                  "xlate_out_filter() - "
                  "charset_source: %s charset_default: %s",
                  dc && dc->charset_source ? dc->charset_source : "(none)",
                  dc && dc->charset_default ? dc->charset_default : "(none)");

    if (!ctx->ran) {  /* filter never ran before */
        chk_filter_chain(f);
        ctx->ran = 1;
        if (!ctx->noop && !ctx->is_sb) {
            /* We're not converting between two single-byte charsets, so unset
             * Content-Length since it is unlikely to remain the same.
             */
            apr_table_unset(f->r->headers_out, "Content-Length");
        }
    }

    if (ctx->noop) {
        return ap_pass_brigade(f->next, bb);
    }

    dptr = APR_BRIGADE_FIRST(bb);
    done = 0;
    cur_len = 0;
    space_avail = sizeof(tmp);
    consumed_bucket = NULL;
    while (!done) {
        if (!cur_len) { /* no bytes left to process in the current bucket... */
            if (consumed_bucket) {
                apr_bucket_delete(consumed_bucket);
                consumed_bucket = NULL;
            }
            if (dptr == APR_BRIGADE_SENTINEL(bb)) {
                break;
            }
            if (APR_BUCKET_IS_EOS(dptr)) {
                cur_len = -1; /* XXX yuck, but that tells us to send
                                 * eos down; when we minimize our bb construction
                                 * we'll fix this crap */
                if (ctx->saved) {
                    /* Oops... we have a partial char from the previous bucket
                     * that won't be completed because there's no more data.
                     */
                    rv = APR_INCOMPLETE;
                    ctx->ees = EES_INCOMPLETE_CHAR;
                }
                break;
            }
            if (APR_BUCKET_IS_METADATA(dptr)) {
                apr_bucket *metadata_bucket;
                metadata_bucket = dptr;
                dptr = APR_BUCKET_NEXT(dptr);
                APR_BUCKET_REMOVE(metadata_bucket);
                rv = send_bucket_downstream(f, metadata_bucket);
                if (rv != APR_SUCCESS) {
                    done = 1;
                }
                continue;
            }
            rv = apr_bucket_read(dptr, &cur_str, &cur_len, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                ctx->ees = EES_BUCKET_READ;
                break;
            }
            consumed_bucket = dptr; /* for axing when we're done reading it */
            dptr = APR_BUCKET_NEXT(dptr); /* get ready for when we access the
                                          * next bucket */
        }
        /* Try to fill up our tmp buffer with translated data. */
        cur_avail = cur_len;

        if (cur_len) { /* maybe we just hit the end of a pipe (len = 0) ? */
            if (ctx->saved) {
                /* Rats... we need to finish a partial character from the previous
                 * bucket.
                 */
                char *tmp_tmp;

                tmp_tmp = tmp + sizeof(tmp) - space_avail;
                rv = finish_partial_char(ctx,
                                         &cur_str, &cur_len,
                                         &tmp_tmp, &space_avail);
            }
            else {
                rv = apr_xlate_conv_buffer(ctx->xlate,
                                           cur_str, &cur_avail,
                                           tmp + sizeof(tmp) - space_avail, &space_avail);

                /* Update input ptr and len after consuming some bytes */
                cur_str += cur_len - cur_avail;
                cur_len = cur_avail;

                if (rv == APR_INCOMPLETE) { /* partial character at end of input */
                    /* We need to save the final byte(s) for next time; we can't
                     * convert it until we look at the next bucket.
                     */
                    rv = set_aside_partial_char(ctx, cur_str, cur_len);
                    cur_len = 0;
                }
            }
        }

        if (rv != APR_SUCCESS) {
            /* bad input byte or partial char too big to store */
            done = 1;
        }

        if (space_avail < XLATE_MIN_BUFF_LEFT) {
            /* It is time to flush, as there is not enough space left in the
             * current output buffer to bother with converting more data.
             */
            rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
            if (rv != APR_SUCCESS) {
                done = 1;
            }

            /* tmp is now empty */
            space_avail = sizeof(tmp);
        }
    }

    if (rv == APR_SUCCESS) {
        if (space_avail < sizeof(tmp)) { /* gotta write out what we converted */
            rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
        }
    }
    if (rv == APR_SUCCESS) {
        if (cur_len == -1) {
            rv = send_eos(f);
        }
    }
    else {
        log_xlate_error(f, rv);
    }

    return rv;
}
Beispiel #10
0
/* xlate_brigade() is used to filter request and response bodies
 *
 * we'll stop when one of the following occurs:
 * . we run out of buckets
 * . we run out of space in the output buffer
 * . we hit an error or metadata
 *
 * inputs:
 *   bb:               brigade to process
 *   buffer:           storage to hold the translated characters
 *   buffer_avail:     size of buffer
 *   (and a few more uninteresting parms)
 *
 * outputs:
 *   return value:     APR_SUCCESS or some error code
 *   bb:               we've removed any buckets representing the
 *                     translated characters; the eos bucket, if
 *                     present, will be left in the brigade
 *   buffer:           filled in with translated characters
 *   buffer_avail:     updated with the bytes remaining
 *   hit_eos:          did we hit an EOS bucket?
 */
static apr_status_t xlate_brigade(charset_filter_ctx_t *ctx,
                                  apr_bucket_brigade *bb,
                                  char *buffer,
                                  apr_size_t *buffer_avail,
                                  int *hit_eos)
{
    apr_bucket *b = NULL; /* set to NULL only to quiet some gcc */
    apr_bucket *consumed_bucket;
    const char *bucket;
    apr_size_t bytes_in_bucket; /* total bytes read from current bucket */
    apr_size_t bucket_avail;    /* bytes left in current bucket */
    apr_status_t rv = APR_SUCCESS;

    *hit_eos = 0;
    bucket_avail = 0;
    consumed_bucket = NULL;
    while (1) {
        if (!bucket_avail) { /* no bytes left to process in the current bucket... */
            if (consumed_bucket) {
                apr_bucket_delete(consumed_bucket);
                consumed_bucket = NULL;
            }
            b = APR_BRIGADE_FIRST(bb);
            if (b == APR_BRIGADE_SENTINEL(bb) ||
                APR_BUCKET_IS_METADATA(b)) {
                break;
            }
            rv = apr_bucket_read(b, &bucket, &bytes_in_bucket, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                ctx->ees = EES_BUCKET_READ;
                break;
            }
            bucket_avail = bytes_in_bucket;
            consumed_bucket = b;   /* for axing when we're done reading it */
        }
        if (bucket_avail) {
            /* We've got data, so translate it. */
            if (ctx->saved) {
                /* Rats... we need to finish a partial character from the previous
                 * bucket.
                 *
                 * Strangely, finish_partial_char() increments the input buffer
                 * pointer but does not increment the output buffer pointer.
                 */
                apr_size_t old_buffer_avail = *buffer_avail;
                rv = finish_partial_char(ctx,
                                         &bucket, &bucket_avail,
                                         &buffer, buffer_avail);
                buffer += old_buffer_avail - *buffer_avail;
            }
            else {
                apr_size_t old_buffer_avail = *buffer_avail;
                apr_size_t old_bucket_avail = bucket_avail;
                rv = apr_xlate_conv_buffer(ctx->xlate,
                                           bucket, &bucket_avail,
                                           buffer,
                                           buffer_avail);
                buffer  += old_buffer_avail - *buffer_avail;
                bucket  += old_bucket_avail - bucket_avail;

                if (rv == APR_INCOMPLETE) { /* partial character at end of input */
                    /* We need to save the final byte(s) for next time; we can't
                     * convert it until we look at the next bucket.
                     */
                    rv = set_aside_partial_char(ctx, bucket, bucket_avail);
                    bucket_avail = 0;
                }
            }
            if (rv != APR_SUCCESS) {
                /* bad input byte or partial char too big to store */
                break;
            }
            if (*buffer_avail < XLATE_MIN_BUFF_LEFT) {
                /* if any data remains in the current bucket, split there */
                if (bucket_avail) {
                    apr_bucket_split(b, bytes_in_bucket - bucket_avail);
                }
                apr_bucket_delete(b);
                break;
            }
        }
    }

    if (!APR_BRIGADE_EMPTY(bb)) {
        b = APR_BRIGADE_FIRST(bb);
        if (APR_BUCKET_IS_EOS(b)) {
            /* Leave the eos bucket in the brigade for reporting to
             * subsequent filters.
             */
            *hit_eos = 1;
            if (ctx->saved) {
                /* Oops... we have a partial char from the previous bucket
                 * that won't be completed because there's no more data.
                 */
                rv = APR_INCOMPLETE;
                ctx->ees = EES_INCOMPLETE_CHAR;
            }
        }
    }

    return rv;
}