/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;

namespace OpenSim.ApplicationPlugins.Rest.Inventory
{

    /// <summary>
    /// This class represents the current REST request. It
    /// encapsulates the request/response state and takes care
    /// of response generation without exposing the REST handler
    /// to the actual mechanisms involved.
    ///
    /// This structure is created on entry to the Handler
    /// method and is disposed of upon return. It is part of
    /// the plug-in infrastructure, rather than the functionally
    /// specific REST handler, and fundamental changes to
    /// this should be reflected in the Rest HandlerVersion. The
    /// object is instantiated, and may be extended by, any
    /// given handler. See the inventory handler for an example
    /// of this.
    ///
    /// If possible, the underlying request/response state is not
    /// changed until the handler explicitly issues a Respond call.
    /// This ensures that the request/response pair can be safely
    /// processed by subsequent, unrelated, handlers even id the
    /// agent handler had completed much of its processing. Think
    /// of it as a transactional req/resp capability.
    /// </summary>

    public class RequestData
    {

        // HTTP Server interface data (Received values)

        internal OSHttpRequest        request = null;
        internal OSHttpResponse       response = null;
        internal string               qprefix = null;

        // Request lifetime values
        // buffer is global because it is referenced by the handler
        // in supported of streamed requests.
        // If a service provider wants to construct the message
        // body explicitly it can use body to do this. The value
        // in body is used if the buffer is still null when a response
        // is generated.
        // Storing information in body will suppress the return of
        // statusBody which is only intended to report status on
        // requests which do not themselves ordinarily generate
        // an informational response. All of this is handled in
        // Respond().

        internal byte[]               buffer = null;
        internal string               body   = null;
        internal string               bodyType = "text/html";

        // The encoding in effect is set to a server default. It may
        // subsequently be overridden by a Content header. This
        // value is established during construction and is used
        // wherever encoding services are needed.

        internal Encoding             encoding = Rest.Encoding;

        // These values are derived from the supplied URL. They
        // are initialized during construction.

        internal string               path = null;
        internal string               method = null;
        internal Uri                  uri = null;
        internal string               query = null;
        internal string               hostname = "localhost";
        internal int                  port = 80;

        // The path part of the URI is decomposed. pathNodes
        // is an array of every element in the URI. Parameters
        // is an array that contains only those nodes that
        // are not a part of the authority prefix

        private string[]              pathNodes = null;
        private string[]              parameters = null;
        private static readonly string[] EmptyPath = { String.Empty };

        // The status code gets set during the course of processing
        // and is the HTTP completion code. The status body is
        // initialized during construction, is appended to during the
        // course of execution, and is finalized during Respond
        // processing.
        //
        // Fail processing marks the request as failed and this is
        // then used to inhibit processing during Response processing.

        internal int                  statusCode = 0;
        internal string               statusBody = String.Empty;
        internal bool                 fail = false;

        // This carries the URL to which the client should be redirected.
        // It is set by the service provider using the Redirect call.

        internal string               redirectLocation = null;

        // These values influence response processing. They can be set by
        // service providers according to need. The defaults are generally
        // good.

        internal bool                 keepAlive = false;
        internal bool                 chunked = false;

        // XML related state

        internal XmlWriter            writer = null;
        internal XmlReader            reader = null;

        // Internal working state

        private StringBuilder         sbuilder = new StringBuilder(1024);
        private MemoryStream          xmldata = null;

        // This is used to make the response mechanism idempotent.

        internal bool                 handled = false;

        // Authentication related state
        //
        // Two supported authentication mechanisms are:
        // scheme = Rest.AS_BASIC;
        // scheme = Rest.AS_DIGEST;
        // Presented in that order (as required by spec)
        // A service provider can set the scheme variable to
        // force selection of a particular authentication model
        // (choosing from amongst those supported of course)
        //

