/*
  This file is part of libmicrohttpd
  Copyright (C) 2015-2024 Evgeny Grin (Karlson2k)

  GNU libmicrohttpd is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  GNU libmicrohttpd is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file microhttpd/mhd_str.c
 * @brief  Functions implementations for string manipulating
 * @author Karlson2k (Evgeny Grin)
 */

#include "mhd_sys_options.h"

#include "mhd_str.h"

#include <string.h>

#include "mhd_assert.h"
#include "mhd_limits.h"
#include "mhd_assert.h"

#ifdef MHD_FAVOR_SMALL_CODE
#  ifdef MHD_static_inline_
#    undef MHD_static_inline_
#  endif /* MHD_static_inline_ */
/* Do not force inlining and do not use macro functions, use normal static
   functions instead.
   This may give more flexibility for size optimizations. */
#  define MHD_static_inline_ static
#  ifndef HAVE_INLINE_FUNCS
#    define HAVE_INLINE_FUNCS 1
#  endif /* !HAVE_INLINE_FUNCS */
#endif /* MHD_FAVOR_SMALL_CODE */

/*
 * Block of functions/macros that use US-ASCII charset as required by HTTP
 * standards. Not affected by current locale settings.
 */

#ifdef HAVE_INLINE_FUNCS

#if 0 /* Disable unused functions. */
/**
 * Check whether character is lower case letter in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is lower case letter, zero otherwise
 */
MHD_static_inline_ bool
isasciilower (char c)
{
  return (c >= 'a') && (c <= 'z');
}


#endif /* Disable unused functions. */


/**
 * Check whether character is upper case letter in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is upper case letter, zero otherwise
 */
MHD_static_inline_ bool
isasciiupper (char c)
{
  return (c <= 'Z') && (c >= 'A');
}


#if 0 /* Disable unused functions. */
/**
 * Check whether character is letter in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is letter in US-ASCII, zero otherwise
 */
MHD_static_inline_ bool
isasciialpha (char c)
{
  return isasciilower (c) || isasciiupper (c);
}


#endif /* Disable unused functions. */


/**
 * Check whether character is decimal digit in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is decimal digit, zero otherwise
 */
MHD_static_inline_ bool
isasciidigit (char c)
{
  return (c <= '9') && (c >= '0');
}


#if 0 /* Disable unused functions. */
/**
 * Check whether character is hexadecimal digit in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is decimal digit, zero otherwise
 */
MHD_static_inline_ bool
isasciixdigit (char c)
{
  return isasciidigit (c) ||
         ( (c <= 'F') && (c >= 'A') ) ||
         ( (c <= 'f') && (c >= 'a') );
}


/**
 * Check whether character is decimal digit or letter in US-ASCII
 *
 * @param c character to check
 * @return non-zero if character is decimal digit or letter, zero otherwise
 */
MHD_static_inline_ bool
isasciialnum (char c)
{
  return isasciialpha (c) || isasciidigit (c);
}


#endif /* Disable unused functions. */


#if 0 /* Disable unused functions. */
/**
 * Convert US-ASCII character to lower case.
 * If character is upper case letter in US-ASCII than it's converted to lower
 * case analog. If character is NOT upper case letter than it's returned
 * unmodified.
 *
 * @param c character to convert
 * @return converted to lower case character
 */
MHD_static_inline_ char
toasciilower (char c)
{
  return isasciiupper (c) ? (c - 'A' + 'a') : c;
}


/**
 * Convert US-ASCII character to upper case.
 * If character is lower case letter in US-ASCII than it's converted to upper
 * case analog. If character is NOT lower case letter than it's returned
 * unmodified.
 *
 * @param c character to convert
 * @return converted to upper case character
 */
MHD_static_inline_ char
toasciiupper (char c)
{
  return isasciilower (c) ? (c - 'a' + 'A') : c;
}


#endif /* Disable unused functions. */


#if defined(MHD_FAVOR_SMALL_CODE) /* Used only in mhd_str_to_uvalue_n() */
/**
 * Convert US-ASCII decimal digit to its value.
 *
 * @param c character to convert
 * @return value of decimal digit or -1 if @ c is not decimal digit
 */
MHD_static_inline_ int
todigitvalue (char c)
{
  if (isasciidigit (c))
    return (unsigned char) (c - '0');

  return -1;
}


#endif /* MHD_FAVOR_SMALL_CODE */


/**
 * Convert US-ASCII hexadecimal digit to its value.
 *
 * @param c character to convert
 * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit
 */
