[Mono.Posix] Add Syscall.getgrouplist().
[mono.git] / mcs / class / System / System.Net / HttpListenerRequest.cs
index 521990a2fa2ef1dd2fe99b267be45fb49f0afb86..92866e6485c0193f9da233f68ffbe69ed0e36de5 100644 (file)
@@ -1,10 +1,12 @@
 //
 // System.Net.HttpListenerRequest
 //
-// Author:
-//     Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Authors:
+//     Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
+//     Marek Safar (marek.safar@gmail.com)
 //
 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2011-2012 Xamarin, Inc. (http://xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -26,7 +28,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-#if NET_2_0 && SECURITY_DEP
+#if SECURITY_DEP
 
 using System.Collections;
 using System.Collections.Specialized;
@@ -34,11 +36,28 @@ using System.Globalization;
 using System.IO;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
+#if NET_4_0
+using System.Security.Authentication.ExtendedProtection;
+#endif
+#if NET_4_5
+using System.Threading.Tasks;
+#endif
+using Mono.Security.Protocol.Tls;
+
 namespace System.Net {
        public sealed class HttpListenerRequest
        {
+#if NET_4_0
+               class Context : TransportContext
+               {
+                       public override ChannelBinding GetChannelBinding (ChannelBindingKind kind)
+                       {
+                               throw new NotImplementedException ();
+                       }
+               }
+#endif
+
                string [] accept_types;
-               int client_cert_error;
                Encoding content_encoding;
                long content_length;
                bool cl_set;
@@ -46,30 +65,30 @@ namespace System.Net {
                WebHeaderCollection headers;
                string method;
                Stream input_stream;
-               bool is_authenticated;
                Version version;
                NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
                string raw_url;
-               Guid identifier;
                Uri url;
                Uri referrer;
                string [] user_languages;
-               bool no_get_certificate;
                HttpListenerContext context;
                bool is_chunked;
+               bool ka_set;
+               bool keep_alive;
+               delegate X509Certificate2 GCCDelegate ();
+               GCCDelegate gcc_delegate;
+
                static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
 
                internal HttpListenerRequest (HttpListenerContext context)
                {
                        this.context = context;
                        headers = new WebHeaderCollection ();
-                       input_stream = Stream.Null;
+                       version = HttpVersion.Version10;
                }
 
                static char [] separators = new char [] { ' ' };
-               // From WebRequestMethods.Http
-               static readonly string [] methods = new string [] { "GET", "POST", "HEAD",
-                                                               "PUT", "CONNECT", "MKCOL" };
+
                internal void SetRequestLine (string req)
                {
                        string [] parts = req.Split (separators, 3);
@@ -79,8 +98,17 @@ namespace System.Net {
                        }
 
                        method = parts [0];
-                       if (Array.IndexOf (methods, method) == -1) {
-                               context.ErrorMessage = "Invalid request line (verb).";
+                       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;
                        }
 
@@ -102,10 +130,14 @@ namespace System.Net {
 
                void CreateQueryString (string query)
                {
-                       query_string = new NameValueCollection ();
-                       if (query == null || query.Length == 0)
+                       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 ('=');
@@ -123,65 +155,77 @@ namespace System.Net {
                internal void FinishInitialization ()
                {
                        string host = UserHostName;
-                       if (version > HttpVersion.Version10 && (host == null || host == "")) {
+                       if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) {
                                context.ErrorMessage = "Invalid host name";
                                return;
                        }
 
-                       if (host == null || host == "")
+                       string path;
+                       Uri raw_uri = null;
+                       if (Uri.MaybeUri (raw_url) && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri))
+                               path = raw_uri.PathAndQuery;
+                       else
+                               path = 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);
-                       try {
-                               url = new Uri (base_uri + raw_url);
-                       } catch {
-                               context.ErrorMessage = "Invalid url";
+                                                               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 (method == "GET" || method == "HEAD")
-                               return;
-
-                       string t_encoding = null;
                        if (version >= HttpVersion.Version11) {
-                               t_encoding = Headers ["Transfer-Encoding"];
+                               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 && t_encoding != "chunked") {
+                               if (t_encoding != null && !is_chunked) {
                                        context.Connection.SendError (null, 501);
                                        return;
                                }
                        }
 
-                       bool is_chunked = (t_encoding == "chunked");
                        if (!is_chunked && !cl_set) {
-                               context.Connection.SendError (null, 411);
-                               return;
-                       }
-
-                       if (is_chunked || content_length > 0) {
-                               input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
+                               if (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
+                                   String.Compare (method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) {
+                                       context.Connection.SendError (null, 411);
+                                       return;
+                               }
                        }
 
-                       if (Headers ["Expect"] == "100-continue") {
+                       if (String.Compare (Headers ["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) {
                                ResponseStream output = context.Connection.GetResponseStream ();
                                output.InternalWrite (_100continue, 0, _100continue.Length);
                        }
                }
 
+               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 ();
+               }
+
                internal void AddHeader (string header)
                {
                        int colon = header.IndexOf (':');
                        if (colon == -1 || colon == 0) {
                                context.ErrorMessage = "Bad Request";
+                               context.ErrorStatus = 400;
                                return;
                        }
 
@@ -193,7 +237,7 @@ namespace System.Net {
                                case "accept-language":
                                        user_languages = val.Split (','); // yes, only split with a ','
                                        break;
-                               case "accept-types":
+                               case "accept":
                                        accept_types = val.Split (','); // yes, only split with a ','
                                        break;
                                case "content-length":
@@ -215,7 +259,73 @@ namespace System.Net {
                                                referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/");
                                        }
                                        break;
-                               //TODO: cookie headers
+                               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;
+                       }
+               }
+
+               // 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 (1000))
+                                               return false;
+                                       if (InputStream.EndRead (ares) <= 0)
+                                               return true;
+                               } catch {
+                                       return false;
+                               }
                        }
                }
 
@@ -225,10 +335,13 @@ namespace System.Net {
 
                public int ClientCertificateError {
                        get {
-                               if (no_get_certificate)
-                                       throw new InvalidOperationException (
-                                               "Call GetClientCertificate() before calling this method.");
-                               return client_cert_error;
+                               HttpConnection cnc = context.Connection;
+                               if (cnc.ClientCertificate == null)
+                                       throw new InvalidOperationException ("No client certificate");
+                               int [] errors = cnc.ClientCertificateErrors;
+                               if (errors != null && errors.Length > 0)
+                                       return errors [0];
+                               return 0;
                        }
                }
 
@@ -258,7 +371,7 @@ namespace System.Net {
                }
 
                public bool HasEntityBody {
-                       get { return (method == "GET" || method == "HEAD" || content_length <= 0 || is_chunked); }
+                       get { return (content_length > 0 || is_chunked); }
                }
 
                public NameValueCollection Headers {
@@ -270,11 +383,21 @@ namespace System.Net {
                }
 
                public Stream InputStream {
-                       get { return input_stream; }
+                       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;
+                       }
                }
 
+               [MonoTODO ("Always returns false")]
                public bool IsAuthenticated {
-                       get { return is_authenticated; }
+                       get { return false; }
                }
 
                public bool IsLocal {
@@ -286,7 +409,26 @@ namespace System.Net {
                }
 
                public bool KeepAlive {
-                       get { return false; }
+                       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 {
@@ -309,8 +451,9 @@ namespace System.Net {
                        get { return context.Connection.RemoteEndPoint; }
                }
 
+               [MonoTODO ("Always returns Guid.Empty")]
                public Guid RequestTraceIdentifier {
-                       get { return identifier; }
+                       get { return Guid.Empty; }
                }
 
                public Uri Url {
@@ -337,23 +480,55 @@ namespace System.Net {
                        get { return user_languages; }
                }
 
-               public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, Object state)
+               public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, object state)
                {
-                       return null;
+                       if (gcc_delegate == null)
+                               gcc_delegate = new GCCDelegate (GetClientCertificate);
+                       return gcc_delegate.BeginInvoke (requestCallback, state);
                }
-#if SECURITY_DEP
+
                public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
                {
-                       return null;
-                       // set no_client_certificate once done.
+                       if (asyncResult == null)
+                               throw new ArgumentNullException ("asyncResult");
+
+                       if (gcc_delegate == null)
+                               throw new InvalidOperationException ();
+
+                       return gcc_delegate.EndInvoke (asyncResult);
                }
 
                public X509Certificate2 GetClientCertificate ()
                {
-                       // set no_client_certificate once done.
+                       return context.Connection.ClientCertificate;
+               }
+
+#if NET_4_0
+               [MonoTODO]
+               public string ServiceName {
+                       get {
+                               return null;
+                       }
+               }
+               
+               public TransportContext TransportContext {
+                       get {
+                               return new Context ();
+                       }
+               }
+#endif
+               
+#if NET_4_5
+               [MonoTODO]
+               public bool IsWebSocketRequest {
+                       get {
+                               return false;
+                       }
+               }
 
-                       // InvalidOp if call in progress.
-                       return null;
+               public Task<X509Certificate2> GetClientCertificateAsync ()
+               {
+                       return Task<X509Certificate2>.Factory.FromAsync (BeginGetClientCertificate, EndGetClientCertificate, null);
                }
 #endif
        }