You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2015/07/04 22:54:30 UTC
[2/2] trafficserver git commit: TS-3739 Fix build errors. Fix
clang-format. Renamed to stream_editor
TS-3739 Fix build errors. Fix clang-format. Renamed to stream_editor
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/9a24e93c
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/9a24e93c
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/9a24e93c
Branch: refs/heads/master
Commit: 9a24e93c53d5c2b360392ad059328c89cfd8b56a
Parents: 3b85aad
Author: Leif Hedstrom <zw...@apache.org>
Authored: Sat Jul 4 14:54:03 2015 -0600
Committer: Leif Hedstrom <zw...@apache.org>
Committed: Sat Jul 4 14:54:03 2015 -0600
----------------------------------------------------------------------
configure.ac | 1 +
plugins/experimental/Makefile.am | 2 +-
plugins/experimental/stream-editor/Makefile.am | 22 -
.../stream-editor/stream-editor.c++ | 830 ------------------
plugins/experimental/stream_editor/Makefile.am | 21 +
.../experimental/stream_editor/stream_editor.cc | 864 +++++++++++++++++++
6 files changed, 887 insertions(+), 853 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index d673a28..d7e8015 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1924,6 +1924,7 @@ AS_IF([test "x$enable_experimental_plugins" = "xyes"], [
plugins/experimental/url_sig/Makefile
plugins/experimental/xdebug/Makefile
plugins/experimental/mp4/Makefile
+ plugins/experimental/stream_editor/Makefile
])])
AS_IF([test "x$enable_cppapi" = "xyes"], [
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index 75d9b25..f7e0e94 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -41,7 +41,7 @@ SUBDIRS = \
url_sig \
xdebug \
mp4 \
- stream-editor
+ stream_editor
if HAS_MYSQL
SUBDIRS += mysql_remap
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/plugins/experimental/stream-editor/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/stream-editor/Makefile.am b/plugins/experimental/stream-editor/Makefile.am
deleted file mode 100644
index 040c525..0000000
--- a/plugins/experimental/stream-editor/Makefile.am
+++ /dev/null
@@ -1,22 +0,0 @@
-# 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
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# 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.
-
-include $(top_srcdir)/build/plugins.mk
-
-pkglib_LTLIBRARIES = stream-editor.la
-epic_la_SOURCES = stream-editor.cc
-epic_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
-
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/plugins/experimental/stream-editor/stream-editor.c++
----------------------------------------------------------------------
diff --git a/plugins/experimental/stream-editor/stream-editor.c++ b/plugins/experimental/stream-editor/stream-editor.c++
deleted file mode 100644
index 53d40d0..0000000
--- a/plugins/experimental/stream-editor/stream-editor.c++
+++ /dev/null
@@ -1,830 +0,0 @@
-/*
- * 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * 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.
- */
-
-/* stream-editor: apply string and/or regexp search-and-replace to
- * HTTP request and response bodies.
- *
- * Load from plugin.config, with one or more filenames as args.
- * These are config files, and all config files are equal.
- *
- * Each line in a config file and conforming to config syntax specifies a
- * rule for rewriting input or output.
- *
- * A line starting with [out] is an output rule.
- * One starting with [in] is an input rule.
- * Any other line is ignored, so blank lines and comments are fine.
- *
- * Each line must have a from: field and a to: field specifying what it
- * rewrites from and to. Other fields are optional. The full list:
- * from:flags:value
- * to:value
- * scope:flags:value
- * prio:value
- * len:value
- *
- * Fields are separated by whitespace. from: and to: fields may contain
- * whitespace if they are quoted. Quoting may use any non-alphanumeric
- * matched-pair delimiter, though the delimiter may not then appear
- * (even escaped) within the value string.
- *
- * Flags are:
- * i - case-independent matching
- * r - regexp match
- * u (applies only to scope) - apply scope match to full URI
- * starting with "http://" (the default is to match the path
- * only, as in for example a <Location> in HTTPD).
- *
- * A from: value is a string or a regexp, according to flags.
- * A to: string is a replacement, and may reference regexp memory $1 - $9.
- *
- * A scope: value is likewise a string or (memory-less) regexp and
- * determines the scope of URLs over which the rule applies.
- *
- * A prio: value is a single digit, and determines the priority of the
- * rule. That is to say, two or more rules generate overlapping matches,
- * the priority value will determine which rule prevails. A lower
- * priority value prevails over a higher one.
- *
- * A len: value is an integer, and applies only to a regexp from:
- * It should be an estimate of the largest match size expected from
- * the from: pattern. It is used internally to determine the size of
- * a continuity buffer, that avoids missing a match that spans more
- * than one incoming data chunk arriving at the stream-editor filter.
- * The default is 20.
- *
- * Performance tips:
- * - A high len: value on any rule can severely impact on performance,
- * especially if mixed with short matches that match frequently.
- * - Specify high-precedence rules (low prio: values) first in your
- * configuration to avoid reshuffling edits while processing data.
- *
- * Example: a trivial ruleset to escape HTML entities:
- * [out] scope::/html-escape/ from::"&" to:"&"
- * [out] scope::/html-escape/ from::< to:<
- * [out] scope::/html-escape/ from::> to:>
- * [out] scope::/html-escape/ from::/"/ to:/"/
- * Note, the first & has to be quoted, as the two ampersands in the line
- * would otherwise be mis-parsed as a matching pair of delimiters.
- * Quoting the &, and the " line with //, are optional (and quoting
- * is not applicable to the scope: field).
- * The double-colons delimit flags, of which none are used in this example.
- */
-#define MAX_CONFIG_LINE 1024
-#define MAX_RX_MATCH 10
-
-#define __STDC_LIMIT_MACROS
-#include <stdint.h>
-
-#include <vector>
-#include <set>
-#include <regex.h>
-#include <ctype.h>
-#include <assert.h>
-#include <string.h>
-#include <string>
-#include <stdio.h>
-#include "ts/ts.h"
-
-struct edit_t;
-typedef std::set<edit_t> editset_t;
-typedef editset_t::const_iterator edit_p;
-struct edit_t {
- const size_t start;
- const size_t bytes;
- const std::string repl;
- const int priority;
- edit_t(size_t s, size_t b, const std::string& r, int p) :
- start(s), bytes(b), repl(r), priority(p) {;}
- bool operator!=(const edit_t& x) const {
- return start != x.start
- || bytes != x.bytes
- || repl != x.repl
- || priority != x.priority ;
- }
- bool operator<(const edit_t& x) const {
- if ((start == x.start)
- || (start < x.start && start+bytes > x.start)
- || (x.start < start && x.start+x.bytes > start)) {
-
- /* conflicting edits. Throw back to resolve conflict */
- /* Problem: we get called from erase() within conflict resolution,
- * and comparing to ourself then re-throws.
- * Need to exclude that case.
- */
- if (*this != x) throw x;
- }
- return start < x.start;
- }
- bool saveto(editset_t &edits) const {
- /* loop to try until inserted or we lose a conflict */
- for(;;) {
- try {
- edits.insert(*this);
- return true;
- }
- catch (const edit_t& conflicted) {
- TSDebug("stream-editor",
- "Conflicting edits [%ld-%ld] vs [%ld-%ld]",
- start,start+bytes,
- conflicted.start,conflicted.start+conflicted.bytes);
- if (priority < conflicted.priority) {
- /* we win conflict and oust our enemy */
- edits.erase(conflicted);
- }
- else {
- /* we lose the conflict - give up */
- return false;
- }
- }
- }
- }
-};
-
-
-
-class scope_t {
- virtual bool match(const char *) const = 0;
- const bool uri;
-public:
- bool in_scope(TSHttpTxn tx) const {
- /* Get the URL from tx, and feed it to match() */
- bool ret = false;
- TSMBuffer bufp;
- TSMLoc offset;
- int length;
- TSReturnCode rc = TSHttpTxnPristineUrlGet(tx, &bufp, &offset);
- if (rc != TS_SUCCESS) {
- TSError("Error getting URL of current Txn");
- return ret;
- }
- char *url = TSUrlStringGet(bufp, offset, &length);
-
- if (!strncasecmp(url, "https://", 8)) {
- /* No use trying to edit https data */
- ret = false;
- }
- else {
- char *p = url;
- if (uri) {
- /* match against path component, discard earlier components */
- if (!strncasecmp(url, "http://", 7)) {
- p += 7;
- while (*p != '/') {
- assert(*p != '\0');
- ++p;
- }
- }
- }
- ret = match(p);
- }
- TSfree(url);
- TSHandleMLocRelease(bufp, TS_NULL_MLOC, offset);
- //TSMBufferDestroy(bufp);
- return ret;
- }
- scope_t(const bool u) : uri(u) {;}
- virtual ~scope_t() {}
-};
-class rxscope : public scope_t {
-private:
- regex_t rx;
- virtual bool match(const char *str) const {
- return (regexec(&rx, str, 0, NULL, 0) == 0) ? true : false;
- }
-public:
- rxscope(const bool u, const bool i, const char *pattern, int len)
- : scope_t(u) {
- int flags = REG_NOSUB | REG_EXTENDED | (i ? REG_ICASE : 0);
- char *str = TSstrndup(pattern, len);
- int error = regcomp(&rx, str, flags);
- if (error) {
- TSError("stream-editor: can't compile regexp [%s]", str);
- TSfree(str);
- throw;
- }
- TSfree(str);
- }
- virtual ~rxscope() {
- regfree(&rx);
- }
-};
-class strscope : public scope_t {
-private:
- const bool icase;
- char *str;
- virtual bool match(const char *p) const {
- return ((icase ? strncasecmp : strncmp)(str, p, strlen(str)) == 0)
- ? true : false;
- }
-public:
- strscope(const bool u, const bool i, const char *pattern, int len)
- : scope_t(u), icase(i) {
- str = TSstrndup(pattern, len);
- }
- virtual ~strscope() {
- if (str) TSfree(str);
- }
-};
-
-class match_t {
-public:
- virtual bool find(const char *, size_t, size_t&, size_t&,
- const char *, std::string&) const = 0;
- virtual size_t cont_size() const = 0;
- virtual ~match_t() {}
-};
-class strmatch : public match_t {
- const bool icase;
- char *str;
- const size_t slen;
-public:
- virtual bool find(const char *buf, size_t len, size_t& found,
- size_t& found_len, const char *to,
- std::string& repl) const {
- const char *match = icase ? strcasestr(buf, str) : strstr(buf, str);
- if (match) {
- found = match - buf;
- found_len = slen;
- repl = to;
- return (found+slen > len) ? false : true;
- }
- else {
- return false;
- }
- }
- strmatch(const bool i, const char *pattern, int len) : icase(i), slen(len) {
- str = TSstrndup(pattern, len);
- }
- virtual ~strmatch() {
- if (str) TSfree(str);
- }
- virtual size_t cont_size() const {
- return slen;
- }
-};
-class rxmatch : public match_t {
- size_t match_len;
- regex_t rx;
-public:
- virtual bool find(const char *buf, size_t len, size_t& found,
- size_t& found_len, const char *tmpl,
- std::string& repl) const {
- regmatch_t pmatch[MAX_RX_MATCH];
- if (regexec(&rx, buf, MAX_RX_MATCH, pmatch, REG_NOTEOL) == 0) {
- char c;
- int n;
- found = pmatch[0].rm_so;
- found_len = pmatch[0].rm_eo - found;
- while (c = *tmpl++, c != '\0') {
- switch (c) {
- case '\\':
- if (*tmpl != '\0') {
- repl.push_back(*tmpl++);
- }
- break;
- case '$':
- if (isdigit(*tmpl)) {
- n = *tmpl - '0';
- }
- else {
- n = MAX_RX_MATCH;
- }
- if (n > 0 && n < MAX_RX_MATCH) {
- repl.append(buf+pmatch[n].rm_so,
- pmatch[n].rm_eo - pmatch[n].rm_so);
- tmpl++; /* we've consumed one more character */
- }
- else {
- repl.push_back(c);
- }
- break;
- default:
- repl.push_back(c);
- break;
- }
- }
- return true;
- }
- else {
- return false;
- }
- }
- virtual size_t cont_size() const {
- return match_len;
- }
- rxmatch(bool i, const char* pattern, size_t sz, size_t match_max)
- : match_len(match_max) {
- char *str = TSstrndup(pattern, sz);
- int flags = REG_EXTENDED | (i ? REG_ICASE : 0);
- int error = regcomp(&rx, str, flags);
- if (error) {
- TSError("stream-editor: can't compile regexp [%s]", str);
- TSfree(str);
- throw;
- }
- TSfree(str);
- }
- virtual ~rxmatch() {
- regfree(&rx);
- }
-};
-
-#define PARSE_VERIFY(line,x,str) \
- while (x) \
- if (!isspace(*(x-1))) \
- x = strcasestr(x+1, str); \
- else \
- break
-
-class rule_t {
-private:
- scope_t *scope;
- unsigned int priority;
- match_t *from;
- char *to;
-public:
- rule_t(const char *line) : scope(NULL), priority(5), from(NULL), to(NULL) {
- const char *scope_spec = strcasestr(line, "scope:");
- const char *from_spec = strcasestr(line, "from:");
- const char *to_spec = strcasestr(line, "to:");
- const char *prio_spec = strcasestr(line, "prio:");
- const char *len_spec = strcasestr(line, "len:");
- bool icase = false;
- bool rx = false;
- bool uri;
- size_t len, match_len;
- char delim;
-
- PARSE_VERIFY(line, scope_spec, "scope:");
- PARSE_VERIFY(line, from_spec, "from:");
- PARSE_VERIFY(line, to_spec, "to:");
- PARSE_VERIFY(line, prio_spec, "prio:");
- PARSE_VERIFY(line, len_spec, "len:");
-
- if (!from_spec || !to_spec) {
- throw "Incomplete stream edit spec";
- }
-
- if (len_spec) {
- match_len = 0;
- len_spec += 4;
- while (isdigit(*len_spec)) {
- match_len = 10*match_len + (*len_spec++ - '0');
- }
- }
- else {
- match_len = 20; // default
- }
-
- /* parse From: now, as failure could abort constructor */
- for (from_spec += 5; *from_spec != ':'; ++from_spec) {
- switch (*from_spec) {
- case 'i': icase = true; break;
- case 'r': rx = true; break;
- }
- }
- delim = *++from_spec;
- if (isalnum(delim)) {
- len = strcspn(from_spec, " \t\r\n");
- }
- else {
- const char *end = strchr(++from_spec, delim);
- if (end) {
- len = end - from_spec;
- }
- else {
- /* it wasn't a delimiter after all */
- len = strcspn(--from_spec, " \t\r\n");
- }
- }
- if (rx) {
- from = new rxmatch(icase, from_spec, len, match_len);
- }
- else {
- from = new strmatch(icase, from_spec, len);
- }
-
- if (scope_spec) {
- icase = false;
- rx = false;
- uri = true;
- for (scope_spec += 6; *scope_spec != ':'; ++scope_spec) {
- switch (*scope_spec) {
- case 'i': icase = true; break;
- case 'r': rx = true; break;
- case 'u': uri = false; break;
- }
- }
- ++scope_spec;
- len = strcspn(scope_spec, " ");
- if (rx) {
- scope = new rxscope(uri, icase, scope_spec, len);
- }
- else {
- scope = new strscope(uri, icase, scope_spec, len);
- }
- }
-
- if (prio_spec) {
- prio_spec += 5;
- if (isdigit(*prio_spec)) {
- priority = *prio_spec - '0';
- }
- }
-
- to_spec += 3;
- delim = *to_spec;
- if (isalnum(delim)) {
- len = strcspn(to_spec, " \t\r\n");
- }
- else {
- const char *end = strchr(++to_spec, delim);
- if (end) {
- len = end - to_spec;
- }
- else {
- /* it wasn't a delimiter after all */
- len = strcspn(--to_spec, " \t\r\n");
- }
- }
- to = TSstrndup(to_spec, len);
- }
- rule_t(const rule_t& r) : scope(r.scope), priority(r.priority),
- from(r.from), to(r.to) { }
-/* FIXME - since rules get copied per-request, we can't delete these.
- But we can leave these to leak 'cos they're only ever created
- as a one-off at startup. Would be cleaner to refcount or to
- use subclasses with and without destructor for original vs copy.
- ~rule_t() {
- if (scope) delete scope;
- if (from) delete from;
- if (to) TSfree(to);
- }
-*/
- bool in_scope(TSHttpTxn tx) const {
- /* if no scope is specified then everything is in-scope */
- return scope ? scope->in_scope(tx) : true;
- }
- size_t cont_size() const {
- return from->cont_size();
- }
- void apply(const char *buf, size_t len, editset_t& edits) const {
- /* find matches in the buf, and add match+replace to edits */
-
- size_t found;
- size_t found_len;
- size_t offs = 0;
- while (offs < len) {
- std::string repl;
- if (from->find(buf+offs, len-offs, found, found_len,
- to, repl)) {
- found += offs;
- edit_t(found, found_len, repl, priority).saveto(edits);
- offs = found + found_len;
- }
- else {
- break;
- }
- }
- }
-};
-typedef std::vector<rule_t> ruleset_t;
-typedef ruleset_t::const_iterator rule_p;
-
-typedef struct contdata_t {
- TSCont cont;
- TSIOBuffer out_buf;
- TSIOBufferReader out_rd;
- TSVIO out_vio;
- ruleset_t rules;
- std::string contbuf;
- size_t contbuf_sz;
- int64_t bytes_in;
- int64_t bytes_out;
-/* Use new/delete so destructor does cleanup for us */
- contdata_t() : cont(NULL), out_buf(NULL), out_rd(NULL), out_vio(NULL),
- contbuf_sz(0), bytes_in(0), bytes_out(0) {}
- ~contdata_t() {
- if (out_rd) TSIOBufferReaderFree(out_rd);
- if (out_buf) TSIOBufferDestroy(out_buf);
- if (cont) TSContDestroy(cont);
- }
- void set_cont_size(size_t sz) {
- if (contbuf_sz < 2*sz)
- contbuf_sz = 2*sz - 1;
- }
-} contdata_t;
-
-static int64_t process_block(contdata_t *contdata, TSIOBufferReader reader) {
- int64_t nbytes, start;
- size_t n = 0;
- size_t buflen;
- size_t keep;
- const char *buf;
- TSIOBufferBlock block;
-
- if (reader == NULL) { // We're just flushing anything we have buffered
- keep = 0;
- buf = contdata->contbuf.c_str();
- buflen = contdata->contbuf.length();
- }
- else {
-
- block = TSIOBufferReaderStart(reader);
- buf = TSIOBufferBlockReadStart(block, reader, &nbytes);
-
- if (contdata->contbuf.empty()) {
- /* Use the data as-is */
- buflen = nbytes;
- }
- else {
- contdata->contbuf.append(buf, nbytes);
- buf = contdata->contbuf.c_str();
- buflen = contdata->contbuf.length();
- }
- keep = contdata->contbuf_sz;
- }
- size_t bytes_read = 0;
-
- editset_t edits;
-
- for (rule_p r = contdata->rules.begin();
- r != contdata->rules.end(); ++r) {
- r->apply(buf, buflen, edits);
- }
-
- for (edit_p p = edits.begin(); p != edits.end(); ++p) {
- /* Preserve continuity buffer */
- if (p->start >= buflen - keep)
- break;
-
- /* pass through bytes before edit */
- start = p->start - bytes_read;
-
- while (start > 0) {
- // FIXME: would this be quicker if we managed a TSIOBuffer
- // so we could use TSIOBufferCopy ?
- n = TSIOBufferWrite(contdata->out_buf, buf+bytes_read, start);
- assert (n > 0); // FIXME - handle error
- bytes_read += n;
- contdata->bytes_out += n;
- start -= n;
- }
-
- /* omit deleted bytes */
- bytes_read += p->bytes;
-
- /* insert replacement bytes */
- n = TSIOBufferWrite(contdata->out_buf, p->repl.c_str(), p->repl.length());
- assert(n == p->repl.length()); // FIXME (if this ever happens)!
- contdata->bytes_out += n;
-
- /* increment counts - done */
- }
- contdata->bytes_in += bytes_read;
-
- /* data after the last edit */
- if (bytes_read < buflen - keep) {
- n = TSIOBufferWrite(contdata->out_buf, buf+bytes_read,
- buflen - bytes_read - keep);
- contdata->bytes_in += n;
- contdata->bytes_out += n;
- bytes_read += n;
- }
- /* reset buf to what we've not processed */
- contdata->contbuf = buf+bytes_read;
-
- return nbytes;
-}
-static void streamedit_process(TSCont contp)
-{
- // Read the data available to us
- // Concatenate with anything we have buffered
- // Loop over rules, and apply them to build our edit set
- // Loop over edits, and apply them to the stream
- // Retain buffered data at the end
- int64_t ntodo, nbytes;
-
- contdata_t *contdata = (contdata_t*) TSContDataGet(contp);
- TSVIO input_vio = TSVConnWriteVIOGet(contp);
- TSIOBufferReader input_rd = TSVIOReaderGet(input_vio);
-
- if (contdata->out_buf == NULL) {
- contdata->out_buf = TSIOBufferCreate();
- contdata->out_rd = TSIOBufferReaderAlloc(contdata->out_buf);
- contdata->out_vio = TSVConnWrite(TSTransformOutputVConnGet(contp),
- contp, contdata->out_rd, INT64_MAX);
- }
-
- TSIOBuffer in_buf = TSVIOBufferGet(input_vio);
- /* Test for EOS */
- if (in_buf == NULL) {
- process_block(contdata, NULL); // flush any buffered data
- TSVIONBytesSet(contdata->out_vio, contdata->bytes_out);
- TSVIOReenable(contdata->out_vio);
- return;
- }
-
- /* Test for EOS */
- ntodo = TSVIONTodoGet(input_vio);
- if (ntodo == 0) {
- /* Call back the input VIO continuation to let it know that we
- * have completed the write operation.
- */
- TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE,
- input_vio);
- TSVIOReenable(contdata->out_vio);
- return;
- }
-
- /* now parse & process buffered data. We can set some aside
- * as a continuity buffer to deal with the problem of matches
- * that span input chunks.
- */
- while (ntodo = TSIOBufferReaderAvail(input_rd), ntodo > 0) {
- nbytes = process_block(contdata, input_rd);
- TSIOBufferReaderConsume(input_rd, nbytes);
- TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + nbytes);
- }
-
- ntodo = TSVIONTodoGet(input_vio);
- if (ntodo == 0) {
- /* Call back the input VIO continuation to let it know that we
- * have completed the write operation.
- */
- TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
- }
- else {
- /* Call back the input VIO continuation to let it know that we
- * are ready for more data.
- */
- TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
- }
- TSVIOReenable(contdata->out_vio);
-}
-static int streamedit_filter(TSCont contp, TSEvent event, void *edata)
-{
- /* Our main function that does the work.
- * Called as a continuation for filtering.
- * *** if necessary, add call at TXN_CLOSE for cleanup.
- */
- TSVIO input_vio;
-
- if (TSVConnClosedGet(contp)) {
- contdata_t *contdata = (contdata_t*) TSContDataGet(contp);
- delete contdata;
- return TS_SUCCESS;
- }
-
- switch (event) {
- case TS_EVENT_ERROR:
- input_vio = TSVConnWriteVIOGet(contp);
- TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
- break;
- case TS_EVENT_VCONN_WRITE_COMPLETE:
- TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
- break;
- default:
- streamedit_process(contp);
- break;
- }
- return TS_SUCCESS;
-}
-
-static int streamedit_setup(TSCont contp, TSEvent event, void *edata)
-{
- TSHttpTxn txn = (TSHttpTxn) edata;
- ruleset_t *rules_in = (ruleset_t*) TSContDataGet(contp);
- contdata_t *contdata = NULL;
-
- assert((event == TS_EVENT_HTTP_READ_RESPONSE_HDR)
- || (event == TS_EVENT_HTTP_READ_REQUEST_HDR));
-
- /* make a new list comprising those rules that are in scope */
- for (rule_p r = rules_in->begin(); r != rules_in->end(); ++r) {
- if (r->in_scope(txn)) {
- if (contdata == NULL) {
- contdata = new contdata_t();
- }
- contdata->rules.push_back(*r);
- contdata->set_cont_size(r->cont_size());
- }
- }
-
- if (contdata == NULL) {
- /* Nothing to do */
- return TS_SUCCESS;
- }
-
- /* we have a job to do, so insert filter */
- contdata->cont = TSTransformCreate(streamedit_filter, txn);
- TSContDataSet(contdata->cont, contdata);
-
- if (event == TS_EVENT_HTTP_READ_REQUEST_HDR) {
- TSHttpTxnHookAdd(txn, TS_HTTP_REQUEST_TRANSFORM_HOOK, contdata->cont);
- }
- else {
- TSHttpTxnHookAdd(txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contdata->cont);
- }
-
- TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
-
- return TS_SUCCESS;
-}
-
-static void read_conf(const char *filename, ruleset_t *&in, ruleset_t *&out)
-{
- char buf[MAX_CONFIG_LINE];
- FILE *file = fopen(filename, "r");
- if (file == NULL) {
- TSError("[stream-editor] Failed to open %s", filename);
- return;
- }
- while (fgets(buf, MAX_CONFIG_LINE, file) != NULL) {
- try {
- if (!strncasecmp(buf, "[in]", 4)) {
- if (in == NULL) {
- in = new ruleset_t();
- }
- in->push_back(rule_t(buf));
- }
- else if (!strncasecmp(buf, "[out]", 5)) {
- if (out == NULL) {
- out = new ruleset_t();
- }
- out->push_back(rule_t(buf));
- }
- }
- catch(...) {
- TSError("stream-editor: failed to parse rule %s", buf);
- }
- }
- fclose(file);
-}
-
-extern "C" void TSPluginInit(int argc, const char *argv[])
-{
- TSPluginRegistrationInfo info;
- TSCont inputcont, outputcont;
- ruleset_t *rewrites_in = NULL;
- ruleset_t *rewrites_out = NULL;
-
- info.plugin_name = (char *)"stream-editor";
- info.vendor_name = (char *)"Apache Software Foundation";
- info.support_email = (char *)"users@trafficserver.apache.org";
-
- if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
- TSError("[stream-editor] Plugin registration failed.");
- return;
- }
-
- /* Allow different config files */
- while (--argc) {
- read_conf(*++argv, rewrites_in, rewrites_out);
- }
-
- if (rewrites_in != NULL) {
- TSDebug("[stream-editor]", "initialising input filtering");
- inputcont = TSContCreate(streamedit_setup, NULL);
- if (inputcont == NULL) {
- TSError("[stream-editor] failed to initialise input filtering!");
- }
- else {
- TSContDataSet(inputcont, rewrites_in);
- TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, inputcont);
- }
- }
- else {
- TSDebug("[stream-editor]", "no input filter rules, skipping filter");
- }
-
- if (rewrites_out != NULL) {
- TSDebug("[stream-editor]", "initialising output filtering");
- outputcont = TSContCreate(streamedit_setup, NULL);
- if (outputcont == NULL) {
- TSError("[stream-editor] failed to initialise output filtering!");
- }
- else {
- TSContDataSet(outputcont, rewrites_out);
- TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, outputcont);
- }
- }
- else {
- TSDebug("[stream-editor]", "no output filter rules, skipping filter");
- }
-}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/plugins/experimental/stream_editor/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/stream_editor/Makefile.am b/plugins/experimental/stream_editor/Makefile.am
new file mode 100644
index 0000000..d2f6968
--- /dev/null
+++ b/plugins/experimental/stream_editor/Makefile.am
@@ -0,0 +1,21 @@
+# 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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+include $(top_srcdir)/build/plugins.mk
+
+pkglib_LTLIBRARIES = stream_editor.la
+stream_editor_la_SOURCES = stream_editor.cc
+stream_editor_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/9a24e93c/plugins/experimental/stream_editor/stream_editor.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/stream_editor/stream_editor.cc b/plugins/experimental/stream_editor/stream_editor.cc
new file mode 100644
index 0000000..97ef1a9
--- /dev/null
+++ b/plugins/experimental/stream_editor/stream_editor.cc
@@ -0,0 +1,864 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+/* stream-editor: apply string and/or regexp search-and-replace to
+ * HTTP request and response bodies.
+ *
+ * Load from plugin.config, with one or more filenames as args.
+ * These are config files, and all config files are equal.
+ *
+ * Each line in a config file and conforming to config syntax specifies a
+ * rule for rewriting input or output.
+ *
+ * A line starting with [out] is an output rule.
+ * One starting with [in] is an input rule.
+ * Any other line is ignored, so blank lines and comments are fine.
+ *
+ * Each line must have a from: field and a to: field specifying what it
+ * rewrites from and to. Other fields are optional. The full list:
+ * from:flags:value
+ * to:value
+ * scope:flags:value
+ * prio:value
+ * len:value
+ *
+ * Fields are separated by whitespace. from: and to: fields may contain
+ * whitespace if they are quoted. Quoting may use any non-alphanumeric
+ * matched-pair delimiter, though the delimiter may not then appear
+ * (even escaped) within the value string.
+ *
+ * Flags are:
+ * i - case-independent matching
+ * r - regexp match
+ * u (applies only to scope) - apply scope match to full URI
+ * starting with "http://" (the default is to match the path
+ * only, as in for example a <Location> in HTTPD).
+ *
+ * A from: value is a string or a regexp, according to flags.
+ * A to: string is a replacement, and may reference regexp memory $1 - $9.
+ *
+ * A scope: value is likewise a string or (memory-less) regexp and
+ * determines the scope of URLs over which the rule applies.
+ *
+ * A prio: value is a single digit, and determines the priority of the
+ * rule. That is to say, two or more rules generate overlapping matches,
+ * the priority value will determine which rule prevails. A lower
+ * priority value prevails over a higher one.
+ *
+ * A len: value is an integer, and applies only to a regexp from:
+ * It should be an estimate of the largest match size expected from
+ * the from: pattern. It is used internally to determine the size of
+ * a continuity buffer, that avoids missing a match that spans more
+ * than one incoming data chunk arriving at the stream-editor filter.
+ * The default is 20.
+ *
+ * Performance tips:
+ * - A high len: value on any rule can severely impact on performance,
+ * especially if mixed with short matches that match frequently.
+ * - Specify high-precedence rules (low prio: values) first in your
+ * configuration to avoid reshuffling edits while processing data.
+ *
+ * Example: a trivial ruleset to escape HTML entities:
+ * [out] scope::/html-escape/ from::"&" to:"&"
+ * [out] scope::/html-escape/ from::< to:<
+ * [out] scope::/html-escape/ from::> to:>
+ * [out] scope::/html-escape/ from::/"/ to:/"/
+ * Note, the first & has to be quoted, as the two ampersands in the line
+ * would otherwise be mis-parsed as a matching pair of delimiters.
+ * Quoting the &, and the " line with //, are optional (and quoting
+ * is not applicable to the scope: field).
+ * The double-colons delimit flags, of which none are used in this example.
+ */
+#define MAX_CONFIG_LINE 1024
+#define MAX_RX_MATCH 10
+
+#include <stdint.h>
+
+#include <vector>
+#include <set>
+#include <regex.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+#include <string>
+#include <stdio.h>
+#include "ts/ts.h"
+
+struct edit_t;
+typedef std::set<edit_t> editset_t;
+typedef editset_t::const_iterator edit_p;
+
+struct edit_t {
+ const size_t start;
+ const size_t bytes;
+ const std::string repl;
+ const int priority;
+ edit_t(size_t s, size_t b, const std::string &r, int p) : start(s), bytes(b), repl(r), priority(p) { ; }
+
+ bool operator!=(const edit_t &x) const
+ {
+ return start != x.start || bytes != x.bytes || repl != x.repl || priority != x.priority;
+ }
+
+ bool operator<(const edit_t &x) const
+ {
+ if ((start == x.start) || (start < x.start && start + bytes > x.start) || (x.start < start && x.start + x.bytes > start)) {
+ /* conflicting edits. Throw back to resolve conflict */
+ /* Problem: we get called from erase() within conflict resolution,
+ * and comparing to ourself then re-throws.
+ * Need to exclude that case.
+ */
+ if (*this != x)
+ throw x;
+ }
+ return start < x.start;
+ }
+
+ bool
+ saveto(editset_t &edits) const
+ {
+ /* loop to try until inserted or we lose a conflict */
+ for (;;) {
+ try {
+ edits.insert(*this);
+ return true;
+ } catch (const edit_t &conflicted) {
+ TSDebug("stream-editor", "Conflicting edits [%ld-%ld] vs [%ld-%ld]", start, start + bytes, conflicted.start,
+ conflicted.start + conflicted.bytes);
+ if (priority < conflicted.priority) {
+ /* we win conflict and oust our enemy */
+ edits.erase(conflicted);
+ } else {
+ /* we lose the conflict - give up */
+ return false;
+ }
+ }
+ }
+ }
+};
+
+
+class scope_t
+{
+ virtual bool match(const char *) const = 0;
+ const bool uri;
+
+public:
+ bool
+ in_scope(TSHttpTxn tx) const
+ {
+ /* Get the URL from tx, and feed it to match() */
+ bool ret = false;
+ TSMBuffer bufp;
+ TSMLoc offset;
+ int length;
+ TSReturnCode rc = TSHttpTxnPristineUrlGet(tx, &bufp, &offset);
+ if (rc != TS_SUCCESS) {
+ TSError("Error getting URL of current Txn");
+ return ret;
+ }
+ char *url = TSUrlStringGet(bufp, offset, &length);
+
+ if (!strncasecmp(url, "https://", 8)) {
+ /* No use trying to edit https data */
+ ret = false;
+ } else {
+ char *p = url;
+ if (uri) {
+ /* match against path component, discard earlier components */
+ if (!strncasecmp(url, "http://", 7)) {
+ p += 7;
+ while (*p != '/') {
+ assert(*p != '\0');
+ ++p;
+ }
+ }
+ }
+ ret = match(p);
+ }
+ TSfree(url);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, offset);
+ // TSMBufferDestroy(bufp);
+ return ret;
+ }
+
+ scope_t(const bool u) : uri(u) { ; }
+
+ virtual ~scope_t() {}
+};
+
+
+class rxscope : public scope_t
+{
+private:
+ regex_t rx;
+ virtual bool
+
+ match(const char *str) const
+ {
+ return (regexec(&rx, str, 0, NULL, 0) == 0) ? true : false;
+ }
+
+public:
+ rxscope(const bool u, const bool i, const char *pattern, int len) : scope_t(u)
+ {
+ int flags = REG_NOSUB | REG_EXTENDED | (i ? REG_ICASE : 0);
+ char *str = TSstrndup(pattern, len);
+ int error = regcomp(&rx, str, flags);
+ if (error) {
+ TSError("stream-editor: can't compile regexp [%s]", str);
+ TSfree(str);
+ throw;
+ }
+ TSfree(str);
+ }
+
+ virtual ~rxscope() { regfree(&rx); }
+};
+
+
+class strscope : public scope_t
+{
+private:
+ const bool icase;
+ char *str;
+ virtual bool
+
+ match(const char *p) const
+ {
+ return ((icase ? strncasecmp : strncmp)(str, p, strlen(str)) == 0) ? true : false;
+ }
+
+public:
+ strscope(const bool u, const bool i, const char *pattern, int len) : scope_t(u), icase(i) { str = TSstrndup(pattern, len); }
+
+ virtual ~strscope()
+ {
+ if (str)
+ TSfree(str);
+ }
+};
+
+
+class match_t
+{
+public:
+ virtual bool find(const char *, size_t, size_t &, size_t &, const char *, std::string &) const = 0;
+ virtual size_t cont_size() const = 0;
+ virtual ~match_t() {}
+};
+
+
+class strmatch : public match_t
+{
+ const bool icase;
+ char *str;
+ const size_t slen;
+
+public:
+ virtual bool
+ find(const char *buf, size_t len, size_t &found, size_t &found_len, const char *to, std::string &repl) const
+ {
+ const char *match = icase ? strcasestr(buf, str) : strstr(buf, str);
+ if (match) {
+ found = match - buf;
+ found_len = slen;
+ repl = to;
+ return (found + slen > len) ? false : true;
+ } else {
+ return false;
+ }
+ }
+
+ strmatch(const bool i, const char *pattern, int len) : icase(i), slen(len) { str = TSstrndup(pattern, len); }
+ virtual ~strmatch()
+ {
+ if (str)
+ TSfree(str);
+ }
+
+ virtual size_t
+ cont_size() const
+ {
+ return slen;
+ }
+};
+
+
+class rxmatch : public match_t
+{
+ size_t match_len;
+ regex_t rx;
+
+public:
+ virtual bool
+ find(const char *buf, size_t len, size_t &found, size_t &found_len, const char *tmpl, std::string &repl) const
+ {
+ regmatch_t pmatch[MAX_RX_MATCH];
+ if (regexec(&rx, buf, MAX_RX_MATCH, pmatch, REG_NOTEOL) == 0) {
+ char c;
+ int n;
+ found = pmatch[0].rm_so;
+ found_len = pmatch[0].rm_eo - found;
+ while (c = *tmpl++, c != '\0') {
+ switch (c) {
+ case '\\':
+ if (*tmpl != '\0') {
+ repl.push_back(*tmpl++);
+ }
+ break;
+ case '$':
+ if (isdigit(*tmpl)) {
+ n = *tmpl - '0';
+ } else {
+ n = MAX_RX_MATCH;
+ }
+ if (n > 0 && n < MAX_RX_MATCH) {
+ repl.append(buf + pmatch[n].rm_so, pmatch[n].rm_eo - pmatch[n].rm_so);
+ tmpl++; /* we've consumed one more character */
+ } else {
+ repl.push_back(c);
+ }
+ break;
+ default:
+ repl.push_back(c);
+ break;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ virtual size_t
+ cont_size() const
+ {
+ return match_len;
+ }
+
+ rxmatch(bool i, const char *pattern, size_t sz, size_t match_max) : match_len(match_max)
+ {
+ char *str = TSstrndup(pattern, sz);
+ int flags = REG_EXTENDED | (i ? REG_ICASE : 0);
+ int error = regcomp(&rx, str, flags);
+ if (error) {
+ TSError("stream-editor: can't compile regexp [%s]", str);
+ TSfree(str);
+ throw;
+ }
+ TSfree(str);
+ }
+
+ virtual ~rxmatch() { regfree(&rx); }
+};
+
+#define PARSE_VERIFY(line, x, str) \
+ while (x) \
+ if (!isspace(*(x - 1))) \
+ x = strcasestr(x + 1, str); \
+ else \
+ break
+
+
+class rule_t
+{
+private:
+ scope_t *scope;
+ unsigned int priority;
+ match_t *from;
+ char *to;
+
+public:
+ rule_t(const char *line) : scope(NULL), priority(5), from(NULL), to(NULL)
+ {
+ const char *scope_spec = strcasestr(line, "scope:");
+ const char *from_spec = strcasestr(line, "from:");
+ const char *to_spec = strcasestr(line, "to:");
+ const char *prio_spec = strcasestr(line, "prio:");
+ const char *len_spec = strcasestr(line, "len:");
+ bool icase = false;
+ bool rx = false;
+ bool uri;
+ size_t len, match_len;
+ char delim;
+
+ PARSE_VERIFY(line, scope_spec, "scope:");
+ PARSE_VERIFY(line, from_spec, "from:");
+ PARSE_VERIFY(line, to_spec, "to:");
+ PARSE_VERIFY(line, prio_spec, "prio:");
+ PARSE_VERIFY(line, len_spec, "len:");
+
+ if (!from_spec || !to_spec) {
+ throw "Incomplete stream edit spec";
+ }
+
+ if (len_spec) {
+ match_len = 0;
+ len_spec += 4;
+ while (isdigit(*len_spec)) {
+ match_len = 10 * match_len + (*len_spec++ - '0');
+ }
+ } else {
+ match_len = 20; // default
+ }
+
+ /* parse From: now, as failure could abort constructor */
+ for (from_spec += 5; *from_spec != ':'; ++from_spec) {
+ switch (*from_spec) {
+ case 'i':
+ icase = true;
+ break;
+ case 'r':
+ rx = true;
+ break;
+ }
+ }
+ delim = *++from_spec;
+ if (isalnum(delim)) {
+ len = strcspn(from_spec, " \t\r\n");
+ } else {
+ const char *end = strchr(++from_spec, delim);
+ if (end) {
+ len = end - from_spec;
+ } else {
+ /* it wasn't a delimiter after all */
+ len = strcspn(--from_spec, " \t\r\n");
+ }
+ }
+ if (rx) {
+ from = new rxmatch(icase, from_spec, len, match_len);
+ } else {
+ from = new strmatch(icase, from_spec, len);
+ }
+
+ if (scope_spec) {
+ icase = false;
+ rx = false;
+ uri = true;
+ for (scope_spec += 6; *scope_spec != ':'; ++scope_spec) {
+ switch (*scope_spec) {
+ case 'i':
+ icase = true;
+ break;
+ case 'r':
+ rx = true;
+ break;
+ case 'u':
+ uri = false;
+ break;
+ }
+ }
+ ++scope_spec;
+ len = strcspn(scope_spec, " ");
+ if (rx) {
+ scope = new rxscope(uri, icase, scope_spec, len);
+ } else {
+ scope = new strscope(uri, icase, scope_spec, len);
+ }
+ }
+
+ if (prio_spec) {
+ prio_spec += 5;
+ if (isdigit(*prio_spec)) {
+ priority = *prio_spec - '0';
+ }
+ }
+
+ to_spec += 3;
+ delim = *to_spec;
+ if (isalnum(delim)) {
+ len = strcspn(to_spec, " \t\r\n");
+ } else {
+ const char *end = strchr(++to_spec, delim);
+ if (end) {
+ len = end - to_spec;
+ } else {
+ /* it wasn't a delimiter after all */
+ len = strcspn(--to_spec, " \t\r\n");
+ }
+ }
+ to = TSstrndup(to_spec, len);
+ }
+
+ rule_t(const rule_t &r) : scope(r.scope), priority(r.priority), from(r.from), to(r.to) {}
+ /* FIXME - since rules get copied per-request, we can't delete these.
+ But we can leave these to leak 'cos they're only ever created
+ as a one-off at startup. Would be cleaner to refcount or to
+ use subclasses with and without destructor for original vs copy.
+ ~rule_t() {
+ if (scope) delete scope;
+ if (from) delete from;
+ if (to) TSfree(to);
+ }
+ */
+
+ bool
+ in_scope(TSHttpTxn tx) const
+ {
+ /* if no scope is specified then everything is in-scope */
+ return scope ? scope->in_scope(tx) : true;
+ }
+
+ size_t
+ cont_size() const
+ {
+ return from->cont_size();
+ }
+
+ void
+ apply(const char *buf, size_t len, editset_t &edits) const
+ {
+ /* find matches in the buf, and add match+replace to edits */
+
+ size_t found;
+ size_t found_len;
+ size_t offs = 0;
+ while (offs < len) {
+ std::string repl;
+ if (from->find(buf + offs, len - offs, found, found_len, to, repl)) {
+ found += offs;
+ edit_t(found, found_len, repl, priority).saveto(edits);
+ offs = found + found_len;
+ } else {
+ break;
+ }
+ }
+ }
+};
+typedef std::vector<rule_t> ruleset_t;
+typedef ruleset_t::const_iterator rule_p;
+
+typedef struct contdata_t {
+ TSCont cont;
+ TSIOBuffer out_buf;
+ TSIOBufferReader out_rd;
+ TSVIO out_vio;
+ ruleset_t rules;
+ std::string contbuf;
+ size_t contbuf_sz;
+ int64_t bytes_in;
+ int64_t bytes_out;
+ /* Use new/delete so destructor does cleanup for us */
+ contdata_t() : cont(NULL), out_buf(NULL), out_rd(NULL), out_vio(NULL), contbuf_sz(0), bytes_in(0), bytes_out(0) {}
+ ~contdata_t()
+ {
+ if (out_rd)
+ TSIOBufferReaderFree(out_rd);
+ if (out_buf)
+ TSIOBufferDestroy(out_buf);
+ if (cont)
+ TSContDestroy(cont);
+ }
+ void
+ set_cont_size(size_t sz)
+ {
+ if (contbuf_sz < 2 * sz)
+ contbuf_sz = 2 * sz - 1;
+ }
+} contdata_t;
+
+static int64_t
+process_block(contdata_t *contdata, TSIOBufferReader reader)
+{
+ int64_t nbytes, start;
+ size_t n = 0;
+ size_t buflen;
+ size_t keep;
+ const char *buf;
+ TSIOBufferBlock block;
+
+ if (reader == NULL) { // We're just flushing anything we have buffered
+ keep = 0;
+ buf = contdata->contbuf.c_str();
+ buflen = contdata->contbuf.length();
+ } else {
+ block = TSIOBufferReaderStart(reader);
+ buf = TSIOBufferBlockReadStart(block, reader, &nbytes);
+
+ if (contdata->contbuf.empty()) {
+ /* Use the data as-is */
+ buflen = nbytes;
+ } else {
+ contdata->contbuf.append(buf, nbytes);
+ buf = contdata->contbuf.c_str();
+ buflen = contdata->contbuf.length();
+ }
+ keep = contdata->contbuf_sz;
+ }
+ size_t bytes_read = 0;
+
+ editset_t edits;
+
+ for (rule_p r = contdata->rules.begin(); r != contdata->rules.end(); ++r) {
+ r->apply(buf, buflen, edits);
+ }
+
+ for (edit_p p = edits.begin(); p != edits.end(); ++p) {
+ /* Preserve continuity buffer */
+ if (p->start >= buflen - keep)
+ break;
+
+ /* pass through bytes before edit */
+ start = p->start - bytes_read;
+
+ while (start > 0) {
+ // FIXME: would this be quicker if we managed a TSIOBuffer
+ // so we could use TSIOBufferCopy ?
+ n = TSIOBufferWrite(contdata->out_buf, buf + bytes_read, start);
+ assert(n > 0); // FIXME - handle error
+ bytes_read += n;
+ contdata->bytes_out += n;
+ start -= n;
+ }
+
+ /* omit deleted bytes */
+ bytes_read += p->bytes;
+
+ /* insert replacement bytes */
+ n = TSIOBufferWrite(contdata->out_buf, p->repl.c_str(), p->repl.length());
+ assert(n == p->repl.length()); // FIXME (if this ever happens)!
+ contdata->bytes_out += n;
+
+ /* increment counts - done */
+ }
+ contdata->bytes_in += bytes_read;
+
+ /* data after the last edit */
+ if (bytes_read < buflen - keep) {
+ n = TSIOBufferWrite(contdata->out_buf, buf + bytes_read, buflen - bytes_read - keep);
+ contdata->bytes_in += n;
+ contdata->bytes_out += n;
+ bytes_read += n;
+ }
+ /* reset buf to what we've not processed */
+ contdata->contbuf = buf + bytes_read;
+
+ return nbytes;
+}
+static void
+streamedit_process(TSCont contp)
+{
+ // Read the data available to us
+ // Concatenate with anything we have buffered
+ // Loop over rules, and apply them to build our edit set
+ // Loop over edits, and apply them to the stream
+ // Retain buffered data at the end
+ int64_t ntodo, nbytes;
+ contdata_t *contdata = (contdata_t *)TSContDataGet(contp);
+ TSVIO input_vio = TSVConnWriteVIOGet(contp);
+ TSIOBufferReader input_rd = TSVIOReaderGet(input_vio);
+
+ if (contdata->out_buf == NULL) {
+ contdata->out_buf = TSIOBufferCreate();
+ contdata->out_rd = TSIOBufferReaderAlloc(contdata->out_buf);
+ contdata->out_vio = TSVConnWrite(TSTransformOutputVConnGet(contp), contp, contdata->out_rd, INT64_MAX);
+ }
+
+ TSIOBuffer in_buf = TSVIOBufferGet(input_vio);
+ /* Test for EOS */
+ if (in_buf == NULL) {
+ process_block(contdata, NULL); // flush any buffered data
+ TSVIONBytesSet(contdata->out_vio, contdata->bytes_out);
+ TSVIOReenable(contdata->out_vio);
+ return;
+ }
+
+ /* Test for EOS */
+ ntodo = TSVIONTodoGet(input_vio);
+ if (ntodo == 0) {
+ /* Call back the input VIO continuation to let it know that we
+ * have completed the write operation.
+ */
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
+ TSVIOReenable(contdata->out_vio);
+ return;
+ }
+
+ /* now parse & process buffered data. We can set some aside
+ * as a continuity buffer to deal with the problem of matches
+ * that span input chunks.
+ */
+ while (ntodo = TSIOBufferReaderAvail(input_rd), ntodo > 0) {
+ nbytes = process_block(contdata, input_rd);
+ TSIOBufferReaderConsume(input_rd, nbytes);
+ TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + nbytes);
+ }
+
+ ntodo = TSVIONTodoGet(input_vio);
+ if (ntodo == 0) {
+ /* Call back the input VIO continuation to let it know that we
+ * have completed the write operation.
+ */
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
+ } else {
+ /* Call back the input VIO continuation to let it know that we
+ * are ready for more data.
+ */
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
+ }
+ TSVIOReenable(contdata->out_vio);
+}
+static int
+streamedit_filter(TSCont contp, TSEvent event, void *edata)
+{
+ /* Our main function that does the work.
+ * Called as a continuation for filtering.
+ * *** if necessary, add call at TXN_CLOSE for cleanup.
+ */
+ TSVIO input_vio;
+
+ if (TSVConnClosedGet(contp)) {
+ contdata_t *contdata = (contdata_t *)TSContDataGet(contp);
+ delete contdata;
+ return TS_SUCCESS;
+ }
+
+ switch (event) {
+ case TS_EVENT_ERROR:
+ input_vio = TSVConnWriteVIOGet(contp);
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
+ break;
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
+ break;
+ default:
+ streamedit_process(contp);
+ break;
+ }
+ return TS_SUCCESS;
+}
+
+static int
+streamedit_setup(TSCont contp, TSEvent event, void *edata)
+{
+ TSHttpTxn txn = (TSHttpTxn)edata;
+ ruleset_t *rules_in = (ruleset_t *)TSContDataGet(contp);
+ contdata_t *contdata = NULL;
+
+ assert((event == TS_EVENT_HTTP_READ_RESPONSE_HDR) || (event == TS_EVENT_HTTP_READ_REQUEST_HDR));
+
+ /* make a new list comprising those rules that are in scope */
+ for (rule_p r = rules_in->begin(); r != rules_in->end(); ++r) {
+ if (r->in_scope(txn)) {
+ if (contdata == NULL) {
+ contdata = new contdata_t();
+ }
+ contdata->rules.push_back(*r);
+ contdata->set_cont_size(r->cont_size());
+ }
+ }
+
+ if (contdata == NULL) {
+ /* Nothing to do */
+ return TS_SUCCESS;
+ }
+
+ /* we have a job to do, so insert filter */
+ contdata->cont = TSTransformCreate(streamedit_filter, txn);
+ TSContDataSet(contdata->cont, contdata);
+
+ if (event == TS_EVENT_HTTP_READ_REQUEST_HDR) {
+ TSHttpTxnHookAdd(txn, TS_HTTP_REQUEST_TRANSFORM_HOOK, contdata->cont);
+ } else {
+ TSHttpTxnHookAdd(txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contdata->cont);
+ }
+
+ TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
+
+ return TS_SUCCESS;
+}
+
+static void
+read_conf(const char *filename, ruleset_t *&in, ruleset_t *&out)
+{
+ char buf[MAX_CONFIG_LINE];
+ FILE *file = fopen(filename, "r");
+
+ if (file == NULL) {
+ TSError("[stream-editor] Failed to open %s", filename);
+ return;
+ }
+ while (fgets(buf, MAX_CONFIG_LINE, file) != NULL) {
+ try {
+ if (!strncasecmp(buf, "[in]", 4)) {
+ if (in == NULL) {
+ in = new ruleset_t();
+ }
+ in->push_back(rule_t(buf));
+ } else if (!strncasecmp(buf, "[out]", 5)) {
+ if (out == NULL) {
+ out = new ruleset_t();
+ }
+ out->push_back(rule_t(buf));
+ }
+ } catch (...) {
+ TSError("stream-editor: failed to parse rule %s", buf);
+ }
+ }
+ fclose(file);
+}
+
+extern "C" void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+ TSCont inputcont, outputcont;
+ ruleset_t *rewrites_in = NULL;
+ ruleset_t *rewrites_out = NULL;
+
+ info.plugin_name = (char *)"stream-editor";
+ info.vendor_name = (char *)"Apache Software Foundation";
+ info.support_email = (char *)"users@trafficserver.apache.org";
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[stream-editor] Plugin registration failed.");
+ return;
+ }
+
+ /* Allow different config files */
+ while (--argc) {
+ read_conf(*++argv, rewrites_in, rewrites_out);
+ }
+
+ if (rewrites_in != NULL) {
+ TSDebug("[stream-editor]", "initialising input filtering");
+ inputcont = TSContCreate(streamedit_setup, NULL);
+ if (inputcont == NULL) {
+ TSError("[stream-editor] failed to initialise input filtering!");
+ } else {
+ TSContDataSet(inputcont, rewrites_in);
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, inputcont);
+ }
+ } else {
+ TSDebug("[stream-editor]", "no input filter rules, skipping filter");
+ }
+
+ if (rewrites_out != NULL) {
+ TSDebug("[stream-editor]", "initialising output filtering");
+ outputcont = TSContCreate(streamedit_setup, NULL);
+ if (outputcont == NULL) {
+ TSError("[stream-editor] failed to initialise output filtering!");
+ } else {
+ TSContDataSet(outputcont, rewrites_out);
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, outputcont);
+ }
+ } else {
+ TSDebug("[stream-editor]", "no output filter rules, skipping filter");
+ }
+}