/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
#include <assert.h>
#include <stdio.h>

#include <apr_date.h>
#include <apr_lib.h>
#include <apr_strings.h>

#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
#include <http_connection.h>
#include <http_protocol.h>
#include <http_request.h>
#include <util_time.h>

#include "h2_private.h"
#include "h2.h"
#include "h2_config.h"
#include "h2_conn_ctx.h"
#include "h2_headers.h"
#include "h2_c1.h"
#include "h2_c2_filter.h"
#include "h2_c2.h"
#include "h2_mplx.h"
#include "h2_request.h"
#include "h2_ws.h"
#include "h2_util.h"


#if AP_HAS_RESPONSE_BUCKETS

apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb)
{
    apr_bucket *b;
    request_rec *r_prev;
    ap_bucket_response *resp;
    const char *err;

    if (!f->r) {
        goto pass;
    }

    for (b = APR_BRIGADE_FIRST(bb);
         b != APR_BRIGADE_SENTINEL(bb);
         b = APR_BUCKET_NEXT(b))
    {
        if (AP_BUCKET_IS_RESPONSE(b)) {
            resp = b->data;
            if (resp->status >= 400 && f->r->prev) {
                /* Error responses are commonly handled via internal
                 * redirects to error documents. That creates a new
                 * request_rec with 'prev' set to the original.
                 * Each of these has its onw 'notes'.
                 * We'd like to copy interesting ones into the current 'r->notes'
                 * as we reset HTTP/2 stream with H2 specific error codes then.
                 */
                for (r_prev = f->r; r_prev != NULL; r_prev = r_prev->prev) {
                    if ((err = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden"))) {
                        if (r_prev != f->r) {
                            apr_table_setn(resp->notes, "ssl-renegotiate-forbidden", err);
                        }
                        break;
                    }
                }
            }
            else if (h2_config_rgeti(f->r, H2_CONF_PUSH) == 0
                     && h2_config_sgeti(f->r->server, H2_CONF_PUSH) != 0) {
                /* location configuration turns off H2 PUSH handling */
                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
                              "h2_c2_filter_notes_out, turning PUSH off");
                apr_table_setn(resp->notes, H2_PUSH_MODE_NOTE, "0");
            }
        }
    }
pass:
    return ap_pass_brigade(f->next, bb);
}

apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
                                     apr_bucket_brigade *bb,
                                     ap_input_mode_t mode,
                                     apr_read_type_e block,
                                     apr_off_t readbytes)
{
    h2_conn_ctx_t *conn_ctx;
    apr_bucket *b;

    /* just get out of the way for things we don't want to handle. */
    if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
        return ap_get_brigade(f->next, bb, mode, block, readbytes);
    }

    /* This filter is a one-time wonder */
    ap_remove_input_filter(f);

    if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) &&
        conn_ctx->stream_id) {
        const h2_request *req = conn_ctx->request;

        if (req->http_status == H2_HTTP_STATUS_UNSET &&
            req->protocol && !strcmp("websocket", req->protocol)) {
            req = h2_ws_rewrite_request(req, f->c, conn_ctx->beam_in == NULL);
            if (!req)
                return APR_EGENERAL;
        }

        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
                      "h2_c2_filter_request_in(%s): adding request bucket",
                      conn_ctx->id);
        b = h2_request_create_bucket(req, f->r);
        APR_BRIGADE_INSERT_TAIL(bb, b);

        if (req->http_status != H2_HTTP_STATUS_UNSET) {
            /* error was encountered preparing this request */
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
                          "h2_c2_filter_request_in(%s): adding error bucket %d",
                          conn_ctx->id, req->http_status);
            b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
                                       f->c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bb, b);
            return APR_SUCCESS;
        }

        if (!conn_ctx->beam_in) {
            b = apr_bucket_eos_create(f->c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bb, b);
        }

        return APR_SUCCESS;
    }

    return ap_get_brigade(f->next, bb, mode, block, readbytes);
}

