plugins/experimental/cookie_remap/cookie_remap.cc (987 lines of code) (raw):

/* 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" namespace { DbgCtl dbg_ctl{MY_NAME}; } 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); } 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 = nullptr; // Doesn't really need initialization, this is only to shut up Coverity. }; // 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("") { Dbg(dbg_ctl, "subop constructor called"); } ~subop() { Dbg(dbg_ctl, "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 { Dbg(dbg_ctl, "\t+++subop+++"); Dbg(dbg_ctl, "\t\tcookie: %s", cookie.c_str()); Dbg(dbg_ctl, "\t\toperation: %s", operation.c_str()); if (str_match.size() > 0) { Dbg(dbg_ctl, "\t\tmatching: %s", str_match.c_str()); } if (regex) { Dbg(dbg_ctl, "\t\tregex: %s", regex_string.c_str()); } if (bucket.size() > 0) { Dbg(dbg_ctl, "\t\tbucket: %s", bucket.c_str()); Dbg(dbg_ctl, "\t\ttaking: %d", how_many); Dbg(dbg_ctl, "\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() { Dbg(dbg_ctl, "op constructor called"); } ~op() { Dbg(dbg_ctl, "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 { Dbg(dbg_ctl, "++++operation++++"); Dbg(dbg_ctl, "sending to: %s", sendto.c_str()); Dbg(dbg_ctl, "if these operations match: "); for (auto subop : subops) { subop->printSubOp(); } if (else_sendto.size() > 0) { Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "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()) { Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "processing non-sublevel cookie"); cookie_found = jar.get_full(c, cookie_data); Dbg(dbg_ctl, "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); Dbg(dbg_ctl, "processing sublevel cookie"); Dbg(dbg_ctl, "c key: %s", cookie_main.c_str()); Dbg(dbg_ctl, "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) { Dbg(dbg_ctl, "cookie %s was not " "found (and we wanted " "that)", object_name.c_str()); continue; // we can short // circuit more // testing } Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "cookie %s was found, " "but operation " "requires " "non-existence", object_name.c_str()); retval &= 0; break; } if (subop_type == EXISTS) { Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "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); Dbg(dbg_ctl, "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()) { Dbg(dbg_ctl, "string match succeeded"); continue; } else { Dbg(dbg_ctl, "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. Dbg(dbg_ctl, "found %d matches", ret); Dbg(dbg_ctl, "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 { Dbg(dbg_ctl, "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 { Dbg(dbg_ctl, "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) { Dbg(dbg_ctl, "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); Dbg(dbg_ctl, "we hashed this to bucket: %u " "taking: %u out of: %u", hash, taking, out_of); if (hash < taking) { Dbg(dbg_ctl, "we hashed in the range, yay!"); continue; // we hashed in the range } else { Dbg(dbg_ctl, "we didn't 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; }; using StringPair = std::pair<std::string, std::string>; 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; Dbg(dbg_ctl, "build_op: key=%s val=%s", key.c_str(), val.c_str()); if (key == "cookie") { if (!sub->empty()) { Dbg(dbg_ctl, "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: Dbg(dbg_ctl, "error building operation"); delete sub; return false; } using OpsQueue = std::vector<const op *>; //---------------------------------------------------------------------------- // init TSReturnCode TSRemapInit(TSRemapInterface * /* api_info ATS_UNUSED */, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */) { return TS_SUCCESS; } //---------------------------------------------------------------------------- // initialization of structures from config parameters TSReturnCode TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */) { if (argc != 3) { TSError("arguments not equal to 3: %d", argc); Dbg(dbg_ctl, "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(); } } Dbg(dbg_ctl, "# 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()); } Dbg(dbg_ctl, "from_path: %*s", FMT_SV(from_path)); Dbg(dbg_ctl, "%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 SubUnmatched { SubUnmatched() = default; // Work-around for Intel compiler problem. int count = 2; CompNext o1{"ath", &sub_unmatched_path_id}; CompNext o2{"path", &sub_unmatched_ppath_id}; }; SubUnmatched const sub_unmatched; struct SubP { SubP() = default; // Work-around for Intel compiler problem. int count = 2; CompNext o1{"ath", &sub_path_id}; CompNext o2{"path", &sub_ppath_id}; }; SubP const sub_p; struct SubCrReq { SubCrReq() = default; // Work-around for Intel compiler problem. int count = 2; CompNext o1{"url", &sub_req_url_id}; CompNext o2{"purl", &sub_req_purl_id}; }; SubCrReq const sub_cr_req; struct SubCr { SubCr() = default; // Work-around for Intel compiler problem. int count = 2; CompNext o1{"req_", &sub_cr_req.count}; CompNext o2{"urlencode(", &sub_url_encode_id}; }; SubCr const sub_cr; struct Sub { Sub() = default; // Work-around for Intel compiler problem. int count = 3; CompNext o1{"cr_", &sub_cr.count}; CompNext o2{"p", &sub_p.count}; CompNext o3{"unmatched_p", &sub_unmatched.count}; }; Sub 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 number. } ++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); Dbg(dbg_ctl, "x req_url.path: %*s %d", FMT_SV(path), static_cast<int>(path.size())); auto url = req_url.url(false); Dbg(dbg_ctl, "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 Dbg(dbg_ctl, "%*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"); Dbg(dbg_ctl, "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; } Dbg(dbg_ctl, "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) { Dbg(dbg_ctl, "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) { Dbg(dbg_ctl, ">>> 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); } Dbg(dbg_ctl, "rewriting to: %s", rewrite_to.c_str()); // Maybe set the return status if (status > TS_HTTP_STATUS_NONE) { Dbg(dbg_ctl, "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; } } Dbg(dbg_ctl, "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); Dbg(dbg_ctl, "deleting loaded operations"); for (auto &op : *ops) { delete op; } delete ops; return; }