bool_t httpCheckPassword(HttpConnection *connection,
   const char_t *password, HttpAuthMode mode)
{
   //This flag tells whether the password is valid
   bool_t status = FALSE;

   //Debug message
   TRACE_DEBUG("Password verification...\r\n");

#if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
   //Basic authentication scheme?
   if(mode == HTTP_AUTH_MODE_BASIC)
   {
      //Point to the authentication credentials
      HttpAuthorizationHeader *auth = &connection->request.auth;

      //Make sure authentication credentials have been found
      if(auth->found && auth->mode == HTTP_AUTH_MODE_BASIC)
      {
         //Sanity check
         if(auth->password != NULL)
         {
            //Check whether the password is valid
            if(!strcmp(password, auth->password))
               status = TRUE;
         }
      }
   }
#endif
#if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
   //Digest authentication scheme?
   if(mode == HTTP_AUTH_MODE_DIGEST)
   {
      //Point to the authentication credentials
      HttpAuthorizationHeader *auth = &connection->request.auth;

      //Make sure authentication credentials have been found
      if(auth->found && auth->mode == HTTP_AUTH_MODE_DIGEST)
      {
         //Sanity check
         if(auth->realm != NULL && auth->realm != NULL &&
            auth->nonce != NULL && auth->qop != NULL &&
            auth->nc != NULL && auth->cnonce != NULL &&
            auth->response != NULL)
         {
            error_t error;
            Md5Context *md5Context;
            char_t ha1[2 * MD5_DIGEST_SIZE + 1];
            char_t ha2[2 * MD5_DIGEST_SIZE + 1];

            //Allocate a memory buffer to hold the MD5 context
            md5Context = osAllocMem(sizeof(Md5Context));

            //MD5 context successfully allocated?
            if(md5Context != NULL)
            {
               //Compute HA1 = MD5(username : realm : password)
               md5Init(md5Context);
               md5Update(md5Context, auth->user, strlen(auth->user));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->realm, strlen(auth->realm));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, password, strlen(password));
               md5Final(md5Context, NULL);

               //Convert MD5 hash to hex string
               httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha1);
               //Debug message
               TRACE_DEBUG("  HA1: %s\r\n", ha1);

               //Compute HA2 = MD5(method : uri)
               md5Init(md5Context);
               md5Update(md5Context, connection->request.method, strlen(connection->request.method));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->uri, strlen(auth->uri));
               md5Final(md5Context, NULL);

               //Convert MD5 hash to hex string
               httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha2);
               //Debug message
               TRACE_DEBUG("  HA2: %s\r\n", ha2);

               //Compute MD5(HA1 : nonce : nc : cnonce : qop : HA1)
               md5Init(md5Context);
               md5Update(md5Context, ha1, strlen(ha1));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->nonce, strlen(auth->nonce));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->nc, strlen(auth->nc));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->cnonce, strlen(auth->cnonce));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, auth->qop, strlen(auth->qop));
               md5Update(md5Context, ":", 1);
               md5Update(md5Context, ha2, strlen(ha2));
               md5Final(md5Context, NULL);

               //Convert MD5 hash to hex string
               httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha1);
               //Debug message
               TRACE_DEBUG("  response: %s\r\n", ha1);

               //Release MD5 context
               osFreeMem(md5Context);

               //Check response
               if(!strcasecmp(auth->response, ha1))
               {
                  //Perform nonce verification
                  error = httpVerifyNonce(connection->serverContext, auth->nonce, auth->nc);

                  //Valid nonce?
                  if(!error)
                  {
                     //Access to the resource is granted
                     status = TRUE;
                  }
                  else
                  {
                     //The client may wish to simply retry the request with a
                     //new encrypted response, without re-prompting the user
                     //for a new username and password
                     connection->response.auth.stale = TRUE;
                  }
               }
            }
         }
      }
   }