#else /* AP_HAS_RESPONSE_BUCKETS */

#define H2_FILTER_LOG(name, c, level, rv, msg, bb) \
    do { \
        if (APLOG_C_IS_LEVEL((c),(level))) { \
            char buffer[4 * 1024]; \
            apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
            len = h2_util_bb_print(buffer, bmax, "", "", (bb)); \
            ap_log_cerror(APLOG_MARK, (level), rv, (c), \
                          "FILTER[%s]: %s %s", \
                          (name), (msg), len? buffer : ""); \
        } \
    } while (0)


/* This routine is called by apr_table_do and merges all instances of
 * the passed field values into a single array that will be further
 * processed by some later routine.  Originally intended to help split
 * and recombine multiple Vary fields, though it is generic to any field
 * consisting of comma/space-separated tokens.
 */
static int uniq_field_values(void *d, const char *key, const char *val)
{
    apr_array_header_t *values;
    char *start;
    char *e;
    char **strpp;
    int  i;

    (void)key;
    values = (apr_array_header_t *)d;

    e = apr_pstrdup(values->pool, val);

    do {
        /* Find a non-empty fieldname */

        while (*e == ',' || apr_isspace(*e)) {
            ++e;
        }
        if (*e == '\0') {
            break;
        }
        start = e;
        while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
            ++e;
        }
        if (*e != '\0') {
            *e++ = '\0';
        }

        /* Now add it to values if it isn't already represented.
         * Could be replaced by a ap_array_strcasecmp() if we had one.
         */
        for (i = 0, strpp = (char **) values->elts; i < values->nelts;
             ++i, ++strpp) {
            if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) {
                break;
            }
        }
        if (i == values->nelts) {  /* if not found */
            *(char **)apr_array_push(values) = start;
        }
    } while (*e != '\0');

    return 1;
}

/*
 * Since some clients choke violently on multiple Vary fields, or
 * Vary fields with duplicate tokens, combine any multiples and remove
 * any duplicates.
 */
static void fix_vary(request_rec *r)
{
    apr_array_header_t *varies;

    varies = apr_array_make(r->pool, 5, sizeof(char *));

    /* Extract all Vary fields from the headers_out, separate each into
     * its comma-separated fieldname values, and then add them to varies
     * if not already present in the array.
     */
    apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);

    /* If we found any, replace old Vary fields with unique-ified value */

    if (varies->nelts > 0) {
        apr_table_setn(r->headers_out, "Vary",
                       apr_array_pstrcat(r->pool, varies, ','));
    }
}

