/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
#include "curlcheck.h"

#include "urldata.h"
#include "hsts.h"

static CURLcode
unit_setup(void)
{
  return CURLE_OK;
}

static void
unit_stop(void)
{
  curl_global_cleanup();
}

#if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_HSTS)
UNITTEST_START
{
  return 0; /* nothing to do when HTTP or HSTS are disabled */
}
UNITTEST_STOP
#else

struct testit {
  const char *host;
  const char *chost; /* if non-NULL, use to lookup with */
  const char *hdr; /* if NULL, just do the lookup */
  const CURLcode result; /* parse result */
};

static const struct testit headers[] = {
  /* two entries read from disk cache, verify first */
  { "-", "readfrom.example", NULL, CURLE_OK},
  { "-", "old.example", NULL, CURLE_OK},
  /* delete the remaining one read from disk */
  { "readfrom.example", NULL, "max-age=\"0\"", CURLE_OK},

  { "example.com", NULL, "max-age=\"31536000\"\r\n", CURLE_OK },
  { "example.com", NULL, "max-age=\"21536000\"\r\n", CURLE_OK },
  { "example.com", NULL, "max-age=\"21536000\"; \r\n", CURLE_OK },
  { "example.com", NULL, "max-age=\"21536000\"; includeSubDomains\r\n",
    CURLE_OK },
  { "example.org", NULL, "max-age=\"31536000\"\r\n", CURLE_OK },
  { "this.example", NULL, "max=\"31536\";", CURLE_BAD_FUNCTION_ARGUMENT },
  { "this.example", NULL, "max-age=\"31536", CURLE_BAD_FUNCTION_ARGUMENT },
  { "this.example", NULL, "max-age=31536\"", CURLE_OK },
  /* max-age=0 removes the entry */
  { "this.example", NULL, "max-age=0", CURLE_OK },
  { "another.example", NULL, "includeSubDomains; ",
    CURLE_BAD_FUNCTION_ARGUMENT },

  /* Two max-age is illegal */
  { "example.com", NULL,
    "max-age=\"21536000\"; includeSubDomains; max-age=\"3\";",
    CURLE_BAD_FUNCTION_ARGUMENT },
  /* Two includeSubDomains is illegal */
  { "2.example.com", NULL,
    "max-age=\"21536000\"; includeSubDomains; includeSubDomains;",
    CURLE_BAD_FUNCTION_ARGUMENT },
  /* use a unknown directive "include" that should be ignored */
  { "3.example.com", NULL, "max-age=\"21536000\"; include; includeSubDomains;",
    CURLE_OK },
  /* remove the "3.example.com" one, should still match the example.com */
  { "3.example.com", NULL, "max-age=\"0\"; includeSubDomains;",
    CURLE_OK },
  { "-", "foo.example.com", NULL, CURLE_OK},
  { "-", "foo.xample.com", NULL, CURLE_OK},

  /* should not match */
  { "example.net", "forexample.net", "max-age=\"31536000\"\r\n", CURLE_OK },

  /* should not match either, since forexample.net is not in the example.net
     domain */
  { "example.net", "forexample.net",
    "max-age=\"31536000\"; includeSubDomains\r\n", CURLE_OK },
  /* remove example.net again */
  { "example.net", NULL, "max-age=\"0\"; includeSubDomains\r\n", CURLE_OK },

  /* make this live for 7 seconds */
  { "expire.example", NULL, "max-age=\"7\"\r\n", CURLE_OK },
  { NULL, NULL, NULL, CURLE_OK }
};

static void showsts(struct stsentry *e, const char *chost)
{
  if(!e)
    printf("'%s' is not HSTS\n", chost);
  else {
    printf("%s [%s]: %" CURL_FORMAT_CURL_OFF_T "%s\n",
           chost, e->host, e->expires,
           e->includeSubDomains ? " includeSubDomains" : "");
  }
}

UNITTEST_START
{
  CURLcode result;
  struct stsentry *e;
  struct hsts *h = Curl_hsts_init();
  int i;
  const char *chost;
  CURL *easy;
  if(!h)
    return 1;

  curl_global_init(CURL_GLOBAL_ALL);
  easy = curl_easy_init();
  if(!easy) {
    Curl_hsts_cleanup(&h);
    curl_global_cleanup();
    return 1;
  }

  Curl_hsts_loadfile(easy, h, "log/input1660");

  for(i = 0; headers[i].host ; i++) {
    if(headers[i].hdr) {
      result = Curl_hsts_parse(h, headers[i].host, headers[i].hdr);

      if(result != headers[i].result) {
        fprintf(stderr, "Curl_hsts_parse(%s) failed: %d\n",
                headers[i].hdr, result);
        unitfail++;
        continue;
      }
      else if(result) {
        printf("Input %u: error %d\n", i, (int) result);
        continue;
      }
    }

    chost = headers[i].chost ? headers[i].chost : headers[i].host;
    e = Curl_hsts(h, chost, TRUE);
    showsts(e, chost);
  }

  printf("Number of entries: %zu\n", h->list.size);

  /* verify that it is exists for 7 seconds */
  chost = "expire.example";
  for(i = 100; i < 110; i++) {
    e = Curl_hsts(h, chost, TRUE);
    showsts(e, chost);
    deltatime++; /* another second passed */
  }

  (void)Curl_hsts_save(easy, h, "log/hsts1660");
  Curl_hsts_cleanup(&h);
  curl_easy_cleanup(easy);
  curl_global_cleanup();
  return unitfail;
}
UNITTEST_STOP
#endif
