]> granicus.if.org Git - apache/blob - modules/filters/mod_sed.c
mod_brotli: Allow compression ratio logging with new BrotliFilterNote
[apache] / modules / filters / mod_sed.c
1 /*
2  * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
3  * Use is subject to license terms.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
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
13  * or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include "httpd.h"
19 #include "http_config.h"
20 #include "http_log.h"
21 #include "apr_strings.h"
22 #include "apr_general.h"
23 #include "util_filter.h"
24 #include "apr_buckets.h"
25 #include "http_request.h"
26 #include "libsed.h"
27
28 static const char *sed_filter_name = "Sed";
29 #define MODSED_OUTBUF_SIZE 8000
30 #define MAX_TRANSIENT_BUCKETS 50
31
32 typedef struct sed_expr_config
33 {
34     sed_commands_t *sed_cmds;
35     const char *last_error;
36 } sed_expr_config;
37
38 typedef struct sed_config
39 {
40     sed_expr_config output;
41     sed_expr_config input;
42 } sed_config;
43
44 /* Context for filter invocation for single HTTP request */
45 typedef struct sed_filter_ctxt
46 {
47     sed_eval_t eval;
48     ap_filter_t *f;
49     request_rec *r;
50     apr_bucket_brigade *bb;
51     apr_bucket_brigade *bbinp;
52     char *outbuf;
53     char *curoutbuf;
54     int bufsize;
55     apr_pool_t *tpool;
56     int numbuckets;
57 } sed_filter_ctxt;
58
59 module AP_MODULE_DECLARE_DATA sed_module;
60
61 /* This function will be call back from libsed functions if there is any error
62  * happend during execution of sed scripts
63  */
64 static apr_status_t log_sed_errf(void *data, const char *error)
65 {
66     request_rec *r = (request_rec *) data;
67     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02998) "%s", error);
68     return APR_SUCCESS;
69 }
70
71 /* This function will be call back from libsed functions if there is any
72  * compilation error.
73  */
74 static apr_status_t sed_compile_errf(void *data, const char *error)
75 {
76     sed_expr_config *sed_cfg = (sed_expr_config *) data;
77     sed_cfg->last_error = error;
78     return APR_SUCCESS;
79 }
80
81 /* clear the temporary pool (used for transient buckets)
82  */
83 static void clear_ctxpool(sed_filter_ctxt* ctx)
84 {
85     apr_pool_clear(ctx->tpool);
86     ctx->outbuf = NULL;
87     ctx->curoutbuf = NULL;
88     ctx->numbuckets = 0;
89 }
90
91 /* alloc_outbuf
92  * allocate output buffer
93  */
94 static void alloc_outbuf(sed_filter_ctxt* ctx)
95 {
96     ctx->outbuf = apr_palloc(ctx->tpool, ctx->bufsize + 1);
97     ctx->curoutbuf = ctx->outbuf;
98 }
99
100 /* append_bucket
101  * Allocate a new bucket from buf and sz and append to ctx->bb
102  */
103 static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz)
104 {
105     apr_status_t status = APR_SUCCESS;
106     apr_bucket *b;
107     if (ctx->tpool == ctx->r->pool) {
108         /* We are not using transient bucket */
109         b = apr_bucket_pool_create(buf, sz, ctx->r->pool,
110                                    ctx->r->connection->bucket_alloc);
111         APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
112     }
113     else {
114         /* We are using transient bucket */
115         b = apr_bucket_transient_create(buf, sz,
116                                         ctx->r->connection->bucket_alloc);
117         APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
118         ctx->numbuckets++;
119         if (ctx->numbuckets >= MAX_TRANSIENT_BUCKETS) {
120             b = apr_bucket_flush_create(ctx->r->connection->bucket_alloc);
121             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
122             status = ap_pass_brigade(ctx->f->next, ctx->bb);
123             apr_brigade_cleanup(ctx->bb);
124             clear_ctxpool(ctx);
125         }
126     }
127     return status;
128 }
129
130 /*
131  * flush_output_buffer
132  * Flush the  output data (stored in ctx->outbuf)
133  */
134 static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
135 {
136     int size = ctx->curoutbuf - ctx->outbuf;
137     char *out;
138     apr_status_t status = APR_SUCCESS;
139     if ((ctx->outbuf == NULL) || (size <=0))
140         return status;
141     out = apr_pmemdup(ctx->tpool, ctx->outbuf, size);
142     status = append_bucket(ctx, out, size);
143     ctx->curoutbuf = ctx->outbuf;
144     return status;
145 }
146
147 /* This is a call back function. When libsed wants to generate the output,
148  * this function will be invoked.
149  */
150 static apr_status_t sed_write_output(void *dummy, char *buf, int sz)
151 {
152     /* dummy is basically filter context. Context is passed during invocation
153      * of sed_eval_buffer
154      */
155     int remainbytes = 0;
156     apr_status_t status = APR_SUCCESS;
157     sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy;
158     if (ctx->outbuf == NULL) {
159         alloc_outbuf(ctx);
160     }
161     remainbytes = ctx->bufsize - (ctx->curoutbuf - ctx->outbuf);
162     if (sz >= remainbytes) {
163         if (remainbytes > 0) {
164             memcpy(ctx->curoutbuf, buf, remainbytes);
165             buf += remainbytes;
166             sz -= remainbytes;
167             ctx->curoutbuf += remainbytes;
168         }
169         /* buffer is now full */
170         status = append_bucket(ctx, ctx->outbuf, ctx->bufsize);
171         /* old buffer is now used so allocate new buffer */
172         alloc_outbuf(ctx);
173         /* if size is bigger than the allocated buffer directly add to output
174          * brigade */
175         if ((status == APR_SUCCESS) && (sz >= ctx->bufsize)) {
176             char* newbuf = apr_pmemdup(ctx->tpool, buf, sz);
177             status = append_bucket(ctx, newbuf, sz);
178             /* pool might get clear after append_bucket */
179             if (ctx->outbuf == NULL) {
180                 alloc_outbuf(ctx);
181             }
182         }
183         else {
184             memcpy(ctx->curoutbuf, buf, sz);
185             ctx->curoutbuf += sz;
186         }
187     }
188     else {
189         memcpy(ctx->curoutbuf, buf, sz);
190         ctx->curoutbuf += sz;
191     }
192     return status;
193 }
194
195 /* Compile a sed expression. Compiled context is saved in sed_cfg->sed_cmds.
196  * Memory required for compilation context is allocated from cmd->pool.
197  */
198 static apr_status_t compile_sed_expr(sed_expr_config *sed_cfg,
199                                      cmd_parms *cmd,
200                                      const char *expr)
201 {
202     apr_status_t status = APR_SUCCESS;
203
204     if (!sed_cfg->sed_cmds) {
205         sed_commands_t *sed_cmds;
206         sed_cmds = apr_pcalloc(cmd->pool, sizeof(sed_commands_t));
207         status = sed_init_commands(sed_cmds, sed_compile_errf, sed_cfg,
208                                    cmd->pool);
209         if (status != APR_SUCCESS) {
210             sed_destroy_commands(sed_cmds);
211             return status;
212         }
213         sed_cfg->sed_cmds = sed_cmds;
214     }
215     status = sed_compile_string(sed_cfg->sed_cmds, expr);
216     if (status != APR_SUCCESS) {
217         sed_destroy_commands(sed_cfg->sed_cmds);
218         sed_cfg->sed_cmds = NULL;
219     }
220     return status;
221 }
222
223 /* sed eval cleanup function */
224 static apr_status_t sed_eval_cleanup(void *data)
225 {
226     sed_eval_t *eval = (sed_eval_t *) data;
227     sed_destroy_eval(eval);
228     return APR_SUCCESS;
229 }
230
231 /* Initialize sed filter context. If successful then context is set in f->ctx
232  */
233 static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int usetpool)
234 {
235     apr_status_t status;
236     sed_filter_ctxt* ctx;
237     request_rec *r = f->r;
238     /* Create the context. Call sed_init_eval. libsed will generated
239      * output by calling sed_write_output and generates any error by
240      * invoking log_sed_errf.
241      */
242     ctx = apr_pcalloc(r->pool, sizeof(sed_filter_ctxt));
243     ctx->r = r;
244     ctx->bb = NULL;
245     ctx->numbuckets = 0;
246     ctx->f = f;
247     status = sed_init_eval(&ctx->eval, sed_cfg->sed_cmds, log_sed_errf,
248                            r, &sed_write_output, r->pool);
249     if (status != APR_SUCCESS) {
250         return status;
251     }
252     apr_pool_cleanup_register(r->pool, &ctx->eval, sed_eval_cleanup,
253                               apr_pool_cleanup_null);
254     ctx->bufsize = MODSED_OUTBUF_SIZE;
255     if (usetpool) {
256         apr_pool_create(&(ctx->tpool), r->pool);
257     }
258     else {
259         ctx->tpool = r->pool;
260     }
261     alloc_outbuf(ctx);
262     f->ctx = ctx;
263     return APR_SUCCESS;
264 }
265
266 /* Entry function for Sed output filter */
267 static apr_status_t sed_response_filter(ap_filter_t *f,
268                                         apr_bucket_brigade *bb)
269 {
270     apr_bucket *b;
271     apr_status_t status = APR_SUCCESS;
272     sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
273                                            &sed_module);
274     sed_filter_ctxt *ctx = f->ctx;
275     sed_expr_config *sed_cfg = &cfg->output;
276
277     if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
278         /* No sed expressions */
279         ap_remove_output_filter(f);
280         return ap_pass_brigade(f->next, bb);
281     }
282
283     if (ctx == NULL) {
284
285         if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
286             /* no need to run sed filter for Head requests */
287             ap_remove_output_filter(f);
288             return ap_pass_brigade(f->next, bb);
289         }
290
291         status = init_context(f, sed_cfg, 1);
292         if (status != APR_SUCCESS)
293              return status;
294         ctx = f->ctx;
295         apr_table_unset(f->r->headers_out, "Content-Length");
296
297         ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
298     }
299
300     /* Here is the main logic. Iterate through all the buckets, read the
301      * content of the bucket, call sed_eval_buffer on the data.
302      * sed_eval_buffer will read the data line by line, run filters on each
303      * line. sed_eval_buffer will generates the output by calling
304      * sed_write_output which will add the output to ctx->bb. At the end of
305      * the loop, ctx->bb is passed to the next filter in chain. At the end of
306      * the data, if new line is not found then sed_eval_buffer will store the
307      * data in its own buffer.
308      *
309      * Once eos bucket is found then sed_finalize_eval will flush the rest of
310      * the data. If there is no new line in last line of data, new line is
311      * appended (that is a solaris sed behavior). libsed's internal memory for
312      * evaluation is allocated on request's pool so it will be cleared once
313      * request is over.
314      *
315      * If flush bucket is found then append the flush bucket to ctx->bb
316      * and pass it to next filter. There may be some data which will still be
317      * in sed's internal buffer which can't be flushed until new line
318      * character is arrived.
319      */
320     while (!APR_BRIGADE_EMPTY(bb)) {
321         b = APR_BRIGADE_FIRST(bb);
322         if (APR_BUCKET_IS_EOS(b)) {
323             /* Now clean up the internal sed buffer */
324             sed_finalize_eval(&ctx->eval, ctx);
325             status = flush_output_buffer(ctx);
326             if (status != APR_SUCCESS) {
327                 break;
328             }
329             /* Move the eos bucket to ctx->bb brigade */
330             APR_BUCKET_REMOVE(b);
331             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
332         }
333         else if (APR_BUCKET_IS_FLUSH(b)) {
334             status = flush_output_buffer(ctx);
335             if (status != APR_SUCCESS) {
336                 break;
337             }
338             /* Move the flush bucket to ctx->bb brigade */
339             APR_BUCKET_REMOVE(b);
340             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
341         }
342         else {
343             if (!APR_BUCKET_IS_METADATA(b)) {
344                 const char *buf = NULL;
345                 apr_size_t bytes = 0;
346
347                 status = apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ);
348                 if (status == APR_SUCCESS) {
349                     status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
350                 }
351                 if (status != APR_SUCCESS) {
352                     break;
353                 }
354             }
355             apr_bucket_delete(b);
356         }
357     }
358     if (status == APR_SUCCESS) {
359         status = flush_output_buffer(ctx);
360     }
361     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
362         if (status == APR_SUCCESS) {
363             status = ap_pass_brigade(f->next, ctx->bb);
364         }
365         apr_brigade_cleanup(ctx->bb);
366     }
367     clear_ctxpool(ctx);
368     return status;
369 }
370
371 /* Entry function for Sed input filter */
372 static apr_status_t sed_request_filter(ap_filter_t *f,
373                                        apr_bucket_brigade *bb,
374                                        ap_input_mode_t mode,
375                                        apr_read_type_e block,
376                                        apr_off_t readbytes)
377 {
378     sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
379                                            &sed_module);
380     sed_filter_ctxt *ctx = f->ctx;
381     apr_status_t status;
382     apr_bucket_brigade *bbinp;
383     sed_expr_config *sed_cfg = &cfg->input;
384
385     if (mode != AP_MODE_READBYTES) {
386         return ap_get_brigade(f->next, bb, mode, block, readbytes);
387     }
388
389     if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
390         /* No sed expression */
391         return ap_get_brigade(f->next, bb, mode, block, readbytes);
392     }
393
394     if (!ctx) {
395         if (!ap_is_initial_req(f->r)) {
396             ap_remove_input_filter(f);
397             /* XXX : Should we filter the sub requests too */
398             return ap_get_brigade(f->next, bb, mode, block, readbytes);
399         }
400         status = init_context(f, sed_cfg, 0);
401         if (status != APR_SUCCESS)
402              return status;
403         ctx = f->ctx;
404         ctx->bb    = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
405         ctx->bbinp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
406     }
407
408     bbinp = ctx->bbinp;
409
410     /* Here is the logic :
411      * Read the readbytes data from next level fiter into bbinp. Loop through
412      * the buckets in bbinp and read the data from buckets and invoke
413      * sed_eval_buffer on the data. libsed will generate its output using
414      * sed_write_output which will add data in ctx->bb. Do it until it have
415      * atleast one bucket in ctx->bb. At the end of data eos bucket
416      * should be there.
417      *
418      * Once eos bucket is seen, then invoke sed_finalize_eval to clear the
419      * output. If the last byte of data is not a new line character then sed
420      * will add a new line to the data that is default sed behaviour. Note
421      * that using this filter with POST data, caller may not expect this
422      * behaviour.
423      *
424      * If next level fiter generate the flush bucket, we can't do much about
425      * it. If we want to return the flush bucket in brigade bb (to the caller)
426      * the question is where to add it?
427      */
428     while (APR_BRIGADE_EMPTY(ctx->bb)) {
429         apr_bucket *b;
430
431         /* read the bytes from next level filter */
432         apr_brigade_cleanup(bbinp);
433         status = ap_get_brigade(f->next, bbinp, mode, block, readbytes);
434         if (status != APR_SUCCESS) {
435             return status;
436         }
437         for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp);
438              b = APR_BUCKET_NEXT(b)) {
439             const char *buf = NULL;
440             apr_size_t bytes;
441
442             if (APR_BUCKET_IS_EOS(b)) {
443                 /* eos bucket. Clear the internal sed buffers */
444                 sed_finalize_eval(&ctx->eval, ctx);
445                 flush_output_buffer(ctx);
446                 APR_BUCKET_REMOVE(b);
447                 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
448                 break;
449             }
450             else if (APR_BUCKET_IS_FLUSH(b)) {
451                 /* What should we do with flush bucket */
452                 continue;
453             }
454             if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
455                      == APR_SUCCESS) {
456                 status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
457                 if (status != APR_SUCCESS)
458                     return status;
459                 flush_output_buffer(ctx);
460             }
461         }
462     }
463
464     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
465         apr_bucket *b = NULL;
466
467         if (apr_brigade_partition(ctx->bb, readbytes, &b) == APR_INCOMPLETE) {
468             APR_BRIGADE_CONCAT(bb, ctx->bb);
469         }
470         else {
471             APR_BRIGADE_CONCAT(bb, ctx->bb);
472             apr_brigade_split_ex(bb, b, ctx->bb);
473         }
474     }
475     return APR_SUCCESS;
476 }
477
478 static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg)
479 {
480     int offset = (int) (long) cmd->info;
481     sed_expr_config *sed_cfg =
482                 (sed_expr_config *) (((char *) cfg) + offset);
483     if (compile_sed_expr(sed_cfg, cmd, arg) != APR_SUCCESS) {
484         return apr_psprintf(cmd->temp_pool,
485                             "Failed to compile sed expression. %s",
486                             sed_cfg->last_error);
487     }
488     return NULL;
489 }
490
491 static void *create_sed_dir_config(apr_pool_t *p, char *s)
492 {
493     sed_config *cfg = apr_pcalloc(p, sizeof(sed_config));
494     return cfg;
495 }
496
497 static const command_rec sed_filter_cmds[] = {
498     AP_INIT_TAKE1("OutputSed", sed_add_expr,
499                   (void *) APR_OFFSETOF(sed_config, output),
500                   ACCESS_CONF,
501                   "Sed regular expression for Response"),
502     AP_INIT_TAKE1("InputSed", sed_add_expr,
503                   (void *) APR_OFFSETOF(sed_config, input),
504                   ACCESS_CONF,
505                   "Sed regular expression for Request"),
506     {NULL}
507 };
508
509 static void register_hooks(apr_pool_t *p)
510 {
511     ap_register_output_filter(sed_filter_name, sed_response_filter, NULL,
512                               AP_FTYPE_RESOURCE);
513     ap_register_input_filter(sed_filter_name, sed_request_filter, NULL,
514                              AP_FTYPE_RESOURCE);
515 }
516
517 AP_DECLARE_MODULE(sed) = {
518     STANDARD20_MODULE_STUFF,
519     create_sed_dir_config,      /* dir config creater */
520     NULL,                       /* dir merger --- default is to override */
521     NULL,                       /* server config */
522     NULL,                       /* merge server config */
523     sed_filter_cmds,            /* command table */
524     register_hooks              /* register hooks */
525 };