Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / apache2_io.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
5  * This product is released under the terms of the General Public Licence,
6  * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7  * distribution) which contains the complete text of the licence.
8  *
9  * There are special exceptions to the terms and conditions of the GPL
10  * as it is applied to this software. View the full text of the exception in
11  * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
12  * distribution.
13  *
14  * If any of the files related to licensing are missing or if you have any
15  * other questions related to licensing please contact Breach Security, Inc.
16  * directly using the email address support@breach.com.
17  *
18  */
19 #include <util_filter.h>
20
21 #include "modsecurity.h"
22 #include "apache2.h"
23
24
25 /* -- Input filter -- */
26
27 #if 0
28 static void dummy_free_func(void *data) {}
29 #endif
30
31 /**
32  * This request filter will forward the previously stored
33  * request body further down the chain (most likely to the
34  * processing module).
35  */
36 apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out,
37     ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
38 {
39     modsec_rec *msr = (modsec_rec *)f->ctx;
40     msc_data_chunk *chunk = NULL;
41     apr_bucket *bucket;
42     apr_status_t rc;
43     char *my_error_msg = NULL;
44
45     if (msr == NULL) {
46         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
47             "ModSecurity: Internal error in input filter: msr is null.");
48         ap_remove_input_filter(f);
49         return APR_EGENERAL;
50     }
51
52     /* Make sure we are using the current request */
53     msr->r = f->r;
54
55     if (msr->phase < PHASE_REQUEST_BODY) {
56             msr_log(msr, 1, "Internal error: REQUEST_BODY phase incomplete for input filter in phase %d", msr->phase);
57             return APR_EGENERAL;
58     }
59
60     if ((msr->if_status == IF_STATUS_COMPLETE)||(msr->if_status == IF_STATUS_NONE)) {
61         if (msr->txcfg->debuglog_level >= 4) {
62             msr_log(msr, 4, "Input filter: Input forwarding already complete, skipping (f %pp, r %pp).", f, f->r);
63         }
64         ap_remove_input_filter(f);
65         return ap_get_brigade(f->next, bb_out, mode, block, nbytes);
66     }
67
68     if (msr->txcfg->debuglog_level >= 4) {
69         msr_log(msr, 4, "Input filter: Forwarding input: mode=%d, block=%d, nbytes=%" APR_OFF_T_FMT
70             " (f %pp, r %pp).", mode, block, nbytes, f, f->r);
71     }
72
73     if (msr->if_started_forwarding == 0) {
74         msr->if_started_forwarding = 1;
75         rc = modsecurity_request_body_retrieve_start(msr, &my_error_msg);
76         if (rc == -1) {
77             if (my_error_msg != NULL) {
78                 msr_log(msr, 1, "%s", my_error_msg);
79             }
80             return APR_EGENERAL;
81         }
82     }
83
84     rc = modsecurity_request_body_retrieve(msr, &chunk, (unsigned int)nbytes, &my_error_msg);
85     if (rc == -1) {
86         if (my_error_msg != NULL) {
87             msr_log(msr, 1, "%s", my_error_msg);
88         }
89         return APR_EGENERAL;
90     }
91
92     if (chunk) {
93         /* Copy the data we received in the chunk */
94         bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL,
95             f->r->connection->bucket_alloc);
96
97         #if 0
98
99         It would seem that we cannot prevent other filters in the chain
100         from modifying data in-place. Hence we copy.
101
102         if (chunk->is_permanent) {
103             /* Do not make a copy of the data we received in the chunk. */
104             bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func,
105                 f->r->connection->bucket_alloc);
106         } else {
107             /* Copy the data we received in the chunk. */
108             bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL,
109                 f->r->connection->bucket_alloc);
110         }
111
112         #endif
113
114         if (bucket == NULL) return APR_EGENERAL;
115         APR_BRIGADE_INSERT_TAIL(bb_out, bucket);
116
117         if (msr->txcfg->debuglog_level >= 4) {
118             msr_log(msr, 4, "Input filter: Forwarded %" APR_SIZE_T_FMT " bytes.", chunk->length);
119         }
120     }
121
122     if (rc == 0) {
123         modsecurity_request_body_retrieve_end(msr);
124
125         bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc);
126         if (bucket == NULL) return APR_EGENERAL;
127         APR_BRIGADE_INSERT_TAIL(bb_out, bucket);
128
129         if (msr->txcfg->debuglog_level >= 4) {
130             msr_log(msr, 4, "Input filter: Sent EOS.");
131         }
132
133         /* We're done */
134         msr->if_status = IF_STATUS_COMPLETE;
135         ap_remove_input_filter(f);
136
137         if (msr->txcfg->debuglog_level >= 4) {
138             msr_log(msr, 4, "Input filter: Input forwarding complete.");
139         }
140     }
141
142     return APR_SUCCESS;
143 }
144
145 /**
146  * Reads request body from a client.
147  */
148 apr_status_t read_request_body(modsec_rec *msr, char **error_msg) {
149     request_rec *r = msr->r;
150     unsigned int seen_eos;
151     apr_bucket_brigade *bb_in;
152     apr_bucket *bucket;
153
154     if (error_msg == NULL) return -1;
155     *error_msg = NULL;
156
157     if (msr->reqbody_should_exist != 1) {
158         if (msr->txcfg->debuglog_level >= 4) {
159             msr_log(msr, 4, "Input filter: This request does not have a body.");
160         }
161         return 0;
162     }
163
164     if (msr->txcfg->reqbody_access != 1) {
165         if (msr->txcfg->debuglog_level >= 4) {
166             msr_log(msr, 4, "Input filter: Request body access not enabled.");
167         }
168         return 0;
169     }
170
171     if (msr->txcfg->debuglog_level >= 4) {
172         msr_log(msr, 4, "Input filter: Reading request body.");
173     }
174
175     if (modsecurity_request_body_start(msr, error_msg) < 0) {
176         return -1;
177     }
178
179     seen_eos = 0;
180     bb_in = apr_brigade_create(msr->mp, r->connection->bucket_alloc);
181     if (bb_in == NULL) return -1;
182     do {
183         apr_status_t rc;
184
185         rc = ap_get_brigade(r->input_filters, bb_in, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
186         if (rc != APR_SUCCESS) {
187             /* NOTE Apache returns AP_FILTER_ERROR here when the request is
188              *      too large and APR_EGENERAL when the client disconnects.
189              */
190             switch(rc) {
191                 case APR_TIMEUP :
192                     *error_msg = apr_psprintf(msr->mp, "Error reading request body: %s", get_apr_error(msr->mp, rc));
193                     return -4;
194                 case AP_FILTER_ERROR :
195                     *error_msg = apr_psprintf(msr->mp, "Error reading request body: HTTP Error 413 - Request entity too large. (Most likely.)");
196                     return -3;
197                 case APR_EGENERAL :
198                     *error_msg = apr_psprintf(msr->mp, "Error reading request body: Client went away.");
199                     return -2;
200                 default :
201                     *error_msg = apr_psprintf(msr->mp, "Error reading request body: %s", get_apr_error(msr->mp, rc));
202                     return -1;
203             }
204         }
205
206         /* Loop through the buckets in the brigade in order
207          * to extract the size of the data available.
208          */
209         for(bucket = APR_BRIGADE_FIRST(bb_in);
210             bucket != APR_BRIGADE_SENTINEL(bb_in);
211             bucket = APR_BUCKET_NEXT(bucket))
212         {
213             const char *buf;
214             apr_size_t buflen;
215
216             rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ);
217             if (rc != APR_SUCCESS) {
218                 *error_msg = apr_psprintf(msr->mp, "Failed reading input / bucket (%d): %s", rc, get_apr_error(msr->mp, rc));
219                 return -1;
220             }
221
222             if (msr->txcfg->debuglog_level >= 9) {
223                 msr_log(msr, 9, "Input filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.",
224                     bucket->type->name, buflen);
225             }
226
227             /* Check request body limit (should only trigger on chunked requests). */
228             if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit) {
229                 *error_msg = apr_psprintf(msr->mp, "Request body is larger than the "
230                     "configured limit (%ld).", msr->txcfg->reqbody_limit);
231                 return -5;
232             }
233
234             if (buflen != 0) {
235                 int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg);
236                 if (rcbs < 0) {
237                     if (rcbs == -5) {
238                         *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the "
239                             "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit);
240                         return -5;
241                     }
242
243                     return -1;
244                 }
245
246                 msr->reqbody_length += buflen;
247             }
248
249             if (APR_BUCKET_IS_EOS(bucket)) {
250                 seen_eos = 1;
251             }
252         }
253
254         apr_brigade_cleanup(bb_in);
255     } while(!seen_eos);
256
257     // TODO: Why ignore the return code here?
258     modsecurity_request_body_end(msr, error_msg);
259
260     if (msr->txcfg->debuglog_level >= 4) {
261         msr_log(msr, 4, "Input filter: Completed receiving request body (length %" APR_SIZE_T_FMT ").",
262             msr->reqbody_length);
263     }
264
265     msr->if_status = IF_STATUS_WANTS_TO_RUN;
266
267     return 1;
268 }
269
270
271 /* -- Output filter -- */
272
273 /**
274  * Examines the configuration and the response MIME type
275  * in order to determine whether output buffering should
276  * run or not.
277  */
278 static int output_filter_should_run(modsec_rec *msr, request_rec *r) {
279     char *content_type = NULL;
280
281     /* Check configuration. */
282     if (msr->txcfg->resbody_access != 1) {
283         if (msr->txcfg->debuglog_level >= 4) {
284             msr_log(msr, 4, "Output filter: Response body buffering is not enabled.");
285         }
286
287         return 0;
288     }
289
290     /* Check MIME type. */
291
292     if ((msr->txcfg->of_mime_types == NULL)||(msr->txcfg->of_mime_types == NOT_SET_P)) {
293         msr_log(msr, 1, "Output filter: MIME type structures corrupted (internal error).");
294         return -1;
295     }
296
297     if (r->content_type != NULL) {
298         char *p = NULL;
299
300         content_type = apr_pstrdup(msr->mp, r->content_type);
301         if (content_type == NULL) {
302             msr_log(msr, 1, "Output filter: Failed to allocate memory for content type.");
303             return -1;
304         }
305
306         /* Hide the character encoding information
307          * if present. Sometimes the content type header
308          * looks like this "text/html; charset=xyz" ...
309          */
310         p = strstr(content_type, ";");
311         if (p != NULL) {
312             *p = '\0';
313         }
314
315         strtolower_inplace((unsigned char *)content_type);
316
317         if (strcmp(content_type, "text/html") == 0) {
318             /* Useful information to have should we later
319              * decide to do something with the HTML output.
320              */
321             msr->resbody_contains_html = 1;
322         }
323     } else {
324         content_type = "null";
325     }
326
327     if (apr_table_get(msr->txcfg->of_mime_types, content_type) != NULL) return 1;
328
329     msr_log(msr, 4, "Output filter: Not buffering response body for unconfigured MIME type \"%s\".", content_type);
330
331     return 0;
332 }
333
334 /**
335  * Initialises the output filter.
336  */
337 static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f,
338     apr_bucket_brigade *bb_in)
339 {
340     request_rec *r = f->r;
341     const char *s_content_length = NULL;
342     apr_status_t rc;
343
344     msr->of_brigade = apr_brigade_create(msr->mp, f->c->bucket_alloc);
345     if (msr->of_brigade == NULL) {
346         msr_log(msr, 1, "Output filter: Failed to create brigade.");
347         return -1;
348     }
349     msr->of_status = OF_STATUS_IN_PROGRESS;
350
351     rc = output_filter_should_run(msr, r);
352     if (rc < 0) return -1; /* output_filter_should_run() generates error msg */
353     if (rc == 0) return 0;
354
355     /* Do not check the output limit if we are willing to
356      * process partial response bodies.
357      */
358
359     if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_PARTIAL) {
360         return 1;
361     }
362
363     /* Look up the Content-Length header to see if we know
364      * the amount of data coming our way. If we do and if
365      * it's too much we might want to stop processing right here.
366      */
367     s_content_length = apr_table_get(r->headers_out, "Content-Length");
368     if (s_content_length == NULL) {
369         /* Try this too, mod_cgi seems to put headers there. */
370         s_content_length = apr_table_get(r->err_headers_out, "Content-Length");
371     }
372
373     if (s_content_length != NULL) {
374         long int len;
375
376         len = strtol(s_content_length, NULL, 10);
377         if ((len == LONG_MIN)||(len == LONG_MAX)||(len < 0)||(len >= 1073741824)) {
378             msr_log(msr, 1, "Output filter: Invalid Content-Length: %s", log_escape_nq(r->pool,
379                 (char *)s_content_length));
380             return -1; /* Invalid. */
381         }
382
383         if (len == 0) {
384             if (msr->txcfg->debuglog_level >= 4) {
385                 msr_log(msr, 4, "Output filter: Skipping response since Content-Length is zero.");
386             }
387
388             return 0;
389         }
390
391         if (len > msr->txcfg->of_limit) {
392             msr_log(msr, 1, "Output filter: Content-Length (%s) over the limit (%ld).",
393                 log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit);
394             return -2; /* Over the limit. */
395         }
396     }
397
398     return 1;
399 }
400
401 /**
402  * Send the accumulated content down the filter stream
403  * and to the client.
404  */
405 static apr_status_t send_of_brigade(modsec_rec *msr, ap_filter_t *f) {
406     apr_status_t rc;
407
408     rc = ap_pass_brigade(f->next, msr->of_brigade);
409     if (rc != APR_SUCCESS) {
410         /* TODO: These need to move to flags in 2.6.  For now log them
411          * at level 4 so that they are not confusing users.
412          */
413         int log_level = 4;
414
415         if (msr->txcfg->debuglog_level >= log_level) {
416             switch(rc) {
417                 case AP_NOBODY_WROTE :
418                     msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): No data", rc);
419                     break;
420                 case AP_FILTER_ERROR :
421                     /* Look like this is caused by the error
422                      * already being handled, so we should ignore it
423                      *
424                     msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): Filter error", rc);
425                      */
426                     break;
427                 default :
428                     msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): %s",
429                         rc, get_apr_error(msr->mp, rc));
430                     break;
431             }
432         }
433
434         return rc;
435     }
436
437     return APR_SUCCESS;
438 }
439
440 /**
441  *
442  */
443 static void prepend_content_to_of_brigade(modsec_rec *msr, ap_filter_t *f) {
444     if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (!msr->of_skipping)) {
445         apr_bucket *bucket_ci = NULL;
446
447         bucket_ci = apr_bucket_heap_create(msr->content_prepend,
448         msr->content_prepend_len, NULL, f->r->connection->bucket_alloc);
449             APR_BRIGADE_INSERT_HEAD(msr->of_brigade, bucket_ci);
450
451         if (msr->txcfg->debuglog_level >= 9) {
452             msr_log(msr, 9, "Content Injection (b): Added content to top: %s",
453                 log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len));
454         }
455     }
456 }
457
458 /**
459  *
460  */
461 static int flatten_response_body(modsec_rec *msr) {
462     apr_status_t rc;
463
464     msr->resbody_status = RESBODY_STATUS_READ_BRIGADE;
465
466     if (msr->resbody_length + 1 <= 0) {
467         msr_log(msr, 1, "Output filter: Invalid response length: %" APR_SIZE_T_FMT, msr->resbody_length);
468         return -1;
469     }
470
471     msr->resbody_data = apr_palloc(msr->mp, msr->resbody_length + 1);
472     if (msr->resbody_data == NULL) {
473         msr_log(msr, 1, "Output filter: Response body data memory allocation failed. Asked for: %" APR_SIZE_T_FMT,
474             msr->resbody_length + 1);
475         return -1;
476     }
477
478     rc = apr_brigade_flatten(msr->of_brigade, msr->resbody_data, &msr->resbody_length);
479     if (rc != APR_SUCCESS) {
480         msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", rc,
481             get_apr_error(msr->mp, rc));
482         return -1;
483     }
484
485     msr->resbody_data[msr->resbody_length] = '\0';
486     msr->resbody_status = RESBODY_STATUS_READ;
487
488     return 1;
489 }
490
491 /**
492  * Output filter.
493  */
494 apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) {
495     request_rec *r = f->r;
496     modsec_rec *msr = (modsec_rec *)f->ctx;
497     apr_bucket *bucket = NULL, *eos_bucket = NULL;
498     apr_status_t rc;
499     int start_skipping = 0;
500
501     /* Do we have the context? */
502     if (msr == NULL) {
503         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
504             "ModSecurity: Internal Error: msr is null in output filter.");
505         ap_remove_output_filter(f);
506         return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
507     }
508
509     msr->r = r;
510
511     if (msr->txcfg->debuglog_level >= 9) {
512         msr_log(msr, 9, "Output filter: Receiving output (f %pp, r %pp).", f, f->r);
513     }
514
515     /* Initialise on first invocation */
516     if (msr->of_status == OF_STATUS_NOT_STARTED) {
517         /* Update our context from the request structure. */
518         msr->r = r;
519         msr->response_status = r->status;
520         msr->status_line = ((r->status_line != NULL)
521             ? r->status_line : ap_get_status_line(r->status));
522         msr->response_protocol = get_response_protocol(r);
523         msr->response_headers = apr_table_overlay(msr->mp, r->err_headers_out, r->headers_out);
524
525         /* Process phase RESPONSE_HEADERS */
526         rc = modsecurity_process_phase(msr, PHASE_RESPONSE_HEADERS);
527         if (rc < 0) { /* error */
528             ap_remove_output_filter(f);
529             return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
530         }
531
532         if (rc > 0) { /* transaction needs to be interrupted */
533             int status = perform_interception(msr);
534             if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
535                 ap_remove_output_filter(f);
536                 msr->of_status = OF_STATUS_COMPLETE;
537                 msr->resbody_status = RESBODY_STATUS_ERROR;
538                 return send_error_bucket(msr, f, status);
539             }
540         }
541
542         /* Decide whether to observe the response body. */
543         rc = output_filter_init(msr, f, bb_in);
544         switch(rc) {
545             case -2 : /* response too large */
546             case -1 : /* error */
547                 /* there's something wrong with this response */
548                 ap_remove_output_filter(f);
549                 msr->of_status = OF_STATUS_COMPLETE;
550                 msr->resbody_status = RESBODY_STATUS_ERROR;
551                 return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
552             case 0 :
553                 /* We do not want to observe this response body
554                  * but we need to remain attached to observe
555                  * when it is completed so that we can run
556                  * the RESPONSE_BODY phase.
557                  */
558                 msr->of_skipping = 1;
559                 msr->resbody_status = RESBODY_STATUS_NOT_READ;
560                 break;
561             default :
562                 /* Continue (observe the response body). */
563                 break;
564         }
565
566         /* If injecting content unset headers now. */
567         if (msr->txcfg->content_injection_enabled == 0) {
568             if (msr->txcfg->debuglog_level >= 9) {
569                 msr_log(msr, 9, "Content Injection: Not enabled.");
570             }
571         } else {
572             if ((msr->content_prepend) || (msr->content_append)) {
573                 apr_table_unset(msr->r->headers_out, "Content-Length");
574                 apr_table_unset(msr->r->headers_out, "Last-Modified");
575                 apr_table_unset(msr->r->headers_out, "ETag");
576                 apr_table_unset(msr->r->headers_out, "Expires");
577
578                 if (msr->txcfg->debuglog_level >= 9) {
579                     msr_log(msr, 9, "Content Injection: Removing headers (C-L, L-M, Etag, Expires).");
580                 }
581             } else {
582                 if (msr->txcfg->debuglog_level >= 9) {
583                     msr_log(msr, 9, "Content Injection: Nothing to inject.");
584                 }
585             }
586         }
587
588         /* Content injection (prepend & non-buffering). */
589         if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (msr->of_skipping)) {
590             apr_bucket *bucket_ci = apr_bucket_heap_create(msr->content_prepend,
591                 msr->content_prepend_len, NULL, f->r->connection->bucket_alloc);
592             APR_BRIGADE_INSERT_HEAD(bb_in, bucket_ci);
593
594             if (msr->txcfg->debuglog_level >= 9) {
595                 msr_log(msr, 9, "Content Injection (nb): Added content to top: %s",
596                     log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len));
597             }
598         }
599     } else
600     if (msr->of_status == OF_STATUS_COMPLETE) {
601         msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked.");
602         ap_remove_output_filter(f);
603         return APR_EGENERAL;
604     }
605
606
607     /* Loop through the buckets in the brigade in order
608      * to extract the size of the data available.
609      */
610     for(bucket = APR_BRIGADE_FIRST(bb_in);
611       bucket != APR_BRIGADE_SENTINEL(bb_in);
612       bucket = APR_BUCKET_NEXT(bucket)) {
613         const char *buf;
614         apr_size_t buflen;
615
616         /* Look into response data if configured to do so,
617          * unless we've already processed a partial response.
618          */
619         if ((msr->of_skipping == 0)&&(!msr->of_partial)) { /* Observe the response data. */
620             /* Retrieve data from the bucket. */
621             rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ);
622             if (rc != APR_SUCCESS) {
623                 msr->of_status = OF_STATUS_COMPLETE;
624                 msr->resbody_status = RESBODY_STATUS_ERROR;
625
626                 msr_log(msr, 1, "Output filter: Failed to read bucket (rc %d): %s",
627                     rc, get_apr_error(r->pool, rc));
628
629                 ap_remove_output_filter(f);
630                 return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
631             }
632
633             if (msr->txcfg->debuglog_level >= 9) {
634                 msr_log(msr, 9, "Output filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.",
635                     bucket->type->name, buflen);
636             }
637
638             /* Check the response size. */
639             if (msr->resbody_length > (apr_size_t)msr->txcfg->of_limit) {
640                 /* The size of the response is larger than what we're
641                  * ready to accept. We need to decide what we want to do
642                  * about it.
643                  */
644                 if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) {
645                     /* Reject response. */
646                     msr_log(msr, 1, "Output filter: Response body too large (over limit of %ld, "
647                         "total not specified).", msr->txcfg->of_limit);
648
649                     msr->of_status = OF_STATUS_COMPLETE;
650                     msr->resbody_status = RESBODY_STATUS_PARTIAL;
651
652                     ap_remove_output_filter(f);
653                     return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
654                 } else {
655                     /* Process partial response. */
656                     start_skipping = 1;
657                     msr->resbody_length = msr->txcfg->of_limit;
658
659                     if (msr->txcfg->debuglog_level >= 4) {
660                         msr_log(msr, 4, "Output filter: Processing partial response body (limit %ld)",
661                             msr->txcfg->of_limit);
662                     }
663                 }
664             } else {
665                 msr->resbody_length += buflen;
666             }
667         }
668
669         /* Have we reached the end of the response? */
670         if (APR_BUCKET_IS_EOS(bucket)) {
671             eos_bucket = bucket;
672
673             /* Inject content (append & non-buffering). */
674             if ((msr->txcfg->content_injection_enabled) && (msr->content_append)
675                 && (msr->of_skipping || msr->of_partial || start_skipping))
676             {
677                 apr_bucket *bucket_ci = NULL;
678
679                 bucket_ci = apr_bucket_heap_create(msr->content_append,
680                     msr->content_append_len, NULL, f->r->connection->bucket_alloc);
681                 APR_BUCKET_INSERT_BEFORE(bucket, bucket_ci);
682
683                 if (msr->txcfg->debuglog_level >= 9) {
684                     msr_log(msr, 9, "Content-Injection (nb): Added content to bottom: %s",
685                         log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len));
686                 }
687             }
688
689             msr->of_done_reading = 1;
690         }
691     }
692
693     /* Add buckets in this brigade to the brigade
694      * we have in the context, but only if we actually
695      * want to keep the response body.
696      */
697     if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) {
698         ap_save_brigade(f, &msr->of_brigade, &bb_in, msr->mp);
699
700         /* Do we need to process a partial response? */
701         if (start_skipping) {
702             if (flatten_response_body(msr) < 0) {
703                 ap_remove_output_filter(f);
704                 return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
705             }
706
707             /* Process phase RESPONSE_BODY */
708             rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY);
709             if (rc < 0) {
710                 ap_remove_output_filter(f);
711                 return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
712             }
713             if (rc > 0) {
714                 int status = perform_interception(msr);
715                 if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
716                     ap_remove_output_filter(f);
717                     return send_error_bucket(msr, f, status);
718                 }
719             }
720
721             /* Prepend content as necessary. */
722             prepend_content_to_of_brigade(msr, f);
723
724             if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) {
725                 return rc;
726             }
727
728             msr->of_partial = 1;
729         }
730
731         if (msr->of_done_reading == 0) {
732             /* We are done for now. We will be called again with more data. */
733             return APR_SUCCESS;
734         }
735
736         if (msr->txcfg->debuglog_level >= 4) {
737             msr_log(msr, 4, "Output filter: Completed receiving response body (buffered %s - %" APR_SIZE_T_FMT " bytes).",
738                 (msr->of_partial ? "partial" : "full"), msr->resbody_length);
739         }
740     } else { /* Not looking at response data. */
741         if (msr->of_done_reading == 0) {
742             if (msr->txcfg->debuglog_level >= 9) {
743                 msr_log(msr, 9, "Output filter: Sending input brigade directly.");
744             }
745
746             return ap_pass_brigade(f->next, bb_in);
747         }
748
749         if (msr->txcfg->debuglog_level >= 4) {
750             msr_log(msr, 4, "Output filter: Completed receiving response body (non-buffering).");
751         }
752     }
753
754     /* We've done our thing; remove us from the filter list. */
755     msr->of_status = OF_STATUS_COMPLETE;
756     ap_remove_output_filter(f);
757
758     /* Process phase RESPONSE_BODY, but
759      * only if it hasn't been processed already.
760      */
761     if (msr->phase < PHASE_RESPONSE_BODY) {
762         if (flatten_response_body(msr) < 0) {
763             ap_remove_output_filter(f);
764             return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
765         }
766
767         rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY);
768         if (rc < 0) {
769             ap_remove_output_filter(f);
770             return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR);
771         }
772         if (rc > 0) {
773             int status = perform_interception(msr);
774             if (status != DECLINED) { /* DECLINED means we allow-ed the request. */
775                 ap_remove_output_filter(f);
776                 return send_error_bucket(msr, f, status);
777             }
778         }
779     }
780
781     /* Now send data down the filter stream
782      * (full-buffering only).
783      */
784     if ((msr->of_skipping == 0)&&(!msr->of_partial)) {
785         record_time_checkpoint(msr, 3);
786
787         prepend_content_to_of_brigade(msr, f);
788
789         /* Inject content into response (append & buffering). */
790         if ((msr->txcfg->content_injection_enabled) && (msr->content_append)) {
791             apr_bucket *bucket_ci = NULL;
792
793             bucket_ci = apr_bucket_heap_create(msr->content_append,
794                 msr->content_append_len, NULL, f->r->connection->bucket_alloc);
795             APR_BUCKET_INSERT_BEFORE(eos_bucket, bucket_ci);
796
797             if (msr->txcfg->debuglog_level >= 9) {
798                 msr_log(msr, 9, "Content-Injection (b): Added content to bottom: %s",
799                     log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len));
800             }
801         }
802
803         /* Send data down the filter stream. */
804         if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) {
805             return rc;
806         }
807     }
808
809     /* Another job well done! */
810     if (msr->txcfg->debuglog_level >= 4) {
811         msr_log(msr, 4, "Output filter: Output forwarding complete.");
812     }
813
814     if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) {
815         return APR_SUCCESS;
816     } else {
817         if (msr->txcfg->debuglog_level >= 9) {
818             msr_log(msr, 9, "Output filter: Sending input brigade directly.");
819         }
820
821         return ap_pass_brigade(f->next, bb_in);
822     }
823 }