gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libmicrohttpd] branch master updated (d629ada1 -> 76b5b195)


From: gnunet
Subject: [libmicrohttpd] branch master updated (d629ada1 -> 76b5b195)
Date: Tue, 19 Jul 2022 16:51:08 +0200

This is an automated email from the git hooks/post-receive script.

karlson2k pushed a change to branch master
in repository libmicrohttpd.

    from d629ada1 -fix typos
     new 420ed698 mhd_str: added functions for percent-decoding
     new 57f57e8b Added tests for percent-decoding functions
     new 93a449ed Use new functions for decode request URLs
     new ee06f775 mhd_str: added MHD_hex_to_bin() internal function
     new b78b38ba Added tests for hex <-> binary functions
     new c80ceb0d mhd_str: added macros for simple comparison against static 
strings
     new 98b3c68a Added new functions MHD_digest_auth_get_request_info3() and 
MHD_digest_auth_get_username3()
     new 619e5f63 Digest: use binary zero to separate get params in digest
     new c8a549bf Digest: check whether all required parameters are present 
before doing heavy calculations
     new cc40800c Digest: moved URI match check to separate function, avoid one 
memcpy()
     new da899424 digest_auth_check: mostly rewritten, simplified, optimised
     new f826bbca microhttpd.h: added special enum for hash types
     new 1a8a2b8d digest: small internal refactoring to clarify used hash type
     new d39ad79a digest_auth_check(): check and report wrong algorithm
     new 76b5b195 microhttpd.h: formatted some deprecation warnings

The 15 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/include/microhttpd.h          |  413 ++++++++++-
 src/microhttpd/.gitignore         |    2 +
 src/microhttpd/Makefile.am        |    8 +
 src/microhttpd/daemon.c           |   20 +-
 src/microhttpd/digestauth.c       | 1416 ++++++++++++++++++++++++-------------
 src/microhttpd/internal.c         |   34 +-
 src/microhttpd/mhd_str.c          |  344 +++++++++
 src/microhttpd/mhd_str.h          |  170 ++++-
 src/microhttpd/test_str_bin_hex.c |  448 ++++++++++++
 src/microhttpd/test_str_pct.c     | 1060 +++++++++++++++++++++++++++
 10 files changed, 3351 insertions(+), 564 deletions(-)
 create mode 100644 src/microhttpd/test_str_bin_hex.c
 create mode 100644 src/microhttpd/test_str_pct.c

diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
index 2eb6e05c..876c666c 100644
--- a/src/include/microhttpd.h
+++ b/src/include/microhttpd.h
@@ -96,7 +96,7 @@ extern "C"
  * they are parsed as decimal numbers.
  * Example: 0x01093001 = 1.9.30-1.
  */
-#define MHD_VERSION 0x00097518
+#define MHD_VERSION 0x00097521
 
 /* If generic headers don't work on your platform, include headers
    which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t',
@@ -493,50 +493,50 @@ _MHD_DEPR_MACRO ( \
 /* Deprecated names and codes */
 /** @deprecated */
 #define MHD_HTTP_METHOD_NOT_ACCEPTABLE \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_METHOD_NOT_ACCEPTABLE is deprecated, use 
MHD_HTTP_NOT_ACCEPTABLE") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_METHOD_NOT_ACCEPTABLE is deprecated, " \
+  "use MHD_HTTP_NOT_ACCEPTABLE") \
   406
 
 /** @deprecated */
 #define MHD_HTTP_REQUEST_ENTITY_TOO_LARGE \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_REQUEST_ENTITY_TOO_LARGE is deprecated, use 
MHD_HTTP_CONTENT_TOO_LARGE") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_REQUEST_ENTITY_TOO_LARGE is deprecated, " 
\
+  "use MHD_HTTP_CONTENT_TOO_LARGE") \
   413
 
 /** @deprecated */
 #define MHD_HTTP_PAYLOAD_TOO_LARGE \
-    _MHD_DEPR_IN_MACRO ( \
-      "Value MHD_HTTP_PAYLOAD_TOO_LARGE is deprecated, use 
MHD_HTTP_CONTENT_TOO_LARGE") \
+    _MHD_DEPR_IN_MACRO("Value MHD_HTTP_PAYLOAD_TOO_LARGE is deprecated, " \
+  "use MHD_HTTP_CONTENT_TOO_LARGE") \
   413
 
 /** @deprecated */
 #define MHD_HTTP_REQUEST_URI_TOO_LONG \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_REQUEST_URI_TOO_LONG is deprecated, use 
MHD_HTTP_URI_TOO_LONG") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_REQUEST_URI_TOO_LONG is deprecated, " \
+  "use MHD_HTTP_URI_TOO_LONG") \
   414
 
 /** @deprecated */
 #define MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE is deprecated, use 
MHD_HTTP_RANGE_NOT_SATISFIABLE") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE is " \
+  "deprecated, use MHD_HTTP_RANGE_NOT_SATISFIABLE") \
   416
 
 /** @deprecated */
 #define MHD_HTTP_UNPROCESSABLE_ENTITY \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_UNPROCESSABLE_ENTITY is deprecated, use 
MHD_HTTP_UNPROCESSABLE_CONTENT") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_UNPROCESSABLE_ENTITY is deprecated, " \
+  "use MHD_HTTP_UNPROCESSABLE_CONTENT") \
   422
 
 /** @deprecated */
 #define MHD_HTTP_UNORDERED_COLLECTION \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_UNORDERED_COLLECTION is deprecated as it was removed from 
RFC") \
+  _MHD_DEPR_IN_MACRO("Value MHD_HTTP_UNORDERED_COLLECTION is deprecated " \
+  "as it was removed from RFC") \
   425
 
 /** @deprecated */
 #define MHD_HTTP_NO_RESPONSE \
-  _MHD_DEPR_IN_MACRO ( \
-    "Value MHD_HTTP_NO_RESPONSE is deprecated as it is nginx internal code for 
logs only") \
+  _MHD_DEPR_IN_MACRO ("Value MHD_HTTP_NO_RESPONSE is deprecated as " \
+  "it is nginx internal code for logs only") \
   444
 
 
@@ -3300,8 +3300,8 @@ MHD_set_panic_func (MHD_PanicCallback cb, void *cls);
 
 /**
  * Process escape sequences ('%HH') Updates val in place; the
- * result should be UTF-8 encoded and cannot be larger than the input.
- * The result must also still be 0-terminated.
+ * result cannot be larger than the input.
+ * The result is still be 0-terminated.
  *
  * @param val value to unescape (modified in the process)
  * @return length of the resulting val (`strlen(val)` may be
@@ -4334,6 +4334,369 @@ MHD_destroy_post_processor (struct MHD_PostProcessor 
*pp);
  */
 #define MHD_INVALID_NONCE -1
 
+/**
+ * Base type of hash calculation.
+ * Used as part of #MHD_DigestAuthAlgo3 values.
+ *
+ * @warning Not used directly by MHD API.
+ * @note Available since #MHD_VERSION 0x00097520
+ */
+enum MHD_DigestBaseAlgo
+{
+  /**
+   * Invalid hash algorithm value
+   */
+  MHD_DIGEST_BASE_ALGO_INVALID = 0,
+  /**
+   * MD5 hash algorithm.
+   * As specified by RFC1321
+   */
+  MHD_DIGEST_BASE_ALGO_MD5 = (1 << 0),
+  /**
+   * SHA-256 has algorithm.
+   * As specified by FIPS PUB 180-4
+   */
+  MHD_DIGEST_BASE_ALGO_SHA256 = (1 << 1),
+  /**
+   * SHA-512/256 has algorithm.
+   * Not supported for calculations, only supported for parsing of
+   * client's authorisation headers.
+   */
+  MHD_DIGEST_BASE_ALGO_SHA512_256 = (1 << 2)
+};
+
+/**
+ * The flag indicating digest calculation types,
+ * like 'MD5' or 'SHA-256'.
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+#define MHD_DIGEST_AUTH_ALGO3_NON_SESSION    (1 << 6)
+
+/**
+ * The flag indicating non-session algorithm types,
+ * like 'MD5' or 'SHA-256'.
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+#define MHD_DIGEST_AUTH_ALGO3_NON_SESSION    (1 << 6)
+
+/**
+ * The flag indicating session algorithm types,
+ * like 'MD5-sess' or 'SHA-256-sess'.
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+#define MHD_DIGEST_AUTH_ALGO3_SESSION        (1 << 7)
+
+/**
+ * Digest algorithm identification
+ * @warning Do not be confused with #MHD_DigestAuthAlgorithm,
+ *          which uses other values!
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+enum MHD_DigestAuthAlgo3
+{
+  /**
+   * Unknown or wrong algorithm type.
+   * Used in struct MHD_DigestAuthInfo to indicate client value that
+   * cannot by identified.
+   */
+  MHD_DIGEST_AUTH_ALGO3_INVALID = 0,
+  /**
+   * The 'MD5' algorithm.
+   */
+  MHD_DIGEST_AUTH_ALGO3_MD5 =
+    MHD_DIGEST_BASE_ALGO_MD5 | MHD_DIGEST_AUTH_ALGO3_NON_SESSION,
+  /**
+   * The 'MD5-sess' algorithm.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_ALGO3_MD5_SESSION =
+    MHD_DIGEST_BASE_ALGO_MD5 | MHD_DIGEST_AUTH_ALGO3_SESSION,
+  /**
+   * The 'SHA-256' algorithm.
+   */
+  MHD_DIGEST_AUTH_ALGO3_SHA256 =
+    MHD_DIGEST_BASE_ALGO_SHA256 | MHD_DIGEST_AUTH_ALGO3_NON_SESSION,
+  /**
+   * The 'SHA-256-sess' algorithm.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION =
+    MHD_DIGEST_BASE_ALGO_SHA256 | MHD_DIGEST_AUTH_ALGO3_SESSION,
+  /**
+   * The 'SHA-512-256' (SHA-512/256) algorithm.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_ALGO3_SHA512_256 =
+    MHD_DIGEST_BASE_ALGO_SHA512_256 | MHD_DIGEST_AUTH_ALGO3_NON_SESSION,
+  /**
+   * The 'SHA-512-256-sess' (SHA-512/256 session) algorithm.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION =
+    MHD_DIGEST_BASE_ALGO_SHA512_256 | MHD_DIGEST_AUTH_ALGO3_SESSION,
+  /**
+   * Any non-session algorithm, MHD will choose.
+   */
+  MHD_DIGEST_AUTH_ALGO3_ANY_NON_SESSION =
+    (0x3F) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION,
+  /**
+   * Any session algorithm, MHD will choose.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_ALGO3_ANY_SESSION =
+    (0x3F) | MHD_DIGEST_AUTH_ALGO3_SESSION,
+  /**
+   * Any algorithm, MHD will choose.
+   */
+  MHD_DIGEST_AUTH_ALGO3_ANY =
+    (0x3F) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION | MHD_DIGEST_AUTH_ALGO3_SESSION
+} _MHD_FLAGS_ENUM;
+
+/**
+ * The type of username used by client in Digest Authorization header
+ *
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+enum MHD_DigestAuthUsernameType
+{
+  /**
+   * No username parameter in in Digest Authorization header.
+   * This should be treated as an error.
+   */
+  MHD_DIGEST_AUTH_UNAME_TYPE_MISSING = 0,
+  /**
+   * The 'username' parameter is used to specify the username.
+   */
+  MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD = 1,
+  /**
+   * The username is specified by 'username*' parameter with
+   * the extended notation (see RFC 5987 #section-3.2.1).
+   * The only difference between standard and extended types is
+   * the way how username value is encoded in the header.
+   */
+  MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED = 2,
+  /**
+   * The username provided in form of 'userhash' as
+   * specified by RFC 7616 #section-3.4.4.
+   */
+  MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH = 3,
+  /**
+   * The invalid combination of username parameters are used by client.
+   * Either:
+   * * both 'username' and 'username*' are used
+   * * 'username*' is used with 'userhash=true'
+   * * 'username*' used with invalid extended notation
+   * * 'username' is not hexadecimal digits, while 'userhash' set to 'true'
+   */
+  MHD_DIGEST_AUTH_UNAME_TYPE_INVALID = 15
+} _MHD_FIXED_ENUM;
+
+/**
+ * The QOP ('quality of protection') types.
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+enum MHD_DigestAuthQOP
+{
+  /**
+   * Invalid/unknown QOP.
+   * Used in struct MHD_DigestAuthInfo to indicate client value that
+   * cannot by identified.
+   */
+  MHD_DIGEST_AUTH_QOP_INVALID = 0,
+  /**
+   * No QOP value.
+   */
+  MHD_DIGEST_AUTH_QOP_NONE = 1 << 0,
+  /**
+   * The 'auth' QOP type.
+   */
+  MHD_DIGEST_AUTH_QOP_AUTH = 1 << 1,
+  /**
+   * The 'auth-int' QOP type.
+   * Not supported by MHD.
+   */
+  MHD_DIGEST_AUTH_QOP_AUTH_INT = 1 << 2
+} _MHD_FLAGS_ENUM;
+
+/**
+ * The invalid value of 'nc' parameter in client Digest Authorization header.
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+#define MHD_DIGEST_AUTH_INVALID_NC_VALUE        (0)
+
+/**
+ * Information from Digest Authorization client's header.
+ *
+ * All buffers pointed by any struct members are freed when #MHD_free() is
+ * called for pointer to this structure.
+ *
+ * Application may modify buffers as needed until #MHD_free() is called for
+ * pointer to this structure
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+struct MHD_DigestAuthInfo
+{
+  /**
+   * The algorithm as defined by client.
+   * Set automatically to MD5 if not specified by client.
+   * No "group" (ALGO3_ANY) values are used.
+   * @warning Do not be confused with #MHD_DigestAuthAlgorithm,
+   *          which uses other values!
+   */
+  enum MHD_DigestAuthAlgo3 algo;
+  /**
+   * The type of username used by client.
+   */
+  enum MHD_DigestAuthUsernameType uname_type;
+  /**
+   * The username string.
+   * Valid only if username is standard, extended, or userhash.
+   * For userhash this is unqoted string without decoding of the
+   * hexadecimal digits (as provided by client).
+   * If extended notation is used, this string is pct-decoded string
+   * with charset and language tag removed (i.e. it is original username
+   * extracted from the extended notation).
+   * This can be NULL is username is missing or invalid.
+   */
+  char *username;
+  /**
+   * The length of the @a username.
+   * When the @a username is NULL, this member is always zero.
+   */
+  size_t username_len;
+  /**
+   * The userhash decoded to binary form.
+   * Used only if username type is userhash, always NULL otherwise.
+   * @warning this is a binary data, no zero termination
+   */
+  uint8_t *userhash_bin;
+  /**
+   * The number of bytes pointed by the @a userhash_bin.
+   * When the @a userhash_bin is NULL, this member is always zero.
+   */
+  size_t userhash_bin_size;
+  /**
+   * The 'opaque' parameter value, as specified by client.
+   * NULL if not specified by client.
+   */
+  char *opaque;
+  /**
+   * The length of the @a opaque.
+   * When the @a opaque is NULL, this member is always zero.
+   */
+  size_t opaque_len;
+  /**
+   * The 'realm' parameter value, as specified by client.
+   * NULL if not specified by client.
+   */
+  char *realm;
+  /**
+   * The length of the @a realm.
+   * When the @a realm is NULL, this member is always zero.
+   */
+  size_t realm_len;
+  /**
+   * The 'qop' parameter value.
+   */
+  enum MHD_DigestAuthQOP qop;
+  /**
+   * The length of the 'cnonce' parameter value, including possible
+   * backslash-escape characters.
+   * 'cnonce' is used in hash calculation, which is CPU-intensive procedure.
+   * An applicaion may want to reject too large cnonces to limit the CPU load.
+   * A few kilobytes is a reasonable limit, typically cnonce is just 32-160
+   * characters long.
+   */
+  size_t cnonce_len;
+  /**
+   * The nc parameter value.
+   * Can be used by application to limit the number of nonce re-uses. If @ nc
+   * is higher than application wants to allow, then fail response with
+   * 'stale=true' could be used to ask force client to get the fresh 'nonce'.
+   * If not specified by client or does not have hexadecimal digits only, the
+   * value is #MHD_DIGEST_AUTH_INVALID_NC_VALUE.
+   */
+  uint32_t nc;
+};
+
+/**
+ * Get information about Digest Authorization client's header.
+ *
+ * @param connection The MHD connection structure
+ * @return NULL if no valid Digest Authorization header is used in the request;
+ *         a pointer to the structure with information if the valid request
+ *         header found, free using #MHD_free().
+ * @note Available since #MHD_VERSION 0x00097519
+ * @ingroup authentication
+ */
+_MHD_EXTERN struct MHD_DigestAuthInfo *
+MHD_digest_auth_get_request_info3 (struct MHD_Connection *connection);
+
+
+/**
+ * Information from Digest Authorization client's header.
+ *
+ * All buffers pointed by any struct members are freed when #MHD_free() is
+ * called for pointer to this structure.
+ *
+ * Application may modify buffers as needed until #MHD_free() is called for
+ * pointer to this structure
+ * @note Available since #MHD_VERSION 0x00097519
+ */
+struct MHD_DigestAuthUsernameInfo
+{
+  /**
+   * The type of username used by client.
+   * The 'invalid' and 'missing' types are not used in this structure,
+   * instead NULL is returned by #MHD_digest_auth_get_username3().
+   */
+  enum MHD_DigestAuthUsernameType uname_type;
+  /**
+   * The username string.
+   * Valid only if username is standard, extended, or userhash.
+   * For userhash this is unqoted string without decoding of the
+   * hexadecimal digits (as provided by client).
+   * If extended notation is used, this string is pct-decoded string
+   * with charset and language tag removed (i.e. it is original username
+   * extracted from the extended notation).
+   * This can be NULL is username is missing or invalid.
+   */
+  char *username;
+  /**
+   * The length of the @a username.
+   * When the @a username is NULL, this member is always zero.
+   */
+  size_t username_len;
+  /**
+   * The userhash decoded to binary form.
+   * Used only if username type is userhash, always NULL if not used.
+   * @warning this is a binary data, no zero termination
+   */
+  uint8_t *userhash_bin;
+  /**
+   * The number of bytes pointed by the @a userhash_bin.
+   * When the @a userhash_bin is NULL, this member is always zero.
+   */
+  size_t userhash_bin_size;
+};
+
+/**
+ * Get the username from Digest Authorization client's header.
+ *
+ * @param connection The MHD connection structure
+ * @return NULL if no valid Digest Authorization header is used in the request,
+ *         or no username parameter is present in the header, or username is
+ *         provided incorrectly by client (see description for
+ *         #MHD_DIGEST_AUTH_UNAME_TYPE_INVALID);
+ *         a pointer structure with information if the valid request header
+ *         found, free using #MHD_free().
+ * @sa MHD_digest_auth_get_request_info3() provides more complete information
+ * @note Available since #MHD_VERSION 0x00097519
+ * @ingroup authentication
+ */
+_MHD_EXTERN struct MHD_DigestAuthUsernameInfo *
+MHD_digest_auth_get_username3 (struct MHD_Connection *connection);
+
 
 /**
  * Get the username from the authorization header sent by the client
@@ -4341,6 +4704,7 @@ MHD_destroy_post_processor (struct MHD_PostProcessor *pp);
  * @param connection The MHD connection structure
  * @return NULL if no username could be found, a pointer
  *      to the username if found, free using #MHD_free().
+ * @deprecated use MHD_digest_auth_get_username3()
  * @ingroup authentication
  */
 _MHD_EXTERN char *