        internal bool                 authenticated = false;
        internal string               scheme = Rest.Scheme;
        internal string               realm = Rest.Realm;
        internal string               domain = null;
        internal string               nonce = null;
        internal string               cnonce = null;
        internal string               qop = Rest.Qop_Auth;
        internal string               opaque = null;
        internal string               stale = null;
        internal string               algorithm = Rest.Digest_MD5;
        internal string               authParms  = null;
        internal string               authPrefix = null;
        internal string               userName  = String.Empty;
        internal string               userPass  = String.Empty;

        // Session related tables. These are only needed if QOP is set to "auth-sess"
        // and for now at least, it is not. Session related authentication is of
        // questionable merit in the context of REST anyway, but it is, arguably, more
        // secure.

        private static Dictionary<string,string>  cntable   = new Dictionary<string,string>();
        private static Dictionary<string,string>  sktable   = new Dictionary<string,string>();

        // This dictionary is used to keep track fo all of the parameters discovered
        // when the authorisation header is anaylsed.

        private        Dictionary<string,string>  authparms = new Dictionary<string,string>();

        // These regular expressions are used to decipher the various header entries.

        private static Regex schema      = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
                                                     RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private static Regex basicParms  = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
                                                     RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
                                                     RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
                                                     RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private static Regex reuserPass  = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
                                                     RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // For efficiency, we create static instances of these objects

        private static MD5   md5hash     = MD5.Create();

        private static StringComparer sc = StringComparer.OrdinalIgnoreCase;

#region properties

        // Just for convenience...

        internal string MsgId
        {
            get { return Rest.MsgId; }
        }

        /// <summary>
        /// Return a boolean indication of whether or no an authenticated user is
        /// associated with this request. This could be wholly integrated, but
        /// that would make authentication mandatory.
        /// </summary>

        internal bool IsAuthenticated
        {
            get
            {
                if (Rest.Authenticate)
                {
                    if (!authenticated)
                    {
                        authenticate();
                    }

                    return authenticated;
                }
                else return true;
            }
        }

        /// <summary>
        /// Access to all 'nodes' in the supplied URI as an
        /// array of strings.
        /// </summary>

        internal string[] PathNodes
        {
            get
            {
                return pathNodes;
            }
        }

        /// <summary>
        /// Access to all non-prefix 'nodes' in the supplied URI as an
        /// array of strings. These identify a specific resource that
        /// is managed by the authority (the prefix).
        /// </summary>

        internal string[] Parameters
        {
            get
            {
                return parameters;
            }
        }

#endregion properties

#region constructors

        // Constructor

        internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
        {

            request  = p_request;
            response = p_response;
            qprefix  = p_qprefix;

            sbuilder.Length = 0;

            encoding = request.ContentEncoding;

            if (encoding == null)
            {
                encoding = Rest.Encoding;
            }

            method = request.HttpMethod.ToLower();
            initUrl();

            initParameters(p_qprefix.Length);

        }

#endregion constructors

#region authentication_common

        /// <summary>
        /// The REST handler has requested authentication. Authentication
        /// is considered to be with respect to the current values for
        /// Realm, domain, etc.
        ///
        /// This method checks to see if the current request is already
        /// authenticated for this domain. If it is, then it returns
        /// true. If it is not, then it issues a challenge to the client
        /// and responds negatively to the request.
        ///
        /// As soon as authentication failure is detected the method calls
        /// DoChallenge() which terminates the request with REST exception
        /// for unauthroized access.
        /// </summary>

        private void authenticate()
        {

            string authdata  = request.Headers.Get("Authorization");
            string reqscheme = String.Empty;

            // If we don't have an authorization header, then this
            // user is certainly not authorized. This is the typical
            // pivot for the 1st request by a client.

            if (authdata == null)
            {
                Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
                DoChallenge();
            }

            // So, we have authentication data, now we have to check to
            // see what we got and whether or not it is valid for the
            // current domain. To do this we need to interpret the data
            // provided in the Authorization header. First we need to
            // identify the scheme being used and route accordingly.

            MatchCollection matches = schema.Matches(authdata);

            foreach (Match m in matches)
            {
                Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
                reqscheme = m.Groups["scheme"].Value.ToLower();
            }

            // If we want a specific authentication mechanism, make sure
            // we get it. null indicates we don't care. non-null indicates
            // a specific scheme requirement.

            if (scheme != null && scheme.ToLower() != reqscheme)
            {
                Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId);
                DoChallenge();
            }

