]> granicus.if.org Git - apache/blob - modules/filters/mod_request.c
mod_brotli: Allow compression ratio logging with new BrotliFilterNote
[apache] / modules / filters / mod_request.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_request.c --- HTTP routines to set aside or process request bodies.
19  */
20
21 #include "apr.h"
22 #include "apr_strings.h"
23 #include "apr_buckets.h"
24 #include "apr_lib.h"
25
26 #include "ap_config.h"
27 #include "httpd.h"
28 #include "http_config.h"
29 #include "http_protocol.h"
30 #include "http_log.h"           /* For errors detected in basic auth common
31                                  * support code... */
32 #include "http_request.h"
33
34 #include "mod_request.h"
35
36 /* Handles for core filters */
37 static ap_filter_rec_t *keep_body_input_filter_handle;
38 static ap_filter_rec_t *kept_body_input_filter_handle;
39
40 static apr_status_t bail_out_on_error(apr_bucket_brigade *bb,
41                                       ap_filter_t *f,
42                                       int http_error)
43 {
44     apr_bucket *e;
45
46     apr_brigade_cleanup(bb);
47     e = ap_bucket_error_create(http_error,
48                                NULL, f->r->pool,
49                                f->c->bucket_alloc);
50     APR_BRIGADE_INSERT_TAIL(bb, e);
51     e = apr_bucket_eos_create(f->c->bucket_alloc);
52     APR_BRIGADE_INSERT_TAIL(bb, e);
53     return ap_pass_brigade(f->r->output_filters, bb);
54 }
55
56 typedef struct keep_body_filter_ctx {
57     apr_off_t remaining;
58     apr_off_t keep_body;
59 } keep_body_ctx_t;
60
61 /**
62  * This is the KEEP_BODY_INPUT filter for HTTP requests, for times when the
63  * body should be set aside for future use by other modules.
64  */
65 static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
66                                      ap_input_mode_t mode,
67                                      apr_read_type_e block,
68                                      apr_off_t readbytes)
69 {
70     apr_bucket *e;
71     keep_body_ctx_t *ctx = f->ctx;
72     apr_status_t rv;
73     apr_bucket *bucket;
74     apr_off_t len = 0;
75
76
77     if (!ctx) {
78         const char *lenp;
79         char *endstr = NULL;
80         request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
81                                                        &request_module);
82
83         /* must we step out of the way? */
84         if (!dconf->keep_body || f->r->kept_body) {
85             ap_remove_input_filter(f);
86             return ap_get_brigade(f->next, b, mode, block, readbytes);
87         }
88
89         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
90
91         /* fail fast if the content length exceeds keep body */
92         lenp = apr_table_get(f->r->headers_in, "Content-Length");
93         if (lenp) {
94
95             /* Protects against over/underflow, non-digit chars in the
96              * string (excluding leading space) (the endstr checks)
97              * and a negative number. */
98             if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10)
99                 || endstr == lenp || *endstr || ctx->remaining < 0) {
100
101                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
102                               "Invalid Content-Length");
103
104                 ap_remove_input_filter(f);
105                 return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
106             }
107
108             /* If we have a limit in effect and we know the C-L ahead of
109              * time, stop it here if it is invalid.
110              */
111             if (dconf->keep_body < ctx->remaining) {
112                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01412)
113                           "Requested content-length of %" APR_OFF_T_FMT
114                           " is larger than the configured limit"
115                           " of %" APR_OFF_T_FMT, ctx->remaining, dconf->keep_body);
116                 ap_remove_input_filter(f);
117                 return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
118             }
119
120         }
121
122         f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
123         ctx->remaining = dconf->keep_body;
124
125     }
126
127     /* get the brigade from upstream, and read it in to get its length */
128     rv = ap_get_brigade(f->next, b, mode, block, readbytes);
129     if (rv == APR_SUCCESS) {
130         rv = apr_brigade_length(b, 1, &len);
131     }
132
133     /* does the length take us over the limit? */
134     if (APR_SUCCESS == rv && len > ctx->remaining) {
135         if (f->r->kept_body) {
136             apr_brigade_cleanup(f->r->kept_body);
137             f->r->kept_body = NULL;
138         }
139         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01413)
140                       "Requested content-length of %" APR_OFF_T_FMT
141                       " is larger than the configured limit"
142                       " of %" APR_OFF_T_FMT, len, ctx->keep_body);
143         return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
144     }
145     ctx->remaining -= len;
146
147     /* pass any errors downstream */
148     if (rv != APR_SUCCESS) {
149         if (f->r->kept_body) {
150             apr_brigade_cleanup(f->r->kept_body);
151             f->r->kept_body = NULL;
152         }
153         return rv;
154     }
155
156     /* all is well, set aside the buckets */
157     for (bucket = APR_BRIGADE_FIRST(b);
158          bucket != APR_BRIGADE_SENTINEL(b);
159          bucket = APR_BUCKET_NEXT(bucket))
160     {
161         apr_bucket_copy(bucket, &e);
162         APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e);
163     }
164
165     return APR_SUCCESS;
166 }
167
168
169 typedef struct kept_body_filter_ctx {
170     apr_off_t offset;
171     apr_off_t remaining;
172 } kept_body_ctx_t;
173
174 /**
175  * Initialisation of filter to handle a kept body on subrequests.
176  *
177  * If a body is to be reinserted into a subrequest, any chunking will have
178  * been removed from the body during storage. We need to change the request
179  * from Transfer-Encoding: chunked to an explicit Content-Length.
180  */
181 static int kept_body_filter_init(ap_filter_t *f)
182 {
183     apr_off_t length = 0;
184     request_rec *r = f->r;
185     apr_bucket_brigade *kept_body = r->kept_body;
186
187     if (kept_body) {
188         apr_table_unset(r->headers_in, "Transfer-Encoding");
189         apr_brigade_length(kept_body, 1, &length);
190         apr_table_setn(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length));
191     }
192
193     return OK;
194 }
195
196 /**
197  * Filter to handle a kept body on subrequests.
198  *
199  * If a body has been previously kept by the request, and if a subrequest wants
200  * to re-insert the body into the request, this input filter makes it happen.
201  */
202 static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
203                                      ap_input_mode_t mode,
204                                      apr_read_type_e block,
205                                      apr_off_t readbytes)
206 {
207     request_rec *r = f->r;
208     apr_bucket_brigade *kept_body = r->kept_body;
209     kept_body_ctx_t *ctx = f->ctx;
210     apr_bucket *ec, *e2;
211     apr_status_t rv;
212
213     /* just get out of the way of things we don't want. */
214     if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) {
215         return ap_get_brigade(f->next, b, mode, block, readbytes);
216     }
217
218     /* set up the context if it does not already exist */
219     if (!ctx) {
220         f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
221         ctx->offset = 0;
222         apr_brigade_length(kept_body, 1, &ctx->remaining);
223     }
224
225     /* kept_body is finished, send next filter */
226     if (ctx->remaining <= 0) {
227         return ap_get_brigade(f->next, b, mode, block, readbytes);
228     }
229
230     /* send all of the kept_body, but no more */
231     if (readbytes > ctx->remaining) {
232         readbytes = ctx->remaining;
233     }
234
235     /* send part of the kept_body */
236     if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) {
237         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414)
238                       "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset);
239         return rv;
240     }
241     if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) {
242         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415)
243                       "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes);
244         return rv;
245     }
246
247     do {
248         apr_bucket *foo;
249         const char *str;
250         apr_size_t len;
251
252         if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
253             /* As above; this should not fail since the bucket has
254              * a known length, but just to be sure, this takes
255              * care of uncopyable buckets that do somehow manage
256              * to slip through.  */
257             /* XXX: check for failure? */
258             apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
259             apr_bucket_copy(ec, &foo);
260         }
261         APR_BRIGADE_INSERT_TAIL(b, foo);
262         ec = APR_BUCKET_NEXT(ec);
263     } while (ec != e2);
264
265     ctx->remaining -= readbytes;
266     ctx->offset += readbytes;
267     return APR_SUCCESS;
268
269 }
270
271 /**
272  * Check whether this filter is not already present.
273  */
274 static int request_is_filter_present(request_rec * r, ap_filter_rec_t *fn)
275 {
276     ap_filter_t * f = r->input_filters;
277     while (f) {
278         if (f->frec == fn) {
279             return 1;
280         }
281         f = f->next;
282     }
283     return 0;
284 }
285
286 /**
287  * Insert filter hook.
288  *
289  * Add the KEEP_BODY filter to the request, if the admin wants to keep
290  * the body using the KeptBodySize directive.
291  *
292  * As a precaution, any pre-existing instances of either the kept_body or
293  * keep_body filters will be removed before the filter is added.
294  *
295  * @param r The request
296  */
297 static void ap_request_insert_filter(request_rec * r)
298 {
299     request_dir_conf *conf = ap_get_module_config(r->per_dir_config,
300                                                   &request_module);
301
302     if (r->kept_body) {
303         if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
304             ap_add_input_filter_handle(kept_body_input_filter_handle,
305                                        NULL, r, r->connection);
306         }
307     }
308     else if (conf->keep_body) {
309         if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
310             ap_add_input_filter_handle(keep_body_input_filter_handle,
311                                        NULL, r, r->connection);
312         }
313     }
314
315 }
316
317 /**
318  * Remove the kept_body and keep body filters from this specific request.
319  */
320 static void ap_request_remove_filter(request_rec * r)
321 {
322     ap_filter_t * f = r->input_filters;
323     while (f) {
324         if (f->frec->filter_func.in_func == kept_body_filter ||
325                 f->frec->filter_func.in_func == keep_body_filter) {
326             ap_remove_input_filter(f);
327         }
328         f = f->next;
329     }
330 }
331
332 static void *create_request_dir_config(apr_pool_t *p, char *dummy)
333 {
334     request_dir_conf *new =
335         (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
336
337     new->keep_body_set = 0; /* unset */
338     new->keep_body = 0; /* don't by default */
339
340     return (void *) new;
341 }
342
343 static void *merge_request_dir_config(apr_pool_t *p, void *basev, void *addv)
344 {
345     request_dir_conf *new = (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
346     request_dir_conf *add = (request_dir_conf *) addv;
347     request_dir_conf *base = (request_dir_conf *) basev;
348
349     new->keep_body = (add->keep_body_set == 0) ? base->keep_body : add->keep_body;
350     new->keep_body_set = add->keep_body_set || base->keep_body_set;
351
352     return new;
353 }
354
355 static const char *set_kept_body_size(cmd_parms *cmd, void *dconf,
356                                       const char *arg)
357 {
358     request_dir_conf *conf = dconf;
359     char *end = NULL;
360
361     if (APR_SUCCESS != apr_strtoff(&(conf->keep_body), arg, &end, 10)
362             || conf->keep_body < 0 || *end) {
363         return "KeptBodySize must be a valid size in bytes, or zero.";
364     }
365     conf->keep_body_set = 1;
366
367     return NULL;
368 }
369
370 static const command_rec request_cmds[] = {
371     AP_INIT_TAKE1("KeptBodySize", set_kept_body_size, NULL, ACCESS_CONF,
372                   "Maximum size of request bodies kept aside for use by filters"),
373     { NULL }
374 };
375
376 static void register_hooks(apr_pool_t *p)
377 {
378     keep_body_input_filter_handle =
379         ap_register_input_filter(KEEP_BODY_FILTER, keep_body_filter,
380                                  NULL, AP_FTYPE_RESOURCE);
381     kept_body_input_filter_handle =
382         ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter,
383                                  kept_body_filter_init, AP_FTYPE_RESOURCE);
384     ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST);
385     APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter);
386     APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter);
387 }
388
389 AP_DECLARE_MODULE(request) = {
390     STANDARD20_MODULE_STUFF,
391     create_request_dir_config, /* create per-directory config structure */
392     merge_request_dir_config,  /* merge per-directory config structures */
393     NULL,                      /* create per-server config structure */
394     NULL,                      /* merge per-server config structures */
395     request_cmds,              /* command apr_table_t */
396     register_hooks             /* register hooks */
397 };