| /** |
| @section license License |
| |
| 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. |
| |
| Copyright 2021, Yahoo Inc. |
| */ |
| |
| #include <string> |
| |
| #include <swoc/TextView.h> |
| #include <swoc/Errata.h> |
| #include <swoc/BufferWriter.h> |
| #include <swoc/bwf_ex.h> |
| |
| #include "txn_box/common.h" |
| #include "txn_box/Config.h" |
| #include "txn_box/Context.h" |
| #include "txn_box/Extractor.h" |
| #include "txn_box/Directive.h" |
| #include "txn_box/Comparison.h" |
| |
| #include "txn_box/ts_util.h" |
| |
| using swoc::BufferWriter; |
| using swoc::Errata; |
| using swoc::Rv; |
| using swoc::TextView; |
| using namespace swoc::literals; |
| |
| /* ------------------------------------------------------------------------------------ */ |
| namespace |
| { |
| /// Standard caseless compare arg. |
| static inline constexpr TextView ARG_NOCASE = "nc"; |
| /// Standard reverse order arg. |
| static inline constexpr TextView ARG_REVERSE = "rev"; |
| |
| struct QPair { |
| static constexpr char Sep = '='; |
| using self_type = QPair; |
| |
| TextView name; |
| TextView value; |
| char elt_sep = 0; ///< Separator before name. |
| char kv_sep = Sep; ///< Separator for name/value - always '=' if not null. |
| self_type *_next = nullptr; |
| self_type *_prev = nullptr; |
| using Linkage = swoc::IntrusiveLinkage<self_type, &self_type::_next, &self_type::_prev>; |
| |
| QPair() = default; |
| QPair(TextView k, TextView v) : name(k), value(v) {} |
| |
| TextView |
| all() const |
| { |
| return TextView(name.data(), value.data_end()); |
| } |
| }; |
| QPair |
| query_take_qpair(TextView &qs) |
| { |
| char elt_sep = 0; |
| // Strip the leading separator, if any, and drop empty elements. |
| // Need to do this to track what was there originally and try to re-use it. |
| while (qs) { |
| if (auto c = *qs; c == '&' || c == ';') { |
| qs.remove_prefix(1); |
| elt_sep = c; |
| } else |
| break; |
| } |
| |
| // If there's anything left, look for kv pair. |
| if (qs) { |
| auto v{qs.clip_prefix_of([](char c) { return c != '&' && c != ';'; })}; |
| auto k = v.take_prefix_at(QPair::Sep); |
| QPair zret{k, v}; |
| zret.elt_sep = elt_sep; |
| return zret; |
| } |
| return {}; |
| } |
| |
| TextView |
| query_value_update(Context &ctx, TextView qs, TextView name, Feature const &value, bool case_p, bool force_equal_p) |
| { |
| TextView zret; |
| bool nv_is_nil_p = (value.value_type() == NIL); |
| if (qs.empty()) { |
| if (!nv_is_nil_p) { |
| zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}={}", name, std::get<STRING>(value)); }); |
| } |
| } else { // invariant - query string was not empty. |
| auto &&[k, v] = ts::query_value_for(qs, name, case_p); |
| if (k.empty()) { // not found at all. |
| if (!nv_is_nil_p) { |
| zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}&{}={}", qs, name, value); }); |
| } |
| } else { |
| // Make a note if there was no value but an '=' anyway. |
| bool equal_p = force_equal_p || (v.empty() && (v.data() != k.end())); |
| // Prefix is the part before the name. |
| TextView prefix{qs.data(), k.data()}; |
| // Suffix is the part after the value. |
| TextView suffix{v.end(), qs.end()}; |
| if (!nv_is_nil_p) { |
| zret = ctx.render_transient([&, k = k](BufferWriter &w) { |
| w.write(prefix).write(k); |
| if (equal_p || !(value.index() == IndexFor(STRING) && std::get<IndexFor(STRING)>(value).empty())) { |
| w.print("={}", value); |
| } |
| w.write(suffix); |
| }); |
| } else { // NIL, remove the pair. |
| prefix.rtrim("&;"_tv); |
| suffix.ltrim("&;"_tv); |
| if (suffix.empty()) { |
| zret = prefix; |
| } else if (prefix.empty()) { |
| zret = suffix; |
| } else { // neither is empty. |
| zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}&{}", prefix, suffix); }); |
| } |
| } |
| } |
| } |
| return zret; |
| } |
| |
| } // namespace |
| /* ------------------------------------------------------------------------------------ */ |
| // Query string. |
| // These have the @c extract method because it can be done as a @c Direct |
| // value and that's better than running through the formatting. |
| |
| class QueryValueExtractor : public Extractor |
| { |
| public: |
| Rv<ActiveType> validate(Config &cfg, Spec &spec, TextView const &arg) override; |
| using Extractor::extract; // declare hidden member function |
| Feature extract(Context &ctx, Spec const &spec) override; |
| |
| protected: |
| TextView _arg; |
| |
| /// @return The key (name) for the extractor. |
| virtual TextView const &key() const = 0; |
| /// @return The appropriate query string. |
| virtual TextView query_string(Context &ctx) const = 0; |
| }; |
| |
| Rv<ActiveType> |
| QueryValueExtractor::validate(Config &cfg, Spec &spec, const TextView &arg) |
| { |
| if (arg.empty()) { |
| return Errata(S_ERROR, "Extractor \"{}\" requires a key name argument.", this->key()); |
| } |
| spec._data.text = cfg.localize(arg); |
| return ActiveType{NIL, STRING}; |
| } |
| |
| Feature |
| QueryValueExtractor::extract(Context &ctx, const Spec &spec) |
| { |
| auto qs = this->query_string(ctx); |
| if (qs.empty()) { |
| return NIL_FEATURE; |
| } |
| auto &&[name, value] = ts::query_value_for(qs, spec._data.text, true); // case insensitive |
| if (value.size() == 0) { |
| if (value.data() == nullptr || value.data() == name.end()) { |
| return NIL_FEATURE; // key not present or value not present |
| } |
| return FeatureView::Literal(""); |
| } |
| return FeatureView::Direct(value); |
| } |
| |
| // -- |
| |
| class Ex_ua_req_query : public StringExtractor |
| { |
| public: |
| static constexpr TextView NAME{"ua-req-query"}; |
| |
| using Extractor::extract; // un-hide the overloaded |
| Feature extract(Context &ctx, Spec const &spec) override; |
| BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override; |
| }; |
| |
| Feature |
| Ex_ua_req_query::extract(Context &ctx, Spec const &) |
| { |
| if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) { |
| if (ts::URL url{hdr.url()}; url.is_valid()) { |
| return FeatureView::Direct(url.query()); |
| } |
| } |
| return NIL_FEATURE; |
| } |
| |
| BufferWriter & |
| Ex_ua_req_query::format(BufferWriter &w, Spec const &spec, Context &ctx) |
| { |
| return bwformat(w, spec, this->extract(ctx, spec)); |
| } |
| |
| class Ex_ua_req_query_value : public QueryValueExtractor |
| { |
| public: |
| static constexpr TextView NAME{"ua-req-query-value"}; |
| |
| protected: |
| TextView const &key() const override; |
| TextView query_string(Context &ctx) const override; |
| }; |
| |
| TextView const & |
| Ex_ua_req_query_value::key() const |
| { |
| return NAME; |
| } |
| |
| TextView |
| Ex_ua_req_query_value::query_string(Context &ctx) const |
| { |
| if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) { |
| if (ts::URL url{hdr.url()}; url.is_valid()) { |
| return url.query(); |
| } |
| } |
| return {}; |
| } |
| |
| // -- |
| |
| class Ex_pre_remap_query : public StringExtractor |
| { |
| public: |
| static constexpr TextView NAME{"pre-remap-query"}; |
| using Extractor::extract; // un-hide the overloaded |
| Feature extract(Context &ctx, Spec const &spec) override; |
| BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override; |
| }; |
| |
| Feature |
| Ex_pre_remap_query::extract(Context &ctx, Spec const &) |
| { |
| if (ts::URL url{ctx._txn.pristine_url_get()}; url.is_valid()) { |
| return FeatureView::Direct(url.query()); |
| } |
| return NIL_FEATURE; |
| } |
| |
| BufferWriter & |
| Ex_pre_remap_query::format(BufferWriter &w, Spec const &spec, Context &ctx) |
| { |
| return bwformat(w, spec, this->extract(ctx, spec)); |
| } |
| |
| class Ex_pre_remap_req_query_value : public QueryValueExtractor |
| { |
| public: |
| static constexpr TextView NAME{"pre-remap-req-query-value"}; |
| |
| protected: |
| TextView const &key() const override; |
| TextView query_string(Context &ctx) const override; |
| }; |
| |
| TextView const & |
| Ex_pre_remap_req_query_value::key() const |
| { |
| return NAME; |
| } |
| |
| TextView |
| Ex_pre_remap_req_query_value::query_string(Context &ctx) const |
| { |
| if (ts::URL url{ctx._txn.pristine_url_get()}; url.is_valid()) { |
| return url.query(); |
| } |
| return {}; |
| } |
| |
| // -- |
| |
| class Ex_proxy_req_query : public StringExtractor |
| { |
| public: |
| static constexpr TextView NAME{"proxy-req-query"}; |
| using Extractor::extract; // un-hide the overloaded |
| Feature extract(Context &ctx, Spec const &spec) override; |
| BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override; |
| }; |
| |
| Feature |
| Ex_proxy_req_query::extract(Context &ctx, Spec const &) |
| { |
| if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) { |
| if (ts::URL url{hdr.url()}; url.is_valid()) { |
| return FeatureView::Direct(url.query()); |
| } |
| } |
| return NIL_FEATURE; |
| } |
| |
| BufferWriter & |
| Ex_proxy_req_query::format(BufferWriter &w, Spec const &spec, Context &ctx) |
| { |
| return bwformat(w, spec, this->extract(ctx, spec)); |
| } |
| |
| class Ex_proxy_req_query_value : public QueryValueExtractor |
| { |
| public: |
| static constexpr TextView NAME{"proxy-req-query-value"}; |
| |
| protected: |
| TextView const &key() const override; |
| TextView query_string(Context &ctx) const override; |
| }; |
| |
| TextView const & |
| Ex_proxy_req_query_value::key() const |
| { |
| return NAME; |
| } |
| |
| TextView |
| Ex_proxy_req_query_value::query_string(Context &ctx) const |
| { |
| if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) { |
| if (ts::URL url{hdr.url()}; url.is_valid()) { |
| return url.query(); |
| } |
| } |
| return {}; |
| } |
| |
| /* ------------------------------------------------------------------------------------ */ |
| /** Sort the query string by name. |
| * |
| */ |
| class Mod_query_sort : public Modifier |
| { |
| using self_type = Mod_query_sort; |
| using super_type = Modifier; |
| |
| public: |
| static inline const std::string KEY{"query-sort"}; |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| ActiveType result_type(ActiveType const &) const override; |
| using super_type::operator(); // un-hide overloaded. |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> qs) override; |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| bool _case_p = false; ///< Case sensitive sort. |
| bool _rev_p = false; ///< Reverse order sort. |
| |
| Mod_query_sort(bool case_p, bool rev_p) : _case_p(case_p), _rev_p(rev_p) {} |
| }; |
| |
| bool |
| Mod_query_sort::is_valid_for(const ActiveType &ex_type) const |
| { |
| return ex_type.can_satisfy(STRING); |
| } |
| |
| ActiveType |
| Mod_query_sort::result_type(const ActiveType &) const |
| { |
| return {NIL, STRING}; |
| } |
| |
| Rv<Feature> |
| Mod_query_sort::operator()(Context &ctx, feature_type_for<STRING> qs) |
| { |
| using list_type = swoc::IntrusiveDList<QPair::Linkage>; |
| auto list = ctx.make<list_type>(); |
| |
| while (qs) { |
| list->append(ctx.make<QPair>(query_take_qpair(qs))); |
| } |
| |
| // Make an array of pointers to list items to sort. |
| size_t length = list->count() - 1; // separators required. |
| auto sa = ctx.alloc_span<QPair *>(list->count()); |
| auto ptr = sa.begin(); |
| for (auto &item : *list) { |
| *ptr++ = &item; |
| length += item.name.size() + item.value.size() + 1; |
| } |
| |
| static auto case_sort = [](QPair *lhs, QPair *rhs) { return strcmp(lhs->name, rhs->name) < 0; }; |
| static auto case_rev_sort = [](QPair *lhs, QPair *rhs) { return strcmp(lhs->name, rhs->name) > 0; }; |
| static auto caseless_sort = [](QPair *lhs, QPair *rhs) { return strcasecmp(lhs->name, rhs->name) < 0; }; |
| static auto caseless_rev_sort = [](QPair *lhs, QPair *rhs) { return strcasecmp(lhs->name, rhs->name) > 0; }; |
| |
| auto sorter = _case_p ? (_rev_p ? case_rev_sort : case_sort) : (_rev_p ? caseless_rev_sort : caseless_sort); |
| |
| std::stable_sort(sa.begin(), sa.end(), sorter); |
| |
| swoc::FixedBufferWriter w{ctx.transient_buffer(length)}; |
| bool first_p = true; |
| for (auto item : sa) { |
| if (!first_p) { |
| w.write(item->elt_sep); |
| } |
| w.print("{}{}{}", item->name, item->kv_sep, item->value); |
| first_p = false; |
| } |
| ctx.transient_finalize(w.view().size()); |
| return {w.view()}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_query_sort::load(Config &, YAML::Node, TextView, TextView arg, YAML::Node) |
| { |
| bool case_p = true; |
| bool rev_p = false; |
| while (arg) { |
| auto token = arg.take_prefix_at(','); |
| if (token == ARG_NOCASE) { |
| case_p = false; |
| } else if (token == ARG_REVERSE) { |
| rev_p = true; |
| } else { |
| return Errata(S_ERROR, R"(Invalid argument "{}" in modifier "{}")", token, KEY); |
| } |
| } |
| return Handle{new self_type(case_p, rev_p)}; |
| } |
| |
| // --- |
| |
| class Mod_query_filter : public FilterMod |
| { |
| using self_type = Mod_query_filter; |
| using super_type = Modifier; |
| |
| public: |
| static inline const std::string KEY{"query-filter"}; |
| |
| static inline const std::string OPT_VALUE = "value"; ///< Compare value |
| static inline const std::string OPT_APPEND = "append"; ///< Append element. |
| static inline const std::string OPT_APPEND_UNIQUE = "append-unique"; ///< Append element uniquely. |
| static inline const std::string OPT_PASS_REST = "pass-rest"; ///< Pass all remainining elements. |
| static inline const std::string OPT_DROP_REST = "drop-rest"; ///< Drop all remainining elements. |
| |
| static inline const std::string PAIR_VALUE = "value"; ///< Element value. |
| static inline const std::string PAIR_NAME = "name"; ///< Element name. |
| |
| static constexpr TextView CFG_STORE_KEY = "mod-query-filter"; |
| using cfg_store_type = ReservedSpan; |
| |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| ActiveType result_type(ActiveType const &) const override; |
| using super_type::operator(); // un-hide overloaded. |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> qs) override; |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| /// Local extractor for name in a pair. |
| class Ex_Name : public Extractor |
| { |
| virtual swoc::Rv<ActiveType> validate(Config &cfg, Spec &spec, swoc::TextView const &) override; |
| using Extractor::extract; // declare hidden member function |
| Feature extract(Context &ctx, Spec const &spec) override; |
| }; |
| /// Local extractor for value in a pair. |
| class Ex_Value : public Extractor |
| { |
| virtual swoc::Rv<ActiveType> validate(Config &cfg, Spec &spec, swoc::TextView const &) override; |
| using Extractor::extract; // declare hidden member function |
| Feature extract(Context &ctx, Spec const &spec) override; |
| }; |
| |
| protected: |
| /// A filter comparison case. |
| struct Case { |
| Action _action = PASS; ///< Action on match. |
| |
| Comparison::Handle _cmp; ///< Comparison. |
| Comparison::Handle _value_cmp; ///< Comparison for value. |
| |
| /// Expressions for name and/or value. |
| struct PairExpr { |
| Expr _name; |
| Expr _value; |
| }; |
| |
| PairExpr _replace; ///< Replacement. |
| |
| swoc::MemSpan<PairExpr> _append; ///< Elements to append. |
| |
| /// Option for the rest of the elements. |
| enum { REST_NONE, REST_PASS, REST_DROP } _opt_rest = REST_NONE; |
| |
| void assign(Comparison::Handle &&handle); |
| |
| bool operator()(Context &ctx, Feature const &feature); |
| |
| Errata pre_load(Config &cfg, YAML::Node node); |
| |
| Errata parse_pair(Config &cfg, YAML::Node node, PairExpr &pair); |
| void eval_pair(Context &ctx, PairExpr const &pe, QPair &qp) const; |
| }; |
| |
| /// Container for cases with comparisons. |
| ComparisonGroup<Case> _cases; |
| |
| Mod_query_filter() = default; |
| Case const *compare(Context &ctx, QPair const &qp) const; |
| |
| static inline Ex_Name ex_name; |
| static inline Ex_Value ex_value; |
| /// Table for local extactors. |
| static Extractor::Table _ex_table; |
| }; |
| Extractor::Table Mod_query_filter::_ex_table{ |
| {"name", &Mod_query_filter::ex_name }, |
| {"value", &Mod_query_filter::ex_value} |
| }; |
| |
| swoc::Rv<ActiveType> |
| Mod_query_filter::Ex_Name::validate(Config &cfg, Extractor::Spec &spec, TextView const &) |
| { |
| spec._data.ctx_reserved_span = *(cfg.named_object<cfg_store_type>(CFG_STORE_KEY)); |
| return ActiveType{NIL, STRING}; |
| } |
| |
| Feature |
| Mod_query_filter::Ex_Name::extract(Context &ctx, Extractor::Spec const &spec) |
| { |
| auto qp = ctx.storage_for(spec._data.ctx_reserved_span).rebind<QPair *>()[0]; |
| return FeatureView::Literal(qp->name); |
| } |
| |
| swoc::Rv<ActiveType> |
| Mod_query_filter::Ex_Value::validate(Config &cfg, Extractor::Spec &spec, TextView const &) |
| { |
| spec._data.ctx_reserved_span = *(cfg.named_object<cfg_store_type>(CFG_STORE_KEY)); |
| return ActiveType{NIL, STRING}; |
| } |
| |
| Feature |
| Mod_query_filter::Ex_Value::extract(Context &ctx, Extractor::Spec const &spec) |
| { |
| auto qp = ctx.storage_for(spec._data.ctx_reserved_span).rebind<QPair *>()[0]; |
| return FeatureView::Literal(qp->value); |
| } |
| |
| bool |
| Mod_query_filter::is_valid_for(const ActiveType &ex_type) const |
| { |
| return ex_type.can_satisfy(STRING); |
| } |
| |
| ActiveType |
| Mod_query_filter::result_type(const ActiveType &) const |
| { |
| return {NIL, STRING}; |
| } |
| |
| auto |
| Mod_query_filter::compare(Context &ctx, QPair const &qp) const -> Case const * |
| { |
| using fstr = feature_type_for<STRING>; |
| for (auto const &c : _cases) { |
| if ((!c._cmp || (*c._cmp)(ctx, fstr(qp.name))) && (!c._value_cmp || (*c._value_cmp)(ctx, fstr(qp.value)))) { |
| return &c; |
| } |
| } |
| return nullptr; |
| } |
| |
| void |
| Mod_query_filter::Case::assign(Comparison::Handle &&handle) |
| { |
| _cmp = std::move(handle); |
| } |
| |
| swoc::Errata |
| Mod_query_filter::Case::parse_pair(Config &cfg, YAML::Node node, PairExpr &pair) |
| { |
| if (!node.IsMap()) { |
| return Errata(S_ERROR, "Element at {} is not an object as required.", node.Mark()); |
| } |
| |
| if (auto knode = node[PAIR_NAME]; knode) { |
| auto &&[expr, errata]{cfg.parse_expr(knode)}; |
| if (!errata.is_ok()) { |
| errata.note("While parsing expression for {}.", PAIR_NAME); |
| } |
| pair._name = std::move(expr); |
| } |
| |
| if (auto vnode = node[PAIR_VALUE]; vnode) { |
| auto &&[expr, errata]{cfg.parse_expr(vnode)}; |
| if (!errata.is_ok()) { |
| errata.note("While parsing expression for {}.", PAIR_VALUE); |
| } |
| pair._value = std::move(expr); |
| } |
| |
| return {}; |
| } |
| |
| Errata |
| Mod_query_filter::Case::pre_load(Config &cfg, YAML::Node cmp_node) |
| { |
| unsigned action_count = 0; |
| |
| if (!cmp_node.IsMap()) { |
| return Errata(S_ERROR, "List element at {} for {} modifier is not a comparison object.", cmp_node.Mark(), KEY); |
| } |
| |
| if (auto do_node = cmp_node[Global::DO_KEY]; do_node) { |
| return Errata(S_ERROR, R"("{}" at line {} is not allowed in a modifier comparison.)", Global::DO_KEY, do_node.Mark()); |
| } |
| |
| YAML::Node drop_node = cmp_node[ACTION_DROP]; |
| if (drop_node) { |
| _action = DROP; |
| cmp_node.remove(ACTION_DROP); |
| ++action_count; |
| } |
| |
| if (YAML::Node pass_node = cmp_node[ACTION_PASS]; pass_node) { |
| _action = PASS; |
| cmp_node.remove(ACTION_PASS); |
| ++action_count; |
| } |
| |
| if (YAML::Node replace_node = cmp_node[ACTION_REPLACE]; replace_node) { |
| Errata errata = this->parse_pair(cfg, replace_node, _replace); |
| if (!errata.is_ok()) { |
| errata.note("While parsing expression at {} for {} key in comparison at {}.", replace_node.Mark(), ACTION_REPLACE, |
| cmp_node.Mark()); |
| return errata; |
| } |
| _action = REPLACE; |
| cmp_node.remove(ACTION_REPLACE); |
| ++action_count; |
| } |
| |
| if (action_count > 1) { |
| return Errata(S_ERROR, "Only one of {}, {}, {} is allowed in the {} comparison at {}.", ACTION_REPLACE, ACTION_DROP, |
| ACTION_PASS, KEY, cmp_node.Mark()); |
| } |
| |
| YAML::Node opt_node = cmp_node[ACTION_OPT]; |
| if (opt_node) { |
| if (!opt_node.IsMap()) { |
| return Errata(S_ERROR, R"("Value for "{}" at {} for "{}" modifier is not an object.)", ACTION_OPT, opt_node.Mark(), KEY); |
| } |
| |
| if (YAML::Node vcmp_node = opt_node[OPT_VALUE]; vcmp_node) { |
| auto &&[vcmp, vcmp_errata]{Comparison::load(cfg, vcmp_node)}; |
| if (!vcmp_errata.is_ok()) { |
| return std::move(vcmp_errata.note(R"(While parsing "{}" option for "{}" modifier)", OPT_VALUE, KEY)); |
| } |
| _value_cmp = std::move(vcmp); |
| opt_node.remove(vcmp_node); |
| } |
| |
| Errata errata; |
| if (YAML::Node append_node = opt_node[OPT_APPEND]; append_node) { |
| if (append_node.IsSequence()) { |
| auto span = cfg.alloc_span<PairExpr>(append_node.size()); |
| for (unsigned idx = 0; idx < append_node.size() && errata.is_ok(); ++idx) { |
| auto &p{span[idx]}; |
| new (&p) PairExpr; |
| errata = this->parse_pair(cfg, append_node[idx], p); |
| } |
| } else { |
| auto span = cfg.alloc_span<PairExpr>(1); |
| new (&span[0]) PairExpr; |
| errata = this->parse_pair(cfg, append_node, span[0]); |
| } |
| opt_node.remove(append_node); |
| } |
| if (!errata.is_ok()) { |
| errata.note("While parsing {} expressions.", OPT_APPEND); |
| return errata; |
| } |
| |
| if (auto pass_rest_node = opt_node[OPT_PASS_REST]; pass_rest_node) { |
| _opt_rest = Case::REST_PASS; |
| opt_node.remove(pass_rest_node); |
| } |
| |
| if (auto drop_rest_node = opt_node[OPT_DROP_REST]; drop_rest_node) { |
| if (_opt_rest != Case::REST_NONE) { |
| return Errata(S_ERROR, "{} at {} has both {} and {} which is not allowed.", ACTION_OPT, opt_node.Mark(), OPT_PASS_REST, |
| OPT_DROP_REST); |
| } |
| _opt_rest = Case::REST_DROP; |
| opt_node.remove(drop_rest_node); |
| } |
| |
| cmp_node.remove(opt_node); |
| } |
| |
| return {}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_query_filter::load(Config &cfg, YAML::Node node, TextView, TextView, YAML::Node key_value) |
| { |
| auto self = new self_type; |
| Handle handle(self); |
| let local_ex_scope{cfg._local_extractors, &_ex_table}; |
| |
| // Need reserved context storage to pass the current @c QPair down to nested extractors. |
| // The reserved span is stored in the configuration and then used at run time. |
| auto ctx_store = cfg.obtain_named_object<cfg_store_type>(CFG_STORE_KEY); |
| if (ctx_store->n == 0) { |
| *ctx_store = cfg.reserve_ctx_storage(sizeof(QPair *)); |
| } |
| |
| if (auto errata = self->_cases.load(cfg, key_value); !errata.is_ok()) { |
| errata.note(R"(While parsing modifier "{}" at line {}.)", KEY, node.Mark()); |
| return errata; |
| } |
| |
| return handle; |
| } |
| |
| void |
| Mod_query_filter::Case::eval_pair(Context &ctx, const PairExpr &pe, QPair &qp) const |
| { |
| if (!pe._name.empty()) { |
| auto name = ctx.extract(pe._name); |
| if (!is_nil(name)) { |
| qp.name = ctx.render_transient([&](BufferWriter &w) { w.print("{}", name); }); |
| ctx.commit_transient(); |
| } |
| } |
| |
| if (!pe._value.empty()) { |
| auto value = ctx.extract(pe._value); |
| if (!is_nil(value)) { |
| qp.value = ctx.render_transient([&](BufferWriter &w) { w.print("{}", value); }); |
| ctx.commit_transient(); |
| } |
| } |
| } |
| |
| Rv<Feature> |
| Mod_query_filter::operator()(Context &ctx, feature_type_for<STRING> qs) |
| { |
| using list_type = swoc::IntrusiveDList<QPair::Linkage>; |
| auto list = ctx.make<list_type>(); |
| QPair *&qp_ex = ctx.storage_for(*ctx.cfg().named_object<cfg_store_type>(CFG_STORE_KEY)).rebind<QPair *>()[0]; |
| |
| while (qs) { |
| auto qp = query_take_qpair(qs); |
| auto c = this->compare(ctx, qp); |
| Action action = (c ? c->_action : DROP); |
| qp_ex = &qp; // pass to potential extractors. |
| switch (action) { |
| case DROP: |
| break; |
| case PASS: |
| list->append(ctx.make<QPair>(qp)); |
| break; |
| case REPLACE: |
| c->eval_pair(ctx, c->_replace, qp); |
| list->append(ctx.make<QPair>(qp)); |
| break; |
| } |
| |
| // Append action. |
| for (auto &pe : c->_append) { |
| QPair q_pair; |
| c->eval_pair(ctx, pe, q_pair); |
| list->append(ctx.make<QPair>(q_pair)); |
| } |
| |
| // drop-rest, pass-rest actions. |
| if (c->_opt_rest != Case::REST_NONE) { |
| if (c->_opt_rest == Case::REST_DROP) { |
| qs.clear(); |
| } |
| break; |
| } |
| } |
| |
| // Consolidate list into a new query string. |
| size_t length = qs.size() + list->count() + 1; // separators. |
| for (auto &p : *list) { |
| length += p.name.size() + p.value.size() + 1; |
| } |
| |
| swoc::FixedBufferWriter w{ctx.transient_buffer(length)}; |
| bool first_p = true; |
| for (auto &item : *list) { |
| if (!first_p) { |
| w.write(item.elt_sep); |
| } |
| w.print("{}{}{}", item.name, swoc::bwf::Optional("{}", item.kv_sep), item.value); |
| first_p = false; |
| } |
| w.write(qs); |
| ctx.transient_finalize(w.view().size()); |
| return {FeatureView(w.view())}; |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| /** Set the query for the request. |
| */ |
| class Do_ua_req_query : public Directive |
| { |
| using super_type = Directive; ///< Parent type. |
| using self_type = Do_ua_req_query; ///< Self reference type. |
| public: |
| static const std::string KEY; ///< Directive name. |
| static const HookMask HOOKS; ///< Valid hooks for directive. |
| |
| explicit Do_ua_req_query(Expr &&expr); |
| |
| Errata invoke(Context &ctx) override; |
| |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| protected: |
| Expr _expr; ///< Host feature. |
| }; |
| |
| const std::string Do_ua_req_query::KEY{"ua-req-query"}; |
| const HookMask Do_ua_req_query::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})}; |
| |
| Do_ua_req_query::Do_ua_req_query(Expr &&expr) : _expr(std::move(expr)) {} |
| |
| Errata |
| Do_ua_req_query::invoke(Context &ctx) |
| { |
| TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))}; |
| if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) { |
| hdr.url().query_set(text); |
| } |
| return {}; |
| } |
| |
| swoc::Rv<Directive::Handle> |
| Do_ua_req_query::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &, |
| YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark()); |
| return std::move(errata); |
| } |
| if (expr.is_null()) { |
| expr = Feature{FeatureView::Literal(""_tv)}; |
| } |
| if (!expr.result_type().can_satisfy(STRING)) { |
| return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark()); |
| } |
| return Handle(new self_type(std::move(expr))); |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| /** Set the query for the proxy request. |
| */ |
| class Do_proxy_req_query : public Directive |
| { |
| using super_type = Directive; ///< Parent type. |
| using self_type = Do_proxy_req_query; ///< Self reference type. |
| public: |
| static const std::string KEY; ///< Directive name. |
| static const HookMask HOOKS; ///< Valid hooks for directive. |
| |
| explicit Do_proxy_req_query(Expr &&fmt); |
| |
| Errata invoke(Context &ctx) override; |
| |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| protected: |
| Expr _fmt; ///< Host feature. |
| }; |
| |
| const std::string Do_proxy_req_query::KEY{"proxy-req-query"}; |
| const HookMask Do_proxy_req_query::HOOKS{MaskFor({Hook::PREQ})}; |
| |
| Do_proxy_req_query::Do_proxy_req_query(Expr &&fmt) : _fmt(std::move(fmt)) {} |
| |
| Errata |
| Do_proxy_req_query::invoke(Context &ctx) |
| { |
| TextView text{std::get<IndexFor(STRING)>(ctx.extract(_fmt))}; |
| if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) { |
| hdr.url().query_set(text); |
| } |
| return {}; |
| } |
| |
| swoc::Rv<Directive::Handle> |
| Do_proxy_req_query::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &, |
| YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark()); |
| return std::move(errata); |
| } |
| if (!expr.result_type().can_satisfy(STRING)) { |
| return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark()); |
| } |
| return Handle(new self_type(std::move(expr))); |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| class QueryValueDirective : public Directive |
| { |
| using self_type = QueryValueDirective; |
| using super_type = Directive; |
| |
| public: |
| TextView _name; ///< Query value key name. |
| Expr _expr; ///< Replacement value. |
| |
| /** Base constructor. |
| * |
| * @param name Name of the field. |
| * @param expr Value to assign to the field. |
| */ |
| QueryValueDirective(TextView const &name, Expr &&expr); |
| |
| /** Load from configuration. |
| * |
| * @param cfg Configuration. |
| * @param maker Subclass maker. |
| * @param key Name of the key identifying the directive. |
| * @param name Field name (directive argumnet). |
| * @param key_value Value of the node for @a key. |
| * @return An instance of the directive for @a key, or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, std::function<Handle(TextView const &name, Expr &&fmt)> const &maker, TextView const &key, |
| TextView const &arg, YAML::Node key_value); |
| |
| /** Invoke the directive on a URL. |
| * |
| * @param ctx Runtime context. |
| * @param url URL. |
| * @return Errors, if any. |
| */ |
| Errata invoke_on_url(Context &ctx, ts::URL &&url); |
| |
| protected: |
| /** Get the directive name (key). |
| * |
| * @return The directive key. |
| * |
| * Used by subclasses to provide the key for diagnostics. |
| */ |
| virtual swoc::TextView key() const = 0; |
| }; |
| |
| QueryValueDirective::QueryValueDirective(TextView const &name, Expr &&expr) : _name(name), _expr(std::move(expr)) {} |
| |
| auto |
| QueryValueDirective::load(Config &cfg, std::function<Handle(const TextView &, Expr &&)> const &maker, TextView const &key, |
| TextView const &arg, YAML::Node key_value) -> Rv<Handle> |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing value for "{}".)", key); |
| return std::move(errata); |
| } |
| |
| auto expr_type = expr.result_type(); |
| if (!expr_type.has_value()) { |
| return Errata(S_ERROR, R"(Directive "{}" must have a value.)", key); |
| } |
| return maker(cfg.localize(arg), std::move(expr)); |
| } |
| |
| Errata |
| QueryValueDirective::invoke_on_url(Context &ctx, ts::URL &&url) |
| { |
| if (url.is_valid()) { |
| auto qs = query_value_update(ctx, url.query(), _name, ctx.extract(_expr), true, false); |
| url.query_set(qs); |
| ctx.transient_discard(); |
| } |
| return Errata(S_ERROR, "Failed to update query value {} because the URL could not be found.", _name); |
| } |
| |
| // -- |
| class Do_ua_req_query_value : public QueryValueDirective |
| { |
| using self_type = Do_ua_req_query_value; |
| using super_type = QueryValueDirective; |
| |
| public: |
| static inline const std::string KEY{"ua-req-query-value"}; ///< Directive key. |
| static inline const HookMask HOOKS{ |
| MaskFor(Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP)}; ///< Valid hooks for directive. |
| |
| using super_type::invoke; |
| Errata invoke(Context &ctx) override; |
| |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| protected: |
| using super_type::super_type; // Inherit super_type constructors. |
| TextView key() const override; |
| }; |
| |
| TextView |
| Do_ua_req_query_value::key() const |
| { |
| return KEY; |
| } |
| |
| Errata |
| Do_ua_req_query_value::invoke(Context &ctx) |
| { |
| return this->invoke_on_url(ctx, ctx.ua_req_hdr().url()); |
| } |
| |
| Rv<Directive::Handle> |
| Do_ua_req_query_value::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg, |
| YAML::Node key_value) |
| { |
| return super_type::load( |
| cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg, |
| key_value); |
| } |
| |
| // -- |
| class Do_proxy_req_query_value : public QueryValueDirective |
| { |
| using self_type = Do_proxy_req_query_value; |
| using super_type = QueryValueDirective; |
| |
| public: |
| static inline const std::string KEY{"proxy-req-query-value"}; ///< Directive key. |
| static inline const HookMask HOOKS{MaskFor(Hook::PREQ)}; ///< Valid hooks for directive. |
| |
| using super_type::invoke; |
| Errata invoke(Context &ctx) override; |
| |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| protected: |
| using super_type::super_type; // Inherit super_type constructors. |
| TextView key() const override; |
| }; |
| |
| TextView |
| Do_proxy_req_query_value::key() const |
| { |
| return KEY; |
| } |
| |
| Errata |
| Do_proxy_req_query_value::invoke(Context &ctx) |
| { |
| return this->invoke_on_url(ctx, ctx.proxy_req_hdr().url()); |
| } |
| |
| Rv<Directive::Handle> |
| Do_proxy_req_query_value::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg, |
| YAML::Node key_value) |
| { |
| return super_type::load( |
| cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg, |
| key_value); |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| namespace |
| { |
| Ex_ua_req_query ua_req_query; |
| Ex_proxy_req_query proxy_req_query; |
| Ex_pre_remap_query pre_remap_query; |
| |
| Ex_ua_req_query_value ua_req_query_value; |
| Ex_pre_remap_query pre_remap_req_query_value; |
| Ex_proxy_req_query_value proxy_req_query_value; |
| |
| [[maybe_unused]] bool INITIALIZED = []() -> bool { |
| Extractor::define(Ex_ua_req_query::NAME, &ua_req_query); |
| Extractor::define(Ex_pre_remap_query::NAME, &pre_remap_query); |
| Extractor::define(Ex_proxy_req_query::NAME, &proxy_req_query); |
| |
| Extractor::define(Ex_ua_req_query_value::NAME, &ua_req_query_value); |
| Extractor::define(Ex_pre_remap_req_query_value::NAME, &ua_req_query_value); |
| Extractor::define(Ex_proxy_req_query_value::NAME, &ua_req_query_value); |
| |
| Modifier::define<Mod_query_sort>(); |
| Modifier::define<Mod_query_filter>(); |
| |
| Config::define<Do_ua_req_query>(); |
| Config::define<Do_ua_req_query_value>(); |
| Config::define<Do_proxy_req_query>(); |
| Config::define<Do_proxy_req_query_value>(); |
| |
| return true; |
| }(); |
| } // namespace |