#endif

   //Return TRUE is the password is valid, else FALSE
   return status;
}
error_t httpWriteHeader(HttpConnection *connection)
{
   error_t error;
   uint_t i;
   char_t *p;

   //HTTP version 0.9?
   if(connection->response.version == HTTP_VERSION_0_9)
   {
      //Enforce default parameters
      connection->response.keepAlive = FALSE;
      connection->response.chunkedEncoding = FALSE;
      //The size of the response body is not limited
      connection->response.byteCount = UINT_MAX;
      //We are done since HTTP 0.9 does not support Full-Response format
      return NO_ERROR;
   }

   //When generating dynamic web pages with HTTP 1.0, the only way to
   //signal the end of the body is to close the connection
   if(connection->response.version == HTTP_VERSION_1_0 &&
      connection->response.chunkedEncoding)
   {
      //Make the connection non persistent
      connection->response.keepAlive = FALSE;
      connection->response.chunkedEncoding = FALSE;
      //The size of the response body is not limited
      connection->response.byteCount = UINT_MAX;
   }
   else
   {
      //Limit the size of the response body
      connection->response.byteCount = connection->response.contentLength;
   }

   //Point to the beginning of the buffer
   p = connection->buffer;

   //The first line of a response message is the Status-Line, consisting
   //of the protocol version followed by a numeric status code and its
   //associated textual phrase
   p += sprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
      LSB(connection->response.version), connection->response.statusCode);

   //Retrieve the Reason-Phrase that corresponds to the Status-Code
   for(i = 0; i < arraysize(statusCodeList); i++)
   {
      //Check the status code
      if(statusCodeList[i].value == connection->response.statusCode)
      {
         //Append the textual phrase to the Status-Line
         p += sprintf(p, statusCodeList[i].message);
         //Break the loop and continue processing
         break;
      }
   }

   //Properly terminate the Status-Line
   p += sprintf(p, "\r\n");
   //The Server response-header field contains information about the
   //software used by the origin server to handle the request
   p += sprintf(p, "Server: Oryx Embedded HTTP Server\r\n");

   //Format Location field?
   if(connection->response.location != NULL)
   {
      //Set Location field
      p += sprintf(p, "Location: %s\r\n", connection->response.location);
   }

   //Persistent connection?
   if(connection->response.keepAlive)
   {
      //Set Connection field
      p += sprintf(p, "Connection: keep-alive\r\n");

      //Set Keep-Alive field
      p += sprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
         HTTP_SERVER_IDLE_TIMEOUT / 1000, HTTP_SERVER_MAX_REQUESTS);
   }
   else
   {
      //Set Connection field
      p += sprintf(p, "Connection: close\r\n");
   }

   //Prevent the client from using cache?
   if(connection->response.noCache)
   {
      //Set Pragma field
      p += sprintf(p, "Pragma: no-cache\r\n");
      //Set Cache-Control field
      p += sprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
      p += sprintf(p, "Cache-Control: post-check=0, pre-check=0\r\n");
   }

#if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
   //Use basic access authentication?
   if(connection->response.auth.mode == HTTP_AUTH_MODE_BASIC)
   {
      //Set WWW-Authenticate field
      p += sprintf(p, "WWW-Authenticate: Basic realm=\"Protected Area\"\r\n");
   }
