/*
  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 "cookiejar.h"
#include "strip.h"
#include <cstring>

/* allowed cookie-name definition from RFC
 * cookie-name       = token
 * token             = <token, defined in [RFC2616], Section 2.2>
 * token             = 1*<any CHAR except CTLs or separators>
 * separators        = "(" | ")" | "<" | ">" | "@"
 *                   | "," | ";" | ":" | "\" | <">
 *                   | "/" | "[" | "]" | "?" | "="
 *                   | "{" | "}" | SP | HT
 * CTL               = <any US-ASCII control character
 *                     (octets 0 - 31) and DEL (127)>
 * SP                = <US-ASCII SP, space (32)>
 * HT                = <US-ASCII HT, horizontal-tab (9)>
 */

static const int rfc_cookie_name_table[256] = {
  /* 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F            */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00-0F */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10-1F */
  0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, /* 20-2F */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 30-3F */
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40-4F */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, /* 50-5F */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60-6F */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, /* 70-7F */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80-8F */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90-9F */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0-AF */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0-BF */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0-CF */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0-DF */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0-EF */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  /* F0-FF */
};

bool
CookieJar::create(const string &strCookie)
{
  if (strCookie.empty()) {
    return false;
  }

  if (parse(strCookie, "; ", true, true) != 0) {
    return false;
  }

  return true;
}

int
CookieJar::parse(const string &arg, const char *sepstr, bool val_check, bool mainElement)
{
  char *arg_copy;
  char *cp;
  char *key;

  if ((arg_copy = strdup(arg.c_str())) == nullptr) {
    return -1;
  }

  cp           = arg_copy;
  char empty[] = "";
  for (key = strsep(&cp, sepstr); key != nullptr; key = strsep(&cp, sepstr)) {
    char *val   = strchr(key, '=');
    char *addme = nullptr;

    if (val) {
      /* split key and value */
      *val++      = '\0';
      int val_len = strlen(val);
      if (val_len > 0) {
        /* if we have DQUOTES around our value then drop them */

        if (val_len > 1 && val[0] == '"' && val[val_len - 1] == '"') {
          val[val_len - 1] = '\0';
          addme            = val + 1;

          /* update the value length accordingly */

          val_len -= 2;
        } else {
          addme = val; /* We have got a valid value eg: "YL=a" */
        }

        /* verify that the value is valid according to our configured
         * option and possibly strip out invalid characters. */

        if (val_check && verify_value(addme, val_len) != 0) {
          continue;
        }
      } else {
        // Empty cookie case eg: "L="
        addme = empty;
      }
    } else /* If val is nullptr, then no need to add this kv pair to hashtable, skip this key */
    {
      continue;
    }

    /* we are going to verify the key name for our top level
     * cookie names only so we'll use the val_check variables
     * to know what we're processing */

    if (val_check && verify_name(key) != 0) {
      continue;
    }

    if (mainElement) {
      addElement(key, addme);
    } else {
      addSubElement(key, addme);
    }
  }

  free(arg_copy);
  return 0;
}

void
CookieJar::addElement(const char *key, const char *val)
{
  /* The insert method avoids duplicates */
  CookieVal cval;
  cval.m_val = val;
  m_jar.insert(std::make_pair(key, cval));
}

void
CookieJar::addSubElement(const char *key, const char *val)
{
  /* The insert method avoids duplicates */
  m_currentVal->m_subelements.insert(std::make_pair(key, val));
}

int
CookieJar::verify_value(char *val, int val_len)
{
  char *buf;
  char *data_ptr       = nullptr;
  char  data_buf[1024] = {
    0,
  };
  int buf_len;

  if (val_len > static_cast<int>(sizeof(data_buf) - 1)) {
    buf_len = val_len + 1;
    if ((data_ptr = static_cast<char *>(malloc(buf_len))) == nullptr) {
      return -1;
    }

    buf = data_ptr;
  } else {
    buf     = data_buf;
    buf_len = sizeof(data_buf);
  }

  if (get_stripped(val, val_len, buf, &buf_len, 0) != STRIP_RESULT_OK) {
    if (data_ptr) {
      free(data_ptr);
    }
    return -1;
  }

  /* returned buf_len includes the null terminator
   * so we need to verify that somehow we didn't end up
   * with a bigger buffer than what we started with */

  if (buf_len > val_len + 1) {
    if (data_ptr) {
      free(data_ptr);
    }
    return -1;
  }

  memcpy(val, buf, buf_len);
  if (data_ptr) {
    free(data_ptr);
  }

  return 0;
}

int
CookieJar::verify_name(char *name)
{
  char *p;

  for (p = name; *p; p++) {
    /* if we get any invalid characters then return failure
     * in order to skip this cookie completely */

    if (rfc_cookie_name_table[static_cast<int>(*p)] == 0) {
      return -1;
    }
  }

  return 0;
}

bool
CookieJar::get_full(const string &cookie_name, string &val)
{
  if (m_jar.find(cookie_name) != m_jar.end()) {
    val = m_jar[cookie_name].m_val;
    return true;
  }
  return false;
}

bool
CookieJar::get_part(const string &cookie_name, const string &part_name, string &val)
{
  if (m_jar.empty()) {
    return false;
  }

  if (m_jar.find(cookie_name) == m_jar.end()) {
    /* full cookie not found */
    return false;
  }

  CookieVal &fe = m_jar[cookie_name];
  /* check if we need to do lazy evaluation */
  if (fe.parts_inited == false) {
    /* since we already validated the value for the full cookie
     * there is no need to validate the components again so
     * we'll be passing 0/false for the val_check argument */

    m_currentVal = &fe;
    if (parse(fe.m_val, "&", false, false) != 0) {
      return false;
    }

    fe.parts_inited = true;
    m_currentVal    = nullptr;
  }

  if (fe.m_subelements.find(part_name) == fe.m_subelements.end()) {
    return false; /* not found */
  }

  val = fe.m_subelements[part_name];
  return true;
}
