1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 /* stream-editor: apply string and/or regexp search-and-replace to
20  * HTTP request and response bodies.
21  *
22  * Load from plugin.config, with one or more filenames as args.
23  * These are config files, and all config files are equal.
24  *
25  * Each line in a config file and conforming to config syntax specifies a
26  * rule for rewriting input or output.
27  *
28  * A line starting with [out] is an output rule.
29  * One starting with [in] is an input rule.
30  * Any other line is ignored, so blank lines and comments are fine.
31  *
32  * Each line must have a from: field and a to: field specifying what it
33  * rewrites from and to.  Other fields are optional.  The full list:
34  *    from:flags:value
35  *    to:value
36  *    scope:flags:value
37  *    prio:value
38  *    len:value
39  *
40  *   Fields are separated by whitespace.  from: and to: fields may contain
41  *   whitespace if they are quoted.  Quoting may use any non-alphanumeric
42  *   matched-pair delimiter, though the delimiter may not then appear
43  *   (even escaped) within the value string.
44  *
45  *   Flags are:
46  *      i - case-independent matching
47  *      r - regexp match
48  *      u (applies only to scope) - apply scope match to full URI
49  *         starting with "http://" (the default is to match the path
50  *         only, as in for example a <Location> in HTTPD).
51  *
52  *   A from: value is a string or a regexp, according to flags.
53  *   A to: string is a replacement, and may reference regexp memory $1 - $9.
54  *
55  *   A scope: value is likewise a string or (memory-less) regexp and
56  *   determines the scope of URLs over which the rule applies.
57  *
58  *   A prio: value is a single digit, and determines the priority of the
59  *   rule.  That is to say, two or more rules generate overlapping matches,
60  *   the priority value will determine which rule prevails.  A lower
61  *   priority value prevails over a higher one.
62  *
63  *   A len: value is an integer, and applies only to a regexp from:
64  *   It should be an estimate of the largest match size expected from
65  *   the from: pattern.  It is used internally to determine the size of
66  *   a continuity buffer, that avoids missing a match that spans more
67  *   than one incoming data chunk arriving at the stream-editor filter.
68  *   The default is 20.
69  *
70  *   Performance tips:
71  *    - A high len: value on any rule can severely impact on performance,
72  *      especially if mixed with short matches that match frequently.
73  *    - Specify high-precedence rules (low prio: values) first in your
74  *      configuration to avoid reshuffling edits while processing data.
75  *
76  *  Example: a trivial ruleset to escape HTML entities:
77  *   [out] scope::/html-escape/ from::"&" to:"&amp;"
78  *   [out] scope::/html-escape/ from::< to:&lt;
79  *   [out] scope::/html-escape/ from::> to:&gt;
80  *   [out] scope::/html-escape/ from::/"/ to:/&quot;/
81  *   Note, the first & has to be quoted, as the two ampersands in the line
82  *   would otherwise be mis-parsed as a matching pair of delimiters.
83  *   Quoting the &amp;, and the " line with //, are optional (and quoting
84  *   is not applicable to the scope: field).
85  *   The double-colons delimit flags, of which none are used in this example.
86  */
87 #define MAX_CONFIG_LINE 1024
88 #define MAX_RX_MATCH 10
89 #define WHITESPACE " \t\r\n"
90 
91 #include <cstdint>
92 
93 #include <vector>
94 #include <set>
95 #include <regex.h>
96 #include <cctype>
97 #include <cassert>
98 #include <cstring>
99 #include <string>
100 #include <cstdio>
101 #include <stdexcept>
102 #include "ts/ts.h"
103 
104 struct edit_t;
105 using editset_t = std::set<edit_t>;
106 using edit_p    = editset_t::const_iterator;
107 
108 struct edit_t {
109   const size_t start;
110   const size_t bytes;
111   const std::string repl;
112   const int priority;
edit_tedit_t113   edit_t(size_t s, size_t b, const std::string &r, int p) : start(s), bytes(b), repl(r), priority(p) { ; }
114   bool
operator !=edit_t115   operator!=(const edit_t &x) const
116   {
117     return start != x.start || bytes != x.bytes || repl != x.repl || priority != x.priority;
118   }
119 
120   bool
operator <edit_t121   operator<(const edit_t &x) const
122   {
123     if ((start == x.start) || (start < x.start && start + bytes > x.start) || (x.start < start && x.start + x.bytes > start)) {
124       /* conflicting edits.  Throw back to resolve conflict */
125       /* Problem: we get called from erase() within conflict resolution,
126        * and comparing to ourself then re-throws.
127        * Need to exclude that case.
128        */
129       if (*this != x) {
130         throw x;
131       }
132     }
133     return start < x.start;
134   }
135 
136   bool
savetoedit_t137   saveto(editset_t &edits) const
138   {
139     /* loop to try until inserted or we lose a conflict */
140     for (;;) {
141       try {
142         edits.insert(*this);
143         return true;
144       } catch (const edit_t &conflicted) {
145         TSDebug("stream-editor", "Conflicting edits [%ld-%ld] vs [%ld-%ld]", start, start + bytes, conflicted.start,
146                 conflicted.start + conflicted.bytes);
147         if (priority < conflicted.priority) {
148           /* we win conflict and oust our enemy */
149           edits.erase(conflicted);
150         } else {
151           /* we lose the conflict - give up */
152           return false;
153         }
154       }
155     }
156   }
157 };
158 
159 class scope_t
160 {
161   virtual bool match(const char *) const = 0;
162   const bool uri;
163 
164 public:
165   bool
in_scope(TSHttpTxn tx) const166   in_scope(TSHttpTxn tx) const
167   {
168     /* Get the URL from tx, and feed it to match() */
169     bool ret = false;
170     TSMBuffer bufp;
171     TSMLoc offset;
172     int length;
173     TSReturnCode rc = TSHttpTxnPristineUrlGet(tx, &bufp, &offset);
174     if (rc != TS_SUCCESS) {
175       TSError("Error getting URL of current Txn");
176       return ret;
177     }
178     char *url = TSUrlStringGet(bufp, offset, &length);
179 
180     if (!strncasecmp(url, "https://", 8)) {
181       /* No use trying to edit https data */
182       ret = false;
183     } else {
184       char *p = url;
185       if (uri) {
186         /* match against path component, discard earlier components */
187         if (!strncasecmp(url, "http://", 7)) {
188           p += 7;
189           while (*p != '/') {
190             assert(*p != '\0');
191             ++p;
192           }
193         }
194       }
195       ret = match(p);
196     }
197     TSfree(url);
198     TSHandleMLocRelease(bufp, TS_NULL_MLOC, offset);
199     // TSMBufferDestroy(bufp);
200     return ret;
201   }
202 
scope_t(const bool u)203   scope_t(const bool u) : uri(u) { ; }
204   virtual ~scope_t() = default;
205 };
206 
207 class rxscope : public scope_t
208 {
209 private:
210   regex_t rx;
211   bool
212 
match(const char * str) const213   match(const char *str) const override
214   {
215     return (regexec(&rx, str, 0, nullptr, 0) == 0) ? true : false;
216   }
217 
218 public:
rxscope(const bool u,const bool i,const char * pattern,int len)219   rxscope(const bool u, const bool i, const char *pattern, int len) : scope_t(u)
220   {
221     int flags = REG_NOSUB | REG_EXTENDED | (i ? REG_ICASE : 0);
222     char *str = TSstrndup(pattern, len);
223     int error = regcomp(&rx, str, flags);
224     if (error) {
225       TSError("stream-editor: can't compile regexp [%s]", str);
226       TSfree(str);
227       throw std::runtime_error("stream editor: Error compiling regex, regcomp in rxscope");
228     }
229     TSfree(str);
230   }
231 
~rxscope()232   ~rxscope() override { regfree(&rx); }
233 };
234 
235 class strscope : public scope_t
236 {
237 private:
238   const bool icase;
239   char *str;
240   bool
241 
match(const char * p) const242   match(const char *p) const override
243   {
244     return ((icase ? strncasecmp : strncmp)(str, p, strlen(str)) == 0) ? true : false;
245   }
246 
247 public:
strscope(const bool u,const bool i,const char * pattern,int len)248   strscope(const bool u, const bool i, const char *pattern, int len) : scope_t(u), icase(i) { str = TSstrndup(pattern, len); }
~strscope()249   ~strscope() override
250   {
251     if (str) {
252       TSfree(str);
253     }
254   }
255 };
256 
257 class match_t
258 {
259 public:
260   virtual bool find(const char *, size_t, size_t &, size_t &, const char *, std::string &) const = 0;
261   virtual size_t cont_size() const                                                               = 0;
262   virtual ~match_t()                                                                             = default;
263 };
264 
265 class strmatch : public match_t
266 {
267   const bool icase;
268   char *str;
269   const size_t slen;
270 
271 public:
272   bool
find(const char * buf,size_t len,size_t & found,size_t & found_len,const char * to,std::string & repl) const273   find(const char *buf, size_t len, size_t &found, size_t &found_len, const char *to, std::string &repl) const override
274   {
275     const char *match = icase ? strcasestr(buf, str) : strstr(buf, str);
276     if (match) {
277       found     = match - buf;
278       found_len = slen;
279       repl      = to;
280       return (found + slen > len) ? false : true;
281     } else {
282       return false;
283     }
284   }
285 
strmatch(const bool i,const char * pattern,int len)286   strmatch(const bool i, const char *pattern, int len) : icase(i), slen(len) { str = TSstrndup(pattern, len); }
~strmatch()287   ~strmatch() override
288   {
289     if (str) {
290       TSfree(str);
291     }
292   }
293 
294   size_t
cont_size() const295   cont_size() const override
296   {
297     return slen;
298   }
299 };
300 
301 class rxmatch : public match_t
302 {
303   size_t match_len;
304   regex_t rx;
305 
306 public:
307   bool
find(const char * buf,size_t len,size_t & found,size_t & found_len,const char * tmpl,std::string & repl) const308   find(const char *buf, size_t len, size_t &found, size_t &found_len, const char *tmpl, std::string &repl) const override
309   {
310     regmatch_t pmatch[MAX_RX_MATCH];
311     if (regexec(&rx, buf, MAX_RX_MATCH, pmatch, REG_NOTEOL) == 0) {
312       char c;
313       int n;
314       found     = pmatch[0].rm_so;
315       found_len = pmatch[0].rm_eo - found;
316       while (c = *tmpl++, c != '\0') {
317         switch (c) {
318         case '\\':
319           if (*tmpl != '\0') {
320             repl.push_back(*tmpl++);
321           }
322           break;
323         case '$':
324           if (isdigit(*tmpl)) {
325             n = *tmpl - '0';
326           } else {
327             n = MAX_RX_MATCH;
328           }
329           if (n > 0 && n < MAX_RX_MATCH) {
330             repl.append(buf + pmatch[n].rm_so, pmatch[n].rm_eo - pmatch[n].rm_so);
331             tmpl++; /* we've consumed one more character */
332           } else {
333             repl.push_back(c);
334           }
335           break;
336         default:
337           repl.push_back(c);
338           break;
339         }
340       }
341       return true;
342     } else {
343       return false;
344     }
345   }
346 
347   size_t
cont_size() const348   cont_size() const override
349   {
350     return match_len;
351   }
352 
rxmatch(bool i,const char * pattern,size_t sz,size_t match_max)353   rxmatch(bool i, const char *pattern, size_t sz, size_t match_max) : match_len(match_max)
354   {
355     char *str = TSstrndup(pattern, sz);
356     int flags = REG_EXTENDED | (i ? REG_ICASE : 0);
357     int error = regcomp(&rx, str, flags);
358     if (error) {
359       TSError("stream-editor: can't compile regexp [%s]", str);
360       TSfree(str);
361       throw std::runtime_error("stream editor: Error compiling regex, regcomp in rxmatch");
362     }
363     TSfree(str);
364   }
365 
~rxmatch()366   ~rxmatch() override { regfree(&rx); }
367 };
368 
369 #define PARSE_VERIFY(line, x, str) \
370   while (x)                        \
371     if (!isspace(*(x - 1)))        \
372       x = strcasestr(x + 1, str);  \
373     else                           \
374       break
375 
376 class rule_t
377 {
378 private:
379   scope_t *scope;
380   unsigned int priority;
381   match_t *from;
382   char *to;
383   int *refcount;
384 
385 public:
rule_t(const char * line)386   rule_t(const char *line) : scope(nullptr), priority(5), from(nullptr), to(nullptr), refcount(nullptr)
387   {
388     const char *scope_spec = strcasestr(line, "scope:");
389     const char *from_spec  = strcasestr(line, "from:");
390     const char *to_spec    = strcasestr(line, "to:");
391     const char *prio_spec  = strcasestr(line, "prio:");
392     const char *len_spec   = strcasestr(line, "len:");
393     bool icase             = false;
394     bool rx                = false;
395     bool uri;
396     size_t len, match_len;
397     char delim;
398 
399     PARSE_VERIFY(line, scope_spec, "scope:");
400     PARSE_VERIFY(line, from_spec, "from:");
401     PARSE_VERIFY(line, to_spec, "to:");
402     PARSE_VERIFY(line, prio_spec, "prio:");
403     PARSE_VERIFY(line, len_spec, "len:");
404 
405     if (!from_spec || !to_spec) {
406       throw "Incomplete stream edit spec";
407     }
408 
409     if (len_spec) {
410       match_len = 0;
411       len_spec += 4;
412       while (isdigit(*len_spec)) {
413         match_len = 10 * match_len + (*len_spec++ - '0');
414       }
415     } else {
416       match_len = 20; // default
417     }
418 
419     /* parse From: now, as failure could abort constructor */
420     for (from_spec += 5; *from_spec != ':'; ++from_spec) {
421       switch (*from_spec) {
422       case 'i':
423         icase = true;
424         break;
425       case 'r':
426         rx = true;
427         break;
428       }
429     }
430     delim = *++from_spec;
431     if (isalnum(delim)) {
432       len = strcspn(from_spec, WHITESPACE);
433     } else {
434       const char *end = strchr(++from_spec, delim);
435       if (end) {
436         len = end - from_spec;
437       } else {
438         /* it wasn't a delimiter after all */
439         len = strcspn(--from_spec, WHITESPACE);
440       }
441     }
442     if (rx) {
443       from = new rxmatch(icase, from_spec, len, match_len);
444     } else {
445       from = new strmatch(icase, from_spec, len);
446     }
447 
448     if (scope_spec) {
449       icase = false;
450       rx    = false;
451       uri   = true;
452       for (scope_spec += 6; *scope_spec != ':'; ++scope_spec) {
453         switch (*scope_spec) {
454         case 'i':
455           icase = true;
456           break;
457         case 'r':
458           rx = true;
459           break;
460         case 'u':
461           uri = false;
462           break;
463         }
464       }
465       ++scope_spec;
466       len = strcspn(scope_spec, WHITESPACE);
467       if (rx) {
468         scope = new rxscope(uri, icase, scope_spec, len);
469       } else {
470         scope = new strscope(uri, icase, scope_spec, len);
471       }
472     }
473 
474     if (prio_spec) {
475       prio_spec += 5;
476       if (isdigit(*prio_spec)) {
477         priority = *prio_spec - '0';
478       }
479     }
480 
481     to_spec += 3;
482     delim = *to_spec;
483     if (isalnum(delim)) {
484       len = strcspn(to_spec, WHITESPACE);
485     } else {
486       const char *end = strchr(++to_spec, delim);
487       if (end) {
488         len = end - to_spec;
489       } else {
490         /* it wasn't a delimiter after all */
491         len = strcspn(--to_spec, WHITESPACE);
492       }
493     }
494     to = TSstrndup(to_spec, len);
495 
496     refcount = new int(1);
497   }
498 
rule_t(const rule_t & r)499   rule_t(const rule_t &r) : scope(r.scope), priority(r.priority), from(r.from), to(r.to), refcount(r.refcount) { ++*refcount; }
~rule_t()500   ~rule_t()
501   {
502     if (refcount) {
503       if (!--*refcount) {
504         if (scope) {
505           delete scope;
506         }
507         if (from) {
508           delete from;
509         }
510         if (to) {
511           TSfree(to);
512         }
513         delete refcount;
514       }
515     }
516   }
517 
518   bool
in_scope(TSHttpTxn tx) const519   in_scope(TSHttpTxn tx) const
520   {
521     /* if no scope was specified then everything is in-scope */
522     return scope ? scope->in_scope(tx) : true;
523   }
524 
525   size_t
cont_size() const526   cont_size() const
527   {
528     return from->cont_size();
529   }
530 
531   void
apply(const char * buf,size_t len,editset_t & edits) const532   apply(const char *buf, size_t len, editset_t &edits) const
533   {
534     /* find matches in the buf, and add match+replace to edits */
535 
536     size_t found;
537     size_t found_len;
538     size_t offs = 0;
539     while (offs < len) {
540       std::string repl;
541       if (from->find(buf + offs, len - offs, found, found_len, to, repl)) {
542         found += offs;
543         edit_t(found, found_len, repl, priority).saveto(edits);
544         offs = found + found_len;
545       } else {
546         break;
547       }
548     }
549   }
550 };
551 using ruleset_t = std::vector<rule_t>;
552 using rule_p    = ruleset_t::const_iterator;
553 
554 typedef struct contdata_t {
555   TSCont cont             = nullptr;
556   TSIOBuffer out_buf      = nullptr;
557   TSIOBufferReader out_rd = nullptr;
558   TSVIO out_vio           = nullptr;
559   ruleset_t rules;
560   std::string contbuf;
561   size_t contbuf_sz = 0;
562   int64_t bytes_in  = 0;
563   int64_t bytes_out = 0;
564   /* Use new/delete so destructor does cleanup for us */
565   contdata_t() = default;
~contdata_tcontdata_t566   ~contdata_t()
567   {
568     if (out_rd) {
569       TSIOBufferReaderFree(out_rd);
570     }
571     if (out_buf) {
572       TSIOBufferDestroy(out_buf);
573     }
574     if (cont) {
575       TSContDestroy(cont);
576     }
577   }
578   void
set_cont_sizecontdata_t579   set_cont_size(size_t sz)
580   {
581     if (contbuf_sz < 2 * sz) {
582       contbuf_sz = 2 * sz - 1;
583     }
584   }
585 } contdata_t;
586 
587 static int64_t
process_block(contdata_t * contdata,TSIOBufferReader reader)588 process_block(contdata_t *contdata, TSIOBufferReader reader)
589 {
590   int64_t nbytes, start;
591   size_t n = 0;
592   size_t buflen;
593   size_t keep;
594   const char *buf;
595   TSIOBufferBlock block;
596 
597   if (reader == nullptr) { // We're just flushing anything we have buffered
598     keep   = 0;
599     buf    = contdata->contbuf.c_str();
600     buflen = contdata->contbuf.length();
601     nbytes = 0;
602   } else {
603     block = TSIOBufferReaderStart(reader);
604     buf   = TSIOBufferBlockReadStart(block, reader, &nbytes);
605 
606     if (contdata->contbuf.empty()) {
607       /* Use the data as-is */
608       buflen = nbytes;
609     } else {
610       contdata->contbuf.append(buf, nbytes);
611       buf    = contdata->contbuf.c_str();
612       buflen = contdata->contbuf.length();
613     }
614     keep = contdata->contbuf_sz;
615   }
616   size_t bytes_read = 0;
617 
618   editset_t edits;
619 
620   for (rule_p r = contdata->rules.begin(); r != contdata->rules.end(); ++r) {
621     r->apply(buf, buflen, edits);
622   }
623 
624   for (edit_p p = edits.begin(); p != edits.end(); ++p) {
625     /* Preserve continuity buffer */
626     if (p->start >= buflen - keep) {
627       break;
628     }
629 
630     /* pass through bytes before edit */
631     start = p->start - bytes_read;
632 
633     while (start > 0) {
634       // FIXME: would this be quicker if we managed a TSIOBuffer
635       //        so we could use TSIOBufferCopy ?
636       n = TSIOBufferWrite(contdata->out_buf, buf + bytes_read, start);
637       assert(n > 0); // FIXME - handle error
638       bytes_read += n;
639       contdata->bytes_out += n;
640       start -= n;
641     }
642 
643     /* omit deleted bytes */
644     bytes_read += p->bytes;
645 
646     /* insert replacement bytes */
647     n = TSIOBufferWrite(contdata->out_buf, p->repl.c_str(), p->repl.length());
648     assert(n == p->repl.length()); // FIXME (if this ever happens)!
649     contdata->bytes_out += n;
650 
651     /* increment counts  - done */
652   }
653   contdata->bytes_in += bytes_read;
654 
655   /* data after the last edit */
656   if (bytes_read < buflen - keep) {
657     n = TSIOBufferWrite(contdata->out_buf, buf + bytes_read, buflen - bytes_read - keep);
658     contdata->bytes_in += n;
659     contdata->bytes_out += n;
660     bytes_read += n;
661   }
662   /* reset buf to what we've not processed */
663   contdata->contbuf = buf + bytes_read;
664 
665   return nbytes;
666 }
667 static void
streamedit_process(TSCont contp)668 streamedit_process(TSCont contp)
669 {
670   // Read the data available to us
671   // Concatenate with anything we have buffered
672   // Loop over rules, and apply them to build our edit set
673   // Loop over edits, and apply them to the stream
674   // Retain buffered data at the end
675   int64_t ntodo, nbytes;
676   contdata_t *contdata      = static_cast<contdata_t *>(TSContDataGet(contp));
677   TSVIO input_vio           = TSVConnWriteVIOGet(contp);
678   TSIOBufferReader input_rd = TSVIOReaderGet(input_vio);
679 
680   if (contdata->out_buf == nullptr) {
681     contdata->out_buf = TSIOBufferCreate();
682     contdata->out_rd  = TSIOBufferReaderAlloc(contdata->out_buf);
683     contdata->out_vio = TSVConnWrite(TSTransformOutputVConnGet(contp), contp, contdata->out_rd, INT64_MAX);
684   }
685 
686   TSIOBuffer in_buf = TSVIOBufferGet(input_vio);
687   /* Test for EOS */
688   if (in_buf == nullptr) {
689     process_block(contdata, nullptr); // flush any buffered data
690     TSVIONBytesSet(contdata->out_vio, contdata->bytes_out);
691     TSVIOReenable(contdata->out_vio);
692     return;
693   }
694 
695   /* Test for EOS */
696   ntodo = TSVIONTodoGet(input_vio);
697   if (ntodo == 0) {
698     /* Call back the input VIO continuation to let it know that we
699      * have completed the write operation.
700      */
701     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
702     TSVIOReenable(contdata->out_vio);
703     return;
704   }
705 
706   /* now parse & process buffered data.  We can set some aside
707    * as a continuity buffer to deal with the problem of matches
708    * that span input chunks.
709    */
710   while (ntodo = TSIOBufferReaderAvail(input_rd), ntodo > 0) {
711     nbytes = process_block(contdata, input_rd);
712     TSIOBufferReaderConsume(input_rd, nbytes);
713     TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + nbytes);
714   }
715 
716   ntodo = TSVIONTodoGet(input_vio);
717   if (ntodo == 0) {
718     /* Call back the input VIO continuation to let it know that we
719      * have completed the write operation.
720      */
721     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
722   } else {
723     /* Call back the input VIO continuation to let it know that we
724      * are ready for more data.
725      */
726     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
727   }
728   TSVIOReenable(contdata->out_vio);
729 }
730 static int
streamedit_filter(TSCont contp,TSEvent event,void * edata)731 streamedit_filter(TSCont contp, TSEvent event, void *edata)
732 {
733   /* Our main function that does the work.
734    * Called as a continuation for filtering.
735    * *** if necessary, add call at TXN_CLOSE for cleanup.
736    */
737   TSVIO input_vio;
738 
739   if (TSVConnClosedGet(contp)) {
740     contdata_t *contdata = static_cast<contdata_t *>(TSContDataGet(contp));
741     delete contdata;
742     return TS_SUCCESS;
743   }
744 
745   switch (event) {
746   case TS_EVENT_ERROR:
747     input_vio = TSVConnWriteVIOGet(contp);
748     TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
749     break;
750   case TS_EVENT_VCONN_WRITE_COMPLETE:
751     TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
752     break;
753   default:
754     streamedit_process(contp);
755     break;
756   }
757   return TS_SUCCESS;
758 }
759 
760 static int
streamedit_setup(TSCont contp,TSEvent event,void * edata)761 streamedit_setup(TSCont contp, TSEvent event, void *edata)
762 {
763   TSHttpTxn txn        = static_cast<TSHttpTxn>(edata);
764   ruleset_t *rules_in  = static_cast<ruleset_t *>(TSContDataGet(contp));
765   contdata_t *contdata = nullptr;
766 
767   assert((event == TS_EVENT_HTTP_READ_RESPONSE_HDR) || (event == TS_EVENT_HTTP_READ_REQUEST_HDR));
768 
769   /* make a new list comprising those rules that are in scope */
770   for (rule_p r = rules_in->begin(); r != rules_in->end(); ++r) {
771     if (r->in_scope(txn)) {
772       if (contdata == nullptr) {
773         contdata = new contdata_t();
774       }
775       contdata->rules.push_back(*r);
776       contdata->set_cont_size(r->cont_size());
777     }
778   }
779 
780   if (contdata == nullptr) {
781     /* Nothing to do */
782     return TS_SUCCESS;
783   }
784 
785   /* we have a job to do, so insert filter */
786   contdata->cont = TSTransformCreate(streamedit_filter, txn);
787   TSContDataSet(contdata->cont, contdata);
788 
789   if (event == TS_EVENT_HTTP_READ_REQUEST_HDR) {
790     TSHttpTxnHookAdd(txn, TS_HTTP_REQUEST_TRANSFORM_HOOK, contdata->cont);
791   } else {
792     TSHttpTxnHookAdd(txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contdata->cont);
793   }
794 
795   TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
796 
797   return TS_SUCCESS;
798 }
799 
800 static void
read_conf(const char * filename,ruleset_t * & in,ruleset_t * & out)801 read_conf(const char *filename, ruleset_t *&in, ruleset_t *&out)
802 {
803   char buf[MAX_CONFIG_LINE];
804   FILE *file = fopen(filename, "r");
805 
806   if (file == nullptr) {
807     TSError("[stream-editor] Failed to open %s", filename);
808     return;
809   }
810   while (fgets(buf, MAX_CONFIG_LINE, file) != nullptr) {
811     try {
812       if (!strncasecmp(buf, "[in]", 4)) {
813         if (in == nullptr) {
814           in = new ruleset_t();
815         }
816         in->push_back(rule_t(buf));
817       } else if (!strncasecmp(buf, "[out]", 5)) {
818         if (out == nullptr) {
819           out = new ruleset_t();
820         }
821         out->push_back(rule_t(buf));
822       }
823     } catch (...) {
824       TSError("stream-editor: failed to parse rule %s", buf);
825     }
826   }
827   fclose(file);
828 }
829 
830 extern "C" void
TSPluginInit(int argc,const char * argv[])831 TSPluginInit(int argc, const char *argv[])
832 {
833   TSPluginRegistrationInfo info;
834   TSCont inputcont, outputcont;
835   ruleset_t *rewrites_in  = nullptr;
836   ruleset_t *rewrites_out = nullptr;
837 
838   info.plugin_name   = (char *)"stream-editor";
839   info.vendor_name   = (char *)"Apache Software Foundation";
840   info.support_email = (char *)"users@trafficserver.apache.org";
841 
842   if (TSPluginRegister(&info) != TS_SUCCESS) {
843     TSError("[stream-editor] Plugin registration failed");
844     return;
845   }
846 
847   /* Allow different config files */
848   while (--argc) {
849     read_conf(*++argv, rewrites_in, rewrites_out);
850   }
851 
852   if (rewrites_in != nullptr) {
853     TSDebug("[stream-editor]", "initializing input filtering");
854     inputcont = TSContCreate(streamedit_setup, nullptr);
855     if (inputcont == nullptr) {
856       TSError("[stream-editor] failed to initialize input filtering!");
857     } else {
858       TSContDataSet(inputcont, rewrites_in);
859       TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, inputcont);
860     }
861   } else {
862     TSDebug("[stream-editor]", "no input filter rules, skipping filter");
863   }
864 
865   if (rewrites_out != nullptr) {
866     TSDebug("[stream-editor]", "initializing output filtering");
867     outputcont = TSContCreate(streamedit_setup, nullptr);
868     if (outputcont == nullptr) {
869       TSError("[stream-editor] failed to initialize output filtering!");
870     } else {
871       TSContDataSet(outputcont, rewrites_out);
872       TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, outputcont);
873     }
874   } else {
875     TSDebug("[stream-editor]", "no output filter rules, skipping filter");
876   }
877 }
878