static h2_headers *create_response(request_rec *r)
{
    const char *clheader;
    const char *ctype;

    /*
     * Now that we are ready to send a response, we need to combine the two
     * header field tables into a single table.  If we don't do this, our
     * later attempts to set or unset a given fieldname might be bypassed.
     */
    if (!apr_is_empty_table(r->err_headers_out)) {
        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
                                           r->headers_out);
        apr_table_clear(r->err_headers_out);
    }

    /*
     * Remove the 'Vary' header field if the client can't handle it.
     * Since this will have nasty effects on HTTP/1.1 caches, force
     * the response into HTTP/1.0 mode.
     */
    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
        apr_table_unset(r->headers_out, "Vary");
        r->proto_num = HTTP_VERSION(1,0);
        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
    }
    else {
        fix_vary(r);
    }

    /*
     * Now remove any ETag response header field if earlier processing
     * says so (such as a 'FileETag None' directive).
     */
    if (apr_table_get(r->notes, "no-etag") != NULL) {
        apr_table_unset(r->headers_out, "ETag");
    }

    /* determine the protocol and whether we should use keepalives. */
    ap_set_keepalive(r);

    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
        apr_table_unset(r->headers_out, "Transfer-Encoding");
        apr_table_unset(r->headers_out, "Content-Length");
        r->content_type = r->content_encoding = NULL;
        r->content_languages = NULL;
        r->clength = r->chunked = 0;
    }
    else if (r->chunked) {
        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
        apr_table_unset(r->headers_out, "Content-Length");
    }

    ctype = ap_make_content_type(r, r->content_type);
    if (ctype) {
        apr_table_setn(r->headers_out, "Content-Type", ctype);
    }

    if (r->content_encoding) {
        apr_table_setn(r->headers_out, "Content-Encoding",
                       r->content_encoding);
    }

    if (!apr_is_empty_array(r->content_languages)) {
        int i;
        char *token;
        char **languages = (char **)(r->content_languages->elts);
        const char *field = apr_table_get(r->headers_out, "Content-Language");

        while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
            for (i = 0; i < r->content_languages->nelts; ++i) {
                if (!ap_cstr_casecmp(token, languages[i]))
                    break;
            }
            if (i == r->content_languages->nelts) {
                *((char **) apr_array_push(r->content_languages)) = token;
            }
        }

        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
        apr_table_setn(r->headers_out, "Content-Language", field);
    }

    /*
     * Control cachability for non-cachable responses if not already set by
     * some other part of the server configuration.
     */
    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
        ap_recent_rfc822_date(date, r->request_time);
        apr_table_add(r->headers_out, "Expires", date);
    }

    /* This is a hack, but I can't find anyway around it.  The idea is that
     * we don't want to send out 0 Content-Lengths if it is a head request.
     * This happens when modules try to outsmart the server, and return
     * if they see a HEAD request.  Apache 1.3 handlers were supposed to
     * just return in that situation, and the core handled the HEAD.  In
     * 2.0, if a handler returns, then the core sends an EOS bucket down
     * the filter stack, and the content-length filter computes a C-L of
     * zero and that gets put in the headers, and we end up sending a
     * zero C-L to the client.  We can't just remove the C-L filter,
     * because well behaved 2.0 handlers will send their data down the stack,
     * and we will compute a real C-L for the head request. RBB
     */
    if (r->header_only
        && (clheader = apr_table_get(r->headers_out, "Content-Length"))
        && !strcmp(clheader, "0")) {
        apr_table_unset(r->headers_out, "Content-Length");
    }

    /*
     * keep the set-by-proxy server and date headers, otherwise
     * generate a new server header / date header
     */
    if (r->proxyreq == PROXYREQ_NONE
        || !apr_table_get(r->headers_out, "Date")) {
        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
        ap_recent_rfc822_date(date, r->request_time);
        apr_table_setn(r->headers_out, "Date", date );
    }
    if (r->proxyreq == PROXYREQ_NONE
        || !apr_table_get(r->headers_out, "Server")) {
        const char *us = ap_get_server_banner();
        if (us && *us) {
            apr_table_setn(r->headers_out, "Server", us);
        }
    }

    return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
}

typedef enum {
    H2_RP_STATUS_LINE,
    H2_RP_HEADER_LINE,
    H2_RP_DONE
} h2_rp_state_t;

typedef struct h2_response_parser h2_response_parser;
struct h2_response_parser {
    const char *id;
    h2_rp_state_t state;
    conn_rec *c;
    apr_pool_t *pool;
    int http_status;
    apr_array_header_t *hlines;
    apr_bucket_brigade *tmp;
    apr_bucket_brigade *saveto;
};

static apr_status_t parse_header(h2_response_parser *parser, char *line) {
    const char *hline;
    if (line[0] == ' ' || line[0] == '\t') {
        char **plast;
        /* continuation line from the header before this */
        while (line[0] == ' ' || line[0] == '\t') {
            ++line;
        }

        plast = apr_array_pop(parser->hlines);
        if (plast == NULL) {
            /* not well formed */
            return APR_EINVAL;
        }
        hline = apr_psprintf(parser->pool, "%s %s", *plast, line);
    }
    else {
        /* new header line */
        hline = apr_pstrdup(parser->pool, line);
    }
    APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
    return APR_SUCCESS;
}

