1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * mod_substitute.c: Perform content rewriting on the fly
19  */
20 
21 #include "httpd.h"
22 #include "http_config.h"
23 #include "http_core.h"
24 #include "http_log.h"
25 #include "apr_general.h"
26 #include "apr_strings.h"
27 #include "apr_strmatch.h"
28 #include "apr_lib.h"
29 #include "util_filter.h"
30 #include "util_varbuf.h"
31 #include "apr_buckets.h"
32 #include "http_request.h"
33 #define APR_WANT_STRFUNC
34 #include "apr_want.h"
35 
36 /*
37  * We want to limit the memory usage in a way that is predictable.
38  * Therefore we limit the resulting length of the line.
39  * This is the default value.
40  */
41 #define AP_SUBST_MAX_LINE_LENGTH (1024*1024)
42 
43 static const char substitute_filter_name[] = "SUBSTITUTE";
44 
45 module AP_MODULE_DECLARE_DATA substitute_module;
46 
47 typedef struct subst_pattern_t {
48     const apr_strmatch_pattern *pattern;
49     const ap_regex_t *regexp;
50     const char *replacement;
51     apr_size_t replen;
52     apr_size_t patlen;
53     int flatten;
54     const char *from;
55 } subst_pattern_t;
56 
57 typedef struct {
58     apr_array_header_t *patterns;
59     apr_size_t max_line_length;
60     int max_line_length_set;
61     int inherit_before;
62 } subst_dir_conf;
63 
64 typedef struct {
65     apr_bucket_brigade *linebb;
66     apr_bucket_brigade *linesbb;
67     apr_bucket_brigade *passbb;
68     apr_bucket_brigade *pattbb;
69     apr_pool_t *tpool;
70 } substitute_module_ctx;
71 
create_substitute_dcfg(apr_pool_t * p,char * d)72 static void *create_substitute_dcfg(apr_pool_t *p, char *d)
73 {
74     subst_dir_conf *dcfg =
75         (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
76 
77     dcfg->patterns = apr_array_make(p, 10, sizeof(subst_pattern_t));
78     dcfg->max_line_length = AP_SUBST_MAX_LINE_LENGTH;
79     dcfg->max_line_length_set = 0;
80     dcfg->inherit_before = -1;
81     return dcfg;
82 }
83 
merge_substitute_dcfg(apr_pool_t * p,void * basev,void * overv)84 static void *merge_substitute_dcfg(apr_pool_t *p, void *basev, void *overv)
85 {
86     subst_dir_conf *a =
87         (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
88     subst_dir_conf *base = (subst_dir_conf *) basev;
89     subst_dir_conf *over = (subst_dir_conf *) overv;
90 
91     a->inherit_before = (over->inherit_before != -1)
92                             ? over->inherit_before
93                             : base->inherit_before;
94     /* SubstituteInheritBefore wasn't the default behavior until 2.5.x,
95      * and may be re-disabled as desired; the original default behavior
96      * was to apply inherited subst patterns after locally scoped patterns.
97      * In later 2.2 and 2.4 versions, SubstituteInheritBefore may be toggled
98      * 'on' to follow the corrected/expected behavior, without violating POLS.
99      */
100     if (a->inherit_before == 1) {
101         a->patterns = apr_array_append(p, base->patterns,
102                                           over->patterns);
103     }
104     else {
105         a->patterns = apr_array_append(p, over->patterns,
106                                           base->patterns);
107     }
108     a->max_line_length = over->max_line_length_set ?
109                              over->max_line_length : base->max_line_length;
110     a->max_line_length_set = over->max_line_length_set
111                            | base->max_line_length_set;
112     return a;
113 }
114 
115 #define AP_MAX_BUCKETS 1000
116 
117 #define SEDRMPATBCKT(b, offset, tmp_b, patlen) do {  \
118     apr_bucket_split(b, offset);                     \
119     tmp_b = APR_BUCKET_NEXT(b);                      \
120     apr_bucket_split(tmp_b, patlen);                 \
121     b = APR_BUCKET_NEXT(tmp_b);                      \
122     apr_bucket_delete(tmp_b);                        \
123 } while (0)
124 
125 #define CAP2LINEMAX(n) ((n) < (apr_size_t)200 ? (int)(n) : 200)
126 
do_pattmatch(ap_filter_t * f,apr_bucket * inb,apr_bucket_brigade * mybb,apr_pool_t * pool)127 static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
128                                  apr_bucket_brigade *mybb,
129                                  apr_pool_t *pool)
130 {
131     int i;
132     int force_quick = 0;
133     ap_regmatch_t regm[AP_MAX_REG_MATCH];
134     apr_size_t bytes;
135     apr_size_t len;
136     const char *buff;
137     struct ap_varbuf vb;
138     apr_bucket *b;
139     apr_bucket *tmp_b;
140 
141     subst_dir_conf *cfg =
142     (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
143                                              &substitute_module);
144     subst_pattern_t *script;
145 
146     APR_BRIGADE_INSERT_TAIL(mybb, inb);
147     ap_varbuf_init(pool, &vb, 0);
148 
149     script = (subst_pattern_t *) cfg->patterns->elts;
150     /*
151      * Simple optimization. If we only have one pattern, then
152      * we can safely avoid the overhead of flattening
153      */
154     if (cfg->patterns->nelts == 1) {
155        force_quick = 1;
156     }
157     for (i = 0; i < cfg->patterns->nelts; i++) {
158         for (b = APR_BRIGADE_FIRST(mybb);
159              b != APR_BRIGADE_SENTINEL(mybb);
160              b = APR_BUCKET_NEXT(b)) {
161             if (APR_BUCKET_IS_METADATA(b)) {
162                 /*
163                  * we should NEVER see this, because we should never
164                  * be passed any, but "handle" it just in case.
165                  */
166                 continue;
167             }
168             if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
169                     == APR_SUCCESS) {
170                 int have_match = 0;
171 
172                 ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
173                               "Line read (%" APR_SIZE_T_FMT " bytes): %.*s",
174                               bytes, CAP2LINEMAX(bytes), buff);
175                 ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
176                               "Replacing %s:'%s' by '%s'",
177                               script->pattern ? "string" :
178                               script->regexp  ? "regex"  :
179                                                 "unknown",
180                               script->from, script->replacement);
181 
182                 vb.strlen = 0;
183                 if (script->pattern) {
184                     const char *repl;
185                     /*
186                      * space_left counts how many bytes we have left until the
187                      * line length reaches max_line_length.
188                      */
189                     apr_size_t space_left = cfg->max_line_length;
190                     apr_size_t repl_len = strlen(script->replacement);
191                     while ((repl = apr_strmatch(script->pattern, buff, bytes)))
192                     {
193                         ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
194                                       "Matching found, result: '%s'",
195                                       script->replacement);
196                         have_match = 1;
197                         /* get offset into buff for pattern */
198                         len = (apr_size_t) (repl - buff);
199                         if (script->flatten && !force_quick) {
200                             /*
201                              * We are flattening the buckets here, meaning
202                              * that we don't do the fast bucket splits.
203                              * Instead we copy over what the buckets would
204                              * contain and use them. This is slow, since we
205                              * are constanting allocing space and copying
206                              * strings.
207                              */
208                             if (vb.strlen + len + repl_len > cfg->max_line_length)
209                                 return APR_ENOMEM;
210                             ap_varbuf_strmemcat(&vb, buff, len);
211                             ap_varbuf_strmemcat(&vb, script->replacement, repl_len);
212                         }
213                         else {
214                             /*
215                              * The string before the match but after the
216                              * previous match (if any) has length 'len'.
217                              * Check if we still have space for this string and
218                              * the replacement string.
219                              */
220                             if (space_left < len + repl_len)
221                                 return APR_ENOMEM;
222                             space_left -= len + repl_len;
223                             /*
224                              * We now split off the string before the match
225                              * as its own bucket, then isolate the matched
226                              * string and delete it.
227                              */
228                             SEDRMPATBCKT(b, len, tmp_b, script->patlen);
229                             /*
230                              * Finally, we create a bucket that contains the
231                              * replacement...
232                              */
233                             tmp_b = apr_bucket_transient_create(script->replacement,
234                                       script->replen,
235                                       f->r->connection->bucket_alloc);
236                             /* ... and insert it */
237                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
238                         }
239                         /* now we need to adjust buff for all these changes */
240                         len += script->patlen;
241                         bytes -= len;
242                         buff += len;
243                     }
244                     if (have_match) {
245                         if (script->flatten && !force_quick) {
246                             /* XXX: we should check for AP_MAX_BUCKETS here and
247                              * XXX: call ap_pass_brigade accordingly
248                              */
249                             char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
250                                                         buff, bytes, &len);
251                             ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
252                                           "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
253                                           len, CAP2LINEMAX(len), copy);
254                             tmp_b = apr_bucket_pool_create(copy, len, pool,
255                                                            f->r->connection->bucket_alloc);
256                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
257                             apr_bucket_delete(b);
258                             b = tmp_b;
259                         }
260                         else {
261                             /*
262                              * We want the behaviour to be predictable.
263                              * Therefore we try to always error out if the
264                              * line length is larger than the limit,
265                              * regardless of the content of the line. So,
266                              * let's check if the remaining non-matching
267                              * string does not exceed the limit.
268                              */
269                             if (space_left < b->length)
270                                 return APR_ENOMEM;
271                             ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
272                                           "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
273                                           bytes, CAP2LINEMAX(bytes), buff);
274                         }
275                     }
276                 }
277                 else if (script->regexp) {
278                     int left = bytes;
279                     const char *pos = buff;
280                     char *repl;
281                     apr_size_t space_left = cfg->max_line_length;
282                     while (!ap_regexec_len(script->regexp, pos, left,
283                                        AP_MAX_REG_MATCH, regm, 0)) {
284                         apr_status_t rv;
285                         ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
286                                       "Matching found");
287                         have_match = 1;
288                         if (script->flatten && !force_quick) {
289                             /* check remaining buffer size */
290                             /* Note that the last param in ap_varbuf_regsub below
291                              * must stay positive. If it gets 0, it would mean
292                              * unlimited space available. */
293                             if (vb.strlen + regm[0].rm_so >= cfg->max_line_length)
294                                 return APR_ENOMEM;
295                             /* copy bytes before the match */
296                             if (regm[0].rm_so > 0)
297                                 ap_varbuf_strmemcat(&vb, pos, regm[0].rm_so);
298                             /* add replacement string, last argument is unsigned! */
299                             rv = ap_varbuf_regsub(&vb, script->replacement, pos,
300                                                   AP_MAX_REG_MATCH, regm,
301                                                   cfg->max_line_length - vb.strlen);
302                             if (rv != APR_SUCCESS)
303                                 return rv;
304                             ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
305                                           "Result: '%s'", vb.buf);
306                         }
307                         else {
308                             apr_size_t repl_len;
309                             /* account for string before the match */
310                             if (space_left <= regm[0].rm_so)
311                                 return APR_ENOMEM;
312                             space_left -= regm[0].rm_so;
313                             rv = ap_pregsub_ex(pool, &repl,
314                                                script->replacement, pos,
315                                                AP_MAX_REG_MATCH, regm,
316                                                space_left);
317                             if (rv != APR_SUCCESS)
318                                 return rv;
319                             repl_len = strlen(repl);
320                             space_left -= repl_len;
321                             len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
322                             SEDRMPATBCKT(b, regm[0].rm_so, tmp_b, len);
323                             tmp_b = apr_bucket_transient_create(repl, repl_len,
324                                                 f->r->connection->bucket_alloc);
325                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
326                             ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
327                                           "Result: '%s'", repl);
328                         }
329                         /*
330                          * reset to past what we just did. pos now maps to b
331                          * again
332                          */
333                         pos += regm[0].rm_eo;
334                         left -= regm[0].rm_eo;
335                     }
336                     if (have_match && script->flatten && !force_quick) {
337                         char *copy;
338                         /* Copy result plus the part after the last match into
339                          * a bucket.
340                          */
341                         copy = ap_varbuf_pdup(pool, &vb, NULL, 0, pos, left,
342                                               &len);
343                         ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
344                                       "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
345                                       len, CAP2LINEMAX(len), copy);
346                         tmp_b = apr_bucket_pool_create(copy, len, pool,
347                                            f->r->connection->bucket_alloc);
348                         APR_BUCKET_INSERT_BEFORE(b, tmp_b);
349                         apr_bucket_delete(b);
350                         b = tmp_b;
351                     }
352                 }
353                 else {
354                     ap_assert(0);
355                     continue;
356                 }
357             }
358         }
359         script++;
360     }
361     ap_varbuf_free(&vb);
362     return APR_SUCCESS;
363 }
364 
substitute_filter(ap_filter_t * f,apr_bucket_brigade * bb)365 static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
366 {
367     apr_size_t bytes;
368     apr_size_t len;
369     apr_size_t fbytes;
370     const char *buff;
371     const char *nl = NULL;
372     char *bflat;
373     apr_bucket *b;
374     apr_bucket *tmp_b;
375     apr_bucket_brigade *tmp_bb = NULL;
376     apr_status_t rv;
377     subst_dir_conf *cfg =
378     (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
379                                              &substitute_module);
380 
381     substitute_module_ctx *ctx = f->ctx;
382 
383     /*
384      * First time around? Create the saved bb that we used for each pass
385      * through. Note that we can also get here when we explicitly clear ctx,
386      * for error handling
387      */
388     if (!ctx) {
389         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
390         /*
391          * Create all the temporary brigades we need and reuse them to avoid
392          * creating them over and over again from r->pool which would cost a
393          * lot of memory in some cases.
394          */
395         ctx->linebb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
396         ctx->linesbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
397         ctx->pattbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
398         /*
399          * Everything to be passed to the next filter goes in
400          * here, our pass brigade.
401          */
402         ctx->passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
403         /* Create our temporary pool only once */
404         apr_pool_create(&(ctx->tpool), f->r->pool);
405         apr_pool_tag(ctx->tpool, "substitute_tpool");
406         apr_table_unset(f->r->headers_out, "Content-Length");
407     }
408 
409     /*
410      * Shortcircuit processing
411      */
412     if (APR_BRIGADE_EMPTY(bb))
413         return APR_SUCCESS;
414 
415     /*
416      * Here's the concept:
417      *  Read in the data and look for newlines. Once we
418      *  find a full "line", add it to our working brigade.
419      *  If we've finished reading the brigade and we have
420      *  any left over data (not a "full" line), store that
421      *  for the next pass.
422      *
423      * Note: anything stored in ctx->linebb for sure does not have
424      * a newline char, so we don't concat that bb with the
425      * new bb, since we would spending time searching for the newline
426      * in data we know it doesn't exist. So instead, we simply scan
427      * our current bb and, if we see a newline, prepend ctx->linebb
428      * to the front of it. This makes the code much less straight-
429      * forward (otherwise we could APR_BRIGADE_CONCAT(ctx->linebb, bb)
430      * and just scan for newlines and not bother with needing to know
431      * when ctx->linebb needs to be reset) but also faster. We'll take
432      * the speed.
433      *
434      * Note: apr_brigade_split_line would be nice here, but we
435      * really can't use it since we need more control and we want
436      * to re-use already read bucket data.
437      *
438      * See mod_include if still confused :)
439      */
440 
441     while ((b = APR_BRIGADE_FIRST(bb)) && (b != APR_BRIGADE_SENTINEL(bb))) {
442         if (APR_BUCKET_IS_EOS(b)) {
443             /*
444              * if we see the EOS, then we need to pass along everything we
445              * have. But if the ctx->linebb isn't empty, then we need to add
446              * that to the end of what we'll be passing.
447              */
448             if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
449                 rv = apr_brigade_pflatten(ctx->linebb, &bflat,
450                                           &fbytes, ctx->tpool);
451                 if (rv != APR_SUCCESS)
452                     goto err;
453                 if (fbytes > cfg->max_line_length) {
454                     rv = APR_ENOMEM;
455                     goto err;
456                 }
457                 tmp_b = apr_bucket_transient_create(bflat, fbytes,
458                                                 f->r->connection->bucket_alloc);
459                 rv = do_pattmatch(f, tmp_b, ctx->pattbb, ctx->tpool);
460                 if (rv != APR_SUCCESS)
461                     goto err;
462                 APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
463                 apr_brigade_cleanup(ctx->linebb);
464             }
465             APR_BUCKET_REMOVE(b);
466             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
467         }
468         /*
469          * No need to handle FLUSH buckets separately as we call
470          * ap_pass_brigade anyway at the end of the loop.
471          */
472         else if (APR_BUCKET_IS_METADATA(b)) {
473             APR_BUCKET_REMOVE(b);
474             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
475         }
476         else {
477             /*
478              * We have actual "data" so read in as much as we can and start
479              * scanning and splitting from our read buffer
480              */
481             rv = apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ);
482             if (rv != APR_SUCCESS || bytes == 0) {
483                 apr_bucket_delete(b);
484             }
485             else {
486                 int num = 0;
487                 while (bytes > 0) {
488                     nl = memchr(buff, '\n', bytes);
489                     if (nl) {
490                         len = (apr_size_t) (nl - buff) + 1;
491                         /* split *after* the newline */
492                         apr_bucket_split(b, len);
493                         /*
494                          * We've likely read more data, so bypass rereading
495                          * bucket data and continue scanning through this
496                          * buffer
497                          */
498                         bytes -= len;
499                         buff += len;
500                         /*
501                          * we need b to be updated for future potential
502                          * splitting
503                          */
504                         tmp_b = APR_BUCKET_NEXT(b);
505                         APR_BUCKET_REMOVE(b);
506                         /*
507                          * Hey, we found a newline! Don't forget the old
508                          * stuff that needs to be added to the front. So we
509                          * add the split bucket to the end, flatten the whole
510                          * bb, morph the whole shebang into a bucket which is
511                          * then added to the tail of the newline bb.
512                          */
513                         if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
514                             APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
515                             rv = apr_brigade_pflatten(ctx->linebb, &bflat,
516                                                       &fbytes, ctx->tpool);
517                             if (rv != APR_SUCCESS)
518                                 goto err;
519                             if (fbytes > cfg->max_line_length) {
520                                 /* Avoid pflattening further lines, we will
521                                  * abort later on anyway.
522                                  */
523                                 rv = APR_ENOMEM;
524                                 goto err;
525                             }
526                             b = apr_bucket_transient_create(bflat, fbytes,
527                                             f->r->connection->bucket_alloc);
528                             apr_brigade_cleanup(ctx->linebb);
529                         }
530                         rv = do_pattmatch(f, b, ctx->pattbb, ctx->tpool);
531                         if (rv != APR_SUCCESS)
532                             goto err;
533                         /*
534                          * Count how many buckets we have in ctx->passbb
535                          * so far. Yes, this is correct we count ctx->passbb
536                          * and not ctx->pattbb as we do not reset num on every
537                          * iteration.
538                          */
539                         for (b = APR_BRIGADE_FIRST(ctx->pattbb);
540                              b != APR_BRIGADE_SENTINEL(ctx->pattbb);
541                              b = APR_BUCKET_NEXT(b)) {
542                             num++;
543                         }
544                         APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
545                         /*
546                          * If the number of buckets in ctx->passbb reaches an
547                          * "insane" level, we consume much memory for all the
548                          * buckets as such. So lets flush them down the chain
549                          * in this case and thus clear ctx->passbb. This frees
550                          * the buckets memory for further processing.
551                          * Usually this condition should not become true, but
552                          * it is a safety measure for edge cases.
553                          */
554                         if (num > AP_MAX_BUCKETS) {
555                             b = apr_bucket_flush_create(
556                                                 f->r->connection->bucket_alloc);
557                             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
558                             rv = ap_pass_brigade(f->next, ctx->passbb);
559                             apr_brigade_cleanup(ctx->passbb);
560                             num = 0;
561                             apr_pool_clear(ctx->tpool);
562                             if (rv != APR_SUCCESS)
563                                 goto err;
564                         }
565                         b = tmp_b;
566                     }
567                     else {
568                         /*
569                          * no newline in whatever is left of this buffer so
570                          * tuck data away and get next bucket
571                          */
572                         APR_BUCKET_REMOVE(b);
573                         APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
574                         bytes = 0;
575                     }
576                 }
577             }
578         }
579         if (!APR_BRIGADE_EMPTY(ctx->passbb)) {
580             rv = ap_pass_brigade(f->next, ctx->passbb);
581             apr_brigade_cleanup(ctx->passbb);
582             if (rv != APR_SUCCESS)
583                 goto err;
584         }
585         apr_pool_clear(ctx->tpool);
586     }
587 
588     /* Anything left we want to save/setaside for the next go-around */
589     if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
590         /*
591          * Provide ap_save_brigade with an existing empty brigade
592          * (ctx->linesbb) to avoid creating a new one.
593          */
594         ap_save_brigade(f, &(ctx->linesbb), &(ctx->linebb), f->r->pool);
595         tmp_bb = ctx->linebb;
596         ctx->linebb = ctx->linesbb;
597         ctx->linesbb = tmp_bb;
598     }
599 
600     return APR_SUCCESS;
601 err:
602     if (rv == APR_ENOMEM)
603         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01328) "Line too long, URI %s",
604                       f->r->uri);
605     apr_pool_clear(ctx->tpool);
606     return rv;
607 }
608 
set_pattern(cmd_parms * cmd,void * cfg,const char * line)609 static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
610 {
611     char *from = NULL;
612     char *to = NULL;
613     char *flags = NULL;
614     char *ourline;
615     char delim;
616     subst_pattern_t *nscript;
617     int is_pattern = 0;
618     int ignore_case = 0;
619     int flatten = 1;
620     ap_regex_t *r = NULL;
621 
622     if (apr_tolower(*line) != 's') {
623         return "Bad Substitute format, must be an s/// pattern";
624     }
625     ourline = apr_pstrdup(cmd->pool, line);
626     delim = *++ourline;
627     if (delim)
628         from = ++ourline;
629     if (from) {
630         if (*ourline != delim) {
631             while (*++ourline && *ourline != delim);
632         }
633         if (*ourline) {
634             *ourline = '\0';
635             to = ++ourline;
636         }
637     }
638     if (to) {
639         if (*ourline != delim) {
640             while (*++ourline && *ourline != delim);
641         }
642         if (*ourline) {
643             *ourline = '\0';
644             flags = ++ourline;
645         }
646     }
647 
648     if (!delim || !from || !*from || !to) {
649         return "Bad Substitute format, must be a complete s/// pattern";
650     }
651 
652     if (flags) {
653         while (*flags) {
654             delim = apr_tolower(*flags);    /* re-use */
655             if (delim == 'i')
656                 ignore_case = 1;
657             else if (delim == 'n')
658                 is_pattern = 1;
659             else if (delim == 'f')
660                 flatten = 1;
661             else if (delim == 'q')
662                 flatten = 0;
663             else
664                 return "Bad Substitute flag, only s///[infq] are supported";
665             flags++;
666         }
667     }
668 
669     /* first see if we can compile the regex */
670     if (!is_pattern) {
671         int flags = AP_REG_NO_DEFAULT
672                     | (ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY)
673                     | (ignore_case ? AP_REG_ICASE : 0);
674         r = ap_pregcomp(cmd->pool, from, flags);
675         if (!r)
676             return "Substitute could not compile regex";
677     }
678     nscript = apr_array_push(((subst_dir_conf *) cfg)->patterns);
679     /* init the new entries */
680     nscript->pattern = NULL;
681     nscript->regexp = NULL;
682     nscript->replacement = NULL;
683     nscript->patlen = 0;
684     nscript->from = from;
685 
686     if (is_pattern) {
687         nscript->patlen = strlen(from);
688         nscript->pattern = apr_strmatch_precompile(cmd->pool, from,
689                                                    !ignore_case);
690     }
691     else {
692         nscript->regexp = r;
693     }
694 
695     nscript->replacement = to;
696     nscript->replen = strlen(to);
697     nscript->flatten = flatten;
698 
699     return NULL;
700 }
701 
702 #define KBYTE         1024
703 #define MBYTE         1048576
704 #define GBYTE         1073741824
705 
set_max_line_length(cmd_parms * cmd,void * cfg,const char * arg)706 static const char *set_max_line_length(cmd_parms *cmd, void *cfg, const char *arg)
707 {
708     subst_dir_conf *dcfg = (subst_dir_conf *)cfg;
709     apr_off_t max;
710     char *end;
711     apr_status_t rv;
712 
713     rv = apr_strtoff(&max, arg, &end, 10);
714     if (rv == APR_SUCCESS) {
715         if ((*end == 'K' || *end == 'k') && !end[1]) {
716             max *= KBYTE;
717         }
718         else if ((*end == 'M' || *end == 'm') && !end[1]) {
719             max *= MBYTE;
720         }
721         else if ((*end == 'G' || *end == 'g') && !end[1]) {
722             max *= GBYTE;
723         }
724         else if (*end && /* neither empty nor [Bb] */
725                  ((*end != 'B' && *end != 'b') || end[1])) {
726             rv = APR_EGENERAL;
727         }
728     }
729 
730     if (rv != APR_SUCCESS || max < 0)
731     {
732         return "SubstituteMaxLineLength must be a non-negative integer optionally "
733                "suffixed with 'b', 'k', 'm' or 'g'.";
734     }
735     dcfg->max_line_length = (apr_size_t)max;
736     dcfg->max_line_length_set = 1;
737     return NULL;
738 }
739 
740 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
register_hooks(apr_pool_t * pool)741 static void register_hooks(apr_pool_t *pool)
742 {
743     ap_register_output_filter(substitute_filter_name, substitute_filter,
744                               NULL, AP_FTYPE_RESOURCE);
745 }
746 
747 static const command_rec substitute_cmds[] = {
748     AP_INIT_TAKE1("Substitute", set_pattern, NULL, OR_FILEINFO,
749                   "Pattern to filter the response content (s/foo/bar/[inf])"),
750     AP_INIT_TAKE1("SubstituteMaxLineLength", set_max_line_length, NULL, OR_FILEINFO,
751                   "Maximum line length"),
752     AP_INIT_FLAG("SubstituteInheritBefore", ap_set_flag_slot,
753                  (void *)APR_OFFSETOF(subst_dir_conf, inherit_before), OR_FILEINFO,
754                  "Apply inherited patterns before those of the current context"),
755     {NULL}
756 };
757 
758 AP_DECLARE_MODULE(substitute) = {
759     STANDARD20_MODULE_STUFF,
760     create_substitute_dcfg,     /* dir config creater */
761     merge_substitute_dcfg,      /* dir merger --- default is to override */
762     NULL,                       /* server config */
763     NULL,                       /* merge server config */
764     substitute_cmds,            /* command table */
765     register_hooks              /* register hooks */
766 };
767