#endif
#if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
   //Use digest access authentication?
   if(connection->response.auth.mode == HTTP_AUTH_MODE_DIGEST)
   {
      size_t n;
      uint8_t opaque[16];

      //Set WWW-Authenticate field
      p += sprintf(p, "WWW-Authenticate: Digest\r\n");
      p += sprintf(p, "  realm=\"Protected Area\",\r\n");
      p += sprintf(p, "  qop=\"auth\",\r\n");
      p += sprintf(p, "  nonce=\"");

      //A server-specified data string which should be uniquely generated
      //each time a 401 response is made
      error = httpGenerateNonce(connection->serverContext, p, &n);
      //Any error to report?
      if(error) return error;

      //Advance pointer
      p += n;
      //Properly terminate the nonce string
      p += sprintf(p, "\",\r\n");

      //Format opaque parameter
      p += sprintf(p, "  opaque=\"");

      //Generate a random value
      error = connection->settings->prngAlgo->read(
         connection->settings->prngContext, opaque, 16);
      //Random number generation failed?
      if(error) return error;

      //Convert the byte array to hex string
      httpConvertArrayToHexString(opaque, 16, p);

      //Advance pointer
      p += 32;
      //Properly terminate the opaque string
      p += sprintf(p, "\"");

      //The STALE flag indicates that the previous request from the client
      //was rejected because the nonce value was stale
      if(connection->response.auth.stale)
         p += sprintf(p, ",\r\n  stale=TRUE");

      //Properly terminate the WWW-Authenticate field
      p += sprintf(p, "\r\n");
   }
#endif

   //Content type
   p += sprintf(p, "Content-Type: %s\r\n", connection->response.contentType);

   //Use chunked encoding transfer?
   if(connection->response.chunkedEncoding)
   {
      //Set Transfer-Encoding field
      p += sprintf(p, "Transfer-Encoding: chunked\r\n");
   }
   //Persistent connection?
   else if(connection->response.keepAlive)
   {
      //Set Content-Length field
      p += sprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
   }

   //Terminate the header with an empty line
   p += sprintf(p, "\r\n");

   //Debug message
   TRACE_DEBUG("HTTP response header:\r\n%s", connection->buffer);

   //Send HTTP response header to the client
   error = httpSend(connection, connection->buffer,
      strlen(connection->buffer), HTTP_FLAG_DELAY);

   //Return status code
   return error;
}
error_t httpGenerateNonce(HttpServerContext *context,
   char_t *output, size_t *length)
{
#if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
   error_t error;
   uint_t i;
   HttpNonceCacheEntry *entry;
   HttpNonceCacheEntry *oldestEntry;
   uint8_t nonce[HTTP_SERVER_NONCE_SIZE];

   //Acquire exclusive access to the nonce cache
   osAcquireMutex(&context->nonceCacheMutex);

   //Keep track of the oldest entry
   oldestEntry = &context->nonceCache[0];

   //Loop through nonce cache entries
   for(i = 0; i < HTTP_SERVER_NONCE_CACHE_SIZE; i++)
   {
      //Point to the current entry
      entry = &context->nonceCache[i];

      //Check whether the entry is currently in used or not
      if(!entry->count)
         break;

      //Keep track of the oldest entry in the table
      if(timeCompare(entry->timestamp, oldestEntry->timestamp) < 0)
         oldestEntry = entry;
   }

   //The oldest entry is removed whenever the table runs out of space
   if(i >= HTTP_SERVER_NONCE_CACHE_SIZE)
      entry = oldestEntry;

   //Generate a new nonce
   error = context->settings.prngAlgo->read(context->settings.prngContext,
      nonce, HTTP_SERVER_NONCE_SIZE);

   //Check status code
   if(!error)
   {
      //Convert the byte array to hex string
      httpConvertArrayToHexString(nonce, HTTP_SERVER_NONCE_SIZE, entry->nonce);
      //Clear nonce count
      entry->count = 1;
      //Save the time at which the nonce was generated
      entry->timestamp = osGetSystemTime();

      //Copy the nonce to the output buffer
      strcpy(output, entry->nonce);
      //Return the length of the nonce excluding the NULL character
      *length = HTTP_SERVER_NONCE_SIZE * 2;
   }
   else
   {
      //Random number generation failed
      memset(entry, 0, sizeof(HttpNonceCacheEntry));
   }

   //Release exclusive access to the nonce cache
   osReleaseMutex(&context->nonceCacheMutex);
   //Return status code
   return error;

#else
   //Not implemented
   return ERROR_NOT_IMPLEMENTED;
#endif
}