You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2014/03/12 00:05:49 UTC

git commit: TS-2623: remove the limit on the number of cachurl regular expressions

Repository: trafficserver
Updated Branches:
  refs/heads/master c12b56d38 -> f8619248d


TS-2623: remove the limit on the number of cachurl regular expressions

  - compile the cacheurl plugin as C++ so we can ust standard containers.
  - remove regex limit from cacheurl plugin
  - load the cacheurl config file relative to config directory


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/f8619248
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/f8619248
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/f8619248

Branch: refs/heads/master
Commit: f8619248d37b8da5f363dd75300d5fc672379ae2
Parents: c12b56d
Author: James Peach <jp...@apache.org>
Authored: Fri Mar 7 15:47:32 2014 -0800
Committer: James Peach <jp...@apache.org>
Committed: Tue Mar 11 16:05:41 2014 -0700

----------------------------------------------------------------------
 CHANGES                      |   2 +
 plugins/cacheurl/Makefile.am |   2 +-
 plugins/cacheurl/cacheurl.c  | 436 --------------------------------------
 plugins/cacheurl/cacheurl.cc | 436 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 439 insertions(+), 437 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f8619248/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 0675250..05dcfc2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.0.0
 
+  *) [TS-2623] Remove the limit on the number of cachurl regular expressions.
+
   *) [TS-2628] traffic_line should tell you when a reload is needed.
 
   *) [TS-2627] Reduce management socket code duplication.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f8619248/plugins/cacheurl/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/cacheurl/Makefile.am b/plugins/cacheurl/Makefile.am
index bd1e5a3..f5907fe 100644
--- a/plugins/cacheurl/Makefile.am
+++ b/plugins/cacheurl/Makefile.am
@@ -17,5 +17,5 @@
 include $(top_srcdir)/build/plugins.mk
 
 pkglib_LTLIBRARIES = cacheurl.la
-cacheurl_la_SOURCES = cacheurl.c
+cacheurl_la_SOURCES = cacheurl.cc
 cacheurl_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f8619248/plugins/cacheurl/cacheurl.c