MHD_static_inline_ int
toxdigitvalue (char c)
{
#if ! defined(MHD_FAVOR_SMALL_CODE)
  switch ((unsigned char) c)
  {
#if 0 /* Disabled to give the compiler a hint about low probability */
  case 0x00U:    /* NUL */
  case 0x01U:    /* SOH */
  case 0x02U:    /* STX */
  case 0x03U:    /* ETX */
  case 0x04U:    /* EOT */
  case 0x05U:    /* ENQ */
  case 0x06U:    /* ACK */
  case 0x07U:    /* BEL */
  case 0x08U:    /* BS */
  case 0x09U:    /* HT */
  case 0x0AU:    /* LF */
  case 0x0BU:    /* VT */
  case 0x0CU:    /* FF */
  case 0x0DU:    /* CR */
  case 0x0EU:    /* SO */
  case 0x0FU:    /* SI */
  case 0x10U:    /* DLE */
  case 0x11U:    /* DC1 */
  case 0x12U:    /* DC2 */
  case 0x13U:    /* DC3 */
  case 0x14U:    /* DC4 */
  case 0x15U:    /* NAK */
  case 0x16U:    /* SYN */
  case 0x17U:    /* ETB */
  case 0x18U:    /* CAN */
  case 0x19U:    /* EM */
  case 0x1AU:    /* SUB */
  case 0x1BU:    /* ESC */
  case 0x1CU:    /* FS */
  case 0x1DU:    /* GS */
  case 0x1EU:    /* RS */
  case 0x1FU:    /* US */
  case 0x20U:    /* ' ' */
  case 0x21U:    /* '!' */
  case 0x22U:    /* '"' */
  case 0x23U:    /* '#' */
  case 0x24U:    /* '$' */
  case 0x25U:    /* '%' */
  case 0x26U:    /* '&' */
  case 0x27U:    /* '\'' */
  case 0x28U:    /* '(' */
  case 0x29U:    /* ')' */
  case 0x2AU:    /* '*' */
  case 0x2BU:    /* '+' */
  case 0x2CU:    /* ',' */
  case 0x2DU:    /* '-' */
  case 0x2EU:    /* '.' */
  case 0x2FU:    /* '/' */
    return -1;
#endif
  case 0x30U: /* '0' */
    return 0;
  case 0x31U: /* '1' */
    return 1;
  case 0x32U: /* '2' */
    return 2;
  case 0x33U: /* '3' */
    return 3;
  case 0x34U: /* '4' */
    return 4;
  case 0x35U: /* '5' */
    return 5;
  case 0x36U: /* '6' */
    return 6;
  case 0x37U: /* '7' */
    return 7;
  case 0x38U: /* '8' */
    return 8;
  case 0x39U: /* '9' */
    return 9;
#if 0         /* Disabled to give the compiler a hint about low probability */
  case 0x3AU: /* ':' */
  case 0x3BU: /* ';' */
  case 0x3CU: /* '<' */
  case 0x3DU: /* '=' */
  case 0x3EU: /* '>' */
  case 0x3FU: /* '?' */
  case 0x40U: /* '@' */
    return -1;
#endif
  case 0x41U: /* 'A' */
    return 0xAU;
  case 0x42U: /* 'B' */
    return 0xBU;
  case 0x43U: /* 'C' */
    return 0xCU;
  case 0x44U: /* 'D' */
    return 0xDU;
  case 0x45U: /* 'E' */
    return 0xEU;
  case 0x46U: /* 'F' */
    return 0xFU;
#if 0         /* Disabled to give the compiler a hint about low probability */
  case 0x47U: /* 'G' */
  case 0x48U: /* 'H' */
  case 0x49U: /* 'I' */
  case 0x4AU: /* 'J' */
  case 0x4BU: /* 'K' */
  case 0x4CU: /* 'L' */
  case 0x4DU: /* 'M' */
  case 0x4EU: /* 'N' */
  case 0x4FU: /* 'O' */
  case 0x50U: /* 'P' */
  case 0x51U: /* 'Q' */
  case 0x52U: /* 'R' */
  case 0x53U: /* 'S' */
  case 0x54U: /* 'T' */
  case 0x55U: /* 'U' */
  case 0x56U: /* 'V' */
  case 0x57U: /* 'W' */
  case 0x58U: /* 'X' */
  case 0x59U: /* 'Y' */
  case 0x5AU: /* 'Z' */
  case 0x5BU: /* '[' */
  case 0x5CU: /* '\' */
  case 0x5DU: /* ']' */
  case 0x5EU: /* '^' */
  case 0x5FU: /* '_' */
  case 0x60U: /* '`' */
    return -1;
#endif
  case 0x61U: /* 'a' */
    return 0xAU;
  case 0x62U: /* 'b' */
    return 0xBU;
  case 0x63U: /* 'c' */
    return 0xCU;
  case 0x64U: /* 'd' */
    return 0xDU;
  case 0x65U: /* 'e' */
    return 0xEU;
  case 0x66U: /* 'f' */
    return 0xFU;
#if 0         /* Disabled to give the compiler a hint about low probability */
  case 0x67U: /* 'g' */
  case 0x68U: /* 'h' */
  case 0x69U: /* 'i' */
  case 0x6AU: /* 'j' */
  case 0x6BU: /* 'k' */
  case 0x6CU: /* 'l' */
  case 0x6DU: /* 'm' */
  case 0x6EU: /* 'n' */
  case 0x6FU: /* 'o' */
  case 0x70U: /* 'p' */
  case 0x71U: /* 'q' */
  case 0x72U: /* 'r' */
  case 0x73U: /* 's' */
  case 0x74U: /* 't' */
  case 0x75U: /* 'u' */
  case 0x76U: /* 'v' */
  case 0x77U: /* 'w' */
  case 0x78U: /* 'x' */
  case 0x79U: /* 'y' */
  case 0x7AU: /* 'z' */
  case 0x7BU: /* '{' */
  case 0x7CU: /* '|' */
  case 0x7DU: /* '}' */
  case 0x7EU: /* '~' */
  case 0x7FU: /* DEL */
  case 0x80U: /* EXT */
  case 0x81U: /* EXT */
  case 0x82U: /* EXT */
  case 0x83U: /* EXT */
  case 0x84U: /* EXT */
  case 0x85U: /* EXT */
  case 0x86U: /* EXT */
  case 0x87U: /* EXT */
  case 0x88U: /* EXT */
  case 0x89U: /* EXT */
  case 0x8AU: /* EXT */
  case 0x8BU: /* EXT */
  case 0x8CU: /* EXT */
  case 0x8DU: /* EXT */
  case 0x8EU: /* EXT */
  case 0x8FU: /* EXT */
  case 0x90U: /* EXT */
  case 0x91U: /* EXT */
  case 0x92U: /* EXT */
  case 0x93U: /* EXT */
  case 0x94U: /* EXT */
  case 0x95U: /* EXT */
  case 0x96U: /* EXT */
  case 0x97U: /* EXT */
  case 0x98U: /* EXT */
  case 0x99U: /* EXT */
  case 0x9AU: /* EXT */
  case 0x9BU: /* EXT */
  case 0x9CU: /* EXT */
  case 0x9DU: /* EXT */
  case 0x9EU: /* EXT */
  case 0x9FU: /* EXT */
  case 0xA0U: /* EXT */
  case 0xA1U: /* EXT */
  case 0xA2U: /* EXT */
  case 0xA3U: /* EXT */
  case 0xA4U: /* EXT */
  case 0xA5U: /* EXT */
  case 0xA6U: /* EXT */
  case 0xA7U: /* EXT */
  case 0xA8U: /* EXT */
  case 0xA9U: /* EXT */
  case 0xAAU: /* EXT */
  case 0xABU: /* EXT */
  case 0xACU: /* EXT */
  case 0xADU: /* EXT */
  case 0xAEU: /* EXT */
  case 0xAFU: /* EXT */
  case 0xB0U: /* EXT */
  case 0xB1U: /* EXT */
  case 0xB2U: /* EXT */
  case 0xB3U: /* EXT */
  case 0xB4U: /* EXT */
  case 0xB5U: /* EXT */
  case 0xB6U: /* EXT */
  case 0xB7U: /* EXT */
  case 0xB8U: /* EXT */
  case 0xB9U: /* EXT */
  case 0xBAU: /* EXT */
  case 0xBBU: /* EXT */
  case 0xBCU: /* EXT */
  case 0xBDU: /* EXT */
  case 0xBEU: /* EXT */
  case 0xBFU: /* EXT */
  case 0xC0U: /* EXT */
  case 0xC1U: /* EXT */
  case 0xC2U: /* EXT */
  case 0xC3U: /* EXT */
  case 0xC4U: /* EXT */
  case 0xC5U: /* EXT */
  case 0xC6U: /* EXT */
  case 0xC7U: /* EXT */
  case 0xC8U: /* EXT */
  case 0xC9U: /* EXT */
  case 0xCAU: /* EXT */
  case 0xCBU: /* EXT */
  case 0xCCU: /* EXT */
  case 0xCDU: /* EXT */
  case 0xCEU: /* EXT */
  case 0xCFU: /* EXT */
  case 0xD0U: /* EXT */
  case 0xD1U: /* EXT */
  case 0xD2U: /* EXT */
  case 0xD3U: /* EXT */
  case 0xD4U: /* EXT */
  case 0xD5U: /* EXT */
  case 0xD6U: /* EXT */
  case 0xD7U: /* EXT */
  case 0xD8U: /* EXT */
  case 0xD9U: /* EXT */
  case 0xDAU: /* EXT */
  case 0xDBU: /* EXT */
  case 0xDCU: /* EXT */
  case 0xDDU: /* EXT */
  case 0xDEU: /* EXT */
  case 0xDFU: /* EXT */
  case 0xE0U: /* EXT */
  case 0xE1U: /* EXT */
  case 0xE2U: /* EXT */
  case 0xE3U: /* EXT */
  case 0xE4U: /* EXT */
  case 0xE5U: /* EXT */
  case 0xE6U: /* EXT */
  case 0xE7U: /* EXT */
  case 0xE8U: /* EXT */
  case 0xE9U: /* EXT */
  case 0xEAU: /* EXT */
  case 0xEBU: /* EXT */
  case 0xECU: /* EXT */
  case 0xEDU: /* EXT */
  case 0xEEU: /* EXT */
  case 0xEFU: /* EXT */
  case 0xF0U: /* EXT */
  case 0xF1U: /* EXT */
  case 0xF2U: /* EXT */
  case 0xF3U: /* EXT */
  case 0xF4U: /* EXT */
  case 0xF5U: /* EXT */
  case 0xF6U: /* EXT */
  case 0xF7U: /* EXT */
  case 0xF8U: /* EXT */
  case 0xF9U: /* EXT */
  case 0xFAU: /* EXT */
  case 0xFBU: /* EXT */
  case 0xFCU: /* EXT */
  case 0xFDU: /* EXT */
  case 0xFEU: /* EXT */
  case 0xFFU: /* EXT */
    return -1;
  default:
    mhd_assert (0);
    break;  /* Should be unreachable */
#else
  default:
    break;
#endif
  }
  return -1;
#else  /* MHD_FAVOR_SMALL_CODE */
  if (c <= 9)
  {
    if (c >= 0)
      return (unsigned char) (c - '0');
  }
  else if (c <= 'F')
  {
    if (c >= 'A')
      return (unsigned char) (c - 'A' + 10);
  }
  else if (c >= 'a')
  {
    if (c <= 'f')
      return (unsigned char) (c - 'a' + 10);
  }

  return -1;
#endif /* MHD_FAVOR_SMALL_CODE */
}


/**
 * Caseless compare two characters.
 *
 * @param c1 the first char to compare
 * @param c2 the second char to compare
 * @return boolean 'true' if chars are caseless equal, false otherwise
 */
MHD_static_inline_ bool
charsequalcaseless (const char c1, const char c2)
{
  return ( (c1 == c2) ||
           (((c1 - 'A' + 'a') == c2) ?
            isasciiupper (c1) :
            ((c1 == (c2 - 'A' + 'a')) && isasciiupper (c2))) );
}


#else  /* !HAVE_INLINE_FUNCS */


/**
 * Checks whether character is lower case letter in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is lower case letter,
 *         boolean false otherwise
 */
#define isasciilower(c) ((((char) (c)) >= 'a') && (((char) (c)) <= 'z'))


/**
 * Checks whether character is upper case letter in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is upper case letter,
 *         boolean false otherwise
 */
#define isasciiupper(c) ((((char) (c)) <= 'Z') && (((char) (c)) >= 'A'))


/**
 * Checks whether character is letter in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is letter, boolean false
 *         otherwise
 */
#define isasciialpha(c) (isasciilower (c) || isasciiupper (c))


/**
 * Check whether character is decimal digit in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is decimal digit, boolean false
 *         otherwise
 */
#define isasciidigit(c) ((((char) (c)) <= '9') && (((char) (c)) >= '0'))


/**
 * Check whether character is hexadecimal digit in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is hexadecimal digit,
 *         boolean false otherwise
 */
#define isasciixdigit(c) (isasciidigit ((c)) || \
                          (((char) (c)) <= 'F' && ((char) (c)) >= 'A') || \
                          (((char) (c)) <= 'f' && ((char) (c)) >= 'a'))


