diff --git a/OpenSim/Framework/Serialization/ArchiveConstants.cs b/OpenSim/Framework/Serialization/ArchiveConstants.cs index 9081411129..d3557a714e 100644 --- a/OpenSim/Framework/Serialization/ArchiveConstants.cs +++ b/OpenSim/Framework/Serialization/ArchiveConstants.cs @@ -125,7 +125,8 @@ namespace OpenSim.Framework.Serialization ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.SoundWAV] = ASSET_EXTENSION_SEPARATOR + "sound.wav"; ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Texture] = ASSET_EXTENSION_SEPARATOR + "texture.jp2"; ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.TextureTGA] = ASSET_EXTENSION_SEPARATOR + "texture.tga"; - ASSET_TYPE_TO_EXTENSION[(sbyte)OpenSimAssetType.Material] = ASSET_EXTENSION_SEPARATOR + "material.xml"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)OpenSimAssetType.Material] = ASSET_EXTENSION_SEPARATOR + "material.xml"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Settings] = ASSET_EXTENSION_SEPARATOR + "settings.bin"; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "animation.bvh"] = (sbyte)AssetType.Animation; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bodypart.txt"] = (sbyte)AssetType.Bodypart; @@ -147,6 +148,7 @@ namespace OpenSim.Framework.Serialization EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.jp2"] = (sbyte)AssetType.Texture; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.tga"] = (sbyte)AssetType.TextureTGA; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "material.xml"] = (sbyte)OpenSimAssetType.Material; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "settings.bin"] = (sbyte)AssetType.Settings; } public static string CreateOarLandDataPath(LandData ld) diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index e108eb58f7..188042df14 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -28,27 +28,24 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Reflection; using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Reflection; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Threading; using System.Xml; -using HttpServer; +using OSHttpServer; +using tinyHTTPListener = OSHttpServer.OSHttpListener; using log4net; using Nwc.XmlRpc; -using OpenMetaverse.StructuredData; -using CoolHTTPListener = HttpServer.HttpListener; -using LogPrio = HttpServer.LogPrio; using OpenSim.Framework.Monitoring; -using System.IO.Compression; -using System.Security.Cryptography; -using OpenSim.Framework.Servers; +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Servers.HttpServer { @@ -94,8 +91,7 @@ namespace OpenSim.Framework.Servers.HttpServer private volatile int NotSocketErrors = 0; public volatile bool HTTPDRunning = false; - // protected HttpListener m_httpListener; - protected CoolHTTPListener m_httpListener2; + protected tinyHTTPListener m_httpListener; protected Dictionary m_rpcHandlers = new Dictionary(); protected Dictionary jsonRpcHandlers = new Dictionary(); protected Dictionary m_rpcHandlersKeepAlive = new Dictionary(); @@ -121,7 +117,6 @@ namespace OpenSim.Framework.Servers.HttpServer protected IPAddress m_listenIPAddress = IPAddress.Any; - public string Protocol { get { return m_ssl ? "https://" : "http://"; } @@ -1243,7 +1238,7 @@ namespace OpenSim.Framework.Servers.HttpServer rcn = "SSLCN:" + rcn; xmlRprcRequest.Params.Add(rcn); // Param[4] or Param[5] } - + try { xmlRpcResponse = method(xmlRprcRequest, request.RemoteIPEndPoint); @@ -1955,7 +1950,7 @@ namespace OpenSim.Framework.Servers.HttpServer if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) { - response.RedirectLocation = (string)responsedata["str_redirect_location"]; + response.AddHeader("Location:", (string)responsedata["str_redirect_location"]); response.StatusCode = responsecode; } @@ -2051,32 +2046,32 @@ namespace OpenSim.Framework.Servers.HttpServer NotSocketErrors = 0; if (!m_ssl) { - //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); - //m_httpListener.Prefixes.Add("http://10.1.1.5:" + m_port + "/"); - m_httpListener2 = CoolHTTPListener.Create(m_listenIPAddress, (int)m_port); - m_httpListener2.ExceptionThrown += httpServerException; - m_httpListener2.LogWriter = httpserverlog; - + m_httpListener = tinyHTTPListener.Create(m_listenIPAddress, (int)m_port); + m_httpListener.ExceptionThrown += httpServerException; + if (DebugLevel > 0) + { + m_httpListener.LogWriter = httpserverlog; + httpserverlog.DebugLevel = 1; + } // Uncomment this line in addition to those in HttpServerLogWriter // if you want more detailed trace information from the HttpServer - //m_httpListener2.UseTraceLogs = true; - //m_httpListener2.DisconnectHandler = httpServerDisconnectMonitor; } else { - //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); - //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); - m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); + m_httpListener = tinyHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); if(m_certificateValidationCallback != null) - m_httpListener2.CertificateValidationCallback = m_certificateValidationCallback; - m_httpListener2.ExceptionThrown += httpServerException; - m_httpListener2.LogWriter = httpserverlog; + m_httpListener.CertificateValidationCallback = m_certificateValidationCallback; + m_httpListener.ExceptionThrown += httpServerException; + if (DebugLevel > 0) + { + m_httpListener.LogWriter = httpserverlog; + httpserverlog.DebugLevel = 1; + } } - m_httpListener2.RequestReceived += OnRequest; - //m_httpListener.Start(); - m_httpListener2.Start(64); + m_httpListener.RequestReceived += OnRequest; + m_httpListener.Start(64); lock(m_generalLock) { @@ -2089,13 +2084,6 @@ namespace OpenSim.Framework.Servers.HttpServer } HTTPDRunning = true; - - //HttpListenerContext context; - //while (true) - //{ - // context = m_httpListener.GetContext(); - // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(HandleRequest), context); - // } } catch (Exception e) { @@ -2155,12 +2143,12 @@ namespace OpenSim.Framework.Servers.HttpServer m_pollServiceManager.Stop(); } - m_httpListener2.ExceptionThrown -= httpServerException; + m_httpListener.ExceptionThrown -= httpServerException; //m_httpListener2.DisconnectHandler = null; - m_httpListener2.LogWriter = null; - m_httpListener2.RequestReceived -= OnRequest; - m_httpListener2.Stop(); + m_httpListener.LogWriter = null; + m_httpListener.RequestReceived -= OnRequest; + m_httpListener.Stop(); } catch (NullReferenceException) { @@ -2305,13 +2293,17 @@ namespace OpenSim.Framework.Servers.HttpServer /// /// You may also be able to get additional trace information from HttpServer if you uncomment the UseTraceLogs /// property in StartHttp() for the HttpListener + /// public class HttpServerLogWriter : ILogWriter { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public int DebugLevel {get; set;} = (int)LogPrio.Error; public void Write(object source, LogPrio priority, string message) { - /* + if((int)priority < DebugLevel) + return; + switch (priority) { case LogPrio.Trace: @@ -2335,8 +2327,6 @@ namespace OpenSim.Framework.Servers.HttpServer default: break; } - */ - return; } } diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs index aba15b019c..ae1ce1e3d4 100644 --- a/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs @@ -92,17 +92,16 @@ namespace OpenSim.Framework.Servers.HttpServer Stream OutputStream { get; } string ProtocolVersion { get; set; } + int Priority { get; set; } + byte[] RawBuffer { get; set; } + int RawBufferStart { get; set; } + int RawBufferLen { get; set; } /// /// Return the output stream feeding the body. /// Stream Body { get; } - /// - /// Set a redirct location. - /// - string RedirectLocation { set; } - /// /// Chunk transfers. /// diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs deleted file mode 100644 index 2c2b47ded2..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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.Text.RegularExpressions; - -namespace OpenSim.Framework.Servers.HttpServer -{ - /// - /// Any OSHttpHandler must return one of the following results: - /// - /// - /// result code - /// meaning - /// - /// - /// Pass - /// handler did not process the request - /// - /// - /// Done - /// handler did process the request, OSHttpServer - /// can clean up and close the request - /// - /// - /// - public enum OSHttpHandlerResult - { - Unprocessed, - Pass, - Done, - } - - /// - /// An OSHttpHandler that matches on the "content-type" header can - /// supply an OSHttpContentTypeChecker delegate which will be - /// invoked by the request matcher in OSHttpRequestPump. - /// - /// true if the handler is interested in the content; - /// false otherwise - public delegate bool OSHttpContentTypeChecker(OSHttpRequest req); - - public abstract class OSHttpHandler - { - /// - /// Regular expression used to match against method of - /// the incoming HTTP request. If you want to match any string - /// either use '.*' or null. To match on the empty string use - /// '^$'. - /// - public virtual Regex Method - { - get { return _method; } - } - protected Regex _method; - - /// - /// Regular expression used to match against path of the - /// incoming HTTP request. If you want to match any string - /// either use '.*' or null. To match on the empty string use - /// '^$'. - /// - public virtual Regex Path - { - get { return _path; } - } - protected Regex _path; - - /// - /// Dictionary of (query name, regular expression) tuples, - /// allowing us to match on URI query fields. - /// - public virtual Dictionary Query - { - get { return _query; } - } - protected Dictionary _query; - - /// - /// Dictionary of (header name, regular expression) tuples, - /// allowing us to match on HTTP header fields. - /// - public virtual Dictionary Headers - { - get { return _headers; } - } - protected Dictionary _headers; - - /// - /// Dictionary of (header name, regular expression) tuples, - /// allowing us to match on HTTP header fields. - /// - /// - /// This feature is currently not implemented as it requires - /// (trivial) changes to HttpServer.HttpListener that have not - /// been implemented. - /// - public virtual Regex IPEndPointWhitelist - { - get { return _ipEndPointRegex; } - } - protected Regex _ipEndPointRegex; - - - /// - /// Base class constructor. - /// - /// null or path regex - /// null or dictionary of header - /// regexs - /// null or content type - /// regex - /// null or IP address regex - public OSHttpHandler(Regex method, Regex path, Dictionary query, - Dictionary headers, Regex contentType, Regex whitelist) - { - _method = method; - _path = path; - _query = query; - _ipEndPointRegex = whitelist; - - if (null == _headers && null != contentType) - { - _headers = new Dictionary(); - _headers.Add("content-type", contentType); - } - } - - - /// - /// Process an incoming OSHttpRequest that matched our - /// requirements. - /// - /// - /// OSHttpHandlerResult.Pass if we are after all not - /// interested in the request; OSHttpHandlerResult.Done if we - /// did process the request. - /// - public abstract OSHttpHandlerResult Process(OSHttpRequest request); - - public override string ToString() - { - StringWriter sw = new StringWriter(); - sw.WriteLine("{0}", base.ToString()); - sw.WriteLine(" method regex {0}", null == Method ? "null" : Method.ToString()); - sw.WriteLine(" path regex {0}", null == Path ? "null": Path.ToString()); - foreach (string tag in Headers.Keys) - { - sw.WriteLine(" header {0} : {1}", tag, Headers[tag].ToString()); - } - sw.WriteLine(" IP whitelist {0}", null == IPEndPointWhitelist ? "null" : IPEndPointWhitelist.ToString()); - sw.WriteLine(); - sw.Close(); - return sw.ToString(); - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs deleted file mode 100644 index 95dafcdd36..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using log4net; -using Nwc.XmlRpc; - -namespace OpenSim.Framework.Servers.HttpServer -{ - public delegate XmlRpcResponse OSHttpHttpProcessor(XmlRpcRequest request); - - public class OSHttpHttpHandler: OSHttpHandler - { - private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - // contains handler for processing HTTP Request - private GenericHTTPMethod _handler; - - /// - /// Instantiate an HTTP handler. - /// - /// a GenericHTTPMethod - /// null or HTTP method regex - /// null or path regex - /// null or dictionary with query regexs - /// null or dictionary with header - /// regexs - /// null or IP address whitelist - public OSHttpHttpHandler(GenericHTTPMethod handler, Regex method, Regex path, - Dictionary query, - Dictionary headers, Regex whitelist) - : base(method, path, query, headers, new Regex(@"^text/html", RegexOptions.IgnoreCase | RegexOptions.Compiled), - whitelist) - { - _handler = handler; - } - - /// - /// Instantiate an HTTP handler. - /// - /// a GenericHTTPMethod - public OSHttpHttpHandler(GenericHTTPMethod handler) - : this(handler, new Regex(@"^GET$", RegexOptions.IgnoreCase | RegexOptions.Compiled), null, null, null, null) - { - } - - /// - /// Invoked by OSHttpRequestPump. - /// - public override OSHttpHandlerResult Process(OSHttpRequest request) - { - // call handler method - Hashtable responseData = _handler(request.Query); - - int responseCode = (int)responseData["int_response_code"]; - string responseString = (string)responseData["str_response_string"]; - string contentType = (string)responseData["content_type"]; - - //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this - //and should check for NullReferenceExceptions - - if (string.IsNullOrEmpty(contentType)) - { - contentType = "text/html"; - } - - OSHttpResponse response = new OSHttpResponse(request); - - // We're forgoing the usual error status codes here because the client - // ignores anything but 200 and 301 - - response.StatusCode = (int)OSHttpStatusCode.SuccessOk; - - if (responseCode == (int)OSHttpStatusCode.RedirectMovedPermanently) - { - response.RedirectLocation = (string)responseData["str_redirect_location"]; - response.StatusCode = responseCode; - } - - response.AddHeader("Content-type", contentType); - - byte[] buffer; - - if (!contentType.Contains("image")) - { - buffer = Encoding.UTF8.GetBytes(responseString); - } - else - { - buffer = Convert.FromBase64String(responseString); - } - - response.SendChunked = false; - response.ContentLength64 = buffer.Length; - response.ContentEncoding = Encoding.UTF8; - - try - { - response.Body.Write(buffer, 0, buffer.Length); - } - catch (Exception ex) - { - _log.ErrorFormat("[OSHttpHttpHandler]: Error: {0}", ex.Message); - } - finally - { - response.Send(); - } - - return OSHttpHandlerResult.Done; - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs index 1a6b8cf5f9..2ffb7a96af 100644 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs @@ -34,7 +34,7 @@ using System.Net; using System.Reflection; using System.Text; using System.Web; -using HttpServer; +using OSHttpServer; using log4net; namespace OpenSim.Framework.Servers.HttpServer diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs deleted file mode 100644 index bdea278810..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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. - */ - -// #define DEBUGGING - -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading; -using log4net; -using HttpServer; - -namespace OpenSim.Framework.Servers.HttpServer -{ - /// - /// An OSHttpRequestPump fetches incoming OSHttpRequest objects - /// from the OSHttpRequestQueue and feeds them to all subscribed - /// parties. Each OSHttpRequestPump encapsulates one thread to do - /// the work and there is a fixed number of pumps for each - /// OSHttpServer object. - /// - public class OSHttpRequestPump - { - private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - protected OSHttpServer _server; - protected OSHttpRequestQueue _queue; - protected Thread _engine; - - private int _id; - - public string EngineID - { - get { return String.Format("{0} pump {1}", _server.EngineID, _id); } - } - - public OSHttpRequestPump(OSHttpServer server, OSHttpRequestQueue queue, int id) - { - _server = server; - _queue = queue; - _id = id; - - _engine = new Thread(new ThreadStart(Engine)); - _engine.IsBackground = true; - _engine.Start(); - _engine.Name = string.Format ("Engine:{0}",EngineID); - - ThreadTracker.Add(_engine); - } - - public static OSHttpRequestPump[] Pumps(OSHttpServer server, OSHttpRequestQueue queue, int poolSize) - { - OSHttpRequestPump[] pumps = new OSHttpRequestPump[poolSize]; - for (int i = 0; i < pumps.Length; i++) - { - pumps[i] = new OSHttpRequestPump(server, queue, i); - } - - return pumps; - } - - public void Start() - { - _engine = new Thread(new ThreadStart(Engine)); - _engine.IsBackground = true; - _engine.Start(); - _engine.Name = string.Format ("Engine:{0}",EngineID); - - ThreadTracker.Add(_engine); - } - - public void Engine() - { - OSHttpRequest req = null; - - while (true) - { - try - { - // dequeue an OSHttpRequest from OSHttpServer's - // request queue - req = _queue.Dequeue(); - - // get a copy of the list of registered handlers - List handlers = _server.OSHttpHandlers; - - // prune list and have it sorted from most - // specific to least specific - handlers = MatchHandlers(req, handlers); - - // process req: we try each handler in turn until - // we are either out of handlers or get back a - // Pass or Done - OSHttpHandlerResult rc = OSHttpHandlerResult.Unprocessed; - foreach (OSHttpHandler h in handlers) - { - rc = h.Process(req); - - // Pass: handler did not process the request, - // try next handler - if (OSHttpHandlerResult.Pass == rc) continue; - - // Handled: handler has processed the request - if (OSHttpHandlerResult.Done == rc) break; - - // hmm, something went wrong - throw new Exception(String.Format("[{0}] got unexpected OSHttpHandlerResult {1}", EngineID, rc)); - } - - if (OSHttpHandlerResult.Unprocessed == rc) - { - _log.InfoFormat("[{0}] OSHttpHandler: no handler registered for {1}", EngineID, req); - - // set up response header - OSHttpResponse resp = new OSHttpResponse(req); - resp.StatusCode = (int)OSHttpStatusCode.ClientErrorNotFound; - resp.StatusDescription = String.Format("no handler on call for {0}", req); - resp.ContentType = "text/html"; - - // add explanatory message - StreamWriter body = new StreamWriter(resp.Body); - body.WriteLine(""); - body.WriteLine("
Ooops...
"); - body.WriteLine(String.Format("

{0}

