| /* 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; |
| /* acount 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_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) { |
| r = ap_pregcomp(cmd->pool, from, AP_REG_EXTENDED | |
| (ignore_case ? AP_REG_ICASE : 0)); |
| 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 */ |
| }; |