summaryrefslogtreecommitdiffstats
path: root/modules/filters/mod_substitute.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/filters/mod_substitute.c')
-rw-r--r--modules/filters/mod_substitute.c766
1 files changed, 766 insertions, 0 deletions
diff --git a/modules/filters/mod_substitute.c b/modules/filters/mod_substitute.c
new file mode 100644
index 0000000..d454bf3
--- /dev/null
+++ b/modules/filters/mod_substitute.c
@@ -0,0 +1,766 @@
+/* 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_substitute.c: Perform content rewriting on the fly
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "apr_general.h"
+#include "apr_strings.h"
+#include "apr_strmatch.h"
+#include "apr_lib.h"
+#include "util_filter.h"
+#include "util_varbuf.h"
+#include "apr_buckets.h"
+#include "http_request.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+/*
+ * We want to limit the memory usage in a way that is predictable.
+ * Therefore we limit the resulting length of the line.
+ * This is the default value.
+ */
+#define AP_SUBST_MAX_LINE_LENGTH (1024*1024)
+
+static const char substitute_filter_name[] = "SUBSTITUTE";
+
+module AP_MODULE_DECLARE_DATA substitute_module;
+
+typedef struct subst_pattern_t {
+ const apr_strmatch_pattern *pattern;
+ const ap_regex_t *regexp;
+ const char *replacement;
+ apr_size_t replen;
+ apr_size_t patlen;
+ int flatten;
+ const char *from;
+} subst_pattern_t;
+
+typedef struct {
+ apr_array_header_t *patterns;
+ apr_size_t max_line_length;
+ int max_line_length_set;
+ int inherit_before;
+} subst_dir_conf;
+
+typedef struct {
+ apr_bucket_brigade *linebb;
+ apr_bucket_brigade *linesbb;
+ apr_bucket_brigade *passbb;
+ apr_bucket_brigade *pattbb;
+ apr_pool_t *tpool;
+} substitute_module_ctx;
+
+static void *create_substitute_dcfg(apr_pool_t *p, char *d)
+{
+ subst_dir_conf *dcfg =
+ (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
+
+ dcfg->patterns = apr_array_make(p, 10, sizeof(subst_pattern_t));
+ dcfg->max_line_length = AP_SUBST_MAX_LINE_LENGTH;
+ dcfg->max_line_length_set = 0;
+ dcfg->inherit_before = -1;
+ return dcfg;
+}
+
+static void *merge_substitute_dcfg(apr_pool_t *p, void *basev, void *overv)
+{
+ subst_dir_conf *a =
+ (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
+ subst_dir_conf *base = (subst_dir_conf *) basev;
+ subst_dir_conf *over = (subst_dir_conf *) overv;
+
+ a->inherit_before = (over->inherit_before != -1)
+ ? over->inherit_before
+ : base->inherit_before;
+ /* SubstituteInheritBefore wasn't the default behavior until 2.5.x,
+ * and may be re-disabled as desired; the original default behavior
+ * was to apply inherited subst patterns after locally scoped patterns.
+ * In later 2.2 and 2.4 versions, SubstituteInheritBefore may be toggled
+ * 'on' to follow the corrected/expected behavior, without violating POLS.
+ */
+ if (a->inherit_before == 1) {
+ a->patterns = apr_array_append(p, base->patterns,
+ over->patterns);
+ }
+ else {
+ a->patterns = apr_array_append(p, over->patterns,
+ base->patterns);
+ }
+ a->max_line_length = over->max_line_length_set ?
+ over->max_line_length : base->max_line_length;
+ a->max_line_length_set = over->max_line_length_set
+ | base->max_line_length_set;
+ return a;
+}
+
+#define AP_MAX_BUCKETS 1000
+
+#define SEDRMPATBCKT(b, offset, tmp_b, patlen) do { \
+ apr_bucket_split(b, offset); \
+ tmp_b = APR_BUCKET_NEXT(b); \
+ apr_bucket_split(tmp_b, patlen); \
+ b = APR_BUCKET_NEXT(tmp_b); \
+ apr_bucket_delete(tmp_b); \
+} while (0)
+
+#define CAP2LINEMAX(n) ((n) < (apr_size_t)200 ? (int)(n) : 200)
+
+static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
+ apr_bucket_brigade *mybb,
+ apr_pool_t *pool)
+{
+ int i;
+ int force_quick = 0;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ apr_size_t bytes;
+ apr_size_t len;
+ const char *buff;
+ struct ap_varbuf vb;
+ apr_bucket *b;
+ apr_bucket *tmp_b;
+
+ subst_dir_conf *cfg =
+ (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
+ &substitute_module);
+ subst_pattern_t *script;
+
+ APR_BRIGADE_INSERT_TAIL(mybb, inb);
+ ap_varbuf_init(pool, &vb, 0);
+
+ script = (subst_pattern_t *) cfg->patterns->elts;
+ /*
+ * Simple optimization. If we only have one pattern, then
+ * we can safely avoid the overhead of flattening
+ */
+ if (cfg->patterns->nelts == 1) {
+ force_quick = 1;
+ }
+ for (i = 0; i < cfg->patterns->nelts; i++) {
+ for (b = APR_BRIGADE_FIRST(mybb);
+ b != APR_BRIGADE_SENTINEL(mybb);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /*
+ * we should NEVER see this, because we should never
+ * be passed any, but "handle" it just in case.
+ */
+ continue;
+ }
+ if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
+ == APR_SUCCESS) {
+ int have_match = 0;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Line read (%" APR_SIZE_T_FMT " bytes): %.*s",
+ bytes, CAP2LINEMAX(bytes), buff);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Replacing %s:'%s' by '%s'",
+ script->pattern ? "string" :
+ script->regexp ? "regex" :
+ "unknown",
+ script->from, script->replacement);
+
+ vb.strlen = 0;
+ if (script->pattern) {
+ const char *repl;
+ /*
+ * space_left counts how many bytes we have left until the
+ * line length reaches max_line_length.
+ */
+ apr_size_t space_left = cfg->max_line_length;
+ apr_size_t repl_len = strlen(script->replacement);
+ while ((repl = apr_strmatch(script->pattern, buff, bytes)))
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Matching found, result: '%s'",
+ script->replacement);
+ have_match = 1;
+ /* get offset into buff for pattern */
+ len = (apr_size_t) (repl - buff);
+ if (script->flatten && !force_quick) {
+ /*
+ * 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 (vb.strlen + len + repl_len > cfg->max_line_length)
+ return APR_ENOMEM;
+ ap_varbuf_strmemcat(&vb, buff, len);
+ ap_varbuf_strmemcat(&vb, script->replacement, repl_len);
+ }
+ else {
+ /*
+ * The string before the match but after the
+ * previous match (if any) has length 'len'.
+ * Check if we still have space for this string and
+ * the replacement string.
+ */
+ if (space_left < len + repl_len)
+ return APR_ENOMEM;
+ space_left -= len + repl_len;
+ /*
+ * We now split off the string before the match
+ * as its own bucket, then isolate the matched
+ * string and delete it.
+ */
+ SEDRMPATBCKT(b, len, tmp_b, script->patlen);
+ /*
+ * Finally, we create a bucket that contains the
+ * replacement...
+ */
+ tmp_b = apr_bucket_transient_create(script->replacement,
+ script->replen,
+ f->r->connection->bucket_alloc);
+ /* ... and insert it */
+ 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 (have_match) {
+ if (script->flatten && !force_quick) {
+ /* XXX: we should check for AP_MAX_BUCKETS here and
+ * XXX: call ap_pass_brigade accordingly
+ */
+ char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
+ buff, bytes, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ len, CAP2LINEMAX(len), copy);
+ tmp_b = apr_bucket_pool_create(copy, len, pool,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ apr_bucket_delete(b);
+ b = tmp_b;
+ }
+ else {
+ /*
+ * We want the behaviour to be predictable.
+ * Therefore we try to always error out if the
+ * line length is larger than the limit,
+ * regardless of the content of the line. So,
+ * let's check if the remaining non-matching
+ * string does not exceed the limit.
+ */
+ if (space_left < b->length)
+ return APR_ENOMEM;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ bytes, CAP2LINEMAX(bytes), buff);
+ }
+ }
+ }
+ else if (script->regexp) {
+ int left = bytes;
+ const char *pos = buff;
+ char *repl;
+ apr_size_t space_left = cfg->max_line_length;
+ while (!ap_regexec_len(script->regexp, pos, left,
+ AP_MAX_REG_MATCH, regm, 0)) {
+ apr_status_t rv;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Matching found");
+ have_match = 1;
+ if (script->flatten && !force_quick) {
+ /* check remaining buffer size */
+ /* Note that the last param in ap_varbuf_regsub below
+ * must stay positive. If it gets 0, it would mean
+ * unlimited space available. */
+ if (vb.strlen + regm[0].rm_so >= cfg->max_line_length)
+ return APR_ENOMEM;
+ /* copy bytes before the match */
+ if (regm[0].rm_so > 0)
+ ap_varbuf_strmemcat(&vb, pos, regm[0].rm_so);
+ /* add replacement string, last argument is unsigned! */
+ rv = ap_varbuf_regsub(&vb, script->replacement, pos,
+ AP_MAX_REG_MATCH, regm,
+ cfg->max_line_length - vb.strlen);
+ if (rv != APR_SUCCESS)
+ return rv;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Result: '%s'", vb.buf);
+ }
+ else {
+ apr_size_t repl_len;
+ /* account for string before the match */
+ if (space_left <= regm[0].rm_so)
+ return APR_ENOMEM;
+ space_left -= regm[0].rm_so;
+ rv = ap_pregsub_ex(pool, &repl,
+ script->replacement, pos,
+ AP_MAX_REG_MATCH, regm,
+ space_left);
+ if (rv != APR_SUCCESS)
+ return rv;
+ repl_len = strlen(repl);
+ space_left -= repl_len;
+ len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
+ SEDRMPATBCKT(b, regm[0].rm_so, tmp_b, len);
+ tmp_b = apr_bucket_transient_create(repl, repl_len,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Result: '%s'", repl);
+ }
+ /*
+ * reset to past what we just did. pos now maps to b
+ * again
+ */
+ pos += regm[0].rm_eo;
+ left -= regm[0].rm_eo;
+ }
+ if (have_match && script->flatten && !force_quick) {
+ char *copy;
+ /* Copy result plus the part after the last match into
+ * a bucket.
+ */
+ copy = ap_varbuf_pdup(pool, &vb, NULL, 0, pos, left,
+ &len);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ len, CAP2LINEMAX(len), copy);
+ tmp_b = apr_bucket_pool_create(copy, len, pool,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ apr_bucket_delete(b);
+ b = tmp_b;
+ }
+ }
+ else {
+ ap_assert(0);
+ continue;
+ }
+ }
+ }
+ script++;
+ }
+ ap_varbuf_free(&vb);
+ return APR_SUCCESS;
+}
+
+static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_size_t bytes;
+ apr_size_t len;
+ apr_size_t fbytes;
+ const char *buff;
+ const char *nl = NULL;
+ char *bflat;
+ apr_bucket *b;
+ apr_bucket *tmp_b;
+ apr_bucket_brigade *tmp_bb = NULL;
+ apr_status_t rv;
+ subst_dir_conf *cfg =
+ (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
+ &substitute_module);
+
+ substitute_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));
+ /*
+ * Create all the temporary brigades we need and reuse them to avoid
+ * creating them over and over again from r->pool which would cost a
+ * lot of memory in some cases.
+ */
+ ctx->linebb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->linesbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->pattbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ /*
+ * Everything to be passed to the next filter goes in
+ * here, our pass brigade.
+ */
+ ctx->passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ /* Create our temporary pool only once */
+ apr_pool_create(&(ctx->tpool), f->r->pool);
+ apr_pool_tag(ctx->tpool, "substitute_tpool");
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ }
+
+ /*
+ * Shortcircuit processing
+ */
+ if (APR_BRIGADE_EMPTY(bb))
+ return APR_SUCCESS;
+
+ /*
+ * 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 ctx->linebb 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 ctx->linebb
+ * to the front of it. This makes the code much less straight-
+ * forward (otherwise we could APR_BRIGADE_CONCAT(ctx->linebb, bb)
+ * and just scan for newlines and not bother with needing to know
+ * when ctx->linebb 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))) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ /*
+ * if we see the EOS, then we need to pass along everything we
+ * have. But if the ctx->linebb isn't empty, then we need to add
+ * that to the end of what we'll be passing.
+ */
+ if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
+ rv = apr_brigade_pflatten(ctx->linebb, &bflat,
+ &fbytes, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ if (fbytes > cfg->max_line_length) {
+ rv = APR_ENOMEM;
+ goto err;
+ }
+ tmp_b = apr_bucket_transient_create(bflat, fbytes,
+ f->r->connection->bucket_alloc);
+ rv = do_pattmatch(f, tmp_b, ctx->pattbb, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
+ apr_brigade_cleanup(ctx->linebb);
+ }
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
+ }
+ /*
+ * No need to handle FLUSH buckets separately as we call
+ * ap_pass_brigade anyway at the end of the loop.
+ */
+ else if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->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_delete(b);
+ }
+ else {
+ int num = 0;
+ while (bytes > 0) {
+ nl = memchr(buff, '\n', 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->linebb)) {
+ APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
+ rv = apr_brigade_pflatten(ctx->linebb, &bflat,
+ &fbytes, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ if (fbytes > cfg->max_line_length) {
+ /* Avoid pflattening further lines, we will
+ * abort later on anyway.
+ */
+ rv = APR_ENOMEM;
+ goto err;
+ }
+ b = apr_bucket_transient_create(bflat, fbytes,
+ f->r->connection->bucket_alloc);
+ apr_brigade_cleanup(ctx->linebb);
+ }
+ rv = do_pattmatch(f, b, ctx->pattbb, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ /*
+ * Count how many buckets we have in ctx->passbb
+ * so far. Yes, this is correct we count ctx->passbb
+ * and not ctx->pattbb as we do not reset num on every
+ * iteration.
+ */
+ for (b = APR_BRIGADE_FIRST(ctx->pattbb);
+ b != APR_BRIGADE_SENTINEL(ctx->pattbb);
+ b = APR_BUCKET_NEXT(b)) {
+ num++;
+ }
+ APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
+ /*
+ * If the number of buckets in ctx->passbb reaches an
+ * "insane" level, we consume much memory for all the
+ * buckets as such. So lets flush them down the chain
+ * in this case and thus clear ctx->passbb. This frees
+ * the buckets memory for further processing.
+ * Usually this condition should not become true, but
+ * it is a safety measure for edge cases.
+ */
+ if (num > AP_MAX_BUCKETS) {
+ b = apr_bucket_flush_create(
+ f->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
+ rv = ap_pass_brigade(f->next, ctx->passbb);
+ apr_brigade_cleanup(ctx->passbb);
+ num = 0;
+ apr_pool_clear(ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ }
+ 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->linebb, b);
+ bytes = 0;
+ }
+ }
+ }
+ }
+ if (!APR_BRIGADE_EMPTY(ctx->passbb)) {
+ rv = ap_pass_brigade(f->next, ctx->passbb);
+ apr_brigade_cleanup(ctx->passbb);
+ if (rv != APR_SUCCESS)
+ goto err;
+ }
+ apr_pool_clear(ctx->tpool);
+ }
+
+ /* Anything left we want to save/setaside for the next go-around */
+ if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
+ /*
+ * Provide ap_save_brigade with an existing empty brigade
+ * (ctx->linesbb) to avoid creating a new one.
+ */
+ ap_save_brigade(f, &(ctx->linesbb), &(ctx->linebb), f->r->pool);
+ tmp_bb = ctx->linebb;
+ ctx->linebb = ctx->linesbb;
+ ctx->linesbb = tmp_bb;
+ }
+
+ return APR_SUCCESS;
+err:
+ if (rv == APR_ENOMEM)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01328) "Line too long, URI %s",
+ f->r->uri);
+ apr_pool_clear(ctx->tpool);
+ return rv;
+}
+
+static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
+{
+ char *from = NULL;
+ char *to = NULL;
+ char *flags = NULL;
+ char *ourline;
+ char delim;
+ subst_pattern_t *nscript;
+ int is_pattern = 0;
+ int ignore_case = 0;
+ int flatten = 1;
+ ap_regex_t *r = NULL;
+
+ if (apr_tolower(*line) != 's') {
+ return "Bad Substitute format, must be an s/// pattern";
+ }
+ ourline = apr_pstrdup(cmd->pool, line);
+ delim = *++ourline;
+ if (delim)
+ from = ++ourline;
+ if (from) {
+ if (*ourline != delim) {
+ while (*++ourline && *ourline != delim);
+ }
+ if (*ourline) {
+ *ourline = '\0';
+ to = ++ourline;
+ }
+ }
+ if (to) {
+ if (*ourline != delim) {
+ while (*++ourline && *ourline != delim);
+ }
+ if (*ourline) {
+ *ourline = '\0';
+ flags = ++ourline;
+ }
+ }
+
+ if (!delim || !from || !*from || !to) {
+ return "Bad Substitute format, must be a complete s/// pattern";
+ }
+
+ if (flags) {
+ 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 if (delim == 'q')
+ flatten = 0;
+ else
+ return "Bad Substitute flag, only s///[infq] are supported";
+ flags++;
+ }
+ }
+
+ /* first see if we can compile the regex */
+ if (!is_pattern) {
+ int flags = AP_REG_NO_DEFAULT
+ | (ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY)
+ | (ignore_case ? AP_REG_ICASE : 0);
+ r = ap_pregcomp(cmd->pool, from, flags);
+ if (!r)
+ return "Substitute could not compile regex";
+ }
+ nscript = apr_array_push(((subst_dir_conf *) cfg)->patterns);
+ /* init the new entries */
+ nscript->pattern = NULL;
+ nscript->regexp = NULL;
+ nscript->replacement = NULL;
+ nscript->patlen = 0;
+ nscript->from = from;
+
+ 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 KBYTE 1024
+#define MBYTE 1048576
+#define GBYTE 1073741824
+
+static const char *set_max_line_length(cmd_parms *cmd, void *cfg, const char *arg)
+{
+ subst_dir_conf *dcfg = (subst_dir_conf *)cfg;
+ apr_off_t max;
+ char *end;
+ apr_status_t rv;
+
+ rv = apr_strtoff(&max, arg, &end, 10);
+ if (rv == APR_SUCCESS) {
+ if ((*end == 'K' || *end == 'k') && !end[1]) {
+ max *= KBYTE;
+ }
+ else if ((*end == 'M' || *end == 'm') && !end[1]) {
+ max *= MBYTE;
+ }
+ else if ((*end == 'G' || *end == 'g') && !end[1]) {
+ max *= GBYTE;
+ }
+ else if (*end && /* neither empty nor [Bb] */
+ ((*end != 'B' && *end != 'b') || end[1])) {
+ rv = APR_EGENERAL;
+ }
+ }
+
+ if (rv != APR_SUCCESS || max < 0)
+ {
+ return "SubstituteMaxLineLength must be a non-negative integer optionally "
+ "suffixed with 'b', 'k', 'm' or 'g'.";
+ }
+ dcfg->max_line_length = (apr_size_t)max;
+ dcfg->max_line_length_set = 1;
+ 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(substitute_filter_name, substitute_filter,
+ NULL, AP_FTYPE_RESOURCE);
+}
+
+static const command_rec substitute_cmds[] = {
+ AP_INIT_TAKE1("Substitute", set_pattern, NULL, OR_FILEINFO,
+ "Pattern to filter the response content (s/foo/bar/[inf])"),
+ AP_INIT_TAKE1("SubstituteMaxLineLength", set_max_line_length, NULL, OR_FILEINFO,
+ "Maximum line length"),
+ AP_INIT_FLAG("SubstituteInheritBefore", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(subst_dir_conf, inherit_before), OR_FILEINFO,
+ "Apply inherited patterns before those of the current context"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(substitute) = {
+ STANDARD20_MODULE_STUFF,
+ create_substitute_dcfg, /* dir config creater */
+ merge_substitute_dcfg, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ substitute_cmds, /* command table */
+ register_hooks /* register hooks */
+};