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);
}
}