/**
 * Check whether character is decimal digit or letter in US-ASCII
 *
 * @param c character to check
 * @return boolean true if character is decimal digit or letter,
 *         boolean false otherwise
 */
#define isasciialnum(c) (isasciialpha (c) || isasciidigit (c))


/**
 * Convert US-ASCII character to lower case.
 * If character is upper case letter in US-ASCII than it's converted to lower
 * case analog. If character is NOT upper case letter than it's returned
 * unmodified.
 *
 * @param c character to convert
 * @return converted to lower case character
 */
#define toasciilower(c) ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : \
                         ((char) (c)))


/**
 * Convert US-ASCII character to upper case.
 * If character is lower case letter in US-ASCII than it's converted to upper
 * case analog. If character is NOT lower case letter than it's returned
 * unmodified.
 *
 * @param c character to convert
 * @return converted to upper case character
 */
#define toasciiupper(c) ((isasciilower (c)) ? (((char) (c)) - 'a' + 'A') : \
                         ((char) (c)))


/**
 * Convert US-ASCII decimal digit to its value.
 *
 * @param c character to convert
 * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit
 */
#define todigitvalue(c) (isasciidigit (c) ? (int) (((char) (c)) - '0') : \
                         (int) (-1))


/**
 * Convert US-ASCII hexadecimal digit to its value.
 * @param c character to convert
 * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit
 */
#define toxdigitvalue(c) (isasciidigit (c) ? (int) (((char) (c)) - '0') : \
                          ( (((char) (c)) >= 'A' && ((char) (c)) <= 'F') ? \
                            (int) (((unsigned char) (c)) - 'A' + 10) : \
                            ( (((char) (c)) >= 'a' && ((char) (c)) <= 'f') ? \
                              (int) (((unsigned char) (c)) - 'a' + 10) : \
                              (int) (-1) )))

/**
 * Caseless compare two characters.
 *
 * @param c1 the first char to compare
 * @param c2 the second char to compare
 * @return boolean 'true' if chars are caseless equal, false otherwise
 */
#define charsequalcaseless(c1, c2) \
        ( ((c1) == (c2)) || \
          ((((c1) - 'A' + 'a') == (c2)) ? \
           isasciiupper (c1) : \
           (((c1) == ((c2) - 'A' + 'a')) && isasciiupper (c2))) )

#endif /* !HAVE_INLINE_FUNCS */


#ifndef MHD_FAVOR_SMALL_CODE
MHD_INTERNAL bool
mhd_str_equal_caseless (const char *str1,
                        const char *str2)
{
  while (0 != (*str1))
  {
    const char c1 = *str1;
    const char c2 = *str2;
    if (charsequalcaseless (c1, c2))
    {
      str1++;
      str2++;
    }
    else
      return false;
  }
  return 0 == (*str2);
}


#endif /* ! MHD_FAVOR_SMALL_CODE */


MHD_INTERNAL bool
mhd_str_equal_caseless_n (const char *const str1,
                          const char *const str2,
                          size_t maxlen)
{
  size_t i;

  for (i = 0; i < maxlen; ++i)
  {
    const char c1 = str1[i];
    const char c2 = str2[i];
    if (0 == c2)
      return 0 == c1;
    if (charsequalcaseless (c1, c2))
      continue;
    else
      return false;
  }
  return true;
}


MHD_INTERNAL bool
mhd_str_equal_caseless_bin_n (const char *const str1,
                              const char *const str2,
                              size_t len)
{
  size_t i;

  for (i = 0; i < len; ++i)
  {
    const char c1 = str1[i];
    const char c2 = str2[i];
    if (charsequalcaseless (c1, c2))
      continue;
    else
      return 0;
  }
  return ! 0;
}


MHD_INTERNAL bool
mhd_str_has_token_caseless (const char *str,
                            const char *const token,
                            size_t token_len)
{
  if (0 == token_len)
    return false;

  while (0 != *str)
  {
    size_t i;
    /* Skip all whitespaces and empty tokens. */
    while (' ' == *str || '\t' == *str || ',' == *str)
      str++;

    /* Check for token match. */
    i = 0;
    while (1)
    {
      const char sc = *(str++);
      const char tc = token[i++];

      if (0 == sc)
        return false;
      if (! charsequalcaseless (sc, tc))
        break;
      if (i >= token_len)
      {
        /* Check whether substring match token fully or
         * has additional unmatched chars at tail. */
        while (' ' == *str || '\t' == *str)
          str++;
        /* End of (sub)string? */
        if ((0 == *str) || (',' == *str) )
          return true;
        /* Unmatched chars at end of substring. */
        break;
      }
    }
    /* Find next substring. */
    while (0 != *str && ',' != *str)
      str++;
  }
  return false;
}


MHD_INTERNAL bool
mhd_str_remove_token_caseless (const char *restrict str,
                               size_t str_len,
                               const char *const restrict token,
                               const size_t token_len,
                               char *restrict buf,
                               ssize_t *restrict buf_size)
{
  const char *s1; /**< the "input" string / character */
  char *s2;       /**< the "output" string / character */
  size_t t_pos;   /**< position of matched character in the token */
  bool token_removed;

  mhd_assert (NULL == memchr (token, 0, token_len));
  mhd_assert (NULL == memchr (token, ' ', token_len));
  mhd_assert (NULL == memchr (token, '\t', token_len));
  mhd_assert (NULL == memchr (token, ',', token_len));
  mhd_assert (0 <= *buf_size);

  if (SSIZE_MAX <= ((str_len / 2) * 3 + 3))
  {
    /* The return value may overflow, refuse */
    *buf_size = (ssize_t) -1;
    return false;
  }
  s1 = str;
  s2 = buf;
  token_removed = false;

  while ((size_t) (s1 - str) < str_len)
  {
    const char *cur_token; /**< the first char of current token */
    size_t copy_size;

    /* Skip any initial whitespaces and empty tokens */
    while ( ((size_t) (s1 - str) < str_len) &&
            ((' ' == *s1) || ('\t' == *s1) || (',' == *s1)) )
      s1++;

    /* 's1' points to the first char of token in the input string or
     * points just beyond the end of the input string */

    if ((size_t) (s1 - str) >= str_len)
      break; /* Nothing to copy, end of the input string */

    /* 's1' points to the first char of token in the input string */

    cur_token = s1; /* the first char of input token */

    /* Check the token with case-insensetive match */
    t_pos = 0;
    while ( ((size_t) (s1 - str) < str_len) && (token_len > t_pos) &&
            (charsequalcaseless (*s1, token[t_pos])) )
    {
      s1++;
      t_pos++;
    }
    /* s1 may point just beyond the end of the input string */
    if ( (token_len == t_pos) && (0 != token_len) )
    {
      /* 'token' matched, check that current input token does not have
       * any suffixes */
      while ( ((size_t) (s1 - str) < str_len) &&
              ((' ' == *s1) || ('\t' == *s1)) )
        s1++;
      /* 's1' points to the first non-whitespace char after the token matched
       * requested token or points just beyond the end of the input string after
       * the requested token */
      if (((size_t) (s1 - str) == str_len) || (',' == *s1))
      {/* full token match, do not copy current token to the output */
        token_removed = true;
        continue;
      }
    }

    /* 's1' points to first non-whitespace char, to some char after
     * first non-whitespace char in the token in the input string, to
     * the ',', or just beyond the end of the input string */
    /* The current token in the input string does not match the token
     * to exclude, it must be copied to the output string */
    /* the current token size excluding leading whitespaces and current char */
    copy_size = (size_t) (s1 - cur_token);
    if (buf == s2)
    { /* The first token to copy to the output */
      if ((size_t) *buf_size < copy_size)
      { /* Not enough space in the output buffer */
        *buf_size = (ssize_t) -1;
        return false;
      }
    }
    else
    { /* Some token was already copied to the output buffer */
      mhd_assert (s2 > buf);
      if ((size_t) *buf_size < ((size_t) (s2 - buf)) + copy_size + 2)
      { /* Not enough space in the output buffer */
        *buf_size = (ssize_t) -1;
        return false;
      }
      *(s2++) = ',';
      *(s2++) = ' ';
    }
    /* Copy non-matched token to the output */
    if (0 != copy_size)
    {
      memcpy (s2, cur_token, copy_size);
      s2 += copy_size;
    }

    while ( ((size_t) (s1 - str) < str_len) && (',' != *s1))
    {
      /* 's1' points to first non-whitespace char, to some char after
       * first non-whitespace char in the token in the input string */
      /* Copy all non-whitespace chars from the current token in
       * the input string */
      while ( ((size_t) (s1 - str) < str_len) &&
              (',' != *s1) && (' ' != *s1) && ('\t' != *s1) )
      {
        mhd_assert (s2 >= buf);
        if ((size_t) *buf_size <= (size_t) (s2 - buf)) /* '<= s2' equals '< s2 + 1' */
        { /* Not enough space in the output buffer */
          *buf_size = (ssize_t) -1;
          return false;
        }
        *(s2++) = *(s1++);
      }
      /* 's1' points to some whitespace char in the token in the input
       * string, to the ',', or just beyond the end of the input string */
      /* Skip all whitespaces */
      while ( ((size_t) (s1 - str) < str_len) &&
              ((' ' == *s1) || ('\t' == *s1)) )
        s1++;

      /* 's1' points to the first non-whitespace char in the input string
       * after whitespace chars, to the ',', or just beyond the end of
       * the input string */
      if (((size_t) (s1 - str) < str_len) && (',' != *s1))
      { /* Not the end of the current token */
        mhd_assert (s2 >= buf);
        if ((size_t) *buf_size <= (size_t) (s2 - buf)) /* '<= s2' equals '< s2 + 1' */
        { /* Not enough space in the output buffer */
          *buf_size = (ssize_t) -1;
          return false;
        }
        *(s2++) = ' ';
      }
    }
  }
  mhd_assert (((ssize_t) (s2 - buf)) <= *buf_size);
  *buf_size = (ssize_t) (s2 - buf);
  return token_removed;
}


