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 } }