@@ -4376,7 +4740,7 @@ enum MHD_DigestAuthAlgorithm
  *
  * All error values are zero or negative.
  *
- * @note Available since #MHD_VERSION 0x00097518
+ * @note Available since #MHD_VERSION 0x00097521
  */
 enum MHD_DigestAuthResult
 {
@@ -4415,6 +4779,11 @@ enum MHD_DigestAuthResult
    */
   MHD_DAUTH_WRONG_QOP = -5,
 
+  /**
+   * Wrong 'algorithm'.
+   */
+  MHD_DAUTH_WRONG_ALGO = -6,
+
   /**
    * Too large (>64 KiB) Authorization parameter value.
    */
@@ -4453,7 +4822,7 @@ enum MHD_DigestAuthResult
  * @param algo the digest algorithms allowed for verification
  * @return #MHD_DAUTH_OK if authenticated,
  *         the error code otherwise
- * @note Available since #MHD_VERSION 0x00097518
+ * @note Available since #MHD_VERSION 0x00097521
  * @ingroup authentication
  */
 _MHD_EXTERN enum MHD_DigestAuthResult
@@ -4479,7 +4848,7 @@ MHD_digest_auth_check3 (struct MHD_Connection *connection,
  * @param algo digest algorithms allowed for verification
  * @return #MHD_DAUTH_OK if authenticated,
  *         the error code otherwise
- * @note Available since #MHD_VERSION 0x00097513
+ * @note Available since #MHD_VERSION 0x00097521
  * @ingroup authentication
  */
 _MHD_EXTERN enum MHD_DigestAuthResult
diff --git a/src/microhttpd/.gitignore b/src/microhttpd/.gitignore
index daddef33..1353f1ec 100644
--- a/src/microhttpd/.gitignore
+++ b/src/microhttpd/.gitignore
@@ -81,3 +81,5 @@ test_postprocessor_md
 /test_auth_parse
 /test_str_quote
 /test_str_base64
+/test_str_pct
+/test_str_bin_hex
diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am
index 9e0bb7a4..58ab10a8 100644
--- a/src/microhttpd/Makefile.am
+++ b/src/microhttpd/Makefile.am
@@ -190,6 +190,8 @@ check_PROGRAMS = \
   test_str_token \
   test_str_token_remove \
   test_str_tokens_remove \
+  test_str_pct \
+  test_str_bin_hex \
   test_http_reasons \
   test_md5 \
   test_sha1 \
@@ -457,6 +459,12 @@ test_str_quote_SOURCES = \
 test_str_base64_SOURCES = \
   test_str_base64.c mhd_str.h mhd_str.c mhd_assert.h
 
+test_str_pct_SOURCES = \
+  test_str_pct.c mhd_str.h mhd_str.c mhd_assert.h
+
+test_str_bin_hex_SOURCES = \
+  test_str_bin_hex.c mhd_str.h mhd_str.c mhd_assert.h
+
 test_options_SOURCES = \
   test_options.c
 test_options_LDADD = \
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
index 8ba1baba..251e563e 100644
--- a/src/microhttpd/daemon.c
+++ b/src/microhttpd/daemon.c
@@ -45,6 +45,7 @@
 #include "mhd_compat.h"
 #include "mhd_send.h"
 #include "mhd_align.h"
+#include "mhd_str.h"
 
 #ifdef HAVE_SEARCH_H
 #include <search.h>
@@ -5672,7 +5673,7 @@ MHD_polling_thread (void *cls)
 
 /**
  * Process escape sequences ('%HH') Updates val in place; the
- * result should be UTF-8 encoded and cannot be larger than the input.
+ * result cannot be larger than the input.
  * The result must also still be 0-terminated.
  *
  * @param cls closure (use NULL)
@@ -5686,10 +5687,23 @@ unescape_wrapper (void *cls,
                   struct MHD_Connection *connection,
                   char *val)
 {
+  bool broken;
+  size_t res;
   (void) cls; /* Mute compiler warning. */
 
-  (void) connection; /* Mute compiler warning. */
-  return MHD_http_unescape (val);
+  /* TODO: add individual parameter */
+  if (1 <= connection->daemon->strict_for_client)
+    return MHD_str_pct_decode_in_place_strict_ (val);
+
+  res = MHD_str_pct_decode_in_place_lenient_ (val, &broken);
+#ifdef HAVE_MESSAGES
+  if (broken)
+  {
+    MHD_DLOG (connection->daemon,
+              _ ("The URL encoding is broken.\n"));
+  }
+#endif /* HAVE_MESSAGES */
+  return res;
 }
 
 
diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c
index 35c2deb5..71561332 100644
--- a/src/microhttpd/digestauth.c
+++ b/src/microhttpd/digestauth.c
@@ -146,11 +146,28 @@
  */
 #define _MHD_SHA256_TOKEN "SHA-256"
 
+/**
+ * The token for SHA-512/256 algorithm.
+ * Unsupported currently by MHD for authentication.
+ */
+#define _MHD_SHA512_256_TOKEN "SHA-512-256"
+
 /**
  * The postfix token for "session" algorithms.
  */
 #define _MHD_SESS_TOKEN "-sess"
 
+/**
+ * The required prefix of parameter with the extended notation
+ */
+#define MHD_DAUTH_EXT_PARAM_PREFIX "UTF-8'"
+
+/**
+ * The minimal size of the prefix for parameter with the extended notation
+ */
+#define MHD_DAUTH_EXT_PARAM_MIN_LEN \
+  MHD_STATICSTR_LEN_(MHD_DAUTH_EXT_PARAM_PREFIX "'")
+
 /**
  * The result of nonce-nc map array check.
  */
@@ -217,15 +234,15 @@ struct DigestAlgorithm
    * Digest in binary form.
    */
   union DigestBin digest;
+
   /**
-   * The digest algorithm.
+   * The hash calculation algorithm.
    */
-  enum MHD_DigestAuthAlgorithm algo;
+  enum MHD_DigestBaseAlgo algo;
 
   /**
    * Buffer for hex-print of the final digest.
    */
-  union DigestHex digest_hex;
 #if _DEBUG
   bool setup; /**< The structure was set-up */
   bool inited; /**< The calculation was initialised */
@@ -243,9 +260,9 @@ _MHD_static_inline const char *
 digest_get_algo_name (struct DigestAlgorithm *da)
 {
   mhd_assert (da->setup);
-  if (MHD_DIGEST_ALG_MD5 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo)
     return _MHD_MD5_TOKEN;
-  if (MHD_DIGEST_ALG_SHA256 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo)
     return _MHD_SHA256_TOKEN;
   mhd_assert (0); /* May not happen */
   return "";
@@ -261,9 +278,9 @@ _MHD_static_inline unsigned int
 digest_get_size (struct DigestAlgorithm *da)
 {
   mhd_assert (da->setup);
-  if (MHD_DIGEST_ALG_MD5 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo)
     return MD5_DIGEST_SIZE;
-  if (MHD_DIGEST_ALG_SHA256 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo)
     return SHA256_DIGEST_SIZE;
   mhd_assert (0); /* May not happen */
   return 0;
@@ -279,18 +296,15 @@ digest_get_size (struct DigestAlgorithm *da)
  */
 _MHD_static_inline bool
 digest_setup (struct DigestAlgorithm *da,
-              enum MHD_DigestAuthAlgorithm algo)
+              enum MHD_DigestBaseAlgo algo)
 {
 #ifdef _DEBUG
   da->setup = false;
   da->inited = false;
   da->digest_calculated = false;
 #endif /* _DEBUG */
-  if (MHD_DIGEST_ALG_AUTO == algo)
-    algo = MHD_DIGEST_ALG_SHA256;
-
-  if ((MHD_DIGEST_ALG_MD5 == algo) ||
-      (MHD_DIGEST_ALG_SHA256 == algo))
+  if ((MHD_DIGEST_BASE_ALGO_MD5 == algo) ||
+      (MHD_DIGEST_BASE_ALGO_SHA256 == algo))
   {
     da->algo = algo;
 #ifdef _DEBUG
@@ -314,14 +328,14 @@ digest_init (struct DigestAlgorithm *da)
 #ifdef _DEBUG
   da->digest_calculated = false;
 #endif
-  if (MHD_DIGEST_ALG_MD5 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo)
   {
     MHD_MD5Init (&da->ctx.md5_ctx);
 #ifdef _DEBUG
     da->inited = true;
 #endif
   }
-  else if (MHD_DIGEST_ALG_SHA256 == da->algo)
+  else if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo)
   {
     MHD_SHA256_init (&da->ctx.sha256_ctx);
 #ifdef _DEBUG
@@ -351,15 +365,29 @@ digest_update (struct DigestAlgorithm *da,
 {
   mhd_assert (da->inited);
   mhd_assert (! da->digest_calculated);
-  if (MHD_DIGEST_ALG_MD5 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo)
     MHD_MD5Update (&da->ctx.md5_ctx, data, length);
-  else if (MHD_DIGEST_ALG_SHA256 == da->algo)
+  else if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo)
     MHD_SHA256_update (&da->ctx.sha256_ctx, data, length);
   else
     mhd_assert (0); /* May not happen */
 }
 
 
+/**
+ * Feed digest calculation with more data from string.
+ * @param da the digest calculation
+ * @param str the zero-terminated string to process
+ */
+_MHD_static_inline void
+digest_update_str (struct DigestAlgorithm *da,
+                   const char *str)
+{
+  const size_t str_len = strlen (str);
+  digest_update (da, (const uint8_t *) str, str_len);
+}
+
+
 /**
  * Finally calculate hash (the digest).
  * @param da the digest calculation
@@ -369,9 +397,9 @@ digest_calc_hash (struct DigestAlgorithm *da)
 {
   mhd_assert (da->inited);
   mhd_assert (! da->digest_calculated);
-  if (MHD_DIGEST_ALG_MD5 == da->algo)
+  if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo)
     MHD_MD5Final (&da->ctx.md5_ctx, da->digest.md5);
-  else if (MHD_DIGEST_ALG_SHA256 == da->algo)
+  else if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo)
     MHD_SHA256_finish (&da->ctx.sha256_ctx, da->digest.sha256);
   else
     mhd_assert (0); /* May not happen */
@@ -396,244 +424,6 @@ digest_get_bin (struct DigestAlgorithm *da)
 }
 
 
-/**
- * Get pointer to the buffer for the printed digest.
- * @param da the digest calculation
- * @return the pointer to the buffer
- */
-_MHD_static_inline char *
-digest_get_hex_buffer (struct DigestAlgorithm *da)
-{
-  return da->digest_hex.sha256;
-}
-
-
-/**
- * Put calculated digest to the buffer as hex digits.
- * @param da the digest calculation
- * @return the pointer to the calculated digest
- */
-_MHD_static_inline void
-digest_make_hex (struct DigestAlgorithm *da)
-{
-  MHD_bin_to_hex (digest_get_bin (da),
-                  digest_get_size (da),
-                  digest_get_hex_buffer (da));
-}
-
-
-/**
- * calculate H(A1) from given hash as per RFC2617 spec
- * and store the result in 'digest_hex'.
- *
- * @param alg The hash algorithm used, can be "MD5" or "MD5-sess"
- *            or "SHA-256" or "SHA-256-sess"
- *    Note that the rest of the code does not support the the "-sess" variants!
- * @param[in,out] da digest implementation, must match @a alg; the
- *          da->digest_hex will be initialized to the digest in HEX
- * @param digest An `unsigned char *' pointer to the binary MD5 sum
- *      for the precalculated hash value "username:realm:password"
- *      of #MHD_MD5_DIGEST_SIZE or #SHA256_DIGEST_SIZE bytes
- * @param nonce A `char *' pointer to the nonce value
- * @param cnonce A `char *' pointer to the cnonce value
- */
-static void
-digest_calc_ha1_from_digest (const char *alg,
-                             struct DigestAlgorithm *da,
-                             const uint8_t *digest,
-                             const char *nonce,
-                             const char *cnonce)
-{
-  /* TODO: disable unsupported code paths */
-  if ( (MHD_str_equal_caseless_ (alg,
-                                 _MHD_MD5_TOKEN _MHD_SESS_TOKEN)) ||
-       (MHD_str_equal_caseless_ (alg,
-                                 _MHD_SHA256_TOKEN _MHD_SESS_TOKEN)) )
-  {
-    digest_init (da);
-    digest_update (da,
-                   digest,
-                   digest_get_size (da));
-    digest_update (da,
-                   (const unsigned char *) ":",
-                   1);
-    digest_update (da,
-                   (const unsigned char *) nonce,
-                   strlen (nonce));
-    digest_update (da,
-                   (const unsigned char *) ":",
-                   1);
-    digest_update (da,
-                   (const unsigned char *) cnonce,
-                   strlen (cnonce));
-    digest_calc_hash (da);
-    digest_make_hex (da);
-  }
-  else
-  {
-    MHD_bin_to_hex (digest,
-                    digest_get_size (da),
-                    digest_get_hex_buffer (da));
-  }
-}
-
-
-/**
- * calculate H(A1) from username, realm and password as per RFC2617 spec
- * and store the result in 'digest_hex'.
- *
- * @param alg The hash algorithm used, can be "MD5" or "MD5-sess"
- *             or "SHA-256" or "SHA-256-sess"
- * @param username A `char *' pointer to the username value
- * @param username the length of the @a username
- * @param realm A `char *' pointer to the realm value
- * @param realm_len the length of the @a realm
- * @param password A `char *' pointer to the password value
- * @param nonce A `char *' pointer to the nonce value
- * @param cnonce A `char *' pointer to the cnonce value
- * @param[in,out] da digest algorithm to use, and where to write
- *         the sessionkey to
- */
-static void
-digest_calc_ha1_from_user (const char *alg,
-                           const char *username,
-                           size_t username_len,
-                           const char *realm,
-                           size_t realm_len,
-                           const char *password,
-                           const char *nonce,
-                           const char *cnonce,
-                           struct DigestAlgorithm *da)
-{
-  digest_init (da);
-  digest_update (da,
-                 (const unsigned char *) username,
-                 username_len);
-  digest_update (da,
-                 (const unsigned char *) ":",
-                 1);
-  digest_update (da,
-                 (const unsigned char *) realm,
-                 realm_len);
-  digest_update (da,
-                 (const unsigned char *) ":",
-                 1);
-  digest_update (da,
-                 (const unsigned char *) password,
-                 strlen (password));
-  digest_calc_hash (da);
-  digest_calc_ha1_from_digest (alg,
-                               da,
-                               digest_get_bin (da),
-                               nonce,
-                               cnonce);
-}
-
-
-/**
- * Calculate request-digest/response-digest as per RFC2617 / RFC7616
- * spec.
- *
- * @param ha1 H(A1), twice the @a da->digest_size + 1 bytes (0-terminated),
- *        MUST NOT be aliased with `da->sessionkey`!
- * @param nonce nonce from server
- * @param noncecount 8 hex digits
- * @param cnonce client nonce
- * @param qop qop-value: "", "auth" or "auth-int" (NOTE: only 'auth' is 
supported today.)
- * @param method method from request
- * @param uri requested URL, could be not zero-terminated
- * @param uri_len the length of @a uri, in characters
- * @param hentity H(entity body) if qop="auth-int"
- * @param[in,out] da digest algorithm to use, also
- *        we write da->sessionkey (set to response request-digest or 
response-digest)
- */
-static void
-digest_calc_response (const char *ha1,
-                      const char *nonce,
-                      const char *noncecount,
-                      const char *cnonce,
-                      const char *qop,
-                      const char *method,
-                      const char *uri,
-                      size_t uri_len,
-                      const char *hentity,
-                      struct DigestAlgorithm *da)
-{
-  (void) hentity; /* Unused. Silence compiler warning. */
-
-  /* Calculate h(a2) */
-  digest_init (da);
-  digest_update (da,
-                 (const unsigned char *) method,
-                 strlen (method));
-  digest_update (da,
-                 (const unsigned char *) ":",
-                 1);
-  digest_update (da,
-                 (const unsigned char *) uri,
-                 uri_len);
-#if 0
-  if (0 == strcasecmp (qop,
-                       "auth-int"))
-  {
-    /* This is dead code since the rest of this module does
- not support auth-int. */
-    digest_update (da,
-                   ":",
-                   1);
-    if (NULL != hentity)
-      da->update (da->ctx,
-                  hentity,
-                  strlen (hentity));
-  }
-#endif
-  digest_calc_hash (da);
-  digest_make_hex (da);
-
-  /* calculate response */
-  digest_init (da);
-  digest_update (da,
-                 (const unsigned char *) ha1,
-                 digest_get_size (da) * 2);
-  digest_update (da,
-                 (const unsigned char *) ":",
-                 1);
-  digest_update (da,
-                 (const unsigned char *) nonce,
-                 strlen (nonce));
-  digest_update (da,
-                 (const unsigned char *) ":",
-                 1);
-  if ('\0' != *qop)
-  {
-    digest_update (da,
-                   (const unsigned char *) noncecount,
-                   strlen (noncecount));
-    digest_update (da,
-                   (const unsigned char *) ":",
-                   1);
-    digest_update (da,
-                   (const unsigned char *) cnonce,
-                   strlen (cnonce));
-    digest_update (da,
-                   (const unsigned char *) ":",
-                   1);
-    digest_update (da,
-                   (const unsigned char *) qop,
-                   strlen (qop));
-    digest_update (da,
-                   (const unsigned char *) ":",
-                   1);
-  }
-  digest_update (da,
-                 (const unsigned char *) digest_get_hex_buffer (da),
-                 digest_get_size (da) * 2);
-
-  digest_calc_hash (da);
-  digest_make_hex (da);
-}
-
-
 static const struct MHD_RqDAuth *
 get_rq_dauth_params (struct MHD_Connection *connection)
 {
@@ -727,7 +517,8 @@ get_nonce_nc_idx (size_t arr_size,
  * Check nonce-nc map array with the new nonce counter.
  *
  * @param connection The MHD connection structure
- * @param nonce A pointer that referenced a zero-terminated array of nonce
+ * @param nonce the pointer that referenced hex nonce, does not need to be
+ *              zero-terminated
  * @param noncelen the length of @a nonce, in characters
  * @param nc The nonce counter
  * @return #MHD_DAUTH_NONCENC_OK if successful,
@@ -749,7 +540,6 @@ check_nonce_nc (struct MHD_Connection *connection,
   enum MHD_CheckNonceNC_ ret;
 
   mhd_assert (0 != noncelen);
-  mhd_assert (strlen (nonce) == noncelen);
   mhd_assert (0 != nc);
   if (MAX_DIGEST_NONCE_LENGTH < noncelen)
     return MHD_CHECK_NONCENC_WRONG; /* This should be impossible, but static 
analysis
@@ -783,7 +573,7 @@ check_nonce_nc (struct MHD_Connection *connection,
     else
     {
       uint64_t slot_ts; /**< The timestamp in the slot */
-      if (! get_nonce_timestamp (nn->nonce, 0, &slot_ts))
+      if (! get_nonce_timestamp (nn->nonce, noncelen, &slot_ts))
       {
         mhd_assert (0); /* The value is the slot is wrong */
         ret = MHD_CHECK_NONCENC_STALE;
@@ -858,6 +648,557 @@ check_nonce_nc (struct MHD_Connection *connection,
 }
 
 
+/**
+ * Get username type used by the client.
+ * This function does not check whether userhash can be decoded or
+ * extended notation (if used) is valid.
+ * @param params the Digest Authorization parameters
+ * @return the type of username
+ */
+_MHD_static_inline enum MHD_DigestAuthUsernameType
+get_rq_uname_type (const struct MHD_RqDAuth *params)
+{
+  if (NULL != params->username.value.str)
+  {
+    if (NULL == params->username_ext.value.str)
+      return params->userhash ?
+             MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH :
+             MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD;
+    else  /* Both 'username' and 'username*' are used */
+      return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID;
+  }
+  else if (NULL != params->username_ext.value.str)
+  {
+    if (! params->username_ext.quoted && ! params->userhash &&
+        (MHD_DAUTH_EXT_PARAM_MIN_LEN <= params->username_ext.value.len) )
+      return MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED;
+    else
+      return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID;
+  }
+
+  return MHD_DIGEST_AUTH_UNAME_TYPE_MISSING;
+}
+
+
+/**
+ * Get total size required for 'username' and 'userhash_bin'
+ * @param params the Digest Authorization parameters
+ * @param uname_type the type of username
+ * @return the total size required for 'username' and
+ *         'userhash_bin' is userhash is used
+ */
+_MHD_static_inline size_t
+get_rq_unames_size (const struct MHD_RqDAuth *params,
+                    enum MHD_DigestAuthUsernameType uname_type)
+{
+  size_t s;
+
+  mhd_assert (get_rq_uname_type (params) == uname_type);
+  s = 0;
+  if ((MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) ||
+      (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) )
+  {
+    s += params->username.value.len + 1; /* Add one byte for zero-termination 
*/
+    if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type)
+      s += (params->username.value.len + 1) / 2;
+  }
+  else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type)
+    s += params->username_ext.value.len + 1; /* Add one byte for 
zero-termination */
+  return s;
+}
+
+
+/**
+ * Get client's Digest Authorization algorithm type.
+ * If no algorithm is specified by client, MD5 is assumed.
+ * @param params the Digest Authorization parameters
+ * @return the algorithm type
+ */
+static enum MHD_DigestAuthAlgo3
+get_rq_algo (const struct MHD_RqDAuth *params)
+{
+  const struct MHD_RqDAuthParam *const algo_param =
+    &params->algorithm;
+  if (NULL == algo_param->value.str)
+    return MHD_DIGEST_AUTH_ALGO3_MD5; /* Assume MD5 by default */
+
+  if (algo_param->quoted)
+  {
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_MD5_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_MD5;
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_SHA256_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_SHA256;
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_MD5_TOKEN _MHD_SESS_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_MD5_SESSION;
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_SHA256_TOKEN \
+                                               _MHD_SESS_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION;
+
+    /* Algorithms below are not supported by MHD for authentication */
+
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_SHA512_256_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_SHA512_256;
+    if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
+                                               algo_param->value.len, \
+                                               _MHD_SHA512_256_TOKEN \
+                                               _MHD_SESS_TOKEN))
+      return MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION;
+
+    /* No known algorithm has been detected */
+    return MHD_DIGEST_AUTH_ALGO3_INVALID;
+  }
+  /* The algorithm value is not quoted */
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_MD5_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA256_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_MD5_TOKEN _MHD_SESS_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA256_TOKEN _MHD_SESS_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+
+  /* Algorithms below are not supported by MHD for authentication */
+
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA512_256_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+  if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA512_256_TOKEN _MHD_SESS_TOKEN, \
+                                       algo_param->value.str, \
+                                       algo_param->value.len))
+    return MHD_DIGEST_AUTH_ALGO3_MD5;
+
+  /* No known algorithm has been detected */
+  return MHD_DIGEST_AUTH_ALGO3_INVALID;
+}
+
+
+/**
+ * Get unquoted version of Digest Authorization parameter.
+ * This function automatically zero-teminate the result.
+ * @param param the parameter to extract
+ * @param[out] buf the output buffer, must be enough size to hold the result,
+ *                 the recommended size is 'param->value.len + 1'
+ * @return the size of the result, not including the terminating zero
+ */
+static size_t
+get_rq_param_unquoted_copy_z (const struct MHD_RqDAuthParam *param, char *buf)
+{
+  size_t len;
+  mhd_assert (NULL != param->value.str);
+  if (! param->quoted)
+  {
+    memcpy (buf, param->value.str, param->value.len);
+    buf [param->value.len] = 0;
+    return param->value.len;
+  }
+
+  len = MHD_str_unquote (param->value.str, param->value.len, buf);
+  mhd_assert (0 != len);
+  mhd_assert (len < param->value.len);
+  buf[len] = 0;
+  return len;
+}
+
+
+/**
+ * Get decoded version of username from extended notation.
+ * This function automatically zero-teminate the result.
+ * @param uname_ext the string of client's 'username*' parameter value
+ * @param uname_ext_len the length of @a uname_ext in chars
+ * @param[out] buf the output buffer to put decoded username value
+ * @param buf_size the size of @a buf
+ * @return the number of characters copied to the output buffer or
+ *         -1 if wrong extended notation is used.
+ */
+static ssize_t
+get_rq_extended_uname_copy_z (const char *uname_ext, size_t uname_ext_len,
+                              char *buf, size_t buf_size)
+{
+  size_t r;
+  size_t w;
+  if ((size_t) SSIZE_MAX < uname_ext_len)
+    return -1; /* Too long input string */
+
+  if (MHD_DAUTH_EXT_PARAM_MIN_LEN > uname_ext_len)
+    return -1; /* Required prefix is missing */
+
+  if (! MHD_str_equal_caseless_bin_n_ (uname_ext, MHD_DAUTH_EXT_PARAM_PREFIX,
+                                       MHD_STATICSTR_LEN_ ( \
+                                         MHD_DAUTH_EXT_PARAM_PREFIX)))
+    return -1; /* Only UTF-8 is supported, as it is implied by RFC 7616 */
+
+  r = MHD_STATICSTR_LEN_ (MHD_DAUTH_EXT_PARAM_PREFIX);
+  /* Skip language tag */
+  while (r < uname_ext_len && '\'' != uname_ext[r])
+  {
+    const char chr = uname_ext[r];
+    if ((' ' == chr) || ('\t' == chr) || ('\"' == chr) || (',' == chr) ||
+        (';' == chr) )
+      return -1; /* Wrong char in language tag */
+    r++;
+  }
+  if (r >= uname_ext_len)
+    return -1; /* The end of the language tag was not found */
+  r++; /* Advance to the next char */
+
+  w = MHD_str_pct_decode_strict_n_ (uname_ext + r, uname_ext_len - r,
+                                    buf, buf_size);
+  if ((0 == w) && (0 != uname_ext_len - r))
+    return -1; /* Broken percent encoding */
+  buf[w] = 0; /* Zero terminate the result */
+  mhd_assert (SSIZE_MAX > w);
+  return (ssize_t) w;
+}
+
+
+/**
+ * Get copy of username used by the client.
+ * @param params the Digest Authorization parameters
+ * @param uname_type the type of username
+ * @param[out] unames the pointer to the structure to be filled
+ * @param buf the buffer to be used for usernames
+ * @param buf_size the size of the @a buf
+ * @return the size of the @a buf used by pointers in @a unames structure
+ */
+static size_t
+get_rq_uname (const struct MHD_RqDAuth *params,
+              enum MHD_DigestAuthUsernameType uname_type,
+              struct MHD_DigestAuthUsernameInfo *uname_info,
+              uint8_t *buf,
+              size_t buf_size)
+{
+  size_t buf_used;
+
+  buf_used = 0;
+  mhd_assert (get_rq_uname_type (params) == uname_type);
+  mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type);
+  mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type);
+
+  if ( (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) ||
+       (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) )
+  {
+    uname_info->username = (char *) (buf + buf_used);
+    uname_info->username_len =
+      get_rq_param_unquoted_copy_z (&params->username,
+                                    uname_info->username);
+    buf_used += uname_info->username_len + 1;
+    if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type)
+    {
+      uname_info->userhash_bin_size = MHD_hex_to_bin (uname_info->username,
+                                                      uname_info->username_len,
+                                                      buf + buf_used);
+      if ( (0 == uname_info->userhash_bin_size) &&
+           (0 != uname_info->username_len) )
+      {
+        uname_info->userhash_bin = NULL;
+        uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID;
+      }
+      else
+      {
+        uname_info->userhash_bin = (uint8_t *) (buf + buf_used);
+        buf_used += uname_info->userhash_bin_size;
+      }
+    }
+  }
+  else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type)
+  {
+    ssize_t res;
+    res = get_rq_extended_uname_copy_z (params->username_ext.value.str,
+                                        params->username_ext.value.len,
+                                        (char *) (buf + buf_used),
+                                        buf_size - buf_used);
+    if (0 > res)
+      uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID;
+    else
+    {
+      uname_info->username = (char *) (buf + buf_used);
+      uname_info->username_len = (size_t) res;
+      buf_used += uname_info->username_len + 1;
+    }
+  }
+  mhd_assert (buf_size >= buf_used);
+  return buf_used;
+}
+
+
+/**
+ * Get QOP ('quality of protection') type.
+ * @param params the Digest Authorization parameters
+ * @return detected QOP ('quality of protection') type.
+ */
+static enum MHD_DigestAuthQOP
+get_rq_qop (const struct MHD_RqDAuth *params)
+{
+  const struct MHD_RqDAuthParam *const qop_param =
+    &params->qop;
+  if (NULL == qop_param->value.str)
+    return MHD_DIGEST_AUTH_QOP_NONE;
+  if (qop_param->quoted)
+  {
+    if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \
+                                               qop_param->value.len, \
+                                               "auth"))
+      return MHD_DIGEST_AUTH_QOP_AUTH;
+    if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \
+                                               qop_param->value.len, \
+                                               "auth-int"))
+      return MHD_DIGEST_AUTH_QOP_AUTH_INT;
+  }
+  else
+  {
+    if (MHD_str_equal_caseless_s_bin_n_ ("auth", \
+                                         qop_param->value.str, \
+                                         qop_param->value.len))
+      return MHD_DIGEST_AUTH_QOP_AUTH;
+    if (MHD_str_equal_caseless_s_bin_n_ ("auth-int", \
+                                         qop_param->value.str, \
+                                         qop_param->value.len))
+      return MHD_DIGEST_AUTH_QOP_AUTH_INT;
+  }
+  /* No know QOP has been detected */
+  return MHD_DIGEST_AUTH_QOP_INVALID;
+}
+
+
+/**
+ * Result of request's Digest Authorization 'nc' value extraction
+ */
+enum MHD_GetRqNCResult
+{
+  MHD_GET_RQ_NC_NONE = -1,    /**< No 'nc' value */
+  MHD_GET_RQ_NC_VALID = 0,    /**< Readable 'nc' value */
+  MHD_GET_RQ_NC_TOO_LONG = 1, /**< The 'nc' value is too long */
+  MHD_GET_RQ_NC_TOO_LARGE = 2,/**< The 'nc' value is too big to fit uint32_t */
+  MHD_GET_RQ_NC_BROKEN = 3    /**< The 'nc' value is not a number */
+};
+
+
+/**
+ * Get 'nc' value from request's Authorization header
+ * @param params the request digest authentication
+ * @param[out] nc the pointer to put nc value to
+ * @return enum value indicating the result
+ */
+static enum MHD_GetRqNCResult
+get_rq_nc (const struct MHD_RqDAuth *params,
+           uint32_t *nc)
+{
+  const struct MHD_RqDAuthParam *const nc_param =
+    &params->nc;
+  char unq[16];
+  const char *val;
+  size_t val_len;
+  size_t res;
+  uint64_t nc_val;
+
+  if (NULL == nc_param->value.str)
+    return MHD_GET_RQ_NC_NONE;
+
+  if (0 == nc_param->value.len)
+    return MHD_GET_RQ_NC_BROKEN;
+
+  if (! nc_param->quoted)
+  {
+    val = nc_param->value.str;
+    val_len = nc_param->value.len;
+  }
+  else
+  {
+    /* Actually no backslashes must be used in 'nc' */
+    if (sizeof(unq) < params->nc.value.len)
+      return MHD_GET_RQ_NC_TOO_LONG;
+    val_len = MHD_str_unquote (nc_param->value.str, nc_param->value.len, unq);
+    if (0 == val_len)
+      return MHD_GET_RQ_NC_BROKEN;
+    val = unq;
+  }
+
+  res = MHD_strx_to_uint64_n_ (val, val_len, &nc_val);
+  if (0 == res)
+  {
+    const char f = val[0];
+    if ( (('9' >= f) && ('0' <= f)) ||
+         (('F' >= f) && ('A' <= f)) ||
+         (('a' <= f) && ('f' >= f)) )
+      return MHD_GET_RQ_NC_TOO_LARGE;
+    else
+      return MHD_GET_RQ_NC_BROKEN;
+  }
+  if (val_len != res)
+    return MHD_GET_RQ_NC_BROKEN;
+  if (UINT32_MAX < nc_val)
+    return MHD_GET_RQ_NC_TOO_LARGE;
+  *nc = (uint32_t) nc_val;
+  return MHD_GET_RQ_NC_VALID;
+}
+
+
+/**
+ * Get information about Digest Authorization client's header.
+ *
+ * @param connection The MHD connection structure
+ * @return NULL no valid Digest Authorization header is used in the request;
+ *         a pointer structure with information if the valid request header
+ *         found, free using #MHD_free().
+ * @note Available since #MHD_VERSION 0x00097519
+ * @ingroup authentication
+ */
+_MHD_EXTERN struct MHD_DigestAuthInfo *
+MHD_digest_auth_get_request_info3 (struct MHD_Connection *connection)
+{
+  const struct MHD_RqDAuth *params;
+  struct MHD_DigestAuthInfo *info;
+  enum MHD_DigestAuthUsernameType uname_type;
+  size_t unif_buf_size;
+  uint8_t *unif_buf_ptr;
+  size_t unif_buf_used;
+  enum MHD_GetRqNCResult nc_res;
+
+  params = get_rq_dauth_params (connection);
+  if (NULL == params)
+    return NULL;
+
+  unif_buf_size = 0;
+
+  uname_type = get_rq_uname_type (params);
+
+  unif_buf_size += get_rq_unames_size (params, uname_type);
+
+  if (NULL != params->opaque.value.str)
+    unif_buf_size += params->opaque.value.len + 1;  /* Add one for 
zero-termination */
+  if (NULL != params->realm.value.str)
+    unif_buf_size += params->realm.value.len + 1;   /* Add one for 
zero-termination */
+  info = (struct MHD_DigestAuthInfo *)
+         MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthInfo)) + unif_buf_size);
+  unif_buf_ptr = (uint8_t *) (info + 1);
+  unif_buf_used = 0;
+
+  info->algo = get_rq_algo (params);
+
+  if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type) &&
+       (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type) )
+  {
+    struct MHD_DigestAuthUsernameInfo uname_strct;
+    memset (&uname_strct, 0, sizeof(uname_strct));
+    unif_buf_used += get_rq_uname (params, uname_type, &uname_strct,
+                                   unif_buf_ptr + unif_buf_used,
+                                   unif_buf_size - unif_buf_used);
+    info->uname_type = uname_strct.uname_type;
+    info->username = uname_strct.username;
+    info->username_len = uname_strct.username_len;
+    info->userhash_bin = uname_strct.userhash_bin;
+    info->userhash_bin_size = uname_strct.userhash_bin_size;
+  }
+  else
+    info->uname_type = uname_type;
+
+  if (NULL != params->opaque.value.str)
+  {
+    info->opaque = (char *) (unif_buf_ptr + unif_buf_used);
+    info->opaque_len = get_rq_param_unquoted_copy_z (&params->opaque,
+                                                     info->opaque);
+    unif_buf_used += info->opaque_len + 1;
+  }
+  if (NULL != params->realm.value.str)
+  {
+    info->realm = (char *) (unif_buf_ptr + unif_buf_used);
+    info->realm_len = get_rq_param_unquoted_copy_z (&params->realm,
+                                                    info->realm);
+    unif_buf_used += info->realm_len + 1;
+  }
+
+  mhd_assert (unif_buf_size >= unif_buf_used);
+
+  info->qop = get_rq_qop (params);
+
+  if (NULL != params->cnonce.value.str)
+    info->cnonce_len = params->cnonce.value.len;
+  else
+    info->cnonce_len = 0;
+
+  nc_res = get_rq_nc (params, &info->nc);
+  if (MHD_GET_RQ_NC_VALID != nc_res)
+    info->nc = MHD_DIGEST_AUTH_INVALID_NC_VALUE;
+
+  return info;
+}
+
+
+/**
+ * Get the username from Digest Authorization client's header.
+ *
+ * @param connection The MHD connection structure
+ * @return NULL if no valid Digest Authorization header is used in the request,
+ *         or no username parameter is present in the header, or username is
+ *         provided incorrectly by client (see description for
+ *         #MHD_DIGEST_AUTH_UNAME_TYPE_INVALID);
+ *         a pointer structure with information if the valid request header
+ *         found, free using #MHD_free().
+ * @sa MHD_digest_auth_get_request_info3() provides more complete information
+ * @note Available since #MHD_VERSION 0x00097519
+ * @ingroup authentication
+ */
+_MHD_EXTERN struct MHD_DigestAuthUsernameInfo *
+MHD_digest_auth_get_username3 (struct MHD_Connection *connection)
+{
+  const struct MHD_RqDAuth *params;
+  struct MHD_DigestAuthUsernameInfo *uname_info;
+  enum MHD_DigestAuthUsernameType uname_type;
+  size_t unif_buf_size;
+  uint8_t *unif_buf_ptr;
+  size_t unif_buf_used;
+
+  params = get_rq_dauth_params (connection);
+  if (NULL == params)
+    return NULL;
+
+  uname_type = get_rq_uname_type (params);
+  if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING == uname_type) ||
+       (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_type) )
+    return NULL;
+
+  unif_buf_size = get_rq_unames_size (params, uname_type);
+
+  uname_info = (struct MHD_DigestAuthUsernameInfo *)
+               MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthUsernameInfo))
+                            + unif_buf_size);
+  unif_buf_ptr = (uint8_t *) (uname_info + 1);
+  unif_buf_used = get_rq_uname (params, uname_type, uname_info, unif_buf_ptr,
+                                unif_buf_size);
+  mhd_assert (unif_buf_size >= unif_buf_used);
+  (void) unif_buf_used; /* Mute compiler warning on non-debug builds */
+  mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_info->uname_type);
+
+  if (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_info->uname_type)
+  {
+    free (uname_info);
+    return NULL;
+  }
+  mhd_assert (uname_type == uname_info->uname_type);
+
+  return uname_info;
+}
+
+
 /**
  * Get the username from the authorization header sent by the client
  *
@@ -977,10 +1318,10 @@ calculate_nonce (uint64_t nonce_time,
   {
     if (MHD_GET_ARGUMENT_KIND != h->kind)
       continue;
-    digest_update (da, (const uint8_t *) "##", 3);
+    digest_update (da, (const uint8_t *) "\0", 2);
     if (0 != h->header_size)
       digest_update (da, (const uint8_t *) h->header, h->header_size);
-    digest_update (da, (const uint8_t *) "#", 2);
+    digest_update (da, (const uint8_t *) "", 1);
     if (0 != h->value_size)
       digest_update (da, (const uint8_t *) h->value, h->value_size);
   }
@@ -1276,39 +1617,29 @@ test_header (void *cls,
  * got as part of the HTTP request URI.
  *
  * @param connection connections with headers to compare against
- * @param args argument URI string (after "?" in URI)
- * @return #MHD_YES if the arguments match,
- *         #MHD_NO if not
+ * @param args the copy of argument URI string (after "?" in URI), will be
+ *             modified by this function
+ * @return boolean true if the arguments match,
+ *         boolean false if not
  */