            // In the future, these could be made into plug-ins...
            // But for now at least we have no reason to use anything other
            // then MD5. TLS/SSL are taken care of elsewhere.

            switch (reqscheme)
            {
                case "digest" :
                    Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
                    DoDigest(authdata);
                    break;

                case "basic" :
                    Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
                    DoBasic(authdata);
                    break;
            }

            // If the current header is invalid, then a challenge is still needed.

            if (!authenticated)
            {
                Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
                DoChallenge();
            }

        }

        /// <summary>
        /// Construct the necessary WWW-Authenticate headers and fail the request
        /// with a NOT AUTHORIZED response. The parameters are the union of values
        /// required by the supported schemes.
        /// </summary>

        private void DoChallenge()
        {
            Flush();
            nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
            Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
            Fail(Rest.HttpStatusCodeNotAuthorized);
        }

        /// <summary>
        /// The Flush() call is here to support a problem encountered with the
        /// client where an authentication rejection was lost because the rejection
        /// may flow before the clienthas finished sending us the inbound data stream,
        /// in which case the client responds to the socket error on out put, and
        /// never sees the authentication challenge. The client should be fixed,
        /// because this solution leaves the server prone to DOS attacks. A message
        /// will be issued whenever flushing occurs. It can be enabled/disabled from
        /// the configuration file.
        /// </summary>

        private void Flush()
        {
            if (Rest.FlushEnabled)
            {
                byte[] dbuffer = new byte[8192];
                Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
                while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
            }
            return;
        }

        // Indicate that authentication is required

