blob: 903961aa1bbcf87663552028d36b385785eafc40 [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.
*/
//////////////////////////////////////////////////////////////////////////////////////////////
// operators.cc: implementation of the operator classes
//
//
#include <arpa/inet.h>
#include <string.h>
#include "ts/ts.h"
#include "operators.h"
#include "expander.h"
// OperatorConfig
void
OperatorSetConfig::initialize(Parser& p)
{
Operator::initialize(p);
_config = p.get_arg();
if (TS_SUCCESS == TSHttpTxnConfigFind(_config.c_str(), _config.size(), &_key, &_type)) {
_value.set_value(p.get_value());
} else {
_key = TS_CONFIG_NULL;
TSError("%s: no such records config: %s", PLUGIN_NAME, _config.c_str());
}
}
void
OperatorSetConfig::exec(const Resources& res) const
{
if (TS_CONFIG_NULL != _key) {
switch (_type) {
case TS_RECORDDATATYPE_INT:
if (TS_SUCCESS == TSHttpTxnConfigIntSet(res.txnp, _key, _value.get_int_value())) {
TSDebug(PLUGIN_NAME, "OperatorSetConfig::exec() invoked on %s=%d", _config.c_str(), _value.get_int_value());
}
break;
case TS_RECORDDATATYPE_FLOAT:
if (TS_SUCCESS == TSHttpTxnConfigFloatSet(res.txnp, _key, _value.get_float_value())) {
TSDebug(PLUGIN_NAME, "OperatorSetConfig::exec() invoked on %s=%f", _config.c_str(), _value.get_float_value());
}
break;
case TS_RECORDDATATYPE_STRING:
if (TS_SUCCESS == TSHttpTxnConfigStringSet(res.txnp, _key, _value.get_value().c_str(), _value.size())) {
TSDebug(PLUGIN_NAME, "OperatorSetConfig::exec() invoked on %s=%s", _config.c_str(), _value.get_value().c_str());
}
break;
default:
TSError("%s: unknown data type, whut?", PLUGIN_NAME);
break;
}
}
}
// OperatorSetStatus
void
OperatorSetStatus::initialize(Parser& p)
{
Operator::initialize(p);
_status.set_value(p.get_arg());
if (NULL == (_reason = TSHttpHdrReasonLookup((TSHttpStatus)_status.get_int_value()))) {
TSError("%s: unknown status %d", PLUGIN_NAME, _status.get_int_value());
_reason_len = 0;
} else {
_reason_len = strlen(_reason);
}
require_resources(RSRC_SERVER_RESPONSE_HEADERS);
require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
require_resources(RSRC_RESPONSE_STATUS);
}
void
OperatorSetStatus::initialize_hooks()
{
add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
}
void
OperatorSetStatus::exec(const Resources& res) const
{
if (res.bufp && res.hdr_loc) {
TSHttpHdrStatusSet(res.bufp, res.hdr_loc, (TSHttpStatus)_status.get_int_value());
if (_reason && _reason_len > 0)
TSHttpHdrReasonSet(res.bufp, res.hdr_loc, _reason, _reason_len);
}
}
// OperatorSetStatusReason
void
OperatorSetStatusReason::initialize(Parser& p)
{
Operator::initialize(p);
_reason.set_value(p.get_arg());
require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
require_resources(RSRC_SERVER_RESPONSE_HEADERS);
}
void
OperatorSetStatusReason::initialize_hooks()
{
add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
}
void
OperatorSetStatusReason::exec(const Resources& res) const
{
if (res.bufp && res.hdr_loc) {
std::string reason;
_reason.append_value(reason, res);
if (reason.size() > 0) {
TSDebug(PLUGIN_NAME, "Setting Status Reason to %s", reason.c_str());
TSHttpHdrReasonSet(res.bufp, res.hdr_loc, reason.c_str(), reason.size());
}
}
}
/// TODO and XXX: These currently only support when running as remap plugin.
// OperatorSetDestination
void
OperatorSetDestination::initialize(Parser& p)
{
Operator::initialize(p);
_url_qual = parse_url_qualifier(p.get_arg());
_value.set_value(p.get_value());
// TODO: What resources would we require here?
}
void
OperatorSetDestination::exec(const Resources& res) const
{
if (res._rri) {
std::string value;
// Never set an empty destination value (I don't think that ever makes sense?)
switch (_url_qual) {
case URL_QUAL_HOST:
_value.append_value(value, res);
if (value.empty()) {
TSDebug(PLUGIN_NAME, "Would set destination HOST to an empty value, skipping");
} else {
const_cast<Resources&>(res).changed_url = true;
TSUrlHostSet(res._rri->requestBufp, res._rri->requestUrl, value.c_str(), value.size());
TSDebug(PLUGIN_NAME, "OperatorSetHost::exec() invoked with HOST: %s", value.c_str());
}
break;
case URL_QUAL_PATH:
_value.append_value(value, res);
if (value.empty()) {
TSDebug(PLUGIN_NAME, "Would set destination PATH to an empty value, skipping");
} else {
const_cast<Resources&>(res).changed_url = true;
TSUrlPathSet(res._rri->requestBufp, res._rri->requestUrl, value.c_str(), value.size());
TSDebug(PLUGIN_NAME, "OperatorSetHost::exec() invoked with PATH: %s", value.c_str());
}
break;
case URL_QUAL_QUERY:
_value.append_value(value, res);
if (value.empty()) {
TSDebug(PLUGIN_NAME, "Would set destination QUERY to an empty value, skipping");
} else {
//1.6.4--Support for preserving QSA in case of set-destination
if (get_oper_modifiers() & OPER_QSA) {
int query_len = 0;
const char* query = TSUrlHttpQueryGet(res._rri->requestBufp, res._rri->requestUrl, &query_len);
TSDebug(PLUGIN_NAME, "QSA mode, append original query string: %.*s", query_len, query);
//std::string connector = (value.find("?") == std::string::npos)? "?" : "&";
value.append("&");
value.append(query, query_len);
}
const_cast<Resources&>(res).changed_url = true;
TSUrlHttpQuerySet(res._rri->requestBufp, res._rri->requestUrl, value.c_str(), value.size());
TSDebug(PLUGIN_NAME, "OperatorSetHost::exec() invoked with QUERY: %s", value.c_str());
}
break;
case URL_QUAL_PORT:
if (_value.get_int_value() <= 0) {
TSDebug(PLUGIN_NAME, "Would set destination PORT to an invalid range, skipping");
} else {
const_cast<Resources&>(res).changed_url = true;
TSUrlPortSet(res._rri->requestBufp, res._rri->requestUrl, _value.get_int_value());
TSDebug(PLUGIN_NAME, "OperatorSetHost::exec() invoked with PORT: %d", _value.get_int_value());
}
break;
case URL_QUAL_URL:
// TODO: Implement URL parser.
break;
default:
break;
}
} else {
// TODO: Handle the non-remap case here (InkAPI hooks)
}
}
/// TODO and XXX: These currently only support when running as remap plugin.
// OperatorSetRedirect
void
OperatorSetRedirect::initialize(Parser& p)
{
Operator::initialize(p);
_status.set_value(p.get_arg());
_location.set_value(p.get_value());
if ((_status.get_int_value() != (int)TS_HTTP_STATUS_MOVED_PERMANENTLY) &&
(_status.get_int_value() != (int)TS_HTTP_STATUS_MOVED_TEMPORARILY)) {
TSError("%s: unsupported redirect status %d", PLUGIN_NAME, _status.get_int_value());
}
require_resources(RSRC_SERVER_RESPONSE_HEADERS);
require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
require_resources(RSRC_RESPONSE_STATUS);
}
void
OperatorSetRedirect::exec(const Resources& res) const
{
if (res._rri) {
if (res.bufp && res.hdr_loc) {
std::string value;
_location.append_value(value, res);
// Replace %{PATH} to original path
size_t pos_path = 0;
if ((pos_path = value.find("%{PATH}")) != std::string::npos) {
value.erase(pos_path, 7); // erase %{PATH} from the rewritten to url
int path_len = 0;
const char *path = TSUrlPathGet(res._rri->requestBufp, res._rri->requestUrl, &path_len);
if (path_len > 0) {
TSDebug(PLUGIN_NAME, "Find %%{PATH} in redirect url, replace it with: %.*s", path_len, path);
value.insert(pos_path, path, path_len);
}
}
// Append the original query string
int query_len = 0;
const char *query = TSUrlHttpQueryGet(res._rri->requestBufp, res._rri->requestUrl, &query_len);
if ((get_oper_modifiers() & OPER_QSA) && (query_len > 0)) {
TSDebug(PLUGIN_NAME, "QSA mode, append original query string: %.*s", query_len, query);
std::string connector = (value.find("?") == std::string::npos)? "?" : "&";
value.append(connector);
value.append(query, query_len);
}
TSHttpTxnSetHttpRetStatus(res.txnp,(TSHttpStatus)_status.get_int_value());
//TSHttpHdrStatusSet(res.bufp, res.hdr_loc, (TSHttpStatus)_status.get_int_value());
const char *start = value.c_str();
const char *end = value.size() + start;
TSUrlParse(res._rri->requestBufp, res._rri->requestUrl, &start, end);
TSDebug(PLUGIN_NAME, "OperatorSetRedirect::exec() invoked with destination=%s and status code=%d",
value.c_str(), _status.get_int_value());
}
} else {
// TODO: Handle the non-remap case here (InkAPI hooks)
}
}
// OperatorSetTimeoutOut
void
OperatorSetTimeoutOut::initialize(Parser& p)
{
Operator::initialize(p);
if (p.get_arg() == "active") {
_type = TO_OUT_ACTIVE;
} else if (p.get_arg() == "inactive") {
_type = TO_OUT_INACTIVE;
} else if (p.get_arg() == "connect") {
_type = TO_OUT_CONNECT;
} else if (p.get_arg() == "dns") {
_type = TO_OUT_DNS;
} else {
_type = TO_OUT_UNDEFINED;
TSError("%s: unsupported timeout qualifier: %s", PLUGIN_NAME, p.get_arg().c_str());
}
_timeout.set_value(p.get_value());
}
void
OperatorSetTimeoutOut::exec(const Resources& res) const
{
switch (_type) {
case TO_OUT_ACTIVE:
TSDebug(PLUGIN_NAME, "OperatorSetTimeoutOut::exec(active, %d)", _timeout.get_int_value());
TSHttpTxnActiveTimeoutSet(res.txnp, _timeout.get_int_value());
break;
case TO_OUT_INACTIVE:
TSDebug(PLUGIN_NAME, "OperatorSetTimeoutOut::exec(inactive, %d)", _timeout.get_int_value());
TSHttpTxnNoActivityTimeoutSet(res.txnp, _timeout.get_int_value());
break;
case TO_OUT_CONNECT:
TSDebug(PLUGIN_NAME, "OperatorSetTimeoutOut::exec(connect, %d)", _timeout.get_int_value());
TSHttpTxnConnectTimeoutSet(res.txnp, _timeout.get_int_value());
break;
case TO_OUT_DNS:
TSDebug(PLUGIN_NAME, "OperatorSetTimeoutOut::exec(dns, %d)", _timeout.get_int_value());
TSHttpTxnDNSTimeoutSet(res.txnp, _timeout.get_int_value());
break;
default:
TSError("%s: unsupported timeout", PLUGIN_NAME);
break;
}
}
// OperatorRMHeader
void
OperatorRMHeader::exec(const Resources& res) const
{
TSMLoc field_loc, tmp;
if (res.bufp && res.hdr_loc) {
TSDebug(PLUGIN_NAME, "OperatorRMHeader::exec() invoked on header %s", _header.c_str());
field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, _header.c_str(), _header.size());
while (field_loc) {
TSDebug(PLUGIN_NAME, "\tdeleting header %s", _header.c_str());
tmp = TSMimeHdrFieldNextDup(res.bufp, res.hdr_loc, field_loc);
TSMimeHdrFieldDestroy(res.bufp, res.hdr_loc, field_loc);
TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc);
field_loc = tmp;
}
}
}
// OperatorAddHeader
void
OperatorAddHeader::initialize(Parser& p)
{
OperatorHeaders::initialize(p);
_value.set_value(p.get_value());
}
void
OperatorAddHeader::exec(const Resources& res) const
{
std::string value;
_value.append_value(value, res);
if (_value.need_expansion()) {
VariableExpander ve(value);
value = ve.expand(res);
}
// Never set an empty header (I don't think that ever makes sense?)
if (value.empty()) {
TSDebug(PLUGIN_NAME, "Would set header %s to an empty value, skipping", _header.c_str());
return;
}
if (res.bufp && res.hdr_loc) {
TSDebug(PLUGIN_NAME, "OperatorAddHeader::exec() invoked on header %s: %s", _header.c_str(), value.c_str());
TSMLoc field_loc;
if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, _header.c_str(), _header.size(), &field_loc)) {
if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) {
TSDebug(PLUGIN_NAME, " adding header %s", _header.c_str());
TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc);
}
TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc);
}
}
}
// OperatorSetHeader
void
OperatorSetHeader::initialize(Parser& p)
{
OperatorHeaders::initialize(p);
_value.set_value(p.get_value());
}
void
OperatorSetHeader::exec(const Resources& res) const
{
std::string value;
_value.append_value(value, res);
// Never set an empty header (I don't think that ever makes sense?)
if (value.empty()) {
TSDebug(PLUGIN_NAME, "Would set header %s to an empty value, skipping", _header.c_str());
return;
}
if (res.bufp && res.hdr_loc) {
TSMLoc field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, _header.c_str(), _header.size());
TSDebug(PLUGIN_NAME, "OperatorSetHeader::exec() invoked on header %s: %s", _header.c_str(), value.c_str());
if (!field_loc) {
// No existing header, so create one
if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, _header.c_str(), _header.size(), &field_loc)) {
if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) {
TSDebug(PLUGIN_NAME, " adding header %s", _header.c_str());
TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc);
}
TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc);
}
} else {
TSMLoc tmp = NULL;
bool first = true;
while (field_loc) {
if (first) {
first = false;
if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) {
TSDebug(PLUGIN_NAME, " overwriting header %s", _header.c_str());
}
} else {
TSMimeHdrFieldDestroy(res.bufp, res.hdr_loc, field_loc);
}
tmp = TSMimeHdrFieldNextDup(res.bufp, res.hdr_loc, field_loc);
TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc);
field_loc = tmp;
}
}
}
}
// OperatorCounter
void
OperatorCounter::initialize(Parser& p) {
Operator::initialize(p);
_counter_name = p.get_arg();
// Sanity
if (_counter_name.length() == 0) {
TSError("%s: counter name is empty", PLUGIN_NAME);
return;
}
// Check if counter already created by another rule
if (TSStatFindName(_counter_name.c_str(), &_counter) == TS_ERROR) {
_counter = TSStatCreate(_counter_name.c_str(), TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
if (_counter == TS_ERROR) {
TSError("%s: TSStatCreate() failed. Can't create counter: %s", PLUGIN_NAME, _counter_name.c_str());
return;
}
TSDebug(PLUGIN_NAME, "OperatorCounter::initialize(%s) created counter with id: %d", _counter_name.c_str(), _counter);
} else {
TSDebug(PLUGIN_NAME, "OperatorCounter::initialize(%s) reusing id: %d", _counter_name.c_str(), _counter);
}
}
void
OperatorCounter::exec(const Resources& /* ATS_UNUSED res */) const
{
// Sanity
if (_counter == TS_ERROR)
return;
TSDebug(PLUGIN_NAME, "OperatorCounter::exec() invoked on counter %s", _counter_name.c_str());
TSStatIntIncrement(_counter, 1);
}