MHD_INTERNAL bool
mhd_str_remove_tokens_caseless (char *restrict str,
                                size_t *restrict str_len,
                                const char *const restrict tkns,
                                const size_t tokens_len)
{
  size_t pt;                      /**< position in @a tokens */
  bool token_removed;

  mhd_assert (NULL == memchr (tkns, 0, tokens_len));

  token_removed = false;
  pt = 0;

  while (pt < tokens_len && *str_len != 0)
  {
    const char *tkn; /**< the current token */
    size_t tkn_len;

    /* Skip any initial whitespaces and empty tokens in 'tokens' */
    while ( (pt < tokens_len) &&
            ((' ' == tkns[pt]) || ('\t' == tkns[pt]) || (',' == tkns[pt])) )
      pt++;

    if (pt >= tokens_len)
      break; /* No more tokens, nothing to remove */

    /* Found non-whitespace char which is not a comma */
    tkn = tkns + pt;
    do
    {
      do
      {
        pt++;
      } while (pt < tokens_len &&
               (' ' != tkns[pt] && '\t' != tkns[pt] && ',' != tkns[pt]));
      /* Found end of the token string, space, tab, or comma */
      tkn_len = pt - (size_t) (tkn - tkns);

      /* Skip all spaces and tabs */
      while (pt < tokens_len && (' ' == tkns[pt] || '\t' == tkns[pt]))
        pt++;
      /* Found end of the token string or non-whitespace char */
    } while (pt < tokens_len && ',' != tkns[pt]);

    /* 'tkn' is the input token with 'tkn_len' chars */
    mhd_assert (0 != tkn_len);

    if (*str_len == tkn_len)
    {
      if (mhd_str_equal_caseless_bin_n (str, tkn, tkn_len))
      {
        *str_len = 0;
        token_removed = true;
      }
      continue;
    }
    /* 'tkn' cannot match part of 'str' if length of 'tkn' is larger
     * than length of 'str'.
     * It's know that 'tkn' is not equal to the 'str' (was checked previously).
     * As 'str' is normalized when 'tkn' is not equal to the 'str'
     * it is required that 'str' to be at least 3 chars larger then 'tkn'
     * (the comma, the space and at least one additional character for the next
     * token) to remove 'tkn' from the 'str'. */
    if (*str_len > tkn_len + 2)
    { /* Remove 'tkn' from the input string */
      size_t pr;    /**< the 'read' position in the @a str */
      size_t pw;    /**< the 'write' position in the @a str */

      pr = 0;
      pw = 0;

      do
      {
        mhd_assert (pr >= pw);
        mhd_assert ((*str_len) >= (pr + tkn_len));
        if ( ( ((*str_len) == (pr + tkn_len)) || (',' == str[pr + tkn_len]) ) &&
             mhd_str_equal_caseless_bin_n (str + pr, tkn, tkn_len) )
        {
          /* current token in the input string matches the 'tkn', skip it */
          mhd_assert ((*str_len == pr + tkn_len) || \
                      (' ' == str[pr + tkn_len + 1])); /* 'str' must be normalized */
          token_removed = true;
          /* Advance to the next token in the input string or beyond
           * the end of the input string. */
          pr += tkn_len + 2;
        }
        else
        {
          /* current token in the input string does not match the 'tkn',
           * copy to the output */
          if (0 != pw)
          { /* not the first output token, add ", " to separate */
            if (pr != pw + 2)
            {
              str[pw++] = ',';
              str[pw++] = ' ';
            }
            else
              pw += 2; /* 'str' is not yet modified in this round */
          }
          do
          {
            if (pr != pw)
              str[pw] = str[pr];
            pr++;
            pw++;
          } while (pr < *str_len && ',' != str[pr]);
          /* Advance to the next token in the input string or beyond
           * the end of the input string. */
          pr += 2;
        }
        /* 'pr' should point to the next token in the input string or beyond
         * the end of the input string */
        if ((*str_len) < (pr + tkn_len))
        { /* The rest of the 'str + pr' is too small to match 'tkn' */
          if ((*str_len) > pr)
          { /* Copy the rest of the string */
            size_t copy_size;
            copy_size = *str_len - pr;
            if (0 != pw)
            { /* not the first output token, add ", " to separate */
              if (pr != pw + 2)
              {
                str[pw++] = ',';
                str[pw++] = ' ';
              }
              else
                pw += 2; /* 'str' is not yet modified in this round */
            }
            if (pr != pw)
              memmove (str + pw, str + pr, copy_size);
            pw += copy_size;
          }
          *str_len = pw;
          break;
        }
        mhd_assert ((' ' != str[0]) && ('\t' != str[0]));
        mhd_assert ((0 == pr) || (3 <= pr));
        mhd_assert ((0 == pr) || (' ' == str[pr - 1]));
        mhd_assert ((0 == pr) || (',' == str[pr - 2]));
      } while (1);
    }
  }

  return token_removed;
}


#ifndef MHD_FAVOR_SMALL_CODE
/* Use individual function for each case */

