// 
 | 
// HttpListenerRequest.cs 
 | 
//    Copied from System.Net.HttpListenerRequest.cs 
 | 
// 
 | 
// Author: 
 | 
//    Gonzalo Paniagua Javier (gonzalo@novell.com) 
 | 
// 
 | 
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) 
 | 
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com) 
 | 
// 
 | 
// Permission is hereby granted, free of charge, to any person obtaining 
 | 
// a copy of this software and associated documentation files (the 
 | 
// "Software"), to deal in the Software without restriction, including 
 | 
// without limitation the rights to use, copy, modify, merge, publish, 
 | 
// distribute, sublicense, and/or sell copies of the Software, and to 
 | 
// permit persons to whom the Software is furnished to do so, subject to 
 | 
// the following conditions: 
 | 
//  
 | 
// The above copyright notice and this permission notice shall be 
 | 
// included in all copies or substantial portions of the Software. 
 | 
//  
 | 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 | 
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 | 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 | 
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 | 
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 | 
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 | 
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 | 
// 
 | 
  
 | 
using System; 
 | 
using System.Collections; 
 | 
using System.Collections.Specialized; 
 | 
using System.Globalization; 
 | 
using System.IO; 
 | 
using System.Net; 
 | 
using System.Security.Cryptography.X509Certificates; 
 | 
using System.Text; 
 | 
  
 | 