-static enum MHD_Result
+static bool
 check_argument_match (struct MHD_Connection *connection,
-                      const char *args)
+                      char *args)
 {
   struct MHD_HTTP_Req_Header *pos;
-  char *argb;
   enum MHD_Result ret;
   struct test_header_param param;
 
-  argb = strdup (args);
-  if (NULL == argb)
-  {
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ ("Failed to allocate memory for copy of URI arguments.\n"));
-#endif /* HAVE_MESSAGES */
-    return MHD_NO;
-  }
   param.connection = connection;
   param.num_headers = 0;
   ret = MHD_parse_arguments_ (connection,
                               MHD_GET_ARGUMENT_KIND,
-                              argb,
+                              args,
                               &test_header,
                               &param);
-  free (argb);
   if (MHD_NO == ret)
   {
-    return MHD_NO;
+    return false;
   }
   /* also check that the number of headers matches */
   for (pos = connection->headers_received; NULL != pos; pos = pos->next)
@@ -1320,9 +1651,64 @@ check_argument_match (struct MHD_Connection *connection,
   if (0 != param.num_headers)
   {
     /* argument count mismatch */
-    return MHD_NO;
+    return false;
+  }
+  return true;
+}
+
+
+/**
+ * Check that the URI provided by the client as part
+ * of the authentication header match the real HTTP request URI.
+ *
+ * @param connection connections with headers to compare against
+ * @param uri the copy of URI in the authentication header, should point to
+ *            modifiable buffer at least @a uri_len + 1 characters long,
+ *            will be modified by this function, not valid upon return
+ * @param uri_len the length of the @a uri string in characters
+ * @return boolean true if the URIs match,
+ *         boolean false if not
+ */
+static bool
+check_uri_match (struct MHD_Connection *connection, char *uri, size_t uri_len)
+{
+  char *qmark;
+  char *args;
+  struct MHD_Daemon *const daemon = connection->daemon;
+
+  uri[uri_len] = 0;
+  qmark = memchr (uri,
+                  '?',
+                  uri_len);
+  if (NULL != qmark)
+    *qmark = '\0';
+
+  /* Need to unescape URI before comparing with connection->url */
+  uri_len = daemon->unescape_callback (daemon->unescape_callback_cls,
+                                       connection,
+                                       uri);
+  if ((uri_len != connection->url_len) ||
+      (0 != memcmp (uri, connection->url, uri_len)))
+  {
+#ifdef HAVE_MESSAGES
+    MHD_DLOG (daemon,
+              _ ("Authentication failed, URI does not match.\n"));
+#endif
+    return false;
+  }
+
+  args = (NULL != qmark) ? (qmark + 1) : uri + uri_len;
+
+  if (! check_argument_match (connection,
+                              args) )
+  {
+#ifdef HAVE_MESSAGES
+    MHD_DLOG (daemon,
+              _ ("Authentication failed, arguments do not match.\n"));
+#endif
+    return false;
   }
-  return MHD_YES;
+  return true;
 }
 
 