        private void Challenge(string scheme, string realm, string domain, string nonce,
                                string opaque, string stale, string alg,
                                string qop, string auth)
        {

            sbuilder.Length = 0;

            // The service provider can force a particular scheme by
            // assigning a value to scheme.

            // Basic authentication is pretty simple.
            // Just specify the realm in question.

            if (scheme == null || scheme == Rest.AS_BASIC)
            {

                sbuilder.Append(Rest.AS_BASIC);

                if (realm != null)
                {
                    sbuilder.Append(" realm=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(realm);
                    sbuilder.Append(Rest.CS_DQUOTE);
                }
                AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
            }

            sbuilder.Length = 0;

            // Digest authentication takes somewhat more
            // to express.

            if (scheme == null || scheme == Rest.AS_DIGEST)
            {

                sbuilder.Append(Rest.AS_DIGEST);
                sbuilder.Append(" ");

                // Specify the effective realm. This should
                // never be null if we are uthenticating, as it is required for all
                // authentication schemes. It defines, in conjunction with the
                // absolute URI information, the domain to which the authentication
                // applies. It is an arbitrary string. I *believe* this allows an
                // authentication to apply to disjoint resources within the same
                // server.

                if (realm != null)
                {
                    sbuilder.Append("realm=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(realm);
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // Share our nonce. This is *uniquely* generated each time a 401 is
                // returned. We do not generate a very sophisticated nonce at the
                // moment (it's simply a base64 encoded UUID).

                if (nonce != null)
                {
                    sbuilder.Append("nonce=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(nonce);
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // The opaque string should be returned by the client unchanged in all
                // subsequent requests.

                if (opaque != null)
                {
                    sbuilder.Append("opaque=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(opaque);
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // This flag indicates that the authentication was rejected because the
                // included nonce was stale. The server might use timestamp information
                // in the nonce to determine this. We do not.

                if (stale != null)
                {
                    sbuilder.Append("stale=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(stale);
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // Identifies the algorithm used to produce the digest and checksum.
                // The default is MD5.

                if (alg != null)
                {
                    sbuilder.Append("algorithm=");
                    sbuilder.Append(alg);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // Theoretically QOP is optional, but it is required by a compliant
                // with current versions of the scheme. In fact IE requires that QOP
                // be specified and will refuse to authenticate otherwise.

                if (qop != String.Empty)
                {
                    sbuilder.Append("qop=");
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(qop);
                    sbuilder.Append(Rest.CS_DQUOTE);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // This parameter allows for arbitrary extensions to the protocol.
                // Unrecognized values should be simply ignored.

                if (auth != null)
                {
                    sbuilder.Append(auth);
                    sbuilder.Append(Rest.CS_COMMA);
                }

                // We don't know the userid that will be used
                // so we cannot make any authentication domain
                // assumptions. So the prefix will determine
                // this.

                sbuilder.Append("domain=");
                sbuilder.Append(Rest.CS_DQUOTE);
                sbuilder.Append(qprefix);
                sbuilder.Append(Rest.CS_DQUOTE);

                // Generate the authenticate header and we're basically
                // done.

                AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());

            }

        }

#endregion authentication_common

#region authentication_basic

        /// <summary>
        /// Interpret a BASIC authorization claim. Some clients can only
        /// understand this and also expect it to be the first one
        /// offered. So we do.
        /// OpenSim also needs this, as it is the only scheme that allows
        /// authentication using the hashed passwords stored in the
        /// user database.
        /// </summary>

        private void DoBasic(string authdata)
        {

            string response = null;

            MatchCollection matches = basicParms.Matches(authdata);

            // In the case of basic authentication there is
            // only expected to be a single argument.

            foreach (Match m in matches)
            {
                authparms.Add("response",m.Groups["pval"].Value);
                Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
                                     MsgId, "response", m.Groups["pval"].Value);
            }

            // Did we get a valid response?

            if (authparms.TryGetValue("response", out response))
            {
                // Decode
                response = Rest.Base64ToString(response);
                Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);

                // Extract user & password
                Match m = reuserPass.Match(response);
                userName = m.Groups["user"].Value;
                userPass = m.Groups["pass"].Value;

                // Validate against user database
                authenticated = Validate(userName,userPass);
            }

        }

        /// <summary>
        /// This method provides validation in support of the BASIC
        /// authentication method. This is not normaly expected to be
        /// used, but is included for completeness (and because I tried
        /// it first).
        /// </summary>

        private bool Validate(string user, string pass)
        {

            Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);

            // Both values are required

            if (user == null || pass == null)
                return false;

            // Eliminate any leading or trailing spaces
            user = user.Trim();

            return vetPassword(user, pass);

        }

        /// <summary>
        /// This is used by the BASIC authentication scheme to calculate
        /// the double hash used by OpenSim to encode user's passwords.
        /// It returns true, if the supplied password is actually correct.
        /// If the specified user-id is not recognized, but the password
        /// matches the God password, then this is accepted as an admin
        /// session.
        /// </summary>

        private bool vetPassword(string user, string pass)
        {

            int x;
            string HA1;
            string first;
            string last;

            // Distinguish the parts, if necessary

            if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
            {
                first = user.Substring(0,x);
                last  = user.Substring(x+1);
            }
            else
            {
                first = user;
                last  = String.Empty;
            }

            UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);

            // If we don't recognize the user id, perhaps it is god?

            if (udata == null)
                return pass == Rest.GodKey;

            HA1 = HashToString(pass);
            HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));

            return (0 == sc.Compare(HA1, udata.PasswordHash));

        }

#endregion authentication_basic

#region authentication_digest

        /// <summary>
        /// This is an RFC2617 compliant HTTP MD5 Digest authentication
        /// implementation. It has been tested with Firefox, Java HTTP client,
        /// and Microsoft's Internet Explorer V7.
        /// </summary>

        private void DoDigest(string authdata)
        {

            string response = null;

            // Find all of the values of the for x = "y"

            MatchCollection matches = digestParm1.Matches(authdata);

            foreach (Match m in matches)
            {
                authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
                Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
                                     MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
            }

            // Find all of the values of the for x = y

            matches = digestParm2.Matches(authdata);

            foreach (Match m in matches)
            {
                authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
                Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
                                     MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
            }

            // A response string MUST be returned, otherwise we are
            // NOT authenticated.

            Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);

            if (authparms.TryGetValue("response", out response))
            {

                string temp   = null;

                do
                {

                    string nck = null;
                    string ncl = null;

                    // The userid is sent in clear text. Needed for the
                    // verification.

                    authparms.TryGetValue("username", out userName);

                    // All URI's of which this is a prefix are
                    // optimistically considered to be authenticated by the
                    // client. This is also needed to verify the response.

                    authparms.TryGetValue("uri", out authPrefix);

                    // There MUST be a nonce string present. We're not preserving any server
                    // side state and we can't validate the MD5 unless the client returns it
                    // to us, as it should.

                    if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
                    {
                        Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
                        break;
                    }

                    // If there is an opaque string present, it had better
                    // match what we sent.

                    if (authparms.TryGetValue("opaque", out temp))
                    {
                        if (temp != opaque)
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
                            break;
                        }
                    }

                    // If an algorithm string is present, it had better
                    // match what we sent.

                    if (authparms.TryGetValue("algorithm", out temp))
                    {
                        if (temp != algorithm)
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
                            break;
                        }
                    }

                    // Quality of protection considerations...

                    if (authparms.TryGetValue("qop", out temp))
                    {

                        qop = temp.ToLower(); // replace with actual value used

                        // if QOP was specified then
                        // these MUST be present.

                        if (!authparms.ContainsKey("cnonce"))
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
                            Fail(Rest.HttpStatusCodeBadRequest);
                            break;
                        }

                        cnonce = authparms["cnonce"];

                        if (!authparms.TryGetValue("nc", out nck) || nck == null)
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
                            Fail(Rest.HttpStatusCodeBadRequest);
                            break;
                        }

                        Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId);

                        if (cntable.TryGetValue(nonce, out ncl))
                        {
                            Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl);

                            if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
                            {
                                Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
                                Fail(Rest.HttpStatusCodeBadRequest);
                                break;
                            }
                            cntable[nonce] = nck;
                        }
                        else
                        {
                            lock (cntable) cntable.Add(nonce, nck);
                        }

                    }
                    else
                    {

                        qop = String.Empty;

                        // if QOP was not specified then
                        // these MUST NOT be present.
                        if (authparms.ContainsKey("cnonce"))
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
                            Fail(Rest.HttpStatusCodeBadRequest);
                            break;
                        }
                        if (authparms.ContainsKey("nc"))
                        {
                            Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
                            Fail(Rest.HttpStatusCodeBadRequest);
                            break;
                        }
                    }

                    // Validate the supplied userid/password info

                    authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);

                }
                while (false);

            }
            else
                Fail(Rest.HttpStatusCodeBadRequest);

        }

        /// <summary>
        /// This mechanism is used by the digest authentication mechanism
        /// to return the user's password. In fact, because the OpenSim
        /// user's passwords are already hashed, and the HTTP mechanism
        /// does not supply an open password, the hashed passwords cannot
        /// be used unless the client has used the same salting mechanism
        /// to has the password before using it in the authentication
        /// algorithn. This is not inconceivable...
        /// </summary>

        private string getPassword(string user)
        {

            int x;
            string first;
            string last;

            // Distinguish the parts, if necessary

            if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
            {
                first = user.Substring(0,x);
                last  = user.Substring(x+1);
            }
            else
            {
                first = user;
                last  = String.Empty;
            }

            UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);

            // If we don;t recognize the user id, perhaps it is god?

            if (udata == null)
            {
                Rest.Log.DebugFormat("{0} Administrator", MsgId);
                return Rest.GodKey;
            }
            else
            {
                Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
                return udata.PasswordHash;
            }

        }

        // Validate the request-digest

        private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
        {

            string patt = null;
            string payl = String.Empty;
            string KDS  = null;
            string HA1  = null;
            string HA2  = null;
            string pass = getPassword(user);

            // Generate H(A1)

            if (algorithm == Rest.Digest_MD5Sess)
            {
                if (!sktable.ContainsKey(cnonce))
                {
                    patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
                    HA1  = HashToString(patt);
                    sktable.Add(cnonce, HA1);
                }
                else
                {
                    HA1  = sktable[cnonce];
                }
            }
            else
            {
                patt = String.Format("{0}:{1}:{2}", user, realm, pass);
                HA1  = HashToString(patt);
            }

            // Generate H(A2)

            if (qop == "auth-int")
            {
                patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
            }
            else
            {
                patt = String.Format("{0}:{1}", request.HttpMethod, uri);
            }

            HA2  = HashToString(patt);

            // Generate Digest

            if (qop != String.Empty)
            {
                patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
            }
            else
            {
                patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
            }

            KDS = HashToString(patt);

            // Compare the generated sequence with the original

            return (0 == sc.Compare(KDS, response));

        }

        private string HashToString(string pattern)
        {

            Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);

            byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));

            sbuilder.Length = 0;

            for (int i = 0; i < hash.Length; i++)
            {
                sbuilder.Append(hash[i].ToString("x2"));
            }

            Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());

            return sbuilder.ToString();

        }