MHD_INTERNAL size_t
mhd_str_to_uint64 (const char *restrict str,
                   uint_fast64_t *restrict out_val)
{
  const char *const start = str;
  uint_fast64_t res;

  if (! str || ! out_val || ! isasciidigit (str[0]))
    return 0;

  res = 0;
  do
  {
    const int digit = (unsigned char) (*str) - '0';
    uint_fast64_t prev_res = res;

    res *= 10;
    if (res / 10 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;

    str++;
  } while (isasciidigit (*str));

  *out_val = res;
  return (size_t) (str - start);
}


MHD_INTERNAL size_t
mhd_str_to_uint64_n (const char *restrict str,
                     size_t maxlen,
                     uint_fast64_t *restrict out_val)
{
  uint_fast64_t res;
  size_t i;

  if (! str || ! maxlen || ! out_val || ! isasciidigit (str[0]))
    return 0;

  res = 0;
  i = 0;
  do
  {
    const int digit = (unsigned char) str[i] - '0';
    uint_fast64_t prev_res = res;

    res *= 10;
    if (res / 10 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;
    i++;
  } while ( (i < maxlen) &&
            isasciidigit (str[i]) );

  *out_val = res;
  return i;
}


MHD_INTERNAL size_t
mhd_strx_to_uint32 (const char *restrict str,
                    uint_fast32_t *restrict out_val)
{
  const char *const start = str;
  uint_fast32_t res;
  int digit;

  if (! str || ! out_val)
    return 0;

  res = 0;
  digit = toxdigitvalue (*str);
  while (digit >= 0)
  {
    uint_fast32_t prev_res = res;

    res *= 16;
    if (res / 16 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;

    str++;
    digit = toxdigitvalue (*str);
  }

  if (str - start > 0)
    *out_val = res;
  return (size_t) (str - start);
}


MHD_INTERNAL size_t
mhd_strx_to_uint32_n (const char *restrict str,
                      size_t maxlen,
                      uint_fast32_t *restrict out_val)
{
  size_t i;
  uint_fast32_t res;
  int digit;
  if (! str || ! out_val)
    return 0;

  res = 0;
  i = 0;
  while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0)
  {
    uint_fast32_t prev_res = res;

    res *= 16;
    if (res / 16 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;

    res *= 16;
    res += (unsigned int) digit;
    i++;
  }

  if (i)
    *out_val = res;
  return i;
}


/**
 * Convert hexadecimal US-ASCII digits in string to number in uint_fast64_t.
 * Conversion stopped at first non-digit character.
 *
 * @param str string to convert
 * @param[out] out_val pointer to uint_fast64_t to store result of conversion
 * @return non-zero number of characters processed on succeed,
 *         zero if no digit is found, resulting value is larger
 *         then possible to store in uint_fast64_t or @a out_val is NULL
 */
MHD_INTERNAL size_t
mhd_strx_to_uint64 (const char *restrict str,
                    uint_fast64_t *restrict out_val)
{
  const char *const start = str;
  uint_fast64_t res;
  int digit;
  if (! str || ! out_val)
    return 0;

  res = 0;
  digit = toxdigitvalue (*str);
  while (digit >= 0)
  {
    uint_fast64_t prev_res = res;

    res *= 16;
    if (res / 16 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;

    str++;
    digit = toxdigitvalue (*str);
  }

  if (str - start > 0)
    *out_val = res;
  return (size_t) (str - start);
}


/**
 * Convert not more then @a maxlen hexadecimal US-ASCII digits in string
 * to number in uint_fast64_t.
 * Conversion stopped at first non-digit character or after @a maxlen
 * digits.
 *
 * @param str string to convert
 * @param maxlen maximum number of characters to process
 * @param[out] out_val pointer to uint_fast64_t to store result of conversion
 * @return non-zero number of characters processed on succeed,
 *         zero if no digit is found, resulting value is larger
 *         then possible to store in uint_fast64_t or @a out_val is NULL
 */
MHD_INTERNAL size_t
mhd_strx_to_uint64_n (const char *restrict str,
                      size_t maxlen,
                      uint_fast64_t *restrict out_val)
{
  size_t i;
  uint_fast64_t res;
  int digit;
  if (! str || ! out_val)
    return 0;

  res = 0;
  i = 0;
  while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0)
  {
    uint_fast64_t prev_res = res;

    res *= 16;
    if (res / 16 != prev_res)
      return 0;
    res += (unsigned int) digit;
    if (res < (unsigned int) digit)
      return 0;
    i++;
  }

  if (i)
    *out_val = res;
  return i;
}


#else  /* MHD_FAVOR_SMALL_CODE */

/**
 * Generic function for converting not more then @a maxlen
 * hexadecimal or decimal US-ASCII digits in string to number.
 * Conversion stopped at first non-digit character or after @a maxlen
 * digits.
 * To be used only within macro.
 *
 * @param str the string to convert
 * @param maxlen the maximum number of characters to process
 * @param out_val the pointer to variable to store result of conversion
 * @param val_size the size of variable pointed by @a out_val, in bytes, 4 or 8
 * @param max_val the maximum decoded number
 * @param base the numeric base, 10 or 16
 * @return non-zero number of characters processed on succeed,
 *         zero if no digit is found, resulting value is larger
 *         then @a max_val, @a val_size is not 4/8 or @a out_val is NULL
 */
MHD_INTERNAL size_t
mhd_str_to_uvalue_n (const char *restrict str,
                     size_t maxlen,
                     void *restrict out_val,
                     size_t val_size,
                     uint_fast64_t max_val,
                     unsigned int base)
{
  size_t i;
  uint_fast64_t res;
  const uint_fast64_t max_v_div_b = max_val / base;
  const uint_fast64_t max_v_mod_b = max_val % base;

  if (! str || ! out_val ||
      ((base != 16) && (base != 10)) )
    return 0;

  res = 0;
  i = 0;
  while (maxlen > i)
  {
    const int digit = (base == 16) ?
                      toxdigitvalue (str[i]) : todigitvalue (str[i]);

    if (0 > digit)
      break;
    if ( ((max_v_div_b) < res) ||
         (( (max_v_div_b) == res) &&
          ( (max_v_mod_b) < (uint_fast64_t) digit) ) )
      return 0;

    res *= base;
    res += (unsigned int) digit;
    i++;
  }

  if (i)
  {
    if (8 == val_size)
      *(uint_fast64_t *) out_val = res;
    else if (4 == val_size)
      *(uint_fast32_t *) out_val = (uint_fast32_t) res;
    else
      return 0;
  }
  return i;
}


#endif /* MHD_FAVOR_SMALL_CODE */


MHD_INTERNAL size_t
mhd_uint32_to_strx (uint_fast32_t val,
                    char *buf,
                    size_t buf_size)
{
  size_t o_pos = 0; /**< position of the output character */
  int digit_pos = 8; /** zero-based, digit position in @a 'val' */
  int digit;

  /* Skip leading zeros */
  do
  {
    digit_pos--;
    digit = (int) (val >> 28);
    val <<= 4;
  } while ((0 == digit) && (0 != digit_pos));

  while (o_pos < buf_size)
  {
    buf[o_pos++] =
      (char) ((digit <= 9) ?
              ('0' + (char) digit) :
              ('A' + (char) digit - 10));
    if (0 == digit_pos)
      return o_pos;
    digit_pos--;
    digit = (int) (val >> 28);
    val <<= 4;
  }
  return 0; /* The buffer is too small */
}


#ifndef MHD_FAVOR_SMALL_CODE
MHD_INTERNAL size_t
mhd_uint16_to_str (uint_least16_t val,
                   char *buf,
                   size_t buf_size)
{
  char *chr;  /**< pointer to the current printed digit */
  /* The biggest printable number is 65535 */
  uint_least16_t divisor = UINT16_C (10000);
  int digit;

  chr = buf;
  digit = (int) (val / divisor);
  mhd_assert (digit < 10);

  /* Do not print leading zeros */
  while ((0 == digit) && (1 < divisor))
  {
    divisor /= 10;
    digit = (int) (val / divisor);
    mhd_assert (digit < 10);
  }

  while (0 != buf_size)
  {
    *chr = (char) ((char) digit + '0');
    chr++;
    buf_size--;
    if (1 == divisor)
      return (size_t) (chr - buf);
    val = (uint_least16_t) (val % divisor);
    divisor /= 10;
    digit = (int) (val / divisor);
    mhd_assert (digit < 10);
  }
  return 0; /* The buffer is too small */
}


#endif /* !MHD_FAVOR_SMALL_CODE */


MHD_INTERNAL size_t
mhd_uint64_to_str (uint_fast64_t val,
                   char *buf,
                   size_t buf_size)
{
  char *chr;  /**< pointer to the current printed digit */
  /* The biggest printable number is 18446744073709551615 */
  uint_fast64_t divisor = (uint_fast64_t) 10000000000000000000U;
  int digit;

  chr = buf;
  digit = (int) (val / divisor);
  mhd_assert (digit < 10);

  /* Do not print leading zeros */
  while ((0 == digit) && (1 < divisor))
  {
    divisor /= 10;
    digit = (int) (val / divisor);
    mhd_assert (digit < 10);
  }

  while (0 != buf_size)
  {
    *chr = (char) ((char) digit + '0');
    chr++;
    buf_size--;
    if (1 == divisor)
      return (size_t) (chr - buf);
    val %= divisor;
    divisor /= 10;
    digit = (int) (val / divisor);
    mhd_assert (digit < 10);
  }
  return 0; /* The buffer is too small */
}


MHD_INTERNAL size_t
mhd_uint8_to_str_pad (uint8_t val,
                      uint8_t min_digits,
                      char *buf,
                      size_t buf_size)
{
  size_t pos; /**< the position of the current printed digit */
  int digit;
  mhd_assert (3 >= min_digits);
  if (0 == buf_size)
    return 0;

  pos = 0;
  digit = val / 100;
  if (0 == digit)
  {
    if (3 <= min_digits)
      buf[pos++] = '0';
  }
  else
  {
    buf[pos++] = (char) ('0' + (char) digit);
    val %= 100;
    min_digits = 2;
  }

  if (buf_size <= pos)
    return 0;
  digit = val / 10;
  if (0 == digit)
  {
    if (2 <= min_digits)
      buf[pos++] = '0';
  }
  else
  {
    buf[pos++] = (char) ('0' + (char) digit);
    val %= 10;
  }

  if (buf_size <= pos)
    return 0;
  buf[pos++] = (char) ('0' + (char) val);
  return pos;
}


MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_IN_SIZE_ (1, 2) MHD_FN_PAR_OUT_ (3) size_t
mhd_bin_to_hex (const void *restrict bin,
                size_t size,
                char *restrict hex)
{
  size_t i;

  for (i = 0; i < size; ++i)
  {
    uint8_t j;
    const uint8_t b = ((const uint8_t *) bin)[i];
    j = b >> 4;
    hex[i * 2] = (char) ((j < 10) ? (j + '0') : (j - 10 + 'a'));
    j = b & 0x0f;
    hex[i * 2 + 1] = (char) ((j < 10) ? (j + '0') : (j - 10 + 'a'));
  }
  return i * 2;
}


MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_IN_SIZE_ (1, 2) MHD_FN_PAR_OUT_ (3) size_t
mhd_bin_to_hex_z (const void *restrict bin,
                  size_t size,
                  char *restrict hex)
{
  size_t res;

  res = mhd_bin_to_hex (bin, size, hex);
  hex[res] = 0;

  return res;
}


MHD_INTERNAL size_t
mhd_hex_to_bin (const char *restrict hex,
                size_t len,
                void *restrict bin)
{
  size_t r;
  size_t w;

  if (0 == len)
    return 0;
  r = 0;
  w = 0;
  if (0 != len % 2)
  {
    /* Assume the first byte is encoded with single digit */
    const char c2 = hex[r++];
    const int l = toxdigitvalue (c2);
    if (0 > l)
      return 0;
    ((uint8_t *) bin)[w++] = (uint8_t) ((unsigned int) l);
  }
  while (r < len)
  {
    const char c1 = hex[r++];
    const char c2 = hex[r++];
    const int h = toxdigitvalue (c1);
    const int l = toxdigitvalue (c2);
    if ((0 > h) || (0 > l))
      return 0;
    ((uint8_t *) bin)[w++] = (uint8_t) ( ((uint8_t) (((uint8_t)
                                                      ((unsigned int) h)) << 4))
                                         | ((uint8_t) ((unsigned int) l)) );
  }
  mhd_assert (len == r);
  mhd_assert ((len + 1) / 2 == w);
  return w;
}


MHD_INTERNAL size_t
mhd_str_pct_decode_strict_n (const char *pct_encoded,
                             size_t pct_encoded_len,
                             char *decoded,
                             size_t buf_size)
{
#ifdef MHD_FAVOR_SMALL_CODE
  bool broken;
  size_t res;

  res = mhd_str_pct_decode_lenient_n (pct_encoded, pct_encoded_len, decoded,
                                      buf_size, &broken);
  if (broken)
    return 0;
  return res;
#else  /* ! MHD_FAVOR_SMALL_CODE */
  size_t r;
  size_t w;
  r = 0;
  w = 0;

  if (buf_size >= pct_encoded_len)
  {
    while (r < pct_encoded_len)
    {
      const char chr = pct_encoded[r];
      if ('%' == chr)
      {
        if (2 > pct_encoded_len - r)
          return 0;
        else
        {
          const char c1 = pct_encoded[++r];
          const char c2 = pct_encoded[++r];
          const int h = toxdigitvalue (c1);
          const int l = toxdigitvalue (c2);
          unsigned char out;
          if ((0 > h) || (0 > l))
            return 0;
          out =
            (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                             | ((uint8_t) ((unsigned int) l)));
          decoded[w] = (char) out;
        }
      }
      else
        decoded[w] = chr;
      ++r;
      ++w;
    }
    return w;
  }

  while (r < pct_encoded_len)
  {
    const char chr = pct_encoded[r];
    if (w >= buf_size)
      return 0;
    if ('%' == chr)
    {
      if (2 > pct_encoded_len - r)
        return 0;
      else
      {
        const char c1 = pct_encoded[++r];
        const char c2 = pct_encoded[++r];
        const int h = toxdigitvalue (c1);
        const int l = toxdigitvalue (c2);
        unsigned char out;
        if ((0 > h) || (0 > l))
          return 0;
        out =
          (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                           | ((uint8_t) ((unsigned int) l)));
        decoded[w] = (char) out;
      }
    }
    else
      decoded[w] = chr;
    ++r;
    ++w;
  }
  return w;
#endif /* ! MHD_FAVOR_SMALL_CODE */
}


MHD_INTERNAL size_t
mhd_str_pct_decode_lenient_n (const char *pct_encoded,
                              size_t pct_encoded_len,
                              char *decoded,
                              size_t buf_size,
                              bool *restrict broken_encoding)
{
  size_t r;
  size_t w;
  r = 0;
  w = 0;
  if (NULL != broken_encoding)
    *broken_encoding = false;
#ifndef MHD_FAVOR_SMALL_CODE
  if (buf_size >= pct_encoded_len)
  {
    while (r < pct_encoded_len)
    {
      const char chr = pct_encoded[r];
      if ('%' == chr)
      {
        if (2 > pct_encoded_len - r)
        {
          if (NULL != broken_encoding)
            *broken_encoding = true;
          decoded[w] = chr; /* Copy "as is" */
        }
        else
        {
          const char c1 = pct_encoded[++r];
          const char c2 = pct_encoded[++r];
          const int h = toxdigitvalue (c1);
          const int l = toxdigitvalue (c2);
          unsigned char out;
          if ((0 > h) || (0 > l))
          {
            r -= 2;
            if (NULL != broken_encoding)
              *broken_encoding = true;
            decoded[w] = chr; /* Copy "as is" */
          }
          else
          {
            out =
              (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                               | ((uint8_t) ((unsigned int) l)));
            decoded[w] = (char) out;
          }
        }
      }
      else
        decoded[w] = chr;
      ++r;
      ++w;
    }
    return w;
  }
#endif /* ! MHD_FAVOR_SMALL_CODE */
  while (r < pct_encoded_len)
  {
    const char chr = pct_encoded[r];
    if (w >= buf_size)
      return 0;
    if ('%' == chr)
    {
      if (2 > pct_encoded_len - r)
      {
        if (NULL != broken_encoding)
          *broken_encoding = true;
        decoded[w] = chr; /* Copy "as is" */
      }
      else
      {
        const char c1 = pct_encoded[++r];
        const char c2 = pct_encoded[++r];
        const int h = toxdigitvalue (c1);
        const int l = toxdigitvalue (c2);
        if ((0 > h) || (0 > l))
        {
          r -= 2;
          if (NULL != broken_encoding)
            *broken_encoding = true;
          decoded[w] = chr; /* Copy "as is" */
        }
        else
        {
          unsigned char out;
          out =
            (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                             | ((uint8_t) ((unsigned int) l)));
          decoded[w] = (char) out;
        }
      }
    }
    else
      decoded[w] = chr;
    ++r;
    ++w;
  }
  return w;
}


MHD_INTERNAL size_t
mhd_str_pct_decode_in_place_strict (char *str)
{
#ifdef MHD_FAVOR_SMALL_CODE
  size_t res;
  bool broken;

  res = mhd_str_pct_decode_in_place_lenient (str, &broken);
  if (broken)
  {
    res = 0;
    str[0] = 0;
  }
  return res;
#else  /* ! MHD_FAVOR_SMALL_CODE */
  size_t r;
  size_t w;
  r = 0;
  w = 0;

  while (0 != str[r])
  {
    const char chr = str[r++];
    if ('%' == chr)
    {
      const char d1 = str[r++];
      if (0 == d1)
        return 0;
      else
      {
        const char d2 = str[r++];
        if (0 == d2)
          return 0;
        else
        {
          const int h = toxdigitvalue (d1);
          const int l = toxdigitvalue (d2);
          unsigned char out;
          if ((0 > h) || (0 > l))
            return 0;
          out =
            (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                             | ((uint8_t) ((unsigned int) l)));
          str[w++] = (char) out;
        }
      }
    }
    else
      str[w++] = chr;
  }
  str[w] = 0;
  return w;
#endif /* ! MHD_FAVOR_SMALL_CODE */
}


MHD_INTERNAL size_t
mhd_str_pct_decode_in_place_lenient (char *restrict str,
                                     bool *restrict broken_encoding)
{
#ifdef MHD_FAVOR_SMALL_CODE
  size_t len;
  size_t res;

  len = strlen (str);
  res = mhd_str_pct_decode_lenient_n (str, len, str, len, broken_encoding);
  str[res] = 0;

  return res;
#else  /* ! MHD_FAVOR_SMALL_CODE */
  size_t r;
  size_t w;
  if (NULL != broken_encoding)
    *broken_encoding = false;
  r = 0;
  w = 0;
  while (0 != str[r])
  {
    const char chr = str[r++];
    if ('%' == chr)
    {
      const char d1 = str[r++];
      if (0 == d1)
      {
        if (NULL != broken_encoding)
          *broken_encoding = true;
        str[w++] = chr; /* Copy "as is" */
        str[w] = 0;
        return w;
      }
      else
      {
        const char d2 = str[r++];
        if (0 == d2)
        {
          if (NULL != broken_encoding)
            *broken_encoding = true;
          str[w++] = chr; /* Copy "as is" */
          str[w++] = d1; /* Copy "as is" */
          str[w] = 0;
          return w;
        }
        else
        {
          const int h = toxdigitvalue (d1);
          const int l = toxdigitvalue (d2);
          unsigned char out;
          if ((0 > h) || (0 > l))
          {
            if (NULL != broken_encoding)
              *broken_encoding = true;
            str[w++] = chr; /* Copy "as is" */
            str[w++] = d1;
            str[w++] = d2;
            continue;
          }
          out =
            (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4))
                             | ((uint8_t) ((unsigned int) l)));
          str[w++] = (char) out;
          continue;
        }
      }
    }
    str[w++] = chr;
  }
  str[w] = 0;
  return w;
#endif /* ! MHD_FAVOR_SMALL_CODE */
}


#ifdef MHD_SUPPORT_AUTH_DIGEST
MHD_INTERNAL bool
mhd_str_equal_quoted_bin_n (const char *quoted,
                            size_t quoted_len,
                            const char *unquoted,
                            size_t unquoted_len)
{
  size_t i;
  size_t j;
  if (unquoted_len < quoted_len / 2)
    return false;

  j = 0;
  for (i = 0; quoted_len > i && unquoted_len > j; ++i, ++j)
  {
    if ('\\' == quoted[i])
    {
      i++; /* Advance to the next character */
      if (quoted_len == i)
        return false; /* No character after escaping backslash */
    }
    if (quoted[i] != unquoted[j])
      return false; /* Different characters */
  }
  if ((quoted_len != i) || (unquoted_len != j))
    return false; /* The strings have different length */

  return true;
}


MHD_INTERNAL bool
mhd_str_equal_caseless_quoted_bin_n (const char *quoted,
                                     size_t quoted_len,
                                     const char *unquoted,
                                     size_t unquoted_len)
{
  size_t i;
  size_t j;
  if (unquoted_len < quoted_len / 2)
    return false;

  j = 0;
  for (i = 0; quoted_len > i && unquoted_len > j; ++i, ++j)
  {
    if ('\\' == quoted[i])
    {
      i++; /* Advance to the next character */
      if (quoted_len == i)
        return false; /* No character after escaping backslash */
    }
    if (! charsequalcaseless (quoted[i], unquoted[j]))
      return false; /* Different characters */
  }
  if ((quoted_len != i) || (unquoted_len != j))
    return false; /* The strings have different length */

  return true;
}


#endif /* MHD_SUPPORT_AUTH_DIGEST */

#if defined(MHD_SUPPORT_AUTH_DIGEST) || defined(MHD_SUPPORT_POST_PARSER)

MHD_INTERNAL size_t
mhd_str_unquote (const char *quoted,
                 size_t quoted_len,
                 char *result)
{
  size_t r;
  size_t w;

  r = 0;
  w = 0;

  while (quoted_len > r)
  {
    if ('\\' == quoted[r])
    {
      ++r;
      if (quoted_len == r)
        return 0; /* Last backslash is not followed by char to unescape */
    }
    result[w++] = quoted[r++];
  }
  return w;
}


#endif /* MHD_SUPPORT_AUTH_DIGEST || MHD_SUPPORT_POST_PARSER */

#if defined(MHD_SUPPORT_AUTH_DIGEST) || defined(MHD_SUPPORT_AUTH_BASIC)

MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_IN_SIZE_ (1,2)
MHD_FN_PAR_OUT_SIZE_ (3,4) size_t
mhd_str_quote (const char *unquoted,
               size_t unquoted_len,
               char *result,
               size_t buf_size)
{
  size_t r;
  size_t w;

  r = 0;
  w = 0;

#ifndef MHD_FAVOR_SMALL_CODE
  if (unquoted_len * 2 <= buf_size)
  {
    /* Fast loop: the output will fit the buffer with any input string content */
    while (unquoted_len > r)
    {
      const char chr = unquoted[r++];
      if (('\\' == chr) || ('\"' == chr))
        result[w++] = '\\'; /* Escape current char */
      result[w++] = chr;
    }
  }
  else
  {
    if (unquoted_len > buf_size)
      return 0; /* Quick fail: the output buffer is too small */
#else  /* MHD_FAVOR_SMALL_CODE */
  if (1)
  {
#endif /* MHD_FAVOR_SMALL_CODE */

    while (unquoted_len > r)
    {
      if (buf_size <= w)
        return 0; /* The output buffer is too small */
      else
      {
        const char chr = unquoted[r++];
        if (('\\' == chr) || ('\"' == chr))
        {
          result[w++] = '\\'; /* Escape current char */
          if (buf_size <= w)
            return 0; /* The output buffer is too small */
        }
        result[w++] = chr;
      }
    }
  }

  mhd_assert (w >= r);
  mhd_assert (w <= r * 2);
  return w;
}


#endif /* MHD_SUPPORT_AUTH_DIGEST || MHD_SUPPORT_AUTH_BASIC */

#ifdef MHD_SUPPORT_AUTH_BASIC

/*
 * MHD_BASE64_FUNC_VERSION
 * 1 = smallest,
 * 2 = medium,
 * 3 = fastest
 */
#ifndef MHD_BASE64_FUNC_VERSION
#ifdef MHD_FAVOR_SMALL_CODE
#define MHD_BASE64_FUNC_VERSION 1
#else  /* ! MHD_FAVOR_SMALL_CODE */
#define MHD_BASE64_FUNC_VERSION 3
#endif /* ! MHD_FAVOR_SMALL_CODE */
#endif /* ! MHD_BASE64_FUNC_VERSION */

#if MHD_BASE64_FUNC_VERSION < 1 || MHD_BASE64_FUNC_VERSION > 3
#error Wrong MHD_BASE64_FUNC_VERSION value
#endif /* MHD_BASE64_FUNC_VERSION < 1 || MHD_BASE64_FUNC_VERSION > 3 */

#if MHD_BASE64_FUNC_VERSION == 3
#define mhd_base64_map_type int
#else  /* MHD_BASE64_FUNC_VERSION < 3 */
#define mhd_base64_map_type int8_t
#endif /* MHD_BASE64_FUNC_VERSION < 3 */

#if MHD_BASE64_FUNC_VERSION == 1
static mhd_base64_map_type
base64_char_to_value_ (uint8_t c)
{
  if ('Z' >= c)
  {
    if ('A' <= c)
      return (mhd_base64_map_type) ((c - 'A') + 0);
    if ('0' <= c)
    {
      if ('9' >= c)
        return (mhd_base64_map_type) ((c - '0') + 52);
      if ('=' == c)
        return -2;
      return -1;
    }
    if ('+' == c)
      return 62;
    if ('/' == c)
      return 63;
    return -1;
  }
  if (('z' >= c) && ('a' <= c))
    return (mhd_base64_map_type) ((c - 'a') + 26);
  return -1;
}


#endif /* MHD_BASE64_FUNC_VERSION == 1 */


mhd_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE


MHD_INTERNAL size_t
mhd_base64_to_bin_n (const char *base64,
                     size_t base64_len,
                     void *bin,
                     size_t bin_size)
{
#if MHD_BASE64_FUNC_VERSION >= 2
  static const mhd_base64_map_type map[] = {
    /* -1 = invalid char, -2 = padding
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    NUL,  SOH,  STX,  ETX,  EOT,  ENQ,  ACK,  BEL,  */
    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    /*
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    BS,   HT,   LF,   VT,   FF,   CR,   SO,   SI,   */
    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    /*
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    DLE,  DC1,  DC2,  DC3,  DC4,  NAK,  SYN,  ETB,  */
    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    /*
    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    CAN,  EM,   SUB,  ESC,  FS,   GS,   RS,   US,   */
    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    /*
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'', */
    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    /*
    0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',  */
    -1,   -1,   -1,   62,   -1,   -1,   -1,   63,
    /*
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  */
    52,   53,   54,   55,   56,   57,   58,   59,
    /*
    0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
    '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',  */
    60,   61,   -1,   -1,   -1,   -2,   -1,   -1,
    /*
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',  */
    -1,    0,    1,    2,    3,    4,    5,    6,
    /*
    0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
    'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',  */
    7,     8,    9,   10,   11,   12,   13,   14,
    /*
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
    'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',  */
    15,   16,   17,   18,   19,   20,   21,   22,
    /*
     0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    'X',  'Y',  'Z',  '[',  '\',  ']',  '^',  '_',  */
    23,   24,   25,   -1,   -1,   -1,   -1,   -1,
    /*
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
    '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  */
    -1,   26,   27,   28,   29,   30,   31,   32,
    /*
    0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
    'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',  */
    33,   34,   35,   36,   37,   38,   39,   40,
    /*
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
    'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  */
    41,   42,   43,   44,   45,   46,   47,   48,
    /*
    0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
    'x',  'y',  'z',  '{',  '|',  '}',  '~',  DEL,  */
    49,   50,   51,   -1,   -1,   -1,   -1,   -1

#if MHD_BASE64_FUNC_VERSION == 3
    ,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 80..8F */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 90..9F */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* A0..AF */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* B0..BF */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* C0..CF */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* D0..DF */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* E0..EF */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* F0..FF */
#endif /* ! MHD_BASE64_FUNC_VERSION == 3 */
  };
#define base64_char_to_value_(c) map[(c)]
#endif /* MHD_BASE64_FUNC_VERSION >= 2 */
  const uint8_t *const in = (const uint8_t *) base64;
  uint8_t *const out = (uint8_t *) bin;
  size_t i;
  size_t j;
  if (0 == base64_len)
    return 0;  /* Nothing to decode */
  if (0 != base64_len % 4)
    return 0;  /* Wrong input length */
  if (base64_len / 4 * 3 - 2 > bin_size)
    return 0;

  j = 0;
  for (i = 0; i < (base64_len - 4); i += 4)
  {
#if MHD_BASE64_FUNC_VERSION == 2
    if (0 != (0x80 & (in[i] | in[i + 1] | in[i + 2] | in[i + 3])))
      return 0;
#endif /* MHD_BASE64_FUNC_VERSION == 2 */
    if (1)
    {
      const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]);
      const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]);
      const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]);
      const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]);
      if ((0 > v1) || (0 > v2) || (0 > v3) || (0 > v4))
        return 0;
      out[j + 0] = (uint8_t) (((uint8_t) (((uint8_t) v1) << 2))
                              | ((uint8_t) (((uint8_t) v2) >> 4)));
      out[j + 1] = (uint8_t) (((uint8_t) (((uint8_t) v2) << 4))
                              | ((uint8_t) (((uint8_t) v3) >> 2)));
      out[j + 2] = (uint8_t) (((uint8_t) (((uint8_t) v3) << 6))
                              | ((uint8_t) v4));
    }
    j += 3;
  }
