Commit fc767bfd authored by Jeff Trawick's avatar Jeff Trawick
Browse files

overhaul of HTTP proxy handling of request bodies to resolve

unlimited memory consumption issue with current 2.1.x
solution


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/proxy-reqbody@124201 13f79535-47bb-0310-9956-ffa450edef68
parent 45db95d7
Loading
Loading
Loading
Loading
+450 −185
Original line number Diff line number Diff line
@@ -235,6 +235,443 @@ static void ap_proxy_clear_connection(apr_pool_t *p, apr_table_t *headers)
    apr_table_unset(headers, "Connection");
}

static void add_te_chunked(apr_pool_t *p,
                           apr_bucket_alloc_t *bucket_alloc,
                           apr_bucket_brigade *header_brigade)
{
    apr_bucket *e;
    char *buf;
    const char te_hdr[] = "Transfer-Encoding: chunked" CRLF;

    buf = apr_pmemdup(p, te_hdr, sizeof(te_hdr)-1);
    ap_xlate_proto_to_ascii(buf, sizeof(te_hdr)-1);

    e = apr_bucket_pool_create(buf, sizeof(te_hdr)-1, p, bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}

static void add_cl(apr_pool_t *p,
                   apr_bucket_alloc_t *bucket_alloc,
                   apr_bucket_brigade *header_brigade,
                   const char *cl_val)
{
    apr_bucket *e;
    char *buf;

    buf = apr_pstrcat(p, "Content-Length: ",
                      cl_val,
                      CRLF,
                      NULL);
    ap_xlate_proto_to_ascii(buf, strlen(buf));
    e = apr_bucket_pool_create(buf, strlen(buf), p, bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}

#define ASCII_CRLF  "\015\012"
#define ASCII_ZERO  "\060"

static void terminate_headers(apr_bucket_alloc_t *bucket_alloc,
                              apr_bucket_brigade *header_brigade)
{
    apr_bucket *e;

    /* add empty line at the end of the headers */
    e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}

static apr_status_t pass_brigade(request_rec *r, proxy_conn_rec *conn,
                                 conn_rec *origin, apr_bucket_brigade *b,
                                 int flush)
{
    apr_status_t status;
    apr_off_t transferred;

    if (flush) {
        apr_bucket *e = apr_bucket_flush_create(r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(b, e);
    }
    apr_brigade_length(b, 0, &transferred);
    if (transferred != -1)
        conn->worker->s->transferred += transferred;
    status = ap_pass_brigade(origin->output_filters, b);
    if (status != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                     "proxy: pass request data failed to %pI (%s)",
                     conn->addr, conn->hostname);
        return status;
    }
    apr_brigade_cleanup(b);
    return APR_SUCCESS;
}

static apr_status_t stream_reqbody_chunked(apr_pool_t *p,
                                           request_rec *r,
                                           proxy_conn_rec *conn,
                                           conn_rec *origin,
                                           apr_bucket_brigade *header_brigade)
{
    int seen_eos = 0;
    apr_size_t hdr_len;
    apr_off_t bytes;
    apr_status_t status;
    apr_bucket_brigade *b, *input_brigade;
    apr_bucket *e;

    input_brigade = apr_brigade_create(p, origin->bucket_alloc);

    do {
        char chunk_hdr[20];  /* must be here due to transient bucket. */

        status = ap_get_brigade(r->input_filters, input_brigade,
                                AP_MODE_READBYTES, APR_BLOCK_READ,
                                HUGE_STRING_LEN);

        if (status != APR_SUCCESS) {
            return status;
        }

        /* If this brigade contain EOS, either stop or remove it. */
        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
            seen_eos = 1;

            /* We can't pass this EOS to the output_filters. */
            e = APR_BRIGADE_LAST(input_brigade);
            apr_bucket_delete(e);

            /* As a shortcut, if this brigade is simply an EOS bucket,
             * don't send anything down the filter chain.
             */
            if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade))) {
                break;
            }
        }

        apr_brigade_length(input_brigade, 1, &bytes);

        hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
                               "%" APR_UINT64_T_HEX_FMT CRLF, 
                               (apr_uint64_t)bytes);
        
        ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
        e = apr_bucket_transient_create(chunk_hdr, hdr_len,
                                        input_brigade->bucket_alloc);
        APR_BRIGADE_INSERT_HEAD(input_brigade, e);
        
        /*
         * Append the end-of-chunk CRLF
         */
        e = apr_bucket_immortal_create(ASCII_CRLF, 2, input_brigade->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
        
        if (header_brigade) {
            /* we never sent the header brigade, so go ahead and
             * take care of that now
             */
            add_te_chunked(p, r->connection->bucket_alloc, header_brigade);
            terminate_headers(r->connection->bucket_alloc, header_brigade);
            b = header_brigade;
            APR_BRIGADE_CONCAT(b, input_brigade);
            header_brigade = NULL;
        }
        else {
            b = input_brigade;
        }
        
        status = pass_brigade(r, conn, origin, b, 1);
        if (status != APR_SUCCESS) {
            return status;
        }
    } while (!seen_eos);

    if (header_brigade) {
        /* we never sent the header brigade because there was no request body;
         * send it now without T-E
         */
        terminate_headers(r->connection->bucket_alloc, header_brigade);
        b = header_brigade;
    }
    else {
        e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
                                       /* <trailers> */
                                       ASCII_CRLF,
                                       5, r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(input_brigade, e);
        b = input_brigade;
    }
    
    status = pass_brigade(r, conn, origin, b, 1);
    return status;
}