----------------------------------------------------------------------
diff --git a/plugins/cacheurl/cacheurl.c b/plugins/cacheurl/cacheurl.c
deleted file mode 100644
index 455dba3..0000000
--- a/plugins/cacheurl/cacheurl.c
+++ /dev/null
@@ -1,436 +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.
- */
-
-/* cacheurl.c - Plugin to modify the URL used as a cache key for certain
- * requests, without modifying the URL used for actually fetching data from
- * the origin server.
- */
-
-#include <stdio.h>
-#include <string.h>
-
-#include "ink_config.h"
-
-#ifdef HAVE_PCRE_PCRE_H
-#include <pcre/pcre.h>
-#else
-#include <pcre.h>
-#endif
-
-#include "ts/ts.h"
-#include "ts/remap.h"
-#include "ink_defs.h"
-
-#define TOKENCOUNT 10
-#define OVECOUNT 30
-#define PATTERNCOUNT 30
-#define PLUGIN_NAME "cacheurl"
-
-typedef struct {
-    pcre *re;       /* Compiled regular expression */
-    int tokcount;   /* Token count */
-    char *pattern;  /* Pattern string */
-    char *replacement; /* Replacement string */
-    int *tokens;    /* Array of $x token values */
-    int *tokenoffset; /* Array of $x token offsets */
-} regex_info;
-
-typedef struct {
-    regex_info *pr[PATTERNCOUNT]; /* Pattern/replacement list */
-    int patterncount; /* Number of patterns */
-    //pr_list *next; /* Link to next set of patterns, if any */
-} pr_list;
-
-static int regex_substitute(char **buf, char *str, regex_info *info) {
-    int matchcount;
-    int ovector[OVECOUNT]; /* Locations of matches in regex */
-
-    int replacelen; /* length of replacement string */
-    int i;
-    int offset;
-    int prev;
-
-    /* Perform the regex matching */
-    matchcount = pcre_exec(info->re, NULL, str, strlen(str), 0, 0, ovector,
-            OVECOUNT);
-    if (matchcount < 0) {
-        switch (matchcount) {
-            case PCRE_ERROR_NOMATCH:
-                break;
-            default:
-                TSError("[%s] Matching error: %d\n", PLUGIN_NAME, matchcount);
-                break;
-        }
-        return 0;
-    }
-
-    /* Verify the replacement has the right number of matching groups */
-    for (i=0; i<info->tokcount; i++) {
-        if (info->tokens[i] >= matchcount) {
-            TSError("[%s] Invalid reference int replacement: $%d\n", PLUGIN_NAME, info->tokens[i]);
-            return 0;
-        }
-    }
-
-    /* malloc the replacement string */
-    replacelen = strlen(info->replacement);
-    replacelen -= info->tokcount * 2; /* Subtract $1, $2 etc... */
-    for (i=0; i<info->tokcount; i++) {
-        replacelen += (ovector[info->tokens[i]*2+1] -
-                ovector[info->tokens[i]*2]);
-    }
-    replacelen++; /* Null terminator */
-    *buf = TSmalloc(replacelen);
-
-    /* perform string replacement */
-    offset = 0; /* Where we are adding new data in the string */
-    prev = 0;
-    for (i=0; i<info->tokcount; i++) {
-        memcpy(*buf + offset, info->replacement + prev,
-                info->tokenoffset[i] - prev);
-        offset += (info->tokenoffset[i] - prev);
-        prev = info->tokenoffset[i] + 2;
-
-        memcpy(*buf + offset,  str + ovector[info->tokens[i]*2],
-                ovector[info->tokens[i]*2+1] - ovector[info->tokens[i]*2]);
-        offset += (ovector[info->tokens[i]*2+1] - ovector[info->tokens[i]*2]);
-    }
-    memcpy(*buf + offset, info->replacement + prev,
-            strlen(info->replacement) - prev);
-    offset += strlen(info->replacement) - prev;
-    (*buf)[offset] = 0; /* Null termination */
-    return 1;
-}
-
-static int regex_compile(regex_info **buf, char *pattern, char *replacement) {
-    const char *reerror; /* Error string from pcre */
-    int reerroffset;     /* Offset where any pcre error occured */
-
-    int tokcount;
-    int *tokens;
-    int *tokenoffset;
-
-    int i;
-
-    int status = 1;      /* Status (return value) of the function */
-
-    regex_info *info = TSmalloc(sizeof(regex_info));
-
-
-    /* Precompile the regular expression */
-    info->re =  pcre_compile(pattern, 0, &reerror, &reerroffset, NULL);
-    if (!info->re) {
-        TSError("[%s] Compilation of regex '%s' failed at char %d: %s\n",
-                PLUGIN_NAME, pattern, reerroffset, reerror);
-        status = 0;
-    }
-
-    /* Precalculate the location of $X tokens in the replacement */
-    tokcount = 0;
-    if (status) {
-        tokens = TSmalloc(sizeof(int) * TOKENCOUNT);
-        tokenoffset = TSmalloc(sizeof(int) * TOKENCOUNT);
-        for (i=0; i<strlen(replacement); i++) {
-            if (replacement[i] == '$') {
-                if (tokcount >= TOKENCOUNT) {
-                    TSError("[%s] Error: too many tokens in replacement "
-                            "string: %s\n", PLUGIN_NAME, replacement);
-                    status = 0;
-                    break;
-                } else if (replacement[i+1] < '0' || replacement[i+1] > '9') {
-                    TSError("[%s] Error: Invalid replacement token $%c in "
-                            "%s: should be $0 - $9\n", PLUGIN_NAME,
-                            replacement[i+1], replacement);
-                    status = 0;
-                    break;
-                } else {
-                    /* Store the location of the replacement */
-                    /* Convert '0' to 0 */
-                    tokens[tokcount] = replacement[i+1] - '0';
-                    tokenoffset[tokcount] = i;
-                    tokcount++;
-                    /* Skip the next char */
-                    i++;
-                }
-            }
-        }
-    }
-
-    if (status) {
-        /* Everything went OK */
-        info->tokcount = tokcount;
-        info->tokens = tokens;
-        info->tokenoffset = tokenoffset;
-
-        info->pattern = TSstrdup(pattern);
-        info->replacement = TSstrdup(replacement);
-
-        *buf = info;
-    } else {
-        /* Something went wrong, clean up */
-        if (info->tokens) TSfree(info->tokens);
-        if (info->tokenoffset) TSfree(info->tokenoffset);
-        if (info->re) pcre_free(info->re);
-        if (info) TSfree(info);
-    }
-    return status;
-}
-
-static pr_list* load_config_file(const char *config_file) {
-    char buffer[1024];
-    char default_config_file[1024];
-    TSFile fh;
-    pr_list *prl = TSmalloc(sizeof(pr_list));
-    prl->patterncount = 0;
-
-    /* locations in a config file line, end of line, split start, split end */
-    char *eol, *spstart, *spend;
-    int lineno = 0;
-    int retval;
-    regex_info *info = 0;
-
-    if (!config_file) {
-        /* Default config file of plugins/cacheurl.config */
-        sprintf(default_config_file, "%s/cacheurl.config", TSPluginDirGet());
-        config_file = (const char *)default_config_file;
-    }
-    TSDebug(PLUGIN_NAME, "Opening config file: %s", config_file);
-    fh = TSfopen(config_file, "r");
-
-    if (!fh) {
-        TSError("[%s] Unable to open %s. No patterns will be loaded\n",
-                PLUGIN_NAME, config_file);
-        return prl;
-    }
-
-    while (TSfgets(fh, buffer, sizeof(buffer) - 1)) {
-        lineno++;
-        if (*buffer == '#') {
-            /* # Comments, only at line beginning */
-            continue;
-        }
-        eol = strstr(buffer, "\n");
-        if (eol) {
-            *eol = 0; /* Terminate string at newline */
-        } else {
-            /* Malformed line - skip */
-            continue;
-        }
-        /* Split line into two parts based on whitespace */
-        /* Find first whitespace */
-        spstart = strstr(buffer, " ");
-        if (!spstart) {
-            spstart = strstr(buffer, "\t");
-        }
-        if (!spstart) {
-            TSError("[%s] ERROR: Invalid format on line %d. Skipping\n",
-                    PLUGIN_NAME, lineno);
-            continue;
-        }
-        /* Find part of the line after any whitespace */
-        spend = spstart + 1;
-        while(*spend == ' ' || *spend == '\t') {
-            spend++;
-        }
-        if (*spend == 0) {
-            /* We reached the end of the string without any non-whitepace */
-            TSError("[%s] ERROR: Invalid format on line %d. Skipping\n",
-                    PLUGIN_NAME, lineno);
-            continue;
-        }
-
-        *spstart = 0;
-        /* We have the pattern/replacement, now do precompilation.
-         * buffer is the first part of the line. spend is the second part just
-         * after the whitespace */
-        TSDebug(PLUGIN_NAME, "Adding pattern/replacement pair: '%s' -> '%s'", buffer, spend);
-        retval = regex_compile(&info, buffer, spend);
-        if (!retval) {
-            TSError("[%s] Error precompiling regex/replacement. Skipping.\n",
-                    PLUGIN_NAME);
-        }
-        // TODO - remove patterncount and make pr_list infinite (linked list)
-        if (prl->patterncount >= PATTERNCOUNT) {
-            TSError("[%s] Warning, too many patterns - skipping the rest"
-                    "(max: %d)\n", PLUGIN_NAME, PATTERNCOUNT);
-            TSfree(info);
-            break;
-        }
-        prl->pr[prl->patterncount] = info;
-        prl->patterncount++;
-    }
-    TSfclose(fh);
-    // Make sure the last element is null
-    if (prl->patterncount < PATTERNCOUNT) {
-        prl->pr[prl->patterncount] = NULL;
-    }
-    return prl;
-}
-
-static int rewrite_cacheurl(pr_list *prl, TSHttpTxn txnp) {
-    int ok = 1;
-    char *newurl = 0;
-    int retval;
-
-    char *url;
-    int url_length;
-    int i;
-    if (ok) {
-        url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length);
-        if (!url) {
-            TSError("[%s] couldn't retrieve request url\n",
-                    PLUGIN_NAME);
-            ok = 0;
-        }
-    }
-
-    if (ok) {
-        i=0;
-        while (i < prl->patterncount && prl->pr[i]) {
-            retval = regex_substitute(&newurl, url, prl->pr[i]);
-            if (retval) {
-                /* Successful match/substitution */
-                break;
-            }
-            i++;
-        }
-        if (newurl) {
-            TSDebug(PLUGIN_NAME, "Rewriting cache URL for %s to %s", url, newurl);
-            if (TSCacheUrlSet(txnp, newurl, strlen(newurl))
-                    != TS_SUCCESS) {
-                TSError("[%s] Unable to modify cache url from "
-                        "%s to %s\n", PLUGIN_NAME, url, newurl);
-                ok = 0;
-            }
-        }
-    }
-    /* Clean up */
-    if (url) TSfree(url);
-    if (newurl) TSfree(newurl);
-    return ok;
-}
-
-static int handle_hook(TSCont contp, TSEvent event, void *edata) {
-    TSHttpTxn txnp = (TSHttpTxn) edata;
-    pr_list *prl;
-    int ok = 1;
-
-    prl = (pr_list *)TSContDataGet(contp);
-
-    switch (event) {
-        case TS_EVENT_HTTP_READ_REQUEST_HDR:
-            ok = rewrite_cacheurl(prl, txnp);
-            TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
-            break;
-        default:
-            TSAssert(!"Unexpected event");
-            ok = 0;
-            break;
-    }
-
-    return ok;
-}
-
-/* Generic error message function for errors in plugin initialization */
-static void initialization_error(char *msg) {
-    TSError("[%s] %s\n", PLUGIN_NAME, msg);
-    TSError("[%s] Unable to initialize plugin (disabled).\n", PLUGIN_NAME);
-}
-
-TSReturnCode TSRemapInit(TSRemapInterface *api_info, char *errbuf,
-    int errbuf_size) {
-    if (!api_info) {
-        strncpy(errbuf, "[tsremap_init] Invalid TSRemapInterface argument",
-                errbuf_size - 1);
-        return TS_ERROR;
-    }
-
-    if (api_info->size < sizeof(TSRemapInterface)) {
-        strncpy(errbuf,
-                "[tsremap_init] Incorrect size of TSRemapInterface structure",
-                errbuf_size - 1);
-        return TS_ERROR;
-    }
-
-    if (api_info->tsremap_version < TSREMAP_VERSION) {
-        snprintf(errbuf, errbuf_size - 1,
-            "[tsremap_init] Incorrect API version %ld.%ld",
-            api_info->tsremap_version >> 16,
-            (api_info->tsremap_version & 0xffff));
-        return TS_ERROR;
-    }
-
-    TSDebug(PLUGIN_NAME, "remap plugin is successfully initialized");
-    return TS_SUCCESS;
-}
-
-TSReturnCode TSRemapNewInstance(int argc, char* argv[], void** ih, char* errbuf ATS_UNUSED,
-                                int errbuf_size ATS_UNUSED) {
-
-    *ih = load_config_file(argc > 2 ? argv[2] : NULL);
-    return TS_SUCCESS;
-}
-
-
-void TSRemapDeleteInstance(void *ih) {
-    // Clean up
-    TSDebug(PLUGIN_NAME, "Deleting remap instance");
-    pr_list *prl = (pr_list *)ih;
-    int i=0;
-    while (prl->pr[i]) {
-        if (prl->pr[i]->tokens) TSfree(prl->pr[i]->tokens);
-        if (prl->pr[i]->tokenoffset) TSfree(prl->pr[i]->tokenoffset);
-        if (prl->pr[i]->re) pcre_free(prl->pr[i]->re);
-        TSfree(prl->pr[i]);
-        i++;
-    }
-    TSfree(prl);
-}
-
-TSRemapStatus TSRemapDoRemap(void* ih, TSHttpTxn rh, TSRemapRequestInfo *rri ATS_UNUSED) {
-    int ok;
-    ok = rewrite_cacheurl((pr_list *)ih, rh);
-    if (ok) {
-        return TSREMAP_NO_REMAP;
-    } else {
-        return TSREMAP_ERROR;
-    }
-}
-
-void TSPluginInit(int argc, const char *argv[]) {
-    TSPluginRegistrationInfo info;
-    TSCont contp;
-    pr_list *prl;
-
-    info.plugin_name = PLUGIN_NAME;
-    info.vendor_name = "Apache Software Foundation";
-    info.support_email = "dev@trafficserver.apache.org";
-
-    if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
-        initialization_error("Plugin registration failed.");
-        return;
-    }
-
-    prl = load_config_file(argc > 1 ? argv[1] : NULL);
-
-    contp = TSContCreate((TSEventFunc)handle_hook, NULL);
-    /* Store the pattern replacement list in the continuation */
-    TSContDataSet(contp, prl);
-    TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, contp);
-}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f8619248/plugins/cacheurl/cacheurl.cc
----------------------------------------------------------------------
diff --git a/plugins/cacheurl/cacheurl.cc b/plugins/cacheurl/cacheurl.cc
new file mode 100644
index 0000000..daeadf9
--- /dev/null
+++ b/plugins/cacheurl/cacheurl.cc
@@ -0,0 +1,436 @@
+/*
+ * 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.
+ */
+
+/* cacheurl.c - Plugin to modify the URL used as a cache key for certain
+ * requests, without modifying the URL used for actually fetching data from
+ * the origin server.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "ink_config.h"
+
+#ifdef HAVE_PCRE_PCRE_H
+#include <pcre/pcre.h>
+#else
+#include <pcre.h>
+#endif
+
+#include "ts/ts.h"
+#include "ts/remap.h"
+#include "ink_defs.h"
+#include <string>
+#include <vector>
+
+#define TOKENCOUNT 10
+#define OVECOUNT 30
+#define PLUGIN_NAME "cacheurl"
+
+struct regex_info {
+    pcre *re;       /* Compiled regular expression */
+    int tokcount;   /* Token count */
+    char *pattern;  /* Pattern string */
+    char *replacement; /* Replacement string */
+    int *tokens;    /* Array of $x token values */
+    int *tokenoffset; /* Array of $x token offsets */
+};
+
+struct pr_list {
+    std::vector <regex_info *> pr;
+
+    pr_list() {
+    }
+
+    ~pr_list() {
+      for (std::vector<regex_info *>::iterator info = this->pr.begin(); info != this->pr.end(); ++info) {
+        TSfree((*info)->tokens);
+        TSfree((*info)->tokenoffset);
+        pcre_free((*info)->re);
+        TSfree(*info);
+      }
+    }
+};
+
+static int regex_substitute(char **buf, char *str, regex_info *info) {
+    int matchcount;
+    int ovector[OVECOUNT]; /* Locations of matches in regex */
+
+    int replacelen; /* length of replacement string */
+    int i;
+    int offset;
+    int prev;
+
+    /* Perform the regex matching */
+    matchcount = pcre_exec(info->re, NULL, str, strlen(str), 0, 0, ovector,
+            OVECOUNT);
+    if (matchcount < 0) {
+        switch (matchcount) {
+            case PCRE_ERROR_NOMATCH:
+                break;
+            default:
+                TSError("[%s] Matching error: %d\n", PLUGIN_NAME, matchcount);
+                break;
+        }
+        return 0;
+    }
+
+    /* Verify the replacement has the right number of matching groups */
+    for (i=0; i<info->tokcount; i++) {
+        if (info->tokens[i] >= matchcount) {
+            TSError("[%s] Invalid reference int replacement: $%d\n", PLUGIN_NAME, info->tokens[i]);
+            return 0;
+        }
+    }
+
+    /* malloc the replacement string */
+    replacelen = strlen(info->replacement);
+    replacelen -= info->tokcount * 2; /* Subtract $1, $2 etc... */
+    for (i=0; i<info->tokcount; i++) {
+        replacelen += (ovector[info->tokens[i]*2+1] -
+                ovector[info->tokens[i]*2]);
+    }
+    replacelen++; /* Null terminator */
+    *buf = (char *)TSmalloc(replacelen);
+
+    /* perform string replacement */
+    offset = 0; /* Where we are adding new data in the string */
+    prev = 0;
+    for (i=0; i<info->tokcount; i++) {
+        memcpy(*buf + offset, info->replacement + prev,
+                info->tokenoffset[i] - prev);
+        offset += (info->tokenoffset[i] - prev);
+        prev = info->tokenoffset[i] + 2;
+
+        memcpy(*buf + offset,  str + ovector[info->tokens[i]*2],
+                ovector[info->tokens[i]*2+1] - ovector[info->tokens[i]*2]);
+        offset += (ovector[info->tokens[i]*2+1] - ovector[info->tokens[i]*2]);
+    }
+    memcpy(*buf + offset, info->replacement + prev,
+            strlen(info->replacement) - prev);
+    offset += strlen(info->replacement) - prev;
+    (*buf)[offset] = 0; /* Null termination */
+    return 1;
+}
+
+static int regex_compile(regex_info **buf, char *pattern, char *replacement) {
+    const char *reerror; /* Error string from pcre */
+    int reerroffset;     /* Offset where any pcre error occured */
+
+    int tokcount;
+    int *tokens;
+    int *tokenoffset;
+
+    int i;
+
+    int status = 1;      /* Status (return value) of the function */
+
+    regex_info *info = (regex_info *)TSmalloc(sizeof(regex_info));
+
+
+    /* Precompile the regular expression */
+    info->re =  pcre_compile(pattern, 0, &reerror, &reerroffset, NULL);
+    if (!info->re) {
+        TSError("[%s] Compilation of regex '%s' failed at char %d: %s\n",
+                PLUGIN_NAME, pattern, reerroffset, reerror);
+        status = 0;
+    }
+
+    /* Precalculate the location of $X tokens in the replacement */
+    tokcount = 0;
+    if (status) {
+        tokens = (int *)TSmalloc(sizeof(int) * TOKENCOUNT);
+        tokenoffset = (int *)TSmalloc(sizeof(int) * TOKENCOUNT);
+        for (i=0; i<strlen(replacement); i++) {
+            if (replacement[i] == '$') {
+                if (tokcount >= TOKENCOUNT) {
+                    TSError("[%s] Error: too many tokens in replacement "
+                            "string: %s\n", PLUGIN_NAME, replacement);
+                    status = 0;
+                    break;
+                } else if (replacement[i+1] < '0' || replacement[i+1] > '9') {
+                    TSError("[%s] Error: Invalid replacement token $%c in "
+                            "%s: should be $0 - $9\n", PLUGIN_NAME,
+                            replacement[i+1], replacement);
+                    status = 0;
+                    break;
+                } else {
+                    /* Store the location of the replacement */
+                    /* Convert '0' to 0 */
+                    tokens[tokcount] = replacement[i+1] - '0';
+                    tokenoffset[tokcount] = i;
+                    tokcount++;
+                    /* Skip the next char */
+                    i++;
+                }
+            }
+        }
+    }
+
+    if (status) {
+        /* Everything went OK */
+        info->tokcount = tokcount;
+        info->tokens = tokens;
+        info->tokenoffset = tokenoffset;
+
+        info->pattern = TSstrdup(pattern);
+        info->replacement = TSstrdup(replacement);
+
+        *buf = info;
+    } else {
+        /* Something went wrong, clean up */
+        if (info->tokens) TSfree(info->tokens);
+        if (info->tokenoffset) TSfree(info->tokenoffset);
+        if (info->re) pcre_free(info->re);
+        if (info) TSfree(info);
+    }
+    return status;
+}
+
+static pr_list *
+load_config_file(const char *config_file) {
+    char buffer[1024];
+    std::string path;
+    TSFile fh;
+    pr_list *prl = new pr_list();
+
+    /* locations in a config file line, end of line, split start, split end */
+    char *eol, *spstart, *spend;
+    int lineno = 0;
+    int retval;
+    regex_info *info = 0;
+
+    if (config_file == NULL) {
+        /* Default config file of plugins/cacheurl.config */
+        path = TSPluginDirGet();
+        path += "/cacheurl.config";
+    } else if (*config_file != '/') {
+        // Relative paths are relative to the config directory
+        path = TSConfigDirGet();
+        path += "/";
+        path += config_file;
+    } else {
+        // Absolute path ...
+        path = config_file;
+    }
+
+    TSDebug(PLUGIN_NAME, "Opening config file: %s", path.c_str());
+    fh = TSfopen(path.c_str(), "r");
+
+    if (!fh) {
+        TSError("[%s] Unable to open %s. No patterns will be loaded\n",
+                PLUGIN_NAME, path.c_str());
+        return prl;
+    }
+
+    while (TSfgets(fh, buffer, sizeof(buffer) - 1)) {
+        lineno++;
+        if (*buffer == '#') {
+            /* # Comments, only at line beginning */
+            continue;
+        }
+        eol = strstr(buffer, "\n");
+        if (eol) {
+            *eol = 0; /* Terminate string at newline */
+        } else {
+            /* Malformed line - skip */
+            continue;
+        }
+        /* Split line into two parts based on whitespace */
+        /* Find first whitespace */
+        spstart = strstr(buffer, " ");
+        if (!spstart) {
+            spstart = strstr(buffer, "\t");
+        }
+        if (!spstart) {
+            TSError("[%s] ERROR: Invalid format on line %d. Skipping\n",
+                    PLUGIN_NAME, lineno);
+            continue;
+        }
+        /* Find part of the line after any whitespace */
+        spend = spstart + 1;
+        while(*spend == ' ' || *spend == '\t') {
+            spend++;
+        }
+        if (*spend == 0) {
+            /* We reached the end of the string without any non-whitepace */
+            TSError("[%s] ERROR: Invalid format on line %d. Skipping\n",
+                    PLUGIN_NAME, lineno);
+            continue;
+        }
+
+        *spstart = 0;
+        /* We have the pattern/replacement, now do precompilation.
+         * buffer is the first part of the line. spend is the second part just
+         * after the whitespace */
+        TSDebug(PLUGIN_NAME, "Adding pattern/replacement pair: '%s' -> '%s'", buffer, spend);
+        retval = regex_compile(&info, buffer, spend);
+        if (!retval) {
+            TSError("[%s] Error precompiling regex/replacement. Skipping.\n",
+                    PLUGIN_NAME);
+        }
+
+        prl->pr.push_back(info);
+    }
+    TSfclose(fh);
+
+    TSDebug(PLUGIN_NAME, "loaded %u regexes", (unsigned)prl->pr.size());
+    return prl;
+}
+
+static int rewrite_cacheurl(pr_list *prl, TSHttpTxn txnp) {
+    int ok = 1;
+    char *newurl = 0;
+    int retval;
+
+    char *url;
+    int url_length;
+    if (ok) {
+        url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length);
+        if (!url) {
+            TSError("[%s] couldn't retrieve request url\n",
+                    PLUGIN_NAME);
+            ok = 0;
+        }
+    }
+
+    if (ok) {
+        for (std::vector<regex_info *>::iterator info = prl->pr.begin(); info != prl->pr.end(); ++info) {
+            retval = regex_substitute(&newurl, url, *info);
+            if (retval) {
+                /* Successful match/substitution */
+                break;
+            }
+        }
+        if (newurl) {
+            TSDebug(PLUGIN_NAME, "Rewriting cache URL for %s to %s", url, newurl);
+            if (TSCacheUrlSet(txnp, newurl, strlen(newurl))
+                    != TS_SUCCESS) {
+                TSError("[%s] Unable to modify cache url from "
+                        "%s to %s\n", PLUGIN_NAME, url, newurl);
+                ok = 0;
+            }
+        }
+    }
+    /* Clean up */
+    if (url) TSfree(url);
+    if (newurl) TSfree(newurl);
+    return ok;
+}
+
+static int handle_hook(TSCont contp, TSEvent event, void *edata) {
+    TSHttpTxn txnp = (TSHttpTxn) edata;
+    pr_list *prl;
+    int ok = 1;
+
+    prl = (pr_list *)TSContDataGet(contp);
+
+    switch (event) {
+        case TS_EVENT_HTTP_READ_REQUEST_HDR:
+            ok = rewrite_cacheurl(prl, txnp);
+            TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+            break;
+        default:
+            TSAssert(!"Unexpected event");
+            ok = 0;
+            break;
+    }
+
+    return ok;
+}
+
+/* Generic error message function for errors in plugin initialization */
+static void initialization_error(const char *msg) {
+    TSError("[%s] %s\n", PLUGIN_NAME, msg);
+    TSError("[%s] Unable to initialize plugin (disabled).\n", PLUGIN_NAME);
+}
+
+TSReturnCode TSRemapInit(TSRemapInterface *api_info, char *errbuf,
+    int errbuf_size) {
+    if (!api_info) {
+        strncpy(errbuf, "[tsremap_init] Invalid TSRemapInterface argument",
+                errbuf_size - 1);
+        return TS_ERROR;
+    }
+
+    if (api_info->size < sizeof(TSRemapInterface)) {
+        strncpy(errbuf,
+                "[tsremap_init] Incorrect size of TSRemapInterface structure",
+                errbuf_size - 1);
+        return TS_ERROR;
+    }
+
+    if (api_info->tsremap_version < TSREMAP_VERSION) {
+        snprintf(errbuf, errbuf_size - 1,
+            "[tsremap_init] Incorrect API version %ld.%ld",
+            api_info->tsremap_version >> 16,
+            (api_info->tsremap_version & 0xffff));
+        return TS_ERROR;
+    }
+
+    TSDebug(PLUGIN_NAME, "remap plugin is successfully initialized");
+    return TS_SUCCESS;
+}
+
+TSReturnCode TSRemapNewInstance(int argc, char* argv[], void** ih, char* errbuf ATS_UNUSED,
+                                int errbuf_size ATS_UNUSED) {
+
+    *ih = load_config_file(argc > 2 ? argv[2] : NULL);
+    return TS_SUCCESS;
+}
+
+
+void TSRemapDeleteInstance(void *ih) {
+    TSDebug(PLUGIN_NAME, "Deleting remap instance");
+    pr_list *prl = (pr_list *)ih;
+    TSfree(prl);
+    delete prl;
+}
+
+TSRemapStatus TSRemapDoRemap(void* ih, TSHttpTxn rh, TSRemapRequestInfo *rri ATS_UNUSED) {
+    int ok;
+    ok = rewrite_cacheurl((pr_list *)ih, rh);
+    if (ok) {
+        return TSREMAP_NO_REMAP;
+    } else {
+        return TSREMAP_ERROR;
+    }
+}
+
+void TSPluginInit(int argc, const char *argv[]) {
+    TSPluginRegistrationInfo info;
+    TSCont contp;
+    pr_list *prl;
+
+    info.plugin_name = (char *)PLUGIN_NAME;
+    info.vendor_name = (char *)"Apache Software Foundation";
+    info.support_email = (char *)"dev@trafficserver.apache.org";
+
+    if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+        initialization_error("Plugin registration failed.");
+        return;
+    }
+
+    prl = load_config_file(argc > 1 ? argv[1] : NULL);
+
+    contp = TSContCreate((TSEventFunc)handle_hook, NULL);
+    /* Store the pattern replacement list in the continuation */
+    TSContDataSet(contp, prl);
+    TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, contp);
+}