| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| ////////////////////////////////////////////////////////////////////////////////////////////// |
| // operators.cc: implementation of the operator classes |
| // |
| // |
| #include <arpa/inet.h> |
| #include <cstring> |
| #include <algorithm> |
| #include <iomanip> |
| |
| #include "records/RecCore.h" |
| #include "ts/ts.h" |
| #include "swoc/swoc_file.h" |
| |
| #include "operators.h" |
| #include "ts/apidefs.h" |
| #include "conditions.h" |
| #include "factory.h" |
| #include "ruleset.h" |
| |
| namespace |
| { |
| const unsigned int LOCAL_IP_ADDRESS = 0x0100007f; |
| const unsigned int MAX_SIZE = 256; |
| const int LOCAL_PORT = 8080; |
| |
| int |
| handleFetchEvents(TSCont cont, TSEvent event, void *edata) |
| { |
| TSHttpTxn http_txn = static_cast<TSHttpTxn>(TSContDataGet(cont)); |
| |
| switch (static_cast<int>(event)) { |
| case OperatorSetBodyFrom::TS_EVENT_FETCHSM_SUCCESS: { |
| TSHttpTxn fetchsm_txn = static_cast<TSHttpTxn>(edata); |
| int data_len; |
| const char *data_start = TSFetchRespGet(fetchsm_txn, &data_len); |
| if (data_start && (data_len > 0)) { |
| const char *data_end = data_start + data_len; |
| TSHttpParser parser = TSHttpParserCreate(); |
| TSMBuffer hdr_buf = TSMBufferCreate(); |
| TSMLoc hdr_loc = TSHttpHdrCreate(hdr_buf); |
| |
| TSHttpHdrTypeSet(hdr_buf, hdr_loc, TS_HTTP_TYPE_RESPONSE); |
| if (TSHttpHdrParseResp(parser, hdr_buf, hdr_loc, &data_start, data_end) == TS_PARSE_DONE) { |
| TSHttpTxnErrorBodySet(http_txn, TSstrdup(data_start), (data_end - data_start), nullptr); |
| } else { |
| TSWarning("[%s] Unable to parse set-custom-body fetch response", __FUNCTION__); |
| } |
| TSHttpParserDestroy(parser); |
| TSHandleMLocRelease(hdr_buf, nullptr, hdr_loc); |
| TSMBufferDestroy(hdr_buf); |
| } else { |
| TSWarning("[%s] Successful set-custom-body fetch did not result in any content", __FUNCTION__); |
| } |
| TSHttpTxnReenable(http_txn, TS_EVENT_HTTP_ERROR); |
| } break; |
| case OperatorSetBodyFrom::TS_EVENT_FETCHSM_FAILURE: { |
| Dbg(pi_dbg_ctl, "OperatorSetBodyFrom: Error getting custom body"); |
| TSHttpTxnReenable(http_txn, TS_EVENT_HTTP_CONTINUE); |
| } break; |
| case OperatorSetBodyFrom::TS_EVENT_FETCHSM_TIMEOUT: { |
| Dbg(pi_dbg_ctl, "OperatorSetBodyFrom: Timeout getting custom body"); |
| TSHttpTxnReenable(http_txn, TS_EVENT_HTTP_CONTINUE); |
| } break; |
| case TS_EVENT_HTTP_TXN_CLOSE: { |
| TSContDestroy(cont); |
| TSHttpTxnReenable(http_txn, TS_EVENT_HTTP_CONTINUE); |
| } break; |
| default: |
| TSError("[%s] handleFetchEvents got unknown event: %d", PLUGIN_NAME, event); |
| break; |
| } |
| return 0; |
| } |
| |
| TSReturnCode |
| createRequestString(const std::string_view &value, char (&req_buf)[MAX_SIZE], int *req_buf_size) |
| { |
| const char *start = value.data(); |
| const char *end = start + value.size(); |
| TSMLoc url_loc; |
| TSMBuffer url_buf = TSMBufferCreate(); |
| int host_len, url_len = 0; |
| |
| if (TSUrlCreate(url_buf, &url_loc) == TS_SUCCESS && TSUrlParse(url_buf, url_loc, &start, end) == TS_PARSE_DONE) { |
| const char *host = TSUrlHostGet(url_buf, url_loc, &host_len); |
| const char *url = TSUrlStringGet(url_buf, url_loc, &url_len); |
| |
| *req_buf_size = snprintf(req_buf, MAX_SIZE, "GET %.*s HTTP/1.1\r\nHost: %.*s\r\n\r\n", url_len, url, host_len, host); |
| |
| TSMBufferDestroy(url_buf); |
| |
| return TS_SUCCESS; |
| } else { |
| Dbg(pi_dbg_ctl, "Failed to parse url %s", start); |
| TSMBufferDestroy(url_buf); |
| return TS_ERROR; |
| } |
| } |
| |
| } // namespace |
| |
| // OperatorConfig |
| void |
| OperatorSetConfig::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| _config = p.get_arg(); |
| |
| if (TS_SUCCESS == TSHttpTxnConfigFind(_config.c_str(), _config.size(), &_key, &_type)) { |
| _value.set_value(p.get_value(), this); |
| } else { |
| _key = TS_CONFIG_NULL; |
| TSError("[%s] no such records config: %s", PLUGIN_NAME, _config.c_str()); |
| } |
| } |
| |
| bool |
| OperatorSetConfig::exec(const Resources &res) const |
| { |
| if (TS_CONFIG_NULL != _key) { |
| switch (_type) { |
| case TS_RECORDDATATYPE_INT: |
| if (TS_SUCCESS == TSHttpTxnConfigIntSet(res.state.txnp, _key, _value.get_int_value())) { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%d", _config.c_str(), _value.get_int_value()); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on %s=%d", _config.c_str(), _value.get_int_value()); |
| } |
| break; |
| case TS_RECORDDATATYPE_FLOAT: |
| if (TS_SUCCESS == TSHttpTxnConfigFloatSet(res.state.txnp, _key, _value.get_float_value())) { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%f", _config.c_str(), _value.get_float_value()); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on %s=%f", _config.c_str(), _value.get_float_value()); |
| } |
| break; |
| case TS_RECORDDATATYPE_STRING: |
| if (TS_SUCCESS == TSHttpTxnConfigStringSet(res.state.txnp, _key, _value.get_value().c_str(), _value.size())) { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%s", _config.c_str(), _value.get_value().c_str()); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on %s=%s", _config.c_str(), _value.get_value().c_str()); |
| } |
| break; |
| default: |
| TSError("[%s] unknown data type, whut?", PLUGIN_NAME); |
| break; |
| } |
| } |
| return true; |
| } |
| |
| // OperatorSetStatus |
| void |
| OperatorSetStatus::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _status.set_value(p.get_arg(), this); |
| |
| if (nullptr == (_reason = TSHttpHdrReasonLookup(static_cast<TSHttpStatus>(_status.get_int_value())))) { |
| TSError("[%s] unknown status %d", PLUGIN_NAME, _status.get_int_value()); |
| _reason_len = 0; |
| } else { |
| _reason_len = strlen(_reason); |
| } |
| |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| require_resources(RSRC_CLIENT_RESPONSE_HEADERS); |
| require_resources(RSRC_RESPONSE_STATUS); |
| } |
| |
| void |
| OperatorSetStatus::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetStatus::exec(const Resources &res) const |
| { |
| switch (get_hook()) { |
| case TS_HTTP_READ_RESPONSE_HDR_HOOK: |
| case TS_HTTP_SEND_RESPONSE_HDR_HOOK: |
| if (res.bufp && res.hdr_loc) { |
| TSHttpHdrStatusSet(res.bufp, res.hdr_loc, static_cast<TSHttpStatus>(_status.get_int_value()), res.state.txnp, PLUGIN_NAME); |
| if (_reason && _reason_len > 0) { |
| TSHttpHdrReasonSet(res.bufp, res.hdr_loc, _reason, _reason_len); |
| } |
| } |
| break; |
| default: |
| TSHttpTxnStatusSet(res.state.txnp, static_cast<TSHttpStatus>(_status.get_int_value()), PLUGIN_NAME); |
| break; |
| } |
| |
| Dbg(pi_dbg_ctl, "OperatorSetStatus::exec() invoked with status=%d", _status.get_int_value()); |
| |
| return true; |
| } |
| |
| // OperatorSetStatusReason |
| void |
| OperatorSetStatusReason::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _reason.set_value(p.get_arg(), this); |
| require_resources(RSRC_CLIENT_RESPONSE_HEADERS); |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| } |
| |
| void |
| OperatorSetStatusReason::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| } |
| |
| bool |
| OperatorSetStatusReason::exec(const Resources &res) const |
| { |
| if (res.bufp && res.hdr_loc) { |
| std::string reason; |
| |
| _reason.append_value(reason, res); |
| if (reason.size() > 0) { |
| Dbg(pi_dbg_ctl, "Setting Status Reason to %s", reason.c_str()); |
| TSHttpHdrReasonSet(res.bufp, res.hdr_loc, reason.c_str(), reason.size()); |
| } |
| } |
| return true; |
| } |
| |
| // OperatorSetDestination |
| void |
| OperatorSetDestination::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _url_qual = parse_url_qualifier(p.get_arg()); |
| _value.set_value(p.get_value(), this); |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); |
| require_resources(RSRC_SERVER_REQUEST_HEADERS); |
| } |
| |
| bool |
| OperatorSetDestination::exec(const Resources &res) const |
| { |
| if (res._rri || (res.bufp && res.hdr_loc)) { |
| std::string value; |
| TSMBuffer bufp; |
| TSMLoc url_m_loc; |
| |
| // Determine which TSMBuffer and TSMLoc to use |
| if (res._rri && !res.changed_url) { |
| bufp = res._rri->requestBufp; |
| url_m_loc = res._rri->requestUrl; |
| } else { |
| bufp = res.bufp; |
| if (TSHttpHdrUrlGet(res.bufp, res.hdr_loc, &url_m_loc) != TS_SUCCESS) { |
| Dbg(pi_dbg_ctl, "TSHttpHdrUrlGet was unable to return the url m_loc"); |
| return true; |
| } |
| } |
| |
| // Never set an empty destination value (I don't think that ever makes sense?) |
| switch (_url_qual) { |
| case URL_QUAL_HOST: |
| _value.append_value(value, res); |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set destination HOST to an empty value, skipping"); |
| } else { |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlHostSet(bufp, url_m_loc, value.c_str(), value.size()); |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() invoked with HOST: %s", value.c_str()); |
| } |
| break; |
| |
| case URL_QUAL_PATH: |
| _value.append_value(value, res); |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set destination PATH to an empty value, skipping"); |
| } else { |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlPathSet(bufp, url_m_loc, value.c_str(), value.size()); |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() invoked with PATH: %s", value.c_str()); |
| } |
| break; |
| |
| case URL_QUAL_QUERY: |
| _value.append_value(value, res); |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set destination QUERY to an empty value, skipping"); |
| } else { |
| // 1.6.4--Support for preserving QSA in case of set-destination |
| if (get_oper_modifiers() & OPER_QSA) { |
| int query_len = 0; |
| const char *query = TSUrlHttpQueryGet(bufp, url_m_loc, &query_len); |
| Dbg(pi_dbg_ctl, "QSA mode, append original query string: %.*s", query_len, query); |
| // std::string connector = (value.find("?") == std::string::npos)? "?" : "&"; |
| value.append("&"); |
| value.append(query, query_len); |
| } |
| |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlHttpQuerySet(bufp, url_m_loc, value.c_str(), value.size()); |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() invoked with QUERY: %s", value.c_str()); |
| } |
| break; |
| |
| case URL_QUAL_PORT: |
| if (_value.get_int_value() <= 0 || _value.get_int_value() > 0xFFFF) { |
| Dbg(pi_dbg_ctl, "Would set destination PORT to an invalid range, skipping"); |
| } else { |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlPortSet(bufp, url_m_loc, _value.get_int_value()); |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() invoked with PORT: %d", _value.get_int_value()); |
| } |
| break; |
| case URL_QUAL_URL: |
| _value.append_value(value, res); |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set destination URL to an empty value, skipping"); |
| } else { |
| const char *start = value.c_str(); |
| const char *end = start + value.size(); |
| TSMLoc new_url_loc; |
| if (TSUrlCreate(bufp, &new_url_loc) == TS_SUCCESS && TSUrlParse(bufp, new_url_loc, &start, end) == TS_PARSE_DONE && |
| TSHttpHdrUrlSet(bufp, res.hdr_loc, new_url_loc) == TS_SUCCESS) { |
| const_cast<Resources &>(res).changed_url = true; |
| Dbg(pi_dbg_ctl, "Set destination URL to %s", value.c_str()); |
| } else { |
| Dbg(pi_dbg_ctl, "Failed to set URL %s", value.c_str()); |
| } |
| } |
| break; |
| case URL_QUAL_SCHEME: |
| _value.append_value(value, res); |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set destination SCHEME to an empty value, skipping"); |
| } else { |
| TSUrlSchemeSet(bufp, url_m_loc, value.c_str(), value.length()); |
| const_cast<Resources &>(res).changed_url = true; |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() invoked with SCHEME: %s", value.c_str()); |
| } |
| break; |
| default: |
| Dbg(pi_dbg_ctl, "Set destination %i has no handler", _url_qual); |
| break; |
| } |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetDestination::exec() unable to continue due to missing bufp=%p or hdr_loc=%p, rri=%p!", res.bufp, |
| res.hdr_loc, res._rri); |
| } |
| return true; |
| } |
| |
| #include <iostream> |
| |
| // OperatorRMDestination |
| static std::vector<std::string_view> |
| _tokenize(swoc::TextView text, char delimiter) |
| { |
| std::vector<std::string_view> tokens; |
| |
| while (text) { |
| tokens.push_back(text.take_prefix_at(delimiter)); |
| } |
| |
| return tokens; |
| } |
| |
| void |
| OperatorRMDestination::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _url_qual = parse_url_qualifier(p.get_arg()); |
| _stop = p.get_value(); |
| |
| if (!_stop.empty()) { |
| if (get_oper_modifiers() & OPER_INV) { |
| _keep = true; |
| } |
| _stop_list = _tokenize(_stop, ','); |
| } |
| |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); |
| require_resources(RSRC_SERVER_REQUEST_HEADERS); |
| } |
| |
| bool |
| OperatorRMDestination::exec(const Resources &res) const |
| { |
| if (res._rri || (res.bufp && res.hdr_loc)) { |
| std::string value = ""; |
| |
| // Determine which TSMBuffer and TSMLoc to use |
| TSMBuffer bufp; |
| TSMLoc url_m_loc; |
| if (res._rri) { |
| bufp = res._rri->requestBufp; |
| url_m_loc = res._rri->requestUrl; |
| } else { |
| bufp = res.bufp; |
| if (TSHttpHdrUrlGet(res.bufp, res.hdr_loc, &url_m_loc) != TS_SUCCESS) { |
| Dbg(pi_dbg_ctl, "TSHttpHdrUrlGet was unable to return the url m_loc"); |
| return true; |
| } |
| } |
| |
| // Never set an empty destination value (I don't think that ever makes sense?) |
| switch (_url_qual) { |
| case URL_QUAL_PATH: |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlPathSet(bufp, url_m_loc, value.c_str(), value.size()); |
| Dbg(pi_dbg_ctl, "OperatorRMDestination::exec() deleting PATH"); |
| break; |
| case URL_QUAL_QUERY: |
| if (_stop_list.size() > 0) { |
| int q_len = 0; |
| const char *query = TSUrlHttpQueryGet(bufp, url_m_loc, &q_len); |
| |
| if (q_len > 0) { |
| for (auto &q : _tokenize({query, static_cast<size_t>(q_len)}, '&')) { |
| auto eq_pos = q.find('='); |
| auto it = std::find(_stop_list.begin(), _stop_list.end(), (eq_pos != std::string_view::npos) ? q.substr(0, eq_pos) : q); |
| |
| if (_keep == (it != _stop_list.end())) { |
| if (!value.empty()) { |
| value.append("&").append(q); |
| } else { |
| value = q; |
| } |
| } |
| } |
| } |
| Dbg(pi_dbg_ctl, "OperatorRMDestination::exec() rewrote QUERY to \"%s\"", value.c_str()); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorRMDestination::exec() deleting QUERY"); |
| } |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlHttpQuerySet(bufp, url_m_loc, value.c_str(), value.size()); |
| break; |
| case URL_QUAL_PORT: |
| const_cast<Resources &>(res).changed_url = true; |
| TSUrlPortSet(bufp, url_m_loc, 0); |
| Dbg(pi_dbg_ctl, "OperatorRMDestination::exec() deleting PORT"); |
| break; |
| default: |
| Dbg(pi_dbg_ctl, "RM Destination %i has no handler", _url_qual); |
| break; |
| } |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorRMDestination::exec() unable to continue due to missing bufp=%p or hdr_loc=%p, rri=%p!", res.bufp, |
| res.hdr_loc, res._rri); |
| } |
| return true; |
| } |
| |
| // OperatorSetRedirect |
| void |
| OperatorSetRedirect::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _status.set_value(p.get_arg(), this); |
| _location.set_value(p.get_value(), this); |
| auto status = _status.get_int_value(); |
| if (status < 300 || status > 399 || status == TS_HTTP_STATUS_NOT_MODIFIED) { |
| TSError("[%s] unsupported redirect status %d", PLUGIN_NAME, status); |
| } |
| |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| require_resources(RSRC_CLIENT_RESPONSE_HEADERS); |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); |
| require_resources(RSRC_RESPONSE_STATUS); |
| } |
| |
| void |
| OperatorSetRedirect::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| void |
| EditRedirectResponse(TSHttpTxn txnp, const std::string &location, TSHttpStatus status, TSMBuffer bufp, TSMLoc hdr_loc) |
| { |
| // Set new location. |
| TSMLoc field_loc; |
| static std::string header("Location"); |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(bufp, hdr_loc, header.c_str(), header.size(), &field_loc)) { |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, location.c_str(), location.size())) { |
| Dbg(pi_dbg_ctl, " Adding header %s", header.c_str()); |
| TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc); |
| } |
| const char *reason = TSHttpHdrReasonLookup(status); |
| size_t len = strlen(reason); |
| TSHttpHdrReasonSet(bufp, hdr_loc, reason, len); |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| } |
| |
| // Set the body. |
| static std::string msg = "<HTML>\n<HEAD>\n<TITLE>Document Has Moved</TITLE>\n</HEAD>\n" |
| "<BODY BGCOLOR=\"white\" FGCOLOR=\"black\">\n" |
| "<H1>Document Has Moved</H1>\n<HR>\n<FONT FACE=\"Helvetica,Arial\"><B>\n" |
| "Description: The document you requested has moved to a new location." |
| " The new location is \"" + |
| location + "\".\n</B></FONT>\n<HR>\n</BODY>\n"; |
| TSHttpTxnErrorBodySet(txnp, TSstrdup(msg.c_str()), msg.length(), TSstrdup("text/html")); |
| } |
| |
| bool |
| OperatorSetRedirect::exec(const Resources &res) const |
| { |
| if (res.bufp && res.hdr_loc && res.client_bufp && res.client_hdr_loc) { |
| std::string value; |
| |
| _location.append_value(value, res); |
| |
| bool remap = false; |
| if (nullptr != res._rri) { |
| remap = true; |
| Dbg(pi_dbg_ctl, "OperatorSetRedirect:exec() invoked from remap plugin"); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetRedirect:exec() not invoked from remap plugin"); |
| } |
| |
| TSMBuffer bufp; |
| TSMLoc url_loc; |
| if (remap) { |
| // Handle when called from remap plugin. |
| bufp = res._rri->requestBufp; |
| url_loc = res._rri->requestUrl; |
| } else { |
| // Handle when not called from remap plugin. |
| bufp = res.client_bufp; |
| if (TS_SUCCESS != TSHttpHdrUrlGet(res.client_bufp, res.client_hdr_loc, &url_loc)) { |
| Dbg(pi_dbg_ctl, "Could not get client URL"); |
| } |
| } |
| |
| // Replace %{PATH} to original path |
| size_t pos_path = 0; |
| if ((pos_path = value.find("%{PATH}")) != std::string::npos) { |
| value.erase(pos_path, 7); // erase %{PATH} from the rewritten to url |
| int path_len = 0; |
| const char *path = TSUrlPathGet(bufp, url_loc, &path_len); |
| if (path_len > 0) { |
| Dbg(pi_dbg_ctl, "Find %%{PATH} in redirect url, replace it with: %.*s", path_len, path); |
| value.insert(pos_path, path, path_len); |
| } |
| } |
| |
| // Append the original query string |
| int query_len = 0; |
| const char *query = TSUrlHttpQueryGet(bufp, url_loc, &query_len); |
| |
| if ((get_oper_modifiers() & OPER_QSA) && (query_len > 0)) { |
| Dbg(pi_dbg_ctl, "QSA mode, append original query string: %.*s", query_len, query); |
| std::string connector = (value.find('?') == std::string::npos) ? "?" : "&"; |
| value.append(connector); |
| value.append(query, query_len); |
| } |
| |
| // Prepare the destination URL for the redirect. |
| const char *start = value.c_str(); |
| const char *end = value.size() + start; |
| if (remap) { |
| // Set new location. |
| if (TS_PARSE_ERROR == TSUrlParse(bufp, url_loc, &start, end)) { |
| Dbg(pi_dbg_ctl, "Could not set Location field value to: %s", value.c_str()); |
| } |
| // Set the new status. |
| TSHttpTxnStatusSet(res.state.txnp, static_cast<TSHttpStatus>(_status.get_int_value()), PLUGIN_NAME); |
| const_cast<Resources &>(res).changed_url = true; |
| res._rri->redirect = 1; |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorSetRedirect::exec() hook=%d", int(get_hook())); |
| // Set the new status code and reason. |
| TSHttpStatus status = static_cast<TSHttpStatus>(_status.get_int_value()); |
| TSHttpHdrStatusSet(res.bufp, res.hdr_loc, status, res.state.txnp, PLUGIN_NAME); |
| EditRedirectResponse(res.state.txnp, value, status, res.bufp, res.hdr_loc); |
| } |
| Dbg(pi_dbg_ctl, "OperatorSetRedirect::exec() invoked with destination=%s and status code=%d", value.c_str(), |
| _status.get_int_value()); |
| } |
| return true; |
| } |
| |
| // OperatorSetTimeoutOut |
| void |
| OperatorSetTimeoutOut::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| if (p.get_arg() == "active") { |
| _type = TO_OUT_ACTIVE; |
| } else if (p.get_arg() == "inactive") { |
| _type = TO_OUT_INACTIVE; |
| } else if (p.get_arg() == "connect") { |
| _type = TO_OUT_CONNECT; |
| } else if (p.get_arg() == "dns") { |
| _type = TO_OUT_DNS; |
| } else { |
| _type = TO_OUT_UNDEFINED; |
| TSError("[%s] unsupported timeout qualifier: %s", PLUGIN_NAME, p.get_arg().c_str()); |
| } |
| |
| _timeout.set_value(p.get_value(), this); |
| } |
| |
| bool |
| OperatorSetTimeoutOut::exec(const Resources &res) const |
| { |
| switch (_type) { |
| case TO_OUT_ACTIVE: |
| Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(active, %d)", _timeout.get_int_value()); |
| TSHttpTxnActiveTimeoutSet(res.state.txnp, _timeout.get_int_value()); |
| break; |
| |
| case TO_OUT_INACTIVE: |
| Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(inactive, %d)", _timeout.get_int_value()); |
| TSHttpTxnNoActivityTimeoutSet(res.state.txnp, _timeout.get_int_value()); |
| break; |
| |
| case TO_OUT_CONNECT: |
| Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(connect, %d)", _timeout.get_int_value()); |
| TSHttpTxnConnectTimeoutSet(res.state.txnp, _timeout.get_int_value()); |
| break; |
| |
| case TO_OUT_DNS: |
| Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(dns, %d)", _timeout.get_int_value()); |
| TSHttpTxnDNSTimeoutSet(res.state.txnp, _timeout.get_int_value()); |
| break; |
| default: |
| TSError("[%s] unsupported timeout", PLUGIN_NAME); |
| break; |
| } |
| return true; |
| } |
| |
| // OperatorSkipRemap |
| // Deprecated: Remove for v10.0.0 |
| void |
| OperatorSkipRemap::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| if (p.get_arg() == "1" || p.get_arg() == "true" || p.get_arg() == "TRUE") { |
| _skip_remap = true; |
| } |
| } |
| |
| bool |
| OperatorSkipRemap::exec(const Resources &res) const |
| { |
| Dbg(pi_dbg_ctl, "OperatorSkipRemap::exec() skipping remap: %s", _skip_remap ? "True" : "False"); |
| TSHttpTxnCntlSet(res.state.txnp, TS_HTTP_CNTL_SKIP_REMAPPING, _skip_remap); |
| return true; |
| } |
| |
| // OperatorRMHeader |
| bool |
| OperatorRMHeader::exec(const Resources &res) const |
| { |
| TSMLoc field_loc, tmp; |
| |
| if (res.bufp && res.hdr_loc) { |
| Dbg(pi_dbg_ctl, "OperatorRMHeader::exec() invoked on %s", _header.c_str()); |
| field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, _header.c_str(), _header.size()); |
| while (field_loc) { |
| Dbg(pi_dbg_ctl, " Deleting header %s", _header.c_str()); |
| tmp = TSMimeHdrFieldNextDup(res.bufp, res.hdr_loc, field_loc); |
| TSMimeHdrFieldDestroy(res.bufp, res.hdr_loc, field_loc); |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| field_loc = tmp; |
| } |
| } |
| return true; |
| } |
| |
| // OperatorAddHeader |
| void |
| OperatorAddHeader::initialize(Parser &p) |
| { |
| OperatorHeaders::initialize(p); |
| |
| _value.set_value(p.get_value(), this); |
| } |
| |
| bool |
| OperatorAddHeader::exec(const Resources &res) const |
| { |
| std::string value; |
| |
| _value.append_value(value, res); |
| |
| // Never set an empty header (I don't think that ever makes sense?) |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set header %s to an empty value, skipping", _header.c_str()); |
| return true; |
| } |
| |
| if (res.bufp && res.hdr_loc) { |
| Dbg(pi_dbg_ctl, "OperatorAddHeader::exec() invoked on %s: %s", _header.c_str(), value.c_str()); |
| TSMLoc field_loc; |
| |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, _header.c_str(), _header.size(), &field_loc)) { |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) { |
| Dbg(pi_dbg_ctl, " Adding header %s", _header.c_str()); |
| TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| } |
| return true; |
| } |
| |
| // OperatorSetHeader |
| void |
| OperatorSetHeader::initialize(Parser &p) |
| { |
| OperatorHeaders::initialize(p); |
| |
| _value.set_value(p.get_value(), this); |
| } |
| |
| bool |
| OperatorSetHeader::exec(const Resources &res) const |
| { |
| std::string value; |
| |
| _value.append_value(value, res); |
| |
| // Never set an empty header (I don't think that ever makes sense?) |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Would set header %s to an empty value, skipping", _header.c_str()); |
| return true; |
| } |
| |
| if (res.bufp && res.hdr_loc) { |
| TSMLoc field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, _header_wks ? _header_wks : _header.c_str(), _header.size()); |
| |
| Dbg(pi_dbg_ctl, "OperatorSetHeader::exec() invoked on %s: %s", _header.c_str(), value.c_str()); |
| |
| if (!field_loc) { |
| // No existing header, so create one |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, _header.c_str(), _header.size(), &field_loc)) { |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) { |
| Dbg(pi_dbg_ctl, " Adding header %s", _header.c_str()); |
| TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| } else { |
| TSMLoc tmp = nullptr; |
| bool first = true; |
| |
| while (field_loc) { |
| tmp = TSMimeHdrFieldNextDup(res.bufp, res.hdr_loc, field_loc); |
| if (first) { |
| first = false; |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) { |
| Dbg(pi_dbg_ctl, " Overwriting header %s", _header.c_str()); |
| } |
| } else { |
| TSMimeHdrFieldDestroy(res.bufp, res.hdr_loc, field_loc); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| field_loc = tmp; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // OperatorSetBody |
| void |
| OperatorSetBody::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| // we want the arg since body only takes one value |
| _value.set_value(p.get_arg(), this); |
| } |
| |
| void |
| OperatorSetBody::initialize_hooks() |
| { |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| } |
| |
| bool |
| OperatorSetBody::exec(const Resources &res) const |
| { |
| std::string value; |
| |
| _value.append_value(value, res); |
| char *msg = nullptr; |
| if (!value.empty()) { |
| msg = TSstrdup(value.c_str()); |
| } |
| TSHttpTxnErrorBodySet(res.state.txnp, msg, value.size(), nullptr); |
| return true; |
| } |
| |
| // OperatorCounter |
| void |
| OperatorCounter::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _counter_name = p.get_arg(); |
| |
| // Sanity |
| if (_counter_name.length() == 0) { |
| TSError("[%s] counter name is empty", PLUGIN_NAME); |
| return; |
| } |
| |
| // Check if counter already created by another rule |
| if (TSStatFindName(_counter_name.c_str(), &_counter) == TS_ERROR) { |
| _counter = TSStatCreate(_counter_name.c_str(), TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT); |
| if (_counter == TS_ERROR) { |
| TSError("[%s] TSStatCreate() failed. Can't create counter: %s", PLUGIN_NAME, _counter_name.c_str()); |
| return; |
| } |
| Dbg(pi_dbg_ctl, "OperatorCounter::initialize(%s) created counter with id: %d", _counter_name.c_str(), _counter); |
| } else { |
| Dbg(pi_dbg_ctl, "OperatorCounter::initialize(%s) reusing id: %d", _counter_name.c_str(), _counter); |
| } |
| } |
| |
| bool |
| OperatorCounter::exec(const Resources & /* ATS_UNUSED res */) const |
| { |
| // Sanity |
| if (_counter == TS_ERROR) { |
| return true; |
| } |
| |
| Dbg(pi_dbg_ctl, "OperatorCounter::exec() invoked on %s", _counter_name.c_str()); |
| TSStatIntIncrement(_counter, 1); |
| return true; |
| } |
| |
| // OperatorRMCookie |
| bool |
| OperatorRMCookie::exec(const Resources &res) const |
| { |
| if (res.bufp && res.hdr_loc) { |
| Dbg(pi_dbg_ctl, "OperatorRMCookie::exec() invoked on cookie %s", _cookie.c_str()); |
| TSMLoc field_loc; |
| |
| // Find Cookie |
| field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE); |
| if (nullptr == field_loc) { |
| Dbg(pi_dbg_ctl, "OperatorRMCookie::exec, no cookie"); |
| return true; |
| } |
| |
| int cookies_len = 0; |
| const char *cookies = TSMimeHdrFieldValueStringGet(res.bufp, res.hdr_loc, field_loc, -1, &cookies_len); |
| std::string updated_cookie; |
| if (CookieHelper::cookieModifyHelper(cookies, cookies_len, updated_cookie, CookieHelper::COOKIE_OP_DEL, _cookie)) { |
| if (updated_cookie.empty()) { |
| if (TS_SUCCESS == TSMimeHdrFieldDestroy(res.bufp, res.hdr_loc, field_loc)) { |
| Dbg(pi_dbg_ctl, "OperatorRMCookie::exec, empty cookie deleted"); |
| } |
| } else if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, updated_cookie.c_str(), |
| updated_cookie.size())) { |
| Dbg(pi_dbg_ctl, "OperatorRMCookie::exec, updated_cookie = [%s]", updated_cookie.c_str()); |
| } |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| return true; |
| } |
| |
| // OperatorAddCookie |
| void |
| OperatorAddCookie::initialize(Parser &p) |
| { |
| OperatorCookies::initialize(p); |
| _value.set_value(p.get_value(), this); |
| } |
| |
| bool |
| OperatorAddCookie::exec(const Resources &res) const |
| { |
| std::string value; |
| |
| _value.append_value(value, res); |
| |
| if (res.bufp && res.hdr_loc) { |
| Dbg(pi_dbg_ctl, "OperatorAddCookie::exec() invoked on cookie %s", _cookie.c_str()); |
| TSMLoc field_loc; |
| |
| // Find Cookie |
| field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE); |
| if (nullptr == field_loc) { |
| Dbg(pi_dbg_ctl, "OperatorAddCookie::exec, no cookie"); |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE, &field_loc)) { |
| value = _cookie + '=' + value; |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) { |
| Dbg(pi_dbg_ctl, "Adding cookie %s", _cookie.c_str()); |
| TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| return true; |
| } |
| |
| int cookies_len = 0; |
| const char *cookies = TSMimeHdrFieldValueStringGet(res.bufp, res.hdr_loc, field_loc, -1, &cookies_len); |
| std::string updated_cookie; |
| if (CookieHelper::cookieModifyHelper(cookies, cookies_len, updated_cookie, CookieHelper::COOKIE_OP_ADD, _cookie, value) && |
| TS_SUCCESS == |
| TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, updated_cookie.c_str(), updated_cookie.size())) { |
| Dbg(pi_dbg_ctl, "OperatorAddCookie::exec, updated_cookie = [%s]", updated_cookie.c_str()); |
| } |
| } |
| return true; |
| } |
| |
| // OperatorSetCookie |
| void |
| OperatorSetCookie::initialize(Parser &p) |
| { |
| OperatorCookies::initialize(p); |
| _value.set_value(p.get_value(), this); |
| } |
| |
| bool |
| OperatorSetCookie::exec(const Resources &res) const |
| { |
| std::string value; |
| |
| _value.append_value(value, res); |
| |
| if (res.bufp && res.hdr_loc) { |
| Dbg(pi_dbg_ctl, "OperatorSetCookie::exec() invoked on cookie %s", _cookie.c_str()); |
| TSMLoc field_loc; |
| |
| // Find Cookie |
| field_loc = TSMimeHdrFieldFind(res.bufp, res.hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE); |
| if (nullptr == field_loc) { |
| Dbg(pi_dbg_ctl, "OperatorSetCookie::exec, no cookie"); |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(res.bufp, res.hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE, &field_loc)) { |
| value = _cookie + "=" + value; |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, value.c_str(), value.size())) { |
| Dbg(pi_dbg_ctl, "Adding cookie %s", _cookie.c_str()); |
| TSMimeHdrFieldAppend(res.bufp, res.hdr_loc, field_loc); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| return true; |
| } |
| |
| int cookies_len = 0; |
| const char *cookies = TSMimeHdrFieldValueStringGet(res.bufp, res.hdr_loc, field_loc, -1, &cookies_len); |
| std::string updated_cookie; |
| if (CookieHelper::cookieModifyHelper(cookies, cookies_len, updated_cookie, CookieHelper::COOKIE_OP_SET, _cookie, value) && |
| TS_SUCCESS == |
| TSMimeHdrFieldValueStringSet(res.bufp, res.hdr_loc, field_loc, -1, updated_cookie.c_str(), updated_cookie.size())) { |
| Dbg(pi_dbg_ctl, "OperatorSetCookie::exec, updated_cookie = [%s]", updated_cookie.c_str()); |
| } |
| TSHandleMLocRelease(res.bufp, res.hdr_loc, field_loc); |
| } |
| return true; |
| } |
| |
| bool |
| CookieHelper::cookieModifyHelper(const char *cookies, const size_t cookies_len, std::string &updated_cookies, |
| const CookieHelper::CookieOp cookie_op, const std::string &cookie_key, |
| const std::string &cookie_value) |
| { |
| if (0 == cookie_key.size()) { |
| Dbg(pi_dbg_ctl, "CookieHelper::cookieModifyHelper, empty cookie_key"); |
| return false; |
| } |
| |
| for (size_t idx = 0; idx < cookies_len;) { |
| // advance any leading spaces |
| for (; idx < cookies_len && std::isspace(cookies[idx]); idx++) { |
| ; |
| } |
| if (0 == strncmp(cookies + idx, cookie_key.c_str(), cookie_key.size())) { |
| size_t key_start_idx = idx; |
| // advance to past the name and any subsequent spaces |
| for (idx += cookie_key.size(); idx < cookies_len && std::isspace(cookies[idx]); idx++) { |
| ; |
| } |
| if (idx < cookies_len && cookies[idx++] == '=') { |
| // cookie_key is found, then we don't need to add it. |
| if (CookieHelper::COOKIE_OP_ADD == cookie_op) { |
| return false; |
| } |
| for (; idx < cookies_len && std::isspace(cookies[idx]); idx++) { |
| ; |
| } |
| size_t value_start_idx = idx; |
| for (; idx < cookies_len && cookies[idx] != ';'; idx++) { |
| ; |
| } |
| // If we have not reached the end and there is a space after the |
| // semi-colon, advance one char |
| if (idx + 1 < cookies_len && std::isspace(cookies[idx + 1])) { |
| idx++; |
| } |
| // cookie value is found |
| size_t value_end_idx = idx; |
| if (CookieHelper::COOKIE_OP_SET == cookie_op) { |
| updated_cookies.append(cookies, value_start_idx); |
| updated_cookies.append(cookie_value); |
| updated_cookies.append(cookies + value_end_idx, cookies_len - value_end_idx); |
| return true; |
| } |
| |
| if (CookieHelper::COOKIE_OP_DEL == cookie_op) { |
| // +1 to skip the semi-colon after the cookie_value |
| updated_cookies.append(cookies, key_start_idx); |
| if (value_end_idx < cookies_len) { |
| updated_cookies.append(cookies + value_end_idx + 1, cookies_len - value_end_idx - 1); |
| } |
| // if the cookie to delete is the last pair, |
| // the semi-colon before this pair needs to be deleted |
| // this handles the case "c = b; key=value", the expected result is "c = b" |
| size_t last_semi_colon = updated_cookies.find_last_of(';'); |
| if (last_semi_colon != std::string::npos) { |
| size_t last_equal = updated_cookies.find_last_of('='); |
| if (last_equal != std::string::npos) { |
| if (last_equal < last_semi_colon) { |
| // remove the last semi colon and subsequent chars |
| updated_cookies = updated_cookies.substr(0, last_semi_colon); |
| } |
| } else { |
| // if there is no equal left in cookie, valid cookie value doesn't exist |
| updated_cookies = ""; |
| } |
| } |
| return true; |
| } |
| } |
| } |
| // find the next cookie pair followed by semi-colon |
| while (idx < cookies_len && cookies[idx++] != ';') { |
| ; |
| } |
| } |
| |
| if (CookieHelper::COOKIE_OP_ADD == cookie_op || CookieHelper::COOKIE_OP_SET == cookie_op) { |
| if (0 == cookies_len) { |
| updated_cookies = cookie_key + '=' + cookie_value; |
| } else { |
| updated_cookies = std::string(cookies, cookies_len) + ';' + cookie_key + '=' + cookie_value; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| // OperatorSetConnDSCP |
| void |
| OperatorSetConnDSCP::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _ds_value.set_value(p.get_arg(), this); |
| } |
| |
| void |
| OperatorSetConnDSCP::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetConnDSCP::exec(const Resources &res) const |
| { |
| if (res.state.txnp) { |
| TSHttpTxnClientPacketDscpSet(res.state.txnp, _ds_value.get_int_value()); |
| Dbg(pi_dbg_ctl, " Setting DSCP to %d", _ds_value.get_int_value()); |
| } |
| return true; |
| } |
| |
| // OperatorSetConnMark |
| void |
| OperatorSetConnMark::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _ds_value.set_value(p.get_arg(), this); |
| } |
| |
| void |
| OperatorSetConnMark::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetConnMark::exec(const Resources &res) const |
| { |
| if (res.state.txnp) { |
| TSHttpTxnClientPacketMarkSet(res.state.txnp, _ds_value.get_int_value()); |
| Dbg(pi_dbg_ctl, " Setting MARK to %d", _ds_value.get_int_value()); |
| } |
| return true; |
| } |
| |
| // OperatorSetDebug |
| // Deprecated: Remove for v10.0.0 |
| void |
| OperatorSetDebug::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| } |
| |
| void |
| OperatorSetDebug::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetDebug::exec(const Resources &res) const |
| { |
| TSHttpTxnCntlSet(res.state.txnp, TS_HTTP_CNTL_TXN_DEBUG, true); |
| return true; |
| } |
| |
| void |
| OperatorSetHttpCntl::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| _cntl_qual = parse_http_cntl_qualifier(p.get_arg()); |
| |
| std::string flag = p.get_value(); // Make a copy of the value |
| |
| std::transform(flag.begin(), flag.end(), flag.begin(), ::tolower); |
| |
| if (flag == "1" || flag == "true" || flag == "on" || flag == "enable") { |
| _flag = true; |
| } |
| } |
| |
| void |
| OperatorSetHttpCntl::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| // This is only for the debug statement, and must be in sync with TSHttpCntlType in apidefs.h.in |
| static const char *const HttpCntls[] = { |
| "LOGGING", "INTERCEPT_RETRY", "RESP_CACHEABLE", "REQ_CACHEABLE", "SERVER_NO_STORE", "TXN_DEBUG", "SKIP_REMAP", |
| }; |
| |
| bool |
| OperatorSetHttpCntl::exec(const Resources &res) const |
| { |
| if (_flag) { |
| TSHttpTxnCntlSet(res.state.txnp, _cntl_qual, true); |
| Dbg(pi_dbg_ctl, " Turning ON %s for transaction", HttpCntls[static_cast<size_t>(_cntl_qual)]); |
| } else { |
| TSHttpTxnCntlSet(res.state.txnp, _cntl_qual, false); |
| Dbg(pi_dbg_ctl, " Turning OFF %s for transaction", HttpCntls[static_cast<size_t>(_cntl_qual)]); |
| } |
| return true; |
| } |
| |
| void |
| OperatorSetPluginCntl::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| const std::string &name = p.get_arg(); |
| const std::string &value = p.get_value(); |
| |
| if (name == "TIMEZONE") { |
| _name = PluginCtrl::TIMEZONE; |
| if (value == "LOCAL") { |
| _value = TIMEZONE_LOCAL; |
| } else if (value == "GMT") { |
| _value = TIMEZONE_GMT; |
| } else { |
| TSError("[%s] Unknown value for TIMZEONE control: %s", PLUGIN_NAME, value.c_str()); |
| } |
| } else if (name == "INBOUND_IP_SOURCE") { |
| _name = PluginCtrl::INBOUND_IP_SOURCE; |
| if (value == "PEER") { |
| _value = IP_SRC_PEER; |
| } else if (value == "PROXY") { |
| _value = IP_SRC_PROXY; |
| } else if (value == "PLUGIN") { |
| _value = IP_SRC_PLUGIN; |
| } else { |
| TSError("[%s] Unknown value for INBOUND_IP_SOURCE control: %s", PLUGIN_NAME, value.c_str()); |
| } |
| } |
| } |
| |
| // This operator should be allowed everywhere |
| void |
| OperatorSetPluginCntl::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_START_HOOK); |
| } |
| |
| bool |
| OperatorSetPluginCntl::exec(const Resources &res) const |
| { |
| PrivateSlotData private_data; |
| private_data.raw = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_private_slot)); |
| |
| switch (_name) { |
| case PluginCtrl::TIMEZONE: |
| private_data.timezone = _value; |
| break; |
| case PluginCtrl::INBOUND_IP_SOURCE: |
| private_data.ip_source = _value; |
| break; |
| } |
| |
| Dbg(pi_dbg_ctl, " Setting plugin control %d to %d", static_cast<int>(_name), _value); |
| TSUserArgSet(res.state.txnp, _txn_private_slot, reinterpret_cast<void *>(private_data.raw)); |
| |
| return true; |
| } |
| |
| void |
| OperatorRunPlugin::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| auto plugin_name = p.get_arg(); |
| auto plugin_args = p.get_value(); |
| |
| if (plugin_name.empty()) { |
| TSError("[%s] missing plugin name", PLUGIN_NAME); |
| return; |
| } |
| |
| std::vector<std::string> tokens; |
| std::istringstream iss(plugin_args); |
| std::string token; |
| |
| while (iss >> std::quoted(token)) { |
| tokens.push_back(token); |
| } |
| |
| // Create argc and argv |
| int argc = tokens.size() + 2; |
| char **argv = new char *[argc]; |
| |
| argv[0] = p.from_url(); |
| argv[1] = p.to_url(); |
| |
| for (size_t i = 0; i < tokens.size(); ++i) { |
| argv[i + 2] = const_cast<char *>(tokens[i].c_str()); |
| } |
| |
| std::string error; |
| |
| // We have to escalate access while loading these plugins, just as done when loading remap.config |
| { |
| uint32_t elevate_access = 0; |
| |
| elevate_access = RecGetRecordInt("proxy.config.plugin.load_elevated").value_or(0); |
| ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0); |
| |
| _plugin = plugin_factory.getRemapPlugin(swoc::file::path(plugin_name), argc, const_cast<char **>(argv), error, |
| isPluginDynamicReloadEnabled()); |
| } // done elevating access |
| |
| delete[] argv; |
| |
| if (!_plugin) { |
| TSError("[%s] Unable to load plugin '%s': %s", PLUGIN_NAME, plugin_name.c_str(), error.c_str()); |
| } |
| } |
| |
| void |
| OperatorRunPlugin::initialize_hooks() |
| { |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); // Need this for the txnp |
| } |
| |
| bool |
| OperatorRunPlugin::exec(const Resources &res) const |
| { |
| TSReleaseAssert(_plugin != nullptr); |
| |
| if (res._rri && res.state.txnp) { |
| _plugin->doRemap(res.state.txnp, res._rri); |
| } |
| return true; |
| } |
| |
| // OperatorSetBody |
| void |
| OperatorSetBodyFrom::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| // we want the arg since body only takes one value |
| _value.set_value(p.get_arg(), this); |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| require_resources(RSRC_RESPONSE_STATUS); |
| } |
| |
| void |
| OperatorSetBodyFrom::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| } |
| |
| bool |
| OperatorSetBodyFrom::exec(const Resources &res) const |
| { |
| if (TSHttpTxnIsInternal(res.state.txnp)) { |
| // If this is triggered by an internal transaction, a infinte loop may occur |
| // It should only be triggered by the original transaction sent by the client |
| Dbg(pi_dbg_ctl, "OperatorSetBodyFrom triggered by an internal transaction"); |
| return true; |
| } |
| |
| char req_buf[MAX_SIZE]; |
| int req_buf_size = 0; |
| if (createRequestString(_value.get_value(), req_buf, &req_buf_size) == TS_SUCCESS) { |
| TSCont fetchCont = TSContCreate(handleFetchEvents, TSMutexCreate()); |
| TSContDataSet(fetchCont, static_cast<void *>(res.state.txnp)); |
| |
| TSHttpTxnHookAdd(res.state.txnp, TS_HTTP_TXN_CLOSE_HOOK, fetchCont); |
| |
| TSFetchEvent event_ids; |
| event_ids.success_event_id = TS_EVENT_FETCHSM_SUCCESS; |
| event_ids.failure_event_id = TS_EVENT_FETCHSM_FAILURE; |
| event_ids.timeout_event_id = TS_EVENT_FETCHSM_TIMEOUT; |
| |
| struct sockaddr_in addr; |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = LOCAL_IP_ADDRESS; |
| addr.sin_port = LOCAL_PORT; |
| TSFetchUrl(static_cast<const char *>(req_buf), req_buf_size, reinterpret_cast<struct sockaddr const *>(&addr), fetchCont, |
| AFTER_BODY, event_ids); |
| |
| // Forces original status code in event TSHttpTxnErrorBodySet changed |
| // the code or another condition was set conflicting with this one. |
| // Set here because res is the only structure that contains the original status code. |
| TSHttpTxnStatusSet(res.state.txnp, res.resp_status, PLUGIN_NAME); |
| } else { |
| TSError(PLUGIN_NAME, "OperatorSetBodyFrom:exec:: Could not create request"); |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| OperatorSetStateFlag::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _flag_ix = strtol(p.get_arg().c_str(), nullptr, 10); |
| |
| if (_flag_ix < 0 || _flag_ix >= NUM_STATE_FLAGS) { |
| TSError("[%s] state flag with index %d is out of range", PLUGIN_NAME, _flag_ix); |
| return; |
| } |
| |
| std::string flag = p.get_value(); // Make a copy of the value |
| |
| std::transform(flag.begin(), flag.end(), flag.begin(), ::tolower); |
| |
| if (flag == "1" || flag == "true" || flag == "on" || flag == "enable") { |
| _mask = 1ULL << _flag_ix; |
| _flag = true; |
| } else { |
| _mask = ~(1ULL << _flag_ix); |
| _flag = false; |
| } |
| } |
| |
| // This operator should be allowed everywhere |
| void |
| OperatorSetStateFlag::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_START_HOOK); |
| } |
| |
| bool |
| OperatorSetStateFlag::exec(const Resources &res) const |
| { |
| if (!res.state.txnp) { |
| TSError("[%s] OperatorSetStateFlag() failed. Transaction is null", PLUGIN_NAME); |
| return false; |
| } |
| |
| Dbg(pi_dbg_ctl, " Setting state flag %d to %d", _flag_ix, _flag); |
| |
| auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot)); |
| |
| TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(_flag ? data | _mask : data & _mask)); |
| |
| return true; |
| } |
| |
| void |
| OperatorSetStateInt8::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _byte_ix = strtol(p.get_arg().c_str(), nullptr, 10); |
| |
| if (_byte_ix < 0 || _byte_ix >= NUM_STATE_INT8S) { |
| TSError("[%s] state int8 with index %d is out of range", PLUGIN_NAME, _byte_ix); |
| return; |
| } |
| |
| _value.set_value(p.get_value(), this); |
| if (!_value.has_conds()) { |
| int v = _value.get_int_value(); |
| |
| if (v < 0 || v > 255) { |
| TSError("[%s] state int8 value %d is out of range", PLUGIN_NAME, v); |
| return; |
| } |
| } |
| } |
| |
| // This operator should be allowed everywhere |
| void |
| OperatorSetStateInt8::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_START_HOOK); |
| } |
| |
| bool |
| OperatorSetStateInt8::exec(const Resources &res) const |
| { |
| if (!res.state.txnp) { |
| TSError("[%s] OperatorSetStateInt8() failed. Transaction is null", PLUGIN_NAME); |
| return false; |
| } |
| |
| auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot)); |
| int val = 0; |
| |
| if (_value.has_conds()) { // If there are conditions, we need to evaluate them, which gives us a string |
| std::string v; |
| |
| _value.append_value(v, res); |
| val = strtol(v.c_str(), nullptr, 10); |
| if (val < 0 || val > 255) { |
| TSWarning("[%s] state int8 value %d is out of range", PLUGIN_NAME, val); |
| return false; |
| } |
| } else { |
| // These values have already been checked at load time |
| val = _value.get_int_value(); |
| } |
| |
| Dbg(pi_dbg_ctl, " Setting state int8 %d to %d", _byte_ix, val); |
| ptr &= ~STATE_INT8_MASKS[_byte_ix]; // Clear any old value |
| ptr |= (static_cast<uint64_t>(val) << (NUM_STATE_FLAGS + _byte_ix * 8)); |
| TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(ptr)); |
| |
| return true; |
| } |
| |
| void |
| OperatorSetStateInt16::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| int ix = strtol(p.get_arg().c_str(), nullptr, 10); |
| |
| if (ix != 0) { |
| TSError("[%s] state int16 with index %d is out of range", PLUGIN_NAME, ix); |
| return; |
| } |
| |
| _value.set_value(p.get_value(), this); |
| if (!_value.has_conds()) { |
| int v = _value.get_int_value(); |
| |
| if (v < 0 || v > 65535) { |
| TSError("[%s] state int16 value %d is out of range", PLUGIN_NAME, v); |
| return; |
| } |
| } |
| } |
| |
| // This operator should be allowed everywhere |
| void |
| OperatorSetStateInt16::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_START_HOOK); |
| } |
| |
| bool |
| OperatorSetStateInt16::exec(const Resources &res) const |
| { |
| if (!res.state.txnp) { |
| TSError("[%s] OperatorSetStateInt16() failed. Transaction is null", PLUGIN_NAME); |
| return false; |
| } |
| |
| auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot)); |
| int val = 0; |
| |
| if (_value.has_conds()) { // If there are conditions, we need to evaluate them, which gives us a string |
| std::string v; |
| |
| _value.append_value(v, res); |
| val = strtol(v.c_str(), nullptr, 10); |
| if (val < 0 || val > 65535) { |
| TSWarning("[%s] state int8 value %d is out of range", PLUGIN_NAME, val); |
| return false; |
| } |
| } else { |
| // These values have already been checked at load time |
| val = _value.get_int_value(); |
| } |
| |
| Dbg(pi_dbg_ctl, " Setting state int16 to %d", val); |
| ptr &= ~STATE_INT16_MASK; // Clear any old value |
| ptr |= (static_cast<uint64_t>(val) << 48); |
| TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(ptr)); |
| |
| return true; |
| } |
| |
| void |
| OperatorSetEffectiveAddress::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _value.set_value(p.get_arg(), this); |
| } |
| |
| void |
| OperatorSetEffectiveAddress::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetEffectiveAddress::exec(const Resources &res) const |
| { |
| std::string value; |
| _value.append_value(value, res); |
| |
| // Never set an empty address |
| if (value.empty()) { |
| Dbg(pi_dbg_ctl, "Address is empty, skipping"); |
| return true; |
| } |
| |
| auto addr = swoc::IPAddr(value); |
| struct sockaddr_storage storage; |
| addr.copy_to(reinterpret_cast<struct sockaddr *>(&storage)); |
| TSHttpTxnVerifiedAddrSet(res.state.txnp, reinterpret_cast<struct sockaddr *>(&storage)); |
| |
| PrivateSlotData private_data; |
| private_data.raw = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_private_slot)); |
| private_data.ip_source = IP_SRC_PLUGIN; |
| |
| Dbg(pi_dbg_ctl, " Setting plugin control INBOUND_IP_SOURCE to IP_SRC_PLUGIN"); |
| TSUserArgSet(res.state.txnp, _txn_private_slot, reinterpret_cast<void *>(private_data.raw)); |
| |
| return true; |
| } |
| |
| // OperatorSetNextHopStrategy |
| void |
| OperatorSetNextHopStrategy::initialize(Parser &p) |
| { |
| Operator::initialize(p); |
| |
| _value.set_value(p.get_arg(), this); |
| Dbg(pi_dbg_ctl, "OperatorSetNextHopStrategy::initialie: %s", _value.get_value().c_str()); |
| } |
| |
| void |
| OperatorSetNextHopStrategy::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK); |
| add_allowed_hook(TS_REMAP_PSEUDO_HOOK); |
| } |
| |
| bool |
| OperatorSetNextHopStrategy::exec(const Resources &res) const |
| { |
| if (!res.state.txnp) { |
| TSError("[%s] OperatorSetNextHopStrategy() failed. Transaction is null", PLUGIN_NAME); |
| } |
| |
| auto const txnp = res.state.txnp; |
| |
| std::string value; |
| _value.append_value(value, res); |
| |
| // Setting an empty strategy clears it for either parent.config or remap to |
| if ("null" == value || value.empty()) { |
| Dbg(pi_dbg_ctl, "Clearing strategy"); |
| TSHttpTxnNextHopStrategySet(txnp, nullptr); |
| return true; |
| } |
| |
| void const *const stratptr = TSHttpTxnNextHopNamedStrategyGet(txnp, value.c_str()); |
| if (nullptr == stratptr) { |
| TSWarning("[%s] Failed to get strategy '%s'", PLUGIN_NAME, value.c_str()); |
| } else { |
| Dbg(pi_dbg_ctl, " Setting strategy '%s'", value.c_str()); |
| TSHttpTxnNextHopStrategySet(txnp, stratptr); |
| } |
| |
| return true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // OperatorIf class implementations |
| // Keep this at the end of the files, since this is not really an Operator. |
| // |
| ConditionGroup * |
| OperatorIf::new_section(Parser::CondClause clause) |
| { |
| TSAssert(_cur_section && !_cur_section->next); |
| |
| _clause = clause; |
| _cur_section->next = std::make_unique<CondOpSection>(); |
| _cur_section = _cur_section->next.get(); |
| |
| return &_cur_section->group; |
| } |
| |
| bool |
| OperatorIf::add_operator(Parser &p, const char *filename, int lineno) |
| { |
| Operator *op = operator_factory(p.get_op()); |
| |
| if (!op) { |
| TSError("[%s] Unknown operator: %s, file: %s, line: %d", PLUGIN_NAME, p.get_op().c_str(), filename, lineno); |
| return false; |
| } |
| |
| Dbg(pi_dbg_ctl, " Adding operator: %s(%s)=\"%s\"", p.get_op().c_str(), p.get_arg().c_str(), p.get_value().c_str()); |
| |
| try { |
| op->initialize(p); |
| } catch (std::exception const &ex) { |
| delete op; |
| TSError("[%s] Failed to initialize operator: %s, file: %s, line: %d, error: %s", PLUGIN_NAME, p.get_op().c_str(), filename, |
| lineno, ex.what()); |
| return false; |
| } |
| |
| // Add to current section |
| if (_cur_section->ops.oper) { |
| _cur_section->ops.oper->append(op); |
| } else { |
| _cur_section->ops.oper.reset(op); |
| _cur_section->ops.oper_mods = op->get_oper_modifiers(); |
| } |
| |
| return true; |
| } |
| |
| Condition * |
| OperatorIf::make_condition(Parser &p, const char *filename, int lineno) |
| { |
| Condition *cond = condition_factory(p.get_op()); |
| |
| if (!cond) { |
| TSError("[%s] Unknown condition: %s, file: %s, line: %d", PLUGIN_NAME, p.get_op().c_str(), filename, lineno); |
| return nullptr; |
| } |
| |
| Dbg(pi_dbg_ctl, " Creating condition: %%{%s} with arg: %s", p.get_op().c_str(), p.get_arg().c_str()); |
| |
| try { |
| cond->initialize(p); |
| } catch (std::exception const &ex) { |
| delete cond; |
| TSError("[%s] Failed to initialize condition: %s, file: %s, line: %d, error: %s", PLUGIN_NAME, p.get_op().c_str(), filename, |
| lineno, ex.what()); |
| return nullptr; |
| } |
| |
| return cond; |
| } |
| |
| bool |
| OperatorIf::has_operator() const |
| { |
| const CondOpSection *section = &_sections; |
| |
| while (section != nullptr) { |
| if (section->has_operator()) { |
| return true; |
| } |
| section = section->next.get(); |
| } |
| return false; |
| } |
| |
| OperModifiers |
| OperatorIf::exec_and_return_mods(const Resources &res) const |
| { |
| Dbg(dbg_ctl, "Executing OperatorIf"); |
| |
| // Go through each section (if/elif/else) until one matches |
| for (auto *section = const_cast<CondOpSection *>(&_sections); section != nullptr; section = section->next.get()) { |
| if (section->group.eval(res)) { |
| Dbg(dbg_ctl, "OperatorIf section condition matched, executing operators"); |
| return exec_section(section, res); |
| } |
| } |
| |
| Dbg(dbg_ctl, "OperatorIf: no section matched"); |
| return OPER_NONE; |
| } |
| |
| OperModifiers |
| OperatorIf::exec_section(const CondOpSection *section, const Resources &res) const |
| { |
| if (nullptr == section->ops.oper) { |
| return section->ops.oper_mods; |
| } |
| |
| auto no_reenable_count = section->ops.oper->do_exec(res); |
| |
| ink_assert(no_reenable_count < 2); |
| if (no_reenable_count) { |
| return static_cast<OperModifiers>(section->ops.oper_mods | OPER_NO_REENABLE); |
| } |
| |
| return section->ops.oper_mods; |
| } |