@@ -1491,6 +1877,30 @@ is_param_equal (const struct MHD_RqDAuthParam *param,
 }
 
 
+/**
+ * Check whether Digest Auth request parameter is caseless equal to given 
string
+ * @param param the parameter to check
+ * @param str the string to compare with, does not need to be zero-terminated
+ * @param str_len the length of the @a str
+ * @return true is parameter is caseless equal to the given string,
+ *         false otherwise
+ */
+_MHD_static_inline bool
+is_param_equal_caseless (const struct MHD_RqDAuthParam *param,
+                         const char *const str,
+                         const size_t str_len)
+{
+  mhd_assert (NULL != param->value.str);
+  mhd_assert (0 != param->value.len);
+  if (param->quoted)
+    return MHD_str_equal_quoted_bin_n (param->value.str, param->value.len,
+                                       str, str_len);
+  return (str_len == param->value.len) &&
+         (0 == memcmp (str, param->value.str, str_len));
+
+}
+
+
 /**
  * Authenticates the authorization header sent by the client
  *
@@ -1523,18 +1933,20 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
                              char **pbuf)
 {
   struct MHD_Daemon *daemon = MHD_get_master (connection->daemon);
-  char cnonce[MAX_CLIENT_NONCE_LENGTH];
+  static const uint8_t colon = (uint8_t) ':';
   const unsigned int digest_size = digest_get_size (da);
-  char ha1[VLA_ARRAY_LEN_DIGEST (digest_size) * 2 + 1];
-  char qop[15]; /* auth,auth-int */
-  char nc[20];
-  char response[MAX_AUTH_RESPONSE_LENGTH];
+  char hdigest1[VLA_ARRAY_LEN_DIGEST (digest_size) * 2 + 1];
+  char hdigest2[VLA_ARRAY_LEN_DIGEST (digest_size) * 2 + 1];
+  char *ha1;
+  char *ha2;
+  uint8_t *response_bin;
+#if 0
   const char *hentity = NULL; /* "auth-int" is not supported */
+#endif
   char noncehashexp[NONCE_STD_LEN (VLA_ARRAY_LEN_DIGEST (digest_size)) + 1];
   uint64_t nonce_time;
   uint64_t t;
   uint64_t nci;
-  char *qmark;
   const struct MHD_RqDAuth *params;
   char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE]; /**< Temporal buffer in stack for 
unquoting */
   char **const ptmp2 = pbuf;     /**< Temporal malloc'ed buffer for unquoting 
*/
@@ -1542,34 +1954,24 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
   struct _MHD_str_w_len unquoted;
   struct _MHD_mstr_w_len unq_copy;
   enum _MHD_GetUnqResult unq_res;
-  enum MHD_DigestAuthResult ret;
   size_t username_len;
   size_t realm_len;
 
   tmp2_size = 0;
+  ha1 = NULL;
+  ha2 = NULL;
 
   params = get_rq_dauth_params (connection);
   if (NULL == params)
     return MHD_DAUTH_WRONG_HEADER;
 
-  /* Check 'username' */
+  /* ** A quick check for presence of all required parameters ** */
   if (NULL == params->username.value.str)
     return MHD_DAUTH_WRONG_HEADER;
 
-  username_len = strlen (username);
-  if (! is_param_equal (&params->username, username, username_len))
-    return MHD_DAUTH_WRONG_USERNAME;
-  /* 'username' valid */
-
-  /* Check 'realm' */
   if (NULL == params->realm.value.str)
     return MHD_DAUTH_WRONG_HEADER;
-  realm_len = strlen (realm);
-  if (! is_param_equal (&params->realm, realm, realm_len))
-    return MHD_DAUTH_WRONG_REALM;
-  /* 'realm' valid */
 
-  /* Check 'nonce' */
   if (NULL == params->nonce.value.str)
     return MHD_DAUTH_WRONG_HEADER;
   else if (0 == params->nonce.value.len)
@@ -1577,116 +1979,82 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
   else if (NONCE_STD_LEN (digest_size) * 2 < params->nonce.value.len)
     return MHD_DAUTH_NONCE_WRONG;
 
