/** @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.
 */

/****************************************************************************

   HdrTest.cc

   Description:
       Unit test code for sanity checking the header system is functioning
         properly


 ****************************************************************************/

#include "tscore/ink_platform.h"
#include "tscore/ink_memory.h"
#include "tscore/ink_time.h"

#include "tscore/Arena.h"
#include "HTTP.h"
#include "MIME.h"
#include "tscore/Regex.h"
#include "URL.h"
#include "HttpCompat.h"

#include "HdrTest.h"

//////////////////////
// Main Test Driver //
//////////////////////

int
HdrTest::go(RegressionTest *t, int /* atype ATS_UNUSED */)
{
  HdrTest::rtest = t;
  int status     = 1;

  hdrtoken_init();
  url_init();
  mime_init();
  http_init();

  status = status & test_error_page_selection();
  status = status & test_http_hdr_print_and_copy();
  status = status & test_comma_vals();
  status = status & test_parse_comma_list();
  status = status & test_set_comma_vals();
  status = status & test_delete_comma_vals();
  status = status & test_extend_comma_vals();
  status = status & test_insert_comma_vals();
  status = status & test_accept_language_match();
  status = status & test_accept_charset_match();
  status = status & test_parse_date();
  status = status & test_format_date();
  status = status & test_url();
  status = status & test_arena();
  status = status & test_regex();
  status = status & test_http_parser_eos_boundary_cases();
  status = status & test_http_mutation();
  status = status & test_mime();
  status = status & test_http();

  return (status ? REGRESSION_TEST_PASSED : REGRESSION_TEST_FAILED);
}

////////////////////////////////////////////////////////////
// Individual Tests --- return 1 on success, 0 on failure //
////////////////////////////////////////////////////////////

