blob: 5515497ca0062e9092f9a6ebe72d938478caf864 [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.
*/
////////////////////////////////////////////////////////////////////////////////
// cookie_remap: ATS plugin to do (simple) cookie based remap rules
// To use this plugin, configure a remap.config rule like
// map http://foo.com http://bar.com @plugin=.../libexec/cookie_remap.so
// @pparam=maps.reg
#include "cookiejar.h"
#include <ts/ts.h>
#include <pcre.h>
#include <ts/remap.h>
#include <yaml-cpp/yaml.h>
#include <string>
#include <vector>
#include <string_view>
#include <cstddef>
#include "hash.h"
#undef FMT_SV
#define FMT_SV(SV) static_cast<int>((SV).size()), (SV).data()
using namespace std;
#define MY_NAME "cookie_remap"
const int OVECCOUNT = 30; // We support $1 - $9 only, and this needs to be 3x that
class UrlComponents
{
public:
UrlComponents(TSRemapRequestInfo *rri, TSHttpTxn txn) : _rri(rri), _txn(txn) {}
std::string const &
path(bool pre_remap)
{
if (_d[pre_remap].path_str.empty()) {
auto urlh = _get_url(pre_remap);
// based on RFC2396, matrix params are part of path segments so
// we will just
// append them to the path
_d[pre_remap].path_str = _get_url_comp(urlh, TSUrlPathGet);
auto matrix = _get_url_comp(urlh, TSUrlHttpParamsGet);
if (!matrix.empty()) {
_d[pre_remap].path_str.append(";", 1).append(matrix);
}
}
return _d[pre_remap].path_str;
}
std::string_view
query(bool pre_remap)
{
if (_d[pre_remap].query.empty()) {
_d[pre_remap].query = _get_url_comp(_get_url(pre_remap), TSUrlHttpQueryGet);
}
return _d[pre_remap].query;
}
std::string_view
from_path()
{
if (_from_path.empty()) {
_UrlHandle urlh{_rri->requestBufp, _rri->mapFromUrl};
_from_path = _get_url_comp(urlh, TSUrlPathGet);
}
return _from_path;
}
std::string_view
url(bool pre_remap)
{
if (_d[pre_remap].url.empty()) {
auto urlh = _get_url(pre_remap);
int length;
auto data = TSUrlStringGet(urlh.bufp, urlh.urlp, &length);
_d[pre_remap].url = std::string_view(data, length);
}
return _d[pre_remap].url;
}
// No copying/moving.
//
UrlComponents(UrlComponents const &) = delete;
UrlComponents &operator=(UrlComponents const &) = delete;
~UrlComponents()
{
// Not calling TSHandleMLocRelease() for the URL TSMLoc pointers because it doesn't do anything.
if (_d[0].url.data() != nullptr) {
TSfree(const_cast<char *>(_d[0].url.data()));
}
if (_d[1].url.data() != nullptr) {
TSfree(const_cast<char *>(_d[1].url.data()));
}
}
private:
TSRemapRequestInfo *_rri;
TSHttpTxn _txn;
struct _UrlHandle {
TSMBuffer bufp = nullptr;
TSMLoc urlp;
};
// Buffer any data that's likely to be used more than once.
struct _Data {
_UrlHandle urlh;
std::string path_str;
std::string_view url;
std::string_view query;
};
// index 0 - remapped
// index 1 - pre-remap
//
_Data _d[2];
std::string_view _from_path;
_UrlHandle
_get_url(bool pre_remap)
{
_UrlHandle h = _d[pre_remap].urlh;
if (!h.bufp) {
if (pre_remap) {
if (TSHttpTxnPristineUrlGet(_txn, &h.bufp, &h.urlp) != TS_SUCCESS) {
TSError("%s: Plugin is unable to get pristine url", MY_NAME);
return _UrlHandle();
}
} else {
h.bufp = _rri->requestBufp;
h.urlp = _rri->requestUrl;
}
_d[pre_remap].urlh = h;
}
return h;
}
static std::string_view
_get_url_comp(_UrlHandle urlh, char const *(*comp_func)(TSMBuffer, TSMLoc, int *))
{
int length;
auto data = comp_func(urlh.bufp, urlh.urlp, &length);
return std::string_view(data, length);
}
};
enum operation_type { UNKNOWN = -1, EXISTS = 1, NOTEXISTS, REGEXP, STRING, BUCKET };
enum target_type {
COOKIE = 1,
URI, // URI = PATH + QUERY
PRE_REMAP_URI,
UNKNOWN_TARGET
};
/***************************************************************************************
Decimal to Hex
converter
This is a template function which returns a char* array filled with hex digits
when
passed to it a number(can work as a decimal to hex conversion)and will work for
signed
and unsigned: char, short, and integer(long) type parameters passed to it.
Shortcomings:It won't work for decimal numbers because of presence of
bitshifting in its algorithm.
Arguments:
* _num is the number to convert to hex
* hdigits two-byte character array, will be populated with the hex number
***************************************************************************************/
template <class type> // template usage to allow multiple types of parameters
void
dec_to_hex(type _num, char *hdigits)
{
const char *hlookup = "0123456789ABCDEF"; // lookup table stores the hex digits into their
// corresponding index.
if (_num < 0) {
_num *= -1; // and make _num positive to clear(zero) the sign bit
}
char mask = 0x000f; // mask will clear(zero) out all the bits except lowest 4
// which represent a single hex digit
hdigits[1] = hlookup[mask & _num];
hdigits[0] = hlookup[mask & (_num >> 4)];
return;
}
void
urlencode(std::string &str)
{
auto orig = str.size();
auto enc = orig;
for (auto c : str) {
if (!isalnum(c)) {
enc += 2;
}
}
if (enc == orig) {
// No changes needed.
return;
}
str.resize(enc);
while (orig--) {
if (!isalnum(str[orig])) {
enc -= 3;
dec_to_hex(str[orig], &(str[enc + 1]));
str[enc] = '%';
} else {
str[--enc] = str[orig];
}
}
}
//----------------------------------------------------------------------------
class subop
{
public:
subop()
: cookie(""),
operation(""),
str_match(""),
bucket("")
{
TSDebug(MY_NAME, "subop constructor called");
}
~subop()
{
TSDebug(MY_NAME, "subop destructor called");
if (regex) {
pcre_free(regex);
}
if (regex_extra) {
pcre_free(regex_extra);
}
}
bool
empty() const
{
return (cookie == "" && operation == "" && op_type == UNKNOWN);
}
void
setCookieName(const std::string &s)
{
cookie = s;
}
const std::string &
getCookieName() const
{
return cookie;
}
const std::string &
getOperation() const
{
return operation;
}
operation_type
getOpType() const
{
return op_type;
}
target_type
getTargetType() const
{
return target;
}
void
setOperation(const std::string &s)
{
operation = s;
if (operation == "string") {
op_type = STRING;
}
if (operation == "regex") {
op_type = REGEXP;
}
if (operation == "exists") {
op_type = EXISTS;
}
if (operation == "not exists") {
op_type = NOTEXISTS;
}
if (operation == "bucket") {
op_type = BUCKET;
}
}
void
setTarget(const std::string &s)
{
if (s == "uri") {
target = URI;
} else if (s == "puri") {
target = PRE_REMAP_URI;
} else {
target = COOKIE;
}
}
void
setStringMatch(const std::string &s)
{
op_type = STRING;
str_match = s;
}
const std::string &
getStringMatch() const
{
return str_match;
}
void
setBucket(const std::string &s)
{
int start_pos = s.find('/');
op_type = BUCKET;
bucket = s;
how_many = atoi(bucket.substr(0, start_pos).c_str());
out_of = atoi(bucket.substr(start_pos + 1).c_str());
}
int
bucketGetTaking() const
{
return how_many;
}
int
bucketOutOf() const
{
return out_of;
}
bool
setRegexMatch(const std::string &s)
{
const char *error_comp = nullptr;
const char *error_study = nullptr;
int erroffset;
op_type = REGEXP;
regex_string = s;
regex = pcre_compile(regex_string.c_str(), 0, &error_comp, &erroffset, nullptr);
if (regex == nullptr) {
return false;
}
regex_extra = pcre_study(regex, 0, &error_study);
if ((regex_extra == nullptr) && (error_study != nullptr)) {
return false;
}
if (pcre_fullinfo(regex, regex_extra, PCRE_INFO_CAPTURECOUNT, &regex_ccount) != 0) {
return false;
}
return true;
}
const std::string &
getRegexString() const
{
return regex_string;
}
int
getRegexCcount() const
{
return regex_ccount;
}
int
regexMatch(const char *str, int len, int ovector[]) const
{
return pcre_exec(regex, // the compiled pattern
regex_extra, // Extra data from study (maybe)
str, // the subject std::string
len, // the length of the subject
0, // start at offset 0 in the subject
0, // default options
ovector, // output vector for substring information
OVECCOUNT); // number of elements in the output vector
};
void
printSubOp() const
{
TSDebug(MY_NAME, "\t+++subop+++");
TSDebug(MY_NAME, "\t\tcookie: %s", cookie.c_str());
TSDebug(MY_NAME, "\t\toperation: %s", operation.c_str());
if (str_match.size() > 0) {
TSDebug(MY_NAME, "\t\tmatching: %s", str_match.c_str());
}
if (regex) {
TSDebug(MY_NAME, "\t\tregex: %s", regex_string.c_str());
}
if (bucket.size() > 0) {
TSDebug(MY_NAME, "\t\tbucket: %s", bucket.c_str());
TSDebug(MY_NAME, "\t\ttaking: %d", how_many);
TSDebug(MY_NAME, "\t\tout of: %d", out_of);
}
}
private:
std::string cookie;
std::string operation;
enum operation_type op_type = UNKNOWN;
enum target_type target = UNKNOWN_TARGET;
std::string str_match;
pcre *regex = nullptr;
pcre_extra *regex_extra = nullptr;
std::string regex_string;
int regex_ccount = 0;
std::string bucket;
unsigned int how_many = 0;
unsigned int out_of = 0;
};
using SubOpQueue = std::vector<const subop *>;
//----------------------------------------------------------------------------
class op
{
public:
op() { TSDebug(MY_NAME, "op constructor called"); }
~op()
{
TSDebug(MY_NAME, "op destructor called");
for (auto &subop : subops) {
delete subop;
}
}
void
addSubOp(const subop *s)
{
subops.push_back(s);
}
void
setSendTo(const std::string &s)
{
sendto = s;
}
const std::string &
getSendTo() const
{
return sendto;
}
void
setElseSendTo(const std::string &s)
{
else_sendto = s;
}
void
setStatus(const std::string &s)
{
if (else_sendto.size() > 0) {
else_status = static_cast<TSHttpStatus>(atoi(s.c_str()));
} else {
status = static_cast<TSHttpStatus>(atoi(s.c_str()));
}
}
void
setElseStatus(const std::string &s)
{
else_status = static_cast<TSHttpStatus>(atoi(s.c_str()));
}
void
printOp() const
{
TSDebug(MY_NAME, "++++operation++++");
TSDebug(MY_NAME, "sending to: %s", sendto.c_str());
TSDebug(MY_NAME, "if these operations match: ");
for (auto subop : subops) {
subop->printSubOp();
}
if (else_sendto.size() > 0) {
TSDebug(MY_NAME, "else: %s", else_sendto.c_str());
}
}
bool
process(CookieJar &jar, std::string &dest, TSHttpStatus &retstat, TSRemapRequestInfo *rri, UrlComponents &req_url) const
{
if (sendto == "") {
return false; // guessing every operation must have a
// sendto url???
}
int retval = 1;
bool cookie_found = false;
std::string c;
std::string cookie_data;
std::string object_name; // name of the thing being processed,
// cookie, or
// request url
TSDebug(MY_NAME, "starting to process a new operation");
for (auto subop : subops) {
// subop* s = *it;
int subop_type = subop->getOpType();
target_type target = subop->getTargetType();
c = subop->getCookieName();
if (c.length()) {
TSDebug(MY_NAME, "processing cookie: %s", c.c_str());
size_t period_pos = c.find_first_of('.');
if (period_pos == std::string::npos) { // not a sublevel
// cookie name
TSDebug(MY_NAME, "processing non-sublevel cookie");
cookie_found = jar.get_full(c, cookie_data);
TSDebug(MY_NAME, "full cookie: %s", cookie_data.c_str());
object_name = c;
} else { // is in the format FOO.BAR
std::string cookie_main = c.substr(0, period_pos);
std::string cookie_subkey = c.substr(period_pos + 1);
TSDebug(MY_NAME, "processing sublevel cookie");
TSDebug(MY_NAME, "c key: %s", cookie_main.c_str());
TSDebug(MY_NAME, "c subkey: %s", cookie_subkey.c_str());
cookie_found = jar.get_part(cookie_main, cookie_subkey, cookie_data);
object_name = cookie_main + " . " + cookie_subkey;
}
// invariant: cookie name is in object_name and
// cookie data (if any) is
// in cookie_data
if (cookie_found == false) { // cookie name or sub-key not found
// inside cookies
if (subop_type == NOTEXISTS) {
TSDebug(MY_NAME,
"cookie %s was not "
"found (and we wanted "
"that)",
object_name.c_str());
continue; // we can short
// circuit more
// testing
}
TSDebug(MY_NAME, "cookie %s was not found", object_name.c_str());
retval &= 0;
break;
} else {
// cookie exists
if (subop_type == NOTEXISTS) { // we found the cookie
// but are asking
// for non existence
TSDebug(MY_NAME,
"cookie %s was found, "
"but operation "
"requires "
"non-existence",
object_name.c_str());
retval &= 0;
break;
}
if (subop_type == EXISTS) {
TSDebug(MY_NAME, "cookie %s was found", object_name.c_str()); // got what
// we were
// looking
// for
continue; // we can short
// circuit more
// testing
}
} // handled EXISTS / NOTEXISTS subops
TSDebug(MY_NAME, "processing cookie data: \"%s\"", cookie_data.c_str());
} else if (target != PRE_REMAP_URI) {
target = URI;
}
// INVARIANT: we now have the data from the cookie (if
// any) inside
// cookie_data and we are here because we need
// to continue processing this suboperation in some way
if (!rri) { // too dangerous to continue without the
// rri; hopefully that
// never happens
TSDebug(MY_NAME, "request info structure is "
"empty; can't continue "
"processing this subop");
retval &= 0;
break;
}
// If the user has specified a cookie in his
// suboperation, use the cookie
// data for matching;
// otherwise, use the request uri (path + query)
std::string request_uri; // only set the value if we
// need it; we might
// match the cookie data instead
bool use_url = (target == URI) || (target == PRE_REMAP_URI);
const std::string &string_to_match(use_url ? request_uri : cookie_data);
if (use_url) {
request_uri = req_url.path(target == PRE_REMAP_URI);
TSDebug(MY_NAME, "process req_url.path = %s", request_uri.c_str());
if (request_uri.length() && request_uri[0] != '/') {
request_uri.insert(0, 1, '/');
}
auto query = req_url.query(target == PRE_REMAP_URI);
if (query.size() > 0) {
request_uri += '?';
request_uri += query;
}
object_name = "request uri";
}
// invariant: we've decided at this point what string
// we'll match, if we
// do matching
// OPERATION::string matching
if (subop_type == STRING) {
if (string_to_match == subop->getStringMatch()) {
TSDebug(MY_NAME, "string match succeeded");
continue;
} else {
TSDebug(MY_NAME, "string match failed");
retval &= 0;
break;
}
}
// OPERATION::regex matching
if (subop_type == REGEXP) {
int ovector[OVECCOUNT];
int ret = subop->regexMatch(string_to_match.c_str(), string_to_match.length(), ovector);
if (ret >= 0) {
std::string::size_type pos = sendto.find('$');
std::string::size_type ppos = 0;
dest.erase(); // we only reset dest if
// there is a successful
// regex
// match
dest.reserve(sendto.size() * 2); // Wild guess at this
// time ... is
// sucks we can't precalculate this
// like regex_remap.
TSDebug(MY_NAME, "found %d matches", ret);
TSDebug(MY_NAME,
"successful regex "
"match of: %s with %s "
"rewriting string: %s",
string_to_match.c_str(), subop->getRegexString().c_str(), sendto.c_str());
// replace the $(1-9) in the sendto url
// as necessary
const size_t LAST_IDX_TO_SEARCH(sendto.length() - 2); // otherwise the below loop can
// access "sendto" out of range
while (pos <= LAST_IDX_TO_SEARCH) {
if (isdigit(sendto[pos + 1])) {
int ix = sendto[pos + 1] - '0';
if (ix <= subop->getRegexCcount()) { // Just skip an illegal regex group
dest += sendto.substr(ppos, pos - ppos);
dest += string_to_match.substr(ovector[ix * 2], ovector[ix * 2 + 1] - ovector[ix * 2]);
ppos = pos + 2;
} else {
TSDebug(MY_NAME,
"bad "
"rewriting "
"string, "
"for group "
"%d: %s",
ix, sendto.c_str());
}
}
pos = sendto.find('$', pos + 1);
}
dest += sendto.substr(ppos);
continue; // next subop, please
} else {
TSDebug(MY_NAME,
"could not match "
"regular expression "
"%s to %s",
subop->getRegexString().c_str(), string_to_match.c_str());
retval &= 0;
break;
}
}
// OPERATION::bucket ranges
if (subop_type == BUCKET) {
unsigned int taking = subop->bucketGetTaking();
unsigned int out_of = subop->bucketOutOf();
uint32_t hash;
if (taking == 0 || out_of == 0) {
TSDebug(MY_NAME,
"taking %d out of %d "
"makes no sense?!",
taking, out_of);
retval &= 0;
break;
}
hash = hash_fnv32_buckets(cookie_data.c_str(), cookie_data.size(), out_of);
TSDebug(MY_NAME,
"we hashed this to bucket: %u "
"taking: %u out of: %u",
hash, taking, out_of);
if (hash < taking) {
TSDebug(MY_NAME, "we hashed in the range, yay!");
continue; // we hashed in the range
} else {
TSDebug(MY_NAME, "we didnt hash in the "
"range requested, so "
"sad");
retval &= 0;
break;
}
}
}
if (retval == 1) {
if (dest.size() == 0) { // Unless already set by one of
// the operators (e.g. regex)
dest = sendto;
}
if (status > 0) {
retstat = status;
}
return true;
} else if (else_sendto.size() > 0 && retval == 0) {
dest = else_sendto;
if (else_status > 0) {
retstat = else_status;
}
return true;
} else {
dest = "";
return false;
}
}
private:
SubOpQueue subops{};
std::string sendto{""};
std::string else_sendto{""};
TSHttpStatus status = TS_HTTP_STATUS_NONE;
TSHttpStatus else_status = TS_HTTP_STATUS_NONE;
};
typedef std::pair<std::string, std::string> StringPair;
using OpMap = std::vector<StringPair>;
//----------------------------------------------------------------------------
static bool
build_op(op &o, OpMap const &q)
{
subop *sub = new subop();
// loop through the array of key->value pairs
for (auto const &pr : q) {
std::string const &key = pr.first;
std::string const &val = pr.second;
TSDebug(MY_NAME, "build_op: key=%s val=%s", key.c_str(), val.c_str());
if (key == "cookie") {
if (!sub->empty()) {
TSDebug(MY_NAME, "ERROR: you need to define a connector");
goto error;
}
sub->setCookieName(val);
}
if (key == "sendto" || key == "url") {
o.setSendTo(val);
}
if (key == "else") {
o.setElseSendTo(val);
}
if (key == "status") {
o.setStatus(val);
}
if (key == "operation") {
sub->setOperation(val);
}
if (key == "target") {
sub->setTarget(val);
}
if (key == "match") {
sub->setStringMatch(val);
}
if (key == "regex") {
bool ret = sub->setRegexMatch(val);
if (!ret) {
goto error;
}
}
if (key == "bucket" || key == "hash") {
sub->setBucket(val);
}
if (key == "connector") {
o.addSubOp(sub);
sub = new subop();
}
}
o.addSubOp(sub);
return true;
error:
TSDebug(MY_NAME, "error building operation");
return false;
}
using OpsQueue = std::vector<const op *>;
//----------------------------------------------------------------------------
// init
TSReturnCode
TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
{
return TS_SUCCESS;
}
//----------------------------------------------------------------------------
// initialization of structures from config parameters
TSReturnCode
TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size)
{
if (argc != 3) {
TSError("arguments not equal to 3: %d", argc);
TSDebug(MY_NAME, "arguments not equal to 3: %d", argc);
return TS_ERROR;
}
std::string filename(argv[2]);
try {
YAML::Node config = YAML::LoadFile(filename);
std::unique_ptr<OpsQueue> ops(new OpsQueue);
OpMap op_data;
for (YAML::const_iterator it = config.begin(); it != config.end(); ++it) {
const string &name = it->first.as<std::string>();
YAML::NodeType::value type = it->second.Type();
if (name != "op" || type != YAML::NodeType::Map) {
const string reason = "Top level nodes must be named op and be of type map";
TSError("Invalid YAML Configuration format for cookie_remap: %s, reason: %s", filename.c_str(), reason.c_str());
return TS_ERROR;
}
for (YAML::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
const YAML::Node first = it2->first;
const YAML::Node second = it2->second;
if (second.IsScalar() == false) {
const string reason = "All op nodes must be of type scalar";
TSError("Invalid YAML Configuration format for cookie_remap: %s, reason: %s", filename.c_str(), reason.c_str());
return TS_ERROR;
}
const string &key = first.as<std::string>();
const string &value = second.as<std::string>();
op_data.emplace_back(key, value);
}
if (op_data.size()) {
op *o = new op();
if (!build_op(*o, op_data)) {
delete o;
TSError("building operation, check configuration file: %s", filename.c_str());
return TS_ERROR;
} else {
ops->push_back(o);
}
o->printOp();
op_data.clear();
}
}
TSDebug(MY_NAME, "# of ops: %d", static_cast<int>(ops->size()));
*ih = static_cast<void *>(ops.release());
} catch (const YAML::Exception &e) {
TSError("YAML::Exception %s when parsing YAML config file %s for cookie_remap", e.what(), filename.c_str());
return TS_ERROR;
}
return TS_SUCCESS;
}
namespace
{
std::string
unmatched_path(UrlComponents &req_url, bool pre_remap)
{
std::string path = req_url.path(pre_remap);
std::string_view from_path = req_url.from_path();
std::size_t pos = path.find(from_path);
if (pos != std::string::npos) {
path.erase(pos, from_path.size());
}
TSDebug(MY_NAME, "from_path: %*s", FMT_SV(from_path));
TSDebug(MY_NAME, "%s: %s", pre_remap ? "unmatched_ppath" : "unmatched_path", path.c_str());
return path;
}
int const sub_req_url_id = 0;
int const sub_req_purl_id = -1;
int const sub_path_id = -2;
int const sub_ppath_id = -3;
int const sub_unmatched_path_id = -4;
int const sub_unmatched_ppath_id = -5;
int const sub_url_encode_id = -6;
struct CompNext {
std::string_view const comp;
int const *const next;
CompNext(std::string_view p, int const *n) : comp(p), next(n) {}
};
struct {
int count = 2;
CompNext o1{"ath", &sub_unmatched_path_id};
CompNext o2{"path", &sub_unmatched_ppath_id};
} const sub_unmatched;
struct {
int count = 2;
CompNext o1{"ath", &sub_path_id};
CompNext o2{"path", &sub_ppath_id};
} const sub_p;
struct {
int count = 2;
CompNext o1{"url", &sub_req_url_id};
CompNext o2{"purl", &sub_req_purl_id};
} const sub_cr_req;
struct {
int count = 2;
CompNext o1{"req_", &sub_cr_req.count};
CompNext o2{"urlencode(", &sub_url_encode_id};
} const sub_cr;
struct {
int count = 3;
CompNext o1{"cr_", &sub_cr.count};
CompNext o2{"p", &sub_p.count};
CompNext o3{"unmatched_p", &sub_unmatched.count};
} const sub;
int
sub_lookup(char const *targ, int targ_len)
{
int count = sub.count;
auto opt = &sub.o1;
for (;;) {
while ((targ_len < static_cast<int>(opt->comp.size())) || (std::string_view(targ, opt->comp.size()) != opt->comp)) {
if (!--count) {
return 1; // Failed lookup, return some positive numver.
}
++opt;
}
count = *opt->next;
if (count <= 0) {
break;
}
targ += opt->comp.size();
targ_len -= opt->comp.size();
opt = reinterpret_cast<CompNext const *>(reinterpret_cast<char const *>(opt->next) + offsetof(decltype(sub), o1));
}
return count;
}
} // end anonymous namespace
//----------------------------------------------------------------------------
// called whenever we need to perform substitutions on a string; used to replace
// things like
// $path, $ppath, $unmatched_path, $unmatched_ppath, $cr_req_url, $cr_req_purl, and $cr_url_encode
void
cr_substitutions(std::string &obj, UrlComponents &req_url)
{
{
auto path = req_url.path(false);
TSDebug(MY_NAME, "x req_url.path: %*s %d", FMT_SV(path), static_cast<int>(path.size()));
auto url = req_url.url(false);
TSDebug(MY_NAME, "x req_url.url: %*s %d", FMT_SV(url), static_cast<int>(url.size()));
}
auto npos = std::string::npos;
std::string tmp;
std::size_t pos = 0;
for (;;) {
pos = obj.find('$', pos);
if (npos == pos) {
break;
}
std::string_view variable, value;
switch (sub_lookup(obj.data() + pos + 1, static_cast<int>(obj.size()) - pos - 1)) {
case sub_req_url_id: {
variable = "$cr_req_url";
value = req_url.url(false);
} break;
case sub_req_purl_id: {
variable = "$cr_req_purl";
value = req_url.url(true);
} break;
case sub_path_id: {
variable = "$path";
value = req_url.path(false);
} break;
case sub_ppath_id: {
variable = "$ppath";
value = req_url.path(true);
} break;
case sub_unmatched_path_id: {
variable = "$unmatched_path";
tmp = unmatched_path(req_url, false);
value = tmp;
} break;
case sub_unmatched_ppath_id: {
variable = "$unmatched_ppath";
tmp = unmatched_path(req_url, true);
value = tmp;
} break;
case sub_url_encode_id: {
std::size_t bpos = pos + sizeof("cr_urlencode(") - 1;
std::size_t epos = obj.find(')', bpos);
if (npos == epos) {
variable = "$";
value = variable;
} else {
variable = std::string_view(obj.data() + pos, epos + 1 - pos);
tmp = obj.substr(bpos, epos - bpos);
cr_substitutions(tmp, req_url);
urlencode(tmp);
value = tmp;
}
} break;
default: {
variable = "$";
value = variable;
} break;
} // end switch
TSDebug(MY_NAME, "%*s => %*s", FMT_SV(variable), FMT_SV(value));
obj.replace(pos, variable.size(), value);
pos += value.size();
} // end for (;;)
}
//----------------------------------------------------------------------------
// called on each request
// returns 0 on error or failure to match rules, 1 on a match
TSRemapStatus
TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
{
OpsQueue *ops = static_cast<OpsQueue *>(ih);
TSHttpStatus status = TS_HTTP_STATUS_NONE;
UrlComponents req_url{rri, txnp};
if (ops == (OpsQueue *)nullptr) {
TSError("serious error with encountered while attempting to "
"cookie_remap");
TSDebug(MY_NAME, "serious error with encountered while attempting to remap");
return TSREMAP_NO_REMAP;
}
// get any query params..we will append that to the answer (possibly)
std::string client_req_query_params;
auto query = req_url.query(false);
if (!query.empty()) {
client_req_query_params = "?";
client_req_query_params += query;
}
TSDebug(MY_NAME, "Query Parameters: %s", client_req_query_params.c_str());
std::string rewrite_to;
char cookie_str[] = "Cookie";
TSMLoc field = TSMimeHdrFieldFind(rri->requestBufp, rri->requestHdrp, cookie_str, sizeof(cookie_str) - 1);
// cookie header doesn't exist
if (field == nullptr) {
TSDebug(MY_NAME, "no cookie header");
// return TSREMAP_NO_REMAP;
}
const char *cookie = nullptr;
int cookie_len = 0;
if (field != nullptr) {
cookie = TSMimeHdrFieldValueStringGet(rri->requestBufp, rri->requestHdrp, field, -1, &cookie_len);
}
std::string temp_cookie(cookie, cookie_len);
CookieJar jar;
jar.create(temp_cookie);
for (auto &op : *ops) {
TSDebug(MY_NAME, ">>> processing new operation");
if (op->process(jar, rewrite_to, status, rri, req_url)) {
cr_substitutions(rewrite_to, req_url);
size_t pos = 7; // 7 because we want to ignore the // in
// http:// :)
size_t tmp_pos = rewrite_to.find('?', pos); // we don't want to alter the query string
do {
pos = rewrite_to.find("//", pos);
if (pos < tmp_pos) {
rewrite_to.erase(pos, 1); // remove one '/'
}
} while (pos <= rewrite_to.length() && pos < tmp_pos);
// Add Query Parameters if not already present
if (!client_req_query_params.empty() && rewrite_to.find('?') == std::string::npos) {
rewrite_to.append(client_req_query_params);
}
TSDebug(MY_NAME, "rewriting to: %s", rewrite_to.c_str());
// Maybe set the return status
if (status > TS_HTTP_STATUS_NONE) {
TSDebug(MY_NAME, "Setting return status to %d", status);
TSHttpTxnStatusSet(txnp, status);
if ((status == TS_HTTP_STATUS_MOVED_PERMANENTLY) || (status == TS_HTTP_STATUS_MOVED_TEMPORARILY)) {
if (rewrite_to.size() > 8192) {
TSError("Redirect in target "
"URL too long");
TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_REQUEST_URI_TOO_LONG);
} else {
const char *start = rewrite_to.c_str();
int dest_len = rewrite_to.size();
if (TS_PARSE_ERROR == TSUrlParse(rri->requestBufp, rri->requestUrl, &start, start + dest_len)) {
TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_INTERNAL_SERVER_ERROR);
TSError("can't parse "
"substituted "
"URL string");
} else {
rri->redirect = 1;
}
}
}
if (field != nullptr) {
TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, field);
}
if (rri->redirect) {
return TSREMAP_DID_REMAP;
} else {
return TSREMAP_NO_REMAP;
}
}
const char *start = rewrite_to.c_str();
// set the new url
if (TSUrlParse(rri->requestBufp, rri->requestUrl, &start, start + rewrite_to.length()) == TS_PARSE_ERROR) {
TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_INTERNAL_SERVER_ERROR);
TSError("can't parse substituted URL string");
goto error;
} else {
if (field != nullptr) {
TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, field);
}
return TSREMAP_DID_REMAP;
}
// Cleanup
error:
if (field != nullptr) {
TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, field);
}
return TSREMAP_NO_REMAP;
}
}
TSDebug(MY_NAME, "could not execute ANY of the cookie remap operations... "
"falling back to default in remap.config");
if (field != nullptr) {
TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, field);
}
return TSREMAP_NO_REMAP;
}
//----------------------------------------------------------------------------
// unload
void
TSRemapDeleteInstance(void *ih)
{
OpsQueue *ops = static_cast<OpsQueue *>(ih);
TSDebug(MY_NAME, "deleting loaded operations");
for (auto &op : *ops) {
delete op;
}
delete ops;
return;
}