-  unq_res = get_unquoted_param (&params->nonce, tmp1, ptmp2, &tmp2_size,
-                                &unquoted);
-  if (_MHD_UNQ_OK != unq_res)
-    return MHD_DAUTH_ERROR;
-  if (NONCE_STD_LEN (digest_size) != unquoted.len)
-    return MHD_DAUTH_NONCE_WRONG;
-
-  if (! get_nonce_timestamp (unquoted.str, unquoted.len, &nonce_time))
-  {
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (daemon,
-              _ ("Authentication failed, invalid timestamp format.\n"));
-#endif
-    return MHD_DAUTH_NONCE_WRONG;
-  }
-  t = MHD_monotonic_msec_counter ();
-  /*
-   * First level vetting for the nonce validity: if the timestamp
-   * attached to the nonce exceeds `nonce_timeout', then the nonce is
-   * invalid.
-   */
-  if (TRIM_TO_TIMESTAMP (t - nonce_time) > (nonce_timeout * 1000))
-    return MHD_DAUTH_NONCE_STALE; /* too old */
-
-  calculate_nonce (nonce_time,
-                   connection->method,
-                   daemon->digest_auth_random,
-                   daemon->digest_auth_rand_size,
-                   connection->url,
-                   connection->url_len,
-                   connection->headers_received,
-                   realm,
-                   realm_len,
-                   da,
-                   noncehashexp);
-  /*
-   * Second level vetting for the nonce validity
-   * if the timestamp attached to the nonce is valid
-   * and possibly fabricated (in case of an attack)
-   * the attacker must also know the random seed to be
-   * able to generate a "sane" nonce, which if he does
-   * not, the nonce fabrication process going to be
-   * very hard to achieve.
-   */
-  if ((0 != strncmp (noncehashexp, unquoted.str, unquoted.len)) ||
-      (0 != noncehashexp[unquoted.len]))
-    return MHD_DAUTH_NONCE_WRONG;
-  /* 'nonce' valid */
+  if (NULL == params->nc.value.str)
+    return MHD_DAUTH_WRONG_HEADER;
+  else if (0 == params->nc.value.len)
+    return MHD_DAUTH_WRONG_HEADER;
+  else if (4 * 8 < params->nc.value.len) /* Four times more than needed */
+    return MHD_DAUTH_WRONG_HEADER;
 
-  /* Get 'cnonce' */
   if (NULL == params->cnonce.value.str)
     return MHD_DAUTH_WRONG_HEADER;
   else if (0 == params->cnonce.value.len)
     return MHD_DAUTH_WRONG_HEADER;
-  unq_res = get_unquoted_param (&params->cnonce, tmp1, ptmp2, &tmp2_size,
-                                &unquoted);
-  if (_MHD_UNQ_OK != unq_res)
-    return (_MHD_UNQ_TOO_LARGE == unq_res) ?
-           MHD_DAUTH_TOO_LARGE : MHD_DAUTH_ERROR;
-
-  if (sizeof(cnonce) <= unquoted.len)
-    return MHD_DAUTH_ERROR; /* TODO: handle large client nonces */
-
-  /* TODO: avoid memcpy() */
-  memcpy (cnonce, unquoted.str, unquoted.len);
-  cnonce[unquoted.len] = 0;
-  /* Got 'cnonce' */
+  else if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < params->cnonce.value.len)
+    return MHD_DAUTH_TOO_LARGE;
 
-  /* Get 'qop' */
   if (NULL == params->qop.value.str)
     return MHD_DAUTH_WRONG_HEADER;
   else if (0 == params->qop.value.len)
     return MHD_DAUTH_WRONG_QOP;
   else if (MHD_STATICSTR_LEN_ ("auth-int") * 2 < params->qop.value.len)
     return MHD_DAUTH_WRONG_QOP;
-  unq_res = get_unquoted_param (&params->qop, tmp1, ptmp2, &tmp2_size,
-                                &unquoted);
-  if (_MHD_UNQ_OK != unq_res)
-    return MHD_DAUTH_ERROR;
-
-  if (sizeof(qop) <= unquoted.len)
-    return MHD_DAUTH_ERROR; /* TODO: handle large client qop */
-  /* TODO: avoid memcpy() */
-  memcpy (qop, unquoted.str, unquoted.len);
-  qop[unquoted.len] = 0;
-  /* TODO: Really support empty value, support "auth-int" */
-  if ( ((MHD_STATICSTR_LEN_ ("auth") != unquoted.len) ||
-        (! MHD_str_equal_caseless_bin_n_ (qop, "auth", unquoted.len))) &&
-       (0 != strcmp (qop,"")) )
-    return MHD_DAUTH_WRONG_QOP;
-  /* Got 'qop' */
 
-  /* Get 'nc' */
-  if (NULL == params->nc.value.str)
+  if (NULL == params->response.value.str)
     return MHD_DAUTH_WRONG_HEADER;
-  else if (0 == params->nc.value.len)
+  else if (0 == params->response.value.len)
+    return MHD_DAUTH_RESPONSE_WRONG;
+  else if (digest_size * 4 < params->response.value.len)
+    return MHD_DAUTH_RESPONSE_WRONG;
+
+  if (NULL == params->uri.value.str)
     return MHD_DAUTH_WRONG_HEADER;
-  else if (4 * 8 < params->nc.value.len) /* Four time more than needed */
-    return MHD_DAUTH_NONCE_WRONG;
+  else if (0 == params->uri.value.len)
+    return MHD_DAUTH_WRONG_URI;
+  else if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < params->uri.value.len)
+    return MHD_DAUTH_TOO_LARGE;
+
+  /* ** Check simple parameters match ** */
+
+  /* Check 'username' */
+  username_len = strlen (username);
+  if (! is_param_equal (&params->username, username, username_len))
+    return MHD_DAUTH_WRONG_USERNAME;
+  /* 'username' valid */
+
+  /* Check 'realm' */
+  realm_len = strlen (realm);
+  if (! is_param_equal (&params->realm, realm, realm_len))
+    return MHD_DAUTH_WRONG_REALM;
+  /* 'realm' valid */
+
+  /* Check 'qop' */
+  /* TODO: support MHD_DIGEST_AUTH_QOP_NONE and MHD_DIGEST_AUTH_QOP_AUTH_INT */
+  if (MHD_DIGEST_AUTH_QOP_AUTH != get_rq_qop (params))
+    return MHD_DAUTH_WRONG_QOP;
+  /* 'qop' valid */
+
+  /* Check 'algorithm' */
+  if (1)
+  {
+    const enum MHD_DigestAuthAlgo3 r_algo = get_rq_algo (params);
+    const enum MHD_DigestBaseAlgo p_algo = da->algo;
+    if ( (! ((MHD_DIGEST_AUTH_ALGO3_MD5 == r_algo) &&
+             (MHD_DIGEST_BASE_ALGO_MD5 == p_algo))) &&
+         (! ((MHD_DIGEST_AUTH_ALGO3_SHA256 == r_algo) &&
+             (MHD_DIGEST_BASE_ALGO_SHA256 == p_algo))) )
+      return MHD_DAUTH_WRONG_ALGO;
+  }
+  /* 'algorithm' valid */
+
+  /* ** Do basic nonce and nonce-counter checks (size, timestamp) ** */
+  /* Get 'nc' digital value */
   unq_res = get_unquoted_param (&params->nc, tmp1, ptmp2, &tmp2_size,
                                 &unquoted);
   if (_MHD_UNQ_OK != unq_res)
     return MHD_DAUTH_ERROR;
 
-  if (sizeof(nc) <= unquoted.len)
-    return MHD_DAUTH_ERROR;
-  /* TODO: avoid memcpy() */
-  memcpy (nc, unquoted.str, unquoted.len);
-  nc[unquoted.len] = 0;
-  if (unquoted.len != MHD_strx_to_uint64_n_ (nc,
+  if (unquoted.len != MHD_strx_to_uint64_n_ (unquoted.str,
                                              unquoted.len,
                                              &nci))
   {
@@ -1704,31 +2072,30 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
 #endif
     return MHD_DAUTH_WRONG_HEADER;   /* invalid nc value */
   }
-  /* Got 'nc' */
-
-  /* Get 'response' */
-  if (NULL == params->response.value.str)
-    return MHD_DAUTH_WRONG_HEADER;
-  else if (0 == params->response.value.len)
-    return MHD_DAUTH_RESPONSE_WRONG;
-  else if (digest_size * 4 < params->response.value.len)
-    return MHD_DAUTH_RESPONSE_WRONG;
-  unq_res = get_unquoted_param (&params->response, tmp1, ptmp2, &tmp2_size,
+  /* Got 'nc' digital value */
+  /* Get 'nonce' with basic checks */
+  unq_res = get_unquoted_param (&params->nonce, tmp1, ptmp2, &tmp2_size,
                                 &unquoted);
   if (_MHD_UNQ_OK != unq_res)
     return MHD_DAUTH_ERROR;
-  if (digest_size * 2 != unquoted.len)
-    return MHD_DAUTH_RESPONSE_WRONG;
-
-  mhd_assert (sizeof(response) > unquoted.len);
-
-  if (sizeof(response) <= unquoted.len)
-    return MHD_DAUTH_ERROR;
-  /* TODO: avoid memcpy() */
-  memcpy (response, unquoted.str, unquoted.len);
-  response[unquoted.len] = 0;
-  /* Got 'response' */
 
+  if ((NONCE_STD_LEN (digest_size) != unquoted.len) ||
+      (! get_nonce_timestamp (unquoted.str, unquoted.len, &nonce_time)))
+  {
+#ifdef HAVE_MESSAGES
+    MHD_DLOG (daemon,
+              _ ("Authentication failed, invalid nonce format.\n"));
+#endif
+    return MHD_DAUTH_NONCE_WRONG;
+  }
+  t = MHD_monotonic_msec_counter ();
+  /*
+   * First level vetting for the nonce validity: if the timestamp
+   * attached to the nonce exceeds `nonce_timeout', then the nonce is
+   * invalid.
+   */
+  if (TRIM_TO_TIMESTAMP (t - nonce_time) > (nonce_timeout * 1000))
+    return MHD_DAUTH_NONCE_STALE; /* too old */
   if (1)
   {
     enum MHD_CheckNonceNC_ nonce_nc_check;
@@ -1738,7 +2105,7 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
      * generated previously.
      */
     nonce_nc_check = check_nonce_nc (connection,
-                                     noncehashexp,
+                                     unquoted.str,
                                      NONCE_STD_LEN (digest_size),
                                      nonce_time,
                                      nci);
@@ -1762,110 +2129,115 @@ digest_auth_check_all_inner (struct MHD_Connection 
*connection,
     }
     mhd_assert (MHD_CHECK_NONCENC_OK == nonce_nc_check);
   }
+  /* The nonce was generated by MHD, is not stale and nonce-nc combination was
+     not used before */
 
+  /* ** Build H(A2) and check URI match in the header and in the request ** */
   /* Get 'uri' */
-  if (NULL == params->uri.value.str)
-    return MHD_DAUTH_WRONG_HEADER;
-  else if (0 == params->uri.value.len)
-    return MHD_DAUTH_WRONG_URI;
+  digest_init (da);
+  digest_update_str (da, connection->method);
+  digest_update (da, &colon, 1);
+#if 0
+  /* TODO: add support for "auth-int" */
+  digest_update_str (da, hentity);
+  digest_update (da, &colon, 1);
+#endif
   unq_res = get_unquoted_param_copy (&params->uri, tmp1, ptmp2, &tmp2_size,
                                      &unq_copy);
   if (_MHD_UNQ_OK != unq_res)
-    return (_MHD_UNQ_TOO_LARGE == unq_res) ?
-           MHD_DAUTH_TOO_LARGE : MHD_DAUTH_ERROR;
+    return MHD_DAUTH_ERROR;
+
+  digest_update (da, (const uint8_t *) unq_copy.str, unq_copy.len);
+  /* The next check will modify copied URI string */
+  if (! check_uri_match (connection, unq_copy.str, unq_copy.len))
+    return MHD_DAUTH_WRONG_URI;
+  digest_calc_hash (da);
+  ha2 = hdigest2;
+  MHD_bin_to_hex (digest_get_bin (da), digest_size, ha2);
+  /* Got H(A2) */
 
-  if (NULL != digest)
+  /* ** Build H(A1) ** */
+  ha1 = hdigest1;
+  if (NULL == digest)
   {
-    /* This will initialize da->digest_hex (ha1) */
-    digest_calc_ha1_from_digest (digest_get_algo_name (da),
-                                 da,
-                                 digest,
-                                 noncehashexp,
-                                 cnonce);
+    digest_init (da);
+    digest_update (da, (const uint8_t *) username, username_len);
+    digest_update (da, &colon, 1);
+    digest_update (da, (const uint8_t *) realm, realm_len);
+    digest_update (da, &colon, 1);
+    digest_update_str (da, password);
+    digest_calc_hash (da);
+    MHD_bin_to_hex (digest_get_bin (da), digest_size, ha1);
   }
   else
-  {
-    /* This will initialize da->digest_hex (ha1) */
-    mhd_assert (NULL != password);   /* NULL == digest => password != NULL */
-    digest_calc_ha1_from_user (digest_get_algo_name (da),
-                               username,
-                               username_len,
-                               realm,
-                               realm_len,
-                               password,
-                               noncehashexp,
-                               cnonce,
-                               da);
-  }
-  memcpy (ha1,
-          digest_get_hex_buffer (da),
-          digest_size * 2 + 1);
-  /* This will initialize da->sessionkey (respexp) */
-  digest_calc_response (ha1,
-                        noncehashexp,
-                        nc,
-                        cnonce,
-                        qop,
-                        connection->method,
-                        unq_copy.str,
-                        unq_copy.len,
-                        hentity,
-                        da);
-  if (1)
-  {
-    char *uri;
-    size_t uri_len;
-    uri = unq_copy.str;
-    uri_len = unq_copy.len;
-
-    uri[uri_len] = 0;
-    qmark = memchr (uri,
-                    '?',
-                    uri_len);
-    if (NULL != qmark)
-      *qmark = '\0';
-
-    /* Need to unescape URI before comparing with connection->url */
-    uri_len = daemon->unescape_callback (daemon->unescape_callback_cls,
-                                         connection,
-                                         uri);
-    if ((uri_len != connection->url_len) ||
-        (0 != memcmp (uri, connection->url, uri_len)))
-    {
-#ifdef HAVE_MESSAGES
-      MHD_DLOG (daemon,
-                _ ("Authentication failed, URI does not match.\n"));
-#endif
-      return MHD_DAUTH_WRONG_URI;
-    }
+    MHD_bin_to_hex (digest, digest_size, ha1);
+  /* TODO: support '-sess' versions */
+  /* Got H(A1) */
 
-    if (1)
-    {
-      const char *args = qmark;
+  /* **  Check 'response' ** */
+  digest_init (da);
+  digest_update (da, (const uint8_t *) ha1, digest_size * 2);
+  ha1 = NULL;
+  /* H(A1) is not needed anymore, reuse the buffer */
+  response_bin = (uint8_t *) hdigest1;
+  unq_res = get_unquoted_param (&params->response, tmp1, ptmp2, &tmp2_size,
+                                &unquoted);
+  if (_MHD_UNQ_OK != unq_res)
+    return MHD_DAUTH_ERROR;
+  if (digest_size != MHD_hex_to_bin (unquoted.str, unquoted.len, response_bin))
+    return MHD_DAUTH_RESPONSE_WRONG;
+  digest_update (da, &colon, 1);
+  unq_res = get_unquoted_param (&params->nonce, tmp1, ptmp2, &tmp2_size,
+                                &unquoted);
+  if (_MHD_UNQ_OK != unq_res)
+    return MHD_DAUTH_ERROR;
+  digest_update (da, (const uint8_t *) unquoted.str, unquoted.len);
+  digest_update (da, &colon, 1);
+  unq_res = get_unquoted_param (&params->nc, tmp1, ptmp2, &tmp2_size,
+                                &unquoted);
+  if (_MHD_UNQ_OK != unq_res)
+    return MHD_DAUTH_ERROR;
+  digest_update (da, (const uint8_t *) unquoted.str, unquoted.len);
+  digest_update (da, &colon, 1);
+  unq_res = get_unquoted_param (&params->cnonce, tmp1, ptmp2, &tmp2_size,
+                                &unquoted);
+  if (_MHD_UNQ_OK != unq_res)
+    return MHD_DAUTH_ERROR;
+  digest_update (da, (const uint8_t *) unquoted.str, unquoted.len);
+  digest_update (da, &colon, 1);
+  unq_res = get_unquoted_param (&params->qop, tmp1, ptmp2, &tmp2_size,
+                                &unquoted);
+  if (_MHD_UNQ_OK != unq_res)
+    return MHD_DAUTH_ERROR;
+  digest_update (da, (const uint8_t *) unquoted.str, unquoted.len);
+  digest_update (da, &colon, 1);
+  digest_update (da, (const uint8_t *) ha2, digest_size * 2);
+  ha2 = NULL;
+  digest_calc_hash (da);
+  if (0 != memcmp (digest_get_bin (da), response_bin, digest_size))
+    return MHD_DAUTH_RESPONSE_WRONG;
+  response_bin = NULL;
 
-      if (NULL == args)
-        args = "";
-      else
-        args++;
-      if (MHD_NO ==
-          check_argument_match (connection,
-                                args) )
-      {
-#ifdef HAVE_MESSAGES
-        MHD_DLOG (daemon,
-                  _ ("Authentication failed, arguments do not match.\n"));
-#endif
-        return MHD_DAUTH_WRONG_URI;
-      }
-    }
+  /* It was already checked that 'nonce' (including timestamp) was generated
+     by MHD. The next check is mostly an overcaution. */
+  calculate_nonce (nonce_time,
+                   connection->method,
+                   daemon->digest_auth_random,
+                   daemon->digest_auth_rand_size,
+                   connection->url,
+                   connection->url_len,
+                   connection->headers_received,
+                   realm,
+                   realm_len,
+                   da,
+                   noncehashexp);
 
-    ret = (0 == strcmp (response,
-                        digest_get_hex_buffer (da)))
-    ? MHD_DAUTH_OK
-    : MHD_DAUTH_RESPONSE_WRONG;
-  }
+  if (! is_param_equal (&params->nonce, noncehashexp,
+                        NONCE_STD_LEN (digest_size)))
+    return MHD_DAUTH_NONCE_WRONG;
+  /* The 'nonce' was generated in the same conditions */
 
-  return ret;
+  return MHD_DAUTH_OK;
 }
 
 
@@ -1970,7 +2342,17 @@ MHD_digest_auth_check3 (struct MHD_Connection 
*connection,
 
   mhd_assert (NULL != password);
 
-  if (! digest_setup (&da, algo))
+  if ((MHD_DIGEST_ALG_MD5 == algo) || (MHD_DIGEST_ALG_AUTO == algo))
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_MD5))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else if (MHD_DIGEST_ALG_SHA256 == algo)
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_SHA256))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else
     MHD_PANIC (_ ("Wrong algo value.\n")); /* API violation! */
 
   return digest_auth_check_all (connection,
@@ -2012,7 +2394,17 @@ MHD_digest_auth_check_digest3 (struct MHD_Connection 
*connection,
   struct DigestAlgorithm da;
 
   mhd_assert (NULL != digest);
-  if (! digest_setup (&da, algo))
+  if ((MHD_DIGEST_ALG_MD5 == algo) || (MHD_DIGEST_ALG_AUTO == algo))
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_MD5))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else if (MHD_DIGEST_ALG_SHA256 == algo)
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_SHA256))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else
     MHD_PANIC (_ ("Wrong algo value.\n")); /* API violation! */
 
   if (digest_get_size (&da) != digest_size)