static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
                             char *line, apr_size_t len)
{
    apr_status_t status;

    if (!parser->tmp) {
        parser->tmp = apr_brigade_create(parser->pool, parser->c->bucket_alloc);
    }
    status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
                                    len);
    if (status == APR_SUCCESS) {
        --len;
        status = apr_brigade_flatten(parser->tmp, line, &len);
        if (status == APR_SUCCESS) {
            /* we assume a non-0 containing line and remove trailing crlf. */
            line[len] = '\0';
            /*
             * XXX: What to do if there is an LF but no CRLF?
             *      Should we error out?
             */
            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
                len -= 2;
                line[len] = '\0';
                apr_brigade_cleanup(parser->tmp);
                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
                              "h2_c2(%s): read response line: %s",
                              parser->id, line);
            }
            else {
                apr_off_t brigade_length;

                /*
                 * If the brigade parser->tmp becomes longer than our buffer
                 * for flattening we never have a chance to get a complete
                 * line. This can happen if we are called multiple times after
                 * previous calls did not find a H2_CRLF and we returned
                 * APR_EAGAIN. In this case parser->tmp (correctly) grows
                 * with each call to apr_brigade_split_line.
                 *
                 * XXX: Currently a stack based buffer of HUGE_STRING_LEN is
                 * used. This means we cannot cope with lines larger than
                 * HUGE_STRING_LEN which might be an issue.
                 */
                status = apr_brigade_length(parser->tmp, 0, &brigade_length);
                if ((status != APR_SUCCESS) || (brigade_length > (apr_off_t)len)) {
                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, parser->c, APLOGNO(10257)
                                  "h2_c2(%s): read response, line too long",
                                  parser->id);
                    return APR_ENOSPC;
                }
                /* this does not look like a complete line yet */
                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
                              "h2_c2(%s): read response, incomplete line: %s",
                              parser->id, line);
                if (!parser->saveto) {
                    parser->saveto = apr_brigade_create(parser->pool,
                                                        parser->c->bucket_alloc);
                }
                /*
                 * Be on the save side and save the parser->tmp brigade
                 * as it could contain transient buckets which could be
                 * invalid next time we are here.
                 *
                 * NULL for the filter parameter is ok since we
                 * provide our own brigade as second parameter
                 * and ap_save_brigade does not need to create one.
                 */
                ap_save_brigade(NULL, &(parser->saveto), &(parser->tmp),
                                parser->tmp->p);
                APR_BRIGADE_CONCAT(parser->tmp, parser->saveto);
                return APR_EAGAIN;
            }
        }
    }
    apr_brigade_cleanup(parser->tmp);
    return status;
}

static apr_table_t *make_table(h2_response_parser *parser)
{
    apr_array_header_t *hlines = parser->hlines;
    if (hlines) {
        apr_table_t *headers = apr_table_make(parser->pool, hlines->nelts);
        int i;

        for (i = 0; i < hlines->nelts; ++i) {
            char *hline = ((char **)hlines->elts)[i];
            char *sep = ap_strchr(hline, ':');
            if (!sep) {
                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, parser->c,
                              APLOGNO(02955) "h2_c2(%s): invalid header[%d] '%s'",
                              parser->id, i, (char*)hline);
                /* not valid format, abort */
                return NULL;
            }
            (*sep++) = '\0';
            while (*sep == ' ' || *sep == '\t') {
                ++sep;
            }

            if (!h2_util_ignore_resp_header(hline)) {
                apr_table_merge(headers, hline, sep);
            }
        }
        return headers;
    }
    else {
        return apr_table_make(parser->pool, 0);
    }
}

static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
                                  h2_response_parser *parser)
{
    apr_bucket *b;
    apr_status_t status;
    h2_headers *response = h2_headers_create(parser->http_status,
                                             make_table(parser),
                                             parser->c->notes,
                                             0, parser->pool);
    apr_brigade_cleanup(parser->tmp);
    b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
    b = apr_bucket_flush_create(parser->c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
    status = ap_pass_brigade(f->next, parser->tmp);
    apr_brigade_cleanup(parser->tmp);

    /* reset parser for possible next response */
    parser->state = H2_RP_STATUS_LINE;
    apr_array_clear(parser->hlines);

    if (response->status >= 200) {
        conn_ctx->has_final_response = 1;
    }
    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
                  APLOGNO(03197) "h2_c2(%s): passed response %d",
                  parser->id, response->status);
    return status;
}

