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