diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/filters/mod_sed.c | |
parent | Initial commit. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | modules/filters/mod_sed.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c new file mode 100644 index 0000000..12cb04a --- /dev/null +++ b/modules/filters/mod_sed.c @@ -0,0 +1,537 @@ +/* + * 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; + apr_size_t 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 + * happened 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, apr_size_t 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) +{ + apr_size_t 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, apr_size_t sz) +{ + /* dummy is basically filter context. Context is passed during invocation + * of sed_eval_buffer + */ + apr_size_t 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); + if (status == APR_SUCCESS) { + /* if size is bigger than the allocated buffer directly add to output + * brigade */ + if (sz >= ctx->bufsize) { + char* newbuf = apr_pmemdup(ctx->tpool, buf, sz); + status = append_bucket(ctx, newbuf, sz); + if (status == APR_SUCCESS) { + /* old buffer is now used so allocate new buffer */ + alloc_outbuf(ctx); + } + else { + clear_ctxpool(ctx); + } + } + else { + /* old buffer is now used so allocate new buffer */ + alloc_outbuf(ctx); + memcpy(ctx->curoutbuf, buf, sz); + ctx->curoutbuf += sz; + } + } + else { + clear_ctxpool(ctx); + } + } + 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); + apr_pool_tag(ctx->tpool, "sed_tpool"); + } + 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) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10394) "error evaluating sed on output"); + 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 + * at least 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) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10395) "error evaluating sed on input"); + 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 */ +}; |