static apr_status_t parse_status(h2_response_parser *parser, char *line)
{
    int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
                  (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
    if (sindex > 0) {
        int k = sindex + 3;
        char keepchar = line[k];
        line[k] = '\0';
        parser->http_status = atoi(&line[sindex]);
        line[k] = keepchar;
        parser->state = H2_RP_HEADER_LINE;

        return APR_SUCCESS;
    }
    /* Seems like there is garbage on the connection. May be a leftover
     * from a previous proxy request.
     * This should only happen if the H2_RESPONSE filter is not yet in
     * place (post_read_request has not been reached and the handler wants
     * to write something. Probably just the interim response we are
     * waiting for. But if there is other data hanging around before
     * that, this needs to fail. */
    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c, APLOGNO(03467)
                  "h2_c2(%s): unable to parse status line: %s",
                  parser->id, line);
    return APR_EINVAL;
}

static apr_status_t parse_response(h2_response_parser *parser,
                                   h2_conn_ctx_t *conn_ctx,
                                   ap_filter_t* f, apr_bucket_brigade *bb)
{
    char line[HUGE_STRING_LEN];
    apr_status_t status = APR_SUCCESS;

    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
        switch (parser->state) {
            case H2_RP_STATUS_LINE:
            case H2_RP_HEADER_LINE:
                status = get_line(parser, bb, line, sizeof(line));
                if (status == APR_EAGAIN) {
                    /* need more data */
                    return APR_SUCCESS;
                }
                else if (status != APR_SUCCESS) {
                    return status;
                }
                if (parser->state == H2_RP_STATUS_LINE) {
                    /* instead of parsing, just take it directly */
                    status = parse_status(parser, line);
                }
                else if (line[0] == '\0') {
                    /* end of headers, pass response onward */
                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
                                  "h2_c2(%s): end of response", parser->id);
                    return pass_response(conn_ctx, f, parser);
                }
                else {
                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
                                  "h2_c2(%s): response header %s", parser->id, line);
                    status = parse_header(parser, line);
                }
                break;

            default:
                return status;
        }
    }
    return status;
}

apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
{
    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
    h2_response_parser *parser = f->ctx;
    apr_status_t rv;

    ap_assert(conn_ctx);
    H2_FILTER_LOG("c2_catch_h1_out", f->c, APLOG_TRACE2, 0, "check", bb);

    if (!f->c->aborted && !conn_ctx->has_final_response) {
        if (!parser) {
            parser = apr_pcalloc(f->c->pool, sizeof(*parser));
            parser->id = apr_psprintf(f->c->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
            parser->pool = f->c->pool;
            parser->c = f->c;
            parser->state = H2_RP_STATUS_LINE;
            parser->hlines = apr_array_make(parser->pool, 10, sizeof(char *));
            f->ctx = parser;
        }

        if (!APR_BRIGADE_EMPTY(bb)) {
            apr_bucket *b = APR_BRIGADE_FIRST(bb);
            if (AP_BUCKET_IS_EOR(b)) {
                /* TODO: Yikes, this happens when errors are encountered on input
                 * before anything from the repsonse has been processed. The
                 * ap_die_r() call will do nothing in certain conditions.
                 */
                int result = ap_map_http_request_error(conn_ctx->last_err,
                                                       HTTP_INTERNAL_SERVER_ERROR);
                request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
                if (r) {
                    ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
                    b = ap_bucket_eor_create(f->c->bucket_alloc, r);
                    APR_BRIGADE_INSERT_TAIL(bb, b);
                }
            }
        }
        /* There are cases where we need to parse a serialized http/1.1 response.
         * One example is a 100-continue answer via a mod_proxy setup. */
        while (bb && !f->c->aborted && !conn_ctx->has_final_response) {
            rv = parse_response(parser, conn_ctx, f, bb);
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
                          "h2_c2(%s): parsed response", parser->id);
            if (APR_BRIGADE_EMPTY(bb) || APR_SUCCESS != rv) {
                return rv;
            }
        }
    }

    return ap_pass_brigade(f->next, bb);
}

apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb)
{
    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
    request_rec *r = f->r;
    apr_bucket *b, *bresp, *body_bucket = NULL, *next;
    ap_bucket_error *eb = NULL;
    h2_headers *response = NULL;
    int headers_passing = 0;

    H2_FILTER_LOG("c2_response_out", f->c, APLOG_TRACE1, 0, "called with", bb);

    if (f->c->aborted || !conn_ctx || conn_ctx->has_final_response) {
        return ap_pass_brigade(f->next, bb);
    }

    if (!conn_ctx->has_final_response) {
        /* check, if we need to send the response now. Until we actually
         * see a DATA bucket or some EOS/EOR, we do not do so. */
        for (b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb);
             b = APR_BUCKET_NEXT(b))
        {
            if (AP_BUCKET_IS_ERROR(b) && !eb) {
                eb = b->data;
            }
            else if (AP_BUCKET_IS_EOC(b)) {
                /* If we see an EOC bucket it is a signal that we should get out
                 * of the way doing nothing.
                 */
                ap_remove_output_filter(f);
                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
                              "h2_c2(%s): eoc bucket passed", conn_ctx->id);
                return ap_pass_brigade(f->next, bb);
            }
            else if (H2_BUCKET_IS_HEADERS(b)) {
                headers_passing = 1;
            }
            else if (!APR_BUCKET_IS_FLUSH(b)) {
                body_bucket = b;
                break;
            }
        }

        if (eb) {
            int st = eb->status;
            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
                          "h2_c2(%s): err bucket status=%d",
                          conn_ctx->id, st);
            /* throw everything away and replace it with the error response
             * generated by ap_die() */
            apr_brigade_cleanup(bb);
            ap_die(st, r);
            return AP_FILTER_ERROR;
        }

        if (body_bucket || !headers_passing) {
            /* time to insert the response bucket before the body or if
             * no h2_headers is passed, e.g. the response is empty */
            response = create_response(r);
            if (response == NULL) {
                ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
                              "h2_c2(%s): unable to create response", conn_ctx->id);
                return APR_ENOMEM;
            }

            bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
            if (body_bucket) {
                APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
            }
            else {
                APR_BRIGADE_INSERT_HEAD(bb, bresp);
            }
            conn_ctx->has_final_response = 1;
            r->sent_bodyct = 1;
            ap_remove_output_filter_byhandle(f->r->output_filters, "H2_C2_NET_CATCH_H1");
        }
    }

    if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
                      "h2_c2(%s): headers only, cleanup output brigade", conn_ctx->id);
        b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
        while (b != APR_BRIGADE_SENTINEL(bb)) {
            next = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
                break;
            }
            if (!H2_BUCKET_IS_HEADERS(b)) {
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
            }
            b = next;
        }
    }
    if (conn_ctx->has_final_response) {
        /* lets get out of the way, our task is done */
        ap_remove_output_filter(f);
    }
    return ap_pass_brigade(f->next, bb);
}


struct h2_chunk_filter_t {
    const char *id;
    int eos_chunk_added;
    apr_bucket_brigade *bbchunk;
    apr_off_t chunked_total;
};
typedef struct h2_chunk_filter_t h2_chunk_filter_t;