static apr_status_t stream_reqbody_cl(apr_pool_t *p,
                                      request_rec *r,
                                      proxy_conn_rec *conn,
                                      conn_rec *origin,
                                      apr_bucket_brigade *header_brigade,
                                      const char *old_cl_val)
{
    int seen_eos = 0;
    apr_status_t status;
    apr_bucket_brigade *b, *input_brigade;
    apr_bucket *e;

    input_brigade = apr_brigade_create(p, origin->bucket_alloc);

    do {
        status = ap_get_brigade(r->input_filters, input_brigade,
                                AP_MODE_READBYTES, APR_BLOCK_READ,
                                HUGE_STRING_LEN);

        if (status != APR_SUCCESS) {
            return status;
        }

        /* If this brigade contain EOS, either stop or remove it. */
        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
            seen_eos = 1;

            /* As a shortcut, if this brigade is simply an EOS bucket,
             * don't send anything down the filter chain.
             */
            if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade))) {
                break;
            }

            /* We can't pass this EOS to the output_filters. */
            e = APR_BRIGADE_LAST(input_brigade);
            apr_bucket_delete(e);
        }

        if (header_brigade) {
            /* we never sent the header brigade, so go ahead and
             * take care of that now
             */
            add_cl(p, r->connection->bucket_alloc, header_brigade, old_cl_val);
            terminate_headers(r->connection->bucket_alloc, header_brigade);
            b = header_brigade;
            APR_BRIGADE_CONCAT(b, input_brigade);
            header_brigade = NULL;
        }
        else {
            b = input_brigade;
        }
        
        status = pass_brigade(r, conn, origin, input_brigade, 1);
        if (status != APR_SUCCESS) {
            return status;
        }
    } while (!seen_eos);

    if (header_brigade) {
        /* we never sent the header brigade since there was no request
         * body; send it now, and only specify C-L if client specified
         * C-L: 0
         */
        if (!strcmp(old_cl_val, "0")) {
            add_cl(p, r->connection->bucket_alloc, header_brigade, old_cl_val);
        }
        terminate_headers(r->connection->bucket_alloc, header_brigade);
        status = pass_brigade(r, conn, origin, header_brigade, 1);
        if (status != APR_SUCCESS) {
            return status;
        }
    }

    return APR_SUCCESS;
}

#define MAX_MEM_SPOOL 16384