#if MHD_BASE64_FUNC_VERSION == 2
  if (0 != (0x80 & (in[i] | in[i + 1] | in[i + 2] | in[i + 3])))
    return 0;
#endif /* MHD_BASE64_FUNC_VERSION == 2 */
  if (1)
  { /* The last four chars block */
    const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]);
    const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]);
    const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]);
    const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]);
    if ((0 > v1) || (0 > v2))
      return 0; /* Invalid char or padding at first two positions */
    mhd_assert (j < bin_size);
    out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v1) << 2))
                          | ((uint8_t) (((uint8_t) v2) >> 4)));
    if (0 > v3)
    { /* Third char is either padding or invalid */
      if ((-2 != v3) || (-2 != v4))
        return 0;  /* Both two last chars must be padding */
      if (0 != (uint8_t) (((uint8_t) v2) << 4))
        return 0;  /* Wrong last char */
      return j;
    }
    if (j >= bin_size)
      return 0; /* Not enough space */
    out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v2) << 4))
                          | ((uint8_t) (((uint8_t) v3) >> 2)));
    if (0 > v4)
    { /* Fourth char is either padding or invalid */
      if (-2 != v4)
        return 0;  /* The char must be padding */
      if (0 != (uint8_t) (((uint8_t) v3) << 6))
        return 0;  /* Wrong last char */
      return j;
    }
    if (j >= bin_size)
      return 0; /* Not enough space */
    out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v3) << 6))
                          | ((uint8_t) v4));
  }
  return j;
