OpenSimMirror/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs

1202 lines
40 KiB
C#

/*
* 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();
}
}
}