summaryrefslogtreecommitdiffstats
path: root/modules/filters/mod_sed.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/filters/mod_sed.c')
-rw-r--r--modules/filters/mod_sed.c537
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 */
+};