/// <summary>
        /// Verifies a MD5 Known User request
        /// </summary>
        /// <param name="secretKey">
        /// The secret key as configured on the queue
        /// </param>
        /// <param name="urlProvider">
        /// An optional way of providing the original hashed URL (in case of URL rewrite or similar)
        /// </param>
        /// <param name="querystringPrefix">The request to verify
        /// An optional querystring prefix as configured on the Queue-it account. 
        /// This can be used if there are name collisions with the queuestring parameters appended by Queue-it 
        /// </param>
        /// <returns>IKnownUser reprecentation of the request</returns>
        /// <exception cref="System.ArgumentNullException">
        /// The Secret Key cannot be null. Invoke KnownUserFactory.Configure or add configuration in config file.
        /// </exception>
        /// <exception cref="QueueIT.Security.InvalidKnownUserUrlException">
        /// The Known User request does not contaion the required parameters
        /// </exception>
        /// <exception cref="QueueIT.Security.InvalidKnownUserHashException">
        /// The hash of the request is invalid
        /// </exception>
        /// <example>
        /// <code language="cs">
        /// try
        /// {
        ///     IKnownUser knownUser = KnownUserFactory.VerifyMd5Hash();
        ///
        ///     if (knownUser == null)
        ///         throw new UnverifiedKnownUserException();
        ///
        ///     if (knownUser.TimeStamp  &lt; DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(3)))
        ///         throw new UnverifiedKnownUserException();
        ///
        ///     PersistModel model = new PersistModel(
        ///         knownUser.QueueId,
        ///         knownUser.PlaceInQueue,
        ///         knownUser.TimeStamp);
        ///
        ///     model.Persist();
        /// }
        /// catch (KnownUserException ex)
        /// {
        ///     Response.Redirect("Error.aspx?queuename=link");
        /// }
        /// </code>
        /// </example>
        /// <example>
        /// PHP example:
        /// <code language="none">
        /// <![CDATA[
        /// <?php
        /// 	require_once('../QueueIT.Security PHP/KnownUserFactory.php');
        /// 	
        /// 	use QueueIT\Security\KnownUserFactory, QueueIT\Security\KnownUserException;
        /// 	
        /// 	try
        /// 	{
        /// 		$knownUser = KnownUserFactory::verifyMd5Hash();
        /// 	
        /// 		if ($knownUser == null)
        /// 			header('Location: link.php');
        /// 				
        /// 		if ($knownUser->getTimeStamp()->getTimestamp() < (time() - 180))
        /// 			header('Location: link.php');
        /// 	}
        /// 	catch (KnownUserException $ex)
        /// 	{
        /// 		header('Location: error.php');
        /// 	}
        /// ?>
        /// ]]>
        /// </code>
        /// </example>
        /// <example>
        /// Java EE example:
        /// <code language="none">
        /// <![CDATA[
        ///     try
        ///     {
        ///         IKnownUser knownUser = KnownUserFactory.verifyMd5Hash();
        /// 
        ///         if (knownUser == null) {
        ///             response.sendRedirect("link.jsp");
        ///             return;
        ///         }
        /// 
        ///         if (knownUser.getTimeStamp().getTime()  < ((new Date()).getTime() - 180 * 1000)) {
        ///             response.sendRedirect("link.jsp");
        ///             return;
        ///         }
        ///     }
        ///     catch (KnownUserException ex)
        ///     {
        ///         response.sendRedirect("error.jsp");
        ///         return;
        ///     }
        /// ]]>
        /// </code>
        /// </example>
        public static IKnownUser VerifyMd5Hash(
            string secretKey = null, 
            IKnownUserUrlProvider urlProvider = null, 
            string querystringPrefix = null)
        {
            if (string.IsNullOrEmpty(secretKey))
                secretKey = SecretKey;

            if (string.IsNullOrEmpty(querystringPrefix))
                querystringPrefix = _defaultQuerystringPrefix;

            if (urlProvider == null && _defaultUrlProviderFactory != null)
                urlProvider = _defaultUrlProviderFactory.Invoke();

            if (string.IsNullOrEmpty(secretKey))
                throw new ArgumentNullException(
                    "secretKey", 
                    "The Secret Key cannot be null. Invoke KnownUserFactory. Configure or add configuration in config file.");

            string url = urlProvider.GetUrl();
            ValidateUrl(url);

            string originalUrl = urlProvider.GetOriginalUrl(querystringPrefix);

            try
            {
                Guid? queueId = ParseQueueId(urlProvider.GetQueueId(querystringPrefix));
                string placeInQueueObfuscated = urlProvider.GetPlaceInQueue(querystringPrefix);
                int? placeInQueue = null; 
                if (!string.IsNullOrEmpty(placeInQueueObfuscated))
                {
                    try
                    {
                        placeInQueue = (int)Hashing.DecryptPlaceInQueue(placeInQueueObfuscated);
                    }
                    catch (Exception)
                    {
                        throw new InvalidKnownUserUrlException();
                    }
                }

                DateTime? timeStamp = ParseTimeStamp(urlProvider.GetTimeStamp(querystringPrefix));
                string customerId = urlProvider.GetCustomerId(querystringPrefix);
                string eventId = urlProvider.GetEventId(querystringPrefix);
                RedirectType redirectType = ParseRedirectType(urlProvider.GetRedirectType(querystringPrefix));

                if (!queueId.HasValue && !placeInQueue.HasValue && !timeStamp.HasValue)
                    return null;

                if (!queueId.HasValue || !placeInQueue.HasValue || !timeStamp.HasValue)
                    throw new InvalidKnownUserUrlException();

                string expectedHash = GetExpectedHash(url);

                ValidateHash(url, secretKey, expectedHash);

                return new Md5KnownUser(queueId.Value, placeInQueue.Value, timeStamp.Value, customerId, eventId, redirectType, originalUrl);
            }
            catch (KnownUserException ex)
            {
                ex.OriginalUrl = originalUrl;
                ex.ValidatedUrl = url;
                throw;
            }
        }
        /// <summary>
        /// Verifies a MD5 Known User request
        /// </summary>
        /// <param name="secretKey">
        /// The secret key as configured on the queue
        /// </param>
        /// <param name="urlProvider">
        /// An optional way of providing the original hashed URL (in case of URL rewrite or similar)
        /// </param>
        /// <param name="querystringPrefix">The request to verify
        /// An optional querystring prefix as configured on the Queue-it account.
        /// This can be used if there are name collisions with the queuestring parameters appended by Queue-it
        /// </param>
        /// <returns>IKnownUser reprecentation of the request</returns>
        /// <exception cref="System.ArgumentNullException">
        /// The Secret Key cannot be null. Invoke KnownUserFactory.Configure or add configuration in config file.
        /// </exception>
        /// <exception cref="QueueIT.Security.InvalidKnownUserUrlException">
        /// The Known User request does not contaion the required parameters
        /// </exception>
        /// <exception cref="QueueIT.Security.InvalidKnownUserHashException">
        /// The hash of the request is invalid
        /// </exception>
        /// <example>
        /// <code language="cs">
        /// try
        /// {
        ///     IKnownUser knownUser = KnownUserFactory.VerifyMd5Hash();
        ///
        ///     if (knownUser == null)
        ///         throw new UnverifiedKnownUserException();
        ///
        ///     if (knownUser.TimeStamp  &lt; DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(3)))
        ///         throw new UnverifiedKnownUserException();
        ///
        ///     PersistModel model = new PersistModel(
        ///         knownUser.QueueId,
        ///         knownUser.PlaceInQueue,
        ///         knownUser.TimeStamp);
        ///
        ///     model.Persist();
        /// }
        /// catch (KnownUserException ex)
        /// {
        ///     Response.Redirect("Error.aspx?queuename=link");
        /// }
        /// </code>
        /// </example>
        /// <example>
        /// PHP example:
        /// <code language="none">
        /// <![CDATA[
        /// <?php
        ///     require_once('../QueueIT.Security PHP/KnownUserFactory.php');
        ///
        ///     use QueueIT\Security\KnownUserFactory, QueueIT\Security\KnownUserException;
        ///
        ///     try
        ///     {
        ///         $knownUser = KnownUserFactory::verifyMd5Hash();
        ///
        ///         if ($knownUser == null)
        ///             header('Location: link.php');
        ///
        ///         if ($knownUser->getTimeStamp()->getTimestamp() < (time() - 180))
        ///             header('Location: link.php');
        ///     }
        ///     catch (KnownUserException $ex)
        ///     {
        ///         header('Location: error.php');
        ///     }
        /// ?>
        /// ]]>
        /// </code>
        /// </example>
        /// <example>
        /// Java EE example:
        /// <code language="none">
        /// <![CDATA[
        ///     try
        ///     {
        ///         IKnownUser knownUser = KnownUserFactory.verifyMd5Hash();
        ///
        ///         if (knownUser == null) {
        ///             response.sendRedirect("link.jsp");
        ///             return;
        ///         }
        ///
        ///         if (knownUser.getTimeStamp().getTime()  < ((new Date()).getTime() - 180 * 1000)) {
        ///             response.sendRedirect("link.jsp");
        ///             return;
        ///         }
        ///     }
        ///     catch (KnownUserException ex)
        ///     {
        ///         response.sendRedirect("error.jsp");
        ///         return;
        ///     }
        /// ]]>
        /// </code>
        /// </example>
        public static IKnownUser VerifyMd5Hash(
            string secretKey = null,
            IKnownUserUrlProvider urlProvider = null,
            string querystringPrefix          = null)
        {
            if (string.IsNullOrEmpty(secretKey))
            {
                secretKey = SecretKey;
            }

            if (string.IsNullOrEmpty(querystringPrefix))
            {
                querystringPrefix = _defaultQuerystringPrefix;
            }

            if (urlProvider == null && _defaultUrlProviderFactory != null)
            {
                urlProvider = _defaultUrlProviderFactory.Invoke();
            }

            if (string.IsNullOrEmpty(secretKey))
            {
                throw new ArgumentNullException(
                          "secretKey",
                          "The Secret Key cannot be null. Invoke KnownUserFactory. Configure or add configuration in config file.");
            }

            string url = urlProvider.GetUrl();

            ValidateUrl(url);

            string originalUrl = urlProvider.GetOriginalUrl(querystringPrefix);

            try
            {
                Guid?  queueId = ParseQueueId(urlProvider.GetQueueId(querystringPrefix));
                string placeInQueueObfuscated = urlProvider.GetPlaceInQueue(querystringPrefix);
                int?   placeInQueue           = null;
                if (!string.IsNullOrEmpty(placeInQueueObfuscated))
                {
                    try
                    {
                        placeInQueue = (int)Hashing.DecryptPlaceInQueue(placeInQueueObfuscated);
                    }
                    catch (Exception)
                    {
                        throw new InvalidKnownUserUrlException();
                    }
                }

                DateTime?    timeStamp    = ParseTimeStamp(urlProvider.GetTimeStamp(querystringPrefix));
                string       customerId   = urlProvider.GetCustomerId(querystringPrefix);
                string       eventId      = urlProvider.GetEventId(querystringPrefix);
                RedirectType redirectType = ParseRedirectType(urlProvider.GetRedirectType(querystringPrefix));

                if (!queueId.HasValue && !placeInQueue.HasValue && !timeStamp.HasValue)
                {
                    return(null);
                }

                if (!queueId.HasValue || !placeInQueue.HasValue || !timeStamp.HasValue)
                {
                    throw new InvalidKnownUserUrlException();
                }

                string expectedHash = GetExpectedHash(url);

                ValidateHash(url, secretKey, expectedHash);

                return(new Md5KnownUser(queueId.Value, placeInQueue.Value, timeStamp.Value, customerId, eventId, redirectType, originalUrl));
            }
            catch (KnownUserException ex)
            {
                ex.OriginalUrl  = originalUrl;
                ex.ValidatedUrl = url;
                throw;
            }
        }