apr_status_t output_filter()

in apache2/apache2_io.c [653:1048]


apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) {
    request_rec *r = f->r;
    modsec_rec *msr = (modsec_rec *)f->ctx;
    apr_bucket *bucket = NULL, *eos_bucket = NULL;
    apr_status_t rc;
    int start_skipping = 0;

    /* Do we have the context? */
    if (msr == NULL) {
        ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
                "ModSecurity: Internal Error: msr is null in output filter.");
        ap_remove_output_filter(f);
        return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
    }

    msr->r = r;

    if (msr->txcfg->debuglog_level >= 9) {
        msr_log(msr, 9, "Output filter: Receiving output (f %pp, r %pp).", f, f->r);
    }

    /* Put back the Accept-Encoding and TE request headers
     * if they were removed from the request.
     */
    if (msr->txcfg->disable_backend_compression) {
        char *ae = (char *)apr_table_get(msr->request_headers, "Accept-Encoding");
        char *te = (char *)apr_table_get(msr->request_headers, "TE");

        if ((ae != NULL)&&(apr_table_get(f->r->headers_in, "Accept-Encoding") == NULL)) {
            apr_table_add(f->r->headers_in, "Accept-Encoding", ae);
        }

        if ((te != NULL)&&(apr_table_get(f->r->headers_in, "TE") == NULL)) {
            apr_table_add(f->r->headers_in, "TE", te);
        }
    }

    /* Initialise on first invocation */
    if (msr->of_status == OF_STATUS_NOT_STARTED) {
        /* Update our context from the request structure. */
        msr->r = r;
        msr->response_status = r->status;
        msr->status_line = ((r->status_line != NULL)
                ? r->status_line : ap_get_status_line(r->status));
        msr->response_protocol = get_response_protocol(r);

        if(msr->txcfg->crypto_hash_location_rx == 1 || msr->txcfg->crypto_hash_location_pm == 1)
            rc = modify_response_header(msr);

        msr->response_headers = apr_table_overlay(msr->mp, r->err_headers_out, r->headers_out);

        /* Process phase RESPONSE_HEADERS */
        rc = modsecurity_process_phase(msr, PHASE_RESPONSE_HEADERS);
        if (rc < 0) { /* error */
            ap_remove_output_filter(f);
            return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
        }

        if (rc > 0) { /* transaction needs to be interrupted */
            int status = perform_interception(msr);
            if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
                ap_remove_output_filter(f);
                msr->of_status = OF_STATUS_COMPLETE;
                msr->resbody_status = RESBODY_STATUS_ERROR;
                return send_error_bucket(msr, f, status);
            }
        }

        msr->outbound_error = 0;
        /* Decide whether to observe the response body. */
        rc = output_filter_init(msr, f, bb_in);
        switch(rc) {
            case -2 : /* response too large */
            case -1 : /* error */
                /* there's something wrong with this response */
                ap_remove_output_filter(f);
                msr->of_status = OF_STATUS_COMPLETE;
                msr->resbody_status = RESBODY_STATUS_ERROR;
                return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
            case 0 :
                /* We do not want to observe this response body
                 * but we need to remain attached to observe
                 * when it is completed so that we can run
                 * the RESPONSE_BODY phase.
                 */
                msr->of_skipping = 1;
                msr->resbody_status = RESBODY_STATUS_NOT_READ;
                break;
            default :
                /* Continue (observe the response body). */
                break;
        }

        /* If injecting content unset headers now. */
        if (msr->txcfg->content_injection_enabled == 0) {
            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Content Injection: Not enabled.");
            }
        } else {
            if ((msr->content_prepend) || (msr->content_append)) {
                apr_table_unset(msr->r->headers_out, "Content-Length");
                apr_table_unset(msr->r->headers_out, "Last-Modified");
                apr_table_unset(msr->r->headers_out, "ETag");
                apr_table_unset(msr->r->headers_out, "Expires");

                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "Content Injection: Removing headers (C-L, L-M, Etag, Expires).");
                }
            } else {
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "Content Injection: Nothing to inject.");
                }
            }
        }

        /* Content injection (prepend & non-buffering). */
        if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (msr->of_skipping)) {
            apr_bucket *bucket_ci = apr_bucket_heap_create(msr->content_prepend,
                    msr->content_prepend_len, NULL, f->r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_HEAD(bb_in, bucket_ci);

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Content Injection (nb): Added content to top: %s",
                        log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len));
            }
        }
    } else
        if (msr->of_status == OF_STATUS_COMPLETE) {
            msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked.");
            ap_remove_output_filter(f);
            return APR_EGENERAL;
        }


    /* Loop through the buckets in the brigade in order
     * to extract the size of the data available.
     */
    for(bucket = APR_BRIGADE_FIRST(bb_in);
            bucket != APR_BRIGADE_SENTINEL(bb_in);
            bucket = APR_BUCKET_NEXT(bucket)) {
        const char *buf;
        apr_size_t buflen;

        /* Look into response data if configured to do so,
         * unless we've already processed a partial response.
         */
        if ((msr->of_skipping == 0)&&(!msr->of_partial)) { /* Observe the response data. */
            /* Retrieve data from the bucket. */
            rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ);
            if (rc != APR_SUCCESS) {
                msr->of_status = OF_STATUS_COMPLETE;
                msr->resbody_status = RESBODY_STATUS_ERROR;

                msr_log(msr, 1, "Output filter: Failed to read bucket (rc %d): %s",
                        rc, get_apr_error(r->pool, rc));

                ap_remove_output_filter(f);
                return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
            }

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Output filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.",
                        bucket->type->name, buflen);
            }

            /* Check the response size. */
            if (msr->resbody_length > (apr_size_t)msr->txcfg->of_limit) {
                /* The size of the response is larger than what we're
                 * ready to accept. We need to decide what we want to do
                 * about it.
                 */
                msr->outbound_error = 1;
                if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) {
                    /* Reject response. */
                    msr_log(msr, 1, "Output filter: Response body too large (over limit of %ld, "
                            "total not specified).", msr->txcfg->of_limit);

                    msr->of_status = OF_STATUS_COMPLETE;
                    msr->resbody_status = RESBODY_STATUS_PARTIAL;

                    ap_remove_output_filter(f);
                    return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
                } else {
                    /* Process partial response. */
                    start_skipping = 1;
                    msr->resbody_length = msr->txcfg->of_limit;

                    if (msr->txcfg->debuglog_level >= 4) {
                        msr_log(msr, 4, "Output filter: Processing partial response body (limit %ld)",
                                msr->txcfg->of_limit);
                    }
                }
            } else {
                msr->resbody_length += buflen;
            }
        }

        /* Have we reached the end of the response? */
        if (APR_BUCKET_IS_EOS(bucket)) {
            eos_bucket = bucket;

            /* Inject content (append & non-buffering). */
            if ((msr->txcfg->content_injection_enabled) && (msr->content_append)
                    && (msr->of_skipping || msr->of_partial || start_skipping))
            {
                apr_bucket *bucket_ci = NULL;

                bucket_ci = apr_bucket_heap_create(msr->content_append,
                        msr->content_append_len, NULL, f->r->connection->bucket_alloc);
                APR_BUCKET_INSERT_BEFORE(bucket, bucket_ci);

                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "Content-Injection (nb): Added content to bottom: %s",
                            log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len));
                }
            }

            msr->of_done_reading = 1;
        }
    }

    /* Add buckets in this brigade to the brigade
     * we have in the context, but only if we actually
     * want to keep the response body.
     */
    if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) {
        ap_save_brigade(f, &msr->of_brigade, &bb_in, msr->mp);

        /* Do we need to process a partial response? */
        if (start_skipping) {

            if (msr->txcfg->stream_outbody_inspection)  {
                if(msr->stream_output_data != NULL) {
                    free(msr->stream_output_data);
                    msr->stream_output_data = NULL;
                }

                msr->stream_output_data = (char *)malloc(msr->resbody_length+1);
            }

            if (flatten_response_body(msr) < 0) {
                if (msr->txcfg->stream_outbody_inspection)  {
                    if(msr->stream_output_data != NULL) {
                        free(msr->stream_output_data);
                        msr->stream_output_data = NULL;
                    }
                }

                ap_remove_output_filter(f);
                return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
            }

            /* Process phase RESPONSE_BODY */
            rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY);
            if (rc < 0) {
                ap_remove_output_filter(f);
                return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
            }
            if (rc > 0) {
                int status = perform_interception(msr);
                if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
                    ap_remove_output_filter(f);
                    return send_error_bucket(msr, f, status);
                }
            }

            /* Prepend content as necessary. */
            prepend_content_to_of_brigade(msr, f);

            if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) {
                return rc;
            }

            msr->of_partial = 1;
        }

        if (msr->of_done_reading == 0) {
            /* We are done for now. We will be called again with more data. */
            return APR_SUCCESS;
        }

        if (msr->txcfg->debuglog_level >= 4) {
            msr_log(msr, 4, "Output filter: Completed receiving response body (buffered %s - %" APR_SIZE_T_FMT " bytes).",
                    (msr->of_partial ? "partial" : "full"), msr->resbody_length);
        }
    } else { /* Not looking at response data. */
        if (msr->of_done_reading == 0) {
            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Output filter: Sending input brigade directly.");
            }

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

        if (msr->txcfg->debuglog_level >= 4) {
            msr_log(msr, 4, "Output filter: Completed receiving response body (non-buffering).");
        }
    }

    /* We've done our thing; remove us from the filter list. */
    msr->of_status = OF_STATUS_COMPLETE;
    ap_remove_output_filter(f);

    /* Process phase RESPONSE_BODY, but
     * only if it hasn't been processed already.
     */
    if (msr->phase < PHASE_RESPONSE_BODY) {

        if (msr->txcfg->stream_outbody_inspection)  {
            if(msr->stream_output_data != NULL) {
                free(msr->stream_output_data);
                msr->stream_output_data = NULL;
            }

            msr->stream_output_data = (char *)malloc(msr->resbody_length+1);
        }

        if (flatten_response_body(msr) < 0) {
            if (msr->txcfg->stream_outbody_inspection)  {
                if(msr->stream_output_data != NULL) {
                    free(msr->stream_output_data);
                    msr->stream_output_data = NULL;
                }
            }

            ap_remove_output_filter(f);
            return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
        }

        rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY);
        if (rc < 0) {
            ap_remove_output_filter(f);
            return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
        }
        if (rc > 0) {
            int status = perform_interception(msr);
            if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
                ap_remove_output_filter(f);
                return send_error_bucket(msr, f, status);
            }
        }
    }

    /* Now send data down the filter stream
     * (full-buffering only).
     */
    if ((msr->of_skipping == 0)&&(!msr->of_partial)) {
        if(msr->of_stream_changed == 1) {
            inject_content_to_of_brigade(msr,f);
            msr->of_stream_changed = 0;
        }

        if (msr->txcfg->stream_outbody_inspection)  {
            if(msr->stream_output_data != NULL) {
                free(msr->stream_output_data);
                msr->stream_output_data = NULL;
            }
        }

        prepend_content_to_of_brigade(msr, f);

        /* Inject content into response (append & buffering). */
        if ((msr->txcfg->content_injection_enabled) && (msr->content_append)) {
            apr_bucket *bucket_ci = NULL;

            bucket_ci = apr_bucket_heap_create(msr->content_append,
                    msr->content_append_len, NULL, f->r->connection->bucket_alloc);
            APR_BUCKET_INSERT_BEFORE(eos_bucket, bucket_ci);

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Content-Injection (b): Added content to bottom: %s",
                        log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len));
            }
        }

        /* Send data down the filter stream. */
        if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) {
            return rc;
        }
    }

    /* Another job well done! */
    if (msr->txcfg->debuglog_level >= 4) {
        msr_log(msr, 4, "Output filter: Output forwarding complete.");
    }

    if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) {
        return APR_SUCCESS;
    } else {
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "Output filter: Sending input brigade directly.");
        }

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