text: /** @file
  
    A brief file description
  
    @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.
   */
  
  /*****************************************************************************
   *
   *  ControlBase.cc - Base class to process generic modifiers to
   *                         ControlMatcher Directives
   *
   *
   ****************************************************************************/
  #include "tscore/ink_platform.h"
  #include "tscore/ink_defs.h"
  #include "tscore/ink_time.h"
  
  #include "URL.h"
  #include "tscore/Tokenizer.h"
  #include "ControlBase.h"
  #include "tscore/MatcherUtils.h"
  #include "HTTP.h"
  #include "ControlMatcher.h"
  #include "HdrUtils.h"
  
  #include "tscore/TsBuffer.h"
  
  #include <vector>
  
  /** Used for printing IP address.
      @code
      uint32_t addr; // IP address.
      printf("IP address = " TS_IP_PRINTF_CODE,TS_IP_OCTETS(addr));
      @endcode
      @internal Need to move these to a common header.
   */
  #define TS_IP_OCTETS(x)                                                                               \
    reinterpret_cast<unsigned const char *>(&(x))[0], reinterpret_cast<unsigned char const *>(&(x))[1], \
      reinterpret_cast<unsigned const char *>(&(x))[2], reinterpret_cast<unsigned char const *>(&(x))[3]
  
  // ----------
  ControlBase::Modifier::~Modifier() = default;
  ControlBase::Modifier::Type
  ControlBase::Modifier::type() const
  {
    return MOD_INVALID;
  }
  // --------------------------
  namespace
  {
  // ----------
  struct TimeMod : public ControlBase::Modifier {
    time_t start_time;
    time_t end_time;
  
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    void print(FILE *f) const override;
    static TimeMod *make(char *value, const char **error);
    static const char *timeOfDayToSeconds(const char *time_str, time_t *seconds);
  };
  
  const char *const TimeMod::NAME = "Time";
  ControlBase::Modifier::Type
  TimeMod::type() const
  {
    return MOD_TIME;
  }
  const char *
  TimeMod::name() const
  {
    return NAME;
  }
  
  void
  TimeMod::print(FILE *f) const
  {
    fprintf(f, "%s=%ld-%ld  ",
            // Have to cast because time_t can be 32 or 64 bits and the compiler
            // will barf if format code doesn't match.
            this->name(), static_cast<long>(start_time), static_cast<long>(end_time));
  }
  bool
  TimeMod::check(HttpRequestData *req) const
  {
    struct tm cur_time;
    time_t timeOfDay = req->xact_start;
    // Use this to account for daylight savings time.
    ink_localtime_r(&timeOfDay, &cur_time);
    timeOfDay = cur_time.tm_hour * (60 * 60) + cur_time.tm_min * 60 + cur_time.tm_sec;
    return start_time <= timeOfDay && timeOfDay <= end_time;
  }
  
  TimeMod *
  TimeMod::make(char *value, const char **error)
  {
    Tokenizer rangeTok("-");
    TimeMod *mod = nullptr;
    TimeMod tmp;
    int num_tok;
  
    num_tok = rangeTok.Initialize(value, SHARE_TOKS);
    if (num_tok == 1) {
      *error = "End time not specified";
    } else if (num_tok > 2) {
      *error = "Malformed time range";
    } else if (nullptr == (*error = timeOfDayToSeconds(rangeTok[0], &tmp.start_time)) &&
               nullptr == (*error = timeOfDayToSeconds(rangeTok[1], &tmp.end_time))) {
      mod = new TimeMod(tmp);
    }
    return mod;
  }
  /**   Converts TimeOfDay (TOD) to second value.
        @a *seconds is set to number of seconds since midnight
        represented by @a time_str.
  
        @return 0 on success, static error string on failure.
  */
  const char *
  TimeMod::timeOfDayToSeconds(const char *time_str, time_t *seconds)
  {
    int hour   = 0;
    int min    = 0;
    int sec    = 0;
    time_t tmp = 0;
  
    // coverity[secure_coding]
    if (sscanf(time_str, "%d:%d:%d", &hour, &min, &sec) != 3) {
      // coverity[secure_coding]
      if (sscanf(time_str, "%d:%d", &hour, &min) != 2) {
        return "Malformed time specified";
      }
    }
  
    if (!(hour >= 0 && hour <= 23)) {
      return "Illegal hour specification";
    }
    tmp = hour * 60;
  
    if (!(min >= 0 && min <= 59)) {
      return "Illegal minute specification";
    }
    tmp = (tmp + min) * 60;
  
    if (!(sec >= 0 && sec <= 59)) {
      return "Illegal second specification";
    }
    tmp += sec;
  
    *seconds = tmp;
    return nullptr;
  }
  
  // ----------
  struct PortMod : public ControlBase::Modifier {
    int start_port;
    int end_port;
  
    static const char *const NAME;
  
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    void print(FILE *f) const override;
  
    static PortMod *make(char *value, const char **error);
  };
  
  const char *const PortMod::NAME = "Port";
  const char *
  PortMod::name() const
  {
    return NAME;
  }
  
  void
  PortMod::print(FILE *f) const
  {
    fprintf(f, "%s=%d-%d  ", this->name(), start_port, end_port);
  }
  
  bool
  PortMod::check(HttpRequestData *req) const
  {
    int port = req->hdr->port_get();
    return start_port <= port && port <= end_port;
  }
  
  PortMod *
  PortMod::make(char *value, const char **error)
  {
    Tokenizer rangeTok("-");
    PortMod tmp;
    int num_tok = rangeTok.Initialize(value, SHARE_TOKS);
  
    *error = nullptr;
    if (num_tok > 2) {
      *error = "Malformed Range";
      // coverity[secure_coding]
    } else if (sscanf(rangeTok[0], "%d", &tmp.start_port) != 1) {
      *error = "Invalid start port";
    } else if (num_tok == 2) {
      // coverity[secure_coding]
      if (sscanf(rangeTok[1], "%d", &tmp.end_port) != 1) {
        *error = "Invalid end port";
      } else if (tmp.end_port < tmp.start_port) {
        *error = "Malformed Range: end port < start port";
      }
    } else {
      tmp.end_port = tmp.start_port;
    }
  
    // If there's an error message, return null.
    // Otherwise create a new item and return it.
    return *error ? nullptr : new PortMod(tmp);
  }
  
  // ----------
  struct IPortMod : public ControlBase::Modifier {
    int _port;
  
    static const char *const NAME;
  
    IPortMod(int port);
  
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    void print(FILE *f) const override;
    static IPortMod *make(char *value, const char **error);
  };
  
  const char *const IPortMod::NAME = "IPort";
  IPortMod::IPortMod(int port) : _port(port) {}
  const char *
  IPortMod::name() const
  {
    return NAME;
  }
  
  void
  IPortMod::print(FILE *f) const
  {
    fprintf(f, "%s=%d  ", this->name(), _port);
  }
  bool
  IPortMod::check(HttpRequestData *req) const
  {
    return req->incoming_port == _port;
  }
  
  IPortMod *
  IPortMod::make(char *value, const char **error)
  {
    IPortMod *zret = nullptr;
    int port;
    // coverity[secure_coding]
    if (sscanf(value, "%u", &port) == 1) {
      zret = new IPortMod(port);
    } else {
      *error = "Invalid incoming port";
    }
    return zret;
  }
  // ----------
  struct SrcIPMod : public ControlBase::Modifier {
    // Stored in host order because that's how they are compared.
    IpEndpoint start_addr; ///< Start address in HOST order.
    IpEndpoint end_addr;   ///< End address in HOST order.
  
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    void print(FILE *f) const override;
    static SrcIPMod *make(char *value, const char **error);
  };
  
  const char *const SrcIPMod::NAME = "SrcIP";
  ControlBase::Modifier::Type
  SrcIPMod::type() const
  {
    return MOD_SRC_IP;
  }
  const char *
  SrcIPMod::name() const
  {
    return NAME;
  }
  
  void
  SrcIPMod::print(FILE *f) const
  {
    ip_text_buffer b1, b2;
    fprintf(f, "%s=%s-%s  ", this->name(), ats_ip_ntop(&start_addr.sa, b1, sizeof(b1)), ats_ip_ntop(&end_addr.sa, b2, sizeof(b2)));
  }
  bool
  SrcIPMod::check(HttpRequestData *req) const
  {
    // Compare in host order
    return ats_ip_addr_cmp(&start_addr, &req->src_ip) <= 0 && ats_ip_addr_cmp(&req->src_ip, &end_addr) <= 0;
  }
  SrcIPMod *
  SrcIPMod::make(char *value, const char **error)
  {
    SrcIPMod tmp;
    SrcIPMod *zret = nullptr;
    *error         = ExtractIpRange(value, &tmp.start_addr.sa, &tmp.end_addr.sa);
  
    if (!*error) {
      zret = new SrcIPMod(tmp);
    }
    return zret;
  }
  // ----------
  struct SchemeMod : public ControlBase::Modifier {
    int _scheme; ///< Tokenized scheme.
  
    static const char *const NAME;
  
    SchemeMod(int scheme);
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    void print(FILE *f) const override;
  
    const char *getWksText() const;
  
    static SchemeMod *make(char *value, const char **error);
  };
  
  const char *const SchemeMod::NAME = "Scheme";
  
  SchemeMod::SchemeMod(int scheme) : _scheme(scheme) {}
  
  ControlBase::Modifier::Type
  SchemeMod::type() const
  {
    return MOD_SCHEME;
  }
  const char *
  SchemeMod::name() const
  {
    return NAME;
  }
  const char *
  SchemeMod::getWksText() const
  {
    return hdrtoken_index_to_wks(_scheme);
  }
  
  bool
  SchemeMod::check(HttpRequestData *req) const
  {
    return req->hdr->url_get()->scheme_get_wksidx() == _scheme;
  }
  void
  SchemeMod::print(FILE *f) const
  {
    fprintf(f, "%s=%s  ", this->name(), hdrtoken_index_to_wks(_scheme));
  }
  SchemeMod *
  SchemeMod::make(char *value, const char **error)
  {
    SchemeMod *zret = nullptr;
    int scheme      = hdrtoken_tokenize(value, strlen(value));
    if (scheme < 0) {
      *error = "Unknown scheme";
    } else {
      zret = new SchemeMod(scheme);
    }
    return zret;
  }
  
  // ----------
  // This is a base class for all of the mods that have a
  // text string.
  struct TextMod : public ControlBase::Modifier {
    ts::Buffer text;
  
    TextMod();
    ~TextMod() override;
  
    // Calls name() which the subclass must provide.
    void print(FILE *f) const override;
  
    // Copy the given NUL-terminated string to the text buffer.
    void set(const char *value);
  };
  
  TextMod::TextMod() : text() {}
  TextMod::~TextMod()
  {
    free(text.data());
  }
  
  void
  TextMod::print(FILE *f) const
  {
    fprintf(f, "%s=%*s  ", this->name(), static_cast<int>(text.size()), text.data());
  }
  
  void
  TextMod::set(const char *value)
  {
    free(this->text.data());
    this->text.set(ats_strdup(value), strlen(value));
  }
  
  struct MultiTextMod : public ControlBase::Modifier {
    std::vector<ts::Buffer> text_vec;
    MultiTextMod();
    ~MultiTextMod() override;
  
    // Copy the value to the MultiTextMod buffer.
    void set(char *value);
  
    // Calls name() which the subclass must provide.
    void print(FILE *f) const override;
  };
  
  MultiTextMod::MultiTextMod() = default;
  MultiTextMod::~MultiTextMod()
  {
    text_vec.clear();
  }
  
  void
  MultiTextMod::print(FILE *f) const
  {
    for (auto text_iter : this->text_vec) {
      fprintf(f, "%s=%*s ", this->name(), static_cast<int>(text_iter.size()), text_iter.data());
    }
  }
  
  void
  MultiTextMod::set(char *value)
  {
    Tokenizer rangeTok(",");
    int num_tok = rangeTok.Initialize(value, SHARE_TOKS);
    for (int i = 0; i < num_tok; i++) {
      ts::Buffer text;
      text.set(ats_strdup(rangeTok[i]), strlen(rangeTok[i]));
      this->text_vec.push_back(text);
    }
  }
  
  // ----------
  struct MethodMod : public TextMod {
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
  
    static MethodMod *make(char *value, const char **error);
  };
  const char *const MethodMod::NAME = "Method";
  ControlBase::Modifier::Type
  MethodMod::type() const
  {
    return MOD_METHOD;
  }
  const char *
  MethodMod::name() const
  {
    return NAME;
  }
  bool
  MethodMod::check(HttpRequestData *req) const
  {
    int method_len;
    const char *method = req->hdr->method_get(&method_len);
    return method_len >= static_cast<int>(text.size()) && 0 == strncasecmp(method, text.data(), text.size());
  }
  MethodMod *
  MethodMod::make(char *value, const char **)
  {
    MethodMod *mod = new MethodMod();
    mod->set(value);
    return mod;
  }
  
  // ----------
  struct PrefixMod : public TextMod {
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    static PrefixMod *make(char *value, const char **error);
  };
  
  const char *const PrefixMod::NAME = "Prefix";
  ControlBase::Modifier::Type
  PrefixMod::type() const
  {
    return MOD_PREFIX;
  }
  const char *
  PrefixMod::name() const
  {
    return NAME;
  }
  bool
  PrefixMod::check(HttpRequestData *req) const
  {
    int path_len;
    const char *path = req->hdr->url_get()->path_get(&path_len);
    bool zret        = path_len >= static_cast<int>(text.size()) && 0 == memcmp(path, text.data(), text.size());
    /*
      Debug("cache_control", "Prefix check: URL=%0.*s Mod=%0.*s Z=%s",
        path_len, path, text.size(), text.data(),
        zret ? "Match" : "Fail"
      );
    */
    return zret;
  }
  PrefixMod *
  PrefixMod::make(char *value, const char ** /* error ATS_UNUSED */)
  {
    PrefixMod *mod = new PrefixMod();
    // strip leading slashes because get_path which is used later
    // doesn't include them from the URL.
    while ('/' == *value) {
      ++value;
    }
    mod->set(value);
    return mod;
  }
  
  // ----------
  struct SuffixMod : public MultiTextMod {
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    static SuffixMod *make(char *value, const char **error);
  };
  const char *const SuffixMod::NAME = "Suffix";
  ControlBase::Modifier::Type
  SuffixMod::type() const
  {
    return MOD_SUFFIX;
  }
  const char *
  SuffixMod::name() const
  {
    return NAME;
  }
  bool
  SuffixMod::check(HttpRequestData *req) const
  {
    int path_len;
    const char *path = req->hdr->url_get()->path_get(&path_len);
  
    if (1 == static_cast<int>(this->text_vec.size()) && 1 == static_cast<int>(this->text_vec[0].size()) &&
        0 == strcmp(this->text_vec[0].data(), "*")) {
      return true;
    }
  
    for (auto text_iter : this->text_vec) {
      if (path_len >= static_cast<int>(text_iter.size()) &&
          0 == strncasecmp(path + path_len - text_iter.size(), text_iter.data(), text_iter.size())) {
        return true;
      }
    }
  
    return false;
  }
  SuffixMod *
  SuffixMod::make(char *value, const char ** /* error ATS_UNUSED */)
  {
    SuffixMod *mod = new SuffixMod();
    mod->set(value);
    return mod;
  }
  
  // ----------
  struct TagMod : public TextMod {
    static const char *const NAME;
  
    Type type() const override;
    const char *name() const override;
    bool check(HttpRequestData *req) const override;
    static TagMod *make(char *value, const char **error);
  };
  const char *const TagMod::NAME = "Tag";
  ControlBase::Modifier::Type
  TagMod::type() const
  {
    return MOD_TAG;
  }
  const char *
  TagMod::name() const
  {
    return NAME;
  }
  bool
  TagMod::check(HttpRequestData *req) const
  {
    return 0 == strcmp(req->tag, text.data());
  }
  TagMod *
  TagMod::make(char *value, const char ** /* error ATS_UNUSED */)
  {
    TagMod *mod = new TagMod();
    mod->set(value);
    return mod;
  }
  
  // ----------
  struct InternalMod : public ControlBase::Modifier {
    bool flag;
    static const char *const NAME;
  
    Type
    type() const override
    {
      return MOD_INTERNAL;
    }
    const char *
    name() const override
    {
      return NAME;
    }
    bool
    check(HttpRequestData *req) const override
    {
      return req->internal_txn == flag;
    }
    void
    print(FILE *f) const override
    {
      fprintf(f, "%s=%s  ", this->name(), flag ? "true" : "false");
    }
    static InternalMod *make(char *value, const char **error);
  };
  
  const char *const InternalMod::NAME = "Internal";
  
  InternalMod *
  InternalMod::make(char *value, const char **error)
  {
    InternalMod tmp;
  
    if (0 == strncasecmp("false", value, 5)) {
      tmp.flag = false;
    } else if (0 == strncasecmp("true", value, 4)) {
      tmp.flag = true;
    } else {
      *error = "Value must be true or false";
    }
  
    if (*error) {
      return nullptr;
    } else {
      return new InternalMod(tmp);
    }
  }
  
  // ----------
  } // namespace
  // ------------------------------------------------
  ControlBase::~ControlBase()
  {
    this->clear();
  }
  
  void
  ControlBase::clear()
  {
    _mods.clear();
  }
  
  // static const modifier_el default_el = { MOD_INVALID, NULL };
  
  void
  ControlBase::Print()
  {
    int n = _mods.size();
  
    if (0 >= n) {
      return;
    }
  
    printf("\t\t\t");
    for (intptr_t i = 0; i < n; ++i) {
      Modifier *cur_mod = _mods[i];
      if (!cur_mod) {
        printf("INVALID  ");
      } else {
        cur_mod->print(stdout);
      }
    }
    printf("\n");
  }
  
  const char *
  ControlBase::getSchemeModText() const
  {
    Modifier *mod = this->findModOfType(Modifier::MOD_SCHEME);
  
    if (mod) {
      return static_cast<SchemeMod *>(mod)->getWksText();
    }
  
    return nullptr;
  }
  
  bool
  ControlBase::CheckModifiers(HttpRequestData *request_data)
  {
    if (!request_data->hdr) {
      // we use the same request_data for Socks as well (only IpMatcher)
      // we just return false here
      return true;
    }
  
    // If the incoming request has no tag but the entry does, or both
    // have tags that do not match, then we do NOT have a match.
    if (!request_data->tag && findModOfType(Modifier::MOD_TAG)) {
      return false;
    }
  
    for (auto &cur_mod : _mods) {
      if (cur_mod && !cur_mod->check(request_data)) {
        return false;
      }
    }
  
    return true;
  }
  
  enum mod_errors {
    ME_UNKNOWN,
    ME_PARSE_FAILED,
    ME_BAD_MOD,
    ME_CALLEE_GENERATED,
  };
  
  static const char *errorFormats[] = {
    "Unknown error parsing modifier",
    "Unable to parse modifier",
    "Unknown modifier",
    "Callee Generated",
  };
  
  ControlBase::Modifier *
  ControlBase::findModOfType(Modifier::Type t) const
  {
    for (auto &m : _mods) {
      if (m && t == m->type()) {
        return m;
      }
    }
  
    return nullptr;
  }
  
  const char *
  ControlBase::ProcessModifiers(matcher_line *line_info)
  {
    // Variables for error processing
    const char *errBuf = nullptr;
    mod_errors err     = ME_UNKNOWN;
  
    int n_elts = line_info->num_el; // Element count for line.
  
    // No elements -> no modifiers.
    if (0 >= n_elts) {
      return nullptr;
    }
    // Can't have more modifiers than elements, so reasonable upper bound.
    _mods.clear();
    _mods.reserve(n_elts);
  
    // As elements are consumed, the labels are nulled out and the element
    // count decremented. So we have to scan the entire array to be sure of
    // finding all the elements. We'll track the element count so we can
    // escape if we've found all of the elements.
    for (int i = 0; n_elts && ME_UNKNOWN == err && i < MATCHER_MAX_TOKENS; ++i) {
      Modifier *mod = nullptr;
  
      char *label = line_info->line[0][i];
      char *value = line_info->line[1][i];
  
      if (!label) {
        continue; // Already use.
      }
      if (!value) {
        err = ME_PARSE_FAILED;
        break;
      }
  
      if (strcasecmp(label, "port") == 0) {
        mod = PortMod::make(value, &errBuf);
      } else if (strcasecmp(label, "iport") == 0) {
        mod = IPortMod::make(value, &errBuf);
      } else if (strcasecmp(label, "scheme") == 0) {
        mod = SchemeMod::make(value, &errBuf);
      } else if (strcasecmp(label, "method") == 0) {
        mod = MethodMod::make(value, &errBuf);
      } else if (strcasecmp(label, "prefix") == 0) {
        mod = PrefixMod::make(value, &errBuf);
      } else if (strcasecmp(label, "suffix") == 0) {
        mod = SuffixMod::make(value, &errBuf);
      } else if (strcasecmp(label, "src_ip") == 0) {
        mod = SrcIPMod::make(value, &errBuf);
      } else if (strcasecmp(label, "time") == 0) {
        mod = TimeMod::make(value, &errBuf);
      } else if (strcasecmp(label, "tag") == 0) {
        mod = TagMod::make(value, &errBuf);
      } else if (strcasecmp(label, "internal") == 0) {
        mod = InternalMod::make(value, &errBuf);
      } else {
        err = ME_BAD_MOD;
      }
  
      if (errBuf) {
        err = ME_CALLEE_GENERATED; // Mod make failed.
      }
  
      // If nothing went wrong, add the mod and bump the element count.
      if (ME_UNKNOWN == err) {
        _mods.push_back(mod);
        --n_elts;
      } else {
        delete mod; // we still need to clean up because we didn't transfer ownership.
      }
    }
  
    if (err != ME_UNKNOWN) {
      this->clear();
      if (err != ME_CALLEE_GENERATED) {
        errBuf = errorFormats[err];
      }
    }
  
    return errBuf;
  }
  