#endregion authentication_digest

#region service_interface

        /// <summary>
        /// Conditionally set a normal completion code. This allows a normal
        /// execution path to default.
        /// </summary>

        internal void Complete()
        {
            if (statusCode == 0)
            {
                statusCode = Rest.HttpStatusCodeOK;
            }
        }

        /// <summary>
        /// Indicate a functionally-dependent conclusion to the
        /// request. See Rest.cs for a list of possible values.
        /// </summary>

        internal void Complete(int code)
        {
            statusCode = code;
        }

        /// <summary>
        /// Indicate that a request should be redirected, using
        /// the HTTP completion codes. Permanent and temporary
        /// redirections may be indicated. The supplied URL is
        /// the new location of the resource.
        /// </summary>

        internal void Redirect(string Url, bool temp)
        {

            redirectLocation = Url;

            if (temp)
            {
                statusCode = Rest.HttpStatusCodeTemporaryRedirect;
            }
            else
            {
                statusCode = Rest.HttpStatusCodePermanentRedirect;
            }

            Fail(statusCode, String.Empty, true);

        }

        /// <summary>
        /// Fail for an arbitrary reason. Just a failure with
        /// headers. The supplied message will be returned in the
        /// message body.
        /// </summary>

        internal void Fail(int code)
        {
            Fail(code, String.Empty, false);
        }

        /// <summary>
        /// For the more adventurous. This failure also includes a
        /// specified entity to be appended to the code-related
        /// status string.
        /// </summary>

        internal void Fail(int code, string addendum)
        {
            Fail(code, addendum, false);
        }

        internal void Fail(int code, string addendum, bool reset)
        {

            statusCode        = code;
            appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));

            // Add any final addendum to the status information

            if (addendum != String.Empty)
            {
                appendStatus(String.Format(addendum));
            }

            // Help us understand why the request is being rejected

            if (Rest.DEBUG)
            {
                Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
                Rest.Log.DebugFormat("{0}     Scheme = {1}", MsgId, scheme);
                Rest.Log.DebugFormat("{0}      Realm = {1}", MsgId, realm);
                Rest.Log.DebugFormat("{0}     Domain = {1}", MsgId, domain);
                Rest.Log.DebugFormat("{0}      Nonce = {1}", MsgId, nonce);
                Rest.Log.DebugFormat("{0}     CNonce = {1}", MsgId, cnonce);
                Rest.Log.DebugFormat("{0}     Opaque = {1}", MsgId, opaque);
                Rest.Log.DebugFormat("{0}      Stale = {1}", MsgId, stale);
                Rest.Log.DebugFormat("{0}  Algorithm = {1}", MsgId, algorithm);
                Rest.Log.DebugFormat("{0}        QOP = {1}", MsgId, qop);
                Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
                Rest.Log.DebugFormat("{0}   UserName = {1}", MsgId, userName);
                Rest.Log.DebugFormat("{0}   UserPass = {1}", MsgId, userPass);
            }

            fail = true;

            // Respond to the client's request, tag the response (for the
            // benefit of trace) to indicate the reason.

            Respond(String.Format("Failure response: ({0}) : {1} ",
                            code, Rest.HttpStatusDesc[code]));

            // Finally initialize and the throw a RestException. All of the
            // handler's infrastructure knows that this is a "normal"
            // completion from a code point-of-view.

            RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");

            re.statusCode = code;
            re.statusDesc = Rest.HttpStatusDesc[code];
            re.httpmethod = method;
            re.httppath   = path;

            throw re;

        }

        // Reject this request

        internal void Reject()
        {
            Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
        }

        // This MUST be called by an agent handler before it returns
        // control to Handle, otherwise the request will be ignored.
        // This is called implciitly for the REST stream handlers and
        // is harmless if it is called twice.

        internal virtual bool Respond(string reason)
        {


            Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);

            // We do this to try and make multiple Respond requests harmless,
            // as it is sometimes convenient to isse a response without
            // certain knowledge that it has not previously been done.

            if (!handled)
            {

                Rest.Log.DebugFormat("{0} Generating Response", MsgId);
                Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);

                // A Head request can NOT have a body! So don't waste time on
                // formatting if we're going to reject it anyway!

                if (method != Rest.HEAD)
                {

                    Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);

                    // If the writer is non-null then we know that an XML
                    // data component exists. Flush and close the writer and
                    // then convert the result to the expected buffer format
                    // unless the request has already been failed for some
                    // reason.

                    if (writer != null)
                    {
                        Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
                        Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
                        writer.Flush();
                        writer.Close();
                        if (!fail)
                        {
                            buffer = xmldata.ToArray();
                            AddHeader("Content-Type","application/xml");
                        }
                        xmldata.Close();
                        Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
                        Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
                    }

                    if (buffer == null && body != null)
                    {
                        buffer = encoding.GetBytes(body);
                        AddHeader("Content-Type",bodyType);
                    }

                    // OK, if the buffer contains something, regardless of how
                    // it got there, set various response headers accordingly.

                    if (buffer != null)
                    {
                        Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
                    }
                    else
                    {
                        if (statusBody != String.Empty)
                        {
                            statusBody += Rest.statusTail;
                            buffer = encoding.GetBytes(statusBody);
                            AddHeader("Content-Type","text/html");
                        }
                        else
                        {
                            statusBody = Rest.statusHead;
                            appendStatus(String.Format(": ({0}) {1}",
                                 statusCode, Rest.HttpStatusDesc[statusCode]));
                            statusBody += Rest.statusTail;
                            buffer = encoding.GetBytes(statusBody);
                            AddHeader("Content-Type","text/html");
                        }
                    }

                    response.ContentLength64 = buffer.Length;

                    if (response.ContentEncoding == null)
                        response.ContentEncoding = encoding;

                    response.SendChunked     = chunked;
                    response.KeepAlive       = keepAlive;

                }

                // Set the status code & description. If nothing has been stored,
                // we consider that a success.

                if (statusCode == 0)
                {
                    Complete();
                }

                // Set the response code in the actual carrier

                response.StatusCode = statusCode;

                // For a redirect we need to set the relocation header accordingly

                if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
                    response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
                {
                    Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
                    response.RedirectLocation = redirectLocation;
                }

                // And include the status description if provided.

                response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];

                // Finally we send back our response.

                // We've left the setting of handled' until the
                // last minute because the header settings included
                // above are pretty harmless. But everything from
                // here on down probably leaves the response
                // element unusable by anyone else.

                handled = true;

                // DumpHeaders();

                // if (request.InputStream != null)
                // {
                //     Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
                //     request.InputStream.Close();
                // }

                if (buffer != null && buffer.Length != 0)
                {
                    Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length);
                    // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
                    //                     MsgId, buffer.Length, encoding.GetString(buffer));
                    response.OutputStream.Write(buffer, 0, buffer.Length);
                }

                // Closing the outputstream should complete the transmission process

                Rest.Log.DebugFormat("{0} Sending response", MsgId);
                // response.OutputStream.Close();
                response.Send();

            }

            Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);

            return handled;

        }

        /// <summary>
        /// These methods allow a service provider to manipulate the
        /// request/response headers. The DumpHeaders method is intended
        /// for problem diagnosis.
        /// </summary>

        internal void AddHeader(string hdr, string data)
        {
            if (Rest.DEBUG) Rest.Log.DebugFormat("{0}   Adding header: <{1}: {2}>", MsgId, hdr, data);
            response.AddHeader(hdr, data);
        }

        // internal void RemoveHeader(string hdr)
        // {
        //     if (Rest.DEBUG)
        //     {
        //         Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
        //         if (response.Headers.Get(hdr) == null)
        //         {
        //             Rest.Log.DebugFormat("{0} No such header existed",
        //                      MsgId, hdr);
        //         }
        //     }
        //     response.Headers.Remove(hdr);
        // }

        // internal void DumpHeaders()
        // {
        //     if (Rest.DEBUG)
        //     {
        //         for (int i=0;i<response.Headers.Count;i++)
        //         {
        //             Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
        //                      response.Headers.Get(i));
        //         }
        //     }
        // }

        // Setup the XML writer for output

        internal void initXmlWriter()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            xmldata = new MemoryStream();
            settings.Indent = true;
            settings.IndentChars = "    ";
            settings.Encoding = encoding;
            settings.CloseOutput = false;
            settings.OmitXmlDeclaration = true;
            settings.ConformanceLevel = ConformanceLevel.Fragment;
            writer = XmlWriter.Create(xmldata, settings);
        }

        internal void initXmlReader()
        {

            XmlReaderSettings        settings = new XmlReaderSettings();

            settings.ConformanceLevel             = ConformanceLevel.Fragment;
            settings.IgnoreComments               = true;
            settings.IgnoreWhitespace             = true;
            settings.IgnoreProcessingInstructions = true;
            settings.ValidationType               = ValidationType.None;

            reader = XmlReader.Create(request.InputStream,settings);

        }

        internal void appendStatus(string msg)
        {
            if (statusBody == String.Empty)
            {
                statusBody = String.Format(Rest.statusHead, request.HttpMethod);
            }

            statusBody = String.Format("{0} {1}", statusBody, msg);
        }

