]> granicus.if.org Git - apache/blobdiff - modules/filters/mod_substitute.c
Merge r1601624 from trunk:
[apache] / modules / filters / mod_substitute.c
index bd0dafd70e6754b7c58aa3a23edbf81ca491922d..99e82933ba40877f4b55c47b6121a47d8bb33264 100644 (file)
@@ -21,6 +21,7 @@
 #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"
 #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;
@@ -47,6 +55,9 @@ typedef struct 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 {
@@ -60,21 +71,43 @@ typedef struct {
 static void *create_substitute_dcfg(apr_pool_t *p, char *d)
 {
     subst_dir_conf *dcfg =
-    (subst_dir_conf *) apr_pcalloc(p, sizeof(subst_dir_conf));
+        (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_pcalloc(p, sizeof(subst_dir_conf));
+        (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->patterns = apr_array_append(p, over->patterns,
-                                                  base->patterns);
+    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;
 }
 
@@ -88,9 +121,9 @@ static void *merge_substitute_dcfg(apr_pool_t *p, void *basev, void *overv)
     apr_bucket_delete(tmp_b);                        \
 } while (0)
 
-static void do_pattmatch(ap_filter_t *f, apr_bucket *inb,
-                         apr_bucket_brigade *mybb,
-                         apr_pool_t *pool)
+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;
@@ -135,6 +168,12 @@ static void do_pattmatch(ap_filter_t *f, apr_bucket *inb,
                 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)))
                     {
                         have_match = 1;
@@ -149,14 +188,25 @@ static void do_pattmatch(ap_filter_t *f, apr_bucket *inb,
                              * 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_strcat(&vb, script->replacement);
+                            ap_varbuf_strmemcat(&vb, script->replacement, repl_len);
                         }
                         else {
                             /*
-                             * We now split off the stuff before the regex
-                             * as its own bucket, then isolate the pattern
-                             * and delete it.
+                             * 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);
                             /*
@@ -174,39 +224,77 @@ static void do_pattmatch(ap_filter_t *f, apr_bucket *inb,
                         bytes -= len;
                         buff += len;
                     }
-                    if (have_match && script->flatten && !force_quick) {
-                        char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
-                                                    buff, bytes, &len);
-                        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;
+                    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);
+                            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;
+                        }
                     }
                 }
                 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;
                         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 */
-                            ap_varbuf_regsub(&vb, script->replacement, pos,
-                                             AP_MAX_REG_MATCH, regm, 0);
+                            /* 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;
                         }
                         else {
-                            ap_pregsub_ex(pool, &repl, script->replacement, pos,
-                                              AP_MAX_REG_MATCH, regm, 0);
+                            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,
-                                             strlen(repl),
-                                             f->r->connection->bucket_alloc);
+                            tmp_b = apr_bucket_transient_create(repl, repl_len,
+                                                f->r->connection->bucket_alloc);
                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
                         }
                         /*
@@ -239,6 +327,7 @@ static void do_pattmatch(ap_filter_t *f, apr_bucket *inb,
         script++;
     }
     ap_varbuf_free(&vb);
+    return APR_SUCCESS;
 }
 
 static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
@@ -253,6 +342,9 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
     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;
 
@@ -323,12 +415,20 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
             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);
-                do_pattmatch(f, tmp_b, ctx->pattbb, ctx->tpool);
+                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_brigade_cleanup(ctx->linebb);
             APR_BUCKET_REMOVE(b);
             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
         }
@@ -381,11 +481,22 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
                             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);
                         }
-                        do_pattmatch(f, b, ctx->pattbb, ctx->tpool);
+                        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
@@ -416,7 +527,7 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
                             num = 0;
                             apr_pool_clear(ctx->tpool);
                             if (rv != APR_SUCCESS)
-                                return rv;
+                                goto err;
                         }
                         b = tmp_b;
                     }
@@ -435,10 +546,8 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
         if (!APR_BRIGADE_EMPTY(ctx->passbb)) {
             rv = ap_pass_brigade(f->next, ctx->passbb);
             apr_brigade_cleanup(ctx->passbb);
-            if (rv != APR_SUCCESS) {
-                apr_pool_clear(ctx->tpool);
-                return rv;
-            }
+            if (rv != APR_SUCCESS)
+                goto err;
         }
         apr_pool_clear(ctx->tpool);
     }
@@ -456,6 +565,12 @@ static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *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)
@@ -548,6 +663,44 @@ static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
     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)
 {
@@ -556,8 +709,13 @@ static void register_hooks(apr_pool_t *pool)
 }
 
 static const command_rec substitute_cmds[] = {
-    AP_INIT_TAKE1("Substitute", set_pattern, NULL, OR_ALL,
+    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}
 };