", resp.StatusDescription)); - body.WriteLine(""); - body.Flush(); - - // and ship it back - resp.Send(); - } - } - catch (Exception e) - { - _log.DebugFormat("[{0}] OSHttpHandler problem: {1}", EngineID, e.ToString()); - _log.ErrorFormat("[{0}] OSHttpHandler problem: {1}", EngineID, e.Message); - } - } - } - - protected List MatchHandlers(OSHttpRequest req, List handlers) - { - Dictionary scoredHandlers = new Dictionary(); - - _log.DebugFormat("[{0}] MatchHandlers for {1}", EngineID, req); - foreach (OSHttpHandler h in handlers) - { - // initial anchor - scoredHandlers[h] = 0; - - // first, check whether IPEndPointWhitelist applies - // and, if it does, whether client is on that white - // list. - if (null != h.IPEndPointWhitelist) - { - // TODO: following code requires code changes to - // HttpServer.HttpRequest to become functional - - IPEndPoint remote = req.RemoteIPEndPoint; - if (null != remote) - { - Match epm = h.IPEndPointWhitelist.Match(remote.ToString()); - if (!epm.Success) - { - scoredHandlers.Remove(h); - continue; - } - } - } - - if (null != h.Method) - { - Match m = h.Method.Match(req.HttpMethod); - if (!m.Success) - { - scoredHandlers.Remove(h); - continue; - } - scoredHandlers[h]++; - } - - // whitelist ok, now check path - if (null != h.Path) - { - Match m = h.Path.Match(req.RawUrl); - if (!m.Success) - { - scoredHandlers.Remove(h); - continue; - } - scoredHandlers[h] += m.ToString().Length; - } - - // whitelist & path ok, now check query string - if (null != h.Query) - { - int queriesMatch = MatchOnNameValueCollection(req.QueryString, h.Query); - if (0 == queriesMatch) - { - _log.DebugFormat("[{0}] request {1}", EngineID, req); - _log.DebugFormat("[{0}] dropping handler {1}", EngineID, h); - - scoredHandlers.Remove(h); - continue; - } - scoredHandlers[h] += queriesMatch; - } - - // whitelist, path, query string ok, now check headers - if (null != h.Headers) - { - int headersMatch = MatchOnNameValueCollection(req.Headers, h.Headers); - if (0 == headersMatch) - { - _log.DebugFormat("[{0}] request {1}", EngineID, req); - _log.DebugFormat("[{0}] dropping handler {1}", EngineID, h); - - scoredHandlers.Remove(h); - continue; - } - scoredHandlers[h] += headersMatch; - } - } - - List matchingHandlers = new List(scoredHandlers.Keys); - matchingHandlers.Sort(delegate(OSHttpHandler x, OSHttpHandler y) - { - return scoredHandlers[x] - scoredHandlers[y]; - }); - LogDumpHandlerList(matchingHandlers); - return matchingHandlers; - } - - protected int MatchOnNameValueCollection(NameValueCollection collection, Dictionary regexs) - { - int matched = 0; - - foreach (string tag in regexs.Keys) - { - // do we have a header "tag"? - if (null == collection[tag]) - { - return 0; - } - - // does the content of collection[tag] match - // the supplied regex? - Match cm = regexs[tag].Match(collection[tag]); - if (!cm.Success) - { - return 0; - } - - // ok: matches - matched++; - continue; - } - - return matched; - } - - [ConditionalAttribute("DEBUGGING")] - private void LogDumpHandlerList(List l) - { - _log.DebugFormat("[{0}] OSHttpHandlerList dump:", EngineID); - foreach (OSHttpHandler h in l) - _log.DebugFormat(" ", h.ToString()); - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs deleted file mode 100644 index 881c5d17e6..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.Threading; -using HttpServer; - -namespace OpenSim.Framework.Servers.HttpServer -{ - /// - /// OSHttpRequestQueues are used to hand over incoming HTTP - /// requests to OSHttpRequestPump objects. - /// - public class OSHttpRequestQueue : Queue - { - private object _syncObject = new object(); - - new public void Enqueue(OSHttpRequest req) - { - lock (_syncObject) - { - base.Enqueue(req); - Monitor.Pulse(_syncObject); - } - } - - new public OSHttpRequest Dequeue() - { - OSHttpRequest req = null; - - lock (_syncObject) - { - while (null == req) - { - Monitor.Wait(_syncObject); - if (0 != this.Count) req = base.Dequeue(); - } - } - - return req; - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs index b505d4eb13..179257d545 100644 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs @@ -28,7 +28,7 @@ using System.IO; using System.Net; using System.Text; -using HttpServer; +using OSHttpServer; namespace OpenSim.Framework.Servers.HttpServer { @@ -243,19 +243,6 @@ namespace OpenSim.Framework.Servers.HttpServer } } - /// - /// Set a redirct location. - /// - public string RedirectLocation - { - // get { return _redirectLocation; } - set - { - _httpResponse.Redirect(value); - } - } - - /// /// Chunk transfers. /// diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs deleted file mode 100644 index cd6284259a..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading; -using System.Security.Cryptography.X509Certificates; -using log4net; -using HttpServer; - -using HttpListener = HttpServer.HttpListener; - -namespace OpenSim.Framework.Servers.HttpServer -{ - /// - /// OSHttpServer provides an HTTP server bound to a specific - /// port. When instantiated with just address and port it uses - /// normal HTTP, when instantiated with address, port, and X509 - /// certificate, it uses HTTPS. - /// - public class OSHttpServer - { - private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private object _syncObject = new object(); - - // underlying HttpServer.HttpListener - protected HttpListener _listener; - // underlying core/engine thread - protected Thread _engine; - - // Queue containing (OS)HttpRequests - protected OSHttpRequestQueue _queue; - - // OSHttpRequestPumps "pumping" incoming OSHttpRequests - // upwards - protected OSHttpRequestPump[] _pumps; - - // thread identifier - protected string _engineId; - public string EngineID - { - get { return _engineId; } - } - - /// - /// True if this is an HTTPS connection; false otherwise. - /// - protected bool _isSecure; - public bool IsSecure - { - get { return _isSecure; } - } - - public int QueueSize - { - get { return _pumps.Length; } - } - - /// - /// List of registered OSHttpHandlers for this OSHttpServer instance. - /// - protected List _httpHandlers = new List(); - public List OSHttpHandlers - { - get - { - lock (_httpHandlers) - { - return new List(_httpHandlers); - } - } - } - - - /// - /// Instantiate an HTTP server. - /// - public OSHttpServer(IPAddress address, int port, int poolSize) - { - _engineId = String.Format("OSHttpServer (HTTP:{0})", port); - _isSecure = false; - _log.DebugFormat("[{0}] HTTP server instantiated", EngineID); - - _listener = new HttpListener(address, port); - _queue = new OSHttpRequestQueue(); - _pumps = OSHttpRequestPump.Pumps(this, _queue, poolSize); - } - - /// - /// Instantiate an HTTPS server. - /// - public OSHttpServer(IPAddress address, int port, X509Certificate certificate, int poolSize) - { - _engineId = String.Format("OSHttpServer [HTTPS:{0}/ps:{1}]", port, poolSize); - _isSecure = true; - _log.DebugFormat("[{0}] HTTPS server instantiated", EngineID); - - _listener = new HttpListener(address, port, certificate); - _queue = new OSHttpRequestQueue(); - _pumps = OSHttpRequestPump.Pumps(this, _queue, poolSize); - } - - /// - /// Turn an HttpRequest into an OSHttpRequestItem and place it - /// in the queue. The OSHttpRequestQueue object will pulse the - /// next available idle pump. - /// - protected void OnHttpRequest(HttpClientContext client, HttpRequest request) - { - // turn request into OSHttpRequest - OSHttpRequest req = new OSHttpRequest(client, request); - - // place OSHttpRequest into _httpRequestQueue, will - // trigger Pulse to idle waiting pumps - _queue.Enqueue(req); - } - - /// - /// Start the HTTP server engine. - /// - public void Start() - { - _engine = new Thread(new ThreadStart(Engine)); - _engine.IsBackground = true; - _engine.Start(); - _engine.Name = string.Format ("Engine:{0}",_engineId); - - ThreadTracker.Add(_engine); - - // start the pumps... - for (int i = 0; i < _pumps.Length; i++) - _pumps[i].Start(); - } - - public void Stop() - { - lock (_syncObject) Monitor.Pulse(_syncObject); - } - - /// - /// Engine keeps the HTTP server running. - /// - private void Engine() - { - try { - _listener.RequestHandler += OnHttpRequest; - _listener.Start(QueueSize); - _log.InfoFormat("[{0}] HTTP server started", EngineID); - - lock (_syncObject) Monitor.Wait(_syncObject); - } - catch (Exception ex) - { - _log.DebugFormat("[{0}] HTTP server startup failed: {1}", EngineID, ex.ToString()); - } - - _log.InfoFormat("[{0}] HTTP server terminated", EngineID); - } - - - /// - /// Add an HTTP request handler. - /// - /// OSHttpHandler delegate - /// regex object for path matching - /// dictionary containing header names - /// and regular expressions to match against header values - public void AddHandler(OSHttpHandler handler) - { - lock (_httpHandlers) - { - if (_httpHandlers.Contains(handler)) - { - _log.DebugFormat("[OSHttpServer] attempt to add already existing handler ignored"); - return; - } - _httpHandlers.Add(handler); - } - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BadRequestException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BadRequestException.cs new file mode 100644 index 0000000000..223e4e5487 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BadRequestException.cs @@ -0,0 +1,33 @@ +using System; +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// The request could not be understood by the server due to malformed syntax. + /// The client SHOULD NOT repeat the request without modifications. + /// + /// Text taken from: http://www.submissionchamber.com/help-guides/error-codes.php + /// + public class BadRequestException : HttpException + { + /// + /// Create a new bad request exception. + /// + /// reason to why the request was bad. + public BadRequestException(string errMsg) + : base(HttpStatusCode.BadRequest, errMsg) + { + } + + /// + /// Create a new bad request exception. + /// + /// reason to why the request was bad. + /// inner exception + public BadRequestException(string errMsg, Exception inner) + : base(HttpStatusCode.BadRequest, errMsg, inner) + { + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BodyEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BodyEventArgs.cs new file mode 100644 index 0000000000..91dec7945a --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/BodyEventArgs.cs @@ -0,0 +1,55 @@ +using System; + +namespace OSHttpServer.Parser +{ + /// + /// Arguments used when more body bytes have come. + /// + public class BodyEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// buffer that contains the received bytes. + /// offset in buffer where to start processing. + /// number of bytes from that should be parsed. + public BodyEventArgs(byte[] buffer, int offset, int count) + { + Buffer = buffer; + Offset = offset; + Count = count; + } + + /// + /// Initializes a new instance of the class. + /// + public BodyEventArgs() + { + } + + /// + /// Gets or sets buffer that contains the received bytes. + /// + public byte[] Buffer { get; set; } + /* + /// + /// Gets or sets number of bytes used by the request. + /// + public int BytesUsed { get; set; } + */ + /// + /// Gets or sets number of bytes from that should be parsed. + /// + public int Count { get; set; } + /* + /// + /// Gets or sets whether the body is complete. + /// + public bool IsBodyComplete { get; set; } + */ + /// + /// Gets or sets offset in buffer where to start processing. + /// + public int Offset { get; set; } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ClientAcceptedEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ClientAcceptedEventArgs.cs new file mode 100644 index 0000000000..4139f9f6e3 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ClientAcceptedEventArgs.cs @@ -0,0 +1,50 @@ +using System; +using System.Net.Sockets; + +namespace OSHttpServer +{ + /// + /// Invoked when a client have been accepted by the + /// + /// + /// Can be used to revoke incoming connections + /// + public class ClientAcceptedEventArgs : EventArgs + { + private readonly Socket _socket; + private bool _revoke; + + /// + /// Initializes a new instance of the class. + /// + /// The socket. + public ClientAcceptedEventArgs(Socket socket) + { + _socket = socket; + } + + /// + /// Accepted socket. + /// + public Socket Socket + { + get { return _socket; } + } + + /// + /// Client should be revoked. + /// + public bool Revoked + { + get { return _revoke; } + } + + /// + /// Client may not be handled. + /// + public void Revoke() + { + _revoke = true; + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ContextTimeoutManager.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ContextTimeoutManager.cs new file mode 100644 index 0000000000..aa20786442 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ContextTimeoutManager.cs @@ -0,0 +1,413 @@ +/* + * 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.Concurrent; +using System.Diagnostics; +using System.Globalization; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace OSHttpServer +{ + /// + /// Timeout Manager. Checks for dead clients. Clients with open connections that are not doing anything. Closes sessions opened with keepalive. + /// + public static class ContextTimeoutManager + { + /// + /// Use a Thread or a Timer to monitor the ugly + /// + private static Thread m_internalThread = null; + private static object m_threadLock = new object(); + private static ConcurrentQueue m_contexts = new ConcurrentQueue(); + private static ConcurrentQueue m_highPrio = new ConcurrentQueue(); + private static ConcurrentQueue m_midPrio = new ConcurrentQueue(); + private static ConcurrentQueue m_lowPrio = new ConcurrentQueue(); + private static AutoResetEvent m_processWaitEven = new AutoResetEvent(false); + private static bool m_shuttingDown; + + private static int m_ActiveSendingCount; + private static double m_lastTimeOutCheckTime = 0; + private static double m_lastSendCheckTime = 0; + + const int m_maxBandWidth = 10485760; //80Mbps + const int m_maxConcurrenSend = 32; + + static ContextTimeoutManager() + { + TimeStampClockPeriod = 1.0 / (double)Stopwatch.Frequency; + TimeStampClockPeriodMS = 1e3 / (double)Stopwatch.Frequency; + } + + public static void Start() + { + lock (m_threadLock) + { + if (m_internalThread != null) + return; + + m_lastTimeOutCheckTime = GetTimeStampMS(); + m_internalThread = new Thread(ThreadRunProcess); + m_internalThread.Priority = ThreadPriority.Normal; + m_internalThread.IsBackground = true; + m_internalThread.CurrentCulture = new CultureInfo("en-US", false); + m_internalThread.Name = "HttpServerMain"; + m_internalThread.Start(); + } + } + + public static void Stop() + { + m_shuttingDown = true; + m_internalThread.Join(); + ProcessShutDown(); + } + + private static void ThreadRunProcess() + { + while (!m_shuttingDown) + { + m_processWaitEven.WaitOne(100); + + if(m_shuttingDown) + return; + + double now = GetTimeStampMS(); + if(m_contexts.Count > 0) + { + ProcessSendQueues(now); + + if (now - m_lastTimeOutCheckTime > 1000) + { + ProcessContextTimeouts(); + m_lastTimeOutCheckTime = now; + } + } + else + m_lastTimeOutCheckTime = now; + } + } + + public static void ProcessShutDown() + { + try + { + SocketError disconnectError = SocketError.HostDown; + for (int i = 0; i < m_contexts.Count; i++) + { + HttpClientContext context = null; + if (m_contexts.TryDequeue(out context)) + { + try + { + context.Disconnect(disconnectError); + } + catch { } + } + } + m_processWaitEven.Dispose(); + m_processWaitEven = null; + } + catch + { + // We can't let this crash. + } + } + + public static void ProcessSendQueues(double now) + { + int inqueues = m_highPrio.Count + m_midPrio.Count + m_lowPrio.Count; + if(inqueues == 0) + return; + + double dt = now - m_lastSendCheckTime; + m_lastSendCheckTime = now; + + int totalSending = m_ActiveSendingCount; + + int curConcurrentLimit = m_maxConcurrenSend - totalSending; + if(curConcurrentLimit <= 0) + return; + + if(curConcurrentLimit > inqueues) + curConcurrentLimit = inqueues; + + if (dt > 0.1) + dt = 0.1; + + dt /= curConcurrentLimit; + int curbytesLimit = (int)(m_maxBandWidth * dt); + if(curbytesLimit < 8192) + curbytesLimit = 8192; + + HttpClientContext ctx; + int sent; + while (curConcurrentLimit > 0) + { + sent = 0; + while (m_highPrio.TryDequeue(out ctx)) + { + if(TrySend(ctx, curbytesLimit)) + m_highPrio.Enqueue(ctx); + + if (m_shuttingDown) + return; + --curConcurrentLimit; + if (++sent == 4) + break; + } + + sent = 0; + while(m_midPrio.TryDequeue(out ctx)) + { + if(TrySend(ctx, curbytesLimit)) + m_midPrio.Enqueue(ctx); + + if (m_shuttingDown) + return; + --curConcurrentLimit; + if (++sent >= 2) + break; + } + + if (m_lowPrio.TryDequeue(out ctx)) + { + --curConcurrentLimit; + if(TrySend(ctx, curbytesLimit)) + m_lowPrio.Enqueue(ctx); + } + + if (m_shuttingDown) + return; + } + } + + private static bool TrySend(HttpClientContext ctx, int bytesLimit) + { + if(!ctx.CanSend()) + return false; + + return ctx.TrySendResponse(bytesLimit); + } + + /// + /// Causes the watcher to immediately check the connections. + /// + public static void ProcessContextTimeouts() + { + try + { + for (int i = 0; i < m_contexts.Count; i++) + { + if (m_shuttingDown) + return; + if (m_contexts.TryDequeue(out HttpClientContext context)) + { + if (!ContextTimedOut(context, out SocketError disconnectError)) + m_contexts.Enqueue(context); + else if(disconnectError != SocketError.InProgress) + context.Disconnect(disconnectError); + } + } + } + catch + { + // We can't let this crash. + } + } + + private static bool ContextTimedOut(HttpClientContext context, out SocketError disconnectError) + { + disconnectError = SocketError.InProgress; + + // First our error conditions + if (context.contextID < 0 || context.StopMonitoring || context.StreamPassedOff) + return true; + + // Now we start checking for actual timeouts + + // First we check that we got at least one line within context.TimeoutFirstLine milliseconds + if (!context.FirstRequestLineReceived) + { + if (EnvironmentTickCountAdd(context.TimeoutFirstLine, context.MonitorStartMS) <= EnvironmentTickCount()) + { + disconnectError = SocketError.TimedOut; + context.MonitorStartMS = 0; + return true; + } + } + + if (!context.FullRequestReceived) + { + if (EnvironmentTickCountAdd(context.TimeoutRequestReceived, context.MonitorStartMS) <= EnvironmentTickCount()) + { + disconnectError = SocketError.TimedOut; + context.MonitorStartMS = 0; + return true; + } + } + + // + if (!context.FullRequestProcessed) + { + if (EnvironmentTickCountAdd(context.TimeoutFullRequestProcessed, context.MonitorStartMS) <= EnvironmentTickCount()) + { + disconnectError = SocketError.TimedOut; + context.MonitorStartMS = 0; + return true; + } + } + + if (context.TriggerKeepalive) + { + context.TriggerKeepalive = false; + context.MonitorKeepaliveMS = EnvironmentTickCount(); + } + + if (context.FullRequestProcessed && context.MonitorKeepaliveMS == 0) + return true; + + if (context.MonitorKeepaliveMS != 0 && + EnvironmentTickCountAdd(context.TimeoutKeepAlive, context.MonitorKeepaliveMS) <= EnvironmentTickCount()) + { + disconnectError = SocketError.TimedOut; + context.MonitorStartMS = 0; + context.MonitorKeepaliveMS = 0; + return true; + } + + return false; + } + + public static void StartMonitoringContext(HttpClientContext context) + { + context.MonitorStartMS = EnvironmentTickCount(); + m_contexts.Enqueue(context); + } + + public static void EnqueueSend(HttpClientContext context, int priority) + { + switch(priority) + { + case 0: + m_highPrio.Enqueue(context); + break; + case 1: + m_midPrio.Enqueue(context); + break; + case 2: + m_lowPrio.Enqueue(context); + break; + default: + return; + } + m_processWaitEven.Set(); + } + + public static void ContextEnterActiveSend() + { + Interlocked.Increment(ref m_ActiveSendingCount); + } + + public static void ContextLeaveActiveSend() + { + Interlocked.Decrement(ref m_ActiveSendingCount); + } + + /// + /// Environment.TickCount is an int but it counts all 32 bits so it goes positive + /// and negative every 24.9 days. This trims down TickCount so it doesn't wrap + /// for the callers. + /// This trims it to a 12 day interval so don't let your frame time get too long. + /// + /// + public static int EnvironmentTickCount() + { + return Environment.TickCount & EnvironmentTickCountMask; + } + const int EnvironmentTickCountMask = 0x3fffffff; + + /// + /// Environment.TickCount is an int but it counts all 32 bits so it goes positive + /// and negative every 24.9 days. Subtracts the passed value (previously fetched by + /// 'EnvironmentTickCount()') and accounts for any wrapping. + /// + /// + /// + /// subtraction of passed prevValue from current Environment.TickCount + public static int EnvironmentTickCountSubtract(Int32 newValue, Int32 prevValue) + { + int diff = newValue - prevValue; + return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1); + } + + /// + /// Environment.TickCount is an int but it counts all 32 bits so it goes positive + /// and negative every 24.9 days. Subtracts the passed value (previously fetched by + /// 'EnvironmentTickCount()') and accounts for any wrapping. + /// + /// + /// + /// subtraction of passed prevValue from current Environment.TickCount + public static int EnvironmentTickCountAdd(Int32 newValue, Int32 prevValue) + { + int ret = newValue + prevValue; + return (ret >= 0) ? ret : (ret + EnvironmentTickCountMask + 1); + } + + public static double TimeStampClockPeriodMS; + public static double TimeStampClockPeriod; + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static double GetTimeStamp() + { + return Stopwatch.GetTimestamp() * TimeStampClockPeriod; + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static double GetTimeStampMS() + { + return Stopwatch.GetTimestamp() * TimeStampClockPeriodMS; + } + + // doing math in ticks is usefull to avoid loss of resolution + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static long GetTimeStampTicks() + { + return Stopwatch.GetTimestamp(); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static double TimeStampTicksToMS(long ticks) + { + return ticks * TimeStampClockPeriodMS; + } + + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionEventArgs.cs new file mode 100644 index 0000000000..b93ee850e1 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionEventArgs.cs @@ -0,0 +1,29 @@ +using System; + +namespace OSHttpServer +{ + /// + /// An unhandled exception have been caught by the system. + /// + public class ExceptionEventArgs : EventArgs + { + private readonly Exception _exception; + + /// + /// Initializes a new instance of the class. + /// + /// Caught exception. + public ExceptionEventArgs(Exception exception) + { + _exception = exception; + } + + /// + /// caught exception + /// + public Exception Exception + { + get { return _exception; } + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionHandler.cs new file mode 100644 index 0000000000..c8f41a8138 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ExceptionHandler.cs @@ -0,0 +1,16 @@ +using System; + +namespace OSHttpServer +{ + /// + /// We dont want to let the server to die due to exceptions thrown in worker threads. + /// therefore we use this delegate to give you a change to handle uncaught exceptions. + /// + /// Class that the exception was thrown in. + /// Exception + /// + /// Server will throw a InternalServerException in release version if you dont + /// handle this delegate. + /// + public delegate void ExceptionHandler(object source, Exception exception); +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ForbiddenException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ForbiddenException.cs new file mode 100644 index 0000000000..2eccdd4116 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ForbiddenException.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// The server understood the request, but is refusing to fulfill it. + /// Authorization will not help and the request SHOULD NOT be repeated. + /// If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, + /// it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information + /// available to the client, the status code 404 (Not Found) can be used instead. + /// + /// Text taken from: http://www.submissionchamber.com/help-guides/error-codes.php + /// + public class ForbiddenException : HttpException + { + /// + /// Initializes a new instance of the class. + /// + /// error message + public ForbiddenException(string errorMsg) + : base(HttpStatusCode.Forbidden, errorMsg) + { + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HeaderEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HeaderEventArgs.cs new file mode 100644 index 0000000000..89f0f8fe44 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HeaderEventArgs.cs @@ -0,0 +1,38 @@ +using System; + +namespace OSHttpServer.Parser +{ + /// + /// Event arguments used when a new header have been parsed. + /// + public class HeaderEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// Name of header. + /// Header value. + public HeaderEventArgs(string name, string value) + { + Name = name; + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + public HeaderEventArgs() + { + } + + /// + /// Gets or sets header name. + /// + public string Name { get; set; } + + /// + /// Gets or sets header value. + /// + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs new file mode 100644 index 0000000000..053764d550 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs @@ -0,0 +1,786 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using OSHttpServer.Exceptions; +using OSHttpServer.Parser; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace OSHttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + /// + /// Remember to after you have hooked the event. + /// + public class HttpClientContext : IHttpClientContext, IDisposable + { + const int MAXREQUESTS = 20; + const int MAXKEEPALIVE = 60000; + + static private int basecontextID; + + private readonly byte[] m_ReceiveBuffer; + private int m_ReceiveBytesLeft; + private ILogWriter _log; + private readonly IHttpRequestParser m_parser; + private readonly int m_bufferSize; + private HashSet requestsInServiceIDs; + private Socket m_sock; + + public bool Available = true; + public bool StreamPassedOff = false; + + public int MonitorStartMS = 0; + public int MonitorKeepaliveMS = 0; + public bool TriggerKeepalive = false; + public int TimeoutFirstLine = 70000; // 70 seconds + public int TimeoutRequestReceived = 180000; // 180 seconds + + // The difference between this and request received is on POST more time is needed before we get the full request. + public int TimeoutFullRequestProcessed = 600000; // 10 minutes + public int m_TimeoutKeepAlive = MAXKEEPALIVE; // 400 seconds before keepalive timeout + // public int TimeoutKeepAlive = 120000; // 400 seconds before keepalive timeout + + public int m_maxRequests = MAXREQUESTS; + + public bool FirstRequestLineReceived; + public bool FullRequestReceived; + public bool FullRequestProcessed; + + private bool isSendingResponse = false; + + private HttpRequest m_currentRequest; + private HttpResponse m_currentResponse; + + public int contextID { get; private set; } + public int TimeoutKeepAlive + { + get { return m_TimeoutKeepAlive; } + set + { + m_TimeoutKeepAlive = (value > MAXKEEPALIVE) ? MAXKEEPALIVE : value; + } + } + + public int MAXRequests + { + get { return m_maxRequests; } + set + { + m_maxRequests = value > MAXREQUESTS ? MAXREQUESTS : value; + } + } + + public bool IsSending() + { + return isSendingResponse; + } + + public bool StopMonitoring; + + /// + /// Context have been started (a new client have connected) + /// + public event EventHandler Started; + + /// + /// Initializes a new instance of the class. + /// + /// true if the connection is secured (SSL/TLS) + /// client that connected. + /// Stream used for communication + /// Used to create a . + /// Size of buffer to use when reading data. Must be at least 4096 bytes. + /// If fails + /// Stream must be writable and readable. + public HttpClientContext(bool secured, IPEndPoint remoteEndPoint, + Stream stream, IRequestParserFactory parserFactory, Socket sock) + { + if (!stream.CanWrite || !stream.CanRead) + throw new ArgumentException("Stream must be writable and readable."); + + RemoteAddress = remoteEndPoint.Address.ToString(); + RemotePort = remoteEndPoint.Port.ToString(); + _log = NullLogWriter.Instance; + m_parser = parserFactory.CreateParser(_log); + m_parser.RequestCompleted += OnRequestCompleted; + m_parser.RequestLineReceived += OnRequestLine; + m_parser.HeaderReceived += OnHeaderReceived; + m_parser.BodyBytesReceived += OnBodyBytesReceived; + m_currentRequest = new HttpRequest(this); + IsSecured = secured; + _stream = stream; + m_sock = sock; + + m_bufferSize = 8196; + m_ReceiveBuffer = new byte[m_bufferSize]; + requestsInServiceIDs = new HashSet(); + + SSLCommonName = ""; + if (secured) + { + SslStream _ssl = (SslStream)_stream; + X509Certificate _cert1 = _ssl.RemoteCertificate; + if (_cert1 != null) + { + X509Certificate2 _cert2 = new X509Certificate2(_cert1); + if (_cert2 != null) + SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); + } + } + + ++basecontextID; + if (basecontextID <= 0) + basecontextID = 1; + + contextID = basecontextID; + } + + public bool CanSend() + { + if (contextID < 0) + return false; + + if (Stream == null || m_sock == null || !m_sock.Connected) + return false; + + return true; + } + + /// + /// Process incoming body bytes. + /// + /// + /// Bytes + protected virtual void OnBodyBytesReceived(object sender, BodyEventArgs e) + { + m_currentRequest.AddToBody(e.Buffer, e.Offset, e.Count); + } + + /// + /// + /// + /// + /// + protected virtual void OnHeaderReceived(object sender, HeaderEventArgs e) + { + if (string.Compare(e.Name, "expect", true) == 0 && e.Value.Contains("100-continue")) + { + lock (requestsInServiceIDs) + { + if (requestsInServiceIDs.Count == 0) + Respond("HTTP/1.1", HttpStatusCode.Continue, "Please continue."); + } + } + m_currentRequest.AddHeader(e.Name, e.Value); + } + + private void OnRequestLine(object sender, RequestLineEventArgs e) + { + m_currentRequest.Method = e.HttpMethod; + m_currentRequest.HttpVersion = e.HttpVersion; + m_currentRequest.UriPath = e.UriPath; + m_currentRequest.AddHeader("remote_addr", RemoteAddress); + m_currentRequest.AddHeader("remote_port", RemotePort); + FirstRequestLineReceived = true; + TriggerKeepalive = false; + MonitorKeepaliveMS = 0; + } + + /// + /// Start reading content. + /// + /// + /// Make sure to call base.Start() if you override this method. + /// + public virtual void Start() + { + ReceiveLoop(); + Started?.Invoke(this, EventArgs.Empty); + } + + /// + /// Clean up context. + /// + /// + /// + public virtual void Cleanup() + { + if (StreamPassedOff) + return; + + if (Stream != null) + { + Stream.Close(); + Stream = null; + m_sock = null; + } + + m_currentRequest?.Clear(); + m_currentRequest = null; + m_currentResponse?.Clear(); + m_currentResponse = null; + requestsInServiceIDs.Clear(); + + FirstRequestLineReceived = false; + FullRequestReceived = false; + FullRequestProcessed = false; + MonitorStartMS = 0; + StopMonitoring = true; + MonitorKeepaliveMS = 0; + TriggerKeepalive = false; + + isSendingResponse = false; + + m_ReceiveBytesLeft = 0; + + contextID = -100; + m_parser.Clear(); + } + + public void Close() + { + Dispose(); + } + + /// + /// Using SSL or other encryption method. + /// + [Obsolete("Use IsSecured instead.")] + public bool Secured + { + get { return IsSecured; } + } + + /// + /// Using SSL or other encryption method. + /// + public bool IsSecured { get; internal set; } + + + // returns the SSL commonName of remote Certificate + public string SSLCommonName { get; internal set; } + + /// + /// Specify which logger to use. + /// + public ILogWriter LogWriter + { + get { return _log; } + set + { + _log = value ?? NullLogWriter.Instance; + m_parser.LogWriter = _log; + } + } + + private Stream _stream; + + /// + /// Gets or sets the network stream. + /// + internal Stream Stream + { + get { return _stream; } + set { _stream = value; } + } + + /// + /// Gets or sets IP address that the client connected from. + /// + internal string RemoteAddress { get; set; } + + /// + /// Gets or sets port that the client connected from. + /// + internal string RemotePort { get; set; } + + /// + /// Disconnect from client + /// + /// error to report in the event. + public void Disconnect(SocketError error) + { + // disconnect may not throw any exceptions + try + { + try + { + if (Stream != null) + { + if (error == SocketError.Success) + Stream.Flush(); + Stream.Close(); + Stream = null; + } + m_sock = null; + } + catch { } + + Disconnected?.Invoke(this, new DisconnectedEventArgs(error)); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Error, "Disconnect threw an exception: " + err); + } + } + + private void OnReceive(IAsyncResult ar) + { + try + { + int bytesRead = 0; + if (Stream == null) + return; + try + { + bytesRead = Stream.EndRead(ar); + } + catch (NullReferenceException) + { + Disconnect(SocketError.ConnectionReset); + return; + } + + if (bytesRead == 0) + { + Disconnect(SocketError.ConnectionReset); + return; + } + + m_ReceiveBytesLeft += bytesRead; + if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length) + { + throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft); + } + + int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft); + if (Stream == null) + return; // "Connection: Close" in effect. + + // try again to see if we can parse another message (check parser to see if it is looking for a new message) + int nextOffset; + int nextBytesleft = m_ReceiveBytesLeft - offset; + + while (offset != 0 && nextBytesleft > 0) + { + nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft); + + if (Stream == null) + return; // "Connection: Close" in effect. + + if (nextOffset == 0) + break; + + offset = nextOffset; + nextBytesleft = m_ReceiveBytesLeft - offset; + } + + // copy unused bytes to the beginning of the array + if (offset > 0 && m_ReceiveBytesLeft > offset) + Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset); + + m_ReceiveBytesLeft -= offset; + if (Stream != null && Stream.CanRead) + { + if (!StreamPassedOff) + Stream.BeginRead(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft, OnReceive, null); + else + { + _log.Write(this, LogPrio.Warning, "Could not read any more from the socket."); + Disconnect(SocketError.Success); + } + } + } + catch (BadRequestException err) + { + LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err); + try + { + Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message); + } + catch (Exception err2) + { + LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2); + } + Disconnect(SocketError.NoRecovery); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + if (err.InnerException is SocketException) + Disconnect((SocketError)((SocketException)err.InnerException).ErrorCode); + else + Disconnect(SocketError.ConnectionReset); + } + catch (ObjectDisposedException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message); + Disconnect(SocketError.NotSocket); + } + catch (NullReferenceException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + } + + private async void ReceiveLoop() + { + m_ReceiveBytesLeft = 0; + try + { + while(true) + { + if (_stream == null || !_stream.CanRead) + return; + + int bytesRead = await _stream.ReadAsync(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft).ConfigureAwait(false); + + if (bytesRead == 0) + { + Disconnect(SocketError.ConnectionReset); + return; + } + + m_ReceiveBytesLeft += bytesRead; + if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length) + throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft); + + int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft); + if (Stream == null) + return; // "Connection: Close" in effect. + + // try again to see if we can parse another message (check parser to see if it is looking for a new message) + int nextOffset; + int nextBytesleft = m_ReceiveBytesLeft - offset; + + while (offset != 0 && nextBytesleft > 0) + { + nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft); + + if (Stream == null) + return; // "Connection: Close" in effect. + + if (nextOffset == 0) + break; + + offset = nextOffset; + nextBytesleft = m_ReceiveBytesLeft - offset; + } + + // copy unused bytes to the beginning of the array + if (offset > 0 && m_ReceiveBytesLeft > offset) + Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset); + + m_ReceiveBytesLeft -= offset; + if (StreamPassedOff) + return; //? + } + } + catch (BadRequestException err) + { + LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err); + try + { + Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message); + } + catch (Exception err2) + { + LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2); + } + Disconnect(SocketError.NoRecovery); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + if (err.InnerException is SocketException) + Disconnect((SocketError)((SocketException)err.InnerException).ErrorCode); + else + Disconnect(SocketError.ConnectionReset); + } + catch (ObjectDisposedException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message); + Disconnect(SocketError.NotSocket); + } + catch (NullReferenceException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + } + + private void OnRequestCompleted(object source, EventArgs args) + { + TriggerKeepalive = false; + MonitorKeepaliveMS = 0; + + // load cookies if they exist + + RequestCookies cookies = m_currentRequest.Headers["cookie"] != null + ? new RequestCookies(m_currentRequest.Headers["cookie"]) : new RequestCookies(String.Empty); + m_currentRequest.SetCookies(cookies); + + m_currentRequest.Body.Seek(0, SeekOrigin.Begin); + + FullRequestReceived = true; + + int nreqs; + lock (requestsInServiceIDs) + { + nreqs = requestsInServiceIDs.Count; + requestsInServiceIDs.Add(m_currentRequest.ID); + if (m_maxRequests > 0) + m_maxRequests--; + } + + // for now pipeline requests need to be serialized by opensim + RequestReceived(this, new RequestEventArgs(m_currentRequest)); + + m_currentRequest = new HttpRequest(this); + + int nreqsnow; + lock (requestsInServiceIDs) + { + nreqsnow = requestsInServiceIDs.Count; + } + if (nreqs != nreqsnow) + { + // request was not done by us + } + } + + public void ReqResponseAboutToSend(uint requestID) + { + isSendingResponse = true; + } + + public void StartSendResponse(HttpResponse response) + { + isSendingResponse = true; + m_currentResponse = response; + ContextTimeoutManager.EnqueueSend(this, response.Priority); + } + + public bool TrySendResponse(int bytesLimit) + { + if(m_currentResponse == null) + return false; + if (m_currentResponse.Sent) + return false; + + if(!CanSend()) + return false; + + m_currentResponse?.SendNextAsync(bytesLimit); + return false; + } + + public void ContinueSendResponse() + { + if(m_currentResponse == null) + return; + ContextTimeoutManager.EnqueueSend(this, m_currentResponse.Priority); + } + + public void ReqResponseSent(uint requestID, ConnectionType ctype) + { + isSendingResponse = false; + m_currentResponse?.Clear(); + m_currentResponse = null; + + bool doclose = ctype == ConnectionType.Close; + lock (requestsInServiceIDs) + { + requestsInServiceIDs.Remove(requestID); +// doclose = doclose && requestsInServiceIDs.Count == 0; + if (requestsInServiceIDs.Count > 1) + { + + } + } + + if (doclose) + Disconnect(SocketError.Success); + else + { + lock (requestsInServiceIDs) + { + if (requestsInServiceIDs.Count == 0) + TriggerKeepalive = true; + } + } + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) + { + if (string.IsNullOrEmpty(contentType)) + contentType = "text/html"; + + if (string.IsNullOrEmpty(reason)) + reason = statusCode.ToString(); + + string response = string.IsNullOrEmpty(body) + ? httpVersion + " " + (int)statusCode + " " + reason + "\r\n\r\n" + : string.Format("{0} {1} {2}\r\nContent-Type: {5}\r\nContent-Length: {3}\r\n\r\n{4}", + httpVersion, (int)statusCode, reason ?? statusCode.ToString(), + body.Length, body, contentType); + byte[] buffer = Encoding.ASCII.GetBytes(response); + + Send(buffer); + if (m_currentRequest.Connection == ConnectionType.Close) + FullRequestProcessed = true; + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) + { + Respond(httpVersion, statusCode, reason, null, null); + } + + /// + /// send a whole buffer + /// + /// buffer to send + /// + public bool Send(byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + return Send(buffer, 0, buffer.Length); + } + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + + private object sendLock = new object(); + + public bool Send(byte[] buffer, int offset, int size) + { + if (Stream == null || m_sock == null || !m_sock.Connected) + return false; + + if (offset + size > buffer.Length) + throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer."); + + bool ok = true; + lock (sendLock) // can't have overlaps here + { + try + { + Stream.Write(buffer, offset, size); + } + catch + { + ok = false; + } + + if (!ok && Stream != null) + Disconnect(SocketError.NoRecovery); + return ok; + } + } + + public async Task SendAsync(byte[] buffer, int offset, int size) + { + if (Stream == null || m_sock == null || !m_sock.Connected) + return false; + + if (offset + size > buffer.Length) + throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer."); + + bool ok = true; + ContextTimeoutManager.ContextEnterActiveSend(); + try + { + await Stream.WriteAsync(buffer, offset, size).ConfigureAwait(false); + } + catch + { + ok = false; + } + + ContextTimeoutManager.ContextLeaveActiveSend(); + + if (!ok && Stream != null) + Disconnect(SocketError.NoRecovery); + return ok; + } + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + public event EventHandler Disconnected = delegate { }; + /// + /// A request have been received in the context. + /// + public event EventHandler RequestReceived = delegate { }; + + public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() + { + StreamPassedOff = true; + m_parser.RequestCompleted -= OnRequestCompleted; + m_parser.RequestLineReceived -= OnRequestLine; + m_parser.HeaderReceived -= OnHeaderReceived; + m_parser.BodyBytesReceived -= OnBodyBytesReceived; + m_parser.Clear(); + + return new HTTPNetworkContext() { Socket = m_sock, Stream = _stream as NetworkStream }; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + if (contextID >= 0) + { + StreamPassedOff = false; + Cleanup(); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpContextFactory.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpContextFactory.cs new file mode 100644 index 0000000000..7b0bbdcfce --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpContextFactory.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace OSHttpServer +{ + /// + /// Used to create and reuse contexts. + /// + public class HttpContextFactory : IHttpContextFactory + { + private readonly ConcurrentDictionary m_activeContexts = new ConcurrentDictionary(); + private readonly IRequestParserFactory m_parserfactory; + private readonly ILogWriter m_logWriter; + + /// + /// A request have been received from one of the contexts. + /// + public event EventHandler RequestReceived; + + /// + /// Initializes a new instance of the class. + /// + /// The writer. + /// Amount of bytes to read from the incoming socket stream. + /// Used to create a request parser. + public HttpContextFactory(ILogWriter writer, IRequestParserFactory factory) + { + m_logWriter = writer; + m_parserfactory = factory; + ContextTimeoutManager.Start(); + } + + /// + /// Create a new context. + /// + /// true if socket is running HTTPS. + /// Client that connected + /// Network/SSL stream. + /// A context. + protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + HttpClientContext context; + + context = CreateNewContext(isSecured, endPoint, stream, sock); + context.Disconnected += OnFreeContext; + context.RequestReceived += OnRequestReceived; + + context.Stream = stream; + context.IsSecured = isSecured; + context.RemotePort = endPoint.Port.ToString(); + context.RemoteAddress = endPoint.Address.ToString(); + ContextTimeoutManager.StartMonitoringContext(context); + m_activeContexts[context.contextID] = context; + context.Start(); + return context; + } + + /// + /// Create a new context. + /// + /// true if HTTPS is used. + /// Remote client + /// Network stream, + /// A new context (always). + protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + return new HttpClientContext(isSecured, endPoint, stream, m_parserfactory, sock); + } + + private void OnRequestReceived(object sender, RequestEventArgs e) + { + RequestReceived?.Invoke(sender, e); + } + + private void OnFreeContext(object sender, DisconnectedEventArgs e) + { + var imp = sender as HttpClientContext; + if (imp == null || imp.contextID < 0) + return; + + m_activeContexts.TryRemove(imp.contextID, out HttpClientContext dummy); + + imp.Close(); + } + + + #region IHttpContextFactory Members + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// + /// A created . + /// + public IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, + SslProtocols protocol, RemoteCertificateValidationCallback _clientCallback = null) + { + socket.NoDelay = true; + var networkStream = new NetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint; + + SslStream sslStream = null; + try + { + if (_clientCallback == null) + { + sslStream = new SslStream(networkStream, false); + sslStream.AuthenticateAsServer(certificate, false, protocol, false); + } + else + { + sslStream = new SslStream(networkStream, false, + new RemoteCertificateValidationCallback(_clientCallback)); + sslStream.AuthenticateAsServer(certificate, true, protocol, false); + } + } + catch (Exception e) + { + m_logWriter.Write(this, LogPrio.Error, e.Message); + sslStream.Close(); + return null; + } + + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// + /// A creates . + /// + public IHttpClientContext CreateContext(Socket socket) + { + socket.NoDelay = true; + var networkStream = new NetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint; + return CreateContext(false, remoteEndPoint, networkStream, socket); + } + + #endregion + + /// + /// Server is shutting down so shut down the factory + /// + public void Shutdown() + { + ContextTimeoutManager.Stop(); + } + } + + /// + /// Used to create es. + /// + public interface IHttpContextFactory + { + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// A creates . + IHttpClientContext CreateContext(Socket socket); + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// A created . + IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, + SslProtocols protocol, RemoteCertificateValidationCallback _clientCallback = null); + + /// + /// A request have been received from one of the contexts. + /// + event EventHandler RequestReceived; + + /// + /// Server is shutting down so shut down the factory + /// + void Shutdown(); + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpException.cs new file mode 100644 index 0000000000..db62590db2 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpException.cs @@ -0,0 +1,43 @@ +using System; +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// All HTTP based exceptions will derive this class. + /// + public class HttpException : Exception + { + private readonly HttpStatusCode _code; + + /// + /// Create a new HttpException + /// + /// http status code (sent in the response) + /// error description + public HttpException(HttpStatusCode code, string message) : base(code + ": " + message) + { + _code = code; + } + + /// + /// Create a new HttpException + /// + /// http status code (sent in the response) + /// error description + /// inner exception + public HttpException(HttpStatusCode code, string message, Exception inner) + : base(code + ": " + message, inner) + { + _code = code; + } + + /// + /// status code to use in the response. + /// + public HttpStatusCode HttpStatusCode + { + get { return _code; } + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpHelper.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpHelper.cs new file mode 100644 index 0000000000..9cdce7caff --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpHelper.cs @@ -0,0 +1,118 @@ +using System; +using System.Web; + +namespace OSHttpServer +{ + /// + /// Generic helper functions for HTTP + /// + public static class HttpHelper + { + /// + /// An empty URI + /// + public static readonly Uri EmptyUri = new Uri("http://localhost/"); + + /// + /// Parses a query string. + /// + /// Query string (URI encoded) + /// A object if successful; otherwise + /// queryString is null. + /// If string cannot be parsed. + public static HttpInput ParseQueryString(string queryString) + { + if (queryString == null) + throw new ArgumentNullException("queryString"); + if (queryString == string.Empty) + return HttpInput.Empty; + + HttpInput input = new HttpInput("QueryString"); + + queryString = queryString.TrimStart('?', '&'); + + // a simple value. + if (queryString.IndexOf("&") == -1 && !queryString.Contains("%3d") && !queryString.Contains("%3D") && !queryString.Contains("=")) + { + input.Add(string.Empty, queryString); + return input; + } + + int state = 0; + int startpos = 0; + string name = null; + for (int i = 0; i < queryString.Length; ++i) + { + int newIndexPos; + if (state == 0 && IsEqual(queryString, ref i, out newIndexPos)) + { + name = queryString.Substring(startpos, i - startpos); + i = newIndexPos; + startpos = i + 1; + ++state; + } + else if (state == 1 && IsAmp(queryString, ref i, out newIndexPos)) + { + Add(input, name, queryString.Substring(startpos, i - startpos)); + i = newIndexPos; + startpos = i + 1; + state = 0; + name = null; + } + } + + if (state == 0 && !input.GetEnumerator().MoveNext()) + throw new FormatException("Not a valid query string: " + queryString); + + if (startpos <= queryString.Length) + { + if (name != null) + Add(input, name, queryString.Substring(startpos, queryString.Length - startpos)); + else + Add(input, string.Empty, queryString.Substring(startpos, queryString.Length - startpos)); + } + + return input; + } + + private static bool IsEqual(string queryStr, ref int index, out int outIndex) + { + outIndex = index; + if (queryStr[index] == '=') + return true; + if (queryStr[index] == '%' && queryStr.Length > index + 2 && queryStr[index + 1] == '3' + && (queryStr[index + 2] == 'd' || queryStr[index + 2] == 'D')) + { + outIndex += 2; + return true; + } + return false; + } + + private static bool IsAmp(string queryStr, ref int index, out int outIndex) + { + outIndex = index; + if (queryStr[index] == '%' && queryStr.Length > index + 2 && queryStr[index + 1] == '2' && + queryStr[index + 2] == '6') + outIndex += 2; + else if (queryStr[index] == '&') + { + if (queryStr.Length > index + 4 + && (queryStr[index + 1] == 'a' || queryStr[index + 1] == 'A') + && (queryStr[index + 2] == 'm' || queryStr[index + 2] == 'M') + && (queryStr[index + 3] == 'p' || queryStr[index + 3] == 'P') + && queryStr[index + 4] == ';') + outIndex += 4; + } + else + return false; + + return true; + } + + private static void Add(IHttpInput input, string name, string value) + { + input.Add(HttpUtility.UrlDecode(name), HttpUtility.UrlDecode(value)); + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInput.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInput.cs new file mode 100644 index 0000000000..c6585b197c --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInput.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OSHttpServer +{ + /// + /// Contains some kind of input from the browser/client. + /// can be QueryString, form data or any other request body content. + /// + public class HttpInput : IHttpInput + { + /// Representation of a non-initialized class instance + public static readonly HttpInput Empty = new HttpInput("Empty", true); + private readonly IDictionary _items = new Dictionary(); + private string _name; + + /// Variable telling the class that it is non-initialized + protected readonly bool _ignoreChanges; + + /// + /// Initializes a new instance of the class. + /// + /// form name. + public HttpInput(string name) + { + Name = name; + } + + /// + /// Initializes a new instance of the class. + /// + /// form name. + /// if set to true all changes will be ignored. + /// this constructor should only be used by Empty + protected HttpInput(string name, bool ignoreChanges) + { + _name = name; + _ignoreChanges = ignoreChanges; + } + + /// Creates a deep copy of the HttpInput class + /// The object to copy + /// The function makes a deep copy of quite a lot which can be slow + protected HttpInput(HttpInput input) + { + foreach (HttpInputItem item in input) + _items.Add(item.Name, new HttpInputItem(item)); + + _name = input._name; + _ignoreChanges = input._ignoreChanges; + } + + /// + /// Form name as lower case + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// Add a new element. Form array elements are parsed + /// and added in a correct hierarchy. + /// + /// Name is converted to lower case. + /// + /// name is null. + /// Cannot add stuff to . + public void Add(string name, string value) + { + if (name == null) + throw new ArgumentNullException("name"); + if (_ignoreChanges) + throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty."); + + // Check if it's a sub item. + // we can have multiple levels of sub items as in user[extension[id]] => user -> extension -> id + int pos = name.IndexOf('['); + if (pos != -1) + { + string name1 = name.Substring(0, pos); + string name2 = ExtractOne(name); + if (!_items.ContainsKey(name1)) + _items.Add(name1, new HttpInputItem(name1, null)); + _items[name1].Add(name2, value); + } + else + { + if (_items.ContainsKey(name)) + _items[name].Add(value); + else + _items.Add(name, new HttpInputItem(name, value)); + } + } + + /// + /// Get a form item. + /// + /// + /// Returns if item was not found. + public HttpInputItem this[string name] + { + get + { + return _items.ContainsKey(name) ? _items[name] : HttpInputItem.Empty; + } + } + + /// + /// Returns true if the class contains a with the corresponding name. + /// + /// The field/query string name + /// True if the value exists + public bool Contains(string name) + { + return _items.ContainsKey(name) && _items[name].Value != null; + } + + /// + /// Parses an item and returns it. + /// This function is primarily used to parse array items as in user[name]. + /// + /// + /// + /// + public static HttpInputItem ParseItem(string name, string value) + { + HttpInputItem item; + + // Check if it's a sub item. + // we can have multiple levels of sub items as in user[extension[id]]] => user -> extension -> id + int pos = name.IndexOf('['); + if (pos != -1) + { + string name1 = name.Substring(0, pos); + string name2 = ExtractOne(name); + item = new HttpInputItem(name1, null); + item.Add(name2, value); + } + else + item = new HttpInputItem(name, value); + + return item; + } + + /// Outputs the instance representing all its values joined together + /// + public override string ToString() + { + string temp = string.Empty; + foreach (KeyValuePair item in _items) + temp += item.Value.ToString(Name); + return temp; + } + + /// Returns all items as an unescaped query string. + /// + public string ToString(bool asQueryString) + { + if (!asQueryString) + return ToString(); + + string temp = string.Empty; + foreach (KeyValuePair item in _items) + temp += item.Value.ToString(null, true) + '&'; + + return temp.Length > 0 ? temp.Substring(0, temp.Length - 1) : string.Empty; + } + + /// + /// Extracts one parameter from an array + /// + /// Containing the string array + /// All but the first value + /// + /// string test1 = ExtractOne("system[user][extension][id]"); + /// string test2 = ExtractOne(test1); + /// string test3 = ExtractOne(test2); + /// // test1 = user[extension][id] + /// // test2 = extension[id] + /// // test3 = id + /// + public static string ExtractOne(string value) + { + int pos = value.IndexOf('['); + if (pos != -1) + { + ++pos; + int gotMore = value.IndexOf('[', pos + 1); + if (gotMore != -1) + value = value.Substring(pos, gotMore - pos - 1) + value.Substring(gotMore); + else + value = value.Substring(pos, value.Length - pos - 1); + } + return value; + } + + /// Resets all data contained by class + virtual public void Clear() + { + _name = string.Empty; + _items.Clear(); + } + + /// + ///Returns an enumerator that iterates through the collection. + /// + /// + /// + ///A that can be used to iterate through the collection. + /// + ///1 + IEnumerator IEnumerable.GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + + /// + ///Returns an enumerator that iterates through a collection. + /// + /// + /// + ///An object that can be used to iterate through the collection. + /// + ///2 + public IEnumerator GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + } + + /// + /// Base class for request data containers + /// + public interface IHttpInput : IEnumerable + { + /// + /// Adds a parameter mapped to the presented name + /// + /// The name to map the parameter to + /// The parameter value + void Add(string name, string value); + + /// + /// Returns a request parameter + /// + /// The name associated with the parameter + /// + HttpInputItem this[string name] + { get; } + + /// + /// Returns true if the container contains the requested parameter + /// + /// Parameter id + /// True if parameter exists + bool Contains(string name); + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInputItem.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInputItem.cs new file mode 100644 index 0000000000..0ab7690618 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpInputItem.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OSHttpServer +{ + /// + /// represents a HTTP input item. Each item can have multiple sub items, a sub item + /// is made in a HTML form by using square brackets + /// + /// + /// // becomes: + /// Console.WriteLine("Value: {0}", form["user"]["FirstName"].Value); + /// + /// + /// All names in a form SHOULD be in lowercase. + /// + public class HttpInputItem : IHttpInput + { + /// Representation of a non-initialized . + public static readonly HttpInputItem Empty = new HttpInputItem(string.Empty, true); + private readonly IDictionary _items = new Dictionary(); + private readonly List _values = new List(); + private string _name; + private readonly bool _ignoreChanges; + + /// + /// Initializes an input item setting its name/identifier and value + /// + /// Parameter name/id + /// Parameter value + public HttpInputItem(string name, string value) + { + Name = name; + Add(value); + } + + private HttpInputItem(string name, bool ignore) + { + Name = name; + _ignoreChanges = ignore; + } + + /// Creates a deep copy of the item specified + /// The item to copy + /// The function makes a deep copy of quite a lot which can be slow + public HttpInputItem(HttpInputItem item) + { + foreach (KeyValuePair pair in item._items) + _items.Add(pair.Key, pair.Value); + + foreach (string value in item._values) + _values.Add(value); + + _ignoreChanges = item._ignoreChanges; + _name = item.Name; + } + + /// + /// Number of values + /// + public int Count + { + get { return _values.Count; } + } + + /// + /// Get a sub item + /// + /// name in lower case. + /// if no item was found. + public HttpInputItem this[string name] + { + get { + return _items.ContainsKey(name) ? _items[name] : Empty; + } + } + + /// + /// Name of item (in lower case). + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// Returns the first value, or null if no value exist. + /// + public string Value + { + get { + return _values.Count == 0 ? null : _values[0]; + } + set + { + if (_values.Count == 0) + _values.Add(value); + else + _values[0] = value; + } + } + + /// + /// Returns the last value, or null if no value exist. + /// + public string LastValue + { + get + { + return _values.Count == 0 ? null : _values[_values.Count - 1]; + } + } + + /// + /// Returns the list with values. + /// + public IList Values + { + get { return _values.AsReadOnly(); } + } + + + /// + /// Add another value to this item + /// + /// Value to add. + /// Cannot add stuff to . + public void Add(string value) + { + if (value == null) + return; + if (_ignoreChanges) + throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty."); + + _values.Add(value); + } + + /// + /// checks if a sub-item exists (and has a value). + /// + /// name in lower case + /// true if the sub-item exists and has a value; otherwise false. + public bool Contains(string name) + { + return _items.ContainsKey(name) && _items[name].Value != null; + } + + /// Returns a formatted representation of the instance with the values of all contained parameters + public override string ToString() + { + return ToString(string.Empty); + } + + /// + /// Outputs the string in a formatted manner + /// + /// A prefix to append, used internally + /// produce a query string + public string ToString(string prefix, bool asQuerySting) + { + string name; + if (string.IsNullOrEmpty(prefix)) + name = Name; + else + name = prefix + "[" + Name + "]"; + + if (asQuerySting) + { + string temp; + if (_values.Count == 0 && _items.Count > 0) + temp = string.Empty; + else + temp = name; + + if (_values.Count > 0) + { + temp += '='; + foreach (string value in _values) + temp += value + ','; + temp = temp.Remove(temp.Length - 1, 1); + } + + foreach (KeyValuePair item in _items) + temp += item.Value.ToString(name, true) + '&'; + + return _items.Count > 0 ? temp.Substring(0, temp.Length - 1) : temp; + } + else + { + string temp = name; + if (_values.Count > 0) + { + temp += " = "; + foreach (string value in _values) + temp += value + ", "; + temp = temp.Remove(temp.Length - 2, 2); + } + temp += Environment.NewLine; + + foreach (KeyValuePair item in _items) + temp += item.Value.ToString(name, false); + return temp; + } + } + + #region IHttpInput Members + + /// + /// + /// + /// name in lower case + /// + HttpInputItem IHttpInput.this[string name] + { + get + { + return _items.ContainsKey(name) ? _items[name] : Empty; + } + } + + /// + /// Add a sub item. + /// + /// Can contain array formatting, the item is then parsed and added in multiple levels + /// Value to add. + /// Argument is null. + /// Cannot add stuff to . + public void Add(string name, string value) + { + if (name == null && value != null) + throw new ArgumentNullException("name"); + if (name == null) + return; + if (_ignoreChanges) + throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty."); + + int pos = name.IndexOf('['); + if (pos != -1) + { + string name1 = name.Substring(0, pos); + string name2 = HttpInput.ExtractOne(name); + if (!_items.ContainsKey(name1)) + _items.Add(name1, new HttpInputItem(name1, null)); + _items[name1].Add(name2, value); + /* + HttpInputItem item = HttpInput.ParseItem(name, value); + + // Add the value to an existing sub item + if (_items.ContainsKey(item.Name)) + _items[item.Name].Add(item.Value); + else + _items.Add(item.Name, item); + */ + } + else + { + if (_items.ContainsKey(name)) + _items[name].Add(value); + else + _items.Add(name, new HttpInputItem(name, value)); + } + } + + #endregion + + /// + ///Returns an enumerator that iterates through the collection. + /// + /// + /// + ///A that can be used to iterate through the collection. + /// + ///1 + IEnumerator IEnumerable.GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + + #region IEnumerable Members + + /// + ///Returns an enumerator that iterates through a collection. + /// + /// + /// + ///An object that can be used to iterate through the collection. + /// + ///2 + public IEnumerator GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + #endregion + + /// + /// Outputs the string in a formatted manner + /// + /// A prefix to append, used internally + /// + public string ToString(string prefix) + { + return ToString(prefix, false); + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpListener.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpListener.cs new file mode 100644 index 0000000000..2c7bd8a0dc --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpListener.cs @@ -0,0 +1,248 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; + +namespace OSHttpServer +{ + public class OSHttpListener: IDisposable + { + private readonly IPAddress m_address; + private readonly X509Certificate m_certificate; + private readonly IHttpContextFactory m_contextFactory; + private readonly int m_port; + private readonly ManualResetEvent m_shutdownEvent = new ManualResetEvent(false); + private readonly SslProtocols m_sslProtocol = SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2; + private TcpListener m_listener; + private ILogWriter m_logWriter = NullLogWriter.Instance; + private int m_pendingAccepts; + private bool m_shutdown; + protected RemoteCertificateValidationCallback m_clientCertValCallback = null; + + public event EventHandler Accepted; + public event ExceptionHandler ExceptionThrown; + public event EventHandler RequestReceived; + + /// + /// Listen for regular HTTP connections + /// + /// IP Address to accept connections on + /// TCP Port to listen on, default HTTP port is 80. + /// Factory used to create es. + /// address is null. + /// Port must be a positive number. + protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory) + { + m_address = address; + m_port = port; + m_contextFactory = factory; + m_contextFactory.RequestReceived += OnRequestReceived; + } + + /// + /// Initializes a new instance of the class. + /// + /// IP Address to accept connections on + /// TCP Port to listen on, default HTTPS port is 443 + /// Factory used to create es. + /// Certificate to use + /// which HTTPS protocol to use, default is TLS. + protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate, + SslProtocols protocol) + : this(address, port, factory, certificate) + { + m_sslProtocol = protocol; + } + + /// + /// Initializes a new instance of the class. + /// + /// IP Address to accept connections on + /// TCP Port to listen on, default HTTPS port is 443 + /// Factory used to create es. + /// Certificate to use + protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate) + : this(address, port, factory) + { + m_certificate = certificate; + } + + public static OSHttpListener Create(IPAddress address, int port) + { + RequestParserFactory requestFactory = new RequestParserFactory(); + HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory); + return new OSHttpListener(address, port, factory); + } + + public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate) + { + RequestParserFactory requestFactory = new RequestParserFactory(); + HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory); + return new OSHttpListener(address, port, factory, certificate); + } + + public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol) + { + RequestParserFactory requestFactory = new RequestParserFactory(); + HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory); + return new OSHttpListener(address, port, factory, certificate, protocol); + } + + private void OnRequestReceived(object sender, RequestEventArgs e) + { + RequestReceived?.Invoke(sender, e); + } + + + public RemoteCertificateValidationCallback CertificateValidationCallback + { + set { m_clientCertValCallback = value; } + } + + /// + /// Gives you a change to receive log entries for all internals of the HTTP library. + /// + /// + /// You may not switch log writer after starting the listener. + /// + public ILogWriter LogWriter + { + get { return m_logWriter; } + set + { + m_logWriter = value ?? NullLogWriter.Instance; + if (m_certificate != null) + m_logWriter.Write(this, LogPrio.Info, + "HTTPS(" + m_sslProtocol + ") listening on " + m_address + ":" + m_port); + else + m_logWriter.Write(this, LogPrio.Info, "HTTP listening on " + m_address + ":" + m_port); + } + } + + /// + /// True if we should turn on trace logs. + /// + public bool UseTraceLogs { get; set; } + + + /// Exception. + private void OnAccept(IAsyncResult ar) + { + bool beginAcceptCalled = false; + try + { + int count = Interlocked.Decrement(ref m_pendingAccepts); + if (m_shutdown) + { + if (count == 0) + m_shutdownEvent.Set(); + return; + } + + Interlocked.Increment(ref m_pendingAccepts); + m_listener.BeginAcceptSocket(OnAccept, null); + beginAcceptCalled = true; + Socket socket = m_listener.EndAcceptSocket(ar); + + if (!OnAcceptingSocket(socket)) + { + socket.Disconnect(true); + return; + } + + m_logWriter.Write(this, LogPrio.Debug, "Accepted connection from: " + socket.RemoteEndPoint); + + if (m_certificate != null) + m_contextFactory.CreateSecureContext(socket, m_certificate, m_sslProtocol, m_clientCertValCallback); + else + m_contextFactory.CreateContext(socket); + } + catch (Exception err) + { + m_logWriter.Write(this, LogPrio.Debug, err.Message); + ExceptionThrown?.Invoke(this, err); + + if (!beginAcceptCalled) + RetryBeginAccept(); + } + } + + /// + /// Will try to accept connections one more time. + /// + /// If any exceptions is thrown. + private void RetryBeginAccept() + { + try + { + m_logWriter.Write(this, LogPrio.Error, "Trying to accept connections again."); + m_listener.BeginAcceptSocket(OnAccept, null); + } + catch (Exception err) + { + m_logWriter.Write(this, LogPrio.Fatal, err.Message); + ExceptionThrown?.Invoke(this, err); + } + } + + /// + /// Can be used to create filtering of new connections. + /// + /// Accepted socket + /// true if connection can be accepted; otherwise false. + protected bool OnAcceptingSocket(Socket socket) + { + ClientAcceptedEventArgs args = new ClientAcceptedEventArgs(socket); + Accepted?.Invoke(this, args); + return !args.Revoked; + } + + /// + /// Start listen for new connections + /// + /// Number of connections that can stand in a queue to be accepted. + /// Listener have already been started. + public void Start(int backlog) + { + if (m_listener != null) + throw new InvalidOperationException("Listener have already been started."); + + m_listener = new TcpListener(m_address, m_port); + m_listener.Start(backlog); + Interlocked.Increment(ref m_pendingAccepts); + m_listener.BeginAcceptSocket(OnAccept, null); + } + + /// + /// Stop the listener + /// + /// + public void Stop() + { + m_shutdown = true; + m_contextFactory.Shutdown(); + m_listener.Stop(); + if (!m_shutdownEvent.WaitOne()) + m_logWriter.Write(this, LogPrio.Error, "Failed to shutdown listener properly."); + m_listener = null; + Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + if (m_shutdownEvent != null) + { + m_shutdownEvent.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpParam.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpParam.cs new file mode 100644 index 0000000000..c1a14b6a57 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpParam.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OSHttpServer +{ + /// + /// Returns item either from a form or a query string (checks them in that order) + /// + public class HttpParam : IHttpInput + { + /// Representation of a non-initialized HttpParam + public static readonly HttpParam Empty = new HttpParam(HttpInput.Empty, HttpInput.Empty); + + private IHttpInput m_form; + private IHttpInput m_query; + + private List _items = new List(); + + /// Initialises the class to hold a value either from a post request or a querystring request + public HttpParam(IHttpInput form, IHttpInput query) + { + m_form = form; + m_query = query; + } + + #region IHttpInput Members + + /// + /// The add method is not availible for HttpParam + /// since HttpParam checks both Request.Form and Request.QueryString + /// + /// name identifying the value + /// value to add + /// + [Obsolete("Not implemented for HttpParam")] + public void Add(string name, string value) + { + throw new NotImplementedException(); + } + + /// + /// Checks whether the form or querystring has the specified value + /// + /// Name, case sensitive + /// true if found; otherwise false. + public bool Contains(string name) + { + return m_form.Contains(name) || m_query.Contains(name); + } + + /// + /// Fetch an item from the form or querystring (in that order). + /// + /// + /// Item if found; otherwise HttpInputItem.EmptyLanguageNode + public HttpInputItem this[string name] + { + get + { + if (m_form[name] != HttpInputItem.Empty) + return m_form[name]; + else + return m_query[name]; + } + } + + #endregion + + internal void SetQueryString(HttpInput query) + { + m_query = query; + } + + internal void SetForm(HttpInput form) + { + m_form = form; + } + + /// + ///Returns an enumerator that iterates through the collection. + /// + /// + /// + ///A that can be used to iterate through the collection. + /// + ///1 + IEnumerator IEnumerable.GetEnumerator() + { + List items = new List(m_query); + items.AddRange(m_form); + return items.GetEnumerator(); + } + + #region IEnumerable Members + + /// + ///Returns an enumerator that iterates through a collection. + /// + /// + /// + ///An object that can be used to iterate through the collection. + /// + ///2 + public IEnumerator GetEnumerator() + { + List items = new List(m_query); + items.AddRange(m_form); + return items.GetEnumerator(); + } + + #endregion + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequest.cs new file mode 100644 index 0000000000..10a3745eb0 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequest.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using OSHttpServer.Exceptions; + + +namespace OSHttpServer +{ + /// + /// Contains server side HTTP request information. + /// + public class HttpRequest : IHttpRequest + { + /// + /// Chars used to split an URL path into multiple parts. + /// + public static readonly char[] UriSplitters = new[] { '/' }; + public static uint baseID = 0; + + private readonly NameValueCollection m_headers = new NameValueCollection(); + private readonly HttpParam m_param = new HttpParam(HttpInput.Empty, HttpInput.Empty); + private Stream m_body = new MemoryStream(); + private int m_bodyBytesLeft; + private ConnectionType m_connection = ConnectionType.KeepAlive; + private int m_contentLength; + private string m_httpVersion = string.Empty; + private string m_method = string.Empty; + private HttpInput m_queryString = HttpInput.Empty; + private Uri m_uri = HttpHelper.EmptyUri; + private string m_uriPath; + public readonly IHttpClientContext m_context; + + public HttpRequest(IHttpClientContext pContext) + { + ID = ++baseID; + m_context = pContext; + } + + public uint ID { get; private set; } + + /// + /// Gets or sets a value indicating whether this is secure. + /// + public bool Secure { get; internal set; } + + public IHttpClientContext Context { get { return m_context; } } + /// + /// Path and query (will be merged with the host header) and put in Uri + /// + /// + public string UriPath + { + get { return m_uriPath; } + set + { + m_uriPath = value; + int pos = m_uriPath.IndexOf('?'); + if (pos != -1) + { + m_queryString = HttpHelper.ParseQueryString(m_uriPath.Substring(pos + 1)); + m_param.SetQueryString(m_queryString); + string path = m_uriPath.Substring(0, pos); + m_uriPath = System.Web.HttpUtility.UrlDecode(path) + "?" + m_uriPath.Substring(pos + 1); + UriParts = value.Substring(0, pos).Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries); + } + else + { + m_uriPath = System.Web.HttpUtility.UrlDecode(m_uriPath); + UriParts = value.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries); + } + } + } + + /// + /// Assign a form. + /// + /// + /* + internal void AssignForm(HttpForm form) + { + _form = form; + } + */ + + #region IHttpRequest Members + + /// + /// Gets whether the body is complete. + /// + public bool BodyIsComplete + { + get { return m_bodyBytesLeft == 0; } + } + + /// + /// Gets kind of types accepted by the client. + /// + public string[] AcceptTypes { get; private set; } + + /// + /// Gets or sets body stream. + /// + public Stream Body + { + get { return m_body; } + set { m_body = value; } + } + + /// + /// Gets or sets kind of connection used for the session. + /// + public ConnectionType Connection + { + get { return m_connection; } + set { m_connection = value; } + } + + /// + /// Gets or sets number of bytes in the body. + /// + public int ContentLength + { + get { return m_contentLength; } + set + { + m_contentLength = value; + m_bodyBytesLeft = value; + } + } + + /// + /// Gets headers sent by the client. + /// + public NameValueCollection Headers + { + get { return m_headers; } + } + + /// + /// Gets or sets version of HTTP protocol that's used. + /// + /// + /// Probably or . + /// + /// + public string HttpVersion + { + get { return m_httpVersion; } + set { m_httpVersion = value; } + } + + /// + /// Gets or sets requested method. + /// + /// + /// + /// Will always be in upper case. + /// + /// + public string Method + { + get { return m_method; } + set { m_method = value; } + } + + /// + /// Gets variables sent in the query string + /// + public HttpInput QueryString + { + get { return m_queryString; } + } + + + /// + /// Gets or sets requested URI. + /// + public Uri Uri + { + get { return m_uri; } + set + { + m_uri = value ?? HttpHelper.EmptyUri; + UriParts = m_uri.AbsolutePath.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries); + } + } + + /// + /// Uri absolute path splitted into parts. + /// + /// + /// // uri is: http://gauffin.com/code/tiny/ + /// Console.WriteLine(request.UriParts[0]); // result: code + /// Console.WriteLine(request.UriParts[1]); // result: tiny + /// + /// + /// If you're using controllers than the first part is controller name, + /// the second part is method name and the third part is Id property. + /// + /// + public string[] UriParts { get; private set; } + + /// + /// Gets parameter from or . + /// + public HttpParam Param + { + get { return m_param; } + } + + /// + /// Gets form parameters. + /// + /* + public HttpForm Form + { + get { return _form; } + } + */ + /// + /// Gets whether the request was made by Ajax (Asynchronous JavaScript) + /// + public bool IsAjax { get; private set; } + + /// + /// Gets cookies that was sent with the request. + /// + public RequestCookies Cookies { get; private set; } + + /// + ///Creates a new object that is a copy of the current instance. + /// + /// + /// + ///A new object that is a copy of this instance. + /// + ///2 + public object Clone() + { + // this method was mainly created for testing. + // dont use it that much... + var request = new HttpRequest(Context); + request.Method = m_method; + if (AcceptTypes != null) + { + request.AcceptTypes = new string[AcceptTypes.Length]; + AcceptTypes.CopyTo(request.AcceptTypes, 0); + } + request.m_httpVersion = m_httpVersion; + request.m_queryString = m_queryString; + request.Uri = m_uri; + + var buffer = new byte[m_body.Length]; + m_body.Read(buffer, 0, (int)m_body.Length); + request.Body = new MemoryStream(); + request.Body.Write(buffer, 0, buffer.Length); + request.Body.Seek(0, SeekOrigin.Begin); + request.Body.Flush(); + + request.m_headers.Clear(); + foreach (string key in m_headers) + { + string[] values = m_headers.GetValues(key); + if (values != null) + foreach (string value in values) + request.AddHeader(key, value); + } + return request; + } + + /// + /// Decode body into a form. + /// + /// A list with form decoders. + /// If body contents is not valid for the chosen decoder. + /// If body is still being transferred. + /* + public void DecodeBody(FormDecoderProvider providers) + { + if (_bodyBytesLeft > 0) + throw new InvalidOperationException("Body have not yet been completed."); + + _form = providers.Decode(_headers["content-type"], _body, Encoding.UTF8); + if (_form != HttpInput.Empty) + _param.SetForm(_form); + } + */ + /// + /// Cookies + /// + ///the cookies + public void SetCookies(RequestCookies cookies) + { + Cookies = cookies; + } + /* + /// + /// Create a response object. + /// + /// A new . + public IHttpResponse CreateResponse(IHttpClientContext context) + { + return new HttpResponse(context, this); + } + */ + /// + /// Called during parsing of a . + /// + /// Name of the header, should not be URL encoded + /// Value of the header, should not be URL encoded + /// If a header is incorrect. + public void AddHeader(string name, string value) + { + if (string.IsNullOrEmpty(name)) + throw new BadRequestException("Invalid header name: " + name ?? ""); + if (string.IsNullOrEmpty(value)) + throw new BadRequestException("Header '" + name + "' do not contain a value."); + + name = name.ToLowerInvariant(); + + switch (name) + { + case "http_x_requested_with": + case "x-requested-with": + if (string.Compare(value, "XMLHttpRequest", true) == 0) + IsAjax = true; + break; + case "accept": + AcceptTypes = value.Split(','); + for (int i = 0; i < AcceptTypes.Length; ++i) + AcceptTypes[i] = AcceptTypes[i].Trim(); + break; + case "content-length": + int t; + if (!int.TryParse(value, out t)) + throw new BadRequestException("Invalid content length."); + ContentLength = t; + break; //todo: maybe throw an exception + case "host": + try + { + m_uri = new Uri(Secure ? "https://" : "http://" + value + m_uriPath); + UriParts = m_uri.AbsolutePath.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries); + } + catch (UriFormatException err) + { + throw new BadRequestException("Failed to parse uri: " + value + m_uriPath, err); + } + break; + case "remote_addr": + // to prevent hacking (since it's added by IHttpClientContext before parsing). + if (m_headers[name] == null) + m_headers.Add(name, value); + break; + + case "connection": + if (string.Compare(value, "close", true) == 0) + Connection = ConnectionType.Close; + else if (value.StartsWith("keep-alive", StringComparison.CurrentCultureIgnoreCase)) + Connection = ConnectionType.KeepAlive; + else if (value.StartsWith("Upgrade", StringComparison.CurrentCultureIgnoreCase)) + Connection = ConnectionType.KeepAlive; + else + throw new BadRequestException("Unknown 'Connection' header type."); + break; + + case "expect": + if (value.Contains("100-continue")) + { + + } + m_headers.Add(name, value); + break; + + default: + m_headers.Add(name, value); + break; + } + } + + /// + /// Add bytes to the body + /// + /// buffer to read bytes from + /// where to start read + /// number of bytes to read + /// Number of bytes actually read (same as length unless we got all body bytes). + /// If body is not writable + /// bytes is null. + /// offset is out of range. + public int AddToBody(byte[] bytes, int offset, int length) + { + if (bytes == null) + throw new ArgumentNullException("bytes"); + if (offset + length > bytes.Length) + throw new ArgumentOutOfRangeException("offset"); + if (length == 0) + return 0; + if (!m_body.CanWrite) + throw new InvalidOperationException("Body is not writable."); + + if (length > m_bodyBytesLeft) + { + length = m_bodyBytesLeft; + } + + m_body.Write(bytes, offset, length); + m_bodyBytesLeft -= length; + + return length; + } + + /// + /// Clear everything in the request + /// + public void Clear() + { + if (m_body != null && m_body.CanRead) + m_body.Dispose(); + m_body = null; + m_contentLength = 0; + m_method = string.Empty; + m_uri = HttpHelper.EmptyUri; + m_queryString = HttpInput.Empty; + m_bodyBytesLeft = 0; + m_headers.Clear(); + m_connection = ConnectionType.KeepAlive; + IsAjax = false; + //_form.Clear(); + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequestParser.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequestParser.cs new file mode 100644 index 0000000000..2f5d5bdd21 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpRequestParser.cs @@ -0,0 +1,417 @@ +using System; +using System.Text; +using OSHttpServer.Exceptions; + +namespace OSHttpServer.Parser +{ + /// + /// Parses a HTTP request directly from a stream + /// + public class HttpRequestParser : IHttpRequestParser + { + private ILogWriter m_log; + private readonly BodyEventArgs m_bodyArgs = new BodyEventArgs(); + private readonly HeaderEventArgs m_headerArgs = new HeaderEventArgs(); + private readonly RequestLineEventArgs m_requestLineArgs = new RequestLineEventArgs(); + private string m_curHeaderName = string.Empty; + private string m_curHeaderValue = string.Empty; + private int m_bodyBytesLeft; + + /// + /// More body bytes have been received. + /// + public event EventHandler BodyBytesReceived; + + /// + /// Request line have been received. + /// + public event EventHandler RequestLineReceived; + + /// + /// A header have been received. + /// + public event EventHandler HeaderReceived; + + + /// + /// Create a new request parser + /// + /// delegate receiving log entries. + public HttpRequestParser(ILogWriter logWriter) + { + m_log = logWriter ?? NullLogWriter.Instance; + } + + /// + /// Add a number of bytes to the body + /// + /// buffer containing more body bytes. + /// starting offset in buffer + /// number of bytes, from offset, to read. + /// offset to continue from. + private int AddToBody(byte[] buffer, int offset, int count) + { + // got all bytes we need, or just a few of them? + int bytesUsed = count > m_bodyBytesLeft ? m_bodyBytesLeft : count; + m_bodyArgs.Buffer = buffer; + m_bodyArgs.Offset = offset; + m_bodyArgs.Count = bytesUsed; + BodyBytesReceived?.Invoke(this, m_bodyArgs); + + m_bodyBytesLeft -= bytesUsed; + if (m_bodyBytesLeft == 0) + { + // got a complete request. + m_log.Write(this, LogPrio.Trace, "Request parsed successfully."); + OnRequestCompleted(); + Clear(); + } + + return offset + bytesUsed; + } + + /// + /// Remove all state information for the request. + /// + public void Clear() + { + m_bodyBytesLeft = 0; + m_curHeaderName = string.Empty; + m_curHeaderValue = string.Empty; + CurrentState = RequestParserState.FirstLine; + } + + /// + /// Gets or sets the log writer. + /// + public ILogWriter LogWriter + { + get { return m_log; } + set { m_log = value ?? NullLogWriter.Instance; } + } + + /// + /// Parse request line + /// + /// + /// If line is incorrect + /// Expects the following format: "Method SP Request-URI SP HTTP-Version CRLF" + protected void OnFirstLine(string value) + { + // + //todo: In the interest of robustness, servers SHOULD ignore any empty line(s) received where a Request-Line is expected. + // In other words, if the server is reading the protocol stream at the beginning of a message and receives a CRLF first, it should ignore the CRLF. + // + m_log.Write(this, LogPrio.Debug, "Got request: " + value); + + //Request-Line = Method SP Request-URI SP HTTP-Version CRLF + int pos = value.IndexOf(' '); + if (pos == -1 || pos + 1 >= value.Length) + { + m_log.Write(this, LogPrio.Warning, "Invalid request line, missing Method. Line: " + value); + throw new BadRequestException("Invalid request line, missing Method. Line: " + value); + } + + string method = value.Substring(0, pos).ToUpper(); + int oldPos = pos + 1; + pos = value.IndexOf(' ', oldPos); + if (pos == -1) + { + m_log.Write(this, LogPrio.Warning, "Invalid request line, missing URI. Line: " + value); + throw new BadRequestException("Invalid request line, missing URI. Line: " + value); + } + string path = value.Substring(oldPos, pos - oldPos); + if (path.Length > 4196) + throw new BadRequestException("Too long URI."); + + if (pos + 1 >= value.Length) + { + m_log.Write(this, LogPrio.Warning, "Invalid request line, missing HTTP-Version. Line: " + value); + throw new BadRequestException("Invalid request line, missing HTTP-Version. Line: " + value); + } + string version = value.Substring(pos + 1); + if (version.Length < 4 || string.Compare(version.Substring(0, 4), "HTTP", true) != 0) + { + m_log.Write(this, LogPrio.Warning, "Invalid HTTP version in request line. Line: " + value); + throw new BadRequestException("Invalid HTTP version in Request line. Line: " + value); + } + + m_requestLineArgs.HttpMethod = method; + m_requestLineArgs.HttpVersion = version; + m_requestLineArgs.UriPath = path; + RequestLineReceived(this, m_requestLineArgs); + } + + /// + /// We've parsed a new header. + /// + /// Name in lower case + /// Value, unmodified. + /// If content length cannot be parsed. + protected void OnHeader(string name, string value) + { + m_headerArgs.Name = name; + m_headerArgs.Value = value; + if (string.Compare(name, "content-length", true) == 0) + { + if (!int.TryParse(value, out m_bodyBytesLeft)) + throw new BadRequestException("Content length is not a number."); + } + + HeaderReceived?.Invoke(this, m_headerArgs); + } + + private void OnRequestCompleted() + { + RequestCompleted?.Invoke(this, EventArgs.Empty); + } + + #region IHttpRequestParser Members + + /// + /// Current state in parser. + /// + public RequestParserState CurrentState { get; private set; } + + /// + /// Parse a message + /// + /// bytes to parse. + /// where in buffer that parsing should start + /// number of bytes to parse, starting on . + /// offset (where to start parsing next). + /// BadRequestException. + public int Parse(byte[] buffer, int offset, int count) + { + // add body bytes + if (CurrentState == RequestParserState.Body) + { + return AddToBody(buffer, offset, count); + } + +#if DEBUG + string temp = Encoding.ASCII.GetString(buffer, offset, count); + _log.Write(this, LogPrio.Trace, "\r\n\r\n HTTP MESSAGE: " + temp + "\r\n"); +#endif + + int currentLine = 1; + int startPos = -1; + + // set start pos since this is from an partial request + if (CurrentState == RequestParserState.HeaderValue) + startPos = 0; + + int endOfBufferPos = offset + count; + + // + // Handled bytes are used to keep track of the number of bytes processed. + // We do this since we can handle partial requests (to be able to check headers and abort + // invalid requests directly without having to process the whole header / body). + // + int handledBytes = 0; + + for (int currentPos = offset; currentPos < endOfBufferPos; ++currentPos) + { + var ch = (char)buffer[currentPos]; + char nextCh = endOfBufferPos > currentPos + 1 ? (char)buffer[currentPos + 1] : char.MinValue; + + if (ch == '\r') + ++currentLine; + + switch (CurrentState) + { + case RequestParserState.FirstLine: + if (currentPos == 8191) + { + m_log.Write(this, LogPrio.Warning, "HTTP Request is too large."); + throw new BadRequestException("Too large request line."); + } + if (char.IsLetterOrDigit(ch) && startPos == -1) + startPos = currentPos; + if (startPos == -1 && (ch != '\r' || nextCh != '\n')) + { + m_log.Write(this, LogPrio.Warning, "Request line is not found."); + throw new BadRequestException("Invalid request line."); + } + if (startPos != -1 && (ch == '\r' || ch == '\n')) + { + int size = GetLineBreakSize(buffer, currentPos); + OnFirstLine(Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos)); + CurrentState = CurrentState + 1; + currentPos += size - 1; + handledBytes = currentPos + size - 1; + startPos = -1; + } + break; + case RequestParserState.HeaderName: + if (ch == '\r' || ch == '\n') + { + currentPos += GetLineBreakSize(buffer, currentPos); + if (m_bodyBytesLeft == 0) + { + CurrentState = RequestParserState.FirstLine; + m_log.Write(this, LogPrio.Trace, "Request parsed successfully (no content)."); + OnRequestCompleted(); + Clear(); + return currentPos; + } + + CurrentState = RequestParserState.Body; + if (currentPos + 1 < endOfBufferPos) + { + m_log.Write(this, LogPrio.Trace, "Adding bytes to the body"); + return AddToBody(buffer, currentPos, endOfBufferPos - currentPos); + } + + return currentPos; + } + if (char.IsWhiteSpace(ch) || ch == ':') + { + if (startPos == -1) + { + m_log.Write(this, LogPrio.Warning, + "Expected header name, got colon on line " + currentLine); + throw new BadRequestException("Expected header name, got colon on line " + currentLine); + } + m_curHeaderName = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); + handledBytes = currentPos + 1; + startPos = -1; + CurrentState = CurrentState + 1; + if (ch == ':') + CurrentState = CurrentState + 1; + } + else if (startPos == -1) + startPos = currentPos; + else if (!char.IsLetterOrDigit(ch) && ch != '-') + { + m_log.Write(this, LogPrio.Warning, "Invalid character in header name on line " + currentLine); + throw new BadRequestException("Invalid character in header name on line " + currentLine); + } + if (startPos != -1 && currentPos - startPos > 200) + { + m_log.Write(this, LogPrio.Warning, "Invalid header name on line " + currentLine); + throw new BadRequestException("Invalid header name on line " + currentLine); + } + break; + case RequestParserState.AfterName: + if (ch == ':') + { + handledBytes = currentPos + 1; + CurrentState = CurrentState + 1; + } + break; + case RequestParserState.Between: + { + if (ch == ' ' || ch == '\t') + continue; + int newLineSize = GetLineBreakSize(buffer, currentPos); + if (newLineSize > 0 && currentPos + newLineSize < endOfBufferPos && + char.IsWhiteSpace((char)buffer[currentPos + newLineSize])) + { + ++currentPos; + continue; + } + startPos = currentPos; + CurrentState = CurrentState + 1; + handledBytes = currentPos; + continue; + } + case RequestParserState.HeaderValue: + { + if (ch != '\r' && ch != '\n') + continue; + int newLineSize = GetLineBreakSize(buffer, currentPos); + if (startPos == -1) + continue; // allow new lines before start of value + + if (m_curHeaderName == string.Empty) + throw new BadRequestException("Missing header on line " + currentLine); + if (startPos == -1) + { + m_log.Write(this, LogPrio.Warning, "Missing header value for '" + m_curHeaderName); + throw new BadRequestException("Missing header value for '" + m_curHeaderName); + } + if (currentPos - startPos > 8190) + { + m_log.Write(this, LogPrio.Warning, "Too large header value on line " + currentLine); + throw new BadRequestException("Too large header value on line " + currentLine); + } + + // Header fields can be extended over multiple lines by preceding each extra line with at + // least one SP or HT. + if (endOfBufferPos > currentPos + newLineSize + && (buffer[currentPos + newLineSize] == ' ' || buffer[currentPos + newLineSize] == '\t')) + { + if (startPos != -1) + m_curHeaderValue = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); + + m_log.Write(this, LogPrio.Trace, "Header value is on multiple lines."); + CurrentState = RequestParserState.Between; + startPos = -1; + currentPos += newLineSize - 1; + handledBytes = currentPos + newLineSize - 1; + continue; + } + + m_curHeaderValue += Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos); + m_log.Write(this, LogPrio.Trace, "Header [" + m_curHeaderName + ": " + m_curHeaderValue + "]"); + OnHeader(m_curHeaderName, m_curHeaderValue); + + startPos = -1; + CurrentState = RequestParserState.HeaderName; + m_curHeaderValue = string.Empty; + m_curHeaderName = string.Empty; + ++currentPos; + handledBytes = currentPos + 1; + + // Check if we got a colon so we can cut header name, or crlf for end of header. + bool canContinue = false; + for (int j = currentPos; j < endOfBufferPos; ++j) + { + if (buffer[j] != ':' && buffer[j] != '\r' && buffer[j] != '\n') continue; + canContinue = true; + break; + } + if (!canContinue) + { + m_log.Write(this, LogPrio.Trace, "Cant continue, no colon."); + return currentPos + 1; + } + } + break; + } + } + + return handledBytes; + } + + int GetLineBreakSize(byte[] buffer, int offset) + { + if (buffer[offset] == '\r') + { + if (buffer.Length > offset + 1 && buffer[offset + 1] == '\n') + return 2; + else + throw new BadRequestException("Got invalid linefeed."); + } + else if (buffer[offset] == '\n') + { + if (buffer.Length == offset + 1) + return 1; // linux line feed + if (buffer[offset + 1] != '\r') + return 1; // linux line feed + else + return 2; // win line feed + } + else + return 0; + } + + /// + /// A request have been successfully parsed. + /// + public event EventHandler RequestCompleted; + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpResponse.cs new file mode 100644 index 0000000000..25553a6f32 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpResponse.cs @@ -0,0 +1,670 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace OSHttpServer +{ + public class HttpResponse : IHttpResponse + { + private const string DefaultContentType = "text/html;charset=UTF-8"; + private readonly IHttpClientContext m_context; + private readonly ResponseCookies m_cookies = new ResponseCookies(); + private readonly NameValueCollection m_headers = new NameValueCollection(); + private string m_httpVersion; + private Stream m_body; + private long m_contentLength; + private string m_contentType; + private Encoding m_encoding = Encoding.UTF8; + private int m_keepAlive = 60; + public uint requestID { get; private set; } + public byte[] RawBuffer { get; set; } + public int RawBufferStart { get; set; } + public int RawBufferLen { get; set; } + + internal byte[] m_headerBytes = null; + + /// + /// Initializes a new instance of the class. + /// + /// Client that send the . + /// Contains information of what the client want to receive. + /// cannot be empty. + public HttpResponse(IHttpClientContext context, IHttpRequest request) + { + m_httpVersion = request.HttpVersion; + if (string.IsNullOrEmpty(m_httpVersion)) + m_httpVersion = "HTTP/1.0"; + + Status = HttpStatusCode.OK; + m_context = context; + m_Connetion = request.Connection; + requestID = request.ID; + RawBufferStart = -1; + RawBufferLen = -1; + } + + /// + /// Initializes a new instance of the class. + /// + /// Client that send the . + /// Version of HTTP protocol that the client uses. + /// Type of HTTP connection used. + internal HttpResponse(IHttpClientContext context, string httpVersion, ConnectionType connectionType) + { + Status = HttpStatusCode.OK; + m_context = context; + m_httpVersion = httpVersion; + m_Connetion = connectionType; + } + private ConnectionType m_Connetion; + public ConnectionType Connection + { + get { return m_Connetion; } + set { return; } + } + + private int m_priority = 0; + public int Priority + { + get { return m_priority;} + set { m_priority = (value > 0 && m_priority < 3)? value : 0;} + } + + #region IHttpResponse Members + + /// + /// The body stream is used to cache the body contents + /// before sending everything to the client. It's the simplest + /// way to serve documents. + /// + public Stream Body + { + get + { + if(m_body == null) + m_body = new MemoryStream(); + return m_body; + } + set { m_body = value; } + } + + /// + /// The chunked encoding modifies the body of a message in order to + /// transfer it as a series of chunks, each with its own size indicator, + /// followed by an OPTIONAL trailer containing entity-header fields. This + /// allows dynamically produced content to be transferred along with the + /// information necessary for the recipient to verify that it has + /// received the full message. + /// + public bool Chunked { get; set; } + + + /// + /// Defines the version of the HTTP Response for applications where it's required + /// for this to be forced. + /// + public string ProtocolVersion + { + get { return m_httpVersion; } + set { m_httpVersion = value; } + } + + /// + /// Encoding to use when sending stuff to the client. + /// + /// Default is UTF8 + public Encoding Encoding + { + get { return m_encoding; } + set { m_encoding = value; } + } + + + /// + /// Number of seconds to keep connection alive + /// + /// Only used if Connection property is set to . + public int KeepAlive + { + get { return m_keepAlive; } + set + { + if (value > 400) + m_keepAlive = 400; + else if (value <= 0) + m_keepAlive = 0; + else + m_keepAlive = value; + } + } + + /// + /// Status code that is sent to the client. + /// + /// Default is + public HttpStatusCode Status { get; set; } + + /// + /// Information about why a specific status code was used. + /// + public string Reason { get; set; } + + /// + /// Size of the body. MUST be specified before sending the header, + /// unless property Chunked is set to true. + /// + public long ContentLength + { + get { return m_contentLength; } + set { m_contentLength = value; } + } + + /// + /// Kind of content in the body + /// + /// Default type is "text/html" + public string ContentType + { + get { return m_contentType; } + set { m_contentType = value; } + } + + /// + /// Headers have been sent to the client- + /// + /// You can not send any additional headers if they have already been sent. + public bool HeadersSent { get; private set; } + + /// + /// The whole response have been sent. + /// + public bool Sent { get; private set; } + + /// + /// Cookies that should be created/changed. + /// + public ResponseCookies Cookies + { + get { return m_cookies; } + } + + /// + /// Add another header to the document. + /// + /// Name of the header, case sensitive, use lower cases. + /// Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n + /// If headers already been sent. + /// If value conditions have not been met. + /// Adding any header will override the default ones and those specified by properties. + public void AddHeader(string name, string value) + { + if (HeadersSent) + throw new InvalidOperationException("Headers have already been sent."); + + for (int i = 1; i < value.Length; ++i) + { + if (value[i] == '\r' && !char.IsWhiteSpace(value[i - 1])) + throw new ArgumentException("New line in value do not start with a white space."); + if (value[i] == '\n' && value[i - 1] != '\r') + throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf)."); + } + + m_headers[name] = value; + } + + /// + /// Send headers and body to the browser. + /// + /// If content have already been sent. + public void SendOri() + { + if (Sent) + throw new InvalidOperationException("Everything have already been sent."); + + m_context.ReqResponseAboutToSend(requestID); + if (m_context.MAXRequests == 0 || m_keepAlive == 0) + { + Connection = ConnectionType.Close; + m_context.TimeoutKeepAlive = 0; + } + else + { + if (m_keepAlive > 0) + m_context.TimeoutKeepAlive = m_keepAlive * 1000; + } + + if (!HeadersSent) + { + if (!SendHeaders()) + { + m_body.Dispose(); + Sent = true; + return; + } + } + + if(RawBuffer != null) + { + if(RawBufferStart >= 0 && RawBufferLen > 0) + { + if (RawBufferStart > RawBuffer.Length) + RawBufferStart = 0; + + if (RawBufferLen + RawBufferStart > RawBuffer.Length) + RawBufferLen = RawBuffer.Length - RawBufferStart; + + /* + int curlen; + while(RawBufferLen > 0) + { + curlen = RawBufferLen; + if(curlen > 8192) + curlen = 8192; + if (!_context.Send(RawBuffer, RawBufferStart, curlen)) + { + RawBuffer = null; + RawBufferStart = -1; + RawBufferLen = -1; + Body.Dispose(); + return; + } + RawBufferLen -= curlen; + RawBufferStart += curlen; + } + */ + if(RawBufferLen > 0) + { + if (!m_context.Send(RawBuffer, RawBufferStart, RawBufferLen)) + { + RawBuffer = null; + RawBufferStart = -1; + RawBufferLen = -1; + if(m_body != null) + m_body.Dispose(); + Sent = true; + return; + } + } + } + + RawBuffer = null; + RawBufferStart = -1; + RawBufferLen = -1; + } + + if(m_body != null && m_body.Length > 0) + { + m_body.Flush(); + m_body.Seek(0, SeekOrigin.Begin); + + var buffer = new byte[8192]; + int bytesRead = m_body.Read(buffer, 0, 8192); + while (bytesRead > 0) + { + if (!m_context.Send(buffer, 0, bytesRead)) + break; + bytesRead = m_body.Read(buffer, 0, 8192); + } + + m_body.Dispose(); + } + Sent = true; + m_context.ReqResponseSent(requestID, Connection); + } + + + /// + /// Make sure that you have specified and sent the headers first. + /// + /// + /// If headers have not been sent. + /// + /// offset of first byte to send + /// number of bytes to send. + /// + /// + /// This method can be used if you want to send body contents without caching them first. This + /// is recommended for larger files to keep the memory usage low. + public bool SendBody(byte[] buffer, int offset, int count) + { + if (!HeadersSent) + throw new InvalidOperationException("Send headers, and remember to specify ContentLength first."); + + bool sent = m_context.Send(buffer, offset, count); + Sent = true; + if (sent) + m_context.ReqResponseSent(requestID, Connection); + return sent; + } + + /// + /// Make sure that you have specified and sent the headers first. + /// + /// + /// If headers have not been sent. + /// + /// + /// + /// This method can be used if you want to send body contents without caching them first. This + /// is recommended for larger files to keep the memory usage low. + public bool SendBody(byte[] buffer) + { + if (!HeadersSent) + throw new InvalidOperationException("Send headers, and remember to specify ContentLength first."); + + bool sent = m_context.Send(buffer); + if (sent) + m_context.ReqResponseSent(requestID, Connection); + Sent = true; + return sent; + } + + /// + /// Send headers to the client. + /// + /// If headers already been sent. + /// + /// + /// + public bool SendHeaders() + { + if (HeadersSent) + throw new InvalidOperationException("Header have already been sent."); + + HeadersSent = true; + + if (m_headers["Date"] == null) + m_headers["Date"] = DateTime.Now.ToString("r"); + if (m_headers["Content-Length"] == null) + { + int len = (int)m_contentLength; + if(len == 0) + { + if(m_body != null) + len = (int)m_body.Length; + if(RawBuffer != null) + len += RawBufferLen; + } + m_headers["Content-Length"] = len.ToString(); + } + if (m_headers["Content-Type"] == null) + m_headers["Content-Type"] = m_contentType ?? DefaultContentType; + if (m_headers["Server"] == null) + m_headers["Server"] = "Tiny WebServer"; + + int keepaliveS = m_context.TimeoutKeepAlive / 1000; + if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0) + { + m_headers["Keep-Alive"] = "timeout=" + keepaliveS + ", max=" + m_context.MAXRequests; + m_headers["Connection"] = "Keep-Alive"; + } + else + m_headers["Connection"] = "close"; + + var sb = new StringBuilder(); + sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status, + string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason); + + for (int i = 0; i < m_headers.Count; ++i) + { + string headerName = m_headers.AllKeys[i]; + string[] values = m_headers.GetValues(i); + if (values == null) continue; + foreach (string value in values) + sb.AppendFormat("{0}: {1}\r\n", headerName, value); + } + + foreach (ResponseCookie cookie in Cookies) + sb.AppendFormat("Set-Cookie: {0}\r\n", cookie); + + sb.Append("\r\n"); + + m_headers.Clear(); + + return m_context.Send(Encoding.GetBytes(sb.ToString())); + } + + public byte[] GetHeaders() + { + HeadersSent = true; + + var sb = new StringBuilder(); + if(string.IsNullOrWhiteSpace(m_httpVersion)) + sb.AppendFormat("HTTP1/0 {0} {1}\r\n", (int)Status, + string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason); + else + sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status, + string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason); + + if (m_headers["Date"] == null) + sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r")); + if (m_headers["Content-Length"] == null) + { + long len = m_contentLength; + if (len == 0) + { + len = Body.Length; + if (RawBuffer != null && RawBufferLen > 0) + len += RawBufferLen; + } + sb.AppendFormat("Content-Length: {0}\r\n", len); + } + if (m_headers["Content-Type"] == null) + sb.AppendFormat("Content-Type: {0}\r\n", m_contentType ?? DefaultContentType); + if (m_headers["Server"] == null) + sb.Append("Server: OSWebServer\r\n"); + + int keepaliveS = m_context.TimeoutKeepAlive / 1000; + if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0) + { + sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MAXRequests); + sb.Append("Connection: Keep-Alive\r\n"); + } + else + sb.Append("Connection: close\r\n"); + + if (m_headers["Connection"] != null) + m_headers["Connection"] = null; + if (m_headers["Keep-Alive"] != null) + m_headers["Keep-Alive"] = null; + + for (int i = 0; i < m_headers.Count; ++i) + { + string headerName = m_headers.AllKeys[i]; + string[] values = m_headers.GetValues(i); + if (values == null) continue; + foreach (string value in values) + sb.AppendFormat("{0}: {1}\r\n", headerName, value); + } + + foreach (ResponseCookie cookie in Cookies) + sb.AppendFormat("Set-Cookie: {0}\r\n", cookie); + + sb.Append("\r\n"); + + m_headers.Clear(); + + return Encoding.GetBytes(sb.ToString()); + } + + public void Send() + { + if (Sent) + throw new InvalidOperationException("Everything have already been sent."); + + if (m_context.MAXRequests == 0 || m_keepAlive == 0) + { + Connection = ConnectionType.Close; + m_context.TimeoutKeepAlive = 0; + } + else + { + if (m_keepAlive > 0) + m_context.TimeoutKeepAlive = m_keepAlive * 1000; + } + + m_headerBytes = GetHeaders(); + if (RawBuffer != null) + { + if (RawBufferStart < 0 || RawBufferStart > RawBuffer.Length) + return; + + if (RawBufferLen < 0) + RawBufferLen = RawBuffer.Length; + + if (RawBufferLen + RawBufferStart > RawBuffer.Length) + RawBufferLen = RawBuffer.Length - RawBufferStart; + + int tlen = m_headerBytes.Length + RawBufferLen; + if(RawBufferLen > 0 && tlen < 16384) + { + byte[] tmp = new byte[tlen]; + Array.Copy(m_headerBytes, tmp, m_headerBytes.Length); + Array.Copy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen); + m_headerBytes = null; + RawBuffer = tmp; + RawBufferStart = 0; + RawBufferLen = tlen; + } + } + m_context.StartSendResponse(this); + } + + public async Task SendNextAsync(int bytesLimit) + { + if (m_headerBytes != null) + { + if(!await m_context.SendAsync(m_headerBytes, 0, m_headerBytes.Length).ConfigureAwait(false)) + { + if(m_body != null) + m_body.Dispose(); + RawBuffer = null; + Sent = true; + return; + } + bytesLimit -= m_headerBytes.Length; + m_headerBytes = null; + if(bytesLimit <= 0) + { + m_context.ContinueSendResponse(); + return; + } + } + + if (RawBuffer != null) + { + if (RawBufferLen > 0) + { + bool sendRes; + if(RawBufferLen > bytesLimit) + { + sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false); + RawBufferLen -= bytesLimit; + RawBufferStart += bytesLimit; + } + else + { + sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false); + RawBufferLen = 0; + } + + if (!sendRes) + { + RawBuffer = null; + if(m_body != null) + Body.Dispose(); + Sent = true; + return; + } + } + if (RawBufferLen <= 0) + RawBuffer = null; + else + { + m_context.ContinueSendResponse(); + return; + } + } + + if (m_body != null && m_body.Length != 0) + { + m_body.Flush(); + m_body.Seek(0, SeekOrigin.Begin); + + RawBuffer = new byte[m_body.Length]; + RawBufferLen = m_body.Read(RawBuffer, 0, (int)m_body.Length); + m_body.Dispose(); + + if(RawBufferLen > 0) + { + bool sendRes; + if (RawBufferLen > bytesLimit) + { + sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false); + RawBufferLen -= bytesLimit; + RawBufferStart += bytesLimit; + } + else + { + sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false); + RawBufferLen = 0; + } + + if (!sendRes) + { + RawBuffer = null; + Sent = true; + return; + } + } + if (RawBufferLen > 0) + { + m_context.ContinueSendResponse(); + return; + } + } + + if (m_body != null) + m_body.Dispose(); + Sent = true; + m_context.ReqResponseSent(requestID, Connection); + } + + /// + /// Redirect client to somewhere else using the 302 status code. + /// + /// Destination of the redirect + /// If headers already been sent. + /// You can not do anything more with the request when a redirect have been done. This should be your last + /// action. + public void Redirect(Uri uri) + { + Status = HttpStatusCode.Redirect; + m_headers["location"] = uri.ToString(); + } + + /// + /// redirect to somewhere + /// + /// where the redirect should go + /// + /// No body are allowed when doing redirects. + /// + public void Redirect(string url) + { + Status = HttpStatusCode.Redirect; + m_headers["location"] = url; + } + + public void Clear() + { + if(Body != null && Body.CanRead) + Body.Dispose(); + } + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpClientContext.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpClientContext.cs new file mode 100644 index 0000000000..8d4fc79c06 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpClientContext.cs @@ -0,0 +1,146 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace OSHttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + public interface IHttpClientContext + { + + /// + /// Get SSL commonName of remote peer + /// + string SSLCommonName { get; } + + /// + /// Using SSL or other encryption method. + /// + bool IsSecured { get; } + + int contextID {get;} + int TimeoutKeepAlive {get; set; } + int MAXRequests{get; set; } + + bool CanSend(); + bool IsSending(); + + /// + /// Disconnect from client + /// + /// error to report in the event. + void Disconnect(SocketError error); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason); + + /// + /// send a whole buffer + /// + /// buffer to send + /// + bool Send(byte[] buffer); + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + bool Send(byte[] buffer, int offset, int size); + Task SendAsync(byte[] buffer, int offset, int size); + + /// + /// Closes the streams and disposes of the unmanaged resources + /// + void Close(); + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + event EventHandler Disconnected; + + /// + /// A request have been received in the context. + /// + event EventHandler RequestReceived; + + HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing(); + + void StartSendResponse(HttpResponse response); + void ContinueSendResponse(); + void ReqResponseAboutToSend(uint requestID); + void ReqResponseSent(uint requestID, ConnectionType connection); + bool TrySendResponse(int limit); + } + public class HTTPNetworkContext + { + public NetworkStream Stream; + public Socket Socket; + } + + /// + /// A have been disconnected. + /// + public class DisconnectedEventArgs : EventArgs + { + /// + /// Gets reason to why client disconnected. + /// + public SocketError Error { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// Reason to disconnection. + public DisconnectedEventArgs(SocketError error) + { + Error = error; + } + } + + /// + /// + /// + public class RequestEventArgs : EventArgs + { + /// + /// Gets received request. + /// + public IHttpRequest Request { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The request. + public RequestEventArgs(IHttpRequest request) + { + Request = request; + } + } + +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequest.cs new file mode 100644 index 0000000000..ea3dc48499 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequest.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using OSHttpServer.Exceptions; + +namespace OSHttpServer +{ + /// + /// Contains server side HTTP request information. + /// + public interface IHttpRequest : ICloneable + { + /// + /// Gets kind of types accepted by the client. + /// + string[] AcceptTypes { get; } + + uint ID {get; } + /// + /// Gets or sets body stream. + /// + Stream Body { get; set; } + + /// + /// Gets whether the body is complete. + /// + bool BodyIsComplete { get; } + + /// + /// Gets or sets kind of connection used for the session. + /// + ConnectionType Connection { get; set; } + + IHttpClientContext Context { get; } + + /// + /// Gets or sets number of bytes in the body. + /// + int ContentLength { get; set; } + + /// + /// Gets cookies that was sent with the request. + /// + RequestCookies Cookies { get; } + + /// + /// Gets form parameters. + /// + //HttpForm Form { get; } + + /// + /// Gets headers sent by the client. + /// + NameValueCollection Headers { get; } + + /// + /// Gets or sets version of HTTP protocol that's used. + /// + /// + /// Probably or . + /// + /// + string HttpVersion { get; set; } + + /// + /// Gets whether the request was made by Ajax (Asynchronous JavaScript) + /// + bool IsAjax { get; } + + /// + /// Gets or sets requested method. + /// + /// + /// Will always be in upper case. + /// + /// + string Method { get; set; } + + /// + /// Gets parameter from or . + /// + HttpParam Param { get; } + + /// + /// Gets variables sent in the query string + /// + HttpInput QueryString { get; } + + /// + /// Gets or sets requested URI. + /// + Uri Uri { get; set; } + + /// + /// Gets URI absolute path divided into parts. + /// + /// + /// // URI is: http://gauffin.com/code/tiny/ + /// Console.WriteLine(request.UriParts[0]); // result: code + /// Console.WriteLine(request.UriParts[1]); // result: tiny + /// + /// + /// If you're using controllers than the first part is controller name, + /// the second part is method name and the third part is Id property. + /// + /// + string[] UriParts { get; } + + /// + /// Gets or sets path and query. + /// + /// + /// + /// Are only used during request parsing. Cannot be set after "Host" header have been + /// added. + /// + string UriPath { get; set; } + + /// + /// Called during parsing of a . + /// + /// Name of the header, should not be URL encoded + /// Value of the header, should not be URL encoded + /// If a header is incorrect. + void AddHeader(string name, string value); + + /// + /// Add bytes to the body + /// + /// buffer to read bytes from + /// where to start read + /// number of bytes to read + /// Number of bytes actually read (same as length unless we got all body bytes). + /// If body is not writable + /// bytes is null. + /// offset is out of range. + int AddToBody(byte[] bytes, int offset, int length); + + /// + /// Clear everything in the request + /// + void Clear(); + + /// + /// Decode body into a form. + /// + /// A list with form decoders. + /// If body contents is not valid for the chosen decoder. + /// If body is still being transferred. + //void DecodeBody(FormDecoderProvider providers); + + /// + /// Sets the cookies. + /// + /// The cookies. + void SetCookies(RequestCookies cookies); + + /// + /// Create a response object. + /// + /// Context for the connected client. + /// A new . + //IHttpResponse CreateResponse(IHttpClientContext context); + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequestParser.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequestParser.cs new file mode 100644 index 0000000000..953fab343c --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpRequestParser.cs @@ -0,0 +1,94 @@ +using System; +using OSHttpServer.Exceptions; +using OSHttpServer.Parser; + +namespace OSHttpServer +{ + /// + /// Event driven parser used to parse incoming HTTP requests. + /// + /// + /// The parser supports partial messages and keeps the states between + /// each parsed buffer. It's therefore important that the parser gets + /// ed if a client disconnects. + /// + public interface IHttpRequestParser + { + /// + /// Current state in parser. + /// + RequestParserState CurrentState { get; } + + /// + /// Parse partial or complete message. + /// + /// buffer containing incoming bytes + /// where in buffer that parsing should start + /// number of bytes to parse + /// Unparsed bytes left in buffer. + /// BadRequestException. + int Parse(byte[] buffer, int offset, int count); + + /// + /// A request have been successfully parsed. + /// + event EventHandler RequestCompleted; + + /// + /// More body bytes have been received. + /// + event EventHandler BodyBytesReceived; + + /// + /// Request line have been received. + /// + event EventHandler RequestLineReceived; + + /// + /// A header have been received. + /// + event EventHandler HeaderReceived; + + /// + /// Clear parser state. + /// + void Clear(); + + /// + /// Gets or sets the log writer. + /// + ILogWriter LogWriter { get; set; } + } + + /// + /// Current state in the parsing. + /// + public enum RequestParserState + { + /// + /// Should parse the request line + /// + FirstLine, + /// + /// Searching for a complete header name + /// + HeaderName, + /// + /// Searching for colon after header name (ignoring white spaces) + /// + AfterName, + /// + /// Searching for start of header value (ignoring white spaces) + /// + Between, + /// + /// Searching for a complete header value (can span over multiple lines, as long as they are prefixed with one/more whitespaces) + /// + HeaderValue, + + /// + /// Adding bytes to body + /// + Body + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpResponse.cs new file mode 100644 index 0000000000..6cfb772b39 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/IHttpResponse.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; +using System.Net; +using System.Text; + +namespace OSHttpServer +{ + /// + /// Response that is sent back to the web browser / client. + /// + /// A response can be sent if different ways. The easiest one is + /// to just fill the Body stream with content, everything else + /// will then be taken care of by the framework. The default content-type + /// is text/html, you should change it if you send anything else. + /// + /// The second and slighty more complex way is to send the response + /// as parts. Start with sending the header using the SendHeaders method and + /// then you can send the body using SendBody method, but do not forget + /// to set ContentType and ContentLength before doing so. + /// + /// + /// public void MyHandler(IHttpRequest request, IHttpResponse response) + /// { + /// + /// } + /// + public interface IHttpResponse + { + /// + /// The body stream is used to cache the body contents + /// before sending everything to the client. It's the simplest + /// way to serve documents. + /// + Stream Body { get; set; } + byte[] RawBuffer { get; set; } + int RawBufferStart { get; set; } + int RawBufferLen { get; set; } + uint requestID { get; } + + /// + /// Defines the version of the HTTP Response for applications where it's required + /// for this to be forced. + /// + string ProtocolVersion { get; set; } + int Priority { get; set; } + + /// + /// The chunked encoding modifies the body of a message in order to + /// transfer it as a series of chunks, each with its own size indicator, + /// followed by an OPTIONAL trailer containing entity-header fields. This + /// allows dynamically produced content to be transferred along with the + /// information necessary for the recipient to verify that it has + /// received the full message. + /// + bool Chunked { get; set; } + + /// + /// Kind of connection + /// + ConnectionType Connection { get; set; } + + /// + /// Encoding to use when sending stuff to the client. + /// + /// Default is UTF8 + Encoding Encoding { get; set; } + + /// + /// Number of seconds to keep connection alive + /// + /// Only used if Connection property is set to ConnectionType.KeepAlive + int KeepAlive { get; set; } + + /// + /// Status code that is sent to the client. + /// + /// Default is HttpStatusCode.Ok + HttpStatusCode Status { get; set; } + + /// + /// Information about why a specific status code was used. + /// + string Reason { get; set; } + + /// + /// Size of the body. MUST be specified before sending the header, + /// unless property Chunked is set to true. + /// + long ContentLength { get; set; } + + /// + /// Kind of content in the body + /// + /// Default is text/html + string ContentType { get; set; } + + /// + /// Headers have been sent to the client- + /// + /// You can not send any additional headers if they have already been sent. + bool HeadersSent { get; } + + /// + /// The whole response have been sent. + /// + bool Sent { get; } + + /// + /// Cookies that should be created/changed. + /// + ResponseCookies Cookies { get; } + + /// + /// Add another header to the document. + /// + /// Name of the header, case sensitive, use lower cases. + /// Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n + /// If headers already been sent. + /// If value conditions have not been met. + /// Adding any header will override the default ones and those specified by properties. + void AddHeader(string name, string value); + + /// + /// Send headers and body to the browser. + /// + /// If content have already been sent. + void Send(); + + /// + /// Make sure that you have specified ContentLength and sent the headers first. + /// + /// + /// If headers have not been sent. + /// + /// offest of first byte to send + /// number of bytes to send. + /// + /// + /// This method can be used if you want to send body contents without caching them first. This + /// is recommended for larger files to keep the memory usage low. + bool SendBody(byte[] buffer, int offset, int count); + + /// + /// Make sure that you have specified ContentLength and sent the headers first. + /// + /// + /// If headers have not been sent. + /// + /// + /// + /// This method can be used if you want to send body contents without caching them first. This + /// is recommended for larger files to keep the memory usage low. + bool SendBody(byte[] buffer); + + /// + /// Send headers to the client. + /// + /// If headers already been sent. + /// + /// + /// + bool SendHeaders(); + } + + /// + /// Type of HTTP connection + /// + public enum ConnectionType + { + /// + /// Connection is closed after each request-response + /// + Close, + + /// + /// Connection is kept alive for X seconds (unless another request have been made) + /// + KeepAlive + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ILogWriter.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ILogWriter.cs new file mode 100644 index 0000000000..790c9a500d --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ILogWriter.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; +using System.Text; + +namespace OSHttpServer +{ + /// + /// Priority for log entries + /// + /// + + public enum LogPrio + { + None, + /// + /// Very detailed logs to be able to follow the flow of the program. + /// + Trace, + + /// + /// Logs to help debug errors in the application + /// + Debug, + + /// + /// Information to be able to keep track of state changes etc. + /// + Info, + + /// + /// Something did not go as we expected, but it's no problem. + /// + Warning, + + /// + /// Something that should not fail failed, but we can still keep + /// on going. + /// + Error, + + /// + /// Something failed, and we cannot handle it properly. + /// + Fatal + } + + /// + /// Interface used to write to log files. + /// + public interface ILogWriter + { + /// + /// Write an entry to the log file. + /// + /// object that is writing to the log + /// importance of the log message + /// the message + void Write(object source, LogPrio priority, string message); + } + + /// + /// Default log writer, writes everything to null (nowhere). + /// + /// + + public sealed class NullLogWriter : ILogWriter + { + /// + /// The logging instance. + /// + public static readonly NullLogWriter Instance = new NullLogWriter(); + + /// + /// Writes everything to null + /// + /// object that wrote the log entry. + /// Importance of the log message + /// The message. + public void Write(object source, LogPrio prio, string message) {} + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/InternalServerException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/InternalServerException.cs new file mode 100644 index 0000000000..6b8467d0f8 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/InternalServerException.cs @@ -0,0 +1,38 @@ +using System; +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// The server encountered an unexpected condition which prevented it from fulfilling the request. + /// + public class InternalServerException : HttpException + { + /// + /// Initializes a new instance of the class. + /// + public InternalServerException() + : base(HttpStatusCode.InternalServerError, "The server encountered an unexpected condition which prevented it from fulfilling the request.") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// error message. + public InternalServerException(string message) + : base(HttpStatusCode.InternalServerError, message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// error message. + /// inner exception. + public InternalServerException(string message, Exception inner) + : base(HttpStatusCode.InternalServerError, message, inner) + { + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/NotFoundException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/NotFoundException.cs new file mode 100644 index 0000000000..1f3d8d76d7 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/NotFoundException.cs @@ -0,0 +1,29 @@ +using System; +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// The requested resource was not found in the web server. + /// + public class NotFoundException : HttpException + { + /// + /// Create a new exception + /// + /// message describing the error + /// inner exception + public NotFoundException(string message, Exception inner) : base(HttpStatusCode.NotFound, message, inner) + { + } + + /// + /// Create a new exception + /// + /// message describing the error + public NotFoundException(string message) + : base(HttpStatusCode.NotFound, message) + { + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookie.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookie.cs new file mode 100644 index 0000000000..bededb9931 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookie.cs @@ -0,0 +1,70 @@ +using System; +using System.Web; + +namespace OSHttpServer +{ + /// + /// cookie sent by the client/browser + /// + /// + public class RequestCookie + { + private readonly string _name = null; + private string _value = null; + + /// + /// Constructor. + /// + /// cookie identifier + /// cookie content + /// id or content is null + /// id is empty + public RequestCookie(string id, string content) + { + if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); + if (content == null) throw new ArgumentNullException("content"); + + _name = id; + _value = content; + } + + + #region inherited methods + + /// + /// Gets the cookie HTML representation. + /// + /// cookie string + public override string ToString() + { + return string.Format("{0}={1}; ", HttpUtility.UrlEncode(_name), HttpUtility.UrlEncode(_value)); + } + + #endregion + + #region public properties + + /// + /// Gets the cookie identifier. + /// + public string Name + { + get { return _name; } + } + + + /// + /// Cookie value. Set to null to remove cookie. + /// + public string Value + { + get { return _value; } + set + { + _value = value; + } + } + + #endregion + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookies.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookies.cs new file mode 100644 index 0000000000..2c11a937cd --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestCookies.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OSHttpServer +{ + /// + /// This class is created as a wrapper, since there are two different cookie types in .Net (Cookie and HttpCookie). + /// The framework might switch class in the future and we dont want to have to replace all instances + /// + public sealed class RequestCookies : IEnumerable + { + private readonly IDictionary _items = new Dictionary(); + + /// + /// Let's copy all the cookies. + /// + /// value from cookie header. + public RequestCookies(string cookies) + { + if (string.IsNullOrEmpty(cookies)) + return; + + string name = string.Empty; + int state = 0; + int start = -1; + for (int i = 0; i < cookies.Length; ++i) + { + char ch = cookies[i]; + + // searching for start of cookie name + switch (state) + { + case 0: + if (char.IsWhiteSpace(ch)) + continue; + start = i; + ++state; + break; + case 1: + if (char.IsWhiteSpace(ch) || ch == '=') + { + if (start == -1) + return; // todo: decide if an exception should be thrown. + name = cookies.Substring(start, i - start); + start = -1; + ++state; + } + break; + case 2: + if (!char.IsWhiteSpace(ch) && ch != '=') + { + start = i; + ++state; + } + break; + case 3: + if (ch == ';') + { + if (start >= -1) + Add(new RequestCookie(name, cookies.Substring(start, i - start))); + start = -1; + state = 0; + name = string.Empty; + } + break; + } + } + + // last cookie + if (name != string.Empty) + Add(new RequestCookie(name, cookies.Substring(start, cookies.Length - start))); + } + + /// + /// Adds a cookie in the collection. + /// + /// cookie to add + /// cookie is null + internal void Add(RequestCookie cookie) + { + // Verifies the parameter + if (cookie == null) + throw new ArgumentNullException("cookie"); + if (cookie.Name == null || cookie.Name.Trim() == string.Empty) + throw new ArgumentException("Name must be specified."); + if (cookie.Value == null || cookie.Value.Trim() == string.Empty) + throw new ArgumentException("Content must be specified."); + + if (_items.ContainsKey(cookie.Name)) + _items[cookie.Name] = cookie; + else _items.Add(cookie.Name, cookie); + } + + /// + /// Gets the count of cookies in the collection. + /// + public int Count + { + get { return _items.Count; } + } + + + /// + /// Gets the cookie of a given identifier (null if not existing). + /// + public RequestCookie this[string id] + { + get + { + return _items.ContainsKey(id) ? _items[id] : null; + } + } + /// + /// Gets a collection enumerator on the cookie list. + /// + /// collection enumerator + public IEnumerator GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + + /// + /// Remove all cookies. + /// + public void Clear() + { + _items.Clear(); + } + + #region IEnumerable Members + + /// + ///Returns an enumerator that iterates through the collection. + /// + /// + /// + ///A that can be used to iterate through the collection. + /// + ///1 + IEnumerator IEnumerable.GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + #endregion + + /// + /// Remove a cookie from the collection. + /// + /// Name of cookie. + public void Remove(string cookieName) + { + lock (_items) + { + if (!_items.ContainsKey(cookieName)) + return; + + _items.Remove(cookieName); + } + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestLineEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestLineEventArgs.cs new file mode 100644 index 0000000000..1b9b8dc69b --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestLineEventArgs.cs @@ -0,0 +1,48 @@ +using System; + +namespace OSHttpServer.Parser +{ + /// + /// Used when the request line have been successfully parsed. + /// + public class RequestLineEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP method. + /// The URI path. + /// The HTTP version. + public RequestLineEventArgs(string httpMethod, string uriPath, string httpVersion) + { + HttpMethod = httpMethod; + UriPath = uriPath; + HttpVersion = httpVersion; + } + + /// + /// Initializes a new instance of the class. + /// + public RequestLineEventArgs() + { + } + + /// + /// Gets or sets http method. + /// + /// + /// Should be one of the methods declared in . + /// + public string HttpMethod { get; set; } + + /// + /// Gets or sets the version of the HTTP protocol that the client want to use. + /// + public string HttpVersion { get; set; } + + /// + /// Gets or sets requested URI path. + /// + public string UriPath { get; set; } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestParserFactory.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestParserFactory.cs new file mode 100644 index 0000000000..03762efafb --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/RequestParserFactory.cs @@ -0,0 +1,33 @@ +using OSHttpServer.Parser; + +namespace OSHttpServer +{ + /// + /// Creates request parsers when needed. + /// + public class RequestParserFactory : IRequestParserFactory + { + /// + /// Create a new request parser. + /// + /// Used when logging should be enabled. + /// A new request parser. + public IHttpRequestParser CreateParser(ILogWriter logWriter) + { + return new HttpRequestParser(logWriter); + } + } + + /// + /// Creates request parsers when needed. + /// + public interface IRequestParserFactory + { + /// + /// Create a new request parser. + /// + /// Used when logging should be enabled. + /// A new request parser. + IHttpRequestParser CreateParser(ILogWriter logWriter); + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookie.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookie.cs new file mode 100644 index 0000000000..f6931f50d6 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookie.cs @@ -0,0 +1,123 @@ +using System; +using System.Web; + +namespace OSHttpServer +{ + /// + /// cookie being sent back to the browser. + /// + /// + public class ResponseCookie : RequestCookie + { + private const string _nullPath = "/"; + private bool _persistant = false; + private DateTime _expires; + private string _path = "/"; + private readonly string _domain; + + #region constructors + + + /// + /// Constructor. + /// + /// cookie identifier + /// cookie content + /// cookie expiration date. Use DateTime.MinValue for session cookie. + /// id or content is null + /// id is empty + public ResponseCookie(string id, string content, DateTime expiresAt) + : base(id, content) + { + if (expiresAt != DateTime.MinValue) + { + _expires = expiresAt; + _persistant = true; + } + } + + /// + /// Create a new cookie + /// + /// name identifying the cookie + /// cookie value + /// when the cookie expires. Setting DateTime.MinValue will delete the cookie when the session is closed. + /// Path to where the cookie is valid + /// Domain that the cookie is valid for. + public ResponseCookie(string name, string value, DateTime expires, string path, string domain) + : this(name, value, expires) + { + _domain = domain; + _path = path; + } + + /// + /// Create a new cookie + /// + /// Name and value will be used + /// when the cookie expires. + public ResponseCookie(RequestCookie cookie, DateTime expires) + : this(cookie.Name, cookie.Value, expires) + {} + + #endregion + + #region inherited methods + + /// + /// Gets the cookie HTML representation. + /// + /// cookie string + public override string ToString() + { + string temp = string.Format("{0}={1}; ", HttpUtility.UrlEncode(Name), HttpUtility.UrlEncode(Value)); + if (_persistant) + { + TimeSpan span = DateTime.Now - DateTime.UtcNow; + DateTime utc = _expires.Subtract(span); + temp += string.Format("expires={0};", utc.ToString("r")); + } + if (!string.IsNullOrEmpty(_path)) + temp += string.Format("path={0}; ", _path); + if (!string.IsNullOrEmpty(_domain)) + temp += string.Format("domain={0}; ", _domain); + + return temp; + } + + #endregion + + #region public properties + + /// + /// When the cookie expires. + /// DateTime.MinValue means that the cookie expires when the session do so. + /// + public DateTime Expires + { + get { return _expires; } + set + { + _expires = value; + _persistant = value != DateTime.MinValue; + } + } + + /// + /// Cookie is only valid under this path. + /// + public string Path + { + get { return _path; } + set + { + if (!string.IsNullOrEmpty(value)) + _path = value; + else + _path = _nullPath; + } + } + + #endregion + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookies.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookies.cs new file mode 100644 index 0000000000..e8caf3b174 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/ResponseCookies.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OSHttpServer +{ + /// + /// Cookies that should be set. + /// + public sealed class ResponseCookies : IEnumerable + { + private readonly IDictionary _items = new Dictionary(); + + /// + /// Adds a cookie in the collection. + /// + /// cookie to add + /// cookie is null + public void Add(ResponseCookie cookie) + { + // Verifies the parameter + if (cookie == null) + throw new ArgumentNullException("cookie"); + if (cookie.Name == null || cookie.Name.Trim() == string.Empty) + throw new ArgumentException("Name must be specified."); + if (cookie.Value == null || cookie.Value.Trim() == string.Empty) + throw new ArgumentException("Content must be specified."); + + if (_items.ContainsKey(cookie.Name)) + _items[cookie.Name] = cookie; + else _items.Add(cookie.Name, cookie); + } + + /// + /// Copy a request cookie + /// + /// + /// When the cookie should expire + public void Add(RequestCookie cookie, DateTime expires) + { + Add(new ResponseCookie(cookie, expires)); + } + + /// + /// Gets the count of cookies in the collection. + /// + public int Count + { + get { return _items.Count; } + } + + + /// + /// Gets the cookie of a given identifier (null if not existing). + /// + public ResponseCookie this[string id] + { + get + { + if (_items.ContainsKey(id)) + return _items[id]; + else + return null; + } + set + { + if (_items.ContainsKey(id)) + _items[id] = value; + else + Add(value); + } + } + /// + /// Gets a collection enumerator on the cookie list. + /// + /// collection enumerator + public IEnumerator GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + + /// + /// Remove all cookies + /// + public void Clear() + { + _items.Clear(); + } + + #region IEnumerable Members + + /// + ///Returns an enumerator that iterates through the collection. + /// + /// + /// + ///A that can be used to iterate through the collection. + /// + ///1 + IEnumerator IEnumerable.GetEnumerator() + { + return _items.Values.GetEnumerator(); + } + + #endregion + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer/UnauthorizedException.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/UnauthorizedException.cs new file mode 100644 index 0000000000..84c552cb71 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer/UnauthorizedException.cs @@ -0,0 +1,56 @@ +using System; +using System.Net; + +namespace OSHttpServer.Exceptions +{ + /// + /// The request requires user authentication. The response MUST include a + /// WWW-Authenticate header field (section 14.47) containing a challenge + /// applicable to the requested resource. + /// + /// The client MAY repeat the request with a suitable Authorization header + /// field (section 14.8). If the request already included Authorization + /// credentials, then the 401 response indicates that authorization has been + /// refused for those credentials. If the 401 response contains the same challenge + /// as the prior response, and the user agent has already attempted authentication + /// at least once, then the user SHOULD be presented the entity that was given in the response, + /// since that entity might include relevant diagnostic information. + /// + /// HTTP access authentication is explained in rfc2617: + /// http://www.ietf.org/rfc/rfc2617.txt + /// + /// (description is taken from + /// http://www.submissionchamber.com/help-guides/error-codes.php#sec10.4.2) + /// + public class UnauthorizedException : HttpException + { + /// + /// Create a new unauhtorized exception. + /// + /// + public UnauthorizedException() + : base(HttpStatusCode.Unauthorized, "The request requires user authentication.") + { + + } + + /// + /// Create a new unauhtorized exception. + /// + /// reason to why the request was unauthorized. + /// inner exception + public UnauthorizedException(string message, Exception inner) + : base(HttpStatusCode.Unauthorized, message, inner) + { + } + + /// + /// Create a new unauhtorized exception. + /// + /// reason to why the request was unauthorized. + public UnauthorizedException(string message) + : base(HttpStatusCode.Unauthorized, message) + { + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs deleted file mode 100644 index ef573a4186..0000000000 --- a/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.Net; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using log4net; -using Nwc.XmlRpc; - -namespace OpenSim.Framework.Servers.HttpServer -{ - public delegate XmlRpcResponse OSHttpXmlRpcProcessor(XmlRpcRequest request); - - public class OSHttpXmlRpcHandler: OSHttpHandler - { - private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// - /// XmlRpcMethodMatch tries to reify (deserialize) an incoming - /// XmlRpc request (and posts it to the "whiteboard") and - /// checks whether the method name is one we are interested - /// in. - /// - /// true if the handler is interested in the content; - /// false otherwise - protected bool XmlRpcMethodMatch(OSHttpRequest req) - { - XmlRpcRequest xmlRpcRequest = null; - - // check whether req is already reified - // if not: reify (and post to whiteboard) - try - { - if (req.Whiteboard.ContainsKey("xmlrequest")) - { - xmlRpcRequest = req.Whiteboard["xmlrequest"] as XmlRpcRequest; - } - else - { - StreamReader body = new StreamReader(req.InputStream); - string requestBody = body.ReadToEnd(); - xmlRpcRequest = (XmlRpcRequest)(new XmlRpcRequestDeserializer()).Deserialize(requestBody); - req.Whiteboard["xmlrequest"] = xmlRpcRequest; - } - } - catch (XmlException) - { - _log.ErrorFormat("[OSHttpXmlRpcHandler] failed to deserialize XmlRpcRequest from {0}", req.ToString()); - return false; - } - - // check against methodName - if ((null != xmlRpcRequest) - && !String.IsNullOrEmpty(xmlRpcRequest.MethodName) - && xmlRpcRequest.MethodName == _methodName) - { - _log.DebugFormat("[OSHttpXmlRpcHandler] located handler {0} for {1}", _methodName, req.ToString()); - return true; - } - - return false; - } - - // contains handler for processing XmlRpc Request - private XmlRpcMethod _handler; - - // contains XmlRpc method name - private string _methodName; - - - /// - /// Instantiate an XmlRpc handler. - /// - /// XmlRpcMethod - /// delegate - /// XmlRpc method name - /// XmlRpc path prefix (regular expression) - /// Dictionary with header names and - /// regular expressions to match content of headers - /// IP whitelist of remote end points - /// to accept (regular expression) - /// - /// Except for handler and methodName, all other parameters - /// can be null, in which case they are not taken into account - /// when the handler is being looked up. - /// - public OSHttpXmlRpcHandler(XmlRpcMethod handler, string methodName, Regex path, - Dictionary headers, Regex whitelist) - : base(new Regex(@"^POST$", RegexOptions.IgnoreCase | RegexOptions.Compiled), path, null, headers, - new Regex(@"^(text|application)/xml", RegexOptions.IgnoreCase | RegexOptions.Compiled), - whitelist) - { - _handler = handler; - _methodName = methodName; - } - - - /// - /// Instantiate an XmlRpc handler. - /// - /// XmlRpcMethod - /// delegate - /// XmlRpc method name - public OSHttpXmlRpcHandler(XmlRpcMethod handler, string methodName) - : this(handler, methodName, null, null, null) - { - } - - - /// - /// Invoked by OSHttpRequestPump. - /// - public override OSHttpHandlerResult Process(OSHttpRequest request) - { - XmlRpcResponse xmlRpcResponse; - string responseString; - - // check whether we are interested in this request - if (!XmlRpcMethodMatch(request)) return OSHttpHandlerResult.Pass; - - - OSHttpResponse resp = new OSHttpResponse(request); - try - { - // reified XmlRpcRequest must still be on the whiteboard - XmlRpcRequest xmlRpcRequest = request.Whiteboard["xmlrequest"] as XmlRpcRequest; - xmlRpcResponse = _handler(xmlRpcRequest); - responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse); - - resp.ContentType = "text/xml"; - byte[] buffer = Encoding.UTF8.GetBytes(responseString); - - resp.SendChunked = false; - resp.ContentLength = buffer.Length; - resp.ContentEncoding = Encoding.UTF8; - - resp.Body.Write(buffer, 0, buffer.Length); - resp.Body.Flush(); - - resp.Send(); - - } - catch (Exception ex) - { - _log.WarnFormat("[OSHttpXmlRpcHandler]: Error: {0}", ex.Message); - return OSHttpHandlerResult.Pass; - } - return OSHttpHandlerResult.Done; - } - } -} diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs index 2f89034259..20eeb03b2f 100644 --- a/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs @@ -30,7 +30,7 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Text; -using HttpServer; +using OSHttpServer; using log4net; using OpenMetaverse; @@ -130,8 +130,16 @@ namespace OpenSim.Framework.Servers.HttpServer return; } - if (responsedata.ContainsKey("error_status_text")) - response.StatusDescription = (string)responsedata["error_status_text"]; + + response.StatusCode = responsecode; + if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) + { + response.AddHeader("Location:", (string)responsedata["str_redirect_location"]); + response.KeepAlive = false; + PollServiceArgs.RequestsHandled++; + response.Send(); + return; + } if (responsedata.ContainsKey("http_protocol_version")) response.ProtocolVersion = (string)responsedata["http_protocol_version"]; @@ -142,17 +150,13 @@ namespace OpenSim.Framework.Servers.HttpServer if (responsedata.ContainsKey("prio")) response.Priority = (int)responsedata["prio"]; + if (responsedata.ContainsKey("error_status_text")) + response.StatusDescription = (string)responsedata["error_status_text"]; + // Cross-Origin Resource Sharing with simple requests if (responsedata.ContainsKey("access_control_allow_origin")) response.AddHeader("Access-Control-Allow-Origin", (string)responsedata["access_control_allow_origin"]); - response.StatusCode = responsecode; - - if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) - { - response.RedirectLocation = (string)responsedata["str_redirect_location"]; - } - if (string.IsNullOrEmpty(contentType)) response.AddHeader("Content-Type", "text/html"); else diff --git a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs index c8af90f75c..6d9395710e 100644 --- a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs +++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs @@ -32,7 +32,7 @@ using System.Net; using System.Security.Cryptography; using System.Text; using System.Threading; -using HttpServer; +using OSHttpServer; namespace OpenSim.Framework.Servers.HttpServer { diff --git a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs deleted file mode 100644 index e5f704381b..0000000000 --- a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs +++ /dev/null @@ -1,439 +0,0 @@ -/* - * 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.Specialized; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using HttpServer; -using HttpServer.FormDecoders; -using NUnit.Framework; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Tests.Common; - -namespace OpenSim.Framework.Servers.Tests -{ -/* - - [TestFixture] - public class OSHttpTests : OpenSimTestCase - { - // we need an IHttpClientContext for our tests - public class TestHttpClientContext: IHttpClientContext - { - private bool _secured; - public bool IsSecured - { - get { return _secured; } - } - public bool Secured - { - get { return _secured; } - } - - public TestHttpClientContext(bool secured) - { - _secured = secured; - } - - public void Disconnect(SocketError error) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} - public void Respond(string body) {} - public void Send(byte[] buffer) {} - public void Send(byte[] buffer, int offset, int size) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {} - public void Close() { } - public bool EndWhenDone { get { return false;} set { return;}} - - public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() - { - return new HTTPNetworkContext(); - } - - public event EventHandler Disconnected = delegate { }; - /// - /// A request have been received in the context. - /// - public event EventHandler RequestReceived = delegate { }; - - public bool CanSend { get { return true; } } - public string RemoteEndPoint { get { return ""; } } - public string RemoteEndPointAddress { get { return ""; } } - public string RemoteEndPointPort { get { return ""; } } - } - - public class TestHttpRequest: IHttpRequest - { - private string _uriPath; - public bool BodyIsComplete - { - get { return true; } - } - public string[] AcceptTypes - { - get {return _acceptTypes; } - } - private string[] _acceptTypes; - public Stream Body - { - get { return _body; } - set { _body = value;} - } - private Stream _body; - public ConnectionType Connection - { - get { return _connection; } - set { _connection = value; } - } - private ConnectionType _connection; - public int ContentLength - { - get { return _contentLength; } - set { _contentLength = value; } - } - private int _contentLength; - public NameValueCollection Headers - { - get { return _headers; } - } - private NameValueCollection _headers = new NameValueCollection(); - public string HttpVersion - { - get { return _httpVersion; } - set { _httpVersion = value; } - } - private string _httpVersion = null; - public string Method - { - get { return _method; } - set { _method = value; } - } - private string _method = null; - public HttpInput QueryString - { - get { return _queryString; } - } - private HttpInput _queryString = null; - public Uri Uri - { - get { return _uri; } - set { _uri = value; } - } - private Uri _uri = null; - public string[] UriParts - { - get { return _uri.Segments; } - } - public HttpParam Param - { - get { return null; } - } - public HttpForm Form - { - get { return null; } - } - public bool IsAjax - { - get { return false; } - } - public RequestCookies Cookies - { - get { return null; } - } - - public TestHttpRequest() {} - - public TestHttpRequest(string contentEncoding, string contentType, string userAgent, - string remoteAddr, string remotePort, string[] acceptTypes, - ConnectionType connectionType, int contentLength, Uri uri) - { - _headers["content-encoding"] = contentEncoding; - _headers["content-type"] = contentType; - _headers["user-agent"] = userAgent; - _headers["remote_addr"] = remoteAddr; - _headers["remote_port"] = remotePort; - - _acceptTypes = acceptTypes; - _connection = connectionType; - _contentLength = contentLength; - _uri = uri; - } - - public void DecodeBody(FormDecoderProvider providers) {} - public void SetCookies(RequestCookies cookies) {} - public void AddHeader(string name, string value) - { - _headers.Add(name, value); - } - public int AddToBody(byte[] bytes, int offset, int length) - { - return 0; - } - public void Clear() {} - - public object Clone() - { - TestHttpRequest clone = new TestHttpRequest(); - clone._acceptTypes = _acceptTypes; - clone._connection = _connection; - clone._contentLength = _contentLength; - clone._uri = _uri; - clone._headers = new NameValueCollection(_headers); - - return clone; - } - public IHttpResponse CreateResponse(IHttpClientContext context) - { - return new HttpResponse(context, this); - } - /// - /// Path and query (will be merged with the host header) and put in Uri - /// - /// - public string UriPath - { - get { return _uriPath; } - set - { - _uriPath = value; - - } - } - - } - - public class TestHttpResponse: IHttpResponse - { - public Stream Body - { - get { return _body; } - - set { _body = value; } - } - private Stream _body; - - public string ProtocolVersion - { - get { return _protocolVersion; } - set { _protocolVersion = value; } - } - private string _protocolVersion; - - public bool Chunked - { - get { return _chunked; } - - set { _chunked = value; } - } - private bool _chunked; - - public ConnectionType Connection - { - get { return _connection; } - - set { _connection = value; } - } - private ConnectionType _connection; - - public Encoding Encoding - { - get { return _encoding; } - - set { _encoding = value; } - } - private Encoding _encoding; - - public int KeepAlive - { - get { return _keepAlive; } - - set { _keepAlive = value; } - } - private int _keepAlive; - - public HttpStatusCode Status - { - get { return _status; } - - set { _status = value; } - } - private HttpStatusCode _status; - - public string Reason - { - get { return _reason; } - - set { _reason = value; } - } - private string _reason; - - public long ContentLength - { - get { return _contentLength; } - - set { _contentLength = value; } - } - private long _contentLength; - - public string ContentType - { - get { return _contentType; } - - set { _contentType = value; } - } - private string _contentType; - - public bool HeadersSent - { - get { return _headersSent; } - } - private bool _headersSent; - - public bool Sent - { - get { return _sent; } - } - private bool _sent; - - public ResponseCookies Cookies - { - get { return _cookies; } - } - private ResponseCookies _cookies = null; - - public TestHttpResponse() - { - _headersSent = false; - _sent = false; - } - - public void AddHeader(string name, string value) {} - public void Send() - { - if (!_headersSent) SendHeaders(); - if (_sent) throw new InvalidOperationException("stuff already sent"); - _sent = true; - } - - public void SendBody(byte[] buffer, int offset, int count) - { - if (!_headersSent) SendHeaders(); - _sent = true; - } - public void SendBody(byte[] buffer) - { - if (!_headersSent) SendHeaders(); - _sent = true; - } - - public void SendHeaders() - { - if (_headersSent) throw new InvalidOperationException("headers already sent"); - _headersSent = true; - } - - public void Redirect(Uri uri) {} - public void Redirect(string url) {} - } - - - public OSHttpRequest req0; - public OSHttpRequest req1; - - public OSHttpResponse rsp0; - - public IPEndPoint ipEP0; - - [TestFixtureSetUp] - public void Init() - { - TestHttpRequest threq0 = new TestHttpRequest("utf-8", "text/xml", "OpenSim Test Agent", "192.168.0.1", "4711", - new string[] {"text/xml"}, - ConnectionType.KeepAlive, 4711, - new Uri("http://127.0.0.1/admin/inventory/Dr+Who/Tardis")); - threq0.Method = "GET"; - threq0.HttpVersion = HttpHelper.HTTP10; - - TestHttpRequest threq1 = new TestHttpRequest("utf-8", "text/xml", "OpenSim Test Agent", "192.168.0.1", "4711", - new string[] {"text/xml"}, - ConnectionType.KeepAlive, 4711, - new Uri("http://127.0.0.1/admin/inventory/Dr+Who/Tardis?a=0&b=1&c=2")); - threq1.Method = "POST"; - threq1.HttpVersion = HttpHelper.HTTP11; - threq1.Headers["x-wuff"] = "wuffwuff"; - threq1.Headers["www-authenticate"] = "go away"; - - req0 = new OSHttpRequest(new TestHttpClientContext(false), threq0); - req1 = new OSHttpRequest(new TestHttpClientContext(false), threq1); - - rsp0 = new OSHttpResponse(new TestHttpResponse()); - - ipEP0 = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 4711); - - } - - [Test] - public void T000_OSHttpRequest() - { - Assert.That(req0.HttpMethod, Is.EqualTo("GET")); - Assert.That(req0.ContentType, Is.EqualTo("text/xml")); - Assert.That(req0.ContentLength, Is.EqualTo(4711)); - - Assert.That(req1.HttpMethod, Is.EqualTo("POST")); - } - - [Test] - public void T001_OSHttpRequestHeaderAccess() - { - Assert.That(req1.Headers["x-wuff"], Is.EqualTo("wuffwuff")); - Assert.That(req1.Headers.Get("x-wuff"), Is.EqualTo("wuffwuff")); - - Assert.That(req1.Headers["www-authenticate"], Is.EqualTo("go away")); - Assert.That(req1.Headers.Get("www-authenticate"), Is.EqualTo("go away")); - - Assert.That(req0.RemoteIPEndPoint, Is.EqualTo(ipEP0)); - } - - [Test] - public void T002_OSHttpRequestUriParsing() - { - Assert.That(req0.RawUrl, Is.EqualTo("/admin/inventory/Dr+Who/Tardis")); - Assert.That(req1.Url.ToString(), Is.EqualTo("http://127.0.0.1/admin/inventory/Dr+Who/Tardis?a=0&b=1&c=2")); - } - - [Test] - public void T100_OSHttpResponse() - { - rsp0.ContentType = "text/xml"; - Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); - } - } -*/ -} diff --git a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs index 9f8b209345..c23004e174 100644 --- a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs +++ b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs @@ -603,7 +603,8 @@ namespace OpenSim.Region.Framework.Scenes || (sbyte)AssetType.Notecard == assetType || (sbyte)AssetType.LSLText == assetType || (sbyte)OpenSimAssetType.Material == assetType - || (sbyte)AssetType.Object == assetType) + || (sbyte)AssetType.Object == assetType + || (sbyte)AssetType.Settings == assetType) { AddForInspection(assetUuid); } diff --git a/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs index 7b20b8c8d5..7293830492 100644 --- a/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs +++ b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs @@ -31,7 +31,7 @@ using System.IO; using System.Net; using System.Net.Sockets; using System.Text; -using HttpServer; +using OSHttpServer; using OpenSim.Framework; namespace OpenSim.Tests.Common diff --git a/OpenSim/Tests/Common/Mock/TestHttpRequest.cs b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs index 4c5ea4a148..b69c70db5c 100644 --- a/OpenSim/Tests/Common/Mock/TestHttpRequest.cs +++ b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs @@ -28,8 +28,7 @@ using System; using System.Collections.Specialized; using System.IO; -using HttpServer; -using HttpServer.FormDecoders; +using OSHttpServer; namespace OpenSim.Tests.Common { diff --git a/OpenSim/Tests/Common/Mock/TestHttpResponse.cs b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs index 190f6d5d8a..f6f789819c 100644 --- a/OpenSim/Tests/Common/Mock/TestHttpResponse.cs +++ b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs @@ -29,7 +29,7 @@ using System; using System.IO; using System.Net; using System.Text; -using HttpServer; +using OSHttpServer; namespace OpenSim.Tests.Common { diff --git a/OpenSim/Tests/Common/Mock/TestOSHttpResponse.cs b/OpenSim/Tests/Common/Mock/TestOSHttpResponse.cs index 4577ca352f..56cec5957d 100644 --- a/OpenSim/Tests/Common/Mock/TestOSHttpResponse.cs +++ b/OpenSim/Tests/Common/Mock/TestOSHttpResponse.cs @@ -68,6 +68,11 @@ namespace OpenSim.Tests.Common /// public long ContentLength64 { get; set; } + public int Priority { get; set; } + public byte[] RawBuffer { get; set; } + public int RawBufferStart { get; set; } + public int RawBufferLen { get; set; } + /// /// Encoding of the body content. /// @@ -97,11 +102,6 @@ namespace OpenSim.Tests.Common /// public Stream Body { get; private set; } - /// - /// Set a redirct location. - /// - public string RedirectLocation { private get; set; } - /// /// Chunk transfers. /// diff --git a/bin/HttpServer_OpenSim.dll b/bin/HttpServer_OpenSim.dll deleted file mode 100755 index 6136987045..0000000000 Binary files a/bin/HttpServer_OpenSim.dll and /dev/null differ diff --git a/prebuild.xml b/prebuild.xml index 0f2b6c9f6d..f2b7705c59 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -209,20 +209,11 @@ - - - - - - - - - @@ -369,11 +360,11 @@ - + @@ -2700,7 +2691,6 @@ - @@ -3018,7 +3008,6 @@ - @@ -3222,7 +3211,6 @@ ../../../../../bin/ -