static void make_chunk(conn_rec *c, h2_chunk_filter_t *fctx, apr_bucket_brigade *bb,
                       apr_bucket *first, apr_off_t chunk_len,
                       apr_bucket *tail)
{
    /* Surround the buckets [first, tail[ with new buckets carrying the
     * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
     * to the end of the brigade. */
    char buffer[128];
    apr_bucket *b;
    apr_size_t len;

    len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer),
                                   "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
    b = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
    APR_BUCKET_INSERT_BEFORE(first, b);
    b = apr_bucket_immortal_create("\r\n", 2, bb->bucket_alloc);
    if (tail) {
        APR_BUCKET_INSERT_BEFORE(tail, b);
    }
    else {
        APR_BRIGADE_INSERT_TAIL(bb, b);
    }
    fctx->chunked_total += chunk_len;
    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
                  "h2_c2(%s): added chunk %ld, total %ld",
                  fctx->id, (long)chunk_len, (long)fctx->chunked_total);
}

static int ser_header(void *ctx, const char *name, const char *value)
{
    apr_bucket_brigade *bb = ctx;
    apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value);
    return 1;
}

static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx,
                                   apr_read_type_e block) {
    h2_chunk_filter_t *fctx = f->ctx;
    request_rec *r = f->r;
    apr_status_t status = APR_SUCCESS;

    if (!fctx->bbchunk) {
        fctx->bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc);
    }

    if (APR_BRIGADE_EMPTY(fctx->bbchunk)) {
        apr_bucket *b, *next, *first_data = NULL;
        apr_bucket_brigade *tmp;
        apr_off_t bblen = 0;

        /* get more data from the lower layer filters. Always do this
         * in larger pieces, since we handle the read modes ourself. */
        status = ap_get_brigade(f->next, fctx->bbchunk,
                                AP_MODE_READBYTES, block, conn_ctx->mplx->stream_max_mem);
        if (status != APR_SUCCESS) {
            return status;
        }

        for (b = APR_BRIGADE_FIRST(fctx->bbchunk);
             b != APR_BRIGADE_SENTINEL(fctx->bbchunk);
             b = next) {
            next = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_METADATA(b)) {
                if (first_data) {
                    make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, b);
                    first_data = NULL;
                }

                if (H2_BUCKET_IS_HEADERS(b)) {
                    h2_headers *headers = h2_bucket_headers_get(b);

                    ap_assert(headers);
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                                  "h2_c2(%s-%d): receiving trailers",
                                  conn_ctx->id, conn_ctx->stream_id);
                    tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
                    if (!apr_is_empty_table(headers->headers)) {
                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n");
                        apr_table_do(ser_header, fctx->bbchunk, headers->headers, NULL);
                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "\r\n");
                    }
                    else {
                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
                    }
                    r->trailers_in = apr_table_clone(r->pool, headers->headers);
                    APR_BUCKET_REMOVE(b);
                    apr_bucket_destroy(b);
                    APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
                    apr_brigade_destroy(tmp);
                    fctx->eos_chunk_added = 1;
                }
                else if (APR_BUCKET_IS_EOS(b)) {
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                                  "h2_c2(%s-%d): receiving eos",
                                  conn_ctx->id, conn_ctx->stream_id);
                    if (!fctx->eos_chunk_added) {
                        tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
                        status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
                        APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
                        apr_brigade_destroy(tmp);
                    }
                    fctx->eos_chunk_added = 0;
                }
            }
            else if (b->length == 0) {
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
            }
            else {
                if (!first_data) {
                    first_data = b;
                    bblen = 0;
                }
                bblen += b->length;
            }
        }

        if (first_data) {
            make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, NULL);
        }
    }
    return status;
}

apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
                                     apr_bucket_brigade* bb,
                                     ap_input_mode_t mode,
                                     apr_read_type_e block,
                                     apr_off_t readbytes)
{
    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
    h2_chunk_filter_t *fctx = f->ctx;
    request_rec *r = f->r;
    apr_status_t status = APR_SUCCESS;
    apr_bucket *b, *next;
    core_server_config *conf =
        (core_server_config *) ap_get_module_config(r->server->module_config,
                                                    &core_module);
    ap_assert(conn_ctx);

    if (!fctx) {
        fctx = apr_pcalloc(r->pool, sizeof(*fctx));
        fctx->id = apr_psprintf(r->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
        f->ctx = fctx;
    }

    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
                  "h2_c2(%s-%d): request input, mode=%d, block=%d, "
                  "readbytes=%ld, exp=%d",
                  conn_ctx->id, conn_ctx->stream_id, mode, block,
                  (long)readbytes, r->expecting_100);
    if (!conn_ctx->input_chunked) {
        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
        /* pipe data through, just take care of trailers */
        for (b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb); b = next) {
            next = APR_BUCKET_NEXT(b);
            if (H2_BUCKET_IS_HEADERS(b)) {
                h2_headers *headers = h2_bucket_headers_get(b);
                ap_assert(headers);
                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
                              "h2_c2(%s-%d): receiving trailers",
                              conn_ctx->id, conn_ctx->stream_id);
                r->trailers_in = headers->headers;
                if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
                    r->headers_in = apr_table_overlay(r->pool, r->headers_in,
                                                      r->trailers_in);
                }
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
                ap_remove_input_filter(f);

                if (headers->raw_bytes && h2_c_logio_add_bytes_in) {
                    h2_c_logio_add_bytes_in(f->c, headers->raw_bytes);
                }
                break;
            }
        }
        return status;
    }

    /* Things are more complicated. The standard HTTP input filter, which
     * does a lot what we do not want to duplicate, also cares about chunked
     * transfer encoding and trailers.
     * We need to simulate chunked encoding for it to be happy.
     */
    if ((status = read_and_chunk(f, conn_ctx, block)) != APR_SUCCESS) {
        return status;
    }

    if (mode == AP_MODE_EXHAUSTIVE) {
        /* return all we have */
        APR_BRIGADE_CONCAT(bb, fctx->bbchunk);
    }
    else if (mode == AP_MODE_READBYTES) {
        status = h2_brigade_concat_length(bb, fctx->bbchunk, readbytes);
    }
    else if (mode == AP_MODE_SPECULATIVE) {
        status = h2_brigade_copy_length(bb, fctx->bbchunk, readbytes);
    }
    else if (mode == AP_MODE_GETLINE) {
        /* we are reading a single LF line, e.g. the HTTP headers.
         * this has the nasty side effect to split the bucket, even
         * though it ends with CRLF and creates a 0 length bucket */
        status = apr_brigade_split_line(bb, fctx->bbchunk, block, HUGE_STRING_LEN);
        if (APLOGctrace1(f->c)) {
            char buffer[1024];
            apr_size_t len = sizeof(buffer)-1;
            apr_brigade_flatten(bb, buffer, &len);
            buffer[len] = 0;
            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                          "h2_c2(%s-%d): getline: %s",
                          conn_ctx->id, conn_ctx->stream_id, buffer);
        }
    }
    else {
        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
         * to support it. Seems to work. */
        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
                      APLOGNO(02942)
                      "h2_c2, unsupported READ mode %d", mode);
        status = APR_ENOTIMPL;
    }

    h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2, "returning input", bb);
    return status;
}

apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
{
    h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
    request_rec *r = f->r;
    apr_bucket *b, *e;

    if (conn_ctx && r) {
        /* Detect the EOS/EOR bucket and forward any trailers that may have
         * been set to our h2_headers.
         */
        for (b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb);
             b = APR_BUCKET_NEXT(b))
        {
            if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
                && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
                h2_headers *headers;
                apr_table_t *trailers;

                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
                              "h2_c2(%s-%d): sending trailers",
                              conn_ctx->id, conn_ctx->stream_id);
                trailers = apr_table_clone(r->pool, r->trailers_out);
                headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
                e = h2_bucket_headers_create(bb->bucket_alloc, headers);
                APR_BUCKET_INSERT_BEFORE(b, e);
                apr_table_clear(r->trailers_out);
                ap_remove_output_filter(f);
                break;
            }
        }
    }

    return ap_pass_brigade(f->next, bb);
}

#endif /* else #if AP_HAS_RESPONSE_BUCKETS */
