| /* |
| * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved. |
| * Use is subject to license terms. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * http://www.apache.org/licenses/LICENSE-2.0. |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_log.h" |
| #include "apr_strings.h" |
| #include "apr_general.h" |
| #include "util_filter.h" |
| #include "apr_buckets.h" |
| #include "http_request.h" |
| #include "libsed.h" |
| |
| static const char *sed_filter_name = "Sed"; |
| #define MODSED_OUTBUF_SIZE 8000 |
| #define MAX_TRANSIENT_BUCKETS 50 |
| |
| typedef struct sed_expr_config |
| { |
| sed_commands_t *sed_cmds; |
| const char *last_error; |
| } sed_expr_config; |
| |
| typedef struct sed_config |
| { |
| sed_expr_config output; |
| sed_expr_config input; |
| } sed_config; |
| |
| /* Context for filter invocation for single HTTP request */ |
| typedef struct sed_filter_ctxt |
| { |
| sed_eval_t eval; |
| ap_filter_t *f; |
| request_rec *r; |
| apr_bucket_brigade *bb; |
| apr_bucket_brigade *bbinp; |
| char *outbuf; |
| char *curoutbuf; |
| int bufsize; |
| apr_pool_t *tpool; |
| int numbuckets; |
| } sed_filter_ctxt; |
| |
| module AP_MODULE_DECLARE_DATA sed_module; |
| |
| /* This function will be call back from libsed functions if there is any error |
| * happend during execution of sed scripts |
| */ |
| static apr_status_t log_sed_errf(void *data, const char *error) |
| { |
| request_rec *r = (request_rec *) data; |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02998) "%s", error); |
| return APR_SUCCESS; |
| } |
| |
| /* This function will be call back from libsed functions if there is any |
| * compilation error. |
| */ |
| static apr_status_t sed_compile_errf(void *data, const char *error) |
| { |
| sed_expr_config *sed_cfg = (sed_expr_config *) data; |
| sed_cfg->last_error = error; |
| return APR_SUCCESS; |
| } |
| |
| /* clear the temporary pool (used for transient buckets) |
| */ |
| static void clear_ctxpool(sed_filter_ctxt* ctx) |
| { |
| apr_pool_clear(ctx->tpool); |
| ctx->outbuf = NULL; |
| ctx->curoutbuf = NULL; |
| ctx->numbuckets = 0; |
| } |
| |
| /* alloc_outbuf |
| * allocate output buffer |
| */ |
| static void alloc_outbuf(sed_filter_ctxt* ctx) |
| { |
| ctx->outbuf = apr_palloc(ctx->tpool, ctx->bufsize + 1); |
| ctx->curoutbuf = ctx->outbuf; |
| } |
| |
| /* append_bucket |
| * Allocate a new bucket from buf and sz and append to ctx->bb |
| */ |
| static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz) |
| { |
| apr_status_t status = APR_SUCCESS; |
| apr_bucket *b; |
| if (ctx->tpool == ctx->r->pool) { |
| /* We are not using transient bucket */ |
| b = apr_bucket_pool_create(buf, sz, ctx->r->pool, |
| ctx->r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| } |
| else { |
| /* We are using transient bucket */ |
| b = apr_bucket_transient_create(buf, sz, |
| ctx->r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| ctx->numbuckets++; |
| if (ctx->numbuckets >= MAX_TRANSIENT_BUCKETS) { |
| b = apr_bucket_flush_create(ctx->r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| status = ap_pass_brigade(ctx->f->next, ctx->bb); |
| apr_brigade_cleanup(ctx->bb); |
| clear_ctxpool(ctx); |
| } |
| } |
| return status; |
| } |
| |
| /* |
| * flush_output_buffer |
| * Flush the output data (stored in ctx->outbuf) |
| */ |
| static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx) |
| { |
| int size = ctx->curoutbuf - ctx->outbuf; |
| char *out; |
| apr_status_t status = APR_SUCCESS; |
| if ((ctx->outbuf == NULL) || (size <=0)) |
| return status; |
| out = apr_pmemdup(ctx->tpool, ctx->outbuf, size); |
| status = append_bucket(ctx, out, size); |
| ctx->curoutbuf = ctx->outbuf; |
| return status; |
| } |
| |
| /* This is a call back function. When libsed wants to generate the output, |
| * this function will be invoked. |
| */ |
| static apr_status_t sed_write_output(void *dummy, char *buf, int sz) |
| { |
| /* dummy is basically filter context. Context is passed during invocation |
| * of sed_eval_buffer |
| */ |
| int remainbytes = 0; |
| apr_status_t status = APR_SUCCESS; |
| sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy; |
| if (ctx->outbuf == NULL) { |
| alloc_outbuf(ctx); |
| } |
| remainbytes = ctx->bufsize - (ctx->curoutbuf - ctx->outbuf); |
| if (sz >= remainbytes) { |
| if (remainbytes > 0) { |
| memcpy(ctx->curoutbuf, buf, remainbytes); |
| buf += remainbytes; |
| sz -= remainbytes; |
| ctx->curoutbuf += remainbytes; |
| } |
| /* buffer is now full */ |
| status = append_bucket(ctx, ctx->outbuf, ctx->bufsize); |
| /* old buffer is now used so allocate new buffer */ |
| alloc_outbuf(ctx); |
| /* if size is bigger than the allocated buffer directly add to output |
| * brigade */ |
| if ((status == APR_SUCCESS) && (sz >= ctx->bufsize)) { |
| char* newbuf = apr_pmemdup(ctx->tpool, buf, sz); |
| status = append_bucket(ctx, newbuf, sz); |
| /* pool might get clear after append_bucket */ |
| if (ctx->outbuf == NULL) { |
| alloc_outbuf(ctx); |
| } |
| } |
| else { |
| memcpy(ctx->curoutbuf, buf, sz); |
| ctx->curoutbuf += sz; |
| } |
| } |
| else { |
| memcpy(ctx->curoutbuf, buf, sz); |
| ctx->curoutbuf += sz; |
| } |
| return status; |
| } |
| |
| /* Compile a sed expression. Compiled context is saved in sed_cfg->sed_cmds. |
| * Memory required for compilation context is allocated from cmd->pool. |
| */ |
| static apr_status_t compile_sed_expr(sed_expr_config *sed_cfg, |
| cmd_parms *cmd, |
| const char *expr) |
| { |
| apr_status_t status = APR_SUCCESS; |
| |
| if (!sed_cfg->sed_cmds) { |
| sed_commands_t *sed_cmds; |
| sed_cmds = apr_pcalloc(cmd->pool, sizeof(sed_commands_t)); |
| status = sed_init_commands(sed_cmds, sed_compile_errf, sed_cfg, |
| cmd->pool); |
| if (status != APR_SUCCESS) { |
| sed_destroy_commands(sed_cmds); |
| return status; |
| } |
| sed_cfg->sed_cmds = sed_cmds; |
| } |
| status = sed_compile_string(sed_cfg->sed_cmds, expr); |
| if (status != APR_SUCCESS) { |
| sed_destroy_commands(sed_cfg->sed_cmds); |
| sed_cfg->sed_cmds = NULL; |
| } |
| return status; |
| } |
| |
| /* sed eval cleanup function */ |
| static apr_status_t sed_eval_cleanup(void *data) |
| { |
| sed_eval_t *eval = (sed_eval_t *) data; |
| sed_destroy_eval(eval); |
| return APR_SUCCESS; |
| } |
| |
| /* Initialize sed filter context. If successful then context is set in f->ctx |
| */ |
| static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int usetpool) |
| { |
| apr_status_t status; |
| sed_filter_ctxt* ctx; |
| request_rec *r = f->r; |
| /* Create the context. Call sed_init_eval. libsed will generated |
| * output by calling sed_write_output and generates any error by |
| * invoking log_sed_errf. |
| */ |
| ctx = apr_pcalloc(r->pool, sizeof(sed_filter_ctxt)); |
| ctx->r = r; |
| ctx->bb = NULL; |
| ctx->numbuckets = 0; |
| ctx->f = f; |
| status = sed_init_eval(&ctx->eval, sed_cfg->sed_cmds, log_sed_errf, |
| r, &sed_write_output, r->pool); |
| if (status != APR_SUCCESS) { |
| return status; |
| } |
| apr_pool_cleanup_register(r->pool, &ctx->eval, sed_eval_cleanup, |
| apr_pool_cleanup_null); |
| ctx->bufsize = MODSED_OUTBUF_SIZE; |
| if (usetpool) { |
| apr_pool_create(&(ctx->tpool), r->pool); |
| } |
| else { |
| ctx->tpool = r->pool; |
| } |
| alloc_outbuf(ctx); |
| f->ctx = ctx; |
| return APR_SUCCESS; |
| } |
| |
| /* Entry function for Sed output filter */ |
| static apr_status_t sed_response_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| apr_bucket *b; |
| apr_status_t status = APR_SUCCESS; |
| sed_config *cfg = ap_get_module_config(f->r->per_dir_config, |
| &sed_module); |
| sed_filter_ctxt *ctx = f->ctx; |
| sed_expr_config *sed_cfg = &cfg->output; |
| |
| if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) { |
| /* No sed expressions */ |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| if (ctx == NULL) { |
| |
| if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) { |
| /* no need to run sed filter for Head requests */ |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| status = init_context(f, sed_cfg, 1); |
| if (status != APR_SUCCESS) |
| return status; |
| ctx = f->ctx; |
| apr_table_unset(f->r->headers_out, "Content-Length"); |
| |
| ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); |
| } |
| |
| /* Here is the main logic. Iterate through all the buckets, read the |
| * content of the bucket, call sed_eval_buffer on the data. |
| * sed_eval_buffer will read the data line by line, run filters on each |
| * line. sed_eval_buffer will generates the output by calling |
| * sed_write_output which will add the output to ctx->bb. At the end of |
| * the loop, ctx->bb is passed to the next filter in chain. At the end of |
| * the data, if new line is not found then sed_eval_buffer will store the |
| * data in its own buffer. |
| * |
| * Once eos bucket is found then sed_finalize_eval will flush the rest of |
| * the data. If there is no new line in last line of data, new line is |
| * appended (that is a solaris sed behavior). libsed's internal memory for |
| * evaluation is allocated on request's pool so it will be cleared once |
| * request is over. |
| * |
| * If flush bucket is found then append the flush bucket to ctx->bb |
| * and pass it to next filter. There may be some data which will still be |
| * in sed's internal buffer which can't be flushed until new line |
| * character is arrived. |
| */ |
| while (!APR_BRIGADE_EMPTY(bb)) { |
| b = APR_BRIGADE_FIRST(bb); |
| if (APR_BUCKET_IS_EOS(b)) { |
| /* Now clean up the internal sed buffer */ |
| sed_finalize_eval(&ctx->eval, ctx); |
| status = flush_output_buffer(ctx); |
| if (status != APR_SUCCESS) { |
| break; |
| } |
| /* Move the eos bucket to ctx->bb brigade */ |
| APR_BUCKET_REMOVE(b); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| } |
| else if (APR_BUCKET_IS_FLUSH(b)) { |
| status = flush_output_buffer(ctx); |
| if (status != APR_SUCCESS) { |
| break; |
| } |
| /* Move the flush bucket to ctx->bb brigade */ |
| APR_BUCKET_REMOVE(b); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| } |
| else { |
| if (!APR_BUCKET_IS_METADATA(b)) { |
| const char *buf = NULL; |
| apr_size_t bytes = 0; |
| |
| status = apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ); |
| if (status == APR_SUCCESS) { |
| status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx); |
| } |
| if (status != APR_SUCCESS) { |
| break; |
| } |
| } |
| apr_bucket_delete(b); |
| } |
| } |
| if (status == APR_SUCCESS) { |
| status = flush_output_buffer(ctx); |
| } |
| if (!APR_BRIGADE_EMPTY(ctx->bb)) { |
| if (status == APR_SUCCESS) { |
| status = ap_pass_brigade(f->next, ctx->bb); |
| } |
| apr_brigade_cleanup(ctx->bb); |
| } |
| clear_ctxpool(ctx); |
| return status; |
| } |
| |
| /* Entry function for Sed input filter */ |
| static apr_status_t sed_request_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb, |
| ap_input_mode_t mode, |
| apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| sed_config *cfg = ap_get_module_config(f->r->per_dir_config, |
| &sed_module); |
| sed_filter_ctxt *ctx = f->ctx; |
| apr_status_t status; |
| apr_bucket_brigade *bbinp; |
| sed_expr_config *sed_cfg = &cfg->input; |
| |
| if (mode != AP_MODE_READBYTES) { |
| return ap_get_brigade(f->next, bb, mode, block, readbytes); |
| } |
| |
| if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) { |
| /* No sed expression */ |
| return ap_get_brigade(f->next, bb, mode, block, readbytes); |
| } |
| |
| if (!ctx) { |
| if (!ap_is_initial_req(f->r)) { |
| ap_remove_input_filter(f); |
| /* XXX : Should we filter the sub requests too */ |
| return ap_get_brigade(f->next, bb, mode, block, readbytes); |
| } |
| status = init_context(f, sed_cfg, 0); |
| if (status != APR_SUCCESS) |
| return status; |
| ctx = f->ctx; |
| ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); |
| ctx->bbinp = apr_brigade_create(f->r->pool, f->c->bucket_alloc); |
| } |
| |
| bbinp = ctx->bbinp; |
| |
| /* Here is the logic : |
| * Read the readbytes data from next level fiter into bbinp. Loop through |
| * the buckets in bbinp and read the data from buckets and invoke |
| * sed_eval_buffer on the data. libsed will generate its output using |
| * sed_write_output which will add data in ctx->bb. Do it until it have |
| * atleast one bucket in ctx->bb. At the end of data eos bucket |
| * should be there. |
| * |
| * Once eos bucket is seen, then invoke sed_finalize_eval to clear the |
| * output. If the last byte of data is not a new line character then sed |
| * will add a new line to the data that is default sed behaviour. Note |
| * that using this filter with POST data, caller may not expect this |
| * behaviour. |
| * |
| * If next level fiter generate the flush bucket, we can't do much about |
| * it. If we want to return the flush bucket in brigade bb (to the caller) |
| * the question is where to add it? |
| */ |
| while (APR_BRIGADE_EMPTY(ctx->bb)) { |
| apr_bucket *b; |
| |
| /* read the bytes from next level filter */ |
| apr_brigade_cleanup(bbinp); |
| status = ap_get_brigade(f->next, bbinp, mode, block, readbytes); |
| if (status != APR_SUCCESS) { |
| return status; |
| } |
| for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp); |
| b = APR_BUCKET_NEXT(b)) { |
| const char *buf = NULL; |
| apr_size_t bytes; |
| |
| if (APR_BUCKET_IS_EOS(b)) { |
| /* eos bucket. Clear the internal sed buffers */ |
| sed_finalize_eval(&ctx->eval, ctx); |
| flush_output_buffer(ctx); |
| APR_BUCKET_REMOVE(b); |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, b); |
| break; |
| } |
| else if (APR_BUCKET_IS_FLUSH(b)) { |
| /* What should we do with flush bucket */ |
| continue; |
| } |
| if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ) |
| == APR_SUCCESS) { |
| status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx); |
| if (status != APR_SUCCESS) |
| return status; |
| flush_output_buffer(ctx); |
| } |
| } |
| } |
| |
| if (!APR_BRIGADE_EMPTY(ctx->bb)) { |
| apr_bucket *b = NULL; |
| |
| if (apr_brigade_partition(ctx->bb, readbytes, &b) == APR_INCOMPLETE) { |
| APR_BRIGADE_CONCAT(bb, ctx->bb); |
| } |
| else { |
| APR_BRIGADE_CONCAT(bb, ctx->bb); |
| apr_brigade_split_ex(bb, b, ctx->bb); |
| } |
| } |
| return APR_SUCCESS; |
| } |
| |
| static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg) |
| { |
| int offset = (int) (long) cmd->info; |
| sed_expr_config *sed_cfg = |
| (sed_expr_config *) (((char *) cfg) + offset); |
| if (compile_sed_expr(sed_cfg, cmd, arg) != APR_SUCCESS) { |
| return apr_psprintf(cmd->temp_pool, |
| "Failed to compile sed expression. %s", |
| sed_cfg->last_error); |
| } |
| return NULL; |
| } |
| |
| static void *create_sed_dir_config(apr_pool_t *p, char *s) |
| { |
| sed_config *cfg = apr_pcalloc(p, sizeof(sed_config)); |
| return cfg; |
| } |
| |
| static const command_rec sed_filter_cmds[] = { |
| AP_INIT_TAKE1("OutputSed", sed_add_expr, |
| (void *) APR_OFFSETOF(sed_config, output), |
| ACCESS_CONF, |
| "Sed regular expression for Response"), |
| AP_INIT_TAKE1("InputSed", sed_add_expr, |
| (void *) APR_OFFSETOF(sed_config, input), |
| ACCESS_CONF, |
| "Sed regular expression for Request"), |
| {NULL} |
| }; |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| ap_register_output_filter(sed_filter_name, sed_response_filter, NULL, |
| AP_FTYPE_RESOURCE); |
| ap_register_input_filter(sed_filter_name, sed_request_filter, NULL, |
| AP_FTYPE_RESOURCE); |
| } |
| |
| AP_DECLARE_MODULE(sed) = { |
| STANDARD20_MODULE_STUFF, |
| create_sed_dir_config, /* dir config creater */ |
| NULL, /* dir merger --- default is to override */ |
| NULL, /* server config */ |
| NULL, /* merge server config */ |
| sed_filter_cmds, /* command table */ |
| register_hooks /* register hooks */ |
| }; |