@@ -2178,7 +2570,17 @@ MHD_queue_auth_fail_response2 (struct MHD_Connection 
*connection,
 
   struct DigestAlgorithm da;
 
-  if (! digest_setup (&da, algo))
+  if ((MHD_DIGEST_ALG_MD5 == algo) || (MHD_DIGEST_ALG_AUTO == algo))
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_MD5))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else if (MHD_DIGEST_ALG_SHA256 == algo)
+  {
+    if (! digest_setup (&da, MHD_DIGEST_BASE_ALGO_SHA256))
+      MHD_PANIC (_ ("Error initialising hash algorithm.\n"));
+  }
+  else
     MHD_PANIC (_ ("Wrong algo value.\n")); /* API violation! */
 
   if (NULL == response)
diff --git a/src/microhttpd/internal.c b/src/microhttpd/internal.c
index e9e92414..57553251 100644
--- a/src/microhttpd/internal.c
+++ b/src/microhttpd/internal.c
@@ -135,8 +135,8 @@ MHD_unescape_plus (char *arg)
 
 /**
  * Process escape sequences ('%HH') Updates val in place; the
- * result should be UTF-8 encoded and cannot be larger than the input.
- * The result must also still be 0-terminated.
+ * result cannot be larger than the input.
+ * The result is still be 0-terminated.
  *
  * @param val value to unescape (modified in the process)
  * @return length of the resulting val (`strlen(val)` may be
@@ -145,35 +145,7 @@ MHD_unescape_plus (char *arg)
 _MHD_EXTERN size_t
 MHD_http_unescape (char *val)
 {
-  char *rpos = val;
-  char *wpos = val;
-
-  while ('\0' != *rpos)
-  {
-    uint32_t num;
-    switch (*rpos)
-    {
-    case '%':
-      if (2 == MHD_strx_to_uint32_n_ (rpos + 1,
-                                      2,
-                                      &num))
-      {
-        *wpos = (char) ((unsigned char) num);
-        wpos++;
-        rpos += 3;
-        break;
-      }
-    /* TODO: add bad sequence handling */
-    /* intentional fall through! */
-    default:
-      *wpos = *rpos;
-      wpos++;
-      rpos++;
-    }
-  }
-  *wpos = '\0'; /* add 0-terminator */
-  mhd_assert (wpos >= val);
-  return (size_t) (wpos - val);
+  return MHD_str_pct_decode_in_place_lenient_ (val, NULL);
 }
 
 
diff --git a/src/microhttpd/mhd_str.c b/src/microhttpd/mhd_str.c
index 0c2b2f46..cf2e43c3 100644
--- a/src/microhttpd/mhd_str.c
+++ b/src/microhttpd/mhd_str.c
@@ -1387,6 +1387,350 @@ MHD_bin_to_hex (const void *bin,
 }
 
 
