bneradt commented on code in PR #12813:
URL: https://github.com/apache/trafficserver/pull/12813#discussion_r2739009974


##########
src/proxy/http/remap/RemapYamlConfig.cc:
##########
@@ -0,0 +1,1170 @@
+/** @file
+ *
+ *  YAML remap configuration file parsing implementation.
+ *
+ *  @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.
+ */
+
+#include "proxy/http/remap/RemapYamlConfig.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <filesystem>
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "tscore/Diags.h"
+#include "tscore/ink_string.h"
+#include "tsutil/ts_errata.h"
+#include "swoc/bwf_base.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/swoc_file.h"
+
+#include "proxy/http/remap/UrlRewrite.h"
+#include "proxy/http/remap/UrlMapping.h"
+#include "proxy/http/remap/RemapConfig.h"
+#include "proxy/http/remap/AclFiltering.h"
+#include "records/RecCore.h"
+
+namespace
+{
+DbgCtl dbg_ctl_remap_yaml{"remap_yaml"};
+DbgCtl dbg_ctl_url_rewrite{"url_rewrite"};
+
+/** will process the regex mapping configuration and create objects in
+    output argument reg_map. It assumes existing data in reg_map is
+    inconsequential and will be perfunctorily null-ed;
+*/
+static bool
+process_regex_mapping_config(const char *from_host_lower, url_mapping 
*new_mapping, UrlRewrite::RegexMapping *reg_map)
+{
+  std::string_view to_host{};
+  int              to_host_len;
+  int              substitution_id;
+  int32_t          captures;
+
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+  reg_map->n_substitutions          = 0;
+
+  reg_map->url_map = new_mapping;
+
+  // using from_host_lower (and not new_mapping->fromURL.host_get())
+  // as this one will be nullptr-terminated (required by pcre_compile)
+  if (reg_map->regular_expression.compile(from_host_lower) == false) {
+    Warning("pcre_compile failed! Regex has error starting at %s", 
from_host_lower);
+    goto lFail;
+  }
+
+  captures = reg_map->regular_expression.get_capture_count();
+  if (captures == -1) {
+    Warning("pcre_fullinfo failed!");
+    goto lFail;
+  }
+  if (captures >= UrlRewrite::MAX_REGEX_SUBS) { // off by one for $0 (implicit 
capture)
+    Warning("regex has %d capturing subpatterns (including entire regex); Max 
allowed: %d", captures + 1,
+            UrlRewrite::MAX_REGEX_SUBS);
+    goto lFail;
+  }
+
+  to_host     = new_mapping->toURL.host_get();
+  to_host_len = static_cast<int>(to_host.length());
+  for (int i = 0; i < to_host_len - 1; ++i) {
+    if (to_host[i] == '$') {
+      substitution_id = to_host[i + 1] - '0';
+      if ((substitution_id < 0) || (substitution_id > captures)) {
+        Warning("Substitution id [%c] has no corresponding capture pattern in 
regex [%s]", to_host[i + 1], from_host_lower);
+        goto lFail;
+      }
+      reg_map->substitution_markers[reg_map->n_substitutions] = i;
+      reg_map->substitution_ids[reg_map->n_substitutions]     = 
substitution_id;
+      ++reg_map->n_substitutions;
+    }
+  }
+
+  // so the regex itself is stored in fromURL.host; string to match
+  // will be in the request; string to use for substitutions will be
+  // in this buffer
+  reg_map->to_url_host_template_len = to_host_len;
+  reg_map->to_url_host_template     = static_cast<char 
*>(ats_malloc(to_host_len));
+  memcpy(reg_map->to_url_host_template, to_host.data(), to_host_len);
+
+  return true;
+
+lFail:
+  ats_free(reg_map->to_url_host_template);
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+
+  return false;
+}
+} // end anonymous namespace
+
+swoc::Errata
+parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, 
std::string_view &url_str)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("URL must be a map");
+  }
+  url.create(nullptr);
+
+  // Use url first if defined
+  ParseResult rparse;
+  if (node["url"]) {
+    url_str = node["url"].as<std::string_view>();
+    if (host_check) {
+      rparse = url.parse_regex(url_str);
+    } else {
+      rparse = url.parse_no_host_check(url_str);
+    }
+    if (rparse != ParseResult::DONE) {
+      return swoc::Errata("malformed URL: {}", url_str);
+    }
+
+    return {};
+  }
+
+  // Build URL string from components
+  if (node["scheme"]) {
+    url.scheme_set(node["scheme"].as<std::string>());
+  }
+
+  if (node["host"]) {
+    url.host_set(node["host"].as<std::string>());
+  }
+
+  if (node["port"]) {
+    url.port_set(node["port"].as<int>());
+  }
+
+  if (node["path"]) {
+    url.path_set(node["path"].as<std::string>());
+  }
+
+  return {};
+}
+
+swoc::Errata
+remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node 
&node, ACLBehaviorPolicy behavior_policy)
+{
+  acl_filter_rule *rule;
+  int              j;
+  bool             new_rule_flg = false;
+
+  if (!rule_pp) {
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)");
+    return swoc::Errata("Invalid argument(s)");
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    printf("validate_filter_args: ");
+    for (const auto &rule : node) {
+      printf("\"%s\" ", rule.first.as<std::string>().c_str());
+    }
+    printf("\n");
+  }
+
+  if ((rule = *rule_pp) == nullptr) {
+    rule = new acl_filter_rule();
+    if (unlikely((*rule_pp = rule) == nullptr)) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation 
error");
+      return swoc::Errata("Memory allocation Error");
+    }
+    new_rule_flg = true;
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class 
was created during remap rule processing");
+  }
+
+  if (!node || !node.IsMap()) {
+    if (new_rule_flg) {
+      delete rule;
+      *rule_pp = nullptr;
+    }
+    return swoc::Errata("filters must be a map");
+  }
+
+  // Parse method
+  auto parse_method = [&](const std::string &method_str) {
+    int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), 
nullptr) - HTTP_WKSIDX_CONNECT;
+
+    if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) {
+      rule->standard_method_lookup[m] = true;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard 
method [%s]", method_str.c_str());
+      rule->nonstandard_methods.insert(method_str);
+    }
+    rule->method_restriction_enabled = true;
+  };
+
+  if (node["method"]) {
+    if (node["method"].IsSequence()) {
+      for (const auto &method : node["method"]) {
+        parse_method(method.as<std::string>());
+      }
+    } else {
+      parse_method(node["method"].as<std::string>());
+    }
+  }
+
+  // Parse src_ip (and src_ip_invert)
+  auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> 
swoc::Errata {
+    if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} src_ip filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+
+    src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    std::string_view arg{ip_str};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", ip_str.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", ip_str);
+    }
+    for (j = 0; j < rule->src_ip_cnt; j++) {
+      if (rule->src_ip_array[j].start == ipi->start && 
rule->src_ip_array[j].end == ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_cnt++;
+      rule->src_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip"]) {
+    if (node["src_ip"].IsSequence()) {
+      for (const auto &src_ip : node["src_ip"]) {
+        auto errata = parse_src_ip(src_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["src_ip_invert"]) {
+    if (node["src_ip_invert"].IsSequence()) {
+      for (const auto &src_ip_invert : node["src_ip_invert"]) {
+        auto errata = parse_src_ip(src_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip_invert"].as<std::string>(), 
true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse src_ip_category (and src_ip_category)
+  auto parse_src_ip_category = [&](const std::string &ip_category, bool 
invert) -> swoc::Errata {
+    if (rule->src_ip_category_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many 
\"src_ip_category=\" filters");
+      return swoc::Errata("Defined more than {} src_ip_category filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+    src_ip_category_info_t *ipi = 
&rule->src_ip_category_array[rule->src_ip_category_cnt];
+    ipi->category.assign(ip_category);
+    if (invert) {
+      ipi->invert = true;
+    }
+    for (j = 0; j < rule->src_ip_category_cnt; j++) {
+      if (rule->src_ip_category_array[j].category == ipi->category) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_category_cnt++;
+      rule->src_ip_category_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip_category"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category"].as<std::string>(), false);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  if (node["src_ip_category_invert"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category_invert"].as<std::string>(), true);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  // Parse in_ip (and in_ip_invert)
+  auto parse_in_ip = [&](const std::string &in_ip, bool invert) -> 
swoc::Errata {
+    if (rule->in_ip_cnt >= ACL_FILTER_MAX_IN_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"in_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} in_ip filters", 
ACL_FILTER_MAX_IN_IP);
+    }
+    src_ip_info_t *ipi = &rule->in_ip_array[rule->in_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    // important! use copy of argument
+    std::string_view arg{in_ip};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", in_ip.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", in_ip);
+    }
+    for (j = 0; j < rule->in_ip_cnt; j++) {
+      if (rule->in_ip_array[j].start == ipi->start && rule->in_ip_array[j].end 
== ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->in_ip_cnt++;
+      rule->in_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["in_ip"]) {
+    if (node["in_ip"].IsSequence()) {
+      for (const auto &in_ip : node["in_ip"]) {
+        auto errata = parse_in_ip(in_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["in_ip_invert"]) {
+    if (node["in_ip_invert"].IsSequence()) {
+      for (const auto &in_ip_invert : node["in_ip_invert"]) {
+        auto errata = parse_in_ip(in_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip_invert"].as<std::string>(), true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse action
+  if (node["action"]) {
+    if (node["action"].IsSequence()) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Only one action is 
allowed per remap ACL");
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Only one action is allowed per remap ACL");
+    }
+    std::string action_str = node["action"].as<std::string>();
+    if (behavior_policy == ACLBehaviorPolicy::ACL_BEHAVIOR_MODERN) {
+      // With the new matching policy, we don't allow the legacy "allow" and
+      // "deny" actions. Users must transition to either add_allow/add_deny or
+      // set_allow/set_deny.
+      if (is_inkeylist(action_str.c_str(), "allow", "deny", nullptr)) {
+        Dbg(dbg_ctl_url_rewrite,
+            R"([validate_filter_args] "allow" and "deny" are no longer valid. 
Use add_allow/add_deny or set_allow/set_deny: "%s"")",
+            action_str.c_str());
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return swoc::Errata("\"allow\" and \"deny\" are no longer valid. Use 
add_allow/add_deny or set_allow/set_deny: {}",
+                            action_str.c_str());
+      }
+    }
+    if (is_inkeylist(action_str.c_str(), "add_allow", "add_deny", nullptr)) {
+      rule->add_flag = 1;
+    } else {
+      rule->add_flag = 0;
+    }
+    // Remove "deny" from this list when MATCH_ON_IP_AND_METHOD is removed in 
11.x.
+    if (is_inkeylist(action_str.c_str(), "0", "off", "deny", "set_deny", 
"add_deny", "disable", nullptr)) {
+      rule->allow_flag = 0;
+      // Remove "allow" from this list when MATCH_ON_IP_AND_METHOD is removed 
in 11.x.
+    } else if (is_inkeylist(action_str.c_str(), "1", "on", "allow", 
"set_allow", "add_allow", "enable", nullptr)) {
+      rule->allow_flag = 1;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unknown argument 
\"%s\"", action_str.c_str());
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Unknown action: {}", action_str);
+    }
+  }
+
+  // Parse internal
+  if (node["internal"] && node["internal"].as<bool>()) {
+    rule->internal = 1;
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    rule->print();
+  }
+
+  return {};
+}
+
+swoc::Errata
+parse_map_referer(const YAML::Node &node, url_mapping *url_mapping)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("redirect must be a map");
+  }
+
+  if (!node["url"]) {
+    return swoc::Errata("Missing 'url' field in redirect map-with-referer");
+  }
+  std::string url                  = node["url"].as<std::string>();
+  url_mapping->filter_redirect_url = ats_strdup(url.c_str());
+  if (!strcasecmp(url.c_str(), "<default>") || !strcasecmp(url.c_str(), 
"default") ||
+      !strcasecmp(url.c_str(), "<default_redirect_url>") || 
!strcasecmp(url.c_str(), "default_redirect_url")) {
+    url_mapping->default_redirect_url = true;
+  }
+  url_mapping->redir_chunk_list = 
redirect_tag_str::parse_format_redirect_url(ats_strdup(url.c_str()));
+
+  if (!node["regex"] || !node["regex"].IsSequence()) {
+    return swoc::Errata("'regex' field must be sequence");
+  }
+
+  referer_info *ri;
+  for (const auto &rule : node["regex"]) {
+    char        refinfo_error_buf[1024];
+    bool        refinfo_error = false;
+    std::string regex         = rule.as<std::string>();
+
+    ri = new referer_info(regex.c_str(), &refinfo_error, refinfo_error_buf, 
sizeof(refinfo_error_buf));
+    if (refinfo_error) {
+      delete ri;
+      ri = nullptr;
+      return swoc::Errata("Incorrect Referer regular expression \"{}\" - {}", 
regex.c_str(), refinfo_error_buf);
+    }
+
+    if (ri && ri->negative) {
+      if (ri->any) {
+        url_mapping->optional_referer = true; /* referer header is optional */
+        delete ri;
+        ri = nullptr;
+      } else {
+        url_mapping->negative_referer = true; /* we have negative referer in 
list */
+      }
+    }
+    if (ri) {
+      ri->next                  = url_mapping->referer_list;
+      url_mapping->referer_list = ri;
+    }
+  }
+  return {};
+}
+
+swoc::Errata
+parse_yaml_plugins(const YAML::Node &node, url_mapping *url_mapping, 
BUILD_TABLE_INFO *bti)
+{
+  char *err;
+  char *pargv[1024];
+  int   parc = 0;
+  memset(pargv, 0, sizeof(pargv));
+
+  if (!node["name"]) {
+    return swoc::Errata("plugin missing 'name' field");
+  }
+
+  std::string plugin_name = node["name"].as<std::string>();
+  Dbg(dbg_ctl_remap_yaml, "Loading plugin: %s", plugin_name.c_str());
+
+  /* Prepare remap plugin parameters from the config */
+  if ((err = url_mapping->fromURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load fromURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  if ((err = url_mapping->toURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load toURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  // Add plugin parameters
+  if (node["params"] && node["params"].IsSequence()) {
+    for (const auto &param : node["params"]) {
+      std::string pparam_str = param.as<std::string>();
+      pargv[parc++]          = ats_strdup(pparam_str.c_str());

Review Comment:
   Are these ats_strdup freed? Indices 0 and 1 are below, but it looks like 
these from the loop need to be freed.



##########
src/proxy/http/remap/RemapYamlConfig.cc:
##########
@@ -0,0 +1,1170 @@
+/** @file
+ *
+ *  YAML remap configuration file parsing implementation.
+ *
+ *  @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.
+ */
+
+#include "proxy/http/remap/RemapYamlConfig.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <filesystem>
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "tscore/Diags.h"
+#include "tscore/ink_string.h"
+#include "tsutil/ts_errata.h"
+#include "swoc/bwf_base.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/swoc_file.h"
+
+#include "proxy/http/remap/UrlRewrite.h"
+#include "proxy/http/remap/UrlMapping.h"
+#include "proxy/http/remap/RemapConfig.h"
+#include "proxy/http/remap/AclFiltering.h"
+#include "records/RecCore.h"
+
+namespace
+{
+DbgCtl dbg_ctl_remap_yaml{"remap_yaml"};
+DbgCtl dbg_ctl_url_rewrite{"url_rewrite"};
+
+/** will process the regex mapping configuration and create objects in
+    output argument reg_map. It assumes existing data in reg_map is
+    inconsequential and will be perfunctorily null-ed;
+*/
+static bool
+process_regex_mapping_config(const char *from_host_lower, url_mapping 
*new_mapping, UrlRewrite::RegexMapping *reg_map)
+{
+  std::string_view to_host{};
+  int              to_host_len;
+  int              substitution_id;
+  int32_t          captures;
+
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+  reg_map->n_substitutions          = 0;
+
+  reg_map->url_map = new_mapping;
+
+  // using from_host_lower (and not new_mapping->fromURL.host_get())
+  // as this one will be nullptr-terminated (required by pcre_compile)
+  if (reg_map->regular_expression.compile(from_host_lower) == false) {
+    Warning("pcre_compile failed! Regex has error starting at %s", 
from_host_lower);
+    goto lFail;
+  }
+
+  captures = reg_map->regular_expression.get_capture_count();
+  if (captures == -1) {
+    Warning("pcre_fullinfo failed!");
+    goto lFail;
+  }
+  if (captures >= UrlRewrite::MAX_REGEX_SUBS) { // off by one for $0 (implicit 
capture)
+    Warning("regex has %d capturing subpatterns (including entire regex); Max 
allowed: %d", captures + 1,
+            UrlRewrite::MAX_REGEX_SUBS);
+    goto lFail;
+  }
+
+  to_host     = new_mapping->toURL.host_get();
+  to_host_len = static_cast<int>(to_host.length());
+  for (int i = 0; i < to_host_len - 1; ++i) {
+    if (to_host[i] == '$') {
+      substitution_id = to_host[i + 1] - '0';
+      if ((substitution_id < 0) || (substitution_id > captures)) {
+        Warning("Substitution id [%c] has no corresponding capture pattern in 
regex [%s]", to_host[i + 1], from_host_lower);
+        goto lFail;
+      }
+      reg_map->substitution_markers[reg_map->n_substitutions] = i;
+      reg_map->substitution_ids[reg_map->n_substitutions]     = 
substitution_id;
+      ++reg_map->n_substitutions;
+    }
+  }
+
+  // so the regex itself is stored in fromURL.host; string to match
+  // will be in the request; string to use for substitutions will be
+  // in this buffer
+  reg_map->to_url_host_template_len = to_host_len;
+  reg_map->to_url_host_template     = static_cast<char 
*>(ats_malloc(to_host_len));
+  memcpy(reg_map->to_url_host_template, to_host.data(), to_host_len);
+
+  return true;
+
+lFail:
+  ats_free(reg_map->to_url_host_template);
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+
+  return false;
+}
+} // end anonymous namespace
+
+swoc::Errata
+parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, 
std::string_view &url_str)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("URL must be a map");
+  }
+  url.create(nullptr);
+
+  // Use url first if defined
+  ParseResult rparse;
+  if (node["url"]) {
+    url_str = node["url"].as<std::string_view>();
+    if (host_check) {
+      rparse = url.parse_regex(url_str);
+    } else {
+      rparse = url.parse_no_host_check(url_str);
+    }
+    if (rparse != ParseResult::DONE) {
+      return swoc::Errata("malformed URL: {}", url_str);
+    }
+
+    return {};
+  }
+
+  // Build URL string from components
+  if (node["scheme"]) {
+    url.scheme_set(node["scheme"].as<std::string>());
+  }
+
+  if (node["host"]) {
+    url.host_set(node["host"].as<std::string>());
+  }
+
+  if (node["port"]) {
+    url.port_set(node["port"].as<int>());
+  }
+
+  if (node["path"]) {
+    url.path_set(node["path"].as<std::string>());
+  }
+
+  return {};
+}
+
+swoc::Errata
+remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node 
&node, ACLBehaviorPolicy behavior_policy)
+{
+  acl_filter_rule *rule;
+  int              j;
+  bool             new_rule_flg = false;
+
+  if (!rule_pp) {
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)");
+    return swoc::Errata("Invalid argument(s)");
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    printf("validate_filter_args: ");
+    for (const auto &rule : node) {
+      printf("\"%s\" ", rule.first.as<std::string>().c_str());
+    }
+    printf("\n");
+  }
+
+  if ((rule = *rule_pp) == nullptr) {
+    rule = new acl_filter_rule();
+    if (unlikely((*rule_pp = rule) == nullptr)) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation 
error");
+      return swoc::Errata("Memory allocation Error");
+    }
+    new_rule_flg = true;
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class 
was created during remap rule processing");
+  }
+
+  if (!node || !node.IsMap()) {
+    if (new_rule_flg) {
+      delete rule;
+      *rule_pp = nullptr;
+    }
+    return swoc::Errata("filters must be a map");
+  }
+
+  // Parse method
+  auto parse_method = [&](const std::string &method_str) {
+    int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), 
nullptr) - HTTP_WKSIDX_CONNECT;
+
+    if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) {
+      rule->standard_method_lookup[m] = true;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard 
method [%s]", method_str.c_str());
+      rule->nonstandard_methods.insert(method_str);
+    }
+    rule->method_restriction_enabled = true;
+  };
+
+  if (node["method"]) {
+    if (node["method"].IsSequence()) {
+      for (const auto &method : node["method"]) {
+        parse_method(method.as<std::string>());
+      }
+    } else {
+      parse_method(node["method"].as<std::string>());
+    }
+  }
+
+  // Parse src_ip (and src_ip_invert)
+  auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> 
swoc::Errata {
+    if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} src_ip filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+
+    src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    std::string_view arg{ip_str};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", ip_str.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", ip_str);
+    }
+    for (j = 0; j < rule->src_ip_cnt; j++) {
+      if (rule->src_ip_array[j].start == ipi->start && 
rule->src_ip_array[j].end == ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_cnt++;
+      rule->src_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip"]) {
+    if (node["src_ip"].IsSequence()) {
+      for (const auto &src_ip : node["src_ip"]) {
+        auto errata = parse_src_ip(src_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["src_ip_invert"]) {
+    if (node["src_ip_invert"].IsSequence()) {
+      for (const auto &src_ip_invert : node["src_ip_invert"]) {
+        auto errata = parse_src_ip(src_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip_invert"].as<std::string>(), 
true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse src_ip_category (and src_ip_category)
+  auto parse_src_ip_category = [&](const std::string &ip_category, bool 
invert) -> swoc::Errata {
+    if (rule->src_ip_category_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many 
\"src_ip_category=\" filters");
+      return swoc::Errata("Defined more than {} src_ip_category filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+    src_ip_category_info_t *ipi = 
&rule->src_ip_category_array[rule->src_ip_category_cnt];
+    ipi->category.assign(ip_category);
+    if (invert) {
+      ipi->invert = true;
+    }
+    for (j = 0; j < rule->src_ip_category_cnt; j++) {
+      if (rule->src_ip_category_array[j].category == ipi->category) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_category_cnt++;
+      rule->src_ip_category_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip_category"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category"].as<std::string>(), false);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  if (node["src_ip_category_invert"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category_invert"].as<std::string>(), true);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  // Parse in_ip (and in_ip_invert)
+  auto parse_in_ip = [&](const std::string &in_ip, bool invert) -> 
swoc::Errata {
+    if (rule->in_ip_cnt >= ACL_FILTER_MAX_IN_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"in_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} in_ip filters", 
ACL_FILTER_MAX_IN_IP);
+    }
+    src_ip_info_t *ipi = &rule->in_ip_array[rule->in_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    // important! use copy of argument
+    std::string_view arg{in_ip};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", in_ip.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", in_ip);
+    }
+    for (j = 0; j < rule->in_ip_cnt; j++) {
+      if (rule->in_ip_array[j].start == ipi->start && rule->in_ip_array[j].end 
== ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->in_ip_cnt++;
+      rule->in_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["in_ip"]) {
+    if (node["in_ip"].IsSequence()) {
+      for (const auto &in_ip : node["in_ip"]) {
+        auto errata = parse_in_ip(in_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["in_ip_invert"]) {
+    if (node["in_ip_invert"].IsSequence()) {
+      for (const auto &in_ip_invert : node["in_ip_invert"]) {
+        auto errata = parse_in_ip(in_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip_invert"].as<std::string>(), true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse action
+  if (node["action"]) {
+    if (node["action"].IsSequence()) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Only one action is 
allowed per remap ACL");
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Only one action is allowed per remap ACL");
+    }
+    std::string action_str = node["action"].as<std::string>();
+    if (behavior_policy == ACLBehaviorPolicy::ACL_BEHAVIOR_MODERN) {
+      // With the new matching policy, we don't allow the legacy "allow" and
+      // "deny" actions. Users must transition to either add_allow/add_deny or
+      // set_allow/set_deny.
+      if (is_inkeylist(action_str.c_str(), "allow", "deny", nullptr)) {
+        Dbg(dbg_ctl_url_rewrite,
+            R"([validate_filter_args] "allow" and "deny" are no longer valid. 
Use add_allow/add_deny or set_allow/set_deny: "%s"")",
+            action_str.c_str());
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return swoc::Errata("\"allow\" and \"deny\" are no longer valid. Use 
add_allow/add_deny or set_allow/set_deny: {}",
+                            action_str.c_str());
+      }
+    }
+    if (is_inkeylist(action_str.c_str(), "add_allow", "add_deny", nullptr)) {
+      rule->add_flag = 1;
+    } else {
+      rule->add_flag = 0;
+    }
+    // Remove "deny" from this list when MATCH_ON_IP_AND_METHOD is removed in 
11.x.
+    if (is_inkeylist(action_str.c_str(), "0", "off", "deny", "set_deny", 
"add_deny", "disable", nullptr)) {
+      rule->allow_flag = 0;
+      // Remove "allow" from this list when MATCH_ON_IP_AND_METHOD is removed 
in 11.x.
+    } else if (is_inkeylist(action_str.c_str(), "1", "on", "allow", 
"set_allow", "add_allow", "enable", nullptr)) {
+      rule->allow_flag = 1;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unknown argument 
\"%s\"", action_str.c_str());
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Unknown action: {}", action_str);
+    }
+  }
+
+  // Parse internal
+  if (node["internal"] && node["internal"].as<bool>()) {
+    rule->internal = 1;
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    rule->print();
+  }
+
+  return {};
+}
+
+swoc::Errata
+parse_map_referer(const YAML::Node &node, url_mapping *url_mapping)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("redirect must be a map");
+  }
+
+  if (!node["url"]) {
+    return swoc::Errata("Missing 'url' field in redirect map-with-referer");
+  }
+  std::string url                  = node["url"].as<std::string>();
+  url_mapping->filter_redirect_url = ats_strdup(url.c_str());
+  if (!strcasecmp(url.c_str(), "<default>") || !strcasecmp(url.c_str(), 
"default") ||
+      !strcasecmp(url.c_str(), "<default_redirect_url>") || 
!strcasecmp(url.c_str(), "default_redirect_url")) {
+    url_mapping->default_redirect_url = true;
+  }
+  url_mapping->redir_chunk_list = 
redirect_tag_str::parse_format_redirect_url(ats_strdup(url.c_str()));
+
+  if (!node["regex"] || !node["regex"].IsSequence()) {
+    return swoc::Errata("'regex' field must be sequence");
+  }
+
+  referer_info *ri;
+  for (const auto &rule : node["regex"]) {
+    char        refinfo_error_buf[1024];
+    bool        refinfo_error = false;
+    std::string regex         = rule.as<std::string>();
+
+    ri = new referer_info(regex.c_str(), &refinfo_error, refinfo_error_buf, 
sizeof(refinfo_error_buf));
+    if (refinfo_error) {
+      delete ri;
+      ri = nullptr;
+      return swoc::Errata("Incorrect Referer regular expression \"{}\" - {}", 
regex.c_str(), refinfo_error_buf);
+    }
+
+    if (ri && ri->negative) {
+      if (ri->any) {
+        url_mapping->optional_referer = true; /* referer header is optional */
+        delete ri;
+        ri = nullptr;
+      } else {
+        url_mapping->negative_referer = true; /* we have negative referer in 
list */
+      }
+    }
+    if (ri) {
+      ri->next                  = url_mapping->referer_list;
+      url_mapping->referer_list = ri;
+    }
+  }
+  return {};
+}
+
+swoc::Errata
+parse_yaml_plugins(const YAML::Node &node, url_mapping *url_mapping, 
BUILD_TABLE_INFO *bti)
+{
+  char *err;
+  char *pargv[1024];
+  int   parc = 0;
+  memset(pargv, 0, sizeof(pargv));
+
+  if (!node["name"]) {
+    return swoc::Errata("plugin missing 'name' field");
+  }
+
+  std::string plugin_name = node["name"].as<std::string>();
+  Dbg(dbg_ctl_remap_yaml, "Loading plugin: %s", plugin_name.c_str());
+
+  /* Prepare remap plugin parameters from the config */
+  if ((err = url_mapping->fromURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load fromURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  if ((err = url_mapping->toURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load toURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  // Add plugin parameters
+  if (node["params"] && node["params"].IsSequence()) {
+    for (const auto &param : node["params"]) {
+      std::string pparam_str = param.as<std::string>();
+      pargv[parc++]          = ats_strdup(pparam_str.c_str());
+      Dbg(dbg_ctl_remap_yaml, "  Plugin param: %s", pparam_str.c_str());
+    }
+  }
+
+  RemapPluginInst *pi = nullptr;
+  std::string      error;
+  {
+    uint32_t elevate_access = 0;
+    elevate_access          = 
RecGetRecordInt("proxy.config.plugin.load_elevated").value_or(0);
+    ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
+
+    pi =
+      
bti->rewrite->pluginFactory.getRemapPlugin(swoc::file::path(plugin_name), parc, 
pargv, error, isPluginDynamicReloadEnabled());
+  } // done elevating access
+
+  if (nullptr == pi) {
+    return swoc::Errata("failed to instantiate plugin ({}) to remap rule: {}", 
plugin_name.c_str(), error.c_str());

Review Comment:
   This early return will leak the pargv strdups.
   
   Can we use std::vector<std::string> instead to manage this memory?



##########
src/proxy/http/remap/RemapYamlConfig.cc:
##########
@@ -0,0 +1,1170 @@
+/** @file
+ *
+ *  YAML remap configuration file parsing implementation.
+ *
+ *  @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.
+ */
+
+#include "proxy/http/remap/RemapYamlConfig.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <filesystem>
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "tscore/Diags.h"
+#include "tscore/ink_string.h"
+#include "tsutil/ts_errata.h"
+#include "swoc/bwf_base.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/swoc_file.h"
+
+#include "proxy/http/remap/UrlRewrite.h"
+#include "proxy/http/remap/UrlMapping.h"
+#include "proxy/http/remap/RemapConfig.h"
+#include "proxy/http/remap/AclFiltering.h"
+#include "records/RecCore.h"
+
+namespace
+{
+DbgCtl dbg_ctl_remap_yaml{"remap_yaml"};
+DbgCtl dbg_ctl_url_rewrite{"url_rewrite"};
+
+/** will process the regex mapping configuration and create objects in
+    output argument reg_map. It assumes existing data in reg_map is
+    inconsequential and will be perfunctorily null-ed;
+*/
+static bool
+process_regex_mapping_config(const char *from_host_lower, url_mapping 
*new_mapping, UrlRewrite::RegexMapping *reg_map)
+{
+  std::string_view to_host{};
+  int              to_host_len;
+  int              substitution_id;
+  int32_t          captures;
+
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+  reg_map->n_substitutions          = 0;
+
+  reg_map->url_map = new_mapping;
+
+  // using from_host_lower (and not new_mapping->fromURL.host_get())
+  // as this one will be nullptr-terminated (required by pcre_compile)
+  if (reg_map->regular_expression.compile(from_host_lower) == false) {
+    Warning("pcre_compile failed! Regex has error starting at %s", 
from_host_lower);
+    goto lFail;
+  }
+
+  captures = reg_map->regular_expression.get_capture_count();
+  if (captures == -1) {
+    Warning("pcre_fullinfo failed!");
+    goto lFail;
+  }
+  if (captures >= UrlRewrite::MAX_REGEX_SUBS) { // off by one for $0 (implicit 
capture)
+    Warning("regex has %d capturing subpatterns (including entire regex); Max 
allowed: %d", captures + 1,
+            UrlRewrite::MAX_REGEX_SUBS);
+    goto lFail;
+  }
+
+  to_host     = new_mapping->toURL.host_get();
+  to_host_len = static_cast<int>(to_host.length());
+  for (int i = 0; i < to_host_len - 1; ++i) {
+    if (to_host[i] == '$') {
+      substitution_id = to_host[i + 1] - '0';
+      if ((substitution_id < 0) || (substitution_id > captures)) {
+        Warning("Substitution id [%c] has no corresponding capture pattern in 
regex [%s]", to_host[i + 1], from_host_lower);
+        goto lFail;
+      }
+      reg_map->substitution_markers[reg_map->n_substitutions] = i;
+      reg_map->substitution_ids[reg_map->n_substitutions]     = 
substitution_id;
+      ++reg_map->n_substitutions;
+    }
+  }
+
+  // so the regex itself is stored in fromURL.host; string to match
+  // will be in the request; string to use for substitutions will be
+  // in this buffer
+  reg_map->to_url_host_template_len = to_host_len;
+  reg_map->to_url_host_template     = static_cast<char 
*>(ats_malloc(to_host_len));
+  memcpy(reg_map->to_url_host_template, to_host.data(), to_host_len);
+
+  return true;
+
+lFail:
+  ats_free(reg_map->to_url_host_template);
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+
+  return false;
+}
+} // end anonymous namespace
+
+swoc::Errata
+parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, 
std::string_view &url_str)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("URL must be a map");
+  }
+  url.create(nullptr);
+
+  // Use url first if defined
+  ParseResult rparse;
+  if (node["url"]) {
+    url_str = node["url"].as<std::string_view>();
+    if (host_check) {
+      rparse = url.parse_regex(url_str);
+    } else {
+      rparse = url.parse_no_host_check(url_str);
+    }
+    if (rparse != ParseResult::DONE) {
+      return swoc::Errata("malformed URL: {}", url_str);
+    }
+
+    return {};
+  }
+
+  // Build URL string from components
+  if (node["scheme"]) {
+    url.scheme_set(node["scheme"].as<std::string>());
+  }
+
+  if (node["host"]) {
+    url.host_set(node["host"].as<std::string>());
+  }
+
+  if (node["port"]) {
+    url.port_set(node["port"].as<int>());
+  }
+
+  if (node["path"]) {
+    url.path_set(node["path"].as<std::string>());
+  }
+
+  return {};
+}
+
+swoc::Errata
+remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node 
&node, ACLBehaviorPolicy behavior_policy)
+{
+  acl_filter_rule *rule;
+  int              j;
+  bool             new_rule_flg = false;
+
+  if (!rule_pp) {
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)");
+    return swoc::Errata("Invalid argument(s)");
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    printf("validate_filter_args: ");
+    for (const auto &rule : node) {
+      printf("\"%s\" ", rule.first.as<std::string>().c_str());
+    }
+    printf("\n");
+  }
+
+  if ((rule = *rule_pp) == nullptr) {
+    rule = new acl_filter_rule();
+    if (unlikely((*rule_pp = rule) == nullptr)) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation 
error");
+      return swoc::Errata("Memory allocation Error");
+    }
+    new_rule_flg = true;
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class 
was created during remap rule processing");
+  }
+
+  if (!node || !node.IsMap()) {
+    if (new_rule_flg) {
+      delete rule;
+      *rule_pp = nullptr;
+    }
+    return swoc::Errata("filters must be a map");
+  }
+
+  // Parse method
+  auto parse_method = [&](const std::string &method_str) {
+    int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), 
nullptr) - HTTP_WKSIDX_CONNECT;
+
+    if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) {
+      rule->standard_method_lookup[m] = true;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard 
method [%s]", method_str.c_str());
+      rule->nonstandard_methods.insert(method_str);
+    }
+    rule->method_restriction_enabled = true;
+  };
+
+  if (node["method"]) {
+    if (node["method"].IsSequence()) {
+      for (const auto &method : node["method"]) {
+        parse_method(method.as<std::string>());
+      }
+    } else {
+      parse_method(node["method"].as<std::string>());
+    }
+  }
+
+  // Parse src_ip (and src_ip_invert)
+  auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> 
swoc::Errata {
+    if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} src_ip filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+
+    src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    std::string_view arg{ip_str};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", ip_str.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", ip_str);
+    }
+    for (j = 0; j < rule->src_ip_cnt; j++) {
+      if (rule->src_ip_array[j].start == ipi->start && 
rule->src_ip_array[j].end == ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_cnt++;
+      rule->src_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip"]) {
+    if (node["src_ip"].IsSequence()) {
+      for (const auto &src_ip : node["src_ip"]) {
+        auto errata = parse_src_ip(src_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["src_ip_invert"]) {
+    if (node["src_ip_invert"].IsSequence()) {
+      for (const auto &src_ip_invert : node["src_ip_invert"]) {
+        auto errata = parse_src_ip(src_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;

Review Comment:
   This is repeated frequently. Maybe something `PostScript` can help with?



##########
src/proxy/http/remap/RemapYamlConfig.cc:
##########
@@ -0,0 +1,1170 @@
+/** @file
+ *
+ *  YAML remap configuration file parsing implementation.
+ *
+ *  @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.
+ */
+
+#include "proxy/http/remap/RemapYamlConfig.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <filesystem>
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "tscore/Diags.h"
+#include "tscore/ink_string.h"
+#include "tsutil/ts_errata.h"
+#include "swoc/bwf_base.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/swoc_file.h"
+
+#include "proxy/http/remap/UrlRewrite.h"
+#include "proxy/http/remap/UrlMapping.h"
+#include "proxy/http/remap/RemapConfig.h"
+#include "proxy/http/remap/AclFiltering.h"
+#include "records/RecCore.h"
+
+namespace
+{
+DbgCtl dbg_ctl_remap_yaml{"remap_yaml"};
+DbgCtl dbg_ctl_url_rewrite{"url_rewrite"};
+
+/** will process the regex mapping configuration and create objects in
+    output argument reg_map. It assumes existing data in reg_map is
+    inconsequential and will be perfunctorily null-ed;
+*/
+static bool
+process_regex_mapping_config(const char *from_host_lower, url_mapping 
*new_mapping, UrlRewrite::RegexMapping *reg_map)
+{
+  std::string_view to_host{};
+  int              to_host_len;
+  int              substitution_id;
+  int32_t          captures;
+
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+  reg_map->n_substitutions          = 0;
+
+  reg_map->url_map = new_mapping;
+
+  // using from_host_lower (and not new_mapping->fromURL.host_get())
+  // as this one will be nullptr-terminated (required by pcre_compile)
+  if (reg_map->regular_expression.compile(from_host_lower) == false) {
+    Warning("pcre_compile failed! Regex has error starting at %s", 
from_host_lower);
+    goto lFail;
+  }
+
+  captures = reg_map->regular_expression.get_capture_count();
+  if (captures == -1) {
+    Warning("pcre_fullinfo failed!");
+    goto lFail;
+  }
+  if (captures >= UrlRewrite::MAX_REGEX_SUBS) { // off by one for $0 (implicit 
capture)
+    Warning("regex has %d capturing subpatterns (including entire regex); Max 
allowed: %d", captures + 1,
+            UrlRewrite::MAX_REGEX_SUBS);
+    goto lFail;
+  }
+
+  to_host     = new_mapping->toURL.host_get();
+  to_host_len = static_cast<int>(to_host.length());
+  for (int i = 0; i < to_host_len - 1; ++i) {
+    if (to_host[i] == '$') {
+      substitution_id = to_host[i + 1] - '0';
+      if ((substitution_id < 0) || (substitution_id > captures)) {
+        Warning("Substitution id [%c] has no corresponding capture pattern in 
regex [%s]", to_host[i + 1], from_host_lower);
+        goto lFail;
+      }
+      reg_map->substitution_markers[reg_map->n_substitutions] = i;
+      reg_map->substitution_ids[reg_map->n_substitutions]     = 
substitution_id;
+      ++reg_map->n_substitutions;
+    }
+  }
+
+  // so the regex itself is stored in fromURL.host; string to match
+  // will be in the request; string to use for substitutions will be
+  // in this buffer
+  reg_map->to_url_host_template_len = to_host_len;
+  reg_map->to_url_host_template     = static_cast<char 
*>(ats_malloc(to_host_len));
+  memcpy(reg_map->to_url_host_template, to_host.data(), to_host_len);
+
+  return true;
+
+lFail:
+  ats_free(reg_map->to_url_host_template);
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+
+  return false;
+}
+} // end anonymous namespace
+
+swoc::Errata
+parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, 
std::string_view &url_str)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("URL must be a map");
+  }
+  url.create(nullptr);
+
+  // Use url first if defined
+  ParseResult rparse;
+  if (node["url"]) {
+    url_str = node["url"].as<std::string_view>();
+    if (host_check) {
+      rparse = url.parse_regex(url_str);
+    } else {
+      rparse = url.parse_no_host_check(url_str);
+    }
+    if (rparse != ParseResult::DONE) {
+      return swoc::Errata("malformed URL: {}", url_str);
+    }
+
+    return {};
+  }
+
+  // Build URL string from components
+  if (node["scheme"]) {
+    url.scheme_set(node["scheme"].as<std::string>());
+  }
+
+  if (node["host"]) {
+    url.host_set(node["host"].as<std::string>());
+  }
+
+  if (node["port"]) {
+    url.port_set(node["port"].as<int>());
+  }
+
+  if (node["path"]) {
+    url.path_set(node["path"].as<std::string>());
+  }
+
+  return {};
+}
+
+swoc::Errata
+remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node 
&node, ACLBehaviorPolicy behavior_policy)
+{
+  acl_filter_rule *rule;
+  int              j;
+  bool             new_rule_flg = false;
+
+  if (!rule_pp) {
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)");
+    return swoc::Errata("Invalid argument(s)");
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    printf("validate_filter_args: ");
+    for (const auto &rule : node) {
+      printf("\"%s\" ", rule.first.as<std::string>().c_str());
+    }
+    printf("\n");
+  }
+
+  if ((rule = *rule_pp) == nullptr) {
+    rule = new acl_filter_rule();
+    if (unlikely((*rule_pp = rule) == nullptr)) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation 
error");
+      return swoc::Errata("Memory allocation Error");
+    }
+    new_rule_flg = true;
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class 
was created during remap rule processing");
+  }
+
+  if (!node || !node.IsMap()) {
+    if (new_rule_flg) {
+      delete rule;
+      *rule_pp = nullptr;
+    }
+    return swoc::Errata("filters must be a map");
+  }
+
+  // Parse method
+  auto parse_method = [&](const std::string &method_str) {
+    int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), 
nullptr) - HTTP_WKSIDX_CONNECT;
+
+    if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) {
+      rule->standard_method_lookup[m] = true;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard 
method [%s]", method_str.c_str());
+      rule->nonstandard_methods.insert(method_str);
+    }
+    rule->method_restriction_enabled = true;
+  };
+
+  if (node["method"]) {
+    if (node["method"].IsSequence()) {
+      for (const auto &method : node["method"]) {
+        parse_method(method.as<std::string>());
+      }
+    } else {
+      parse_method(node["method"].as<std::string>());
+    }
+  }
+
+  // Parse src_ip (and src_ip_invert)
+  auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> 
swoc::Errata {
+    if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} src_ip filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+
+    src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    std::string_view arg{ip_str};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", ip_str.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", ip_str);
+    }
+    for (j = 0; j < rule->src_ip_cnt; j++) {
+      if (rule->src_ip_array[j].start == ipi->start && 
rule->src_ip_array[j].end == ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_cnt++;
+      rule->src_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip"]) {
+    if (node["src_ip"].IsSequence()) {
+      for (const auto &src_ip : node["src_ip"]) {
+        auto errata = parse_src_ip(src_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["src_ip_invert"]) {
+    if (node["src_ip_invert"].IsSequence()) {
+      for (const auto &src_ip_invert : node["src_ip_invert"]) {
+        auto errata = parse_src_ip(src_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip_invert"].as<std::string>(), 
true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse src_ip_category (and src_ip_category)

Review Comment:
   Probably: "(and src_ip_category_invert)"
   



##########
src/proxy/http/remap/RemapYamlConfig.cc:
##########
@@ -0,0 +1,1170 @@
+/** @file
+ *
+ *  YAML remap configuration file parsing implementation.
+ *
+ *  @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.
+ */
+
+#include "proxy/http/remap/RemapYamlConfig.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <filesystem>
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "tscore/Diags.h"
+#include "tscore/ink_string.h"
+#include "tsutil/ts_errata.h"
+#include "swoc/bwf_base.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/swoc_file.h"
+
+#include "proxy/http/remap/UrlRewrite.h"
+#include "proxy/http/remap/UrlMapping.h"
+#include "proxy/http/remap/RemapConfig.h"
+#include "proxy/http/remap/AclFiltering.h"
+#include "records/RecCore.h"
+
+namespace
+{
+DbgCtl dbg_ctl_remap_yaml{"remap_yaml"};
+DbgCtl dbg_ctl_url_rewrite{"url_rewrite"};
+
+/** will process the regex mapping configuration and create objects in
+    output argument reg_map. It assumes existing data in reg_map is
+    inconsequential and will be perfunctorily null-ed;
+*/
+static bool
+process_regex_mapping_config(const char *from_host_lower, url_mapping 
*new_mapping, UrlRewrite::RegexMapping *reg_map)
+{
+  std::string_view to_host{};
+  int              to_host_len;
+  int              substitution_id;
+  int32_t          captures;
+
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+  reg_map->n_substitutions          = 0;
+
+  reg_map->url_map = new_mapping;
+
+  // using from_host_lower (and not new_mapping->fromURL.host_get())
+  // as this one will be nullptr-terminated (required by pcre_compile)
+  if (reg_map->regular_expression.compile(from_host_lower) == false) {
+    Warning("pcre_compile failed! Regex has error starting at %s", 
from_host_lower);
+    goto lFail;
+  }
+
+  captures = reg_map->regular_expression.get_capture_count();
+  if (captures == -1) {
+    Warning("pcre_fullinfo failed!");
+    goto lFail;
+  }
+  if (captures >= UrlRewrite::MAX_REGEX_SUBS) { // off by one for $0 (implicit 
capture)
+    Warning("regex has %d capturing subpatterns (including entire regex); Max 
allowed: %d", captures + 1,
+            UrlRewrite::MAX_REGEX_SUBS);
+    goto lFail;
+  }
+
+  to_host     = new_mapping->toURL.host_get();
+  to_host_len = static_cast<int>(to_host.length());
+  for (int i = 0; i < to_host_len - 1; ++i) {
+    if (to_host[i] == '$') {
+      substitution_id = to_host[i + 1] - '0';
+      if ((substitution_id < 0) || (substitution_id > captures)) {
+        Warning("Substitution id [%c] has no corresponding capture pattern in 
regex [%s]", to_host[i + 1], from_host_lower);
+        goto lFail;
+      }
+      reg_map->substitution_markers[reg_map->n_substitutions] = i;
+      reg_map->substitution_ids[reg_map->n_substitutions]     = 
substitution_id;
+      ++reg_map->n_substitutions;
+    }
+  }
+
+  // so the regex itself is stored in fromURL.host; string to match
+  // will be in the request; string to use for substitutions will be
+  // in this buffer
+  reg_map->to_url_host_template_len = to_host_len;
+  reg_map->to_url_host_template     = static_cast<char 
*>(ats_malloc(to_host_len));
+  memcpy(reg_map->to_url_host_template, to_host.data(), to_host_len);
+
+  return true;
+
+lFail:
+  ats_free(reg_map->to_url_host_template);
+  reg_map->to_url_host_template     = nullptr;
+  reg_map->to_url_host_template_len = 0;
+
+  return false;
+}
+} // end anonymous namespace
+
+swoc::Errata
+parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, 
std::string_view &url_str)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("URL must be a map");
+  }
+  url.create(nullptr);
+
+  // Use url first if defined
+  ParseResult rparse;
+  if (node["url"]) {
+    url_str = node["url"].as<std::string_view>();
+    if (host_check) {
+      rparse = url.parse_regex(url_str);
+    } else {
+      rparse = url.parse_no_host_check(url_str);
+    }
+    if (rparse != ParseResult::DONE) {
+      return swoc::Errata("malformed URL: {}", url_str);
+    }
+
+    return {};
+  }
+
+  // Build URL string from components
+  if (node["scheme"]) {
+    url.scheme_set(node["scheme"].as<std::string>());
+  }
+
+  if (node["host"]) {
+    url.host_set(node["host"].as<std::string>());
+  }
+
+  if (node["port"]) {
+    url.port_set(node["port"].as<int>());
+  }
+
+  if (node["path"]) {
+    url.path_set(node["path"].as<std::string>());
+  }
+
+  return {};
+}
+
+swoc::Errata
+remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node 
&node, ACLBehaviorPolicy behavior_policy)
+{
+  acl_filter_rule *rule;
+  int              j;
+  bool             new_rule_flg = false;
+
+  if (!rule_pp) {
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)");
+    return swoc::Errata("Invalid argument(s)");
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    printf("validate_filter_args: ");
+    for (const auto &rule : node) {
+      printf("\"%s\" ", rule.first.as<std::string>().c_str());
+    }
+    printf("\n");
+  }
+
+  if ((rule = *rule_pp) == nullptr) {
+    rule = new acl_filter_rule();
+    if (unlikely((*rule_pp = rule) == nullptr)) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation 
error");
+      return swoc::Errata("Memory allocation Error");
+    }
+    new_rule_flg = true;
+    Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class 
was created during remap rule processing");
+  }
+
+  if (!node || !node.IsMap()) {
+    if (new_rule_flg) {
+      delete rule;
+      *rule_pp = nullptr;
+    }
+    return swoc::Errata("filters must be a map");
+  }
+
+  // Parse method
+  auto parse_method = [&](const std::string &method_str) {
+    int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), 
nullptr) - HTTP_WKSIDX_CONNECT;
+
+    if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) {
+      rule->standard_method_lookup[m] = true;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard 
method [%s]", method_str.c_str());
+      rule->nonstandard_methods.insert(method_str);
+    }
+    rule->method_restriction_enabled = true;
+  };
+
+  if (node["method"]) {
+    if (node["method"].IsSequence()) {
+      for (const auto &method : node["method"]) {
+        parse_method(method.as<std::string>());
+      }
+    } else {
+      parse_method(node["method"].as<std::string>());
+    }
+  }
+
+  // Parse src_ip (and src_ip_invert)
+  auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> 
swoc::Errata {
+    if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} src_ip filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+
+    src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    std::string_view arg{ip_str};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", ip_str.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", ip_str);
+    }
+    for (j = 0; j < rule->src_ip_cnt; j++) {
+      if (rule->src_ip_array[j].start == ipi->start && 
rule->src_ip_array[j].end == ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_cnt++;
+      rule->src_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip"]) {
+    if (node["src_ip"].IsSequence()) {
+      for (const auto &src_ip : node["src_ip"]) {
+        auto errata = parse_src_ip(src_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["src_ip_invert"]) {
+    if (node["src_ip_invert"].IsSequence()) {
+      for (const auto &src_ip_invert : node["src_ip_invert"]) {
+        auto errata = parse_src_ip(src_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_src_ip(node["src_ip_invert"].as<std::string>(), 
true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse src_ip_category (and src_ip_category)
+  auto parse_src_ip_category = [&](const std::string &ip_category, bool 
invert) -> swoc::Errata {
+    if (rule->src_ip_category_cnt >= ACL_FILTER_MAX_SRC_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many 
\"src_ip_category=\" filters");
+      return swoc::Errata("Defined more than {} src_ip_category filters", 
ACL_FILTER_MAX_SRC_IP);
+    }
+    src_ip_category_info_t *ipi = 
&rule->src_ip_category_array[rule->src_ip_category_cnt];
+    ipi->category.assign(ip_category);
+    if (invert) {
+      ipi->invert = true;
+    }
+    for (j = 0; j < rule->src_ip_category_cnt; j++) {
+      if (rule->src_ip_category_array[j].category == ipi->category) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->src_ip_category_cnt++;
+      rule->src_ip_category_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["src_ip_category"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category"].as<std::string>(), false);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  if (node["src_ip_category_invert"]) {
+    auto errata = 
parse_src_ip_category(node["src_ip_category_invert"].as<std::string>(), true);
+    if (!errata.is_ok()) {
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return errata;
+    }
+  }
+
+  // Parse in_ip (and in_ip_invert)
+  auto parse_in_ip = [&](const std::string &in_ip, bool invert) -> 
swoc::Errata {
+    if (rule->in_ip_cnt >= ACL_FILTER_MAX_IN_IP) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"in_ip=\" 
filters");
+      return swoc::Errata("Defined more than {} in_ip filters", 
ACL_FILTER_MAX_IN_IP);
+    }
+    src_ip_info_t *ipi = &rule->in_ip_array[rule->in_ip_cnt];
+    if (invert) {
+      ipi->invert = true;
+    }
+    // important! use copy of argument
+    std::string_view arg{in_ip};
+    if (arg == "all") {
+      ipi->match_all_addresses = true;
+    } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP 
value in %s", in_ip.c_str());
+      return swoc::Errata("Unable to parse IP value: {}", in_ip);
+    }
+    for (j = 0; j < rule->in_ip_cnt; j++) {
+      if (rule->in_ip_array[j].start == ipi->start && rule->in_ip_array[j].end 
== ipi->end) {
+        ipi->reset();
+        return {};
+      }
+    }
+    if (ipi) {
+      rule->in_ip_cnt++;
+      rule->in_ip_valid = 1;
+    }
+    return {};
+  };
+
+  if (node["in_ip"]) {
+    if (node["in_ip"].IsSequence()) {
+      for (const auto &in_ip : node["in_ip"]) {
+        auto errata = parse_in_ip(in_ip.as<std::string>(), false);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip"].as<std::string>(), false);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  if (node["in_ip_invert"]) {
+    if (node["in_ip_invert"].IsSequence()) {
+      for (const auto &in_ip_invert : node["in_ip_invert"]) {
+        auto errata = parse_in_ip(in_ip_invert.as<std::string>(), true);
+        if (!errata.is_ok()) {
+          if (new_rule_flg) {
+            delete rule;
+            *rule_pp = nullptr;
+          }
+          return errata;
+        }
+      }
+    } else {
+      auto errata = parse_in_ip(node["in_ip_invert"].as<std::string>(), true);
+      if (!errata.is_ok()) {
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return errata;
+      }
+    }
+  }
+
+  // Parse action
+  if (node["action"]) {
+    if (node["action"].IsSequence()) {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Only one action is 
allowed per remap ACL");
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Only one action is allowed per remap ACL");
+    }
+    std::string action_str = node["action"].as<std::string>();
+    if (behavior_policy == ACLBehaviorPolicy::ACL_BEHAVIOR_MODERN) {
+      // With the new matching policy, we don't allow the legacy "allow" and
+      // "deny" actions. Users must transition to either add_allow/add_deny or
+      // set_allow/set_deny.
+      if (is_inkeylist(action_str.c_str(), "allow", "deny", nullptr)) {
+        Dbg(dbg_ctl_url_rewrite,
+            R"([validate_filter_args] "allow" and "deny" are no longer valid. 
Use add_allow/add_deny or set_allow/set_deny: "%s"")",
+            action_str.c_str());
+        if (new_rule_flg) {
+          delete rule;
+          *rule_pp = nullptr;
+        }
+        return swoc::Errata("\"allow\" and \"deny\" are no longer valid. Use 
add_allow/add_deny or set_allow/set_deny: {}",
+                            action_str.c_str());
+      }
+    }
+    if (is_inkeylist(action_str.c_str(), "add_allow", "add_deny", nullptr)) {
+      rule->add_flag = 1;
+    } else {
+      rule->add_flag = 0;
+    }
+    // Remove "deny" from this list when MATCH_ON_IP_AND_METHOD is removed in 
11.x.
+    if (is_inkeylist(action_str.c_str(), "0", "off", "deny", "set_deny", 
"add_deny", "disable", nullptr)) {
+      rule->allow_flag = 0;
+      // Remove "allow" from this list when MATCH_ON_IP_AND_METHOD is removed 
in 11.x.
+    } else if (is_inkeylist(action_str.c_str(), "1", "on", "allow", 
"set_allow", "add_allow", "enable", nullptr)) {
+      rule->allow_flag = 1;
+    } else {
+      Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unknown argument 
\"%s\"", action_str.c_str());
+      if (new_rule_flg) {
+        delete rule;
+        *rule_pp = nullptr;
+      }
+      return swoc::Errata("Unknown action: {}", action_str);
+    }
+  }
+
+  // Parse internal
+  if (node["internal"] && node["internal"].as<bool>()) {
+    rule->internal = 1;
+  }
+
+  if (dbg_ctl_url_rewrite.on()) {
+    rule->print();
+  }
+
+  return {};
+}
+
+swoc::Errata
+parse_map_referer(const YAML::Node &node, url_mapping *url_mapping)
+{
+  if (!node || !node.IsMap()) {
+    return swoc::Errata("redirect must be a map");
+  }
+
+  if (!node["url"]) {
+    return swoc::Errata("Missing 'url' field in redirect map-with-referer");
+  }
+  std::string url                  = node["url"].as<std::string>();
+  url_mapping->filter_redirect_url = ats_strdup(url.c_str());
+  if (!strcasecmp(url.c_str(), "<default>") || !strcasecmp(url.c_str(), 
"default") ||
+      !strcasecmp(url.c_str(), "<default_redirect_url>") || 
!strcasecmp(url.c_str(), "default_redirect_url")) {
+    url_mapping->default_redirect_url = true;
+  }
+  url_mapping->redir_chunk_list = 
redirect_tag_str::parse_format_redirect_url(ats_strdup(url.c_str()));
+
+  if (!node["regex"] || !node["regex"].IsSequence()) {
+    return swoc::Errata("'regex' field must be sequence");
+  }
+
+  referer_info *ri;
+  for (const auto &rule : node["regex"]) {
+    char        refinfo_error_buf[1024];
+    bool        refinfo_error = false;
+    std::string regex         = rule.as<std::string>();
+
+    ri = new referer_info(regex.c_str(), &refinfo_error, refinfo_error_buf, 
sizeof(refinfo_error_buf));
+    if (refinfo_error) {
+      delete ri;
+      ri = nullptr;
+      return swoc::Errata("Incorrect Referer regular expression \"{}\" - {}", 
regex.c_str(), refinfo_error_buf);
+    }
+
+    if (ri && ri->negative) {
+      if (ri->any) {
+        url_mapping->optional_referer = true; /* referer header is optional */
+        delete ri;
+        ri = nullptr;
+      } else {
+        url_mapping->negative_referer = true; /* we have negative referer in 
list */
+      }
+    }
+    if (ri) {
+      ri->next                  = url_mapping->referer_list;
+      url_mapping->referer_list = ri;
+    }
+  }
+  return {};
+}
+
+swoc::Errata
+parse_yaml_plugins(const YAML::Node &node, url_mapping *url_mapping, 
BUILD_TABLE_INFO *bti)
+{
+  char *err;
+  char *pargv[1024];
+  int   parc = 0;
+  memset(pargv, 0, sizeof(pargv));
+
+  if (!node["name"]) {
+    return swoc::Errata("plugin missing 'name' field");
+  }
+
+  std::string plugin_name = node["name"].as<std::string>();
+  Dbg(dbg_ctl_remap_yaml, "Loading plugin: %s", plugin_name.c_str());
+
+  /* Prepare remap plugin parameters from the config */
+  if ((err = url_mapping->fromURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load fromURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  if ((err = url_mapping->toURL.string_get(nullptr)) == nullptr) {
+    return swoc::Errata("Can't load toURL from URL class");
+  }
+  pargv[parc++] = ats_strdup(err);
+  ats_free(err);
+
+  // Add plugin parameters
+  if (node["params"] && node["params"].IsSequence()) {
+    for (const auto &param : node["params"]) {
+      std::string pparam_str = param.as<std::string>();
+      pargv[parc++]          = ats_strdup(pparam_str.c_str());
+      Dbg(dbg_ctl_remap_yaml, "  Plugin param: %s", pparam_str.c_str());
+    }
+  }
+
+  RemapPluginInst *pi = nullptr;
+  std::string      error;
+  {
+    uint32_t elevate_access = 0;
+    elevate_access          = 
RecGetRecordInt("proxy.config.plugin.load_elevated").value_or(0);
+    ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
+
+    pi =
+      
bti->rewrite->pluginFactory.getRemapPlugin(swoc::file::path(plugin_name), parc, 
pargv, error, isPluginDynamicReloadEnabled());
+  } // done elevating access
+
+  if (nullptr == pi) {
+    return swoc::Errata("failed to instantiate plugin ({}) to remap rule: {}", 
plugin_name.c_str(), error.c_str());
+  } else {
+    url_mapping->add_plugin_instance(pi);
+  }
+
+  ats_free(pargv[0]); // fromURL
+  ats_free(pargv[1]); // toURL
+
+  return {};
+}
+
+swoc::Errata
+parse_yaml_filter_directive(const YAML::Node &node, BUILD_TABLE_INFO *bti)
+{
+  acl_filter_rule *rp;
+
+  // Check for activate_filters directive
+  if (node["activate_filter"]) {
+    std::string filter_name = node["activate_filter"].as<std::string>();
+
+    // Check if for ip_allow filter
+    if (strcmp(filter_name.c_str(), "ip_allow") == 0) {
+      bti->ip_allow_check_enabled_p = true;
+      return {};
+      ;
+    }

Review Comment:
   Extra `;`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to