using System; using System.Collections.Specialized; using System.IO; using System.Net; using System.Text; using System.Web; using OpenSim.Framework; using OSHttpServer.Exceptions; namespace OSHttpServer { /// /// Contains server side HTTP request information. /// public class HttpRequest : IHttpRequest { private const int MAXCONTENTLENGTH = 250 * 1024 * 1024; /// /// 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(); //private readonly HttpParam m_param = new(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 NameValueCollection m_queryString = null; private Uri m_uri = null; private string m_uriPath; public IHttpClientContext m_context; IPEndPoint m_remoteIPEndPoint = null; 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 { return m_context.IsSecured; } } 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; } } /// /// Assign a form. /// /// /* internal void AssignForm(HttpForm form) { _form = form; } */ #region IHttpRequest Members /// /// 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 NameValueCollection QueryString { get { if(m_queryString is null) { if(m_uri is null || m_uri.Query.Length == 0) m_queryString = new NameValueCollection(); else { try { m_queryString = HttpUtility.ParseQueryString(m_uri.Query); } catch { m_queryString = new NameValueCollection(); } } } return m_queryString; } } public static readonly Uri EmptyUri = new("http://localhost/"); /// /// Gets or sets requested URI. /// public Uri Uri { get { return m_uri; } set { m_uri = value ?? EmptyUri; } // not safe } /* /// /// 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; } public double ArrivalTS { get; 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) { Method = m_method, m_httpVersion = m_httpVersion, m_queryString = m_queryString, Uri = m_uri }; if (AcceptTypes != null) { request.AcceptTypes = new string[AcceptTypes.Length]; AcceptTypes.CopyTo(request.AcceptTypes, 0); } 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; } public IPEndPoint LocalIPEndPoint { get {return m_context.LocalIPEndPoint; }} public IPEndPoint RemoteIPEndPoint { get { if(m_remoteIPEndPoint == null) { string addr = m_headers["x-forwarded-for"]; if(!string.IsNullOrEmpty(addr)) { int port = m_context.LocalIPEndPoint.Port; try { m_remoteIPEndPoint = new IPEndPoint(IPAddress.Parse(addr), port); } catch { m_remoteIPEndPoint = null; } } } m_remoteIPEndPoint ??= m_context.LocalIPEndPoint; return m_remoteIPEndPoint; } } /* /// /// 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": if (!int.TryParse(value, out int t)) throw new BadRequestException("Invalid content length."); if (t > MAXCONTENTLENGTH) throw new OSHttpServer.Exceptions.HttpException(HttpStatusCode.RequestEntityTooLarge,"Request Entity Too Large"); ContentLength = t; break; case "host": try { m_uri = new Uri((Secure ? "https://" : "http://") + value + m_uriPath); m_uriPath = m_uri.AbsolutePath; } catch (UriFormatException err) { throw new BadRequestException("Failed to parse uri: " + value + m_uriPath, err); } break; case "remote_addr": if (m_headers[name] == null) m_headers.Add(name, value); break; case "forwarded": string[] parts = value.Split(Util.SplitSemicolonArray); string addr = string.Empty; for(int i = 0; i < parts.Length; ++i) { string s = parts[i].TrimStart(); if(s.Length < 10) continue; if(s.StartsWith("for", StringComparison.InvariantCultureIgnoreCase)) { int indx = s.IndexOf("=", 3); if(indx < 0 || indx >= s.Length - 1) continue; s = s[indx..]; addr = s.Trim(); } } if(addr.Length > 7) { m_headers.Add("x-forwarded-for", addr); } break; case "x-forwarded-for": if (value.Length > 7) { string[] xparts = value.Split(Util.SplitCommaArray); if(xparts.Length > 0) { string xs = xparts[0].Trim(); if(xs.Length > 7) m_headers.Add("x-forwarded-for", xs); } } 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; case "user-agent": 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.Dispose(); m_body = null; } m_contentLength = 0; m_method = string.Empty; m_uri = null; m_queryString = null; m_bodyBytesLeft = 0; m_headers.Clear(); m_connection = ConnectionType.KeepAlive; IsAjax = false; m_context = null; //_form.Clear(); } #endregion } }