in modules/filters/mod_substitute.c [134:411]
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_pattern_t *script;
subst_dir_conf *cfg =
(subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
&substitute_module);
subst_req_t *rconf =
(subst_req_t*) ap_get_module_config(f->r->request_config,
&substitute_module);
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++) {
const char *replacement = script->replacement;
apr_size_t replen = script->replen;
if (script->expr_replacement) {
if (!rconf) {
rconf = apr_pcalloc(f->r->pool, sizeof(*rconf));
rconf->expcache = apr_pcalloc(f->r->pool, sizeof(const char*) * cfg->patterns->nelts);
rconf->expcache_len = apr_pcalloc(f->r->pool, sizeof(int) * cfg->patterns->nelts);
ap_set_module_config(f->r->request_config, &substitute_module, rconf);
}
}
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;
while ((repl = apr_strmatch(script->pattern, buff, bytes)))
{
if (!have_match && script->expr_replacement) {
if (!rconf->expcache[i]) {
const char *err = NULL;
rconf->expcache[i] = ap_expr_str_exec(f->r, script->expr_replacement, &err);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "error evaluating expression: %s", err);
return APR_EINVAL;
}
rconf->expcache_len[i] = strlen(rconf->expcache[i]);
}
replacement = rconf->expcache[i];
replen = rconf->expcache_len[i];
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
"Matching found, result: '%s'",
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 + replen > cfg->max_line_length)
return APR_ENOMEM;
ap_varbuf_strmemcat(&vb, buff, len);
ap_varbuf_strmemcat(&vb, replacement, replen);
}
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 + replen)
return APR_ENOMEM;
space_left -= len + replen;
/*
* 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(replacement,
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) {
apr_size_t 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;
if (!have_match && script->expr_replacement) {
if (!rconf->expcache[i]) {
const char *err = NULL;
rconf->expcache[i] = ap_expr_str_exec(f->r, script->expr_replacement, &err);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "error evaluating expression: %s", err);
return APR_EGENERAL;
}
rconf->expcache_len[i] = strlen(rconf->expcache[i]);
}
replacement = rconf->expcache[i];
replen = rconf->expcache_len[i];
}
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, 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,
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;
}