#if MHD_BASE64_FUNC_VERSION >= 2
#undef base64_char_to_value_
#endif /* MHD_BASE64_FUNC_VERSION >= 2 */
}


mhd_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE


#undef mhd_base64_map_type

#endif /* MHD_SUPPORT_AUTH_BASIC */


MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
mhd_str_starts_with_token_opt_param (const struct MHD_String *restrict str,
                                     const struct MHD_String *restrict token)
{
  size_t i;

  mhd_assert (0 != token->len);
  mhd_assert (NULL == memchr (token->cstr, '=', token->len));
  mhd_assert (NULL == memchr (token->cstr, ' ', token->len));
  mhd_assert (NULL == memchr (token->cstr, '\t', token->len));

  if (str->len < token->len)
    return false; /* The string is too short to match */

  if (! mhd_str_equal_caseless_bin_n (str->cstr,
                                      token->cstr,
                                      token->len))
    return false; /* The string does not start with the token */

  for (i = token->len; i < str->len; ++i)
  {
    const char c = str->cstr[i];
    if ((' ' == c) || ('\t' == c))
      continue;
    if (';' == c)
      return true; /* Found the start of the token parameters */
    return false; /* The initial part of the string does not fully match the token */
  }
  mhd_assert (0 && "The string should not have whitespace at the end");
  return true;
}


MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_OUT_ (4)
MHD_FN_PAR_OUT_ (5) enum mhd_StingStartsWithTokenResult
mhd_str_starts_with_token_req_param (
  const struct MHD_String *restrict str,
  const struct MHD_String *restrict token,
  const struct MHD_String *restrict par,
  struct mhd_BufferConst *restrict par_value,
  bool *restrict par_value_needs_unquote)
{
  size_t i;
  const char *const restrict cstr = str->cstr;
  bool token_found;
  bool param_found;

  mhd_assert (0 != token->len);
  mhd_assert (NULL == memchr (token->cstr, '=', token->len));
  mhd_assert (NULL == memchr (token->cstr, ' ', token->len));
  mhd_assert (NULL == memchr (token->cstr, '\t', token->len));
  mhd_assert (NULL == memchr (par->cstr, '=', par->len));
  mhd_assert (NULL == memchr (par->cstr, ' ', par->len));
  mhd_assert (NULL == memchr (par->cstr, '\t', par->len));

  par_value->data = NULL;
  par_value->size = 0;

  if (str->len < token->len)
    return mhd_STR_STARTS_W_TOKEN_NO_TOKEN; /* The string is too short to match */

  if (! mhd_str_equal_caseless_bin_n (cstr,
                                      token->cstr,
                                      token->len))
    return mhd_STR_STARTS_W_TOKEN_NO_TOKEN; /* The string does not start with the token */
  token_found = false;
  param_found = false;

  i = token->len;
  do
  {
    /* Find start of the next parameter */
    for ((void) 0; i < str->len; ++i)
    {
      const char c = cstr[i];
      if ((' ' == c) || ('\t' == c))
        continue;
      if (';' == c)
      {
        /* Found the start of the next token parameter */
        if (param_found)
          return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN;
        ++i; /* Move to the next char */
        break;
      }
      if (',' == c)
        return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* Found the start of the next token */

      if (! token_found)
      {
        if (i == token->len)
        {
          /* The initial part of the string does not fully match the token or
             formatting is not correct */
          return mhd_STR_STARTS_W_TOKEN_NO_TOKEN;
        }
        /* The string has garbage after the token and whitespace */
        return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT;
      }
      /* The garbage after the parameter */
      return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT;
    }
    token_found = true;

    if (i == str->len)
      return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN;

    /* 'i' is at the start of the parameter */

    while ((' ' == cstr[i]) || ('\t' == cstr[i]))
    {
      if (++i == str->len)
        return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN;
    }

    /* 'i' is at the start of the parameter name */

    if (par->len > str->len - i - 1)
      return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* the token is found, but the parameter is not */
    else
    { /* Check the parameter */
      bool val_needs_unquote;
      size_t j;
      const char *const prm_str = cstr + i;

      for (j = 0; j < par->len; ++j)
        if (! charsequalcaseless (prm_str[j],
                                  par->cstr[j]))
          break;
      i += j;
      mhd_assert (str->len > i);
      if ((j == par->len) &&
          ('=' == cstr[i]))
      {
        /* The parameter name matches required parameter */
        param_found = true;
        par_value->data = cstr + i + 1;
      }
      else
      {
        /* i points to the char in the parameter name */
        while ('=' != cstr[i])
        {
          if ((';' == cstr[i])      /* end of the parameter */
              || (',' == cstr[i])   /* end of the token */
              || (str->len == ++i)) /* end of the field string */
          {
            /* parameter without the value */
            return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT;
          }
        }
      }
      mhd_assert (str->len > i);
      mhd_assert ('=' == cstr[i]);

      /* 'i' points to '=' between parameter name and parameter value */

      ++i; /* Advance to the first char in the parameter value */
      if (str->len == i)
        return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* Zero-length parameter value */

      val_needs_unquote = false;

      /* 'i' points to the char after '=' */

      if ('"' == cstr[i])
      {
        /* The value is quoted */
        if (param_found)
          ++(par_value->data); /* Point to the first quoted char */
        do
        {
          ++i; /* Advance to the next char */
          if (str->len == i)
            return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; /* No closing quote */
          if ('\\' == cstr[i])
          {
            val_needs_unquote = true;
            ++i; /* Skip quoted char */
            if (str->len == i)
              return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; /* No closing quote */
          }
        } while ('"' != cstr[i]);
        if (param_found)
        {
          par_value->size = (size_t) ((cstr + i) - par_value->data);
          *par_value_needs_unquote = val_needs_unquote;
        }
        /* Complete value found */
        /* Check for the garbage data at the end */
        ++i; /* Advance to the next char */
      }
      else
      {
        /* The value is not quoted */
        while ((' ' != cstr[i]) &&
               ('\t' != cstr[i]))
        {
          if ((';' == cstr[i])      /* end of the parameter */
              || (',' == cstr[i])   /* end of the token */
              || (str->len == ++i)) /* end of the field string */
            break;
        }
        /* The end parameter value */
        if (param_found)
        {
          par_value->size = (size_t) ((cstr + i) - par_value->data);
          *par_value_needs_unquote = false;
        }
        /* Check for the garbage data at the end */
      }

      /* 'i' points to the next char after end of the parameter value */
    }
  } while (i < str->len);

  mhd_assert (token_found);
  return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN;
}