int
HdrTest::test_error_page_selection()
{
  static struct {
    const char *set_name;
    const char *content_language;
    const char *content_charset;
  } sets[] = {{"default", "en", "iso-8859-1"}, {"en-cockney", "en-cockney", "iso-8859-1"},
              {"en", "en", "iso-8859-1"},      {"en-us", "en-us", "us-ascii"},
              {"en", "en", "unicode"},         {"en-cockney-slang", "en-cockney-slang", "iso-8859-1"},
              {"ko", "ko", "iso-8859-1"},      {"ko", "ko", "iso-2022-kr"},
              {"jp", "jp", "shift-jis"}};

  static struct {
    const char *accept_language;
    const char *accept_charset;
    const char *expected_set;
    float expected_Q;
    int expected_La;
    int expected_I;
  } tests[] = {
    {nullptr, nullptr, "default", 1, 0, INT_MAX},
    {"en", nullptr, "en", 1, 2, 1},
    {"ko", nullptr, "ko", 1, 2, 1},
    {"en-us", nullptr, "en-us", 1, 5, 1},
    {"en-US", nullptr, "en-us", 1, 5, 1},
    {"en,ko", nullptr, "en", 1, 2, 1},
    {"ko,en", nullptr, "ko", 1, 2, 1},
    {"en;q=0.7,ko", nullptr, "ko", 1, 2, 2},
    {"en;q=.7,ko", nullptr, "ko", 1, 2, 2},
    {"en;q=.7,ko;q=.7", nullptr, "en", 0.7, 2, 1},
    {"en;q=.7,ko;q=.701", nullptr, "ko", 0.701, 2, 2},
    {"en;q=.7  ,  ko;q=.701", nullptr, "ko", 0.701, 2, 2},
    {"en  ;  q=.7  ,  ko  ;  ;  ;  ; q=.701", nullptr, "ko", 0.701, 2, 2},
    {"en,ko;q=.7", nullptr, "en", 1, 2, 1},
    {"en;q=1,ko;q=.7", nullptr, "en", 1, 2, 1},
    {"en;;;q=1,ko;q=.7", nullptr, "en", 1, 2, 1},
    {"en;;;q=1,,,,ko;q=.7", nullptr, "en", 1, 2, 1},
    {"en;;;q=.7,,,,ko;q=.7", nullptr, "en", 0.7, 2, 1},
    {"en;;;q=.699,,,,ko;q=.7", nullptr, "ko", 0.7, 2, 5},
    {"en;q=0,ko;q=1", nullptr, "ko", 1, 2, 2},
    {"en;q=0, ko;q=1", nullptr, "ko", 1, 2, 2},
    {"en;q=0,ko;q=.5", nullptr, "ko", 0.5, 2, 2},
    {"en;q=0, ko;q=.5", nullptr, "ko", 0.5, 2, 2},
    {"en;q=000000000.00000000000000000000,ko;q=1.0000000000000000000", nullptr, "ko", 1, 2, 2},
  };

  bri_box("test_error_page_selection");

  int i;
  int failures = 0;

  int nsets  = sizeof(sets) / sizeof(sets[0]);
  int ntests = sizeof(tests) / sizeof(tests[0]);

  // (1) build fake hash table of sets

  RawHashTable *table_of_sets = new RawHashTable(RawHashTable_KeyType_String);

  for (i = 0; i < nsets; i++) {
    HttpBodySetRawData *body_set;

    body_set                   = (HttpBodySetRawData *)ats_malloc(sizeof(HttpBodySetRawData));
    body_set->magic            = 0;
    body_set->set_name         = (char *)(sets[i].set_name);
    body_set->content_language = (char *)(sets[i].content_language);
    body_set->content_charset  = (char *)(sets[i].content_charset);
    body_set->table_of_pages   = (RawHashTable *)1; // hack --- can't be NULL

    table_of_sets->setValue((RawHashTable_Key)(body_set->set_name), (RawHashTable_Value)body_set);
  }

  // (2) for each test, parse accept headers into lists, and test matching

  for (i = 0; i < ntests; i++) {
    float Q_best;
    int La_best, Lc_best, I_best;
    const char *set_best;

    StrList accept_language_list;
    StrList accept_charset_list;

    HttpCompat::parse_comma_list(&accept_language_list, tests[i].accept_language);
    HttpCompat::parse_comma_list(&accept_charset_list, tests[i].accept_charset);

    printf("         test #%d: (Accept-Language='%s', Accept-Charset='%s')\n", i + 1,
           (tests[i].accept_language ? tests[i].accept_language : "<null>"),
           (tests[i].accept_charset ? tests[i].accept_charset : "<null>"));

    set_best = HttpCompat::determine_set_by_language(table_of_sets, &(accept_language_list), &(accept_charset_list), &Q_best,
                                                     &La_best, &Lc_best, &I_best);

    if ((strcmp(set_best, tests[i].expected_set) == 0) && (Q_best == tests[i].expected_Q) && (La_best == tests[i].expected_La) &&
        (I_best == tests[i].expected_I)) {
      printf("SUCCESS: test #%d expected [ S='%s', Q=%g, La=%d, I=%d ] got [ S='%s', Q=%g, La=%d, I=%d ]\n", i + 1,
             tests[i].expected_set, tests[i].expected_Q, tests[i].expected_La, tests[i].expected_I, set_best, Q_best, La_best,
             I_best);
    } else {
      ++failures;
      printf(" FAILED: test #%d expected [ S='%s', Q=%g, La=%d, I=%d ] got [ S='%s', Q=%g, La=%d, I=%d ]\n", i + 1,
             tests[i].expected_set, tests[i].expected_Q, tests[i].expected_La, tests[i].expected_I, set_best, Q_best, La_best,
             I_best);
    }
  }

  delete table_of_sets;
  return (failures_to_status("test_error_page_selection", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_parse_date()
{
  static struct {
    const char *fast;
    const char *slow;
  } dates[] = {
    {"Sun, 06 Nov 1994 08:49:37 GMT", "Sunday, 06-Nov-1994 08:49:37 GMT"},
    {"Mon, 07 Nov 1994 08:49:37 GMT", "Monday, 07-Nov-1994 08:49:37 GMT"},
    {"Tue, 08 Nov 1994 08:49:37 GMT", "Tuesday, 08-Nov-1994 08:49:37 GMT"},
    {"Wed, 09 Nov 1994 08:49:37 GMT", "Wednesday, 09-Nov-1994 08:49:37 GMT"},
    {"Thu, 10 Nov 1994 08:49:37 GMT", "Thursday, 10-Nov-1994 08:49:37 GMT"},
    {"Fri, 11 Nov 1994 08:49:37 GMT", "Friday, 11-Nov-1994 08:49:37 GMT"},
    {"Sat, 11 Nov 1994 08:49:37 GMT", "Saturday, 11-Nov-1994 08:49:37 GMT"},
    {"Sun, 03 Jan 1999 08:49:37 GMT", "Sunday, 03-Jan-1999 08:49:37 GMT"},
    {"Sun, 07 Feb 1999 08:49:37 GMT", "Sunday, 07-Feb-1999 08:49:37 GMT"},
    {"Sun, 07 Mar 1999 08:49:37 GMT", "Sunday, 07-Mar-1999 08:49:37 GMT"},
    {"Sun, 04 Apr 1999 08:49:37 GMT", "Sunday, 04-Apr-1999 08:49:37 GMT"},
    {"Sun, 02 May 1999 08:49:37 GMT", "Sunday, 02-May-1999 08:49:37 GMT"},
    {"Sun, 06 Jun 1999 08:49:37 GMT", "Sunday, 06-Jun-1999 08:49:37 GMT"},
    {"Sun, 04 Jul 1999 08:49:37 GMT", "Sunday, 04-Jul-1999 08:49:37 GMT"},
    {"Sun, 01 Aug 1999 08:49:37 GMT", "Sunday, 01-Aug-1999 08:49:37 GMT"},
    {"Sun, 05 Sep 1999 08:49:37 GMT", "Sunday, 05-Sep-1999 08:49:37 GMT"},
    {"Sun, 03 Oct 1999 08:49:37 GMT", "Sunday, 03-Oct-1999 08:49:37 GMT"},
    {"Sun, 07 Nov 1999 08:49:37 GMT", "Sunday, 07-Nov-1999 08:49:37 GMT"},
    {"Sun, 05 Dec 1999 08:49:37 GMT", "Sunday, 05-Dec-1999 08:49:37 GMT"},
    {nullptr, nullptr},
  };

  int i;
  int failures = 0;
  time_t fast_t, slow_t;

  bri_box("test_parse_date");

  for (i = 0; dates[i].fast; i++) {
    fast_t = mime_parse_date(dates[i].fast, dates[i].fast + (int)strlen(dates[i].fast));
    slow_t = mime_parse_date(dates[i].slow, dates[i].slow + (int)strlen(dates[i].slow));
    // compare with strptime here!
    if (fast_t != slow_t) {
      printf("FAILED: date %lu (%s) != %lu (%s)\n", (unsigned long)fast_t, dates[i].fast, (unsigned long)slow_t, dates[i].slow);
      ++failures;
    }
  }

  return (failures_to_status("test_parse_date", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_format_date()
{
  static const char *dates[] = {
    "Sun, 06 Nov 1994 08:49:37 GMT",
    "Sun, 03 Jan 1999 08:49:37 GMT",
    "Sun, 05 Dec 1999 08:49:37 GMT",
    "Tue, 25 Apr 2000 20:29:53 GMT",
    nullptr,
  };

  bri_box("test_format_date");

  // (1) Test a few hand-created dates

  int i;
  time_t t, t2, t3;
  char buffer[128], buffer2[128];
  static const char *envstr = "TZ=GMT0";
  int failures              = 0;

  // shift into GMT timezone for cftime conversions
  putenv((char *)envstr);
  tzset();

  for (i = 0; dates[i]; i++) {
    t = mime_parse_date(dates[i], dates[i] + (int)strlen(dates[i]));

    cftime_replacement(buffer, sizeof(buffer), "%a, %d %b %Y %T %Z", &t);
    if (memcmp(dates[i], buffer, 29) != 0) {
      printf("FAILED: original date doesn't match cftime date\n");
      printf("  input date:  %s\n", dates[i]);
      printf("  cftime date: %s\n", buffer);
      ++failures;
    }

    mime_format_date(buffer, t);
    if (memcmp(dates[i], buffer, 29) != 0) {
      printf("FAILED: original date doesn't match mime_format_date date\n");
      printf("  input date:  %s\n", dates[i]);
      printf("  cftime date: %s\n", buffer);
      ++failures;
    }
  }

  // (2) test a few times per day from 1/1/1970 to past 2010

  // coverity[dont_call]
  for (t = 0; t < 40 * 366 * (24 * 60 * 60); t += (int)(drand48() * (24 * 60 * 60))) {
    cftime_replacement(buffer, sizeof(buffer), "%a, %d %b %Y %T %Z", &t);
    t2 = mime_parse_date(buffer, buffer + (int)strlen(buffer));
    if (t2 != t) {
      printf("FAILED: parsed time_t doesn't match original time_t\n");
      printf("  input time_t:  %d (%s)\n", (int)t, buffer);
      printf("  parsed time_t: %d\n", (int)t2);
      ++failures;
    }
    mime_format_date(buffer2, t);
    if (memcmp(buffer, buffer2, 29) != 0) {
      printf("FAILED: formatted date doesn't match original date\n");
      printf("  original date:  %s\n", buffer);
      printf("  formatted date: %s\n", buffer2);
      ++failures;
    }
    t3 = mime_parse_date(buffer2, buffer2 + (int)strlen(buffer2));
    if (t != t3) {
      printf("FAILED: parsed time_t doesn't match original time_t\n");
      printf("  input time_t:  %d (%s)\n", (int)t, buffer2);
      printf("  parsed time_t: %d\n", (int)t3);
      ++failures;
    }

    if (failures > 20) {
      // Already too many failures, don't need to continue
      break;
    }
  }

  return (failures_to_status("test_format_date", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_url()
{
  static const char *strs[] = {
    "http://some.place/path;params?query#fragment",

    // Start with an easy one...
    "http://trafficserver.apache.org/index.html",

    "cheese://bogosity",

    "some.place",
    "some.place/",
    "http://some.place",
    "http://some.place/",
    "http://some.place/path",
    "http://some.place/path;params",
    "http://some.place/path;params?query",
    "http://some.place/path;params?query#fragment",
    "http://some.place/path?query#fragment",
    "http://some.place/path#fragment",

    "some.place:80",
    "some.place:80/",
    "http://some.place:80",
    "http://some.place:80/",

    "foo@some.place:80",
    "foo@some.place:80/",
    "http://foo@some.place:80",
    "http://foo@some.place:80/",

    "foo:bar@some.place:80",
    "foo:bar@some.place:80/",
    "http://foo:bar@some.place:80",
    "http://foo:bar@some.place:80/",

    // Some address stuff
    "http://172.16.28.101",
    "http://172.16.28.101:8080",
    "http://[::]",
    "http://[::1]",
    "http://[fc01:172:16:28::101]",
    "http://[fc01:172:16:28::101]:80",
    "http://[fc01:172:16:28:BAAD:BEEF:DEAD:101]",
    "http://[fc01:172:16:28:BAAD:BEEF:DEAD:101]:8080",
    "http://172.16.28.101/some/path",
    "http://172.16.28.101:8080/some/path",
    "http://[::1]/some/path",
    "http://[fc01:172:16:28::101]/some/path",
    "http://[fc01:172:16:28::101]:80/some/path",
    "http://[fc01:172:16:28:BAAD:BEEF:DEAD:101]/some/path",
    "http://[fc01:172:16:28:BAAD:BEEF:DEAD:101]:8080/some/path",
    "http://172.16.28.101/",
    "http://[fc01:172:16:28:BAAD:BEEF:DEAD:101]:8080/",

    // "foo:@some.place", TODO - foo:@some.place is change to foo@some.place in the test
    "foo:bar@some.place",
    "foo:bar@some.place/",
    "http://foo:bar@some.place",
    "http://foo:bar@some.place/",
    "http://foo:bar@[::1]:8080/",
    "http://foo@[::1]",

    "mms://sm02.tsqa.example.com/0102rally.asf",
    "pnm://foo:bar@some.place:80/path;params?query#fragment",
    "rtsp://foo:bar@some.place:80/path;params?query#fragment",
    "rtspu://foo:bar@some.place:80/path;params?query#fragment",
    "/finance/external/cbsm/*http://cbs.marketwatch.com/archive/19990713/news/current/net.htx?source=blq/yhoo&dist=yhoo",
    "http://a.b.com/xx.jpg?newpath=http://bob.dave.com",

    "ht-tp://a.b.com",
    "ht+tp://a.b.com",
    "ht.tp://a.b.com",

    "h1ttp://a.b.com",
    "http1://a.b.com",
  };

  static const char *bad[] = {
    "http://[1:2:3:4:5:6:7:8:9]",
    "http://1:2:3:4:5:6:7:8:A:B",
    "http://bob.com[::1]",
    "http://[::1].com",

    "http://foo:bar:baz@bob.com/",
    "http://foo:bar:baz@[::1]:8080/",

    "http://]",
    "http://:",

    "http:/",
    "http:/foo.bar.com/",
    "~http://invalid.char.in.scheme/foo",
    "http~://invalid.char.in.scheme/foo",
    "ht~tp://invalid.char.in.scheme/foo",
    "1http://first.char.not.alpha",
    "some.domain.com/http://invalid.domain/foo",
    ":",
    "://",

    // maybe this should be a valid URL
    "a.b.com/xx.jpg?newpath=http://bob.dave.com",
  };

  int err, failed;
  URL url;
  const char *start;
  const char *end;
  int old_length, new_length;

  bri_box("test_url");

  failed = 0;
  for (unsigned i = 0; i < countof(strs); i++) {
    old_length = (int)strlen(strs[i]);
    start      = strs[i];
    end        = start + old_length;

    url.create(nullptr);
    err = url.parse(&start, end);
    if (err < 0) {
      printf("Failed to parse url '%s'\n", start);
      failed = 1;
      break;
    }

    char print_buf[1024];
    new_length = 0;
    int offset = 0;
    url.print(print_buf, 1024, &new_length, &offset);
    print_buf[new_length] = '\0';

    const char *fail_text = nullptr;

    if (old_length == new_length) {
      if (memcmp(print_buf, strs[i], new_length) != 0) {
        fail_text = "URLS DIFFER";
      }
    } else if (old_length == new_length - 1) {
      // Check to see if the difference is the trailing
      //   slash we add
      if (memcmp(print_buf, strs[i], old_length) != 0 || print_buf[new_length - 1] != '/' || (strs[i])[old_length - 1] == '/') {
        fail_text = "TRAILING SLASH";
      }
    } else {
      fail_text = "LENGTHS DIFFER";
    }

    if (fail_text) {
      failed = 1;
      printf("%16s: OLD: (%4d) %s\n", fail_text, old_length, strs[i]);
      printf("%16s: NEW: (%4d) %s\n", "", new_length, print_buf);
      obj_describe(url.m_url_impl, true);
    } else {
      printf("%16s: '%s'\n", "PARSE SUCCESS", strs[i]);
    }

    url.destroy();
  }

  for (unsigned i = 0; i < countof(bad); ++i) {
    const char *x = bad[i];

    url.create(nullptr);
    err = url.parse(x, strlen(x));
    url.destroy();
    if (err == PARSE_RESULT_DONE) {
      failed = 1;
      printf("Successfully parsed invalid url '%s'", x);
      break;
    } else {
      printf("   bad URL - PARSE FAILED: '%s'\n", bad[i]);
    }
  }

#if 0
  if (!failed) {
    Note("URL performance test start");
    for (int j = 0 ; j < 100000 ; ++j) {
      for (i = 0 ; i < countof(strs) ; ++i) {
        const char* x = strs[i];
        url.create(NULL);
        err = url.parse(x, strlen(x));
        url.destroy();
      }
    }
    Note("URL performance test end");
  }
#endif

  return (failures_to_status("test_url", failed));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_mime()
{
  // This can not be a static string (any more) since we unfold the headers
  // in place.
  char mime[] = {
    //        "Date: Tuesday, 08-Dec-98 20:32:17 GMT\r\n"
    "Date: 6 Nov 1994 08:49:37 GMT\r\n"
    "Max-Forwards: 65535\r\n"
    "Cache-Control: private\r\n"
    "accept: foo\r\n"
    "accept: bar\n"
    ": (null) field name\r\n"
    "aCCept: \n"
    "ACCEPT\r\n"
    "foo: bar\r\n"
    "foo: argh\r\n"
    "foo: three, four\r\n"
    "word word: word \r\n"
    "accept: \"fazzle, dazzle\"\r\n"
    "accept: 1, 2, 3, 4, 5, 6, 7, 8\r\n"
    "continuation: part1\r\n"
    " part2\r\n"
    "scooby: doo\r\n"
    " scooby: doo\r\n"
    "bar: foo\r\n"
    "\r\n",
  };

  int err;
  MIMEHdr hdr;
  MIMEParser parser;
  const char *start;
  const char *end;

  bri_box("test_mime");

  printf("   <<< MUST BE HAND-VERIFIED FOR FULL-BENEFIT>>>\n\n");

  start = mime;
  end   = start + strlen(start);

  mime_parser_init(&parser);

  bool must_copy_strs = false;

  hdr.create(nullptr);
  err = hdr.parse(&parser, &start, end, must_copy_strs, false);

  if (err < 0) {
    return (failures_to_status("test_mime", 1));
  }

  // Test the (new) continuation line folding to be correct. This should replace the
  // \r\n with two spaces (so a total of three between "part1" and "part2").
  int length               = 0;
  const char *continuation = hdr.value_get("continuation", 12, &length);

  if ((13 != length)) {
    printf("FAILED: continue header folded line was too short\n");
    return (failures_to_status("test_mime", 1));
  }

  if (strncmp(continuation + 5, "   ", 3)) {
    printf("FAILED: continue header unfolding did not produce correct WS's\n");
    return (failures_to_status("test_mime", 1));
  }

  if (strncmp(continuation, "part1   part2", 13)) {
    printf("FAILED: continue header unfolding was not correct\n");
    return (failures_to_status("test_mime", 1));
  }

  hdr.field_delete("not_there", 9);
  hdr.field_delete("accept", 6);
  hdr.field_delete("scooby", 6);
  hdr.field_delete("scooby", 6);
  hdr.field_delete("bar", 3);
  hdr.field_delete("continuation", 12);

  int count = hdr.fields_count();
  printf("hdr.fields_count() = %d\n", count);

  int i_max_forwards = hdr.value_get_int("Max-Forwards", 12);
  int u_max_forwards = hdr.value_get_uint("Max-Forwards", 12);
  printf("i_max_forwards = %d   u_max_forwards = %d\n", i_max_forwards, u_max_forwards);

  hdr.set_age(9999);

  length = hdr.length_get();
  printf("hdr.length_get() = %d\n", length);

  time_t t0, t1, t2;

  t0 = hdr.get_date();
  if (t0 == 0) {
    printf("FAILED: Initial date is zero but shouldn't be\n");
    return (failures_to_status("test_mime", 1));
  }

  t1 = time(nullptr);
  hdr.set_date(t1);
  t2 = hdr.get_date();
  if (t1 != t2) {
    printf("FAILED: set_date(%" PRId64 ") ... get_date = %" PRId64 "\n\n", (int64_t)t1, (int64_t)t2);
    return (failures_to_status("test_mime", 1));
  }

  hdr.value_append("Cache-Control", 13, "no-cache", 8, true);

  MIMEField *cc_field;
  StrList slist;

  cc_field = hdr.field_find("Cache-Control", 13);

  if (cc_field == nullptr) {
    printf("FAILED: missing Cache-Control header\n\n");
    return (failures_to_status("test_mime", 1));
  }

  // TODO: Do we need to check the "count" returned?
  cc_field->value_get_comma_list(&slist); // FIX: correct usage?

  if (cc_field->value_get_index("Private", 7) < 0) {
    printf("Failed: value_get_index of Cache-Control did not find private");
    return (failures_to_status("test_mime", 1));
  }
  if (cc_field->value_get_index("Bogus", 5) >= 0) {
    printf("Failed: value_get_index of Cache-Control incorrectly found bogus");
    return (failures_to_status("test_mime", 1));
  }
  if (hdr.value_get_index("foo", 3, "three", 5) < 0) {
    printf("Failed: value_get_index of foo did not find three");
    return (failures_to_status("test_mime", 1));
  }
  if (hdr.value_get_index("foo", 3, "bar", 3) < 0) {
    printf("Failed: value_get_index of foo did not find bar");
    return (failures_to_status("test_mime", 1));
  }
  if (hdr.value_get_index("foo", 3, "Bogus", 5) >= 0) {
    printf("Failed: value_get_index of foo incorrectly found bogus");
    return (failures_to_status("test_mime", 1));
  }

  mime_parser_clear(&parser);

  hdr.print(nullptr, 0, nullptr, nullptr);
  printf("\n");

  obj_describe((HdrHeapObjImpl *)(hdr.m_mime), true);

  hdr.fields_clear();

  hdr.destroy();

  return (failures_to_status("test_mime", 0));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_parser_eos_boundary_cases()
{
  struct {
    const char *msg;
    int expected_result;
    int expected_bytes_consumed;
  } tests[] = {
    {"GET /index.html HTTP/1.0\r\n", PARSE_RESULT_DONE, 26},
    {"GET /index.html HTTP/1.0\r\n\r\n***BODY****", PARSE_RESULT_DONE, 28},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar\r\n\r\n***BODY****", PARSE_RESULT_DONE, 48},
    {"GET", PARSE_RESULT_ERROR, 3},
    {"GET /index.html", PARSE_RESULT_ERROR, 15},
    {"GET /index.html\r\n", PARSE_RESULT_ERROR, 17},
    {"GET /index.html HTTP/1.0", PARSE_RESULT_ERROR, 24},
    {"GET /index.html HTTP/1.0\r", PARSE_RESULT_ERROR, 25},
    {"GET /index.html HTTP/1.0\n", PARSE_RESULT_DONE, 25},
    {"GET /index.html HTTP/1.0\n\n", PARSE_RESULT_DONE, 26},
    {"GET /index.html HTTP/1.0\r\n\r\n", PARSE_RESULT_DONE, 28},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar", PARSE_RESULT_ERROR, 44},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar\n", PARSE_RESULT_DONE, 45},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar\r\n", PARSE_RESULT_DONE, 46},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar\r\n\r\n", PARSE_RESULT_DONE, 48},
    {"GET /index.html HTTP/1.0\nUser-Agent: foobar\n", PARSE_RESULT_DONE, 44},
    {"GET /index.html HTTP/1.0\nUser-Agent: foobar\nBoo: foo\n", PARSE_RESULT_DONE, 53},
    {"GET /index.html HTTP/1.0\r\nUser-Agent: foobar\r\n", PARSE_RESULT_DONE, 46},
    {"GET /index.html HTTP/1.0\r\n", PARSE_RESULT_DONE, 26},
    {"", PARSE_RESULT_ERROR, 0},
    {nullptr, 0, 0},
  };

  int i, ret, bytes_consumed;
  const char *orig_start;
  const char *start;
  const char *end;
  HTTPParser parser;

  int failures = 0;

  bri_box("test_http_parser_eos_boundary_cases");

  http_parser_init(&parser);

  for (i = 0; tests[i].msg != nullptr; i++) {
    HTTPHdr req_hdr;

    start = tests[i].msg;
    end   = start + strlen(start); // 1 character past end of string

    req_hdr.create(HTTP_TYPE_REQUEST);

    http_parser_clear(&parser);

    orig_start     = start;
    ret            = req_hdr.parse_req(&parser, &start, end, true);
    bytes_consumed = (int)(start - orig_start);

    printf("======== test %d (length=%d, consumed=%d)\n", i, (int)strlen(tests[i].msg), bytes_consumed);
    printf("[%s]\n", tests[i].msg);
    printf("\n[");
    req_hdr.print(nullptr, 0, nullptr, nullptr);
    printf("]\n\n");

    if ((ret != tests[i].expected_result) || (bytes_consumed != tests[i].expected_bytes_consumed)) {
      ++failures;
      printf("FAILED: test %d: retval <expected %d, got %d>, eaten <expected %d, got %d>\n\n", i, tests[i].expected_result, ret,
             tests[i].expected_bytes_consumed, bytes_consumed);
    }

    req_hdr.destroy();
  }

  return (failures_to_status("test_http_parser_eos_boundary_cases", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_aux(const char *request, const char *response)
{
  int err;
  HTTPHdr req_hdr, rsp_hdr;
  HTTPParser parser;
  const char *start;
  const char *end;

  int status = 1;

  printf("   <<< MUST BE HAND-VERIFIED FOR FULL BENEFIT >>>\n\n");

  /*** (1) parse the request string into req_hdr ***/

  start = request;
  end   = start + strlen(start); // 1 character past end of string

  http_parser_init(&parser);

  req_hdr.create(HTTP_TYPE_REQUEST);
  rsp_hdr.create(HTTP_TYPE_RESPONSE);

  printf("======== parsing\n\n");
  while (true) {
    err = req_hdr.parse_req(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }
  if (err == PARSE_RESULT_ERROR) {
    req_hdr.destroy();
    rsp_hdr.destroy();
    return (failures_to_status("test_http_aux", (status == 0)));
  }

  /*** useless copy to exercise copy function ***/

  HTTPHdr new_hdr;
  new_hdr.create(HTTP_TYPE_REQUEST);
  new_hdr.copy(&req_hdr);
  new_hdr.destroy();

  /*** (2) print out the request ***/

  printf("======== real request (length=%d)\n\n", (int)strlen(request));
  printf("%s\n", request);

  printf("\n[");
  req_hdr.print(nullptr, 0, nullptr, nullptr);
  printf("]\n\n");

  obj_describe(req_hdr.m_http, true);

  // req_hdr.destroy ();
  // ink_release_assert(!"req_hdr.destroy() not defined");

  /*** (3) parse the response string into rsp_hdr ***/

  start = response;
  end   = start + strlen(start);

  http_parser_clear(&parser);
  http_parser_init(&parser);

  while (true) {
    err = rsp_hdr.parse_resp(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }
  if (err == PARSE_RESULT_ERROR) {
    req_hdr.destroy();
    rsp_hdr.destroy();
    return (failures_to_status("test_http_aux", (status == 0)));
  }

  http_parser_clear(&parser);

  /*** (4) print out the response ***/

  printf("\n======== real response (length=%d)\n\n", (int)strlen(response));
  printf("%s\n", response);

  printf("\n[");
  rsp_hdr.print(nullptr, 0, nullptr, nullptr);
  printf("]\n\n");

  obj_describe(rsp_hdr.m_http, true);

#define NNN 1000
  {
    char buf[NNN];
    int bufindex, last_bufindex;
    int tmp;
    int i;

    bufindex = 0;

    do {
      last_bufindex = bufindex;
      tmp           = bufindex;
      buf[0]        = '#'; // make it obvious if hdr.print doesn't print anything
      err           = rsp_hdr.print(buf, NNN, &bufindex, &tmp);

      // printf("test_header: tmp = %d  err = %d  bufindex = %d\n", tmp, err, bufindex);
      putchar('{');
      for (i = 0; i < bufindex - last_bufindex; i++) {
        if (!iscntrl(buf[i])) {
          putchar(buf[i]);
        } else {
          printf("\\%o", buf[i]);
        }
      }
      putchar('}');
    } while (!err);
  }

  // rsp_hdr.print (NULL, 0, NULL, NULL);

  req_hdr.destroy();
  rsp_hdr.destroy();

  return (failures_to_status("test_http_aux", (status == 0)));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_hdr_print_and_copy()
{
  static struct {
    const char *req;
    const char *req_tgt;
    const char *rsp;
    const char *rsp_tgt;
  } tests[] = {
    {"GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa\r\n"
     "\r\n",
     "GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n"},
    {"GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa fjfj dslkfjdslk fjsdafkl dsajfkldsa jfkldsafj "
     "klsafjs lkafjdsalk fsdjakfl sdjaflkdsaj flksdjflsd ;ffd salfdjs lf;sdaf ;dsaf jdsal;fdjsaflkjsda \r\n"
     "\r\n",
     "GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa fjfj dslkfjdslk fjsdafkl dsajfkldsa jfkldsafj "
     "klsafjs lkafjdsalk fsdjakfl sdjaflkdsaj flksdjflsd ;ffd salfdjs lf;sdaf ;dsaf jdsal;fdjsaflkjsda \r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n"},
    {"GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa fjfj dslkfjdslk fjsdafkl dsajfkldsa jfkldsafj "
     "klsafjs lkafjdsalk fsdjakfl sdjaflkdsaj flksdjflsd ;ffd salfdjs lf;sdaf ;dsaf jdsal;fdjsaflkjsda kfl; fsdajfl; "
     "sdjafl;dsajlsjfl;sdafjsdal;fjds al;fdjslaf ;slajdk;f\r\n"
     "\r\n",
     "GET http://foo.com/bar.txt HTTP/1.0\r\n"
     "Accept-Language: fjdfjdslkf dsjkfdj flkdsfjlk sjfdlk ajfdlksa fjfj dslkfjdslk fjsdafkl dsajfkldsa jfkldsafj "
     "klsafjs lkafjdsalk fsdjakfl sdjaflkdsaj flksdjflsd ;ffd salfdjs lf;sdaf ;dsaf jdsal;fdjsaflkjsda kfl; fsdajfl; "
     "sdjafl;dsajlsjfl;sdafjsdal;fjds al;fdjslaf ;slajdk;f\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "\r\n"},
    {"GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: chocolate fribble\r\n", // missing final CRLF
     "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: chocolate fribble\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "MIME-Version: 1.0\r\n"
     "Server: WebSTAR/2.1 ID/30013\r\n"
     "Content-Type: text/html\r\n"
     "Content-Length: 939\r\n"
     "Last-Modified: Thursday, 01-Jan-04 05:00:00 GMT\r\n", // missing final CRLF
     "HTTP/1.0 200 OK\r\n"
     "MIME-Version: 1.0\r\n"
     "Server: WebSTAR/2.1 ID/30013\r\n"
     "Content-Type: text/html\r\n"
     "Content-Length: 939\r\n"
     "Last-Modified: Thursday, 01-Jan-04 05:00:00 GMT\r\n"
     "\r\n"},
    {"GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: \r\n", // missing final CRLF
     "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: \r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "MIME-Version: 1.0\r\n"
     "Server: WebSTAR/2.1 ID/30013\r\n"
     "Content-Type: text/html\r\n"
     "Content-Length: 939\r\n"
     "Last-Modified: Thursday, 01-Jan-04 05:00:00 GMT\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "MIME-Version: 1.0\r\n"
     "Server: WebSTAR/2.1 ID/30013\r\n"
     "Content-Type: text/html\r\n"
     "Content-Length: 939\r\n"
     "Last-Modified: Thursday, 01-Jan-04 05:00:00 GMT\r\n"
     "\r\n"},
    {"GET http://www.news.com:80/ HTTP/1.0\r\n"
     "Proxy-Connection: Keep-Alive\r\n"
     "User-Agent: Mozilla/4.04 [en] (X11; I; Linux 2.0.33 i586)\r\n"
     "Pragma: no-cache\r\n"
     "Host: www.news.com\r\n"
     "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"
     "Accept-Language: en\r\n"
     "Accept-Charset: iso-8859-1, *, utf-8\r\n"
     "Client-ip: D1012148\r\n"
     "Foo: abcdefghijklmnopqrtu\r\n"
     "\r\n",
     "GET http://www.news.com:80/ HTTP/1.0\r\n"
     "Proxy-Connection: Keep-Alive\r\n"
     "User-Agent: Mozilla/4.04 [en] (X11; I; Linux 2.0.33 i586)\r\n"
     "Pragma: no-cache\r\n"
     "Host: www.news.com\r\n"
     "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"
     "Accept-Language: en\r\n"
     "Accept-Charset: iso-8859-1, *, utf-8\r\n"
     "Client-ip: D1012148\r\n"
     "Foo: abcdefghijklmnopqrtu\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "Content-Length: 16428\r\n"
     "Content-Type: text/html\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "Content-Length: 16428\r\n"
     "Content-Type: text/html\r\n"
     "\r\n"},
    {"GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: http://people.netscape.com/jwz/index.html\r\n"
     "Proxy-Connection: Keep-Alive\r\n"
     "User-Agent:  Mozilla/3.01 (X11; I; Linux 2.0.28 i586)\r\n"
     "Pragma: no-cache\r\n"
     "Host: people.netscape.com\r\n"
     "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
     "\r\n",
     "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
     "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
     "Referer: http://people.netscape.com/jwz/index.html\r\n"
     "Proxy-Connection: Keep-Alive\r\n"
     "User-Agent:  Mozilla/3.01 (X11; I; Linux 2.0.28 i586)\r\n"
     "Pragma: no-cache\r\n"
     "Host: people.netscape.com\r\n"
     "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "Content-Length: 16428\r\n"
     "Content-Type: text/html\r\n"
     "\r\n",
     "HTTP/1.0 200 OK\r\n"
     "Content-Length: 16428\r\n"
     "Content-Type: text/html\r\n"
     "\r\n"},
  };

  int ntests = sizeof(tests) / sizeof(tests[0]);
  int i, failures;

  failures = 0;

  bri_box("test_http_hdr_print_and_copy");

  for (i = 0; i < ntests; i++) {
    int status = test_http_hdr_print_and_copy_aux(i + 1, tests[i].req, tests[i].req_tgt, tests[i].rsp, tests[i].rsp_tgt);
    if (status == 0) {
      ++failures;
    }

    // Test for expected failures
    // parse with a '\0' in the header.  Should fail
    status = test_http_hdr_null_char(i + 1, tests[i].req, tests[i].req_tgt);
    if (status == 0) {
      ++failures;
    }

    // Parse with a CTL character in the method name.  Should fail
    status = test_http_hdr_ctl_char(i + 1, tests[i].req, tests[i].req_tgt);
    if (status == 0) {
      ++failures;
    }
  }

  return (failures_to_status("test_http_hdr_print_and_copy", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/
static const char *
comp_http_hdr(HTTPHdr *h1, HTTPHdr *h2)
{
  int h1_len, h2_len;
  int p_index, p_dumpoffset, rval;
  char *h1_pbuf, *h2_pbuf;

  h1_len = h1->length_get();
  h2_len = h2->length_get();

  if (h1_len != h2_len) {
    return "length mismatch";
  }

  h1_pbuf = (char *)ats_malloc(h1_len + 1);
  h2_pbuf = (char *)ats_malloc(h2_len + 1);

  p_index = p_dumpoffset = 0;
  rval                   = h1->print(h1_pbuf, h1_len, &p_index, &p_dumpoffset);
  if (rval != 1) {
    ats_free(h1_pbuf);
    ats_free(h2_pbuf);
    return "hdr print failed";
  }

  p_index = p_dumpoffset = 0;
  rval                   = h2->print(h2_pbuf, h2_len, &p_index, &p_dumpoffset);
  if (rval != 1) {
    ats_free(h1_pbuf);
    ats_free(h2_pbuf);
    return "hdr print failed";
  }

  rval = memcmp(h1_pbuf, h2_pbuf, h1_len);
  ats_free(h1_pbuf);
  ats_free(h2_pbuf);

  if (rval != 0) {
    return "compare failed";
  } else {
    return nullptr;
  }
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_hdr_copy_over_aux(int testnum, const char *request, const char *response)
{
  int err;
  HTTPHdr req_hdr;
  HTTPHdr resp_hdr;
  HTTPHdr copy1;
  HTTPHdr copy2;

  HTTPParser parser;
  const char *start;
  const char *end;
  const char *comp_str = nullptr;

  /*** (1) parse the request string into hdr ***/

  req_hdr.create(HTTP_TYPE_REQUEST);

  start = request;
  end   = start + strlen(start); // 1 character past end of string

  http_parser_init(&parser);

  while (true) {
    err = req_hdr.parse_req(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  if (err == PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) parse error parsing request hdr\n", testnum);
    return (0);
  }
  http_parser_clear(&parser);

  /*** (2) parse the response string into hdr ***/

  resp_hdr.create(HTTP_TYPE_RESPONSE);

  start = response;
  end   = start + strlen(start); // 1 character past end of string

  http_parser_init(&parser);

  while (true) {
    err = resp_hdr.parse_resp(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  if (err == PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) parse error parsing response hdr\n", testnum);
    return (0);
  }

  /*** (3) Basic copy testing ***/
  copy1.create(HTTP_TYPE_REQUEST);
  copy1.copy(&req_hdr);
  comp_str = comp_http_hdr(&req_hdr, &copy1);
  if (comp_str) {
    goto done;
  }

  copy2.create(HTTP_TYPE_RESPONSE);
  copy2.copy(&resp_hdr);
  comp_str = comp_http_hdr(&resp_hdr, &copy2);
  if (comp_str) {
    goto done;
  }

// The APIs for copying headers uses memcpy() which can be unsafe for
// overlapping memory areas. It's unclear to me why these tests were
// created in the first place honestly, since nothing else does this.
#if 0
    /*** (4) Copying over yourself ***/
  copy1.copy(&copy1);
  comp_str = comp_http_hdr(&req_hdr, &copy1);
  if (comp_str)
    goto done;

  copy2.copy(&copy2);
  comp_str = comp_http_hdr(&resp_hdr, &copy2);
  if (comp_str)
    goto done;
#endif

  /*** (5) Gender bending copying ***/
  copy1.copy(&resp_hdr);
  comp_str = comp_http_hdr(&resp_hdr, &copy1);
  if (comp_str) {
    goto done;
  }

  copy2.copy(&req_hdr);
  comp_str = comp_http_hdr(&req_hdr, &copy2);
  if (comp_str) {
    goto done;
  }

done:
  req_hdr.destroy();
  resp_hdr.destroy();
  copy1.destroy();
  copy2.destroy();

  if (comp_str) {
    printf("FAILED: (test #%d) copy & compare: %s\n", testnum, comp_str);
    printf("REQ:\n[%.*s]\n", (int)strlen(request), request);
    printf("RESP  :\n[%.*s]\n", (int)strlen(response), response);
    return (0);
  } else {
    return (1);
  }
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/
int
HdrTest::test_http_hdr_null_char(int testnum, const char *request, const char * /*request_tgt*/)
{
  int err;
  HTTPHdr hdr;
  HTTPParser parser;
  const char *start;
  char cpy_buf[2048];
  const char *cpy_buf_ptr = cpy_buf;

  /*** (1) parse the request string into hdr ***/

  hdr.create(HTTP_TYPE_REQUEST);

  start = request;

  if (strlen(start) > sizeof(cpy_buf)) {
    printf("FAILED: (test #%d) Internal buffer too small for null char test\n", testnum);
    return (0);
  }
  strcpy(cpy_buf, start);

  // Put a null character somewhere in the header
  int length          = strlen(start);
  cpy_buf[length / 2] = '\0';
  http_parser_init(&parser);

  while (true) {
    err = hdr.parse_req(&parser, &cpy_buf_ptr, cpy_buf_ptr + length, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }
  if (err != PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) no parse error parsing request with null char\n", testnum);
    return (0);
  }
  return 1;
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/
int
HdrTest::test_http_hdr_ctl_char(int testnum, const char *request, const char * /*request_tgt */)
{
  int err;
  HTTPHdr hdr;
  HTTPParser parser;
  const char *start;
  char cpy_buf[2048];
  const char *cpy_buf_ptr = cpy_buf;

  /*** (1) parse the request string into hdr ***/

  hdr.create(HTTP_TYPE_REQUEST);

  start = request;

  if (strlen(start) > sizeof(cpy_buf)) {
    printf("FAILED: (test #%d) Internal buffer too small for ctl char test\n", testnum);
    return (0);
  }
  strcpy(cpy_buf, start);

  // Replace a character in the method
  cpy_buf[1] = 16;

  http_parser_init(&parser);

  while (true) {
    err = hdr.parse_req(&parser, &cpy_buf_ptr, cpy_buf_ptr + strlen(start), true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  if (err != PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) no parse error parsing method with ctl char\n", testnum);
    return (0);
  }
  return 1;
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_hdr_print_and_copy_aux(int testnum, const char *request, const char *request_tgt, const char *response,
                                          const char *response_tgt)
{
  int err;
  HTTPHdr hdr;
  HTTPParser parser;
  const char *start;
  const char *end;

  char prt_buf[2048];
  int prt_bufsize = sizeof(prt_buf);
  int prt_bufindex, prt_dumpoffset, prt_ret;

  char cpy_buf[2048];
  int cpy_bufsize = sizeof(cpy_buf);
  int cpy_bufindex, cpy_dumpoffset, cpy_ret;

  char *marshal_buf   = (char *)ats_malloc(2048);
  int marshal_bufsize = sizeof(cpy_buf);

  /*** (1) parse the request string into hdr ***/

  hdr.create(HTTP_TYPE_REQUEST);

  start = request;
  end   = start + strlen(start); // 1 character past end of string

  http_parser_init(&parser);

  while (true) {
    err = hdr.parse_req(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  if (err == PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) parse error parsing request hdr\n", testnum);
    ats_free(marshal_buf);
    return (0);
  }

  /*** (2) copy the request header ***/
  HTTPHdr new_hdr, marshal_hdr;
  RefCountObj ref;

  // Pretend to pin this object with a refcount.
  ref.refcount_inc();

  int marshal_len = hdr.m_heap->marshal(marshal_buf, marshal_bufsize);
  marshal_hdr.create(HTTP_TYPE_REQUEST);
  marshal_hdr.unmarshal(marshal_buf, marshal_len, &ref);
  new_hdr.create(HTTP_TYPE_REQUEST);
  new_hdr.copy(&marshal_hdr);

  /*** (3) print the request header and copy to buffers ***/

  prt_bufindex = prt_dumpoffset = 0;
  prt_ret                       = hdr.print(prt_buf, prt_bufsize, &prt_bufindex, &prt_dumpoffset);

  cpy_bufindex = cpy_dumpoffset = 0;
  cpy_ret                       = new_hdr.print(cpy_buf, cpy_bufsize, &cpy_bufindex, &cpy_dumpoffset);

  ats_free(marshal_buf);

  if ((prt_ret != 1) || (cpy_ret != 1)) {
    printf("FAILED: (test #%d) couldn't print req hdr or copy --- prt_ret=%d, cpy_ret=%d\n", testnum, prt_ret, cpy_ret);
    return (0);
  }

  if (((size_t)prt_bufindex != strlen(request_tgt)) || ((size_t)cpy_bufindex != strlen(request_tgt))) {
    printf("FAILED: (test #%d) print req output size mismatch --- tgt=%d, prt_bufsize=%d, cpy_bufsize=%d\n", testnum,
           (int)strlen(request_tgt), prt_bufindex, cpy_bufindex);

    printf("ORIGINAL:\n[%.*s]\n", (int)strlen(request), request);
    printf("TARGET  :\n[%.*s]\n", (int)strlen(request_tgt), request_tgt);
    printf("PRT_BUFF:\n[%.*s]\n", prt_bufindex, prt_buf);
    printf("CPY_BUFF:\n[%.*s]\n", cpy_bufindex, cpy_buf);
    return (0);
  }

  if ((strncasecmp(request_tgt, prt_buf, strlen(request_tgt)) != 0) ||
      (strncasecmp(request_tgt, cpy_buf, strlen(request_tgt)) != 0)) {
    printf("FAILED: (test #%d) print req output mismatch\n", testnum);
    printf("ORIGINAL:\n[%.*s]\n", (int)strlen(request), request);
    printf("TARGET  :\n[%.*s]\n", (int)strlen(request_tgt), request_tgt);
    printf("PRT_BUFF:\n[%.*s]\n", prt_bufindex, prt_buf);
    printf("CPY_BUFF:\n[%.*s]\n", cpy_bufindex, cpy_buf);
    return (0);
  }

  hdr.destroy();
  new_hdr.destroy();

  /*** (4) parse the response string into hdr ***/

  hdr.create(HTTP_TYPE_RESPONSE);

  start = response;
  end   = start + strlen(start); // 1 character past end of string

  http_parser_init(&parser);

  while (true) {
    err = hdr.parse_resp(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  if (err == PARSE_RESULT_ERROR) {
    printf("FAILED: (test #%d) parse error parsing response hdr\n", testnum);
    return (0);
  }

  /*** (2) copy the response header ***/

  new_hdr.create(HTTP_TYPE_RESPONSE);
  new_hdr.copy(&hdr);

  /*** (3) print the response header and copy to buffers ***/

  prt_bufindex = prt_dumpoffset = 0;
  prt_ret                       = hdr.print(prt_buf, prt_bufsize, &prt_bufindex, &prt_dumpoffset);

  cpy_bufindex = cpy_dumpoffset = 0;
  cpy_ret                       = new_hdr.print(cpy_buf, cpy_bufsize, &cpy_bufindex, &cpy_dumpoffset);

  if ((prt_ret != 1) || (cpy_ret != 1)) {
    printf("FAILED: (test #%d) couldn't print rsp hdr or copy --- prt_ret=%d, cpy_ret=%d\n", testnum, prt_ret, cpy_ret);
    return (0);
  }

  if (((size_t)prt_bufindex != strlen(response_tgt)) || ((size_t)cpy_bufindex != strlen(response_tgt))) {
    printf("FAILED: (test #%d) print rsp output size mismatch --- tgt=%d, prt_bufsize=%d, cpy_bufsize=%d\n", testnum,
           (int)strlen(response_tgt), prt_bufindex, cpy_bufindex);
    printf("ORIGINAL:\n[%.*s]\n", (int)strlen(response), response);
    printf("TARGET  :\n[%.*s]\n", (int)strlen(response_tgt), response_tgt);
    printf("PRT_BUFF:\n[%.*s]\n", prt_bufindex, prt_buf);
    printf("CPY_BUFF:\n[%.*s]\n", cpy_bufindex, cpy_buf);
    return (0);
  }

  if ((strncasecmp(response_tgt, prt_buf, strlen(response_tgt)) != 0) ||
      (strncasecmp(response_tgt, cpy_buf, strlen(response_tgt)) != 0)) {
    printf("FAILED: (test #%d) print rsp output mismatch\n", testnum);
    printf("ORIGINAL:\n[%.*s]\n", (int)strlen(response), response);
    printf("TARGET  :\n[%.*s]\n", (int)strlen(response_tgt), response_tgt);
    printf("PRT_BUFF:\n[%.*s]\n", prt_bufindex, prt_buf);
    printf("CPY_BUFF:\n[%.*s]\n", cpy_bufindex, cpy_buf);
    return (0);
  }

  hdr.destroy();
  new_hdr.destroy();

  if (test_http_hdr_copy_over_aux(testnum, request, response) == 0) {
    return 0;
  }

  return (1);
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http()
{
  int status = 1;

  static const char request0[] = {
    "GET http://www.news.com:80/ HTTP/1.0\r\n"
    "Proxy-Connection: Keep-Alive\r\n"
    "User-Agent: Mozilla/4.04 [en] (X11; I; Linux 2.0.33 i586)\r\n"
    "Pragma: no-cache\r\n"
    "Host: www.news.com\r\n"
    "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"
    "Accept-Language: en\r\n"
    "Accept-Charset: iso-8859-1, *, utf-8\r\n"
    "Cookie: u_vid_0_0=00031ba3; "
    "s_cur_0_0=0101sisi091314775496e7d3Jx4+POyJakrMybmNOsq6XOn5bVn5Z6a4Ln5crU5M7Rxq2lm5aWpqupo20=; "
    "SC_Cnet001=Sampled; SC_Cnet002=Sampled\r\n"
    "Client-ip: D1012148\r\n"
    "Foo: abcdefghijklmnopqrtu\r\n"
    "\r\n",
  };

  static const char request09[] = {
    "GET /index.html\r\n"
    "\r\n",
  };

  static const char request1[] = {
    "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
    "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
    "Referer: http://people.netscape.com/jwz/index.html\r\n"
    "Proxy-Connection: Keep-Alive\r\n"
    "User-Agent:  Mozilla/3.01 (X11; I; Linux 2.0.28 i586)\r\n"
    "Pragma: no-cache\r\n"
    "Host: people.netscape.com\r\n"
    "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
    "\r\n",
  };

  static const char request_no_colon[] = {
    "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
    "If-Modified-Since Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
    "Referer http://people.netscape.com/jwz/index.html\r\n"
    "Proxy-Connection Keep-Alive\r\n"
    "User-Agent  Mozilla/3.01 (X11; I; Linux 2.0.28 i586)\r\n"
    "Pragma no-cache\r\n"
    "Host people.netscape.com\r\n"
    "Accept image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
    "\r\n",
  };

  static const char request_no_val[] = {
    "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
    "If-Modified-Since:\r\n"
    "Referer:     "
    "Proxy-Connection:\r\n"
    "User-Agent:     \r\n"
    "Host:::\r\n"
    "\r\n",
  };

  static const char request_multi_fblock[] = {
    "GET http://people.netscape.com/jwz/hacks-1.gif HTTP/1.0\r\n"
    "If-Modified-Since: Wednesday, 26-Feb-97 06:58:17 GMT; length=842\r\n"
    "Referer: http://people.netscape.com/jwz/index.html\r\n"
    "Proxy-Connection: Keep-Alive\r\n"
    "User-Agent:  Mozilla/3.01 (X11; I; Linux 2.0.28 i586)\r\n"
    "Pragma: no-cache\r\n"
    "Host: people.netscape.com\r\n"
    "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*\r\n"
    "X-1: blah\r\n"
    "X-2: blah\r\n"
    "X-3: blah\r\n"
    "X-4: blah\r\n"
    "X-5: blah\r\n"
    "X-6: blah\r\n"
    "X-7: blah\r\n"
    "X-8: blah\r\n"
    "X-9: blah\r\n"
    "Pragma: no-cache\r\n"
    "X-X-1: blah\r\n"
    "X-X-2: blah\r\n"
    "X-X-3: blah\r\n"
    "X-X-4: blah\r\n"
    "X-X-5: blah\r\n"
    "X-X-6: blah\r\n"
    "X-X-7: blah\r\n"
    "X-X-8: blah\r\n"
    "X-X-9: blah\r\n"
    "\r\n",
  };

  static const char request_leading_space[] = {
    " GET http://www.news.com:80/ HTTP/1.0\r\n"
    "Proxy-Connection: Keep-Alive\r\n"
    "User-Agent: Mozilla/4.04 [en] (X11; I; Linux 2.0.33 i586)\r\n"
    "\r\n",
  };

  static const char request_padding[] = {
    "GET http://www.padding.com:80/ HTTP/1.0\r\n"
    "X-1: blah1\r\n"
    //       "X-2:  blah2\r\n"
    "X-3:   blah3\r\n"
    //       "X-4:    blah4\r\n"
    "X-5:     blah5\r\n"
    //       "X-6:      blah6\r\n"
    "X-7:       blah7\r\n"
    //       "X-8:        blah8\r\n"
    "X-9:         blah9\r\n"
    "\r\n",
  };

  static const char request_09p[] = {
    "GET http://www.news09.com/\r\n"
    "\r\n",
  };

  static const char request_09ht[] = {
    "GET http://www.news09.com/ HT\r\n"
    "\r\n",
  };

  static const char request_11[] = {
    "GET http://www.news.com/ HTTP/1.1\r\n"
    "Connection: close\r\n"
    "\r\n",
  };

  static const char request_unterminated[] = {
    "GET http://www.unterminated.com/ HTTP/1.1",
  };

  static const char request_blank[] = {
    "\r\n",
  };

  static const char request_blank2[] = {
    "\r\n"
    "\r\n",
  };

  static const char request_blank3[] = {
    "     "
    "\r\n",
  };

  ////////////////////////////////////////////////////

  static const char response0[] = {
    "HTTP/1.0 200 OK\r\n"
    "MIME-Version: 1.0\r\n"
    "Server: WebSTAR/2.1 ID/30013\r\n"
    "Content-Type: text/html\r\n"
    "Content-Length: 939\r\n"
    "Last-Modified: Thursday, 01-Jan-04 05:00:00 GMT\r\n"
    "\r\n",
  };

  static const char response1[] = {
    "HTTP/1.0 200 OK\r\n"
    "Server: Netscape-Communications/1.12\r\n"
    "Date: Tuesday, 08-Dec-98 20:32:17 GMT\r\n"
    "Content-Type: text/html\r\n"
    "\r\n",
  };

  static const char response_no_colon[] = {
    "HTTP/1.0 200 OK\r\n"
    "Server Netscape-Communications/1.12\r\n"
    "Date: Tuesday, 08-Dec-98 20:32:17 GMT\r\n"
    "Content-Type: text/html\r\n"
    "\r\n",
  };

  static const char response_unterminated[] = {
    "HTTP/1.0 200 OK",
  };

  static const char response09[] = {
    "",
  };

  static const char response_blank[] = {
    "\r\n",
  };

  static const char response_blank2[] = {
    "\r\n"
    "\r\n",
  };

  static const char response_blank3[] = {
    "     "
    "\r\n",
  };

  status = status & test_http_aux(request0, response0);
  status = status & test_http_aux(request09, response09);
  status = status & test_http_aux(request1, response1);
  status = status & test_http_aux(request_no_colon, response_no_colon);
  status = status & test_http_aux(request_no_val, response_no_colon);
  status = status & test_http_aux(request_leading_space, response0);
  status = status & test_http_aux(request_multi_fblock, response0);
  status = status & test_http_aux(request_padding, response0);
  status = status & test_http_aux(request_09p, response0);
  status = status & test_http_aux(request_09ht, response0);
  status = status & test_http_aux(request_11, response0);
  status = status & test_http_aux(request_unterminated, response_unterminated);
  status = status & test_http_aux(request_blank, response_blank);
  status = status & test_http_aux(request_blank2, response_blank2);
  status = status & test_http_aux(request_blank3, response_blank3);

  return (failures_to_status("test_http", (status == 0)));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_http_mutation()
{
  int status = 1;

  bri_box("test_http_mutation");

  printf("   <<< MUST BE HAND-VERIFIED FOR FULL BENEFIT>>>\n\n");

  HTTPHdr resp_hdr;
  int err, i;
  HTTPParser parser;
  const char base_resp[] = "HTTP/1.0 200 OK\r\n\r\n";
  const char *start, *end;

  /*** (1) parse the response string into req_hdr ***/

  start = base_resp;
  end   = start + strlen(start);

  http_parser_init(&parser);

  resp_hdr.create(HTTP_TYPE_RESPONSE);

  while (true) {
    err = resp_hdr.parse_resp(&parser, &start, end, true);
    if (err != PARSE_RESULT_CONT) {
      break;
    }
  }

  printf("\n======== before mutation ==========\n\n");
  printf("\n[");
  resp_hdr.print(nullptr, 0, nullptr, nullptr);
  printf("]\n\n");

  /*** (2) add in a bunch of header fields ****/
  char field_name[1024];
  char field_value[1024];
  for (i = 1; i <= 100; i++) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);
    snprintf(field_value, sizeof(field_value), "%d %d %d %d %d", i, i, i, i, i);
    resp_hdr.value_set(field_name, (int)strlen(field_name), field_value, (int)strlen(field_value));
  }

  /**** (3) delete all the even numbered fields *****/
  for (i = 2; i <= 100; i += 2) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);
    resp_hdr.field_delete(field_name, (int)strlen(field_name));
  }

  /***** (4) add in secondary fields for all multiples of 3 ***/
  for (i = 3; i <= 100; i += 3) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);
    MIMEField *f = resp_hdr.field_create(field_name, (int)strlen(field_name));
    resp_hdr.field_attach(f);
    snprintf(field_value, sizeof(field_value), "d %d %d %d %d %d", i, i, i, i, i);
    f->value_set(resp_hdr.m_heap, resp_hdr.m_mime, field_value, (int)strlen(field_value));
  }

  /***** (5) append all fields with multiples of 5 ***/
  for (i = 5; i <= 100; i += 5) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);
    snprintf(field_value, sizeof(field_value), "a %d", i);

    resp_hdr.value_append(field_name, (int)strlen(field_name), field_value, (int)strlen(field_value), true);
  }

  /**** (6) delete all multiples of nine *****/
  for (i = 9; i <= 100; i += 9) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);
    resp_hdr.field_delete(field_name, (int)strlen(field_name));
  }

  printf("\n======== mutated response ==========\n\n");
  printf("\n[");
  resp_hdr.print(nullptr, 0, nullptr, nullptr);
  printf("]\n\n");

  resp_hdr.destroy();

  return (failures_to_status("test_http_mutation", (status == 0)));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_arena_aux(Arena *arena, int len)
{
  char *str      = arena->str_alloc(len);
  int verify_len = (int)arena->str_length(str);

  if (len != verify_len) {
    printf("FAILED: requested %d, got %d bytes\n", len, verify_len);
    return (1); // 1 error (different return convention)
  } else {
    return (0); // no errors (different return convention)
  }
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_arena()
{
  bri_box("test_arena");

  Arena *arena;
  int failures = 0;

  arena = new Arena;

  failures += test_arena_aux(arena, 1);
  failures += test_arena_aux(arena, 127);
  failures += test_arena_aux(arena, 128);
  failures += test_arena_aux(arena, 129);
  failures += test_arena_aux(arena, 255);
  failures += test_arena_aux(arena, 256);
  failures += test_arena_aux(arena, 16384);
  failures += test_arena_aux(arena, 16385);
  failures += test_arena_aux(arena, 16511);
  failures += test_arena_aux(arena, 16512);
  failures += test_arena_aux(arena, 2097152);
  failures += test_arena_aux(arena, 2097153);
  failures += test_arena_aux(arena, 2097279);
  failures += test_arena_aux(arena, 2097280);

  delete arena;

  return (failures_to_status("test_arena", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_regex()
{
  DFA dfa;
  int status = 1;

  const char *test_harness[] = {"foo", "(.*\\.apache\\.org)", "(.*\\.example\\.com)"};

  bri_box("test_regex");

  dfa.compile(test_harness, SIZEOF(test_harness));
  status = status & (dfa.match("trafficserver.apache.org") == 1);
  status = status & (dfa.match("www.example.com") == 2);
  status = status & (dfa.match("aaaaaafooooooooinktomi....com.org") == -1);
  status = status & (dfa.match("foo") == 0);

  return (failures_to_status("test_regex", (status != 1)));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_accept_language_match()
{
  bri_box("test_accept_language_match");

  struct {
    const char *content_language;
    const char *accept_language;
    float Q;
    int L;
    int I;
  } test_cases[] = {
    {"en", "*", 1.0, 1, 1},
    {"en", "fr", 0.0, 0, 0},
    {"en", "de, fr, en;q=0.7", 0.7, 2, 3},
    {"en-cockney", "de, fr, en;q=0.7", 0.7, 2, 3},
    {"en-cockney", "de, fr, en-foobar;q=0.8, en;q=0.7", 0.7, 2, 4},
    {"en-cockney", "de, fr, en-cockney;q=0.8, en;q=0.7", 0.8, 10, 3},
    {"en-cockney", "de, fr, en;q=0.8, en;q=0.7", 0.8, 2, 3},
    {"en-cockney", "de, fr, en;q=0.7, en;q=0.8", 0.8, 2, 4},
    {"en-cockney", "de, fr, en;q=0.8, en;q=0.8", 0.8, 2, 3},
    {"en-cockney", "de, fr, en-cockney;q=0.7, en;q=0.8", 0.7, 10, 3},
    {"en-cockney", "de, fr, en;q=0.8, en-cockney;q=0.7", 0.7, 10, 4},
    {"en-cockney", "de, fr, en-cockney;q=0.8, en;q=0.8", 0.8, 10, 3},
    {"en-cockney", "de, fr, en-cockney;q=0.8, en;q=0.7", 0.8, 10, 3},
    {"en-cockney", "de, fr, en-american", 0.0, 0, 0},
    {"en-cockney", "de, fr, en;q=0.8, en;q=0.8, *", 0.8, 2, 3},
    {"en-cockney", "de, fr, en;q=0.8, en;q=0.8, *;q=0.9", 0.8, 2, 3},
    {"en-foobar", "de, fr, en;q=0.8, en;q=0.8, *;q=0.9", 0.8, 2, 3},
    {"oo-foobar", "de, fr, en;q=0.8, en;q=0.8, *;q=0.9", 0.9, 1, 5},
    {"oo-foobar", "de, fr, en;q=0.8, en;q=0.8, *;q=0.9, *", 1.0, 1, 6},
    {"oo-foobar", "de, fr, en;q=0.8, en;q=0.8, *, *;q=0.9", 1.0, 1, 5},
    {"fr-belgian", "de, fr;hi-there;q=0.9, fr;q=0.8, en", 0.9, 2, 2},
    {"fr-belgian", "de, fr;q=0.8, fr;hi-there;q=0.9, en", 0.9, 2, 3},
    {nullptr, nullptr, 0.0, 0, 0},
  };

  int i, I, L;
  float Q;
  int failures = 0;

  for (i = 0; test_cases[i].accept_language; i++) {
    StrList acpt_lang_list(false);
    HttpCompat::parse_comma_list(&acpt_lang_list, test_cases[i].accept_language, (int)strlen(test_cases[i].accept_language));

    Q = HttpCompat::match_accept_language(test_cases[i].content_language, (int)strlen(test_cases[i].content_language),
                                          &acpt_lang_list, &L, &I);

    if ((Q != test_cases[i].Q) || (L != test_cases[i].L) || (I != test_cases[i].I)) {
      printf("FAILED: (#%d) got { Q = %.3f; L = %d; I = %d; }, expected { Q = %.3f; L = %d; I = %d; }, from matching\n  '%s' "
             "against '%s'\n",
             i, Q, L, I, test_cases[i].Q, test_cases[i].L, test_cases[i].I, test_cases[i].content_language,
             test_cases[i].accept_language);
      ++failures;
    }
  }

  return (failures_to_status("test_accept_language_match", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_accept_charset_match()
{
  bri_box("test_accept_charset_match");

  struct {
    const char *content_charset;
    const char *accept_charset;
    float Q;
    int I;
  } test_cases[] = {
    {"iso-8859-1", "*", 1.0, 1},
    {"iso-8859-1", "iso-8859-2", 0.0, 0},
    {"iso-8859-1", "iso-8859", 0.0, 0},
    {"iso-8859-1", "iso-8859-12", 0.0, 0},
    {"iso-8859-1", "koi-8-r", 0.0, 0},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp;q=0.7", 0.7, 3},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp;q=0.7", 0.7, 3},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp;q=0.8, euc-jp;q=0.7", 0.8, 3},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp;q=0.7, euc-jp;q=0.8", 0.8, 4},
    {"euc-jp", "euc-jp;q=0.9, shift_jis, iso-2022-jp, euc-jp;q=0.7, euc-jp;q=0.8", 0.9, 1},
    {"EUC-JP", "euc-jp;q=0.9, shift_jis, iso-2022-jp, euc-jp, euc-jp;q=0.8", 1.0, 4},
    {"euc-jp", "euc-jp;q=0.9, shift_jis, iso-2022-jp, EUC-JP, euc-jp;q=0.8", 1.0, 4},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp-foobar", 0.0, 0},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp-foobar, *", 1.0, 4},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp-foobar, *;q=0.543", 0.543, 4},
    {"euc-jp", "shift_jis, iso-2022-jp, euc-jp-foobar, *;q=0.0", 0.0, 4},
    {"euc-jp", "shift_jis, iso-2022-jp, *;q=0.0, euc-jp-foobar, *;q=0.0", 0.0, 3},
    {"euc-jp", "shift_jis, iso-2022-jp, *;q=0.0, euc-jp-foobar, *;q=0.5", 0.5, 5},
    {"euc-jp", "shift_jis, iso-2022-jp, *;q=0.5, euc-jp-foobar, *;q=0.0", 0.5, 3},
    {"euc-jp", "shift_jis, iso-2022-jp, *;q=0.5, euc-jp-foobar, *, *;q=0.0", 1.0, 5},
    {"euc-jp", "shift_jis, euc-jp;hi-there;q=0.5, iso-2022-jp", 0.5, 2},
    {"euc-jp", "shift_jis, euc-jp;hi-there;q= 0.5, iso-2022-jp", 0.5, 2},
    {"euc-jp", "shift_jis, euc-jp;hi-there;q = 0.5, iso-2022-jp", 0.5, 2},
    {"euc-jp", "shift_jis, euc-jp;hi-there ; q = 0.5, iso-2022-jp", 0.5, 2},
    {"euc-jp", "shift_jis, euc-jp;hi-there ;; q = 0.5, iso-2022-jp", 0.5, 2},
    {"euc-jp", "shift_jis, euc-jp;hi-there ;; Q = 0.5, iso-2022-jp", 0.5, 2},
    {nullptr, nullptr, 0.0, 0},
  };

  int i, I;
  float Q;
  int failures = 0;

  for (i = 0; test_cases[i].accept_charset; i++) {
    StrList acpt_lang_list(false);
    HttpCompat::parse_comma_list(&acpt_lang_list, test_cases[i].accept_charset, (int)strlen(test_cases[i].accept_charset));

    Q = HttpCompat::match_accept_charset(test_cases[i].content_charset, (int)strlen(test_cases[i].content_charset), &acpt_lang_list,
                                         &I);

    if ((Q != test_cases[i].Q) || (I != test_cases[i].I)) {
      printf("FAILED: (#%d) got { Q = %.3f; I = %d; }, expected { Q = %.3f; I = %d; }, from matching\n  '%s' against '%s'\n", i, Q,
             I, test_cases[i].Q, test_cases[i].I, test_cases[i].content_charset, test_cases[i].accept_charset);
      ++failures;
    }
  }

  return (failures_to_status("test_accept_charset_match", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_comma_vals()
{
  static struct {
    const char *value;
    int value_count;
    struct {
      int offset;
      int len;
    } pieces[4];
  } tests[] = {
    {",", 2, {{0, 0}, {1, 0}, {-1, 0}, {-1, 0}}},
    {"", 1, {{0, 0}, {-1, 0}, {-1, 0}, {-1, 0}}},
    {" ", 1, {{0, 0}, {-1, 0}, {-1, 0}, {-1, 0}}},
    {", ", 2, {{0, 0}, {1, 0}, {-1, 0}, {-1, 0}}},
    {",,", 3, {{0, 0}, {1, 0}, {2, 0}, {-1, 0}}},
    {" ,", 2, {{0, 0}, {2, 0}, {-1, 0}, {-1, 0}}},
    {" , ", 2, {{0, 0}, {2, 0}, {-1, 0}, {-1, 0}}},
    {"a, ", 2, {{0, 1}, {2, 0}, {-1, 0}, {-1, 0}}},
    {" a, ", 2, {{1, 1}, {3, 0}, {-1, 0}, {-1, 0}}},
    {" ,a", 2, {{0, 0}, {2, 1}, {-1, 0}, {-1, 0}}},
    {" , a", 2, {{0, 0}, {3, 1}, {-1, 0}, {-1, 0}}},
    {"a,a", 2, {{0, 1}, {2, 1}, {-1, 0}, {-1, 0}}},
    {"foo", 1, {{0, 3}, {-1, 0}, {-1, 0}, {-1, 0}}},
    {"foo,", 2, {{0, 3}, {4, 0}, {-1, 0}, {-1, 0}}},
    {"foo, ", 2, {{0, 3}, {4, 0}, {-1, 0}, {-1, 0}}},
    {"foo, bar", 2, {{0, 3}, {5, 3}, {-1, 0}, {-1, 0}}},
    {"foo, bar,", 3, {{0, 3}, {5, 3}, {9, 0}, {-1, 0}}},
    {"foo, bar, ", 3, {{0, 3}, {5, 3}, {9, 0}, {-1, 0}}},
    {
      ",foo,bar,",
      4,
      {{0, 0}, {1, 3}, {5, 3}, {9, 0}},
    },
  };

  bri_box("test_comma_vals");

  HTTPHdr hdr;
  char field_name[32];
  int i, j, len, failures, ntests, ncommavals;

  failures = 0;
  ntests   = sizeof(tests) / sizeof(tests[0]);

  hdr.create(HTTP_TYPE_REQUEST);

  for (i = 0; i < ntests; i++) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);

    MIMEField *f = hdr.field_create(field_name, (int)strlen(field_name));
    ink_release_assert(f->m_ptr_value == nullptr);

    hdr.field_attach(f);
    ink_release_assert(f->m_ptr_value == nullptr);

    hdr.field_value_set(f, tests[i].value, strlen(tests[i].value));
    ink_release_assert(f->m_ptr_value != tests[i].value); // should be copied
    ink_release_assert(f->m_len_value == strlen(tests[i].value));
    ink_release_assert(memcmp(f->m_ptr_value, tests[i].value, f->m_len_value) == 0);

    ncommavals = mime_field_value_get_comma_val_count(f);
    if (ncommavals != tests[i].value_count) {
      ++failures;
      printf("FAILED: test #%d (field value '%s') expected val count %d, got %d\n", i + 1, tests[i].value, tests[i].value_count,
             ncommavals);
    }

    for (j = 0; j < tests[i].value_count; j++) {
      const char *val = mime_field_value_get_comma_val(f, &len, j);
      int offset      = ((val == nullptr) ? -1 : (val - f->m_ptr_value));

      if ((offset != tests[i].pieces[j].offset) || (len != tests[i].pieces[j].len)) {
        ++failures;
        printf("FAILED: test #%d (field value '%s', commaval idx %d) expected [offset %d, len %d], got [offset %d, len %d]\n",
               i + 1, tests[i].value, j, tests[i].pieces[j].offset, tests[i].pieces[j].len, offset, len);
      }
    }
  }

  hdr.destroy();
  return (failures_to_status("test_comma_vals", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_set_comma_vals()
{
  static struct {
    const char *old_raw;
    int idx;
    const char *slice;
    const char *new_raw;
  } tests[] = {
    {"a,b,c", 0, "fred", "fred, b, c"},
    {"a,b,c", 1, "fred", "a, fred, c"},
    {"a,b,c", 2, "fred", "a, b, fred"},
    {"a,b,c", 3, "fred", "a,b,c"},
    {"", 0, "", ""},
    {"", 0, "foo", "foo"},
    {"", 1, "foo", ""},
    {" ", 0, "", ""},
    {" ", 0, "foo", "foo"},
    {" ", 1, "foo", " "},
    {",", 0, "foo", "foo, "},
    {",", 1, "foo", ", foo"},
    {",,", 0, "foo", "foo, , "},
    {",,", 1, "foo", ", foo, "},
    {",,", 2, "foo", ", , foo"},
    {"foo", 0, "abc", "abc"},
    {"foo", 1, "abc", "foo"},
    {"foo", 0, "abc,", "abc,"},
    {"foo", 0, ",abc", ",abc"},
    {",,", 1, ",,,", ", ,,,, "},
    {" a , b , c", 0, "fred", "fred, b, c"},
    {" a , b , c", 1, "fred", "a, fred, c"},
    {" a , b , c", 2, "fred", "a, b, fred"},
    {" a , b , c", 3, "fred", " a , b , c"},
    {"    a   ,   b ", 0, "fred", "fred, b"},
    {"    a   ,   b ", 1, "fred", "a, fred"},
    {"    a   , b ", 1, "fred", "a, fred"},
    {"    a   ,b ", 1, "fred", "a, fred"},
    {"a, , , , e, , g,", 0, "fred", "fred, , , , e, , g, "},
    {"a, , , , e, , g,", 1, "fred", "a, fred, , , e, , g, "},
    {"a, , , , e, , g,", 2, "fred", "a, , fred, , e, , g, "},
    {"a, , , , e, , g,", 5, "fred", "a, , , , e, fred, g, "},
    {"a, , , , e, , g,", 7, "fred", "a, , , , e, , g, fred"},
    {"a, , , , e, , g,", 8, "fred", "a, , , , e, , g,"},
    {"a, \"boo,foo\", c", 0, "wawa", "wawa, \"boo,foo\", c"},
    {"a, \"boo,foo\", c", 1, "wawa", "a, wawa, c"},
    {"a, \"boo,foo\", c", 2, "wawa", "a, \"boo,foo\", wawa"},
  };

  bri_box("test_set_comma_vals");

  HTTPHdr hdr;
  char field_name[32];
  int i, failures, ntests;

  failures = 0;
  ntests   = sizeof(tests) / sizeof(tests[0]);

  hdr.create(HTTP_TYPE_REQUEST);

  for (i = 0; i < ntests; i++) {
    snprintf(field_name, sizeof(field_name), "Test%d", i);

    MIMEField *f = hdr.field_create(field_name, (int)strlen(field_name));
    hdr.field_value_set(f, tests[i].old_raw, strlen(tests[i].old_raw));
    mime_field_value_set_comma_val(hdr.m_heap, hdr.m_mime, f, tests[i].idx, tests[i].slice, strlen(tests[i].slice));
    ink_release_assert(f->m_ptr_value != nullptr);

    if ((f->m_len_value != strlen(tests[i].new_raw)) || (memcmp(f->m_ptr_value, tests[i].new_raw, f->m_len_value) != 0)) {
      ++failures;
      printf("FAILED:  test #%d (setting idx %d of '%s' to '%s') expected '%s' len %d, got '%.*s' len %d\n", i + 1, tests[i].idx,
             tests[i].old_raw, tests[i].slice, tests[i].new_raw, (int)strlen(tests[i].new_raw), f->m_len_value, f->m_ptr_value,
             f->m_len_value);
    }
  }

  hdr.destroy();
  return (failures_to_status("test_set_comma_vals", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_delete_comma_vals()
{
  bri_box("test_delete_comma_vals");
  rprintf(rtest, "  HdrTest test_delete_comma_vals: TEST NOT IMPLEMENTED\n");
  return (1);
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_extend_comma_vals()
{
  bri_box("test_extend_comma_vals");
  rprintf(rtest, "  HdrTest test_extend_comma_vals: TEST NOT IMPLEMENTED\n");
  return (1);
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_insert_comma_vals()
{
  bri_box("test_insert_comma_vals");
  rprintf(rtest, "  HdrTest test_insert_comma_vals: TEST NOT IMPLEMENTED\n");
  return (1);
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::test_parse_comma_list()
{
  static struct {
    const char *value;
    int count;
    struct {
      int offset;
      int len;
    } pieces[3];
  } tests[] = {
    {"", 1, {{0, 0}, {-1, 0}, {-1, 0}}},
    {",", 2, {{0, 0}, {1, 0}, {-1, 0}}},
    {" ,", 2, {{0, 0}, {2, 0}, {-1, 0}}},
    {", ", 2, {{0, 0}, {1, 0}, {-1, 0}}},
    {" , ", 2, {{0, 0}, {2, 0}, {-1, 0}}},
    {"abc,", 2, {{0, 3}, {4, 0}, {-1, 0}}},
    {"abc, ", 2, {{0, 3}, {4, 0}, {-1, 0}}},
    {"", 1, {{0, 0}, {-1, 0}, {-1, 0}}},
    {" ", 1, {{0, 0}, {-1, 0}, {-1, 0}}},
    {"  ", 1, {{0, 0}, {-1, 0}, {-1, 0}}},
    {"a", 1, {{0, 1}, {-1, 0}, {-1, 0}}},
    {" a", 1, {{1, 1}, {-1, 0}, {-1, 0}}},
    {"  a  ", 1, {{2, 1}, {-1, 0}, {-1, 0}}},
    {"abc,defg", 2, {{0, 3}, {4, 4}, {-1, 0}}},
    {" abc,defg", 2, {{1, 3}, {5, 4}, {-1, 0}}},
    {" abc, defg", 2, {{1, 3}, {6, 4}, {-1, 0}}},
    {" abc , defg", 2, {{1, 3}, {7, 4}, {-1, 0}}},
    {" abc , defg ", 2, {{1, 3}, {7, 4}, {-1, 0}}},
    {" abc , defg, ", 3, {{1, 3}, {7, 4}, {12, 0}}},
    {" abc , defg ,", 3, {{1, 3}, {7, 4}, {13, 0}}},
    {", abc , defg ", 3, {{0, 0}, {2, 3}, {8, 4}}},
    {" ,abc , defg ", 3, {{0, 0}, {2, 3}, {8, 4}}},
    {"a,b", 2, {{0, 1}, {2, 1}, {-1, 0}}},
    {"a,,b", 3, {{0, 1}, {2, 0}, {3, 1}}},
    {"a, ,b", 3, {{0, 1}, {2, 0}, {4, 1}}},
    {"a ,,b", 3, {{0, 1}, {3, 0}, {4, 1}}},
    {",", 2, {{0, 0}, {1, 0}, {-1, 0}}},
    {" ,", 2, {{0, 0}, {2, 0}, {-1, 0}}},
    {", ", 2, {{0, 0}, {1, 0}, {-1, 0}}},
    {" , ", 2, {{0, 0}, {2, 0}, {-1, 0}}},
    {"a,b,", 3, {{0, 1}, {2, 1}, {4, 0}}},
    {"a,b, ", 3, {{0, 1}, {2, 1}, {4, 0}}},
    {"a,b,  ", 3, {{0, 1}, {2, 1}, {4, 0}}},
    {"a,b,  c", 3, {{0, 1}, {2, 1}, {6, 1}}},
    {"a,b,  c ", 3, {{0, 1}, {2, 1}, {6, 1}}},
    {"a,\"b,c\",d", 3, {{0, 1}, {3, 3}, {8, 1}}},
  };

  bri_box("test_parse_comma_list");

  int i, j, failures, ntests, offset;

  failures = (offset = 0);
  ntests   = sizeof(tests) / sizeof(tests[0]);

  for (i = 0; i < ntests; i++) {
    StrList list(false);
    HttpCompat::parse_comma_list(&list, tests[i].value);
    if (list.count != tests[i].count) {
      ++failures;
      printf("FAILED: test #%d (string '%s') expected list count %d, got %d\n", i + 1, tests[i].value, tests[i].count, list.count);
    }

    for (j = 0; j < tests[i].count; j++) {
      Str *cell = list.get_idx(j);
      if (cell != nullptr) {
        offset = cell->str - tests[i].value;
      }

      if (tests[i].pieces[j].offset == -1) // should not have a piece
      {
        if (cell != nullptr) {
          ++failures;
          printf("FAILED: test #%d (string '%s', idx %d) expected NULL piece, got [offset %d len %d]\n", i + 1, tests[i].value, j,
                 offset, (int)cell->len);
        }
      } else // should have a piece
      {
        if (cell == nullptr) {
          ++failures;
          printf("FAILED: test #%d (string '%s', idx %d) expected [offset %d len %d], got NULL piece\n", i + 1, tests[i].value, j,
                 tests[i].pieces[j].offset, tests[i].pieces[j].len);
        } else if ((offset != tests[i].pieces[j].offset) || (cell->len != (size_t)tests[i].pieces[j].len)) {
          ++failures;
          printf("FAILED: test #%d (string '%s', idx %d) expected [offset %d len %d], got [offset %d len %d]\n", i + 1,
                 tests[i].value, j, tests[i].pieces[j].offset, tests[i].pieces[j].len, offset, (int)cell->len);
        }
      }
    }
  }

  return (failures_to_status("test_parse_comma_list", failures));
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

void
HdrTest::bri_box(const char *s)
{
  int i, len;

  len = (int)strlen(s);
  printf("\n+-");
  for (i = 0; i < len; i++) {
    putchar('-');
  }
  printf("-+\n");
  printf("| %s |\n", s);
  printf("+-");
  for (i = 0; i < len; i++) {
    putchar('-');
  }
  printf("-+\n\n");
}

/*-------------------------------------------------------------------------
  -------------------------------------------------------------------------*/

int
HdrTest::failures_to_status(const char *testname, int nfail)
{
  rprintf(rtest, "  HdrTest %s: %s\n", testname, ((nfail > 0) ? "FAILED" : "PASSED"));
  return ((nfail > 0) ? 0 : 1);
}