static apr_status_t spool_reqbody_cl(apr_pool_t *p,
                                     request_rec *r,
                                     proxy_conn_rec *conn,
                                     conn_rec *origin,
                                     apr_bucket_brigade *header_brigade)
{
    int seen_eos = 0;
    apr_status_t status;
    apr_bucket_brigade *body_brigade, *input_brigade;
    apr_bucket *e;
    apr_off_t bytes, bytes_spooled = 0, fsize = 0;
    apr_file_t *tmpfile = NULL;

    body_brigade = apr_brigade_create(p, origin->bucket_alloc);
    input_brigade = apr_brigade_create(p, origin->bucket_alloc);

    do {
        status = ap_get_brigade(r->input_filters, input_brigade,
                                AP_MODE_READBYTES, APR_BLOCK_READ,
                                HUGE_STRING_LEN);

        if (status != APR_SUCCESS) {
            return status;
        }

        /* If this brigade contain EOS, either stop or remove it. */
        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
            seen_eos = 1;

            /* As a shortcut, if this brigade is simply an EOS bucket,
             * don't send anything down the filter chain.
             */
            if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade))) {
                break;
            }

            /* We can't pass this EOS to the output_filters. */
            e = APR_BRIGADE_LAST(input_brigade);
            apr_bucket_delete(e);
        }

        apr_brigade_length(input_brigade, 1, &bytes);

        if (bytes_spooled + bytes > MAX_MEM_SPOOL) {
            /* can't spool any more in memory; write latest brigade to disk */
            if (tmpfile == NULL) {
                const char *temp_dir;
                char *template;

                status = apr_temp_dir_get(&temp_dir, p);
                if (status != APR_SUCCESS) {
                    ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                                 "proxy: request data temp directory search failed");
                    return status;
                }
                apr_filepath_merge(&template, temp_dir,
                                   "proxy_reqbody_spoolXXXXXX",
                                   APR_FILEPATH_NATIVE, p);
                status = apr_file_mktemp(&tmpfile, template, 0, p);
                if (status != APR_SUCCESS) {
                    ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                                 "proxy: request data tmp file creation failed");
                    return status;
                }
            }
            for (e = APR_BRIGADE_FIRST(input_brigade);
                 e != APR_BRIGADE_SENTINEL(input_brigade);
                 e = APR_BUCKET_NEXT(e)) {
                const char *data;
                apr_size_t bytes_read, bytes_written;

                apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
                status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
                if (status != APR_SUCCESS) {
                    ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                                 "proxy: request data tmp file I/O failed");
                    return status;
                }
                AP_DEBUG_ASSERT(bytes_read == bytes_written);
                fsize += bytes_written;
            }
        }
        else {
            APR_BRIGADE_CONCAT(body_brigade, input_brigade);
        }
        
        bytes_spooled += bytes;

    } while (!seen_eos);

    if (bytes_spooled) {
        add_cl(p, r->connection->bucket_alloc, header_brigade, apr_off_t_toa(p, bytes_spooled));
    }
    terminate_headers(r->connection->bucket_alloc, header_brigade);
    APR_BRIGADE_CONCAT(header_brigade, body_brigade);
    if (tmpfile) {
        e = apr_bucket_file_create(tmpfile, 0, fsize, p, origin->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    }
    status = pass_brigade(r, conn, origin, header_brigade, 1);
    if (status != APR_SUCCESS) {
        return status;
    }

    return APR_SUCCESS;
}

static apr_status_t send_request_body(apr_pool_t *p,
                                      request_rec *r,
                                      proxy_conn_rec *conn,
                                      conn_rec *origin,
                                      apr_bucket_brigade *header_brigade,
                                      int force10)
{
    enum {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL} rb_method = RB_INIT;
    const char *old_cl_val, *te_val;
    apr_status_t status;

    /* send CL or use chunked encoding?
     *
     * . CL is the most friendly to the origin server since it is the
     *   most widely supported
     * . CL stinks if we don't know the length since we have to buffer
     *   the data in memory or on disk until we get the entire data
     *
     * special cases to check for:
     * . if we're using HTTP/1.0 to origin server, then we must send CL
     * . if client sent C-L and there are no input resource filters, the
     *   the body size can't change so we send the same CL and stream the
     *   body
     * . if client used chunked or proxy-sendchunks is set, we'll also
     *   use chunked
     *
     * normal case:
     *   we have to compute content length by reading the entire request
     *   body; if request body is not small, we'll spool the remaining input
     *   to a temporary file
     *
     * special envvars to override the normal decision:
     * . proxy-sendchunks
     *   use chunked encoding; not compatible with force-proxy-request-1.0
     * . proxy-sendcl
     *   spool the request body to compute C-L
     * . proxy-sendunchangedcl
     *   use C-L from client and spool the request body
     */
    if (!force10 && apr_table_get(r->subprocess_env, "proxy-sendchunks")) {
        rb_method = RB_STREAM_CHUNKED;
    }
    else if (apr_table_get(r->subprocess_env, "proxy-sendcl")) {
        rb_method = RB_SPOOL_CL;
    }
    else {
        old_cl_val = apr_table_get(r->headers_in, "Content-Length");
        if (old_cl_val &&
            (r->input_filters == r->proto_input_filters
             || !strcmp(old_cl_val, "0")
             || apr_table_get(r->subprocess_env, "proxy-sendunchangedcl"))) {
            rb_method = RB_STREAM_CL;
        }
        else if (force10) {
            rb_method = RB_SPOOL_CL;
        }
        else if ((te_val = apr_table_get(r->headers_in, "Transfer-Encoding"))
                  && !strcasecmp(te_val, "chunked")) {
            rb_method = RB_STREAM_CHUNKED;
        }
        else {
            rb_method = RB_SPOOL_CL;
        }
    }

    switch(rb_method) {
    case RB_STREAM_CHUNKED:
        status = stream_reqbody_chunked(p, r, conn, origin, header_brigade);
        break;
    case RB_STREAM_CL:
        status = stream_reqbody_cl(p, r, conn, origin, header_brigade, old_cl_val);
        break;
    case RB_SPOOL_CL:
        status = spool_reqbody_cl(p, r, conn, origin, header_brigade);
        break;
    default:
        ap_assert(1 != 1);
    }

    return status;
}

