blob: 1300742372a9799adb7b014fd49c5c74c472338b [file]
/*
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.
*/
//////////////////////////////////////////////////////////////////////////////////////////////
// header_rewrite: YTS plugin to do header rewrites
// --------------
//
//
#include <fstream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <ts/ts.h>
#include <ts/remap.h>
#include "parser.h"
#include "ruleset.h"
#include "resources.h"
// "Defines"
const char* PLUGIN_NAME = "header_rewrite";
const char* PLUGIN_NAME_DBG = "header_rewrite_dbg";
static const char* DEFAULT_CONF_PATH = "/usr/local/etc/header_rewrite/";
// Global holding the rulesets and resource IDs
static RuleSet* all_rules[TS_HTTP_LAST_HOOK+1];
static ResourceIDs all_resids[TS_HTTP_LAST_HOOK+1];
// Helper function to add a rule to the rulesets
static bool
add_rule(RuleSet* rule) {
if (rule && rule->has_operator()) {
TSDebug(PLUGIN_NAME, "Adding rule to hook=%d\n", rule->get_hook());
if (NULL == all_rules[rule->get_hook()]) {
all_rules[rule->get_hook()] = rule;
} else {
all_rules[rule->get_hook()]->append(rule);
}
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Simple "config" parser, this modifies the global above. ToDo: What happens
// on a "config" reload?
//
// Note that this isn't particularly efficient, but it's a startup time cost
// anyways (or reload for remap.config), so not really in the critical path.
//
bool
parse_config(const std::string fname, TSHttpHookID default_hook)
{
RuleSet* rule = NULL;
std::string filename = fname;
std::ifstream f;
int lineno = 0;
// Try appending the default conf path if the fname doesn't exist.
if (0 != access(filename.c_str(), R_OK)) {
filename = DEFAULT_CONF_PATH;
filename += fname;
}
f.open(filename.c_str(), std::ios::in);
if (!f.is_open()) {
TSError("header_rewrite: unable to open %s", filename.c_str());
return false;
}
TSDebug(PLUGIN_NAME, "Loading header_rewrite config from %s", filename.c_str());
while (!f.eof()) {
std::string line;
getline(f, line);
++lineno; // ToDo: we should probably use this for error messages ...
TSDebug(PLUGIN_NAME, "Reading line: %d: %s", lineno, line.c_str());
boost::trim(line);
if (line.empty() || (line[0] == '#'))
continue;
Parser p(line); // Tokenize and parse this line
if (p.empty())
continue;
// If we are at the beginning of a new condition, save away the previous rule (but only if it has operators).
if (p.is_cond() && add_rule(rule))
rule = NULL;
if (NULL == rule) {
rule = new RuleSet();
rule->set_hook(default_hook);
// Special case for specifying the HOOK this rule applies to.
// These can only be at the beginning of a rule, and have an implicit [AND].
if (p.cond_op_is("READ_RESPONSE_HDR_HOOK")) {
rule->set_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
continue;
} else if (p.cond_op_is("READ_REQUEST_HDR_HOOK")) {
rule->set_hook(TS_HTTP_READ_REQUEST_HDR_HOOK);
continue;
} else if (p.cond_op_is("READ_REQUEST_PRE_REMAP_HOOK")) {
rule->set_hook(TS_HTTP_READ_REQUEST_PRE_REMAP_HOOK);
continue;
} else if (p.cond_op_is("SEND_REQUEST_HDR_HOOK")) {
rule->set_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK);
continue;
} else if (p.cond_op_is("SEND_RESPONSE_HDR_HOOK")) {
rule->set_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
continue;
}
}
if (p.is_cond()) {
rule->add_condition(p);
} else {
rule->add_operator(p);
}
}
// Add the last rule (possibly the only rule)
add_rule(rule);
// Collect all resource IDs that we need
for (int i=TS_HTTP_READ_REQUEST_HDR_HOOK; i<TS_HTTP_LAST_HOOK; ++i)
if (all_rules[i])
all_resids[i] = all_rules[i]->get_all_resource_ids();
return true;
}
///////////////////////////////////////////////////////////////////////////////
// Continuation
//
static int
cont_rewrite_headers(TSCont contp, TSEvent event, void *edata)
{
TSDebug(PLUGIN_NAME, "plugin: %d", event);
TSHttpTxn txnp = (TSHttpTxn) edata;
Resources res(txnp, contp);
const RuleSet* rule = NULL;
TSHttpHookID hook = TS_HTTP_LAST_HOOK;
// Get the resources necessary to process this event
switch (event) {
case TS_EVENT_HTTP_READ_RESPONSE_HDR:
hook = TS_HTTP_READ_RESPONSE_HDR_HOOK;
break;
case TS_EVENT_HTTP_READ_REQUEST_HDR:
hook = TS_HTTP_READ_REQUEST_HDR_HOOK;
break;
case TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP:
hook = TS_HTTP_READ_REQUEST_PRE_REMAP_HOOK;
break;
case TS_EVENT_HTTP_SEND_REQUEST_HDR:
hook = TS_HTTP_SEND_REQUEST_HDR_HOOK;
break;
case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
hook = TS_HTTP_SEND_RESPONSE_HDR_HOOK;
break;
default:
TSError("header_rewrite: unknown event for this plugin");
TSDebug(PLUGIN_NAME, "unknown event for this plugin");
break;
}
if (hook != TS_HTTP_LAST_HOOK) {
res.gather(all_resids[hook], hook);
rule = all_rules[hook];
// Evaluation
while (rule) {
if (rule->eval(res)) {
OperModifiers rt = rule->exec(res);
if (rule->last() || (rt & OPER_LAST)) {
break; // Conditional break, force a break with [L]
}
}
rule = rule->next;
}
}
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Initialize the InkAPI plugin for the global hooks we support.
//
void
TSPluginInit(int argc, const char *argv[])
{
TSPluginRegistrationInfo info;
info.plugin_name = (char*)PLUGIN_NAME;
info.vendor_name = (char*)"";
info.support_email = (char*)"";
if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_3_0 , &info)) {
TSError("header_rewrite: plugin registration failed.\n");
}
TSDebug(PLUGIN_NAME, "number of arguments: %d", argc);
if (argc != 2) {
TSError("usage: %s <config-file>\n", argv[0] );
assert(argc == 2);
}
// Initialize the globals
for (int i=TS_HTTP_READ_REQUEST_HDR_HOOK; i<TS_HTTP_LAST_HOOK; ++i) {
all_rules[i] = NULL;
all_resids[i] = RSRC_NONE;
}
// Parse the config file
if (parse_config(argv[1], TS_HTTP_READ_RESPONSE_HDR_HOOK)) {
for (int i=TS_HTTP_READ_REQUEST_HDR_HOOK; i<TS_HTTP_LAST_HOOK; ++i) {
if (all_rules[i]) {
TSDebug(PLUGIN_NAME, "adding hook: %d", i);
TSHttpHookAdd(static_cast<TSHttpHookID>(i), TSContCreate(cont_rewrite_headers, NULL));
}
}
} else {
TSError("header_rewrite: failed to parse configuration file");
}
}
///////////////////////////////////////////////////////////////////////////////
// Initialize the plugin as a remap plugin.
//
TSReturnCode
TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
{
if (!api_info) {
strncpy(errbuf, "[TSRemapInit] - Invalid TSRemapInterface argument", errbuf_size - 1);
return TS_ERROR;
}
if (api_info->size < sizeof(TSRemapInterface)) {
strncpy(errbuf, "[TSRemapInit] - Incorrect size of TSRemapInterface structure", errbuf_size - 1);
return TS_ERROR;
}
if (api_info->tsremap_version < TSREMAP_VERSION) {
snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - 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 */)
{
TSDebug(PLUGIN_NAME, "initializing the remap plugin header_rewrite");
if (argc < 3) {
TSError("Unable to create remap instance, need config file");
return TS_ERROR;
}
// TODO: this is a big ugly, we use a pseudo hook for parsing the config for a
// remap instantiation.
all_rules[TS_REMAP_PSEUDO_HOOK] = NULL;
if (!parse_config(argv[2], TS_REMAP_PSEUDO_HOOK)) {
TSError("Unable to create remap instance");
return TS_ERROR;
}
*ih = all_rules[TS_REMAP_PSEUDO_HOOK];
all_rules[TS_REMAP_PSEUDO_HOOK] = NULL;
TSDebug(PLUGIN_NAME, "successfully initialize the header_rewrite plugin");
return TS_SUCCESS;
}
void
TSRemapDeleteInstance(void *ih)
{
RuleSet* rule = static_cast<RuleSet*>(ih);
delete rule;
}
///////////////////////////////////////////////////////////////////////////////
// This is the main "entry" point for the plugin, called for every request.
//
TSRemapStatus
TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo *rri)
{
TSRemapStatus rval = TSREMAP_NO_REMAP;
if (NULL == ih) {
TSDebug(PLUGIN_NAME, "No Rules configured, falling back to default");
return rval;
} else {
RuleSet* rule = (RuleSet*)ih;
Resources res((TSHttpTxn)rh, rri);
// TODO: This might be suboptimal, do we always need the client request
// headers in a remap plugin?
res.gather(RSRC_CLIENT_REQUEST_HEADERS, TS_REMAP_PSEUDO_HOOK);
// Evaluation
while (rule) {
if (rule->eval(res)) {
OperModifiers rt = rule->exec(res);
if (res.changed_url == true)
rval = TSREMAP_DID_REMAP;
if (rule->last() || (rt & OPER_LAST)) {
break; // Conditional break, force a break with [L]
}
}
rule = rule->next;
}
}
TSDebug(PLUGIN_NAME, "returing with status: %d", rval);
return rval;
}