text: /*
    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.
  */
  //////////////////////////////////////////////////////////////////////////////////////////////
  // conditions.cc: Implementation of the condition classes
  //
  //
  #include <sys/time.h>
  #include <unistd.h>
  #include <arpa/inet.h>
  #include <sstream>
  
  #include "ts/ts.h"
  
  #include "conditions.h"
  #include "lulu.h"
  
  // ConditionStatus
  void
  ConditionStatus::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<TSHttpStatus>* match = new Matchers<TSHttpStatus>(_cond_op);
  
    match->set(static_cast<TSHttpStatus>(strtol(p.get_arg().c_str(), NULL, 10)));
    _matcher = match;
  
    require_resources(RSRC_SERVER_RESPONSE_HEADERS);
    require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
    require_resources(RSRC_RESPONSE_STATUS);
  }
  
  
  void
  ConditionStatus::initialize_hooks()
  {
    add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
    add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
  }
  
  
  bool
  ConditionStatus::eval(const Resources& res)
  {
    TSDebug(PLUGIN_NAME, "Evaluating STATUS()"); // TODO: It'd be nice to get the args here ...
    return static_cast<const Matchers<TSHttpStatus>*>(_matcher)->test(res.resp_status);
  }
  
  
  void
  ConditionStatus::append_value(std::string& s, const Resources& res)
  {
    std::ostringstream oss;
    oss << res.resp_status;
    s += oss.str();
    TSDebug(PLUGIN_NAME, "Appending STATUS(%d) to evaluation value -> %s", res.resp_status, s.c_str());
  }
  
  
  // ConditionRandom: random 0 to (N-1)
  void
  ConditionRandom::initialize(Parser& p)
  {
    struct timeval tv;
  
    Condition::initialize(p);
  
    gettimeofday(&tv, NULL);
  
    Matchers<unsigned int>* match = new Matchers<unsigned int>(_cond_op);
    _seed = getpid()* tv.tv_usec;
    _max = strtol(_qualifier.c_str(), NULL, 10);
  
    match->set(static_cast<unsigned int>(strtol(p.get_arg().c_str(), NULL, 10)));
    _matcher = match;
  }
  
  
  bool
  ConditionRandom::eval(const Resources& /* res ATS_UNUSED */)
  {
    TSDebug(PLUGIN_NAME, "Evaluating RANDOM(%d)", _max);
    return static_cast<const Matchers<unsigned int>*>(_matcher)->test(rand_r(&_seed) % _max);
  }
  
  
  void
  ConditionRandom::append_value(std::string& s, const Resources& /* res ATS_UNUSED */)
  {
    std::ostringstream oss;
    oss << rand_r(&_seed) % _max;
    s += oss.str();
    TSDebug(PLUGIN_NAME, "Appending RANDOM(%d) to evaluation value -> %s", _max, s.c_str());
  }
  
  
  // ConditionAccess: access(file)
  void
  ConditionAccess::initialize(Parser& p)
  {
    struct timeval tv;
  
    Condition::initialize(p);
  
    gettimeofday(&tv, NULL);
  
    _next = tv.tv_sec + 2;
    _last = !access(_qualifier.c_str(), R_OK);
  }
  
  
  void
  ConditionAccess::append_value(std::string& s, const Resources& res)
  {
    if (eval(res)) {
      s += "OK";
    } else {
      s += "NOT OK";
    }
  }
  
  
  bool
  ConditionAccess::eval(const Resources& /* res ATS_UNUSED */)
  {
    struct timeval tv;
  
    gettimeofday(&tv, NULL);
  
    TSDebug(PLUGIN_NAME, "Evaluating ACCESS(%s)", _qualifier.c_str());
    if (tv.tv_sec > _next) {
      // There is a small "race" here, where we could end up calling access() a few times extra. I think
      // that is OK, and not worth protecting with a lock.
      bool check = !access(_qualifier.c_str(), R_OK);
  
      tv.tv_sec += 2;
      mb();
      _next = tv.tv_sec; // I hope this is an atomic "set"...
      _last = check; // This sure ought to be
    }
  
    return _last;
  }
  
  
  // ConditionHeader: request or response header
  void
  ConditionHeader::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
  
    _matcher = match;
  
    require_resources(RSRC_CLIENT_REQUEST_HEADERS);
    require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
    require_resources(RSRC_SERVER_REQUEST_HEADERS);
    require_resources(RSRC_SERVER_RESPONSE_HEADERS);
  }
  
  
  void
  ConditionHeader::append_value(std::string& s, const Resources& res)
  {
    TSMBuffer bufp;
    TSMLoc hdr_loc;
    TSMLoc field_loc;
    const char* value;
    int len;
  
    if (_client) {
      bufp = res.client_bufp;
      hdr_loc = res.client_hdr_loc;
    } else {
      bufp = res.bufp;
      hdr_loc = res.hdr_loc;
    }
  
    if (bufp && hdr_loc) {
      field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, _qualifier.c_str(), _qualifier.size());
      TSDebug(PLUGIN_NAME, "Getting Header: %s, field_loc: %p", _qualifier.c_str(), field_loc);
      if (field_loc != NULL) {
        value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &len);
        TSDebug(PLUGIN_NAME, "Appending HEADER(%s) to evaluation value -> %.*s", _qualifier.c_str(), len, value);
        s.append(value, len);
        TSHandleMLocRelease(bufp, hdr_loc, field_loc);
      }
    }
  }
  
  
  bool
  ConditionHeader::eval(const Resources& res)
  {
    std::string s;
  
    append_value(s, res);
    bool rval = static_cast<const Matchers<std::string>*>(_matcher)->test(s);
    TSDebug(PLUGIN_NAME, "Evaluating HEADER(): %s - rval: %d", s.c_str(), rval);
    return rval;
  }
  
  // ConditionPath
  void
  ConditionPath::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
  
    _matcher = match;
  }
  
  void ConditionPath::append_value(std::string& s, const Resources& res) {
    TSMBuffer bufp;
    TSMLoc url_loc;
  
    if (TSHttpTxnPristineUrlGet(res.txnp, &bufp, &url_loc) == TS_SUCCESS) {
      int path_length;
      const char *path = TSUrlPathGet(bufp, url_loc, &path_length);
  
      if (path && path_length)
        s.append(path, path_length);
  
      TSHandleMLocRelease(bufp, TS_NULL_MLOC, url_loc);
    }
  }
  
  bool
  ConditionPath::eval(const Resources& res)
  {
    std::string s;
  
    append_value(s, res);
    TSDebug(PLUGIN_NAME, "Evaluating PATH");
  
    return static_cast<const Matchers<std::string>*>(_matcher)->test(s);
  }
  
  // ConditionQuery
  void
  ConditionQuery::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
    _matcher = match;
  
  }
  
  void
  ConditionQuery::append_value(std::string& s, const Resources& res)
  {
    int query_len = 0;
    const char *query = TSUrlHttpQueryGet(res._rri->requestBufp, res._rri->requestUrl, &query_len);
    TSDebug(PLUGIN_NAME, "Appending QUERY to evaluation value: %.*s", query_len, query);
    s.append(query, query_len);
  }
  
  bool
  ConditionQuery::eval(const Resources& res)
  {
    std::string s;
  
    if (NULL == res._rri) {
      TSDebug(PLUGIN_NAME, "QUERY requires remap initialization! Evaluating to false!");
      return false;
    }
    append_value(s, res);
    TSDebug(PLUGIN_NAME, "Evaluating QUERY - %s", s.c_str());
    return static_cast<const Matchers<std::string>*>(_matcher)->test(s);
  }
  
  
  // ConditionUrl: request or response header. TODO: This is not finished, at all!!!
  void
  ConditionUrl::initialize(Parser& /* p ATS_UNUSED */)
  {
  }
  
  
  void
  ConditionUrl::set_qualifier(const std::string& q)
  {
    Condition::set_qualifier(q);
  
    _url_qual = parse_url_qualifier(q);
  }
  
  
  void
  ConditionUrl::append_value(std::string& /* s ATS_UNUSED */, const Resources& /* res ATS_UNUSED */)
  {
  }
  
  
  bool
  ConditionUrl::eval(const Resources& /* res ATS_UNUSED */)
  {
    bool ret = false;
  
    return ret;
  }
  
  
  // ConditionDBM: do a lookup against a DBM
  void
  ConditionDBM::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
    _matcher = match;
  
    std::string::size_type pos = _qualifier.find_first_of(',');
  
    if (pos != std::string::npos) {
      _file = _qualifier.substr(0, pos);
      //_dbm = mdbm_open(_file.c_str(), O_RDONLY, 0, 0, 0);
      // if (NULL != _dbm) {
      //   TSDebug(PLUGIN_NAME, "Opened DBM file %s\n", _file.c_str());
      //   _key.set_value(_qualifier.substr(pos + 1));
      // } else {
      //   TSError("Failed to open DBM file: %s", _file.c_str());
      // }
    } else {
      TSError("%s: Malformed DBM condition", PLUGIN_NAME);
    }
  }
  
  
  void
  ConditionDBM::append_value(std::string& /* s ATS_UNUSED */, const Resources& /* res ATS_UNUSED */)
  {
    // std::string key;
  
    // if (!_dbm)
    //   return;
  
    // _key.append_value(key, res);
    // if (key.size() > 0) {
    //   datum k, v;
  
    //   TSDebug(PLUGIN_NAME, "Looking up DBM(\"%s\")", key.c_str());
    //   k.dptr = const_cast<char*>(key.c_str());
    //   k.dsize = key.size();
  
    //   TSMutexLock(_mutex);
    //   //v = mdbm_fetch(_dbm, k);
    //   TSMutexUnlock(_mutex);
    //   if (v.dsize > 0) {
    //     TSDebug(PLUGIN_NAME, "Appending DBM(%.*s) to evaluation value -> %.*s", k.dsize, k.dptr, v.dsize, v.dptr);
    //     s.append(v.dptr, v.dsize);
    //   }
    // }
  }
  
  
  bool
  ConditionDBM::eval(const Resources& res)
  {
    std::string s;
  
    append_value(s, res);
    TSDebug(PLUGIN_NAME, "Evaluating DBM(%s, \"%s\")", _file.c_str(), s.c_str());
  
    return static_cast<const Matchers<std::string>*>(_matcher)->test(s);
  }
  
  
  // ConditionCookie: request or response header
  void
  ConditionCookie::initialize(Parser& p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
  
    _matcher = match;
  
    require_resources(RSRC_CLIENT_REQUEST_HEADERS);
  }
  
  void
  ConditionCookie::append_value(std::string& s, const Resources& res)
  {
    TSMBuffer bufp = res.client_bufp;
    TSMLoc hdr_loc = res.client_hdr_loc;
    TSMLoc field_loc;
    int error;
    int cookies_len;
    int cookie_value_len;
    const char *cookies;
    const char *cookie_value;
    const char * const cookie_name = _qualifier.c_str();
    const int cookie_name_len = _qualifier.length();
  
    // Sanity
    if (bufp == NULL || hdr_loc == NULL)
      return;
  
    // Find Cookie
    field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE);
    if (field_loc == NULL)
      return;
  
    // Get all cookies
    cookies = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &cookies_len);
    if (cookies == NULL || cookies_len <= 0)
      goto out_release_field;
  
    // Find particular cookie's value
    error = get_cookie_value(cookies, cookies_len, cookie_name, cookie_name_len, &cookie_value, &cookie_value_len);
    if (error == TS_ERROR)
      goto out_release_field;
  
    TSDebug(PLUGIN_NAME, "Appending COOKIE(%s) to evaluation value -> %.*s", cookie_name, cookie_value_len, cookie_value);
    s.append(cookie_value, cookie_value_len);
  
    // Unwind
  out_release_field:
    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
  }
  
  bool
  ConditionCookie::eval(const Resources& res)
  {
    std::string s;
  
    append_value(s, res);
    bool rval = static_cast<const Matchers<std::string>*>(_matcher)->test(s);
    TSDebug(PLUGIN_NAME, "Evaluating COOKIE(%s): %s: rval: %d", _qualifier.c_str(), s.c_str(), rval);
    return rval;
  }
  
  bool
  ConditionInternalTransaction::eval(const Resources& res)
  {
    return TSHttpIsInternalRequest(res.txnp) == TS_SUCCESS;
  }
  
  void
  ConditionClientIp::initialize(Parser &p)
  {
    Condition::initialize(p);
  
    Matchers<std::string>* match = new Matchers<std::string>(_cond_op);
    match->set(p.get_arg());
  
    _matcher = match;
  }
  
  bool
  ConditionClientIp::eval(const Resources &res)
  {
    std::string s;
  
    append_value(s, res);
    bool rval = static_cast<const Matchers<std::string>*>(_matcher)->test(s);
    TSDebug(PLUGIN_NAME, "Evaluating CLIENT-IP(): %s: rval: %d", s.c_str(), rval);
    return rval;
  }
  
  void
  ConditionClientIp::append_value(std::string &s, const Resources &res)
  {
    char ip[INET6_ADDRSTRLEN];
  
    if (getIP(TSHttpTxnClientAddrGet(res.txnp), ip)) {
      s.append(ip);
    }
  }
  
  void
  ConditionIncomingPort::initialize(Parser &p)
  {
    Condition::initialize(p);
  
    Matchers<uint16_t>* match = new Matchers<uint16_t>(_cond_op);
    match->set(static_cast<uint16_t>(strtoul(p.get_arg().c_str(), NULL, 10)));
    _matcher = match;
  }
  
  bool
  ConditionIncomingPort::eval(const Resources &res)
  {
    uint16_t port = getPort(TSHttpTxnIncomingAddrGet(res.txnp));
    bool rval = static_cast<const Matchers<uint16_t>*>(_matcher)->test(port);
    TSDebug(PLUGIN_NAME, "Evaluating INCOMING-PORT(): %d: rval: %d", port, rval);
    return rval;
  }
  
  void
  ConditionIncomingPort::append_value(std::string &s, const Resources &res)
  {
    std::ostringstream oss;
    uint16_t port = getPort(TSHttpTxnIncomingAddrGet(res.txnp));
    oss << port;
    s += oss.str();
    TSDebug(PLUGIN_NAME, "Appending %d to evaluation value -> %s", port, s.c_str());
  }
  