static
apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r,
                                   proxy_conn_rec *conn, conn_rec *origin, 
@@ -244,17 +681,15 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r,
{
    conn_rec *c = r->connection;
    char *buf;
    apr_bucket *e, *last_header_bucket = NULL;
    apr_bucket *e;
    const apr_array_header_t *headers_in_array;
    const apr_table_entry_t *headers_in;
    int counter, seen_eos, send_chunks;
    int counter;
    apr_status_t status;
    apr_bucket_brigade *header_brigade, *body_brigade, *input_brigade;
    apr_off_t transferred = 0;
    apr_bucket_brigade *header_brigade;
    int force10;

    header_brigade = apr_brigade_create(p, origin->bucket_alloc);
    body_brigade = apr_brigade_create(p, origin->bucket_alloc);
    input_brigade = apr_brigade_create(p, origin->bucket_alloc);

    /*
     * Send the HTTP/1.1 request to the remote server
@@ -282,17 +717,20 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r,

    /* By default, we can not send chunks. That means we must buffer
     * the entire request before sending it along to ensure we have
     * the correct Content-Length attached.
     * the correct Content-Length attached.  A special case is when
     * the client specifies Content-Length and there are no filters
     * which muck with the request body.  In that situation, we don't
     * have to buffer the entire request and can still send the
     * Content-Length.  Another special case is when the client
     * specifies a C-L of 0.  Pass that through as well.
     */
    send_chunks = 0;

    if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
        buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
        force10 = 1;
    } else {
        buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
        if (apr_table_get(r->subprocess_env, "proxy-sendchunks")) {
            send_chunks = 1;
        }
        force10 = 0;
    }
    if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
        apr_table_unset(r->headers_in, "Connection");
