]> granicus.if.org Git - apache/blob - modules/filters/mod_substitute.c
Also do length check if the last line is not LF terminated
[apache] / modules / filters / mod_substitute.c
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 static const char substitute_filter_name[] = "SUBSTITUTE";
37
38 module AP_MODULE_DECLARE_DATA substitute_module;
39
40 typedef struct subst_pattern_t {
41     const apr_strmatch_pattern *pattern;
42     const ap_regex_t *regexp;
43     const char *replacement;
44     apr_size_t replen;
45     apr_size_t patlen;
46     int flatten;
47 } subst_pattern_t;
48
49 typedef struct {
50     apr_array_header_t *patterns;
51 } subst_dir_conf;
52
53 typedef struct {
54     apr_bucket_brigade *linebb;
55     apr_bucket_brigade *linesbb;
56     apr_bucket_brigade *passbb;
57     apr_bucket_brigade *pattbb;
58     apr_pool_t *tpool;
59 } substitute_module_ctx;
60
61 static void *create_substitute_dcfg(apr_pool_t *p, char *d)
62 {
63     subst_dir_conf *dcfg =
64     (subst_dir_conf *) apr_pcalloc(p, sizeof(subst_dir_conf));
65
66     dcfg->patterns = apr_array_make(p, 10, sizeof(subst_pattern_t));
67     return dcfg;
68 }
69
70 static void *merge_substitute_dcfg(apr_pool_t *p, void *basev, void *overv)
71 {
72     subst_dir_conf *a =
73     (subst_dir_conf *) apr_pcalloc(p, sizeof(subst_dir_conf));
74     subst_dir_conf *base = (subst_dir_conf *) basev;
75     subst_dir_conf *over = (subst_dir_conf *) overv;
76
77     a->patterns = apr_array_append(p, over->patterns,
78                                                   base->patterns);
79     return a;
80 }
81
82 #define AP_MAX_BUCKETS 1000
83 #define AP_SUBST_MAX_LINE_LENGTH (128*MAX_STRING_LEN)
84
85 #define SEDRMPATBCKT(b, offset, tmp_b, patlen) do {  \
86     apr_bucket_split(b, offset);                     \
87     tmp_b = APR_BUCKET_NEXT(b);                      \
88     apr_bucket_split(tmp_b, patlen);                 \
89     b = APR_BUCKET_NEXT(tmp_b);                      \
90     apr_bucket_delete(tmp_b);                        \
91 } while (0)
92
93 static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
94                                  apr_bucket_brigade *mybb,
95                                  apr_pool_t *pool)
96 {
97     int i;
98     int force_quick = 0;
99     ap_regmatch_t regm[AP_MAX_REG_MATCH];
100     apr_size_t bytes;
101     apr_size_t len;
102     const char *buff;
103     struct ap_varbuf vb;
104     apr_bucket *b;
105     apr_bucket *tmp_b;
106
107     subst_dir_conf *cfg =
108     (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
109                                              &substitute_module);
110     subst_pattern_t *script;
111
112     APR_BRIGADE_INSERT_TAIL(mybb, inb);
113     ap_varbuf_init(pool, &vb, 0);
114
115     script = (subst_pattern_t *) cfg->patterns->elts;
116     /*
117      * Simple optimization. If we only have one pattern, then
118      * we can safely avoid the overhead of flattening
119      */
120     if (cfg->patterns->nelts == 1) {
121        force_quick = 1;
122     }
123     for (i = 0; i < cfg->patterns->nelts; i++) {
124         for (b = APR_BRIGADE_FIRST(mybb);
125              b != APR_BRIGADE_SENTINEL(mybb);
126              b = APR_BUCKET_NEXT(b)) {
127             if (APR_BUCKET_IS_METADATA(b)) {
128                 /*
129                  * we should NEVER see this, because we should never
130                  * be passed any, but "handle" it just in case.
131                  */
132                 continue;
133             }
134             if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
135                     == APR_SUCCESS) {
136                 int have_match = 0;
137                 vb.strlen = 0;
138                 if (script->pattern) {
139                     const char *repl;
140                     apr_size_t space_left = AP_SUBST_MAX_LINE_LENGTH;
141                     apr_size_t repl_len = strlen(script->replacement);
142                     while ((repl = apr_strmatch(script->pattern, buff, bytes)))
143                     {
144                         have_match = 1;
145                         /* get offset into buff for pattern */
146                         len = (apr_size_t) (repl - buff);
147                         if (script->flatten && !force_quick) {
148                             /*
149                              * We are flattening the buckets here, meaning
150                              * that we don't do the fast bucket splits.
151                              * Instead we copy over what the buckets would
152                              * contain and use them. This is slow, since we
153                              * are constanting allocing space and copying
154                              * strings.
155                              */
156                             if (vb.strlen + len + repl_len > AP_SUBST_MAX_LINE_LENGTH)
157                                 return APR_ENOMEM;
158                             ap_varbuf_strmemcat(&vb, buff, len);
159                             ap_varbuf_strmemcat(&vb, script->replacement, repl_len);
160                         }
161                         else {
162                             /*
163                              * We now split off the stuff before the regex
164                              * as its own bucket, then isolate the pattern
165                              * and delete it.
166                              */
167                             if (space_left < len + repl_len)
168                                 return APR_ENOMEM;
169                             space_left -= len + repl_len;
170                             SEDRMPATBCKT(b, len, tmp_b, script->patlen);
171                             /*
172                              * Finally, we create a bucket that contains the
173                              * replacement...
174                              */
175                             tmp_b = apr_bucket_transient_create(script->replacement,
176                                       script->replen,
177                                       f->r->connection->bucket_alloc);
178                             /* ... and insert it */
179                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
180                         }
181                         /* now we need to adjust buff for all these changes */
182                         len += script->patlen;
183                         bytes -= len;
184                         buff += len;
185                     }
186                     if (have_match && script->flatten && !force_quick) {
187                         /* XXX: we should check for AP_MAX_BUCKETS here and
188                          * XXX: call ap_pass_brigade accordingly
189                          */
190                         char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
191                                                     buff, bytes, &len);
192                         tmp_b = apr_bucket_pool_create(copy, len, pool,
193                                                        f->r->connection->bucket_alloc);
194                         APR_BUCKET_INSERT_BEFORE(b, tmp_b);
195                         apr_bucket_delete(b);
196                         b = tmp_b;
197                     }
198                 }
199                 else if (script->regexp) {
200                     int left = bytes;
201                     const char *pos = buff;
202                     char *repl;
203                     apr_size_t space_left = AP_SUBST_MAX_LINE_LENGTH;
204                     while (!ap_regexec_len(script->regexp, pos, left,
205                                        AP_MAX_REG_MATCH, regm, 0)) {
206                         apr_status_t rv;
207                         have_match = 1;
208                         if (script->flatten && !force_quick) {
209                             /* copy bytes before the match */
210                             if (regm[0].rm_so > 0)
211                                 ap_varbuf_strmemcat(&vb, pos, regm[0].rm_so);
212                             /* add replacement string */
213                             rv = ap_varbuf_regsub(&vb, script->replacement, pos,
214                                                   AP_MAX_REG_MATCH, regm,
215                                                   AP_SUBST_MAX_LINE_LENGTH - vb.strlen);
216                             if (rv != APR_SUCCESS)
217                                 return rv;
218                         }
219                         else {
220                             apr_size_t repl_len;
221                             rv = ap_pregsub_ex(pool, &repl,
222                                                script->replacement, pos,
223                                                AP_MAX_REG_MATCH, regm,
224                                                space_left);
225                             if (rv != APR_SUCCESS)
226                                 return rv;
227                             len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
228                             repl_len = strlen(repl);
229                             space_left -= len + repl_len;
230                             SEDRMPATBCKT(b, regm[0].rm_so, tmp_b, len);
231                             tmp_b = apr_bucket_transient_create(repl, repl_len,
232                                                 f->r->connection->bucket_alloc);
233                             APR_BUCKET_INSERT_BEFORE(b, tmp_b);
234                         }
235                         /*
236                          * reset to past what we just did. pos now maps to b
237                          * again
238                          */
239                         pos += regm[0].rm_eo;
240                         left -= regm[0].rm_eo;
241                     }
242                     if (have_match && script->flatten && !force_quick) {
243                         char *copy;
244                         /* Copy result plus the part after the last match into
245                          * a bucket.
246                          */
247                         copy = ap_varbuf_pdup(pool, &vb, NULL, 0, pos, left,
248                                               &len);
249                         tmp_b = apr_bucket_pool_create(copy, len, pool,
250                                            f->r->connection->bucket_alloc);
251                         APR_BUCKET_INSERT_BEFORE(b, tmp_b);
252                         apr_bucket_delete(b);
253                         b = tmp_b;
254                     }
255                 }
256                 else {
257                     ap_assert(0);
258                     continue;
259                 }
260             }
261         }
262         script++;
263     }
264     ap_varbuf_free(&vb);
265     return APR_SUCCESS;
266 }
267
268 static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
269 {
270     apr_size_t bytes;
271     apr_size_t len;
272     apr_size_t fbytes;
273     const char *buff;
274     const char *nl = NULL;
275     char *bflat;
276     apr_bucket *b;
277     apr_bucket *tmp_b;
278     apr_bucket_brigade *tmp_bb = NULL;
279     apr_status_t rv;
280
281     substitute_module_ctx *ctx = f->ctx;
282
283     /*
284      * First time around? Create the saved bb that we used for each pass
285      * through. Note that we can also get here when we explicitly clear ctx,
286      * for error handling
287      */
288     if (!ctx) {
289         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
290         /*
291          * Create all the temporary brigades we need and reuse them to avoid
292          * creating them over and over again from r->pool which would cost a
293          * lot of memory in some cases.
294          */
295         ctx->linebb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
296         ctx->linesbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
297         ctx->pattbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
298         /*
299          * Everything to be passed to the next filter goes in
300          * here, our pass brigade.
301          */
302         ctx->passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
303         /* Create our temporary pool only once */
304         apr_pool_create(&(ctx->tpool), f->r->pool);
305         apr_table_unset(f->r->headers_out, "Content-Length");
306     }
307
308     /*
309      * Shortcircuit processing
310      */
311     if (APR_BRIGADE_EMPTY(bb))
312         return APR_SUCCESS;
313
314     /*
315      * Here's the concept:
316      *  Read in the data and look for newlines. Once we
317      *  find a full "line", add it to our working brigade.
318      *  If we've finished reading the brigade and we have
319      *  any left over data (not a "full" line), store that
320      *  for the next pass.
321      *
322      * Note: anything stored in ctx->linebb for sure does not have
323      * a newline char, so we don't concat that bb with the
324      * new bb, since we would spending time searching for the newline
325      * in data we know it doesn't exist. So instead, we simply scan
326      * our current bb and, if we see a newline, prepend ctx->linebb
327      * to the front of it. This makes the code much less straight-
328      * forward (otherwise we could APR_BRIGADE_CONCAT(ctx->linebb, bb)
329      * and just scan for newlines and not bother with needing to know
330      * when ctx->linebb needs to be reset) but also faster. We'll take
331      * the speed.
332      *
333      * Note: apr_brigade_split_line would be nice here, but we
334      * really can't use it since we need more control and we want
335      * to re-use already read bucket data.
336      *
337      * See mod_include if still confused :)
338      */
339
340     while ((b = APR_BRIGADE_FIRST(bb)) && (b != APR_BRIGADE_SENTINEL(bb))) {
341         if (APR_BUCKET_IS_EOS(b)) {
342             /*
343              * if we see the EOS, then we need to pass along everything we
344              * have. But if the ctx->linebb isn't empty, then we need to add
345              * that to the end of what we'll be passing.
346              */
347             if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
348                 rv = apr_brigade_pflatten(ctx->linebb, &bflat,
349                                           &fbytes, ctx->tpool);
350                 if (rv != APR_SUCCESS)
351                     goto err;
352                 if (fbytes > AP_SUBST_MAX_LINE_LENGTH) {
353                     rv = APR_ENOMEM;
354                     goto err;
355                 }
356                 tmp_b = apr_bucket_transient_create(bflat, fbytes,
357                                                 f->r->connection->bucket_alloc);
358                 rv = do_pattmatch(f, tmp_b, ctx->pattbb, ctx->tpool);
359                 if (rv != APR_SUCCESS)
360                     goto err;
361                 APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
362             }
363             apr_brigade_cleanup(ctx->linebb);
364             APR_BUCKET_REMOVE(b);
365             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
366         }
367         /*
368          * No need to handle FLUSH buckets separately as we call
369          * ap_pass_brigade anyway at the end of the loop.
370          */
371         else if (APR_BUCKET_IS_METADATA(b)) {
372             APR_BUCKET_REMOVE(b);
373             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
374         }
375         else {
376             /*
377              * We have actual "data" so read in as much as we can and start
378              * scanning and splitting from our read buffer
379              */
380             rv = apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ);
381             if (rv != APR_SUCCESS || bytes == 0) {
382                 apr_bucket_delete(b);
383             }
384             else {
385                 int num = 0;
386                 while (bytes > 0) {
387                     nl = memchr(buff, APR_ASCII_LF, bytes);
388                     if (nl) {
389                         len = (apr_size_t) (nl - buff) + 1;
390                         /* split *after* the newline */
391                         apr_bucket_split(b, len);
392                         /*
393                          * We've likely read more data, so bypass rereading
394                          * bucket data and continue scanning through this
395                          * buffer
396                          */
397                         bytes -= len;
398                         buff += len;
399                         /*
400                          * we need b to be updated for future potential
401                          * splitting
402                          */
403                         tmp_b = APR_BUCKET_NEXT(b);
404                         APR_BUCKET_REMOVE(b);
405                         /*
406                          * Hey, we found a newline! Don't forget the old
407                          * stuff that needs to be added to the front. So we
408                          * add the split bucket to the end, flatten the whole
409                          * bb, morph the whole shebang into a bucket which is
410                          * then added to the tail of the newline bb.
411                          */
412                         if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
413                             APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
414                             rv = apr_brigade_pflatten(ctx->linebb, &bflat,
415                                                       &fbytes, ctx->tpool);
416                             if (rv != APR_SUCCESS)
417                                 goto err;
418                             if (fbytes > AP_SUBST_MAX_LINE_LENGTH) {
419                                 /* Avoid pflattening further lines, we will
420                                  * abort later on anyway.
421                                  */
422                                 rv = APR_ENOMEM;
423                                 goto err;
424                             }
425                             b = apr_bucket_transient_create(bflat, fbytes,
426                                             f->r->connection->bucket_alloc);
427                             apr_brigade_cleanup(ctx->linebb);
428                         }
429                         rv = do_pattmatch(f, b, ctx->pattbb, ctx->tpool);
430                         if (rv != APR_SUCCESS)
431                             goto err;
432                         /*
433                          * Count how many buckets we have in ctx->passbb
434                          * so far. Yes, this is correct we count ctx->passbb
435                          * and not ctx->pattbb as we do not reset num on every
436                          * iteration.
437                          */
438                         for (b = APR_BRIGADE_FIRST(ctx->pattbb);
439                              b != APR_BRIGADE_SENTINEL(ctx->pattbb);
440                              b = APR_BUCKET_NEXT(b)) {
441                             num++;
442                         }
443                         APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
444                         /*
445                          * If the number of buckets in ctx->passbb reaches an
446                          * "insane" level, we consume much memory for all the
447                          * buckets as such. So lets flush them down the chain
448                          * in this case and thus clear ctx->passbb. This frees
449                          * the buckets memory for further processing.
450                          * Usually this condition should not become true, but
451                          * it is a safety measure for edge cases.
452                          */
453                         if (num > AP_MAX_BUCKETS) {
454                             b = apr_bucket_flush_create(
455                                                 f->r->connection->bucket_alloc);
456                             APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
457                             rv = ap_pass_brigade(f->next, ctx->passbb);
458                             apr_brigade_cleanup(ctx->passbb);
459                             num = 0;
460                             apr_pool_clear(ctx->tpool);
461                             if (rv != APR_SUCCESS)
462                                 goto err;
463                         }
464                         b = tmp_b;
465                     }
466                     else {
467                         /*
468                          * no newline in whatever is left of this buffer so
469                          * tuck data away and get next bucket
470                          */
471                         APR_BUCKET_REMOVE(b);
472                         APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
473                         bytes = 0;
474                     }
475                 }
476             }
477         }
478         if (!APR_BRIGADE_EMPTY(ctx->passbb)) {
479             rv = ap_pass_brigade(f->next, ctx->passbb);
480             apr_brigade_cleanup(ctx->passbb);
481             if (rv != APR_SUCCESS)
482                 goto err;
483         }
484         apr_pool_clear(ctx->tpool);
485     }
486
487     /* Anything left we want to save/setaside for the next go-around */
488     if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
489         /*
490          * Provide ap_save_brigade with an existing empty brigade
491          * (ctx->linesbb) to avoid creating a new one.
492          */
493         ap_save_brigade(f, &(ctx->linesbb), &(ctx->linebb), f->r->pool);
494         tmp_bb = ctx->linebb;
495         ctx->linebb = ctx->linesbb;
496         ctx->linesbb = tmp_bb;
497     }
498
499     return APR_SUCCESS;
500 err:
501     if (rv == APR_ENOMEM)
502         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Line too long, URI %s",
503                       f->r->uri);
504     apr_pool_clear(ctx->tpool);
505     return rv;
506 }
507
508 static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
509 {
510     char *from = NULL;
511     char *to = NULL;
512     char *flags = NULL;
513     char *ourline;
514     char delim;
515     subst_pattern_t *nscript;
516     int is_pattern = 0;
517     int ignore_case = 0;
518     int flatten = 1;
519     ap_regex_t *r = NULL;
520
521     if (apr_tolower(*line) != 's') {
522         return "Bad Substitute format, must be an s/// pattern";
523     }
524     ourline = apr_pstrdup(cmd->pool, line);
525     delim = *++ourline;
526     if (delim)
527         from = ++ourline;
528     if (from) {
529         if (*ourline != delim) {
530             while (*++ourline && *ourline != delim);
531         }
532         if (*ourline) {
533             *ourline = '\0';
534             to = ++ourline;
535         }
536     }
537     if (to) {
538         if (*ourline != delim) {
539             while (*++ourline && *ourline != delim);
540         }
541         if (*ourline) {
542             *ourline = '\0';
543             flags = ++ourline;
544         }
545     }
546
547     if (!delim || !from || !*from || !to) {
548         return "Bad Substitute format, must be a complete s/// pattern";
549     }
550
551     if (flags) {
552         while (*flags) {
553             delim = apr_tolower(*flags);    /* re-use */
554             if (delim == 'i')
555                 ignore_case = 1;
556             else if (delim == 'n')
557                 is_pattern = 1;
558             else if (delim == 'f')
559                 flatten = 1;
560             else if (delim == 'q')
561                 flatten = 0;
562             else
563                 return "Bad Substitute flag, only s///[infq] are supported";
564             flags++;
565         }
566     }
567
568     /* first see if we can compile the regex */
569     if (!is_pattern) {
570         r = ap_pregcomp(cmd->pool, from, AP_REG_EXTENDED |
571                         (ignore_case ? AP_REG_ICASE : 0));
572         if (!r)
573             return "Substitute could not compile regex";
574     }
575     nscript = apr_array_push(((subst_dir_conf *) cfg)->patterns);
576     /* init the new entries */
577     nscript->pattern = NULL;
578     nscript->regexp = NULL;
579     nscript->replacement = NULL;
580     nscript->patlen = 0;
581
582     if (is_pattern) {
583         nscript->patlen = strlen(from);
584         nscript->pattern = apr_strmatch_precompile(cmd->pool, from,
585                                                    !ignore_case);
586     }
587     else {
588         nscript->regexp = r;
589     }
590
591     nscript->replacement = to;
592     nscript->replen = strlen(to);
593     nscript->flatten = flatten;
594
595     return NULL;
596 }
597
598 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
599 static void register_hooks(apr_pool_t *pool)
600 {
601     ap_register_output_filter(substitute_filter_name, substitute_filter,
602                               NULL, AP_FTYPE_RESOURCE);
603 }
604
605 static const command_rec substitute_cmds[] = {
606     AP_INIT_TAKE1("Substitute", set_pattern, NULL, OR_ALL,
607                   "Pattern to filter the response content (s/foo/bar/[inf])"),
608     {NULL}
609 };
610
611 AP_DECLARE_MODULE(substitute) = {
612     STANDARD20_MODULE_STUFF,
613     create_substitute_dcfg,     /* dir config creater */
614     merge_substitute_dcfg,      /* dir merger --- default is to override */
615     NULL,                       /* server config */
616     NULL,                       /* merge server config */
617     substitute_cmds,            /* command table */
618     register_hooks              /* register hooks */
619 };