namespace WebSocketSharp.Net 
 | 
{ 
 | 
    public sealed class HttpListenerRequest 
 | 
    { 
 | 
        #region Private Static Fields 
 | 
  
 | 
        static char[] separators = new char[] { ' ' }; 
 | 
        static byte[] _100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Fields 
 | 
  
 | 
        string[] accept_types; 
 | 
        //        int                 client_cert_error; 
 | 
        bool cl_set; 
 | 
        Encoding content_encoding; 
 | 
        long content_length; 
 | 
        HttpListenerContext context; 
 | 
        CookieCollection cookies; 
 | 
        WebHeaderCollection headers; 
 | 
        Stream input_stream; 
 | 
        bool is_chunked; 
 | 
        bool ka_set; 
 | 
        bool keep_alive; 
 | 
        string method; 
 | 
        //        bool                no_get_certificate; 
 | 
        Version version; 
 | 
        NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness 
 | 
        string raw_url; 
 | 
        Uri referrer; 
 | 
        Uri url; 
 | 
        string[] user_languages; 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Constructor 
 | 
  
 | 
        internal HttpListenerRequest(HttpListenerContext context) 
 | 
        { 
 | 
            this.context = context; 
 | 
            headers = new WebHeaderCollection(); 
 | 
            version = HttpVersion.Version10; 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Properties 
 | 
  
 | 
        public string[] AcceptTypes 
 | 
        { 
 | 
            get { return accept_types; } 
 | 
        } 
 | 
  
 | 
        // TODO: Always returns 0 
 | 
        public int ClientCertificateError 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                /*                 
 | 
                                if (no_get_certificate) 
 | 
                                    throw new InvalidOperationException ( 
 | 
                                        "Call GetClientCertificate() before calling this method."); 
 | 
                                return client_cert_error; 
 | 
                */ 
 | 
                return 0; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public Encoding ContentEncoding 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                if (content_encoding == null) 
 | 
                    content_encoding = Encoding.Default; 
 | 
                return content_encoding; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public long ContentLength64 
 | 
        { 
 | 
            get { return content_length; } 
 | 
        } 
 | 
  
 | 
        public string ContentType 
 | 
        { 
 | 
            get { return headers["content-type"]; } 
 | 
        } 
 | 
  
 | 
        public CookieCollection Cookies 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                // TODO: check if the collection is read-only 
 | 
                if (cookies == null) 
 | 
                    cookies = new CookieCollection(); 
 | 
                return cookies; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public bool HasEntityBody 
 | 
        { 
 | 
            get { return (content_length > 0 || is_chunked); } 
 | 
        } 
 | 
  
 | 
        public NameValueCollection Headers 
 | 
        { 
 | 
            get { return headers; } 
 | 
        } 
 | 
  
 | 
        public string HttpMethod 
 | 
        { 
 | 
            get { return method; } 
 | 
        } 
 | 
  
 | 
        public Stream InputStream 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                if (input_stream == null) 
 | 
                { 
 | 
                    if (is_chunked || content_length > 0) 
 | 
                        input_stream = context.Connection.GetRequestStream(is_chunked, content_length); 
 | 
                    else 
 | 
                        input_stream = Stream.Null; 
 | 
                } 
 | 
  
 | 
                return input_stream; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // TODO: Always returns false 
 | 
        public bool IsAuthenticated 
 | 
        { 
 | 
            get { return false; } 
 | 
        } 
 | 
  
 | 
        public bool IsLocal 
 | 
        { 
 | 
            get { return IPAddress.IsLoopback(RemoteEndPoint.Address); } 
 | 
        } 
 | 
  
 | 
        public bool IsSecureConnection 
 | 
        { 
 | 
            get { return context.Connection.IsSecure; } 
 | 
        } 
 | 
  
 | 
        public bool IsWebSocketRequest 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                if (method != "GET") 
 | 
                    return false; 
 | 
  
 | 
                if (version != HttpVersion.Version11) 
 | 
                    return false; 
 | 
  
 | 
                if (!Ext.Exists(headers, "Upgrade", "websocket")) 
 | 
                    return false; 
 | 
  
 | 
                if (!Ext.Exists(headers, "Connection", "Upgrade")) 
 | 
                    return false; 
 | 
  
 | 
                if (!Ext.Exists(headers, "Host")) 
 | 
                    return false; 
 | 
  
 | 
                if (!Ext.Exists(headers, "Sec-WebSocket-Key")) 
 | 
                    return false; 
 | 
  
 | 
                if (!Ext.Exists(headers, "Sec-WebSocket-Version")) 
 | 
                    return false; 
 | 
  
 | 
                return true; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public bool KeepAlive 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                if (ka_set) 
 | 
                    return keep_alive; 
 | 
  
 | 
                ka_set = true; 
 | 
                // 1. Connection header 
 | 
                // 2. Protocol (1.1 == keep-alive by default) 
 | 
                // 3. Keep-Alive header 
 | 
                string cnc = headers["Connection"]; 
 | 
                if (!String.IsNullOrEmpty(cnc)) 
 | 
                { 
 | 
                    keep_alive = (0 == String.Compare(cnc, "keep-alive", StringComparison.OrdinalIgnoreCase)); 
 | 
                } 
 | 
                else if (version == HttpVersion.Version11) 
 | 
                { 
 | 
                    keep_alive = true; 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    cnc = headers["keep-alive"]; 
 | 
                    if (!String.IsNullOrEmpty(cnc)) 
 | 
                        keep_alive = (0 != String.Compare(cnc, "closed", StringComparison.OrdinalIgnoreCase)); 
 | 
                } 
 | 
                return keep_alive; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public IPEndPoint LocalEndPoint 
 | 
        { 
 | 
            get { return context.Connection.LocalEndPoint; } 
 | 
        } 
 | 
  
 | 
        public Version ProtocolVersion 
 | 
        { 
 | 
            get { return version; } 
 | 
        } 
 | 
  
 | 
        public NameValueCollection QueryString 
 | 
        { 
 | 
            get { return query_string; } 
 | 
        } 
 | 
  
 | 
        public string RawUrl 
 | 
        { 
 | 
            get { return raw_url; } 
 | 
        } 
 | 
  
 | 
        public IPEndPoint RemoteEndPoint 
 | 
        { 
 | 
            get { return context.Connection.RemoteEndPoint; } 
 | 
        } 
 | 
  
 | 
        // TODO: Always returns Guid.Empty 
 | 
        public Guid RequestTraceIdentifier 
 | 
        { 
 | 
            get { return Guid.Empty; } 
 | 
        } 
 | 
  
 | 
        public Uri Url 
 | 
        { 
 | 
            get { return url; } 
 | 
        } 
 | 
  
 | 
        public Uri UrlReferrer 
 | 
        { 
 | 
            get { return referrer; } 
 | 
        } 
 | 
  
 | 
        public string UserAgent 
 | 
        { 
 | 
            get { return headers["user-agent"]; } 
 | 
        } 
 | 
  
 | 
        public string UserHostAddress 
 | 
        { 
 | 
            get { return LocalEndPoint.ToString(); } 
 | 
        } 
 | 
  
 | 
        public string UserHostName 
 | 
        { 
 | 
            get { return headers["host"]; } 
 | 
        } 
 | 
  
 | 
        public string[] UserLanguages 
 | 
        { 
 | 
            get { return user_languages; } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Methods 
 | 
  
 | 
        void CreateQueryString(string query) 
 | 
        { 
 | 
            if (query == null || query.Length == 0) 
 | 
            { 
 | 
                query_string = new NameValueCollection(1); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            query_string = new NameValueCollection(); 
 | 
            if (query[0] == '?') 
 | 
                query = query.Substring(1); 
 | 
            string[] components = query.Split('&'); 
 | 
            foreach (string kv in components) 
 | 
            { 
 | 
                int pos = kv.IndexOf('='); 
 | 
                if (pos == -1) 
 | 
                { 
 | 
                    query_string.Add(null, HttpUtility.UrlDecode(kv)); 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    string key = HttpUtility.UrlDecode(kv.Substring(0, pos)); 
 | 
                    string val = HttpUtility.UrlDecode(kv.Substring(pos + 1)); 
 | 
  
 | 
                    query_string.Add(key, val); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Internal Methods 
 | 
  
 | 
        internal void AddHeader(string header) 
 | 
        { 
 | 
            int colon = header.IndexOf(':'); 
 | 
            if (colon == -1 || colon == 0) 
 | 
            { 
 | 
                context.ErrorMessage = "Bad Request"; 
 | 
                context.ErrorStatus = 400; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            string name = header.Substring(0, colon).Trim(); 
 | 
            string val = header.Substring(colon + 1).Trim(); 
 | 
            string lower = name.ToLower(CultureInfo.InvariantCulture); 
 | 
            headers.SetInternal(name, val); 
 | 
            switch (lower) 
 | 
            { 
 | 
                case "accept-language": 
 | 
                    user_languages = val.Split(','); // yes, only split with a ',' 
 | 
                    break; 
 | 
                case "accept": 
 | 
                    accept_types = val.Split(','); // yes, only split with a ',' 
 | 
                    break; 
 | 
                case "content-length": 
 | 
                    try 
 | 
                    { 
 | 
                        //TODO: max. content_length? 
 | 
                        content_length = Int64.Parse(val.Trim()); 
 | 
                        if (content_length < 0) 
 | 
                            context.ErrorMessage = "Invalid Content-Length."; 
 | 
                        cl_set = true; 
 | 
                    } 
 | 
                    catch 
 | 
                    { 
 | 
                        context.ErrorMessage = "Invalid Content-Length."; 
 | 
                    } 
 | 
  
 | 
                    break; 
 | 
                case "referer": 
 | 
                    try 
 | 
                    { 
 | 
                        referrer = new Uri(val); 
 | 
                    } 
 | 
                    catch 
 | 
                    { 
 | 
                        referrer = new Uri("http://someone.is.screwing.with.the.headers.com/"); 
 | 
                    } 
 | 
                    break; 
 | 
                case "cookie": 
 | 
                    if (cookies == null) 
 | 
                        cookies = new CookieCollection(); 
 | 
  
 | 
                    string[] cookieStrings = val.Split(new char[] { ',', ';' }); 
 | 
                    Cookie current = null; 
 | 
                    int version = 0; 
 | 
                    foreach (string cookieString in cookieStrings) 
 | 
                    { 
 | 
                        string str = cookieString.Trim(); 
 | 
                        if (str.Length == 0) 
 | 
                            continue; 
 | 
                        if (str.StartsWith("$Version")) 
 | 
                        { 
 | 
                            version = Int32.Parse(Unquote(str.Substring(str.IndexOf('=') + 1))); 
 | 
                        } 
 | 
                        else if (str.StartsWith("$Path")) 
 | 
                        { 
 | 
                            if (current != null) 
 | 
                                current.Path = str.Substring(str.IndexOf('=') + 1).Trim(); 
 | 
                        } 
 | 
                        else if (str.StartsWith("$Domain")) 
 | 
                        { 
 | 
                            if (current != null) 
 | 
                                current.Domain = str.Substring(str.IndexOf('=') + 1).Trim(); 
 | 
                        } 
 | 
                        else if (str.StartsWith("$Port")) 
 | 
                        { 
 | 
                            if (current != null) 
 | 
                                current.Port = str.Substring(str.IndexOf('=') + 1).Trim(); 
 | 
                        } 
 | 
                        else 
 | 
                        { 
 | 
                            if (current != null) 
 | 
                            { 
 | 
                                cookies.Add(current); 
 | 
                            } 
 | 
                            current = new Cookie(); 
 | 
                            int idx = str.IndexOf('='); 
 | 
                            if (idx > 0) 
 | 
                            { 
 | 
                                current.Name = str.Substring(0, idx).Trim(); 
 | 
                                current.Value = str.Substring(idx + 1).Trim(); 
 | 
                            } 
 | 
                            else 
 | 
                            { 
 | 
                                current.Name = str.Trim(); 
 | 
                                current.Value = String.Empty; 
 | 
                            } 
 | 
                            current.Version = version; 
 | 
                        } 
 | 
                    } 
 | 
                    if (current != null) 
 | 
                    { 
 | 
                        cookies.Add(current); 
 | 
                    } 
 | 
                    break; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        internal void FinishInitialization() 
 | 
        { 
 | 
            string host = UserHostName; 
 | 
            if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) 
 | 
            { 
 | 
                context.ErrorMessage = "Invalid host name"; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            string path; 
 | 
            Uri raw_uri = null; 
 | 
            if (Ext.MaybeUri(raw_url) && Uri.TryCreate(raw_url, UriKind.Absolute, out raw_uri)) 
 | 
                path = raw_uri.PathAndQuery; 
 | 
            else 
 | 
                path = HttpUtility.UrlDecode(raw_url); 
 | 
  
 | 
            if ((host == null || host.Length == 0)) 
 | 
                host = UserHostAddress; 
 | 
  
 | 
            if (raw_uri != null) 
 | 
                host = raw_uri.Host; 
 | 
  
 | 
            int colon = host.IndexOf(':'); 
 | 
            if (colon >= 0) 
 | 
                host = host.Substring(0, colon); 
 | 
  
 | 
            string base_uri = String.Format("{0}://{1}:{2}", 
 | 
                                (IsSecureConnection) ? "https" : "http", 
 | 
                                host, 
 | 
                                LocalEndPoint.Port); 
 | 
  
 | 
            if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out url)) 
 | 
            { 
 | 
                context.ErrorMessage = "Invalid url: " + base_uri + path; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            CreateQueryString(url.Query); 
 | 
  
 | 
            if (version >= HttpVersion.Version11) 
 | 
            { 
 | 
                string t_encoding = Headers["Transfer-Encoding"]; 
 | 
                is_chunked = (t_encoding != null && String.Compare(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0); 
 | 
                // 'identity' is not valid! 
 | 
                if (t_encoding != null && !is_chunked) 
 | 
                { 
 | 
                    context.Connection.SendError(null, 501); 
 | 
                    return; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            if (!is_chunked && !cl_set) 
 | 
            { 
 | 
                if (String.Compare(method, "POST", StringComparison.OrdinalIgnoreCase) == 0 || 
 | 
                    String.Compare(method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) 
 | 
                { 
 | 
                    context.Connection.SendError(null, 411); 
 | 
                    return; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) 
 | 
            { 
 | 
                ResponseStream output = context.Connection.GetResponseStream(); 
 | 
                output.InternalWrite(_100continue, 0, _100continue.Length); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // returns true is the stream could be reused. 
 | 
        internal bool FlushInput() 
 | 
        { 
 | 
            if (!HasEntityBody) 
 | 
                return true; 
 | 
  
 | 
            int length = 2048; 
 | 
            if (content_length > 0) 
 | 
                length = (int)Math.Min(content_length, (long)length); 
 | 
  
 | 
            byte[] bytes = new byte[length]; 
 | 
            while (true) 
 | 
            { 
 | 
                // TODO: test if MS has a timeout when doing this 
 | 
                try 
 | 
                { 
 | 
                    IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null); 
 | 
                    if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(100, false)) 
 | 
                        return false; 
 | 
                    if (InputStream.EndRead(ares) <= 0) 
 | 
                        return true; 
 | 
                } 
 | 
                catch 
 | 
                { 
 | 
                    return false; 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        internal void SetRequestLine(string req) 
 | 
        { 
 | 
            string[] parts = req.Split(separators, 3); 
 | 
            if (parts.Length != 3) 
 | 
            { 
 | 
                context.ErrorMessage = "Invalid request line (parts)."; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            method = parts[0]; 
 | 
            foreach (char c in method) 
 | 
            { 
 | 
                int ic = (int)c; 
 | 
  
 | 
                if ((ic >= 'A' && ic <= 'Z') || 
 | 
                    (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && 
 | 
                     c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && 
 | 
                     c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && 
 | 
                     c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) 
 | 
                    continue; 
 | 
  
 | 
                context.ErrorMessage = "(Invalid verb)"; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            raw_url = parts[1]; 
 | 
            if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/")) 
 | 
            { 
 | 
                context.ErrorMessage = "Invalid request line (version)."; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            try 
 | 
            { 
 | 
                version = new Version(parts[2].Substring(5)); 
 | 
                if (version.Major < 1) 
 | 
                    throw new Exception(); 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
                context.ErrorMessage = "Invalid request line (version)."; 
 | 
                return; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        internal static string Unquote(String str) 
 | 
        { 
 | 
            int start = str.IndexOf('\"'); 
 | 
            int end = str.LastIndexOf('\"'); 
 | 
            if (start >= 0 && end >= 0) 
 | 
                str = str.Substring(start + 1, end - 1); 
 | 
            return str.Trim(); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Public Methods 
 | 
  
 | 
        // TODO: Always returns null 
 | 
        public IAsyncResult BeginGetClientCertificate(AsyncCallback requestCallback, Object state) 
 | 
        { 
 | 
            return null; 
 | 
        } 
 | 
  
 | 
        // TODO: Always returns null 
 | 
        public X509Certificate2 EndGetClientCertificate(IAsyncResult asyncResult) 
 | 
        { 
 | 
            // set no_client_certificate once done. 
 | 
  
 | 
            return null; 
 | 
        } 
 | 
  
 | 
        // TODO: Always returns null 
 | 
        public X509Certificate2 GetClientCertificate() 
 | 
        { 
 | 
            // set no_client_certificate once done. 
 | 
  
 | 
            // InvalidOp if call in progress. 
 | 
            return null; 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
    } 
 | 
} 
 |