@@ -460,187 +898,14 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r,
        APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    }

    /* If we can send chunks, do so! */
    if (send_chunks) {
        const char te_hdr[] = "Transfer-Encoding: chunked" CRLF;

        buf = apr_pmemdup(p, te_hdr, sizeof(te_hdr)-1);
        ap_xlate_proto_to_ascii(buf, sizeof(te_hdr)-1);

        e = apr_bucket_pool_create(buf, sizeof(te_hdr)-1, p, c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    }
    else {
        last_header_bucket = APR_BRIGADE_LAST(header_brigade);
    }

    /* add empty line at the end of the headers */
#if APR_CHARSET_EBCDIC
    e = apr_bucket_immortal_create("\015\012", 2, c->bucket_alloc);
#else
    e = apr_bucket_immortal_create(CRLF, sizeof(CRLF)-1, c->bucket_alloc);
#endif
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    e = apr_bucket_flush_create(c->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
    
    apr_brigade_length(header_brigade, 0, &transferred);
    if (transferred != -1)
        conn->worker->s->transferred += transferred;
    if (send_chunks) {
        status = ap_pass_brigade(origin->output_filters, header_brigade);

        if (status != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                         "proxy: request failed to %pI (%s)",
                         conn->addr, conn->hostname);
            return status;
        }
    }

    /* send the request data, if any. */
    seen_eos = 0;
    do {
        char chunk_hdr[20];  /* must be here due to transient bucket. */

        status = ap_get_brigade(r->input_filters, input_brigade,
                                AP_MODE_READBYTES, APR_BLOCK_READ,
                                HUGE_STRING_LEN);

        if (status != APR_SUCCESS) {
            return status;
        }

        /* If this brigade contain EOS, either stop or remove it. */
        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
            seen_eos = 1;

            /* As a shortcut, if this brigade is simply an EOS bucket,
             * don't send anything down the filter chain.
             */
            if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade))) {
                break;
            }

            /* We can't pass this EOS to the output_filters. */
            e = APR_BRIGADE_LAST(input_brigade);
            apr_bucket_delete(e);
        }

        if (send_chunks) {
#define ASCII_CRLF  "\015\012"
#define ASCII_ZERO  "\060"
            apr_size_t hdr_len;
            apr_off_t bytes;

            apr_brigade_length(input_brigade, 1, &bytes);

            hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
                                   "%" APR_UINT64_T_HEX_FMT CRLF, 
                                   (apr_uint64_t)bytes);

            ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
            e = apr_bucket_transient_create(chunk_hdr, hdr_len,
                                            body_brigade->bucket_alloc);
            APR_BRIGADE_INSERT_HEAD(input_brigade, e);

            /*
             * Append the end-of-chunk CRLF
             */
            e = apr_bucket_immortal_create(ASCII_CRLF, 2, c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(input_brigade, e);
        }
        else {
            /* The send_chunks case does not need to be setaside, but this
             * case does because we may have transient buckets that may get
             * overwritten in the next iteration of the loop.
             */
            e = APR_BRIGADE_FIRST(input_brigade);
            while (e != APR_BRIGADE_SENTINEL(input_brigade)) {
                apr_bucket_setaside(e, p);
                e = APR_BUCKET_NEXT(e);
            }
        }

        e = apr_bucket_flush_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(input_brigade, e);

        APR_BRIGADE_CONCAT(body_brigade, input_brigade);

        if (send_chunks) {
            status = ap_pass_brigade(origin->output_filters, body_brigade);

    status = send_request_body(p, r, conn, origin, header_brigade, force10);
    if (status != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                     "proxy: pass request data failed to %pI (%s)",
                      conn->addr, conn->hostname);
        return status;
    }

            apr_brigade_cleanup(body_brigade);
        }

    } while (!seen_eos);

    if (send_chunks) {
        e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
                                       /* <trailers> */
                                       ASCII_CRLF, 5, c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(body_brigade, e);
        e = apr_bucket_flush_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(body_brigade, e);
    }

    if (!send_chunks) {
        apr_off_t bytes;

        apr_brigade_length(body_brigade, 1, &bytes);

        if (bytes) {
            const char *cl_hdr = "Content-Length", *cl_val;
            cl_val = apr_off_t_toa(p, bytes);
            buf = apr_pstrcat(p, cl_hdr, ": ", cl_val, CRLF, NULL);
            ap_xlate_proto_to_ascii(buf, strlen(buf));
            e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
            APR_BUCKET_INSERT_AFTER(last_header_bucket, e);
        }
        else {
            /* A client might really have sent a C-L of 0.  Pass it on. */
            const char *cl_hdr = "Content-Length", *cl_val;

            cl_val = apr_table_get(r->headers_in, cl_hdr);
            if (cl_val && cl_val[0] == '0' && cl_val[1] == 0) {
                buf = apr_pstrcat(p, cl_hdr, ": ", cl_val, CRLF, NULL);
                ap_xlate_proto_to_ascii(buf, strlen(buf));
                e = apr_bucket_pool_create(buf, strlen(buf), p,
                                           c->bucket_alloc);
                APR_BUCKET_INSERT_AFTER(last_header_bucket, e);
            }
        }
        status = ap_pass_brigade(origin->output_filters, header_brigade);
        if (status != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                         "proxy: pass request data failed to %pI (%s)",
                         conn->addr, conn->hostname);
            return status;
        }

        apr_brigade_cleanup(header_brigade);
    }

    status = ap_pass_brigade(origin->output_filters, body_brigade);

    if (status != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
                     "proxy: pass request data failed to %pI (%s)",
                      conn->addr, conn->hostname);
        return status;
    }
    apr_brigade_length(body_brigade, 0, &transferred);
    if (transferred != -1)
        conn->worker->s->transferred += transferred;

    apr_brigade_cleanup(body_brigade);
    return APR_SUCCESS;
}
static void process_proxy_header(request_rec* r, proxy_server_conf* c,