blob: 455dba38e3cec8b5dd9c32f6a1cffe46c364f333 [file] [log] [blame]
/*
* 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);
}