#endregion service_interface

#region internal_methods

        /// <summary>
        /// Helper methods for deconstructing and reconstructing
        /// URI path data.
        /// </summary>

        private void initUrl()
        {

            uri = request.Url;

            if (query == null)
            {
                query = uri.Query;
            }

            // If the path has not been previously initialized,
            // do so now.

            if (path == null)
            {
                path = uri.AbsolutePath;
                if (path.EndsWith(Rest.UrlPathSeparator))
                    path = path.Substring(0,path.Length-1);
            }

            // If we succeeded in getting a path, perform any
            // additional pre-processing required.

            if (path != null)
            {
                if (Rest.ExtendedEscape)
                {
                    // Handle "+". Not a standard substitution, but
                    // common enough...
                    path      = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
                }
                pathNodes = path.Split(Rest.CA_PATHSEP);
            }
            else
            {
                pathNodes = EmptyPath;
            }

            // Elimiate any %-escaped values. This is left until here
            // so that escaped "+' are not mistakenly replaced.

            path = Uri.UnescapeDataString(path);

            // Request server context info

            hostname = uri.Host;
            port     = uri.Port;

        }

        private int initParameters(int prfxlen)
        {

            if (prfxlen < path.Length-1)
            {
                parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
            }
            else
            {
                parameters = new string[0];
            }

            // Generate a debug list of the decoded parameters

            if (Rest.DEBUG && prfxlen < path.Length-1)
            {
                Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
                for (int i = 0; i < parameters.Length; i++)
                {
                    Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
                }
            }

            return parameters.Length;

        }

#endregion internal_methods

    }
}