+size_t
+MHD_hex_to_bin (const char *hex,
+                size_t len,
+                void *bin)
+{
+  uint8_t *const out = (uint8_t *) 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 int l = toxdigitvalue (hex[r++]);
+    if (0 > l)
+      return 0;
+    out[w++] = (uint8_t) ((unsigned int) l);
+  }
+  while (r < len)
+  {
+    const int h = toxdigitvalue (hex[r++]);
+    const int l = toxdigitvalue (hex[r++]);
+    if ((0 > h) || (0 > l))
+      return 0;
+    out[w++] = ( (((uint8_t) ((unsigned int) h)) << 4)
+                 | ((uint8_t) ((unsigned int) l)) );
+  }
+  mhd_assert (len == r);
+  mhd_assert ((len + 1) / 2 == w);
+  return w;
+}
+
+
+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 int h = toxdigitvalue (pct_encoded[++r]);
+          const int l = toxdigitvalue (pct_encoded[++r]);
+          unsigned char out;
+          if ((0 > h) || (0 > l))
+            return 0;
+          out = (unsigned char) ( (((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 int h = toxdigitvalue (pct_encoded[++r]);
+        const int l = toxdigitvalue (pct_encoded[++r]);
+        unsigned char out;
+        if ((0 > h) || (0 > l))
+          return 0;
+        out = (unsigned char) ( (((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 */
+}
+
+
+size_t
+MHD_str_pct_decode_lenient_n_ (const char *pct_encoded,
+                               size_t pct_encoded_len,
+                               char *decoded,
+                               size_t buf_size,
+                               bool *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 int h = toxdigitvalue (pct_encoded[++r]);
+          const int l = toxdigitvalue (pct_encoded[++r]);
+          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) ((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 int h = toxdigitvalue (pct_encoded[++r]);
+        const int l = toxdigitvalue (pct_encoded[++r]);
+        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) ((unsigned int) h)) << 4)
+                                  | ((uint8_t) ((unsigned int) l)) );
+          decoded[w] = (char) out;
+        }
+      }
+    }
+    else
+      decoded[w] = chr;
+    ++r;
+    ++w;
+  }
+  return w;
+}
+
+
+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) ((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 */
+}
+
+
+size_t
+MHD_str_pct_decode_in_place_lenient_ (char *str,
+                                      bool *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) ((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 DAUTH_SUPPORT
 bool
 MHD_str_equal_quoted_bin_n (const char *quoted,
diff --git a/src/microhttpd/mhd_str.h b/src/microhttpd/mhd_str.h
index 9e3a0334..dd74e602 100644
--- a/src/microhttpd/mhd_str.h
+++ b/src/microhttpd/mhd_str.h
@@ -143,6 +143,24 @@ MHD_str_equal_caseless_bin_n_ (const char *const str1,
                                size_t len);
 
 
+/**
+ * Check whether string is equal statically allocated another string,
+ * ignoring case of US-ASCII letters and checking not more than @a len bytes.
+ *
+ * If strings have different sizes (lengths) then macro returns boolean false
+ * without checking the content.
+ *
+ * Compares not more first than @a len bytes, including binary zero characters.
+ * Comparison stops at first unmatched byte.
+ * @param a the statically allocated string to compare
+ * @param s the string to compare
+ * @param len number of characters to compare
+ * @return non-zero if @a len bytes are equal, zero otherwise.
+ */
+#define MHD_str_equal_caseless_s_bin_n_(a,s,l) \
+  ((MHD_STATICSTR_LEN_(a) == (l)) \
+   && MHD_str_equal_caseless_bin_n_(a,s,l))
+
 /**
  * Check whether @a str has case-insensitive @a token.
  * Token could be surrounded by spaces and tabs and delimited by comma.
@@ -484,7 +502,7 @@ MHD_uint8_to_str_pad (uint8_t val,
  * hexadecimal digits, zero-terminate the result.
  * @param bin the pointer to the binary data to convert
  * @param size the size in bytes of the binary data to convert
- * @param hex the output buffer, should be at least 2 * @a size + 1
+ * @param[out] hex the output buffer, should be at least 2 * @a size + 1
  * @return The number of characters written to the output buffer,
  *         not including terminating zero.
  */
@@ -493,6 +511,117 @@ MHD_bin_to_hex (const void *bin,
                 size_t size,
                 char *hex);
 
+/**
+ * Convert hexadecimal digits to binary data.
+ *
+ * The input decoded byte-by-byte (each byte is two hexadecimal digits).
+ * If length is an odd number, extra leading zero is assumed.
+ *
+ * @param hex the input string with hexadecimal digits
+ * @param len the length of the input string
+ * @param[out] bin the output buffer, must be at least len/2 bytes long (or
+ *                 len/2 + 1 if @a len is not even number)
+ * @return the number of bytes written to the output buffer,
+ *         zero if found any character which is not hexadecimal digits
+ */
+size_t
+MHD_hex_to_bin (const char *hex,
+                size_t len,
+                void *bin);
+
+/**
+ * Decode string with percent-encoded characters as defined by
+ * RFC 3986 #section-2.1.
+ *
+ * This function decode string by converting percent-encoded characters to
+ * their decoded versions and copying all other characters without extra
+ * processing.
+ *
+ * @param pct_encoded the input string to be decoded
+ * @param pct_encoded_len the length of the @a pct_encoded
+ * @param[out] decoded the output buffer, NOT zero-terminated, can point
+ *                     to the same buffer as @a pct_encoded
+ * @param buf_size the size of the output buffer
+ * @return the number of characters written to the output buffer or
+ *         zero if any percent-encoded characters is broken ('%' followed
+ *         by less than two hexadecimal digits) or output buffer is too
+ *         small to hold the result
+ */
+size_t
+MHD_str_pct_decode_strict_n_ (const char *pct_encoded,
+                              size_t pct_encoded_len,
+                              char *decoded,
+                              size_t buf_size);
+
+/**
+ * Decode string with percent-encoded characters as defined by
+ * RFC 3986 #section-2.1.
+ *
+ * This function decode string by converting percent-encoded characters to
+ * their decoded versions and copying all other characters without extra
+ * processing.
+ *
+ * Any invalid percent-encoding sequences ('%' symbol not followed by two
+ * valid hexadecimal digits) are copied to the output string without decoding.
+ *
+ * @param pct_encoded the input string to be decoded
+ * @param pct_encoded_len the length of the @a pct_encoded
+ * @param[out] decoded the output buffer, NOT zero-terminated, can point
+ *                     to the same buffer as @a pct_encoded
+ * @param buf_size the size of the output buffer
+ * @param[out] broken_encoding will be set to true if any '%' symbol is not
+ *                             followed by two valid hexadecimal digits,
+ *                             optional, can be NULL
+ * @return the number of characters written to the output buffer or
+ *         zero if output buffer is too small to hold the result
+ */
+size_t
+MHD_str_pct_decode_lenient_n_ (const char *pct_encoded,
+                               size_t pct_encoded_len,
+                               char *decoded,
+                               size_t buf_size,
+                               bool *broken_encoding);
+
+
+/**
+ * Decode string in-place with percent-encoded characters as defined by
+ * RFC 3986 #section-2.1.
+ *
+ * This function decode string by converting percent-encoded characters to
+ * their decoded versions and copying back all other characters without extra
+ * processing.
+ *
+ * @param[in,out] str the string to be updated in-place, must be 
zero-terminated
+ *                    on input, the output is zero-terminated; the string is
+ *                    truncated to zero length if broken encoding is found
+ * @return the number of character in decoded string
+ */
+size_t
+MHD_str_pct_decode_in_place_strict_ (char *str);
+
+
+/**
+ * Decode string in-place with percent-encoded characters as defined by
+ * RFC 3986 #section-2.1.
+ *
+ * This function decode string by converting percent-encoded characters to
+ * their decoded versions and copying back all other characters without extra
+ * processing.
+ *
+ * Any invalid percent-encoding sequences ('%' symbol not followed by two
+ * valid hexadecimal digits) are copied to the output string without decoding.
+ *
+ * @param[in,out] str the string to be updated in-place, must be 
zero-terminated
+ *                    on input, the output is zero-terminated
+ * @param[out] broken_encoding will be set to true if any '%' symbol is not
+ *                             followed by two valid hexadecimal digits,
+ *                             optional, can be NULL
+ * @return the number of character in decoded string
+ */
+size_t
+MHD_str_pct_decode_in_place_lenient_ (char *str,
+                                      bool *broken_encoding);
+
 #ifdef DAUTH_SUPPORT
 /**
  * Check two strings for equality, "unquoting" the first string from quoted
@@ -519,6 +648,25 @@ MHD_str_equal_quoted_bin_n (const char *quoted,
                             const char *unquoted,
                             size_t unquoted_len);
 
+/**
+ * Check whether the string after "unquoting" equals static string.
+ *
+ * Null-termination for input string is not required, binary zeros compared
+ * like other characters.
+ *
+ * @param q the quoted string to compare, must NOT include leading and
+ *          closing DQUOTE chars, does not need to be zero-terminated
+ * @param l the length in chars of the @a q string
+ * @param u the unquoted static string to compare
+ * @return zero if quoted form is broken (no character after the last escaping
+ *         backslash), zero if strings are not equal after unquoting of the
+ *         first string,
+ *         non-zero if two strings are equal after unquoting of the
+ *         first string.
+ */
+#define MHD_str_equal_quoted_s_bin_n(q,l,u) \
+    MHD_str_equal_quoted_bin_n(q,l,u,MHD_STATICSTR_LEN_(u))
+
 /**
  * Check two strings for equality, "unquoting" the first string from quoted
  * form as specified by RFC7230#section-3.2.6 and RFC7694#quoted.strings and
@@ -545,6 +693,26 @@ MHD_str_equal_caseless_quoted_bin_n (const char *quoted,
                                      const char *unquoted,
                                      size_t unquoted_len);
 
+/**
+ * Check whether the string after "unquoting" equals static string, ignoring
+ * case of US-ASCII letters.
+ *
+ * Null-termination for input string is not required, binary zeros compared
+ * like other characters.
+ *
+ * @param q the quoted string to compare, must NOT include leading and
+ *          closing DQUOTE chars, does not need to be zero-terminated
+ * @param l the length in chars of the @a q string
+ * @param u the unquoted static string to compare
+ * @return zero if quoted form is broken (no character after the last escaping
+ *         backslash), zero if strings are not equal after unquoting of the
+ *         first string,
+ *         non-zero if two strings are caseless equal after unquoting of the
+ *         first string.
+ */
+#define MHD_str_equal_caseless_quoted_s_bin_n(q,l,u) \
+    MHD_str_equal_caseless_quoted_bin_n(q,l,u,MHD_STATICSTR_LEN_(u))
+
 /**
  * Convert string from quoted to unquoted form as specified by
  * RFC7230#section-3.2.6 and RFC7694#quoted.strings.
diff --git a/src/microhttpd/test_str_bin_hex.c 
b/src/microhttpd/test_str_bin_hex.c
new file mode 100644
index 00000000..2b45040a
--- /dev/null
+++ b/src/microhttpd/test_str_bin_hex.c
@@ -0,0 +1,448 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2022 Karlson2k (Evgeny Grin)
+
+  This test tool is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2, or
+  (at your option) any later version.
+
+  This test tool 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/test_str_bin_hex.c
+ * @brief  Unit tests for hex strings <-> binary data processing
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_options.h"
+#include <string.h>
+#include <stdio.h>
+#include "mhd_str.h"
+#include "mhd_assert.h"
+
+#ifndef MHD_STATICSTR_LEN_
+/**
+ * Determine length of static string / macro strings at compile time.
+ */
+#define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1)
+#endif /* ! MHD_STATICSTR_LEN_ */
+
+
+static char tmp_bufs[4][4 * 1024]; /* should be enough for testing */
+static size_t buf_idx = 0;
+
+/* print non-printable chars as char codes */
+static char *
+n_prnt (const char *str, size_t len)
+{
+  static char *buf;  /* should be enough for testing */
+  static const size_t buf_size = sizeof(tmp_bufs[0]);
+  size_t r_pos = 0;
+  size_t w_pos = 0;
+  if (++buf_idx >= (sizeof(tmp_bufs) / sizeof(tmp_bufs[0])))
+    buf_idx = 0;
+  buf = tmp_bufs[buf_idx];
+
+  while (len > r_pos && w_pos + 1 < buf_size)
+  {
+    const unsigned char c = (unsigned char) str[r_pos];
+    if ((c == '\\') || (c == '"') )
+    {
+      if (w_pos + 2 >= buf_size)
+        break;
+      buf[w_pos++] = '\\';
+      buf[w_pos++] = (char) c;
+    }
+    else if ((c >= 0x20) && (c <= 0x7E) )
+      buf[w_pos++] = (char) c;
+    else
+    {
+      if (w_pos + 4 >= buf_size)
+        break;
+      if (snprintf (buf + w_pos, buf_size - w_pos, "\\x%02hX", (short unsigned
+                                                                int) c) != 4)
+        break;
+      w_pos += 4;
+    }
+    r_pos++;
+  }
+
+  if (len != r_pos)
+  {   /* not full string is printed */
+      /* enough space for "..." ? */
+    if (w_pos + 3 > buf_size)
+      w_pos = buf_size - 4;
+    buf[w_pos++] = '.';
+    buf[w_pos++] = '.';
+    buf[w_pos++] = '.';
+  }
+  buf[w_pos] = 0;
+  return buf;
+}
+
+
+#define TEST_BIN_MAX_SIZE (2 * 1024)
+
+/* return zero if succeed, number of failures otherwise */
+static unsigned int
+expect_decoded_n (const char *const hex, const size_t hex_len,
+                  const uint8_t *const bin, const size_t bin_size,
+                  const unsigned int line_num)
+{
+  static const char fill_chr = '#';
+  static char buf[TEST_BIN_MAX_SIZE];
+  size_t res_size;
+  unsigned int ret;
+
+  mhd_assert (NULL != hex);
+  mhd_assert (NULL != bin);
+  mhd_assert (TEST_BIN_MAX_SIZE > bin_size + 1);
+  mhd_assert (TEST_BIN_MAX_SIZE > hex_len + 1);
+  mhd_assert (hex_len >= bin_size);
+  mhd_assert (1 >= hex_len || hex_len > bin_size);
+
+  ret = 0;
+
+  /* check MHD_hex_to_bin() */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_hex_to_bin (hex, hex_len, buf);
+    if (res_size != bin_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_hex_to_bin ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if ((0 != bin_size) &&
+          (0 != memcmp (buf, bin, bin_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_hex_to_bin ()' FAILED: "
+                 "Wrong output data:\n");
+      }
+    }
+    if (fill_chr != buf[res_size])
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+               "A char written outside the buffer:\n");
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_hex_to_bin (\"%s\", %u, "
+               "->\"%s\") -> %u\n",
+               n_prnt (hex, hex_len), (unsigned) hex_len,
+               n_prnt (buf, res_size),
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_hex_to_bin (\"%s\", %u, "
+               "->\"%s\") -> %u\n",
+               n_prnt (hex, hex_len), (unsigned) hex_len,
+               n_prnt ((const char *) bin, bin_size),
+               (unsigned) bin_size);
+    }
+  }
+
+  /* check MHD_bin_to_hex() */
+  if (0 == hex_len % 2)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_bin_to_hex (bin, bin_size, buf);
+
+    if (res_size != hex_len)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_bin_to_hex ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if ((0 != hex_len) &&
+          (! MHD_str_equal_caseless_bin_n_ (buf, hex, hex_len)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_bin_to_hex ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (fill_chr != buf[res_size + 1])
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_bin_to_hex ()' FAILED: "
+               "A char written outside the buffer:\n");
+    }
+    if (0 != buf[res_size])
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_bin_to_hex ()' FAILED: "
+               "The result is not zero-terminated:\n");
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_bin_to_hex (\"%s\", %u, "
+               "->\"%s\") -> %u\n",
+               n_prnt ((const char *) bin, bin_size), (unsigned) bin_size,
+               n_prnt (buf, res_size),
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_bin_to_hex (\"%s\", %u, "
+               "->(lower case)\"%s\") -> %u\n",
+               n_prnt ((const char *) bin, bin_size), (unsigned) bin_size,
+               n_prnt (hex, hex_len),
+               (unsigned) bin_size);
+    }
+  }
+
+  if (0 != ret)
+  {
+    fprintf (stderr,
+             "The check is at line: %u\n\n", line_num);
+  }
+  return ret;
+}
+
+
+#define expect_decoded_arr(h,a) \
+        expect_decoded_n(h,MHD_STATICSTR_LEN_(h),\
+                         a,(sizeof(a)/sizeof(a[0])), \
+                         __LINE__)
+
+static unsigned int
+check_decode_bin (void)
+{
+  unsigned int r = 0; /**< The number of errors */
+
+  if (1)
+  {
+    static const uint8_t bin[256] =
+    {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe,
+     0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+     0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+     0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
+     0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+     0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+     0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56,
+     0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+     0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+     0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+     0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+     0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92,
+     0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
+     0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+     0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+     0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2,
+     0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+     0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+     0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+     0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+     0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+     0xff };
+    /* The lower case */
+    r += expect_decoded_arr ("000102030405060708090a0b0c0d0e" \
+                             "0f101112131415161718191a1b1c1d" \
+                             "1e1f202122232425262728292a2b2c" \
+                             "2d2e2f303132333435363738393a3b" \
+                             "3c3d3e3f404142434445464748494a" \
+                             "4b4c4d4e4f50515253545556575859" \
+                             "5a5b5c5d5e5f606162636465666768" \
+                             "696a6b6c6d6e6f7071727374757677" \
+                             "78797a7b7c7d7e7f80818283848586" \
+                             "8788898a8b8c8d8e8f909192939495" \
+                             "969798999a9b9c9d9e9fa0a1a2a3a4" \
+                             "a5a6a7a8a9aaabacadaeafb0b1b2b3" \
+                             "b4b5b6b7b8b9babbbcbdbebfc0c1c2" \
+                             "c3c4c5c6c7c8c9cacbcccdcecfd0d1" \
+                             "d2d3d4d5d6d7d8d9dadbdcdddedfe0" \
+                             "e1e2e3e4e5e6e7e8e9eaebecedeeef" \
+                             "f0f1f2f3f4f5f6f7f8f9fafbfcfdfe" \
+                             "ff", bin);
+  }
+
+  if (1)
+  {
+    static const uint8_t bin[256] =
+    {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe,
+     0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+     0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+     0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
+     0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+     0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+     0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56,
+     0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+     0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+     0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+     0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+     0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92,
+     0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
+     0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+     0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+     0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2,
+     0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+     0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+     0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+     0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+     0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+     0xff };
+    /* The upper case */
+    r += expect_decoded_arr ("000102030405060708090A0B0C0D0E" \
+                             "0F101112131415161718191A1B1C1D" \
+                             "1E1F202122232425262728292A2B2C" \
+                             "2D2E2F303132333435363738393A3B" \
+                             "3C3D3E3F404142434445464748494A" \
+                             "4B4C4D4E4F50515253545556575859" \
+                             "5A5B5C5D5E5F606162636465666768" \
+                             "696A6B6C6D6E6F7071727374757677" \
+                             "78797A7B7C7D7E7F80818283848586" \
+                             "8788898A8B8C8D8E8F909192939495" \
+                             "969798999A9B9C9D9E9FA0A1A2A3A4" \
+                             "A5A6A7A8A9AAABACADAEAFB0B1B2B3" \
+                             "B4B5B6B7B8B9BABBBCBDBEBFC0C1C2" \
+                             "C3C4C5C6C7C8C9CACBCCCDCECFD0D1" \
+                             "D2D3D4D5D6D7D8D9DADBDCDDDEDFE0" \
+                             "E1E2E3E4E5E6E7E8E9EAEBECEDEEEF" \
+                             "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFE" \
+                             "FF", bin);
+  }
+  if (1)
+  {
+    static const uint8_t bin[3] =
+    {0x1, 0x2, 0x3};
+    r += expect_decoded_arr ("010203", bin);
+  }
+  if (1)
+  {
+    static const uint8_t bin[3] =
+    {0x1, 0x2, 0x3};
+    r += expect_decoded_arr ("10203", bin);
+  }
+  if (1)
+  {
+    static const uint8_t bin[1] =
+    {0x1};
+    r += expect_decoded_arr ("01", bin);
+  }
+  if (1)
+  {
+    static const uint8_t bin[1] =
+    {0x1};
+    r += expect_decoded_arr ("1", bin);
+  }
+
+  return r;
+}
+
+
+/* return zero if succeed, number of failures otherwise */
+static unsigned int
+expect_failed_n (const char *const hex, const size_t hex_len,
+                 const unsigned int line_num)
+{
+  static const char fill_chr = '#';
+  static char buf[TEST_BIN_MAX_SIZE];
+  size_t res_size;
+  unsigned int ret;
+
+  mhd_assert (NULL != hex);
+  mhd_assert (TEST_BIN_MAX_SIZE > hex_len + 1);
+
+  ret = 0;
+
+  /* check MHD_hex_to_bin() */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_hex_to_bin (hex, hex_len, buf);
+    if (res_size != 0)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_hex_to_bin ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_hex_to_bin (\"%s\", %u, "
+               "->\"%s\") -> %u\n",
+               n_prnt (hex, hex_len), (unsigned) hex_len,
+               n_prnt (buf, res_size),
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_hex_to_bin (\"%s\", %u, "
+               "->(not defined)) -> 0\n",
+               n_prnt (hex, hex_len), (unsigned) hex_len);
+    }
+  }
+
+  if (0 != ret)
+  {
+    fprintf (stderr,
+             "The check is at line: %u\n\n", line_num);
+  }
+  return ret;
+}
+
+
+#define expect_failed(h) \
+        expect_failed_n(h,MHD_STATICSTR_LEN_(h), \
+                         __LINE__)
+
+
+static unsigned int
+check_broken_str (void)
+{
+  unsigned int r = 0; /**< The number of errors */
+
+  r += expect_failed ("abcx");
+  r += expect_failed ("X");
+  r += expect_failed ("!");
+  r += expect_failed ("01z");
+  r += expect_failed ("0z");
+  r += expect_failed ("00z");
+  r += expect_failed ("000Y");
+
+  return r;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  unsigned int errcount = 0;
+  (void) argc; (void) argv; /* Unused. Silent compiler warning. */
+  errcount += check_decode_bin ();
+  errcount += check_broken_str ();
+  if (0 == errcount)
+    printf ("All tests have been passed without errors.\n");
+  return errcount == 0 ? 0 : 1;
+}
diff --git a/src/microhttpd/test_str_pct.c b/src/microhttpd/test_str_pct.c
new file mode 100644
index 00000000..8c3375ce
--- /dev/null
+++ b/src/microhttpd/test_str_pct.c
@@ -0,0 +1,1060 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2022 Karlson2k (Evgeny Grin)
+
+  This test tool is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2, or
+  (at your option) any later version.
+
+  This test tool 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/test_str_pct.c
+ * @brief  Unit tests for percent (URL) encoded strings processing
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_options.h"
+#include <string.h>
+#include <stdio.h>
+#include "mhd_str.h"
+#include "mhd_assert.h"
+
+#ifndef MHD_STATICSTR_LEN_
+/**
+ * Determine length of static string / macro strings at compile time.
+ */
+#define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1)
+#endif /* ! MHD_STATICSTR_LEN_ */
+
+
+static char tmp_bufs[4][4 * 1024]; /* should be enough for testing */
+static size_t buf_idx = 0;
+
+/* print non-printable chars as char codes */
+static char *
+n_prnt (const char *str, size_t len)
+{
+  static char *buf;  /* should be enough for testing */
+  static const size_t buf_size = sizeof(tmp_bufs[0]);
+  size_t r_pos = 0;
+  size_t w_pos = 0;
+  if (++buf_idx >= (sizeof(tmp_bufs) / sizeof(tmp_bufs[0])))
+    buf_idx = 0;
+  buf = tmp_bufs[buf_idx];
+
+  while (len > r_pos && w_pos + 1 < buf_size)
+  {
+    const unsigned char c = (unsigned char) str[r_pos];
+    if ((c == '\\') || (c == '"') )
+    {
+      if (w_pos + 2 >= buf_size)
+        break;
+      buf[w_pos++] = '\\';
+      buf[w_pos++] = (char) c;
+    }
+    else if ((c >= 0x20) && (c <= 0x7E) )
+      buf[w_pos++] = (char) c;
+    else
+    {
+      if (w_pos + 4 >= buf_size)
+        break;
+      if (snprintf (buf + w_pos, buf_size - w_pos, "\\x%02hX", (short unsigned
+                                                                int) c) != 4)
+        break;
+      w_pos += 4;
+    }
+    r_pos++;
+  }
+
+  if (len != r_pos)
+  {   /* not full string is printed */
+      /* enough space for "..." ? */
+    if (w_pos + 3 > buf_size)
+      w_pos = buf_size - 4;
+    buf[w_pos++] = '.';
+    buf[w_pos++] = '.';
+    buf[w_pos++] = '.';
+  }
+  buf[w_pos] = 0;
+  return buf;
+}
+
+
+#define TEST_BIN_MAX_SIZE 1024
+
+/* return zero if succeed, number of failures otherwise */
+static unsigned int
+expect_decoded_n (const char *const encoded, const size_t encoded_len,
+                  const char *const decoded, const size_t decoded_size,
+                  const unsigned int line_num)
+{
+  static const char fill_chr = '#';
+  static char buf[TEST_BIN_MAX_SIZE];
+  size_t res_size;
+  unsigned int ret;
+
+  mhd_assert (NULL != encoded);
+  mhd_assert (NULL != decoded);
+  mhd_assert (TEST_BIN_MAX_SIZE > decoded_size + 1);
+  mhd_assert (TEST_BIN_MAX_SIZE > encoded_len + 1);
+  mhd_assert (encoded_len >= decoded_size);
+
+  ret = 0;
+
+  /* check MHD_str_pct_decode_strict_n_() with small out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_strict_n_ (encoded, encoded_len, buf,
+                                             decoded_size + 1);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      else
+      {
+        memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some 
character */
+        res_size = MHD_str_pct_decode_strict_n_ (encoded, encoded_len, buf,
+                                                 decoded_size);
+        if (res_size != decoded_size)
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+                   "Wrong returned value:\n");
+        }
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) decoded_size,
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) decoded_size,
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_strict_n_() with large out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_strict_n_ (encoded, encoded_len, buf,
+                                             encoded_len + 1);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) (encoded_len + 1),
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) (encoded_len + 1),
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_lenient_n_() with small out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+    bool is_broken = true;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                              decoded_size + 1, &is_broken);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      else
+      {
+        is_broken = true;
+        memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some 
character */
+        res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                                  decoded_size, &is_broken);
+        if (res_size != decoded_size)
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                   "Wrong returned value:\n");
+        }
+      }
+      if (is_broken)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong 'broken_encoding' result:\n");
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->%s) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) decoded_size,
+               is_broken ? "true" : "false",
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->false) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) decoded_size,
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_lenient_n_() with large out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+    bool is_broken = true;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                              encoded_len + 1, &is_broken);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      if (is_broken)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong 'broken_encoding' result:\n");
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->%s) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) (encoded_len + 1),
+               is_broken ? "true" : "false",
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->false) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) (encoded_len + 1),
+               (unsigned) decoded_size);
+    }
+  }
+
+  if (strlen (encoded) == encoded_len)
+  {
+    /* check MHD_str_pct_decode_in_place_strict_() */
+    if (1)
+    {
+      unsigned int check_res = 0;
+
+      memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character 
*/
+      memcpy (buf, encoded, encoded_len);
+      buf[encoded_len] = 0;
+      res_size = MHD_str_pct_decode_in_place_strict_ (buf);
+      if (res_size != decoded_size)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_in_place_strict_ ()' FAILED: "
+                 "Wrong returned value:\n");
+      }
+      else
+      {
+        if (0 != buf[res_size])
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_strict_ ()' FAILED: "
+                   "The result is not zero-terminated:\n");
+        }
+        if (((res_size + 1) < encoded_len) ?
+            (encoded[res_size + 1] != buf[res_size + 1]) :
+            (fill_chr != buf[res_size + 1]))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_strict_ ()' FAILED: "
+                   "A char written outside the buffer:\n");
+        }
+        if ((res_size == decoded_size) && (0 != decoded_size) &&
+            (0 != memcmp (buf, decoded, decoded_size)))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_strict_ ()' FAILED: "
+                   "Wrong output string:\n");
+        }
+      }
+      if (0 != check_res)
+      {
+        ret++;
+        fprintf (stderr,
+                 "\tRESULT  : MHD_str_pct_decode_in_place_strict_ (\"%s\" "
+                 "-> \"%s\") -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (buf, res_size),
+                 (unsigned) res_size);
+        fprintf (stderr,
+                 "\tEXPECTED: MHD_str_pct_decode_in_place_strict_ (\"%s\" "
+                 "-> \"%s\") -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (decoded, decoded_size),
+                 (unsigned) decoded_size);
+      }
+    }
+
+    /* check MHD_str_pct_decode_in_place_lenient_() */
+    if (1)
+    {
+      unsigned int check_res = 0;
+      bool is_broken = true;
+
+      memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character 
*/
+      memcpy (buf, encoded, encoded_len);
+      buf[encoded_len] = 0;
+      res_size = MHD_str_pct_decode_in_place_lenient_ (buf, &is_broken);
+      if (res_size != decoded_size)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                 "Wrong returned value:\n");
+      }
+      else
+      {
+        if (0 != buf[res_size])
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "The result is not zero-terminated:\n");
+        }
+        if (((res_size + 1) < encoded_len) ?
+            (encoded[res_size + 1] != buf[res_size + 1]) :
+            (fill_chr != buf[res_size + 1]))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "A char written outside the buffer:\n");
+        }
+        if (is_broken)
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "Wrong 'broken_encoding' result:\n");
+        }
+        if ((res_size == decoded_size) && (0 != decoded_size) &&
+            (0 != memcmp (buf, decoded, decoded_size)))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "Wrong output string:\n");
+        }
+      }
+      if (0 != check_res)
+      {
+        ret++;
+        fprintf (stderr,
+                 "\tRESULT  : MHD_str_pct_decode_in_place_lenient_ (\"%s\" "
+                 "-> \"%s\", ->%s) -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (buf, res_size),
+                 is_broken ? "true" : "false",
+                 (unsigned) res_size);
+        fprintf (stderr,
+                 "\tEXPECTED: MHD_str_pct_decode_in_place_lenient_ (\"%s\" "
+                 "-> \"%s\", ->false) -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (decoded, decoded_size),
+                 (unsigned) decoded_size);
+      }
+    }
+  }
+
+  if (0 != ret)
+  {
+    fprintf (stderr,
+             "The check is at line: %u\n\n", line_num);
+  }
+  return ret;
+}
+
+
+#define expect_decoded(e,d) \
+        expect_decoded_n(e,MHD_STATICSTR_LEN_(e),\
+                         d,MHD_STATICSTR_LEN_(d), \
+                         __LINE__)
+
+static unsigned int
+check_decode_str (void)
+{
+  unsigned int r = 0; /**< The number of errors */
+
+  r += expect_decoded ("", "");
+
+  /* Base sequences without percent symbol */
+  r += expect_decoded ("aaa", "aaa");
+  r += expect_decoded ("bbb", "bbb");
+  r += expect_decoded ("ccc", "ccc");
+  r += expect_decoded ("ddd", "ddd");
+  r += expect_decoded ("lll", "lll");
+  r += expect_decoded ("mmm", "mmm");
+  r += expect_decoded ("nnn", "nnn");
+  r += expect_decoded ("ooo", "ooo");
+  r += expect_decoded ("www", "www");
+  r += expect_decoded ("xxx", "xxx");
+  r += expect_decoded ("yyy", "yyy");
+  r += expect_decoded ("zzz", "zzz");
+  r += expect_decoded ("AAA", "AAA");
+  r += expect_decoded ("GGG", "GGG");
+  r += expect_decoded ("MMM", "MMM");
+  r += expect_decoded ("TTT", "TTT");
+  r += expect_decoded ("ZZZ", "ZZZ");
+  r += expect_decoded ("012", "012");
+  r += expect_decoded ("345", "345");
+  r += expect_decoded ("678", "678");
+  r += expect_decoded ("901", "901");
+  r += expect_decoded ("aaaaaa", "aaaaaa");
+  r += expect_decoded ("bbbbbb", "bbbbbb");
+  r += expect_decoded ("cccccc", "cccccc");
+  r += expect_decoded ("dddddd", "dddddd");
+  r += expect_decoded ("llllll", "llllll");
+  r += expect_decoded ("mmmmmm", "mmmmmm");
+  r += expect_decoded ("nnnnnn", "nnnnnn");
+  r += expect_decoded ("oooooo", "oooooo");
+  r += expect_decoded ("wwwwww", "wwwwww");
+  r += expect_decoded ("xxxxxx", "xxxxxx");
+  r += expect_decoded ("yyyyyy", "yyyyyy");
+  r += expect_decoded ("zzzzzz", "zzzzzz");
+  r += expect_decoded ("AAAAAA", "AAAAAA");
+  r += expect_decoded ("GGGGGG", "GGGGGG");
+  r += expect_decoded ("MMMMMM", "MMMMMM");
+  r += expect_decoded ("TTTTTT", "TTTTTT");
+  r += expect_decoded ("ZZZZZZ", "ZZZZZZ");
+  r += expect_decoded ("012012", "012012");
+  r += expect_decoded ("345345", "345345");
+  r += expect_decoded ("678678", "678678");
+  r += expect_decoded ("901901", "901901");
+  r += expect_decoded ("a", "a");
+  r += expect_decoded ("bc", "bc");
+  r += expect_decoded ("DEFG", "DEFG");
+  r += expect_decoded ("123t", "123t");
+  r += expect_decoded ("12345", "12345");
+  r += expect_decoded ("TestStr", "TestStr");
+  r += expect_decoded ("Teststring", "Teststring");
+  r += expect_decoded ("Teststring.", "Teststring.");
+  r += expect_decoded ("Longerstring", "Longerstring");
+  r += expect_decoded ("Longerstring.", "Longerstring.");
+  r += expect_decoded ("Longerstring2.", "Longerstring2.");
+
+  /* Simple percent-encoded strings */
+  r += expect_decoded ("Test%20string", "Test string");
+  r += expect_decoded ("Test%3Fstring.", "Test?string.");
+  r += expect_decoded ("100%25", "100%");
+  r += expect_decoded ("a%2C%20b%3Dc%26e%3Dg", "a, b=c&e=g");
+  r += expect_decoded ("%20%21%23%24%25%26%27%28%29%2A%2B%2C"
+                       "%2F%3A%3B%3D%3F%40%5B%5D%09",
+                       " !#$%&'()*+,/:;=?@[]\t");
+
+  return r;
+}
+
+
+#define expect_decoded_arr(e,a) \
+        expect_decoded_n(e,MHD_STATICSTR_LEN_(e),\
+                         (const char *)a,(sizeof(a)/sizeof(a[0])), \
+                         __LINE__)
+
+static unsigned int
+check_decode_bin (void)
+{
+  unsigned int r = 0; /**< The number of errors */
+
+  if (1)
+  {
+    static const uint8_t bin[256] =
+    {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe,
+     0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+     0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+     0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
+     0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+     0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+     0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56,
+     0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+     0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+     0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+     0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+     0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92,
+     0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
+     0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+     0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+     0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2,
+     0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+     0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+     0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+     0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+     0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+     0xff };
+    /* The lower case */
+    r += expect_decoded_arr ("%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e" \
+                             "%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d" \
+                             "%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c" \
+                             "%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b" \
+                             "%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a" \
+                             "%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59" \
+                             "%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68" \
+                             "%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77" \
+                             "%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86" \
+                             "%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95" \
+                             "%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4" \
+                             "%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3" \
+                             "%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2" \
+                             "%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1" \
+                             "%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0" \
+                             "%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef" \
+                             "%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe" \
+                             "%ff", bin);
+  }
+
+  if (1)
+  {
+    static const uint8_t bin[256] =
+    {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe,
+     0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+     0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+     0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
+     0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+     0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+     0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56,
+     0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+     0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+     0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+     0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+     0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92,
+     0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
+     0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+     0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+     0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2,
+     0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+     0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+     0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+     0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+     0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+     0xff };
+    /* The upper case */
+    r += expect_decoded_arr ("%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E" \
+                             "%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D" \
+                             "%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C" \
+                             "%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B" \
+                             "%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A" \
+                             "%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59" \
+                             "%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68" \
+                             "%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77" \
+                             "%78%79%7A%7B%7C%7D%7E%7F%80%81%82%83%84%85%86" \
+                             "%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95" \
+                             "%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4" \
+                             "%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3" \
+                             "%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2" \
+                             "%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1" \
+                             "%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0" \
+                             "%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" \
+                             "%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE" \
+                             "%FF", bin);
+  }
+
+  return r;
+}
+
+
+/* return zero if succeed, number of failures otherwise */
+static unsigned int
+expect_decoded_bad_n (const char *const encoded, const size_t encoded_len,
+                      const char *const decoded, const size_t decoded_size,
+                      const unsigned int line_num)
+{
+  static const char fill_chr = '#';
+  static char buf[TEST_BIN_MAX_SIZE];
+  size_t res_size;
+  unsigned int ret;
+
+  mhd_assert (NULL != encoded);
+  mhd_assert (NULL != decoded);
+  mhd_assert (TEST_BIN_MAX_SIZE > decoded_size + 1);
+  mhd_assert (TEST_BIN_MAX_SIZE > encoded_len + 1);
+  mhd_assert (encoded_len >= decoded_size);
+
+  ret = 0;
+
+  /* check MHD_str_pct_decode_strict_n_() with small out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_strict_n_ (encoded, encoded_len, buf,
+                                             decoded_size);
+    if (res_size != 0)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) decoded_size,
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->(not defined), %u) -> 0\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_strict_n_() with large out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_strict_n_ (encoded, encoded_len, buf,
+                                             encoded_len + 1);
+    if (res_size != 0)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_strict_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->\"%s\", %u) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) (encoded_len + 1),
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_strict_n_ (\"%s\", %u, "
+               "->(not defined), %u) -> 0\n",
+               n_prnt (encoded, encoded_len), (unsigned) (encoded_len + 1),
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_lenient_n_() with small out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+    bool is_broken = false;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                              decoded_size + 1, &is_broken);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      else
+      {
+        is_broken = false;
+        memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some 
character */
+        res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                                  decoded_size, &is_broken);
+        if (res_size != decoded_size)
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                   "Wrong returned value:\n");
+        }
+      }
+      if (! is_broken)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong 'broken_encoding' result:\n");
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->%s) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) decoded_size,
+               is_broken ? "true" : "false",
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->true) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) decoded_size,
+               (unsigned) decoded_size);
+    }
+  }
+
+  /* check MHD_str_pct_decode_lenient_n_() with large out buffer */
+  if (1)
+  {
+    unsigned int check_res = 0;
+    bool is_broken = false;
+
+    memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character */
+    res_size = MHD_str_pct_decode_lenient_n_ (encoded, encoded_len, buf,
+                                              encoded_len + 1, &is_broken);
+    if (res_size != decoded_size)
+    {
+      check_res = 1;
+      fprintf (stderr,
+               "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+               "Wrong returned value:\n");
+    }
+    else
+    {
+      if (fill_chr != buf[res_size])
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "A char written outside the buffer:\n");
+      }
+      if (! is_broken)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong 'broken_encoding' result:\n");
+      }
+      if ((res_size == decoded_size) && (0 != decoded_size) &&
+          (0 != memcmp (buf, decoded, decoded_size)))
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_lenient_n_ ()' FAILED: "
+                 "Wrong output string:\n");
+      }
+    }
+    if (0 != check_res)
+    {
+      ret++;
+      fprintf (stderr,
+               "\tRESULT  : MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->%s) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (buf, res_size), (unsigned) (encoded_len + 1),
+               is_broken ? "true" : "false",
+               (unsigned) res_size);
+      fprintf (stderr,
+               "\tEXPECTED: MHD_str_pct_decode_lenient_n_ (\"%s\", %u, "
+               "->\"%s\", %u, ->true) -> %u\n",
+               n_prnt (encoded, encoded_len), (unsigned) encoded_len,
+               n_prnt (decoded, decoded_size), (unsigned) (encoded_len + 1),
+               (unsigned) decoded_size);
+    }
+  }
+
+  if (strlen (encoded) == encoded_len)
+  {
+    /* check MHD_str_pct_decode_in_place_strict_() */
+    if (1)
+    {
+      unsigned int check_res = 0;
+
+      memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character 
*/
+      memcpy (buf, encoded, encoded_len);
+      buf[encoded_len] = 0;
+      res_size = MHD_str_pct_decode_in_place_strict_ (buf);
+      if (res_size != 0)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_in_place_strict_ ()' FAILED: "
+                 "Wrong returned value:\n");
+      }
+      if (0 != check_res)
+      {
+        ret++;
+        fprintf (stderr,
+                 "\tRESULT  : MHD_str_pct_decode_in_place_strict_ (\"%s\" "
+                 "-> \"%s\") -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (buf, res_size),
+                 (unsigned) res_size);
+        fprintf (stderr,
+                 "\tEXPECTED: MHD_str_pct_decode_in_place_strict_ (\"%s\" "
+                 "-> (not defined)) -> 0\n",
+                 n_prnt (encoded, encoded_len));
+      }
+    }
+
+    /* check MHD_str_pct_decode_in_place_lenient_() */
+    if (1)
+    {
+      unsigned int check_res = 0;
+      bool is_broken = false;
+
+      memset (buf, fill_chr, sizeof(buf)); /* Fill buffer with some character 
*/
+      memcpy (buf, encoded, encoded_len);
+      buf[encoded_len] = 0;
+      res_size = MHD_str_pct_decode_in_place_lenient_ (buf, &is_broken);
+      if (res_size != decoded_size)
+      {
+        check_res = 1;
+        fprintf (stderr,
+                 "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                 "Wrong returned value:\n");
+      }
+      else
+      {
+        if (0 != buf[res_size])
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "The result is not zero-terminated:\n");
+        }
+        if (((res_size + 1) < encoded_len) ?
+            (encoded[res_size + 1] != buf[res_size + 1]) :
+            (fill_chr != buf[res_size + 1]))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "A char written outside the buffer:\n");
+        }
+        if (! is_broken)
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "Wrong 'broken_encoding' result:\n");
+        }
+        if ((res_size == decoded_size) && (0 != decoded_size) &&
+            (0 != memcmp (buf, decoded, decoded_size)))
+        {
+          check_res = 1;
+          fprintf (stderr,
+                   "'MHD_str_pct_decode_in_place_lenient_ ()' FAILED: "
+                   "Wrong output string:\n");
+        }
+      }
+      if (0 != check_res)
+      {
+        ret++;
+        fprintf (stderr,
+                 "\tRESULT  : MHD_str_pct_decode_in_place_lenient_ (\"%s\" "
+                 "-> \"%s\", ->%s) -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (buf, res_size),
+                 is_broken ? "true" : "false",
+                 (unsigned) res_size);
+        fprintf (stderr,
+                 "\tEXPECTED: MHD_str_pct_decode_in_place_lenient_ (\"%s\" "
+                 "-> \"%s\", ->true) -> %u\n",
+                 n_prnt (encoded, encoded_len),
+                 n_prnt (decoded, decoded_size),
+                 (unsigned) decoded_size);
+      }
+    }
+  }
+
+  if (0 != ret)
+  {
+    fprintf (stderr,
+             "The check is at line: %u\n\n", line_num);
+  }
+  return ret;
+}
+
+
+#define expect_decoded_bad(e,d) \
+        expect_decoded_bad_n(e,MHD_STATICSTR_LEN_(e),\
+                             d,MHD_STATICSTR_LEN_(d), \
+                             __LINE__)
+
+static unsigned int
+check_decode_bad_str (void)
+{
+  unsigned int r = 0; /**< The number of errors */
+
+  r += expect_decoded_bad ("50%/50%", "50%/50%");
+  r += expect_decoded_bad ("This is 100% incorrect.",
+                           "This is 100% incorrect.");
+  r += expect_decoded_bad ("Some %%", "Some %%");
+  r += expect_decoded_bad ("1 %", "1 %");
+  r += expect_decoded_bad ("%", "%");
+  r += expect_decoded_bad ("%a", "%a");
+  r += expect_decoded_bad ("%0", "%0");
+  r += expect_decoded_bad ("%0x", "%0x");
+  r += expect_decoded_bad ("%FX", "%FX");
+  r += expect_decoded_bad ("Valid%20and%2invalid", "Valid and%2invalid");
+
+  return r;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  unsigned int errcount = 0;
+  (void) argc; (void) argv; /* Unused. Silent compiler warning. */
+  errcount += check_decode_str ();
+  errcount += check_decode_bin ();
+  errcount += check_decode_bad_str ();
+  if (0 == errcount)
+    printf ("All tests have been passed without errors.\n");
+  return errcount == 0 ? 0 : 1;
+}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]