| /** | |
| @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 2019, Oath Inc. | |
| */ | |
| #include <swoc/MemSpan.h> | |
| #include <swoc/ArenaWriter.h> | |
| #include "txn_box/Context.h" | |
| #include "txn_box/Config.h" | |
| #include "txn_box/Expr.h" | |
| #include "txn_box/ts_util.h" | |
| using swoc::ArenaWriter; | |
| using swoc::BufferWriter; | |
| using swoc::Errata; | |
| using swoc::FixedBufferWriter; | |
| using swoc::MemSpan; | |
| using swoc::TextView; | |
| using namespace swoc::literals; | |
| /* ------------------------------------------------------------------------------------ */ | |
| bool | |
| Expr::bwf_ex::operator()(std::string_view &literal, Extractor::Spec &spec) | |
| { | |
| bool zret = false; | |
| if (_iter->_type == swoc::bwf::Spec::LITERAL_TYPE) { | |
| literal = _iter->_ext; | |
| if (++_iter == _specs.end()) { // all done! | |
| return zret; | |
| } | |
| } | |
| if (_iter->_type != swoc::bwf::Spec::LITERAL_TYPE) { | |
| spec = *_iter; | |
| ++_iter; | |
| zret = true; | |
| } | |
| return zret; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| Context::Context(std::shared_ptr<Config> const &cfg) : _cfg(cfg) | |
| { | |
| size_t reserved_size = G._remap_ctx_storage_required + (cfg ? cfg->reserved_ctx_storage_size() : 0); | |
| // This is arranged so @a _arena destructor will clean up properly, nothing more need be done. | |
| _arena.reset(swoc::MemArena::construct_self_contained(4000 + reserved_size)); | |
| _rxp_ctx = pcre2_general_context_create( | |
| [](PCRE2_SIZE size, void *ctx) -> void * { return static_cast<self_type *>(ctx)->_arena->alloc(size).data(); }, | |
| [](void *, void *) -> void {}, this); | |
| if (cfg) { | |
| /// Make sure there are sufficient capture groups. | |
| this->rxp_match_require(cfg->_capture_groups); | |
| } | |
| if (reserved_size) { | |
| // Directive shared storage | |
| _ctx_store = _arena->alloc(reserved_size); | |
| memset(_ctx_store, 0); // Zero initialize it. | |
| } | |
| } | |
| Context::~Context() | |
| { | |
| // Invoke all the finalizers to do additional cleanup. | |
| for (auto &&f : _finalizers) { | |
| f._f(f._ptr); | |
| std::destroy_at(&f._f); // clean up the cleaner too, just in case. | |
| } | |
| } | |
| swoc::Errata | |
| Context::Callback::invoke(Context &ctx) | |
| { | |
| return _drtv->invoke(ctx); | |
| } | |
| Errata | |
| Context::on_hook_do(Hook hook_idx, Directive *drtv) | |
| { | |
| auto &info{_hooks[IndexFor(hook_idx)]}; | |
| if (!info.hook_set_p) { // no hook to invoke this directive, set one up. | |
| if (hook_idx >= _cur_hook) { | |
| TSHttpTxnHookAdd(_txn, TS_Hook[IndexFor(hook_idx)], _cont); | |
| info.hook_set_p = true; | |
| } else if (hook_idx < _cur_hook) { | |
| // error condition - should report. Also, should detect this on config load. | |
| } | |
| } | |
| info.cb_list.append(_arena->make<Callback>(drtv)); | |
| return {}; | |
| } | |
| Errata | |
| Context::invoke_callbacks() | |
| { | |
| // Bit of subtlety here - directives / callbacks can be added to the list due to the action of the | |
| // invoked directive from this list. However, because this is an intrusive list and items are only | |
| // added to the end, the @c next pointer for the current item will be updated before the loop | |
| // iteration occurs and therefore new directives will be invoked. | |
| auto &info{_hooks[IndexFor(_cur_hook)]}; | |
| for (auto &cb : info.cb_list) { | |
| _terminal_p = false; // reset before each scheduled callback. | |
| cb.invoke(*this); | |
| } | |
| return {}; | |
| } | |
| Errata | |
| Context::invoke_for_hook(Hook hook) | |
| { | |
| _cur_hook = hook; | |
| this->clear_cache(); | |
| // Run the top level directives in the config first. | |
| if (_cfg) { | |
| for (auto const &handle : _cfg->hook_directives(hook)) { | |
| _terminal_p = false; // reset before each top level invocation. | |
| handle->invoke(*this); // need to log errors here. | |
| } | |
| } | |
| this->invoke_callbacks(); | |
| _cur_hook = Hook::INVALID; | |
| return {}; | |
| } | |
| Errata | |
| Context::invoke_for_remap(Config &rule_cfg, TSRemapRequestInfo *rri) | |
| { | |
| _cur_hook = Hook::REMAP; | |
| _remap_info = rri; | |
| this->clear_cache(); | |
| this->rxp_match_require(rule_cfg._capture_groups); | |
| // What about directive storage? | |
| // Remap rule directives. | |
| _terminal_p = false; // reset before each top level invocation. | |
| for (auto const &handle : rule_cfg.hook_directives(_cur_hook)) { | |
| handle->invoke(*this); // need to log errors here. | |
| if (_terminal_p) { | |
| break; | |
| } | |
| } | |
| // Now the global config directives for REMAP | |
| if (_cfg) { | |
| for (auto const &handle : _cfg->hook_directives(_cur_hook)) { | |
| _terminal_p = false; // reset before each top level invocation. | |
| handle->invoke(*this); // need to log errors here. | |
| } | |
| } | |
| this->invoke_callbacks(); // Any accumulated callbacks. | |
| // Revert from remap style invocation. | |
| _cur_hook = Hook::INVALID; | |
| _remap_info = nullptr; | |
| return {}; | |
| } | |
| void | |
| Context::operator()(swoc::BufferWriter &w, Extractor::Spec const &spec) | |
| { | |
| spec._exf->format(w, spec, *this); | |
| } | |
| Feature | |
| Expr::bwf_visitor::operator()(const Composite &comp) | |
| { | |
| return _ctx.render_transient([&](BufferWriter &w) { w.print_nfv(_ctx, bwf_ex{comp._specs}, Context::ArgPack(_ctx)); }); | |
| } | |
| Feature | |
| Expr::bwf_visitor::operator()(List const &list) | |
| { | |
| feature_type_for<TUPLE> expr_tuple = _ctx.alloc_span<Feature>(list._exprs.size()); | |
| unsigned idx = 0; | |
| for (auto const &expr : list._exprs) { | |
| Feature feature{_ctx.extract(expr)}; | |
| _ctx.commit(feature); | |
| expr_tuple[idx++] = feature; | |
| } | |
| return expr_tuple; | |
| } | |
| Feature | |
| Context::extract(Expr const &expr) | |
| { | |
| auto value = std::visit(Expr::bwf_visitor(*this), expr._raw); | |
| for (auto const &mod : expr._mods) { | |
| value = (*mod)(*this, value); | |
| } | |
| return value; | |
| } | |
| FeatureView | |
| Context::extract_view(const Expr &expr, std::initializer_list<ViewOption> opts) | |
| { | |
| FeatureView zret{}; | |
| bool commit_p = false; | |
| bool cstr_p = false; | |
| for (auto opt : opts) { | |
| switch (opt) { | |
| case EX_COMMIT: | |
| commit_p = true; | |
| break; | |
| case EX_C_STR: | |
| cstr_p = true; | |
| break; | |
| } | |
| } | |
| auto f = this->extract(expr); | |
| if (IndexFor(STRING) == f.index()) { | |
| auto view = std::get<IndexFor(STRING)>(f); | |
| if (cstr_p && !view._cstr_p) { | |
| if (!view._literal_p && !view._direct_p) { // in temporary memory | |
| // If there's room, just add the null terminator. | |
| if (auto span = _arena->remnant().rebind<char>(); span.data() == view.data_end()) { | |
| _arena->alloc(1); | |
| span[0] = '\0'; | |
| view._cstr_p = true; | |
| } else { | |
| _arena->alloc(view.size()); // commit the view data and copy. | |
| view._literal_p = true; | |
| } | |
| } | |
| // if it's still in fixed memory, need to copy and terminate. | |
| if (view._literal_p) { | |
| auto span = _arena->require(view.size() + 1).remnant().rebind<char>(); | |
| memcpy(span, view); | |
| span[view.size()] = '\0'; | |
| view = TextView(span); | |
| view.remove_suffix(1); // drop null from view. | |
| view._cstr_p = true; | |
| view._literal_p = false; | |
| } | |
| } | |
| zret = view; | |
| } else { | |
| ArenaWriter w{*_arena}; | |
| if (cstr_p) { | |
| w.print("{}\0", f); | |
| zret = TextView{w.view()}.remove_suffix(1); | |
| zret._cstr_p = true; | |
| } else { | |
| w.print("{}", f); | |
| zret = w.view(); | |
| } | |
| } | |
| if (commit_p && !zret._literal_p && !zret._direct_p) { | |
| _arena->alloc(zret.size() + (zret._cstr_p ? 1 : 0)); | |
| zret._literal_p = true; | |
| } | |
| return zret; | |
| } | |
| FeatureView | |
| Context::commit(FeatureView const &view) | |
| { | |
| FeatureView zret{view}; | |
| if (view._literal_p) { // already committed. | |
| } else if (view._direct_p) { | |
| auto span{_arena->alloc(view.size())}; | |
| memcpy(span, view); | |
| zret = TextView(span.rebind<char>()); // update full to be the localized copy. | |
| zret._direct_p = false; | |
| zret._literal_p = true; | |
| } else if (auto r{_arena->remnant()}; r.contains(view.data())) { | |
| // it's in transient memory, finalize it. | |
| // Need to commit everything before it as well. | |
| size_t n = (view.data() - r.rebind<char>().data()) + view.size(); | |
| _arena->alloc(n); | |
| _transient = (n >= _transient ? 0 : _transient - n); | |
| zret._literal_p = true; | |
| } else if (_arena->contains(view.data())) { // it's already been committed, mark it so. | |
| zret._literal_p = true; | |
| } | |
| return zret; | |
| } | |
| Feature & | |
| Context::commit(Feature &feature) | |
| { | |
| if (auto fv = std::get_if<STRING>(&feature); fv != nullptr) { | |
| feature = this->commit(*fv); | |
| } | |
| return feature; | |
| } | |
| ts::HttpRequest | |
| Context::ua_req_hdr() | |
| { | |
| if (!_ua_req.is_valid()) { | |
| _ua_req = _txn.ua_req_hdr(); | |
| } | |
| return _ua_req; | |
| } | |
| ts::HttpRequest | |
| Context::proxy_req_hdr() | |
| { | |
| if (!_proxy_req.is_valid()) { | |
| _proxy_req = _txn.preq_hdr(); | |
| } | |
| return _proxy_req; | |
| } | |
| ts::HttpResponse | |
| Context::upstream_rsp_hdr() | |
| { | |
| if (!_upstream_rsp.is_valid()) { | |
| _upstream_rsp = _txn.ursp_hdr(); | |
| } | |
| return _upstream_rsp; | |
| } | |
| ts::HttpResponse | |
| Context::proxy_rsp_hdr() | |
| { | |
| if (!_proxy_rsp.is_valid()) { | |
| _proxy_rsp = _txn.prsp_hdr(); | |
| } | |
| return _proxy_rsp; | |
| } | |
| Context::self_type & | |
| Context::enable_hooks(TSHttpTxn txn) | |
| { | |
| // Create a continuation to hold the data. | |
| _cont = TSContCreate(ts_callback, TSContMutexGet(reinterpret_cast<TSCont>(txn))); | |
| TSContDataSet(_cont, this); | |
| _txn = txn; | |
| // set hooks for top level directives. | |
| if (_cfg) { | |
| for (unsigned idx = 0; idx < std::tuple_size<Hook>::value; ++idx) { | |
| auto const &drtv_list{_cfg->hook_directives(static_cast<Hook>(idx))}; | |
| if (!drtv_list.empty()) { | |
| TSHttpTxnHookAdd(txn, TS_Hook[idx], _cont); | |
| _hooks[idx].hook_set_p = true; | |
| } | |
| } | |
| } | |
| // Always set a cleanup hook. | |
| TSHttpTxnHookAdd(txn, TS_HTTP_TXN_CLOSE_HOOK, _cont); | |
| _txn.arg_assign(G.TxnArgIdx, this); | |
| return *this; | |
| } | |
| int | |
| Context::ts_callback(TSCont cont, TSEvent evt, void *) | |
| { | |
| self_type *self = static_cast<self_type *>(TSContDataGet(cont)); | |
| auto txn = self->_txn; // cache for TXN_CLOSE. | |
| self->_global_status = TS_EVENT_HTTP_CONTINUE; | |
| // Run the directives. | |
| Hook hook{Convert_TS_Event_To_TxB_Hook(evt)}; | |
| if (Hook::INVALID != hook) { | |
| self->invoke_for_hook(hook); | |
| } | |
| /// TXN Close is special - do internal cleanup after explicit directives are done. | |
| TSEvent saved_status = self->_global_status; // avoid possible use after free. | |
| if (TS_EVENT_HTTP_TXN_CLOSE == evt) { | |
| TSContDataSet(cont, nullptr); | |
| TSContDestroy(cont); | |
| delete self; | |
| } | |
| TSHttpTxnReenable(txn, saved_status); | |
| return TS_SUCCESS; | |
| } | |
| Context & | |
| Context::rxp_match_require(unsigned n) | |
| { | |
| if (_rxp_n < n) { | |
| // Bump up at least 7, or 50%, or at least @a n. | |
| n = std::max(_rxp_n + 7, n); | |
| n = std::max((3 * _rxp_n) / 2, n); | |
| _rxp_working = pcre2_match_data_create(n, _rxp_ctx); | |
| _rxp_active = pcre2_match_data_create(n, _rxp_ctx); | |
| _rxp_n = n; | |
| } | |
| return *this; | |
| } | |
| void | |
| Context::set_literal_capture(swoc::TextView text) | |
| { | |
| auto ovector = pcre2_get_ovector_pointer(_rxp_active); | |
| ovector[0] = 0; | |
| ovector[1] = text.size() - 1; | |
| _rxp_src = text; | |
| } | |
| pcre2_match_data * | |
| Context::rxp_commit_match(swoc::TextView const &src) | |
| { | |
| _rxp_src = src; | |
| std::swap(_rxp_active, _rxp_working); | |
| return _rxp_active; | |
| } | |
| Feature const & | |
| Context::load_txn_var(swoc::TextView const &name) | |
| { | |
| auto spot = _txn_vars.find(name); | |
| if (spot == _txn_vars.end()) { | |
| // Later, need to search inbound_ssn and global variables and retrieve those if found. | |
| return NIL_FEATURE; | |
| } | |
| return spot->_value; | |
| } | |
| Context::self_type & | |
| Context::store_txn_var(swoc::TextView const &name, Feature &value) | |
| { | |
| auto spot = _txn_vars.find(name); | |
| this->commit(value); | |
| if (spot == _txn_vars.end()) { | |
| _txn_vars.insert(_arena->make<TxnVar>(name, value)); | |
| } else { | |
| spot->_value = value; | |
| } | |
| return *this; | |
| } | |
| TextView | |
| Context::localize_as_c_str(swoc::TextView text) | |
| { | |
| // If it's empty or isn't already a C string, make a copy that is. | |
| if (text.empty() || '\0' != text.back()) { | |
| auto span = _arena->alloc_span<char>(text.size() + 1); | |
| memcpy(span, text); | |
| span[text.size()] = '\0'; | |
| text = span; | |
| } | |
| return text; | |
| } | |
| MemSpan<void> | |
| Context::overflow_storage_for(const ReservedSpan &span) | |
| { | |
| using T = decltype(_overflow_spans)::value_type; | |
| // Is this offset already in the list? | |
| auto spot = | |
| std::find_if(_overflow_spans.begin(), _overflow_spans.end(), [&](auto const &item) { return item._offset == span.offset; }); | |
| if (spot != _overflow_spans.end()) { | |
| return spot->_storage; | |
| } | |
| // Make a record for the list to find later. | |
| auto item = _arena->alloc_span<T>(1).data(); | |
| new (item) T; | |
| item->_offset = span.offset; | |
| _overflow_spans.append(item); | |
| // Get the actual reserved memory block, along with the required status block in front of it. | |
| item->_storage = _arena->alloc(span.n + sizeof(ReservedStatus), alignof(ReservedStatus)); | |
| memset(item->_storage, 0); | |
| item->_storage.remove_prefix(sizeof(ReservedStatus)); | |
| return item->_storage; | |
| } | |
| swoc::MemSpan<char> | |
| Context::transient_buffer(size_t required) | |
| { | |
| this->commit_transient(); | |
| auto span{_arena->require(required).remnant().rebind<char>()}; | |
| _transient = TRANSIENT_ACTIVE; | |
| return span; | |
| } | |
| Context::self_type & | |
| Context::transient_require(size_t n) | |
| { | |
| this->commit_transient(); | |
| _arena->require(n); | |
| return *this; | |
| } | |
| Context::self_type & | |
| Context::commit_transient() | |
| { | |
| if (_transient == TRANSIENT_ACTIVE) { | |
| throw std::logic_error("Recursive use of transient buffer in context"); | |
| } else if (_transient) { | |
| _arena->alloc(_transient); | |
| _transient = 0; | |
| } | |
| return *this; | |
| } | |
| TextView | |
| Context::active_group(int idx) | |
| { | |
| auto ovector = pcre2_get_ovector_pointer(_rxp_active); | |
| idx *= 2; // To account for offset pairs. | |
| TS_DBG("Access match group %d at offsets %ld:%ld", idx / 2, ovector[idx], ovector[idx + 1]); | |
| return _rxp_src.substr(ovector[idx], ovector[idx + 1] - ovector[idx]); | |
| } | |
| unsigned | |
| Context::ArgPack::count() const | |
| { | |
| return pcre2_get_ovector_count(_ctx._rxp_active); | |
| } | |
| BufferWriter & | |
| Context::ArgPack::print(BufferWriter &w, swoc::bwf::Spec const &spec, unsigned idx) const | |
| { | |
| return bwformat(w, spec, _ctx.active_group(idx)); | |
| } | |
| std::any | |
| Context::ArgPack::capture(unsigned) const | |
| { | |
| return "Bogus"_sv; | |
| } |