#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 {
apr_array_header_t *patterns;
+ apr_size_t max_line_length;
+ int max_line_length_set;
+ int inherit_before;
} subst_dir_conf;
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;
}
#define AP_MAX_BUCKETS 1000
-#define AP_SUBST_MAX_LINE_LENGTH (128*MAX_STRING_LEN)
#define SEDRMPATBCKT(b, offset, tmp_b, patlen) do { \
apr_bucket_split(b, offset); \
vb.strlen = 0;
if (script->pattern) {
const char *repl;
- apr_size_t space_left = AP_SUBST_MAX_LINE_LENGTH;
+ /*
+ * 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)))
{
* are constanting allocing space and copying
* strings.
*/
- if (vb.strlen + len + repl_len > AP_SUBST_MAX_LINE_LENGTH)
+ 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 {
/*
- * 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);
/*
* Finally, we create a bucket that contains the
bytes -= len;
buff += len;
}
- if (have_match && 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;
+ 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 = AP_SUBST_MAX_LINE_LENGTH;
+ 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 */
+ /* add replacement string, last argument is unsigned! */
rv = ap_varbuf_regsub(&vb, script->replacement, pos,
AP_MAX_REG_MATCH, regm,
- AP_SUBST_MAX_LINE_LENGTH - vb.strlen);
+ cfg->max_line_length - vb.strlen);
if (rv != APR_SUCCESS)
return rv;
}
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;
- len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
repl_len = strlen(repl);
- space_left -= len + repl_len;
+ 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 *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;
&fbytes, ctx->tpool);
if (rv != APR_SUCCESS)
goto err;
- if (fbytes > AP_SUBST_MAX_LINE_LENGTH) {
+ if (fbytes > cfg->max_line_length) {
rv = APR_ENOMEM;
goto err;
}
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);
}
&fbytes, ctx->tpool);
if (rv != APR_SUCCESS)
goto err;
- if (fbytes > AP_SUBST_MAX_LINE_LENGTH) {
+ if (fbytes > cfg->max_line_length) {
/* Avoid pflattening further lines, we will
* abort later on anyway.
*/
return APR_SUCCESS;
err:
if (rv == APR_ENOMEM)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Line too long, URI %s",
+ 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;
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)
{
}
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}
};