/* 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
*
*
* 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.
*/
typedef struct subst_pattern_t {
const char *replacement;
int flatten;
typedef struct {
int max_line_length_set;
typedef struct {
{
return dcfg;
}
{
subst_dir_conf *a =
return a;
}
apr_bucket_split(b, offset); \
tmp_b = APR_BUCKET_NEXT(b); \
b = APR_BUCKET_NEXT(tmp_b); \
} while (0)
{
int i;
int force_quick = 0;
const char *buff;
apr_bucket *b;
/*
* Simple optimization. If we only have one pattern, then
* we can safely avoid the overhead of flattening
*/
force_quick = 1;
}
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;
}
== APR_SUCCESS) {
int have_match = 0;
const char *repl;
/*
* space_left counts how many bytes we have left until the
* line length reaches max_line_length.
*/
{
have_match = 1;
/* get offset into buff for pattern */
/*
* 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.
*/
return APR_ENOMEM;
}
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.
*/
return APR_ENOMEM;
/*
* We now split off the string before the match
* as its own bucket, then isolate the matched
* string and delete it.
*/
/*
* Finally, we create a bucket that contains the
* replacement...
*/
f->r->connection->bucket_alloc);
/* ... and insert it */
}
/* now we need to adjust buff for all these changes */
}
if (have_match) {
/* XXX: we should check for AP_MAX_BUCKETS here and
* XXX: call ap_pass_brigade accordingly
*/
f->r->connection->bucket_alloc);
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;
}
}
}
char *repl;
AP_MAX_REG_MATCH, regm, 0)) {
have_match = 1;
/* 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. */
return APR_ENOMEM;
/* copy bytes before the match */
/* add replacement string, last argument is unsigned! */
if (rv != APR_SUCCESS)
return rv;
}
else {
/* acount for string before the match */
return APR_ENOMEM;
if (rv != APR_SUCCESS)
return rv;
space_left -= repl_len;
f->r->connection->bucket_alloc);
}
/*
* reset to past what we just did. pos now maps to b
* again
*/
}
char *copy;
/* Copy result plus the part after the last match into
* a bucket.
*/
&len);
f->r->connection->bucket_alloc);
b = tmp_b;
}
}
else {
ap_assert(0);
continue;
}
}
}
script++;
}
ap_varbuf_free(&vb);
return APR_SUCCESS;
}
{
const char *buff;
char *bflat;
apr_bucket *b;
/*
* 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) {
/*
* 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.
*/
/*
* Everything to be passed to the next filter goes in
* here, our pass brigade.
*/
/* Create our temporary pool only once */
}
/*
* 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 :)
*/
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 (rv != APR_SUCCESS)
goto err;
rv = APR_ENOMEM;
goto err;
}
f->r->connection->bucket_alloc);
if (rv != APR_SUCCESS)
goto err;
}
}
/*
* 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)) {
}
else {
/*
* We have actual "data" so read in as much as we can and start
* scanning and splitting from our read buffer
*/
}
else {
int num = 0;
while (bytes > 0) {
if (nl) {
/* 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
*/
/*
* we need b to be updated for future potential
* splitting
*/
tmp_b = APR_BUCKET_NEXT(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 (rv != APR_SUCCESS)
goto err;
/* Avoid pflattening further lines, we will
* abort later on anyway.
*/
rv = APR_ENOMEM;
goto err;
}
f->r->connection->bucket_alloc);
}
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.
*/
b = APR_BUCKET_NEXT(b)) {
num++;
}
/*
* 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) {
f->r->connection->bucket_alloc);
num = 0;
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
*/
bytes = 0;
}
}
}
}
if (rv != APR_SUCCESS)
goto err;
}
}
/*
* Provide ap_save_brigade with an existing empty brigade
* (ctx->linesbb) to avoid creating a new one.
*/
}
return APR_SUCCESS;
err:
if (rv == APR_ENOMEM)
f->r->uri);
return rv;
}
{
char *ourline;
char delim;
int is_pattern = 0;
int ignore_case = 0;
ap_regex_t *r = NULL;
return "Bad Substitute format, must be an s/// pattern";
}
if (delim)
if (from) {
}
if (*ourline) {
*ourline = '\0';
}
}
if (to) {
}
if (*ourline) {
*ourline = '\0';
}
}
return "Bad Substitute format, must be a complete s/// pattern";
}
if (flags) {
while (*flags) {
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) {
(ignore_case ? AP_REG_ICASE : 0));
if (!r)
return "Substitute could not compile regex";
}
/* init the new entries */
if (is_pattern) {
!ignore_case);
}
else {
}
return NULL;
}
{
char *end;
if (rv == APR_SUCCESS) {
}
}
}
else if (*end && /* neither empty nor [Bb] */
rv = APR_EGENERAL;
}
}
{
return "SubstituteMaxLineLength must be a non-negative integer optionally "
"suffixed with 'b', 'k', 'm' or 'g'.";
}
return NULL;
}
{
}
"Maximum line length"),
{NULL}
};
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 */
};