1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201 |
- /*
- * 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 OpenSim 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.IO;
- using System.Reflection;
- using System.Text;
- using System.Security.Cryptography;
- using System.Text.RegularExpressions;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using OpenSim.Framework.Servers;
- using libsecondlife;
- using System.Xml;
- 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
- /// specifici 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>
- internal class RequestData
- {
- // HTTP Server interface data
- internal OSHttpRequest request = null;
- internal OSHttpResponse response = null;
- // Request lifetime values
- internal NameValueCollection headers = null;
- internal List<string> removed_headers = null;
- internal byte[] buffer = null;
- internal string body = null;
- internal string html = null;
- internal string entity = null;
- internal string path = null;
- internal string method = null;
- internal string statusDescription = null;
- internal string redirectLocation = null;
- internal string[] pathNodes = null;
- internal string[] parameters = null;
- internal int statusCode = 0;
- internal bool handled = false;
- internal LLUUID uuid = LLUUID.Zero;
- internal Encoding encoding = Rest.Encoding;
- internal Uri uri = null;
- internal string query = null;
- internal bool fail = false;
- internal string hostname = "localhost";
- internal int port = 80;
- internal string prefix = Rest.UrlPathSeparator;
- // Authentication related state
-
- internal bool authenticated = false;
- internal string scheme = Rest.AS_DIGEST;
- 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;
- internal LLUUID client = LLUUID.Zero;
- // XML related state
- internal XmlWriter writer = null;
- internal XmlReader reader = null;
- // Internal working state
- private StringBuilder sbuilder = new StringBuilder(1024);
- private MemoryStream xmldata = null;
- private static readonly string[] EmptyPath = { 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>\\S+)\"",
- 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("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\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;
- // Constructor
-
- internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix)
- {
- request = p_request;
- response = p_response;
- sbuilder.Length = 0;
- encoding = request.ContentEncoding;
- if (encoding == null)
- {
- encoding = Rest.Encoding;
- }
- method = request.HttpMethod.ToLower();
- initUrl();
- initParameters(qprefix.Length);
- }
- // Just for convenience...
- internal string MsgId
- {
- get { return Rest.MsgId; }
- }
- // Defer authentication check until requested
- internal bool IsAuthenticated
- {
- get
- {
- if (Rest.Authenticate)
- {
- if (!authenticated)
- {
- authenticate();
- }
- return authenticated;
- }
- else return true;
- }
- }
- /// <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.
- /// </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.
- if (scheme != null && scheme.ToLower() != reqscheme)
- {
- Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", 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, Rest.HttpStatusDescNotAuthorized);
- }
- /// <summary>
- /// Interpret a BASIC authorization claim
- /// This is here for completeness, it is not used.
- /// </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 is an RFC2617 compliant HTTP MD5 Digest authentication
- /// implementation. It has been tested with Firefox, Java HTTP client,
- /// and Miscrosoft's Internet Explorer V7.
- /// </summary>
- private void DoDigest(string authdata)
- {
- string response = null;
- MatchCollection matches = digestParm1.Matches(authdata);
- // Collect all of the supplied parameters and store them
- // in a dictionary (for ease of access)
- 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);
- }
- // And pick up any tokens too
- 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 lcient returns it
- // to us, as it should.
- if (!authparms.TryGetValue("nonce", out nonce))
- {
- 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);
- break;
- }
- cnonce = authparms["cnonce"];
- if (!authparms.ContainsKey("nc"))
- {
- Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
- break;
- }
- nck = authparms["nc"];
- if (cntable.TryGetValue(cnonce, out ncl))
- {
- if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck))
- {
- Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
- break;
- }
- cntable[cnonce] = nck;
- }
- else
- {
- lock (cntable) cntable.Add(cnonce, 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);
- break;
- }
- if (authparms.ContainsKey("nc"))
- {
- Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
- break;
- }
- }
- // Validate the supplied userid/password info
- authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
- }
- while (false);
- }
- }
- // Indicate that authentication is required
- internal void Challenge(string scheme, string realm, string domain, string nonce,
- string opaque, string stale, string alg,
- string qop, string auth)
- {
- sbuilder.Length = 0;
- if (scheme == null || scheme == Rest.AS_DIGEST)
- {
- sbuilder.Append(Rest.AS_DIGEST);
- sbuilder.Append(" ");
- if (realm != null)
- {
- sbuilder.Append("realm=");
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(realm);
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (nonce != null)
- {
- sbuilder.Append("nonce=");
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(nonce);
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (opaque != null)
- {
- sbuilder.Append("opaque=");
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(opaque);
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (stale != null)
- {
- sbuilder.Append("stale=");
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(stale);
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (alg != null)
- {
- sbuilder.Append("algorithm=");
- sbuilder.Append(alg);
- sbuilder.Append(Rest.CS_COMMA);
- }
- 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);
- }
- if (auth != null)
- {
- sbuilder.Append(auth);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (Rest.Domains.Count != 0)
- {
- sbuilder.Append("domain=");
- sbuilder.Append(Rest.CS_DQUOTE);
- foreach (string dom in Rest.Domains.Values)
- {
- sbuilder.Append(dom);
- sbuilder.Append(Rest.CS_SPACE);
- }
- if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE)
- {
- sbuilder.Length = sbuilder.Length-1;
- }
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
- if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA)
- {
- sbuilder.Length = sbuilder.Length-1;
- }
- AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
- }
- if (scheme == null || scheme == Rest.AS_BASIC)
- {
- sbuilder.Append(Rest.AS_BASIC);
- if (realm != null)
- {
- sbuilder.Append(" realm=\"");
- sbuilder.Append(realm);
- sbuilder.Append("\"");
- }
- AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
- }
- }
- private bool Validate(string user, string pass)
- {
- Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
- return user == "awebb" && pass == getPassword(user);
- }
- private string getPassword(string user)
- {
- return Rest.GodKey;
- }
- // 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();
- }
- internal void Complete()
- {
- statusCode = Rest.HttpStatusCodeOK;
- statusDescription = Rest.HttpStatusDescOK;
- }
- internal void Redirect(string Url, bool temp)
- {
- redirectLocation = Url;
- if (temp)
- {
- statusCode = Rest.HttpStatusCodeTemporaryRedirect;
- statusDescription = Rest.HttpStatusDescTemporaryRedirect;
- }
- else
- {
- statusCode = Rest.HttpStatusCodePermanentRedirect;
- statusDescription = Rest.HttpStatusDescPermanentRedirect;
- }
- Fail(statusCode, statusDescription, true);
- }
- // Fail for an arbitrary reason. Just a failure with
- // headers.
- internal void Fail(int code, string message)
- {
- Fail(code, message, true);
- }
- // More adventurous. This failure also includes a
- // specified entity.
- internal void Fail(int code, string message, string data)
- {
- buffer = null;
- body = data;
- Fail(code, message, false);
- }
- internal void Fail(int code, string message, bool reset)
- {
- statusCode = code;
- statusDescription = message;
- if (reset)
- {
- buffer = null;
- body = null;
- }
- if (Rest.DEBUG)
- {
- 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("Failure response");
-
- RestException re = new RestException(message+" <"+code+">");
- re.statusCode = code;
- re.statusDesc = message;
- re.httpmethod = method;
- re.httppath = path;
- throw re;
- }
- // Reject this request
- internal void Reject()
- {
- Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented);
- }
- // 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);
- if (!handled)
- {
- Rest.Log.DebugFormat("{0} Generating Response", MsgId);
- // Process any arbitrary headers collected
- BuildHeaders();
- // A Head request can NOT have a body!
- if (method != Rest.HEAD)
- {
- Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
- 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, then we assume that
- // this has already been done some other
- // way. For example, transfer encoding might
- // have been done.
- if (buffer == null)
- {
- if (body != null && body.Length > 0)
- {
- Rest.Log.DebugFormat("{0} String-based entity", MsgId);
- buffer = encoding.GetBytes(body);
- }
- }
- if (buffer != null)
- {
- Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
- if (response.Headers.Get("Content-Encoding") == null)
- response.ContentEncoding = encoding;
- response.ContentLength64 = buffer.Length;
- response.SendChunked = false;
- response.KeepAlive = false;
- }
- }
- // Set the status code & description. If nothing
- // has been stored, we consider that a success
- if (statusCode == 0)
- {
- Complete();
- }
- response.StatusCode = statusCode;
- if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily ||
- response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
- {
- response.RedirectLocation = redirectLocation;
- }
- if (statusDescription != null)
- {
- response.StatusDescription = statusDescription;
- }
- // Finally we send back our response, consuming
- // any exceptions that doing so might produce.
- // 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;
- if (buffer != null && buffer.Length != 0)
- {
- Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
- MsgId, buffer.Length, encoding.GetString(buffer));
- response.OutputStream.Write(buffer, 0, buffer.Length);
- }
- response.OutputStream.Close();
- if (request.InputStream != null)
- {
- request.InputStream.Close();
- }
- }
- Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
- return handled;
- }
- // Add a header to the table. If the header
- // already exists, it is replaced.
- internal void AddHeader(string hdr, string data)
- {
- if (headers == null)
- {
- headers = new NameValueCollection();
- }
- headers[hdr] = data;
- }
- // Keep explicit track of any headers which
- // are to be removed.
- internal void RemoveHeader(string hdr)
- {
- if (removed_headers == null)
- {
- removed_headers = new List<string>();
- }
- removed_headers.Add(hdr);
- if (headers != null)
- {
- headers.Remove(hdr);
- }
- }
- // Should it prove necessary, we could always
- // restore the header collection from a cloned
- // copy, but for now we'll assume that that is
- // not necessary.
- private void BuildHeaders()
- {
- if (removed_headers != null)
- {
- foreach (string h in removed_headers)
- {
- Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
- response.Headers.Remove(h);
- }
- }
- if (headers!= null)
- {
- for (int i = 0; i < headers.Count; i++)
- {
- Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
- MsgId, headers.GetKey(i), headers.Get(i));
- response.Headers.Add(headers.GetKey(i), headers.Get(i));
- }
- }
- }
- /// <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);
- path = Uri.UnescapeDataString(path);
- }
- // 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;
- }
- // Request server context info
- hostname = uri.Host;
- port = uri.Port;
- }
- internal 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;
- }
-
- internal string[] PathNodes
- {
- get
- {
- if (pathNodes == null)
- {
- initUrl();
- }
- return pathNodes;
- }
- }
-
- internal string BuildUrl(int first, int last)
- {
-
- if (pathNodes == null)
- {
- initUrl();
- }
- if (first < 0)
- {
- first = first + pathNodes.Length;
- }
- if (last < 0)
- {
- last = last + pathNodes.Length;
- if (last < 0)
- {
- return Rest.UrlPathSeparator;
- }
- }
- sbuilder.Length = 0;
- sbuilder.Append(Rest.UrlPathSeparator);
- if (first <= last)
- {
- for (int i = first; i <= last; i++)
- {
- sbuilder.Append(pathNodes[i]);
- sbuilder.Append(Rest.UrlPathSeparator);
- }
- }
- else
- {
- for (int i = last; i >= first; i--)
- {
- sbuilder.Append(pathNodes[i]);
- sbuilder.Append(Rest.UrlPathSeparator);
- }
- }
- return sbuilder.ToString();
- }
- // 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(new StringReader(entity),settings);
- reader = XmlReader.Create(request.InputStream,settings);
- }
- private void Flush()
- {
- byte[] dbuffer = new byte[8192];
- while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
- return;
- }
- // This allows us to make errors a bit more apparent in REST
- internal void SendHtml(string text)
- {
- SendHtml("OpenSim REST Interface 1.0", text);
- }
- internal void SendHtml(string title, string text)
- {
- AddHeader(Rest.HttpHeaderContentType, "text/html");
- sbuilder.Length = 0;
- sbuilder.Append("<html>");
- sbuilder.Append("<head>");
- sbuilder.Append("<title>");
- sbuilder.Append(title);
- sbuilder.Append("</title>");
- sbuilder.Append("</head>");
- sbuilder.Append("<body>");
- sbuilder.Append("<br />");
- sbuilder.Append("<p>");
- sbuilder.Append(text);
- sbuilder.Append("</p>");
- sbuilder.Append("</body>");
- sbuilder.Append("</html>");
- html = sbuilder.ToString();
- }
- }
- }
|