]> granicus.if.org Git - apache/blob - modules/filters/mod_sed.c
mod_cache: Don't add cached/revalidated entity headers to a 304 response.
[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, "%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;
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
298     ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
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 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     for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);) {
321         const char *buf = NULL;
322         apr_size_t bytes = 0;
323         if (APR_BUCKET_IS_EOS(b)) {
324             apr_bucket *b1 = APR_BUCKET_NEXT(b);
325             /* Now clean up the internal sed buffer */
326             sed_finalize_eval(&ctx->eval, ctx);
327             status = flush_output_buffer(ctx);
328             if (status != APR_SUCCESS) {
329                 clear_ctxpool(ctx);
330                 return status;
331             }
332             APR_BUCKET_REMOVE(b);
333             /* Insert the eos bucket to ctx->bb brigade */
334             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
335             b = b1;
336         }
337         else if (APR_BUCKET_IS_FLUSH(b)) {
338             apr_bucket *b1 = APR_BUCKET_NEXT(b);
339             APR_BUCKET_REMOVE(b);
340             status = flush_output_buffer(ctx);
341             if (status != APR_SUCCESS) {
342                 clear_ctxpool(ctx);
343                 return status;
344             }
345             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
346             b = b1;
347         }
348         else if (APR_BUCKET_IS_METADATA(b)) {
349             b = APR_BUCKET_NEXT(b);
350         }
351         else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
352                  == APR_SUCCESS) {
353             apr_bucket *b1 = APR_BUCKET_NEXT(b);
354             status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
355             if (status != APR_SUCCESS) {
356                 clear_ctxpool(ctx);
357                 return status;
358             }
359             APR_BUCKET_REMOVE(b);
360             apr_bucket_delete(b);
361             b = b1;
362         }
363         else {
364             apr_bucket *b1 = APR_BUCKET_NEXT(b);
365             APR_BUCKET_REMOVE(b);
366             b = b1;
367         }
368     }
369     apr_brigade_cleanup(bb);
370     status = flush_output_buffer(ctx);
371     if (status != APR_SUCCESS) {
372         clear_ctxpool(ctx);
373         return status;
374     }
375     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
376         status = ap_pass_brigade(f->next, ctx->bb);
377         apr_brigade_cleanup(ctx->bb);
378     }
379     clear_ctxpool(ctx);
380     return status;
381 }
382
383 /* Entry function for Sed input filter */
384 static apr_status_t sed_request_filter(ap_filter_t *f,
385                                        apr_bucket_brigade *bb,
386                                        ap_input_mode_t mode,
387                                        apr_read_type_e block,
388                                        apr_off_t readbytes)
389 {
390     sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
391                                            &sed_module);
392     sed_filter_ctxt *ctx = f->ctx;
393     apr_status_t status;
394     apr_bucket_brigade *bbinp;
395     sed_expr_config *sed_cfg = &cfg->input;
396
397     if (mode != AP_MODE_READBYTES) {
398         return ap_get_brigade(f->next, bb, mode, block, readbytes);
399     }
400
401     if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
402         /* No sed expression */
403         return ap_get_brigade(f->next, bb, mode, block, readbytes);
404     }
405
406     if (!ctx) {
407         if (!ap_is_initial_req(f->r)) {
408             ap_remove_input_filter(f);
409             /* XXX : Should we filter the sub requests too */
410             return ap_get_brigade(f->next, bb, mode, block, readbytes);
411         }
412         status = init_context(f, sed_cfg, 0);
413         if (status != APR_SUCCESS)
414              return status;
415         ctx = f->ctx;
416         ctx->bb    = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
417         ctx->bbinp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
418     }
419
420     bbinp = ctx->bbinp;
421
422     /* Here is the logic :
423      * Read the readbytes data from next level fiter into bbinp. Loop through
424      * the buckets in bbinp and read the data from buckets and invoke
425      * sed_eval_buffer on the data. libsed will generate its output using
426      * sed_write_output which will add data in ctx->bb. Do it until it have
427      * atleast one bucket bucket in ctx->bb. At the end of data eos bucket
428      * should be there.
429      *
430      * Once eos bucket is seen, then invoke sed_finalize_eval to clear the
431      * output. If the last byte of data is not a new line character then sed
432      * will add a new line to the data that is default sed behaviour. Note
433      * that using this filter with POST data, caller may not expect this
434      * behaviour.
435      *
436      * If next level fiter generate the flush bucket, we can't do much about
437      * it. If we want to return the flush bucket in brigade bb (to the caller)
438      * the question is where to add it?
439      */
440     while (APR_BRIGADE_EMPTY(ctx->bb)) {
441         apr_bucket *b;
442
443         /* read the bytes from next level filter */
444         apr_brigade_cleanup(bbinp);
445         status = ap_get_brigade(f->next, bbinp, mode, block, readbytes);
446         if (status != APR_SUCCESS) {
447             return status;
448         }
449         for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp);
450              b = APR_BUCKET_NEXT(b)) {
451             const char *buf = NULL;
452             apr_size_t bytes;
453
454             if (APR_BUCKET_IS_EOS(b)) {
455                 /* eos bucket. Clear the internal sed buffers */
456                 sed_finalize_eval(&ctx->eval, ctx);
457                 flush_output_buffer(ctx);
458                 APR_BUCKET_REMOVE(b);
459                 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
460                 break;
461             }
462             else if (APR_BUCKET_IS_FLUSH(b)) {
463                 /* What should we do with flush bucket */
464                 continue;
465             }
466             if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
467                      == APR_SUCCESS) {
468                 status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
469                 if (status != APR_SUCCESS)
470                     return status;
471                 flush_output_buffer(ctx);
472             }
473         }
474     }
475
476     if (!APR_BRIGADE_EMPTY(ctx->bb)) {
477         apr_bucket *b = NULL;
478
479         if (apr_brigade_partition(ctx->bb, readbytes, &b) == APR_INCOMPLETE) {
480             APR_BRIGADE_CONCAT(bb, ctx->bb);
481         }
482         else {
483             APR_BRIGADE_CONCAT(bb, ctx->bb);
484             apr_brigade_split_ex(bb, b, ctx->bb);
485         }
486     }
487     return APR_SUCCESS;
488 }
489
490 static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg)
491 {
492     int offset = (int) (long) cmd->info;
493     sed_expr_config *sed_cfg =
494                 (sed_expr_config *) (((char *) cfg) + offset);
495     if (compile_sed_expr(sed_cfg, cmd, arg) != APR_SUCCESS) {
496         return apr_psprintf(cmd->temp_pool,
497                             "Failed to compile sed expression. %s",
498                             sed_cfg->last_error);
499     }
500     return NULL;
501 }
502
503 static void *create_sed_dir_config(apr_pool_t *p, char *s)
504 {
505     sed_config *cfg = apr_pcalloc(p, sizeof(sed_config));
506     return cfg;
507 }
508
509 static const command_rec sed_filter_cmds[] = {
510     AP_INIT_TAKE1("OutputSed", sed_add_expr,
511                   (void *) APR_OFFSETOF(sed_config, output),
512                   ACCESS_CONF,
513                   "Sed regular expression for Response"),
514     AP_INIT_TAKE1("InputSed", sed_add_expr,
515                   (void *) APR_OFFSETOF(sed_config, input),
516                   ACCESS_CONF,
517                   "Sed regular expression for Request"),
518     {NULL}
519 };
520
521 static void register_hooks(apr_pool_t *p)
522 {
523     ap_register_output_filter(sed_filter_name, sed_response_filter, NULL,
524                               AP_FTYPE_RESOURCE);
525     ap_register_input_filter(sed_filter_name, sed_request_filter, NULL,
526                              AP_FTYPE_RESOURCE);
527 }
528
529 AP_DECLARE_MODULE(sed) = {
530     STANDARD20_MODULE_STUFF,
531     create_sed_dir_config,      /* dir config creater */
532     NULL,                       /* dir merger --- default is to override */
533     NULL,                       /* server config */
534     NULL,                       /* merge server config */
535     sed_filter_cmds,            /* command table */
536     register_hooks              /* register hooks */
537 };