529 lines
22 KiB
C#
529 lines
22 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.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Framework.Servers;
|
|
using OpenSim.Framework.Communications;
|
|
using OpenSim.Framework.Communications.Cache;
|
|
using Nini.Config;
|
|
|
|
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
|
{
|
|
public class Rest
|
|
{
|
|
internal static readonly log4net.ILog Log =
|
|
log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
internal static bool DEBUG = Log.IsDebugEnabled;
|
|
|
|
/// <summary>
|
|
/// Supported authentication schemes
|
|
/// </summary>
|
|
|
|
public const string AS_BASIC = "Basic"; // simple user/password verification
|
|
public const string AS_DIGEST = "Digest"; // password safe authentication
|
|
|
|
/// Supported Digest algorithms
|
|
|
|
public const string Digest_MD5 = "MD5"; // assumed default if omitted
|
|
public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
|
|
|
|
public const string Qop_Auth = "auth"; // authentication only
|
|
public const string Qop_Int = "auth-int"; // TODO
|
|
|
|
/// <summary>
|
|
/// These values have a single value for the whole
|
|
/// domain and lifetime of the plugin handler. We
|
|
/// make them static for ease of reference within
|
|
/// the assembly. These are initialized by the
|
|
/// RestHandler class during start-up.
|
|
/// </summary>
|
|
|
|
internal static IRestHandler Plugin = null;
|
|
internal static OpenSimBase main = null;
|
|
internal static CommunicationsManager Comms = null;
|
|
internal static IInventoryServices InventoryServices = null;
|
|
internal static IUserService UserServices = null;
|
|
internal static IAvatarService AvatarServices = null;
|
|
internal static IAssetCache AssetServices = null;
|
|
internal static string Prefix = null;
|
|
internal static IConfig Config = null;
|
|
internal static string GodKey = null;
|
|
internal static bool Authenticate = true;
|
|
internal static bool Secure = true;
|
|
internal static bool ExtendedEscape = true;
|
|
internal static bool DumpAsset = false;
|
|
internal static bool Fill = false;
|
|
internal static bool FlushEnabled = true;
|
|
internal static string Realm = "OpenSim REST";
|
|
internal static string Scheme = AS_BASIC;
|
|
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
|
|
|
|
/// <summary>
|
|
/// HTTP requires that status information be generated for PUT
|
|
/// and POST opertaions. This is in support of that. The
|
|
/// operation verb gets substituted into the first string,
|
|
/// and the completion code is inserted into the tail. The
|
|
/// strings are put here to encourage consistency.
|
|
/// </summary>
|
|
|
|
internal static string statusHead = "<html><body><title>{0} status</title><break>";
|
|
internal static string statusTail = "</body></html>";
|
|
|
|
internal static Dictionary<int,string> HttpStatusDesc;
|
|
|
|
static Rest()
|
|
{
|
|
HttpStatusDesc = new Dictionary<int,string>();
|
|
if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length)
|
|
{
|
|
Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match");
|
|
throw new Exception("HTTP Status array discrepancy");
|
|
}
|
|
|
|
// Repackage the data into something more tractable. The sparse
|
|
// nature of HTTP return codes makes an array a bad choice.
|
|
|
|
for (int i=0; i<HttpStatusCodeArray.Length; i++)
|
|
{
|
|
HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]);
|
|
}
|
|
}
|
|
|
|
internal static int CreationDate
|
|
{
|
|
get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; }
|
|
}
|
|
|
|
internal static string MsgId
|
|
{
|
|
get { return Plugin.MsgId; }
|
|
}
|
|
|
|
internal static string RequestId
|
|
{
|
|
get { return Plugin.RequestId; }
|
|
}
|
|
|
|
internal static Encoding Encoding = Encoding.UTF8;
|
|
|
|
/// <summary>
|
|
/// Version control for REST implementation. This
|
|
/// refers to the overall infrastructure represented
|
|
/// by the following classes
|
|
/// RequestData
|
|
/// RequestInventoryPlugin
|
|
/// Rest
|
|
/// It does no describe implementation classes such as
|
|
/// RestInventoryServices, which may morph much more
|
|
/// often. Such classes ARE dependent upon this however
|
|
/// and should check it in their Initialize method.
|
|
/// </summary>
|
|
|
|
public static readonly float Version = 1.0F;
|
|
public const string Name = "REST 1.0";
|
|
|
|
/// <summary>
|
|
/// Currently defined HTTP methods.
|
|
/// Only GET and HEAD are required to be
|
|
/// supported by all servers. See Respond
|
|
/// to see how these are handled.
|
|
/// </summary>
|
|
|
|
// REST AGENT 1.0 interpretations
|
|
public const string GET = "get"; // information retrieval - server state unchanged
|
|
public const string HEAD = "head"; // same as get except only the headers are returned.
|
|
public const string POST = "post"; // Replace the URI designated resource with the entity.
|
|
public const string PUT = "put"; // Add the entity to the context represented by the URI
|
|
public const string DELETE = "delete"; // Remove the URI designated resource from the server.
|
|
|
|
public const string OPTIONS = "options"; //
|
|
public const string TRACE = "trace"; //
|
|
public const string CONNECT = "connect"; //
|
|
|
|
// Define this in one place...
|
|
|
|
public const string UrlPathSeparator = "/";
|
|
public const string UrlMethodSeparator = ":";
|
|
|
|
// Redirection qualifications
|
|
|
|
public const bool PERMANENT = false;
|
|
public const bool TEMPORARY = true;
|
|
|
|
// Constant arrays used by String.Split
|
|
|
|
public static readonly char C_SPACE = ' ';
|
|
public static readonly char C_SLASH = '/';
|
|
public static readonly char C_PATHSEP = '/';
|
|
public static readonly char C_COLON = ':';
|
|
public static readonly char C_PLUS = '+';
|
|
public static readonly char C_PERIOD = '.';
|
|
public static readonly char C_COMMA = ',';
|
|
public static readonly char C_DQUOTE = '"';
|
|
|
|
public static readonly string CS_SPACE = " ";
|
|
public static readonly string CS_SLASH = "/";
|
|
public static readonly string CS_PATHSEP = "/";
|
|
public static readonly string CS_COLON = ":";
|
|
public static readonly string CS_PLUS = "+";
|
|
public static readonly string CS_PERIOD = ".";
|
|
public static readonly string CS_COMMA = ",";
|
|
public static readonly string CS_DQUOTE = "\"";
|
|
|
|
public static readonly char[] CA_SPACE = { C_SPACE };
|
|
public static readonly char[] CA_SLASH = { C_SLASH };
|
|
public static readonly char[] CA_PATHSEP = { C_PATHSEP };
|
|
public static readonly char[] CA_COLON = { C_COLON };
|
|
public static readonly char[] CA_PERIOD = { C_PERIOD };
|
|
public static readonly char[] CA_PLUS = { C_PLUS };
|
|
public static readonly char[] CA_COMMA = { C_COMMA };
|
|
public static readonly char[] CA_DQUOTE = { C_DQUOTE };
|
|
|
|
// HTTP Code Values (in value order)
|
|
|
|
public const int HttpStatusCodeContinue = 100;
|
|
public const int HttpStatusCodeSwitchingProtocols = 101;
|
|
|
|
public const int HttpStatusCodeOK = 200;
|
|
public const int HttpStatusCodeCreated = 201;
|
|
public const int HttpStatusCodeAccepted = 202;
|
|
public const int HttpStatusCodeNonAuthoritative = 203;
|
|
public const int HttpStatusCodeNoContent = 204;
|
|
public const int HttpStatusCodeResetContent = 205;
|
|
public const int HttpStatusCodePartialContent = 206;
|
|
|
|
public const int HttpStatusCodeMultipleChoices = 300;
|
|
public const int HttpStatusCodePermanentRedirect = 301;
|
|
public const int HttpStatusCodeFound = 302;
|
|
public const int HttpStatusCodeSeeOther = 303;
|
|
public const int HttpStatusCodeNotModified = 304;
|
|
public const int HttpStatusCodeUseProxy = 305;
|
|
public const int HttpStatusCodeReserved306 = 306;
|
|
public const int HttpStatusCodeTemporaryRedirect = 307;
|
|
|
|
public const int HttpStatusCodeBadRequest = 400;
|
|
public const int HttpStatusCodeNotAuthorized = 401;
|
|
public const int HttpStatusCodePaymentRequired = 402;
|
|
public const int HttpStatusCodeForbidden = 403;
|
|
public const int HttpStatusCodeNotFound = 404;
|
|
public const int HttpStatusCodeMethodNotAllowed = 405;
|
|
public const int HttpStatusCodeNotAcceptable = 406;
|
|
public const int HttpStatusCodeProxyAuthenticate = 407;
|
|
public const int HttpStatusCodeTimeOut = 408;
|
|
public const int HttpStatusCodeConflict = 409;
|
|
public const int HttpStatusCodeGone = 410;
|
|
public const int HttpStatusCodeLengthRequired = 411;
|
|
public const int HttpStatusCodePreconditionFailed = 412;
|
|
public const int HttpStatusCodeEntityTooLarge = 413;
|
|
public const int HttpStatusCodeUriTooLarge = 414;
|
|
public const int HttpStatusCodeUnsupportedMedia = 415;
|
|
public const int HttpStatusCodeRangeNotSatsified = 416;
|
|
public const int HttpStatusCodeExpectationFailed = 417;
|
|
|
|
public const int HttpStatusCodeServerError = 500;
|
|
public const int HttpStatusCodeNotImplemented = 501;
|
|
public const int HttpStatusCodeBadGateway = 502;
|
|
public const int HttpStatusCodeServiceUnavailable = 503;
|
|
public const int HttpStatusCodeGatewayTimeout = 504;
|
|
public const int HttpStatusCodeHttpVersionError = 505;
|
|
|
|
public static readonly int[] HttpStatusCodeArray = {
|
|
HttpStatusCodeContinue,
|
|
HttpStatusCodeSwitchingProtocols,
|
|
HttpStatusCodeOK,
|
|
HttpStatusCodeCreated,
|
|
HttpStatusCodeAccepted,
|
|
HttpStatusCodeNonAuthoritative,
|
|
HttpStatusCodeNoContent,
|
|
HttpStatusCodeResetContent,
|
|
HttpStatusCodePartialContent,
|
|
HttpStatusCodeMultipleChoices,
|
|
HttpStatusCodePermanentRedirect,
|
|
HttpStatusCodeFound,
|
|
HttpStatusCodeSeeOther,
|
|
HttpStatusCodeNotModified,
|
|
HttpStatusCodeUseProxy,
|
|
HttpStatusCodeReserved306,
|
|
HttpStatusCodeTemporaryRedirect,
|
|
HttpStatusCodeBadRequest,
|
|
HttpStatusCodeNotAuthorized,
|
|
HttpStatusCodePaymentRequired,
|
|
HttpStatusCodeForbidden,
|
|
HttpStatusCodeNotFound,
|
|
HttpStatusCodeMethodNotAllowed,
|
|
HttpStatusCodeNotAcceptable,
|
|
HttpStatusCodeProxyAuthenticate,
|
|
HttpStatusCodeTimeOut,
|
|
HttpStatusCodeConflict,
|
|
HttpStatusCodeGone,
|
|
HttpStatusCodeLengthRequired,
|
|
HttpStatusCodePreconditionFailed,
|
|
HttpStatusCodeEntityTooLarge,
|
|
HttpStatusCodeUriTooLarge,
|
|
HttpStatusCodeUnsupportedMedia,
|
|
HttpStatusCodeRangeNotSatsified,
|
|
HttpStatusCodeExpectationFailed,
|
|
HttpStatusCodeServerError,
|
|
HttpStatusCodeNotImplemented,
|
|
HttpStatusCodeBadGateway,
|
|
HttpStatusCodeServiceUnavailable,
|
|
HttpStatusCodeGatewayTimeout,
|
|
HttpStatusCodeHttpVersionError
|
|
};
|
|
|
|
// HTTP Status Descriptions (in status code order)
|
|
// This array must be kept strictly consistent with respect
|
|
// to the status code array above.
|
|
|
|
public static readonly string[] HttpStatusDescArray = {
|
|
"Continue Request",
|
|
"Switching Protocols",
|
|
"OK",
|
|
"CREATED",
|
|
"ACCEPTED",
|
|
"NON-AUTHORITATIVE INFORMATION",
|
|
"NO CONTENT",
|
|
"RESET CONTENT",
|
|
"PARTIAL CONTENT",
|
|
"MULTIPLE CHOICES",
|
|
"PERMANENT REDIRECT",
|
|
"FOUND",
|
|
"SEE OTHER",
|
|
"NOT MODIFIED",
|
|
"USE PROXY",
|
|
"RESERVED CODE 306",
|
|
"TEMPORARY REDIRECT",
|
|
"BAD REQUEST",
|
|
"NOT AUTHORIZED",
|
|
"PAYMENT REQUIRED",
|
|
"FORBIDDEN",
|
|
"NOT FOUND",
|
|
"METHOD NOT ALLOWED",
|
|
"NOT ACCEPTABLE",
|
|
"PROXY AUTHENTICATION REQUIRED",
|
|
"TIMEOUT",
|
|
"CONFLICT",
|
|
"GONE",
|
|
"LENGTH REQUIRED",
|
|
"PRECONDITION FAILED",
|
|
"ENTITY TOO LARGE",
|
|
"URI TOO LARGE",
|
|
"UNSUPPORTED MEDIA",
|
|
"RANGE NOT SATISFIED",
|
|
"EXPECTATION FAILED",
|
|
"SERVER ERROR",
|
|
"NOT IMPLEMENTED",
|
|
"BAD GATEWAY",
|
|
"SERVICE UNAVAILABLE",
|
|
"GATEWAY TIMEOUT",
|
|
"HTTP VERSION NOT SUPPORTED"
|
|
};
|
|
|
|
// HTTP Headers
|
|
|
|
public const string HttpHeaderAccept = "Accept";
|
|
public const string HttpHeaderAcceptCharset = "Accept-Charset";
|
|
public const string HttpHeaderAcceptEncoding = "Accept-Encoding";
|
|
public const string HttpHeaderAcceptLanguage = "Accept-Language";
|
|
public const string HttpHeaderAcceptRanges = "Accept-Ranges";
|
|
public const string HttpHeaderAge = "Age";
|
|
public const string HttpHeaderAllow = "Allow";
|
|
public const string HttpHeaderAuthorization = "Authorization";
|
|
public const string HttpHeaderCacheControl = "Cache-Control";
|
|
public const string HttpHeaderConnection = "Connection";
|
|
public const string HttpHeaderContentEncoding = "Content-Encoding";
|
|
public const string HttpHeaderContentLanguage = "Content-Language";
|
|
public const string HttpHeaderContentLength = "Content-Length";
|
|
public const string HttpHeaderContentLocation = "Content-Location";
|
|
public const string HttpHeaderContentMD5 = "Content-MD5";
|
|
public const string HttpHeaderContentRange = "Content-Range";
|
|
public const string HttpHeaderContentType = "Content-Type";
|
|
public const string HttpHeaderDate = "Date";
|
|
public const string HttpHeaderETag = "ETag";
|
|
public const string HttpHeaderExpect = "Expect";
|
|
public const string HttpHeaderExpires = "Expires";
|
|
public const string HttpHeaderFrom = "From";
|
|
public const string HttpHeaderHost = "Host";
|
|
public const string HttpHeaderIfMatch = "If-Match";
|
|
public const string HttpHeaderIfModifiedSince = "If-Modified-Since";
|
|
public const string HttpHeaderIfNoneMatch = "If-None-Match";
|
|
public const string HttpHeaderIfRange = "If-Range";
|
|
public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since";
|
|
public const string HttpHeaderLastModified = "Last-Modified";
|
|
public const string HttpHeaderLocation = "Location";
|
|
public const string HttpHeaderMaxForwards = "Max-Forwards";
|
|
public const string HttpHeaderPragma = "Pragma";
|
|
public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate";
|
|
public const string HttpHeaderProxyAuthorization = "Proxy-Authorization";
|
|
public const string HttpHeaderRange = "Range";
|
|
public const string HttpHeaderReferer = "Referer";
|
|
public const string HttpHeaderRetryAfter = "Retry-After";
|
|
public const string HttpHeaderServer = "Server";
|
|
public const string HttpHeaderTE = "TE";
|
|
public const string HttpHeaderTrailer = "Trailer";
|
|
public const string HttpHeaderTransferEncoding = "Transfer-Encoding";
|
|
public const string HttpHeaderUpgrade = "Upgrade";
|
|
public const string HttpHeaderUserAgent = "User-Agent";
|
|
public const string HttpHeaderVary = "Vary";
|
|
public const string HttpHeaderVia = "Via";
|
|
public const string HttpHeaderWarning = "Warning";
|
|
public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
|
|
|
|
/// Utility routines
|
|
|
|
public static string StringToBase64(string str)
|
|
{
|
|
try
|
|
{
|
|
byte[] encData_byte = new byte[str.Length];
|
|
encData_byte = Encoding.UTF8.GetBytes(str);
|
|
return Convert.ToBase64String(encData_byte);
|
|
}
|
|
catch
|
|
{
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
public static string Base64ToString(string str)
|
|
{
|
|
UTF8Encoding encoder = new UTF8Encoding();
|
|
Decoder utf8Decode = encoder.GetDecoder();
|
|
try
|
|
{
|
|
byte[] todecode_byte = Convert.FromBase64String(str);
|
|
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
|
|
char[] decoded_char = new char[charCount];
|
|
utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
|
|
return new String(decoded_char);
|
|
}
|
|
catch
|
|
{
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
private const string hvals = "0123456789abcdef";
|
|
|
|
public static int Hex2Int(string hex)
|
|
{
|
|
int val = 0;
|
|
int sum = 0;
|
|
string tmp = null;
|
|
|
|
if (hex != null)
|
|
{
|
|
tmp = hex.ToLower();
|
|
for (int i = 0; i < tmp.Length; i++)
|
|
{
|
|
val = hvals.IndexOf(tmp[i]);
|
|
if (val == -1)
|
|
break;
|
|
sum *= 16;
|
|
sum += val;
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
// Nonce management
|
|
|
|
public static string NonceGenerator()
|
|
{
|
|
return StringToBase64(CreationDate + Guid.NewGuid().ToString());
|
|
}
|
|
|
|
// Dump the specified data stream
|
|
|
|
public static void Dump(byte[] data)
|
|
{
|
|
char[] buffer = new char[Rest.DumpLineSize];
|
|
int cc = 0;
|
|
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
|
|
|
|
if (i % 4 == 0) Console.Write(" ");
|
|
|
|
Console.Write("{0}",data[i].ToString("x2"));
|
|
|
|
if (data[i] < 127 && data[i] > 31)
|
|
buffer[i % Rest.DumpLineSize] = (char) data[i];
|
|
else
|
|
buffer[i % Rest.DumpLineSize] = '.';
|
|
|
|
cc++;
|
|
|
|
if (i != 0 && (i + 1) % Rest.DumpLineSize == 0)
|
|
{
|
|
Console.Write(" |"+(new String(buffer))+"|");
|
|
cc = 0;
|
|
}
|
|
}
|
|
|
|
// Finish off any incomplete line
|
|
|
|
if (cc != 0)
|
|
{
|
|
for (int i = cc ; i < Rest.DumpLineSize; i++)
|
|
{
|
|
if (i % 4 == 0) Console.Write(" ");
|
|
Console.Write(" ");
|
|
buffer[i % Rest.DumpLineSize] = ' ';
|
|
}
|
|
Console.WriteLine(" |"+(new String(buffer))+"|");
|
|
}
|
|
else
|
|
{
|
|
Console.Write("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Local exception type
|
|
|
|
public class RestException : Exception
|
|
{
|
|
internal int statusCode;
|
|
internal string statusDesc;
|
|
internal string httpmethod;
|
|
internal string httppath;
|
|
|
|
public RestException(string msg) : base(msg)
|
|
{
|
|
}
|
|
}
|
|
}
|