2 * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Use is subject to license terms.
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.
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
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 #include "http_config.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"
28 static const char *sed_filter_name = "Sed";
29 #define MODSED_OUTBUF_SIZE 8000
30 #define MAX_TRANSIENT_BUCKETS 50
32 typedef struct sed_expr_config
34 sed_commands_t *sed_cmds;
35 const char *last_error;
38 typedef struct sed_config
40 sed_expr_config output;
41 sed_expr_config input;
44 /* Context for filter invocation for single HTTP request */
45 typedef struct sed_filter_ctxt
50 apr_bucket_brigade *bb;
51 apr_bucket_brigade *bbinp;
59 module AP_MODULE_DECLARE_DATA sed_module;
61 /* This function will be call back from libsed functions if there is any error
62 * happend during execution of sed scripts
64 static apr_status_t log_sed_errf(void *data, const char *error)
66 request_rec *r = (request_rec *) data;
67 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02998) "%s", error);
71 /* This function will be call back from libsed functions if there is any
74 static apr_status_t sed_compile_errf(void *data, const char *error)
76 sed_expr_config *sed_cfg = (sed_expr_config *) data;
77 sed_cfg->last_error = error;
81 /* clear the temporary pool (used for transient buckets)
83 static void clear_ctxpool(sed_filter_ctxt* ctx)
85 apr_pool_clear(ctx->tpool);
87 ctx->curoutbuf = NULL;
92 * allocate output buffer
94 static void alloc_outbuf(sed_filter_ctxt* ctx)
96 ctx->outbuf = apr_palloc(ctx->tpool, ctx->bufsize + 1);
97 ctx->curoutbuf = ctx->outbuf;
101 * Allocate a new bucket from buf and sz and append to ctx->bb
103 static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz)
105 apr_status_t status = APR_SUCCESS;
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);
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);
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);
131 * flush_output_buffer
132 * Flush the output data (stored in ctx->outbuf)
134 static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
136 int size = ctx->curoutbuf - ctx->outbuf;
138 apr_status_t status = APR_SUCCESS;
139 if ((ctx->outbuf == NULL) || (size <=0))
141 out = apr_pmemdup(ctx->tpool, ctx->outbuf, size);
142 status = append_bucket(ctx, out, size);
143 ctx->curoutbuf = ctx->outbuf;
147 /* This is a call back function. When libsed wants to generate the output,
148 * this function will be invoked.
150 static apr_status_t sed_write_output(void *dummy, char *buf, int sz)
152 /* dummy is basically filter context. Context is passed during invocation
156 apr_status_t status = APR_SUCCESS;
157 sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy;
158 if (ctx->outbuf == NULL) {
161 remainbytes = ctx->bufsize - (ctx->curoutbuf - ctx->outbuf);
162 if (sz >= remainbytes) {
163 if (remainbytes > 0) {
164 memcpy(ctx->curoutbuf, buf, remainbytes);
167 ctx->curoutbuf += remainbytes;
169 /* buffer is now full */
170 status = append_bucket(ctx, ctx->outbuf, ctx->bufsize);
171 /* old buffer is now used so allocate new buffer */
173 /* if size is bigger than the allocated buffer directly add to output
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) {
184 memcpy(ctx->curoutbuf, buf, sz);
185 ctx->curoutbuf += sz;
189 memcpy(ctx->curoutbuf, buf, sz);
190 ctx->curoutbuf += sz;
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.
198 static apr_status_t compile_sed_expr(sed_expr_config *sed_cfg,
202 apr_status_t status = APR_SUCCESS;
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,
209 if (status != APR_SUCCESS) {
210 sed_destroy_commands(sed_cmds);
213 sed_cfg->sed_cmds = sed_cmds;
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;
223 /* sed eval cleanup function */
224 static apr_status_t sed_eval_cleanup(void *data)
226 sed_eval_t *eval = (sed_eval_t *) data;
227 sed_destroy_eval(eval);
231 /* Initialize sed filter context. If successful then context is set in f->ctx
233 static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int usetpool)
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.
242 ctx = apr_pcalloc(r->pool, sizeof(sed_filter_ctxt));
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) {
252 apr_pool_cleanup_register(r->pool, &ctx->eval, sed_eval_cleanup,
253 apr_pool_cleanup_null);
254 ctx->bufsize = MODSED_OUTBUF_SIZE;
256 apr_pool_create(&(ctx->tpool), r->pool);
259 ctx->tpool = r->pool;
266 /* Entry function for Sed output filter */
267 static apr_status_t sed_response_filter(ap_filter_t *f,
268 apr_bucket_brigade *bb)
271 apr_status_t status = APR_SUCCESS;
272 sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
274 sed_filter_ctxt *ctx = f->ctx;
275 sed_expr_config *sed_cfg = &cfg->output;
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);
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);
291 status = init_context(f, sed_cfg, 1);
292 if (status != APR_SUCCESS)
295 apr_table_unset(f->r->headers_out, "Content-Length");
297 ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
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.
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
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.
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) {
329 /* Move the eos bucket to ctx->bb brigade */
330 APR_BUCKET_REMOVE(b);
331 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
333 else if (APR_BUCKET_IS_FLUSH(b)) {
334 status = flush_output_buffer(ctx);
335 if (status != APR_SUCCESS) {
338 /* Move the flush bucket to ctx->bb brigade */
339 APR_BUCKET_REMOVE(b);
340 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
343 if (!APR_BUCKET_IS_METADATA(b)) {
344 const char *buf = NULL;
345 apr_size_t bytes = 0;
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);
351 if (status != APR_SUCCESS) {
355 apr_bucket_delete(b);
358 if (status == APR_SUCCESS) {
359 status = flush_output_buffer(ctx);
361 if (!APR_BRIGADE_EMPTY(ctx->bb)) {
362 if (status == APR_SUCCESS) {
363 status = ap_pass_brigade(f->next, ctx->bb);
365 apr_brigade_cleanup(ctx->bb);
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,
378 sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
380 sed_filter_ctxt *ctx = f->ctx;
382 apr_bucket_brigade *bbinp;
383 sed_expr_config *sed_cfg = &cfg->input;
385 if (mode != AP_MODE_READBYTES) {
386 return ap_get_brigade(f->next, bb, mode, block, readbytes);
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);
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);
400 status = init_context(f, sed_cfg, 0);
401 if (status != APR_SUCCESS)
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);
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
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
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?
428 while (APR_BRIGADE_EMPTY(ctx->bb)) {
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) {
437 for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp);
438 b = APR_BUCKET_NEXT(b)) {
439 const char *buf = NULL;
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);
450 else if (APR_BUCKET_IS_FLUSH(b)) {
451 /* What should we do with flush bucket */
454 if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
456 status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
457 if (status != APR_SUCCESS)
459 flush_output_buffer(ctx);
464 if (!APR_BRIGADE_EMPTY(ctx->bb)) {
465 apr_bucket *b = NULL;
467 if (apr_brigade_partition(ctx->bb, readbytes, &b) == APR_INCOMPLETE) {
468 APR_BRIGADE_CONCAT(bb, ctx->bb);
471 APR_BRIGADE_CONCAT(bb, ctx->bb);
472 apr_brigade_split_ex(bb, b, ctx->bb);
478 static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg)
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);
491 static void *create_sed_dir_config(apr_pool_t *p, char *s)
493 sed_config *cfg = apr_pcalloc(p, sizeof(sed_config));
497 static const command_rec sed_filter_cmds[] = {
498 AP_INIT_TAKE1("OutputSed", sed_add_expr,
499 (void *) APR_OFFSETOF(sed_config, output),
501 "Sed regular expression for Response"),
502 AP_INIT_TAKE1("InputSed", sed_add_expr,
503 (void *) APR_OFFSETOF(sed_config, input),
505 "Sed regular expression for Request"),
509 static void register_hooks(apr_pool_t *p)
511 ap_register_output_filter(sed_filter_name, sed_response_filter, NULL,
513 ap_register_input_filter(sed_filter_name, sed_request_filter, NULL,
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 */