httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject svn commit: r518807 - /httpd/httpd/trunk/modules/experimental/mod_sedfilter.c
Date Thu, 15 Mar 2007 23:29:24 GMT
Author: jim
Date: Thu Mar 15 16:29:23 2007
New Revision: 518807

URL: http://svn.apache.org/viewvc?view=rev&rev=518807
Log:
Initial import: mod_sedfilter.c (was mod_sed_filter.c) as
per discussion on dev@httpd.


Added:
    httpd/httpd/trunk/modules/experimental/mod_sedfilter.c   (with props)

Added: httpd/httpd/trunk/modules/experimental/mod_sedfilter.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/experimental/mod_sedfilter.c?view=auto&rev=518807
==============================================================================
--- httpd/httpd/trunk/modules/experimental/mod_sedfilter.c (added)
+++ httpd/httpd/trunk/modules/experimental/mod_sedfilter.c Thu Mar 15 16:29:23 2007
@@ -0,0 +1,522 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/*
+ * mod_sedfilter.c: Perform sed-like rewriting on the fly
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "apr_general.h"
+#include "apr_strings.h"
+#include "apr_strmatch.h"
+#include "apr_lib.h"
+#include "util_filter.h"
+#include "apr_buckets.h"
+#include "http_request.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+static const char sedFilterName[] = "SEDFILTER";
+
+module AP_MODULE_DECLARE_DATA sedfilter_module;
+
+typedef struct {
+    const apr_strmatch_pattern *pattern;
+    const ap_regex_t *regexp;
+    const char *replacement;
+    apr_size_t replen;
+    apr_size_t patlen;
+    int flatten;
+} sed_script;
+
+typedef struct {
+    apr_array_header_t *sed_scripts;
+} sed_module_dcfg;
+
+typedef struct {
+    apr_bucket_brigade *ctxbb;
+} sed_module_ctx;
+
+static void *create_sed_dcfg(apr_pool_t *p, char *d)
+{
+    sed_module_dcfg *dcfg =
+    (sed_module_dcfg *) apr_pcalloc(p, sizeof(sed_module_dcfg));
+
+    dcfg->sed_scripts = apr_array_make(p, 10, sizeof(sed_script));
+    return dcfg;
+}
+
+static void *merge_sed_dcfg(apr_pool_t *p, void *basev, void *overv)
+{
+    sed_module_dcfg *a =
+    (sed_module_dcfg *) apr_pcalloc(p, sizeof(sed_module_dcfg));
+    sed_module_dcfg *base = (sed_module_dcfg *) basev;
+    sed_module_dcfg *over = (sed_module_dcfg *) overv;
+
+    a->sed_scripts = apr_array_append(p, over->sed_scripts,
+                                      base->sed_scripts);
+    return a;
+}
+
+static apr_bucket_brigade *do_pattmatch(ap_filter_t *f, apr_bucket *inb)
+{
+    int i;
+    ap_regmatch_t regm[AP_MAX_REG_MATCH];
+    apr_size_t bytes;
+    apr_size_t len;
+    apr_size_t fbytes;
+    const char *buff;
+    const char *repl;
+    char *scratch;
+    char *p;
+    char *s1;
+    char *s2;
+    apr_bucket *b;
+    apr_bucket *tmp_b;
+    apr_bucket_brigade *mybb;
+    apr_pool_t *tpool;
+
+    sed_module_dcfg *cfg =
+    (sed_module_dcfg *) ap_get_module_config(f->r->per_dir_config,
+                                             &sedfilter_module);
+    sed_script *script;
+
+    mybb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(mybb, inb);
+    
+    script = (sed_script *) cfg->sed_scripts->elts;
+    apr_pool_create(&tpool, f->r->pool);
+    scratch = NULL;
+    fbytes = 0;
+    for (i = 0; i < cfg->sed_scripts->nelts; i++) {
+        for (b = APR_BRIGADE_FIRST(mybb);
+             b != APR_BRIGADE_SENTINEL(mybb);
+             b = APR_BUCKET_NEXT(b)) {
+            if (APR_BUCKET_IS_METADATA(b))
+                continue;
+            if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ) == APR_SUCCESS)
{
+                s1 = NULL;
+                if (script->pattern) {
+                    while ((repl = apr_strmatch(script->pattern, buff, bytes))) {
+                        /* get offset into buff for pattern */
+                        len = (apr_size_t) (repl - buff);
+                        if (script->flatten) {
+                            /*
+                             * We are flattening the buckets here, meaning
+                             * that we don't do the fast bucket splits.
+                             * Instead we copy over what the buckets would
+                             * contain and use them. This is slow, since we
+                             * are constanting allocing space and copying
+                             * strings.
+                             */
+                            if (!s1) {
+                                s1 = apr_pstrmemdup(f->r->pool, buff, len);
+                            }
+                            else {
+                                s2 = apr_pstrmemdup(f->r->pool, buff, len);
+                                s1 = apr_pstrcat(f->r->pool, s1, s2, NULL);
+                            }
+                            s1 = apr_pstrcat(f->r->pool, s1, script->replacement,
NULL);
+                        }
+                        else {
+                            /* and split off the stuff before */
+                            apr_bucket_split(b, len);
+                            tmp_b = APR_BUCKET_NEXT(b);
+                            /* now isolate the pattern and delete it */
+                            apr_bucket_split(tmp_b, script->patlen);
+                            b = APR_BUCKET_NEXT(tmp_b);
+                            apr_bucket_delete(tmp_b);
+                            /*
+                             * as above, create a bucket that contains the
+                             * replacement and insert it
+                             */
+                            tmp_b = apr_bucket_pool_create(script->replacement, script->replen,
+                                f->r->pool, f->r->connection->bucket_alloc);
+                            APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+                        }
+                        /* now we need to adjust buff for all these changes */
+                        len += script->patlen;
+                        bytes -= len;
+                        buff += len;
+                    }
+                    if (script->flatten && s1) {
+                        /*
+                         * we've finished looking at the bucket, so remove the
+                         * old one and add in our new one
+                         */
+                        s2 = apr_pstrmemdup(f->r->pool, buff, bytes);
+                        s1 = apr_pstrcat(f->r->pool, s1, s2, NULL);
+                        tmp_b = apr_bucket_pool_create(s1, strlen(s1),
+                                f->r->pool, f->r->connection->bucket_alloc);
+                        APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+                        tmp_b = APR_BUCKET_NEXT(b);
+                        apr_bucket_delete(b);
+                        b = tmp_b;
+                    }
+
+                }
+                else if (script->regexp) {
+                    /*
+                     * we need a null terminated string here :(. To hopefully
+                     * save time and memory, we don't alloc for each run
+                     * through, but only if we need to have a larger chunk
+                     * to save the string to. So we keep track of how much
+                     * we've allocated and only re-alloc when we need it.
+                     * NOTE: this screams for a macro.
+                     */
+                    if (!scratch || (bytes > (fbytes + 1))) {
+                        fbytes = bytes + 1;
+                        scratch = apr_palloc(tpool, fbytes);
+                    }
+                    /* reset pointer to the scratch space */
+                    p = scratch;
+                    memcpy(p, buff, bytes);
+                    p[bytes] = '\0';
+                    while (!ap_regexec(script->regexp, p, AP_MAX_REG_MATCH, regm, 0))
{
+                        /* first, grab the replacement string */
+                        repl = ap_pregsub(f->r->pool, script->replacement, p,
+                                          AP_MAX_REG_MATCH, regm);
+                        if (script->flatten) {
+                            if (!s1)
+                                s1 = apr_pstrmemdup(f->r->pool, p, regm[0].rm_so);
+                            else {
+                                s2 = apr_pstrmemdup(f->r->pool, p, regm[0].rm_so);
+                                s1 = apr_pstrcat(f->r->pool, s1, s2, NULL);
+                            }
+                            s1 = apr_pstrcat(f->r->pool, s1, repl, NULL);
+                        }
+                        else {
+                            /* now split off the stuff before the regex */
+                            apr_bucket_split(b, regm[0].rm_so);
+                            tmp_b = APR_BUCKET_NEXT(b);
+                            /* and after */
+                            apr_bucket_split(tmp_b, regm[0].rm_eo - regm[0].rm_so);
+                            b = APR_BUCKET_NEXT(tmp_b);
+                            /*
+                             * now that the regex string is isolated into one
+                             * bucket, delete it
+                             */
+                            apr_bucket_delete(tmp_b);
+                            /*
+                             * now create a bucket that is just our
+                             * replacement
+                             */
+                            tmp_b = apr_bucket_pool_create(repl, strlen(repl), f->r->pool,
+                                            f->r->connection->bucket_alloc);
+                            APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+                        }
+                        /*
+                         * reset to past what we just did. buff now maps to b
+                         * again
+                         */
+                        p += regm[0].rm_eo;
+                    }
+                    if (script->flatten && s1) {
+                        /*
+                         * we've finished looking at the bucket, so remove the
+                         * old one and add in our new one
+                         */
+                        s1 = apr_pstrcat(f->r->pool, s1, p, NULL);
+                        tmp_b = apr_bucket_pool_create(s1, strlen(s1),
+                                f->r->pool, f->r->connection->bucket_alloc);
+                        APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+                        tmp_b = APR_BUCKET_NEXT(b);
+                        apr_bucket_delete(b);
+                        b = tmp_b;
+                    }
+
+                }
+                else {
+                    /* huh? */
+                    continue;
+                }
+            }
+        }
+        script++;
+    }
+
+    apr_pool_destroy(tpool);
+
+    return mybb;
+}
+
+static apr_status_t sed_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    apr_size_t bytes;
+    apr_size_t len;
+    apr_size_t fbytes;
+    apr_off_t blen;
+    const char *buff;
+    const char *nl = NULL;
+    char *bflat;
+    apr_bucket *b;
+    apr_bucket *tmp_b;
+    apr_bucket_brigade *passbb;
+    apr_bucket_brigade *pattbb;
+    apr_bucket_brigade *tmp_ctxbb = NULL;
+    apr_status_t rv;
+
+    sed_module_ctx *ctx = f->ctx;
+    /*
+     * First time around? Create the saved bb that we used for each pass
+     * through. Note that we can also get here when we explicitly clear ctx,
+     * for error handling
+     */
+    if (!ctx) {
+        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+        ctx->ctxbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+        apr_table_unset(f->r->headers_out, "Content-Length");
+    }
+
+    /*
+     * Everything to be passed to the next filter goes in
+     * here, our pass brigade.
+     */
+    passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+
+    /*
+     * Here's the concept:
+     *  Read in the data and look for newlines. Once we
+     *  find a full "line", add it to our working brigade.
+     *  If we've finished reading the brigade and we have
+     *  any left over data (not a "full" line), store that
+     *  for the next pass.
+     *
+     * Note: anything stored in ctxbb for sure does not have
+     * a newline char, so we don't concat that bb with the
+     * new bb, since we would spending time searching for the newline
+     * in data we know it doesn't exist. So instead, we simply scan
+     * our current bb and, if we see a newline, prepend ctxbb
+     * to the front of it. This makes the code much less straight-
+     * forward (otherwise we could APR_BRIGADE_CONCAT(ctx->ctxbb, bb)
+     * and just scan for newlines and not bother with needing to know
+     * when ctx->ctxbb needs to be reset) but also faster. We'll take
+     * the speed.
+     *
+     * Note: apr_brigade_split_line would be nice here, but we
+     * really can't use it since we need more control and we want
+     * to re-use already read bucket data.
+     *
+     * See mod_include if still confused :)
+     */
+
+    while ((b = APR_BRIGADE_FIRST(bb)) && (b != APR_BRIGADE_SENTINEL(bb))) {
+        apr_brigade_length(passbb, 0, &blen);
+        if ((blen != -1) && (blen > AP_MIN_BYTES_TO_WRITE)) {
+            ap_pass_brigade(f->next, passbb);
+            apr_brigade_cleanup(passbb);
+        }
+        if (APR_BUCKET_IS_EOS(b)) {
+            /*
+             * if we see the EOS, then we need to pass along everything we
+             * have. But if the ctxbb isn't empty, then we need to add that
+             * to the end of what we'll be passing.
+             */
+            if (!APR_BRIGADE_EMPTY(ctx->ctxbb)) {
+                rv = apr_brigade_pflatten(ctx->ctxbb, &bflat, &fbytes, f->r->pool);
+                tmp_b = apr_bucket_pool_create(bflat, fbytes, f->r->pool,
+                                            f->r->connection->bucket_alloc);
+                pattbb = do_pattmatch(f, tmp_b);
+                APR_BRIGADE_CONCAT(passbb, pattbb);
+            }
+            apr_brigade_cleanup(ctx->ctxbb);
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(passbb, b);
+        }
+        else if (APR_BUCKET_IS_METADATA(b)) {
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(passbb, b);
+        }
+        else {
+            /*
+             * We have actual "data" so read in as much as we can and start
+             * scanning and splitting from our read buffer
+             */
+            rv = apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ);
+            if (rv != APR_SUCCESS || bytes == 0) {
+                APR_BUCKET_REMOVE(b);
+            }
+            else {
+                while (bytes > 0) {
+                    nl = memchr(buff, APR_ASCII_LF, bytes);
+                    if (nl) {
+                        len = (apr_size_t) (nl - buff) + 1;
+                        /* split *after* the newline */
+                        apr_bucket_split(b, len);
+                        /*
+                         * We've likely read more data, so bypass rereading
+                         * bucket data and continue scanning through this
+                         * buffer
+                         */
+                        bytes -= len;
+                        buff += len;
+                        /*
+                         * we need b to be updated for future potential
+                         * splitting
+                         */
+                        tmp_b = APR_BUCKET_NEXT(b);
+                        APR_BUCKET_REMOVE(b);
+                        /*
+                         * Hey, we found a newline! Don't forget the old
+                         * stuff that needs to be added to the front. So we
+                         * add the split bucket to the end, flatten the whole bb,
+                         * morph the whole shebang into a bucket which is
+                         * then added to the tail of the newline bb.
+                         */
+                        if (!APR_BRIGADE_EMPTY(ctx->ctxbb)) {
+                            APR_BRIGADE_INSERT_TAIL(ctx->ctxbb, b);
+                            rv = apr_brigade_pflatten(ctx->ctxbb, &bflat, &fbytes,
+                                                      f->r->pool);
+                            b = apr_bucket_pool_create(bflat, fbytes, f->r->pool,
+                                            f->r->connection->bucket_alloc);
+                            apr_brigade_cleanup(ctx->ctxbb);
+                        }
+                        pattbb = do_pattmatch(f, b);
+                        APR_BRIGADE_CONCAT(passbb, pattbb);
+                        b = tmp_b;
+                    }
+                    else {
+                        /*
+                         * no newline in whatever is left of this buffer so
+                         * tuck data away and get next bucket
+                         */
+                        APR_BUCKET_REMOVE(b);
+                        APR_BRIGADE_INSERT_TAIL(ctx->ctxbb, b);
+                        bytes = 0;
+                    }
+                }
+            }
+        }
+    }
+
+    /* Pass it down */
+    rv = ap_pass_brigade(f->next, passbb);
+
+    /* Anything left we want to save/setaside for the next go-around */
+    if (!APR_BRIGADE_EMPTY(ctx->ctxbb)) {
+        ap_save_brigade(f, &tmp_ctxbb, &(ctx->ctxbb), f->r->pool);
+        ctx->ctxbb = tmp_ctxbb;
+    }
+
+    return rv;
+}
+
+static const char *set_sed_script(cmd_parms *cmd, void *cfg,
+                                       const char *line)
+{
+    char *from = NULL;
+    char *to = NULL;
+    char *flags = NULL;
+    char *ourline;
+    char delim;
+    sed_script *nscript;
+    int is_pattern = 0;
+    int ignore_case = 0;
+    int flatten = 0;
+    ap_regex_t *r = NULL;
+
+    if (apr_tolower(*line) != 's') {
+        return "Bad Sed format";
+    }
+    ourline = apr_pstrdup(cmd->pool, line);
+    delim = *++ourline;
+    if (delim)
+        from = ++ourline;
+    if (from) {
+        while (*++ourline && *ourline != delim);
+        if (*ourline) {
+            *ourline = '\0';
+            to = ++ourline;
+        }
+    }
+    if (to) {
+        while (*++ourline && *ourline != delim);
+        if (*ourline) {
+            *ourline = '\0';
+            flags = ++ourline;
+        }
+    }
+
+    if (!delim || !from || !to) {
+        return "Bad Sed format: cannot parse";
+    }
+
+    while (*flags) {
+        delim = apr_tolower(*flags);    /* re-use */
+        if (delim == 'i')
+            ignore_case = 1;
+        else if (delim == 'n')
+            is_pattern = 1;
+        else if (delim == 'f')
+            flatten = 1;
+        else
+            return "Bad Sed format: bad flag";
+        flags++;
+    }
+
+    /* first see if we can compile the regex */
+    if (!is_pattern) {
+        r = ap_pregcomp(cmd->pool, from, AP_REG_EXTENDED |
+                        (ignore_case ? AP_REG_ICASE : 0));
+        if (!r)
+            return "Could not compile Sed regex";
+    }
+    nscript = apr_array_push(((sed_module_dcfg *) cfg)->sed_scripts);
+    /* init the new entries */
+    nscript->pattern = NULL;
+    nscript->regexp = NULL;
+    nscript->replacement = NULL;
+    nscript->patlen = 0;
+
+    if (is_pattern) {
+        nscript->patlen = strlen(from);
+        nscript->pattern = apr_strmatch_precompile(cmd->pool, from, !ignore_case);
+    }
+    else {
+        nscript->regexp = r;
+    }
+
+    nscript->replacement = to;
+    nscript->replen = strlen(to);
+    nscript->flatten = flatten;
+
+    return NULL;
+}
+
+#define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
+static void register_hooks(apr_pool_t *pool)
+{
+    ap_register_output_filter(sedFilterName, sed_filter, NULL,
+                              AP_FTYPE_RESOURCE);
+}
+
+static const command_rec sed_filter_cmds[] = {
+    AP_INIT_TAKE1("Sed", set_sed_script, NULL, OR_ALL,
+                  "Define the sed script (s/foo/bar/)"),
+    {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA sedfilter_module = {
+    STANDARD20_MODULE_STUFF,
+    create_sed_dcfg,            /* dir config creater */
+    merge_sed_dcfg,             /* dir merger --- default is to override */
+    NULL,                       /* server config */
+    NULL,                       /* merge server config */
+    sed_filter_cmds,            /* command table */
+    register_hooks              /* register hooks */
+};

Propchange: httpd/httpd/trunk/modules/experimental/mod_sedfilter.c
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message