initial implementation of 'unify request'
[mono.git] / mcs / class / System.Web / System.Web / HttpRequest.cs
index d2a5deceffa48b11f49c75445d95dbbd45ede4ce..16e12ff59b22d88a18c10e6de2d172583c427813 100644 (file)
@@ -45,13 +45,13 @@ namespace System.Web {
        
        // CAS - no InheritanceDemand here as the class is sealed
        [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
-       public sealed class HttpRequest {
+       public sealed partial class HttpRequest {
                HttpWorkerRequest worker_request;
                HttpContext context;
                WebROCollection query_string_nvc;
 
                //
-               string filename;
+               //string filename;
                string orig_url = null;
                UriBuilder url_components;
 
@@ -67,6 +67,7 @@ namespace System.Web {
                Encoding encoding;
                string current_exe_path;
                string physical_path;
+               string unescaped_path;
                string path_info;
                WebROCollection all_params;
                WebROCollection headers;
@@ -87,50 +88,119 @@ namespace System.Web {
                string [] user_languages;
                Uri cached_url;
                TempFileStream request_file;
+
+               readonly static System.Net.IPAddress [] host_addresses;
                
                // Validations
                bool validate_cookies, validate_query_string, validate_form;
                bool checked_cookies, checked_query_string, checked_form;
+
+               readonly static char [] queryTrimChars = {'?'};
+               
+               static HttpRequest ()
+               {
+                       host_addresses = GetLocalHostAddresses ();
+               }
                
                public HttpRequest (string filename, string url, string queryString)
                {
                        // warning 169: what are we supposed to do with filename?
                        
-                       this.filename = filename;
+                       //this.filename = filename;
 
                        orig_url = url;
                        url_components = new UriBuilder (url);
                        url_components.Query = queryString;
                        
-                       query_string_nvc = new WebROCollection ();                                      
-                       HttpUtility.ParseQueryString (queryString, Encoding.Default, query_string_nvc);
+                       query_string_nvc = new WebROCollection ();
+                       if (queryString != null)
+                               HttpUtility.ParseQueryString (queryString, Encoding.Default, query_string_nvc);
                        query_string_nvc.Protect ();
                }
 
+               internal HttpRequest (HttpWorkerRequest worker_request, HttpContext context)
+               {
+                       this.worker_request = worker_request;
+                       this.context = context;
+               }
+               
                UriBuilder UrlComponents {
                        get {
                                if (url_components == null) {
-                                       url_components = new UriBuilder ();
-                                       url_components.Scheme = worker_request.GetProtocol ();
-                                       url_components.Host = worker_request.GetServerName ();
-                                       url_components.Port = worker_request.GetLocalPort ();
-                                       url_components.Path = worker_request.GetUriPath ();
-                                       
+                                       string query;
                                        byte[] queryStringRaw = worker_request.GetQueryStringRawBytes();
                                        if(queryStringRaw != null)
-                                               url_components.Query = ContentEncoding.GetString(queryStringRaw);
+                                               query = ContentEncoding.GetString(queryStringRaw);
                                        else
-                                               url_components.Query = worker_request.GetQueryString();
+                                               query = worker_request.GetQueryString();
+                                       
+                                       BuildUrlComponents (
+#if NET_2_0
+                                               ApplyUrlMapping (worker_request.GetUriPath ()),
+#else
+                                               worker_request.GetUriPath (),
+#endif
+                                               query);
                                }
                                return url_components;
                        }
                }
-               
-               internal HttpRequest (HttpWorkerRequest worker_request, HttpContext context)
+
+               void BuildUrlComponents (string path, string query)
                {
-                       this.worker_request = worker_request;
-                       this.context = context;
+                       if (url_components != null)
+                               return;
+                       url_components = new UriBuilder ();
+                       url_components.Scheme = worker_request.GetProtocol ();
+                       url_components.Host = worker_request.GetServerName ();
+                       url_components.Port = worker_request.GetLocalPort ();
+                       url_components.Path = path;
+                       if (query != null && query.Length > 0)
+                               url_components.Query = query.TrimStart (queryTrimChars);
+               }
+
+#if NET_2_0
+               internal string ApplyUrlMapping (string url)
+               {
+                       if (WebConfigurationManager.HasConfigErrors)
+                               return url;
+                       
+                       UrlMappingsSection ums = WebConfigurationManager.GetSection ("system.web/urlMappings", ApplicationPath) as UrlMappingsSection;
+                       UrlMappingCollection umc;
+
+                       if (ums == null || !ums.IsEnabled || (umc = ums.UrlMappings).Count == 0)
+                               return url;
+
+                       string relUrl = VirtualPathUtility.ToAppRelative (url);
+                       UrlMapping um = null;
+                       
+                       foreach (UrlMapping u in umc) {
+                               if (u == null)
+                                       continue;
+                               if (String.Compare (relUrl, u.Url, StringComparison.Ordinal) == 0) {
+                                       um = u;
+                                       break;
+                               }
+                       }
+
+                       if (um == null)
+                               return url;
+
+                       string rawUrl = VirtualPathUtility.ToAbsolute (um.MappedUrl.Trim ());
+                       Uri newUrl = new Uri ("http://host.com" + rawUrl);
+
+                       if (url_components != null) {
+                               url_components.Path = newUrl.AbsolutePath;
+                               url_components.Query = newUrl.Query.TrimStart (queryTrimChars);
+                               query_string_nvc = new WebROCollection ();
+                               HttpUtility.ParseQueryString (newUrl.Query, Encoding.Default, query_string_nvc);
+                               query_string_nvc.Protect ();
+                       } else
+                               BuildUrlComponents (newUrl.AbsolutePath, newUrl.Query);
+
+                       return url_components.Path;
                }
+#endif
 
                string [] SplitHeader (int header_index)
                {
@@ -298,6 +368,10 @@ namespace System.Web {
                                        }
                                }
 
+#if TARGET_J2EE
+                               // For J2EE portal support we emulate cookies using the session.
+                               GetSessionCookiesForPortal (cookies);
+#endif
                                if (validate_cookies && !checked_cookies){
                                        ValidateCookieCollection (cookies);
                                        checked_cookies = true;
@@ -317,13 +391,27 @@ namespace System.Web {
                        }
                }
 
+#if NET_2_0
+               public string AppRelativeCurrentExecutionFilePath {
+                       get {
+                               return VirtualPathUtility.ToAppRelative (CurrentExecutionFilePath);
+                       }
+               }
+#endif
+
                public string FilePath {
                        get {
                                if (worker_request == null)
                                        return "/"; // required for 2.0
 
                                if (file_path == null)
-                                       file_path = UrlUtils.Canonic (worker_request.GetFilePath ());
+                                       file_path = UrlUtils.Canonic (
+#if NET_2_0
+                                               ApplyUrlMapping (worker_request.GetFilePath ())
+#else
+                                               worker_request.GetFilePath ()
+#endif
+                                       );
 
                                return file_path;
                        }
@@ -440,7 +528,8 @@ namespace System.Web {
                //
                void AddRawKeyValue (StringBuilder key, StringBuilder value)
                {
-                       form.Add (HttpUtility.UrlDecode (key.ToString (), ContentEncoding),
+                       string decodedKey = HttpUtility.UrlDecode (key.ToString (), ContentEncoding);
+                       form.Add (decodedKey,
                                  HttpUtility.UrlDecode (value.ToString (), ContentEncoding));
 
                        key.Length = 0;
@@ -450,7 +539,11 @@ namespace System.Web {
                //
                // Loads the form data from on a application/x-www-form-urlencoded post
                // 
+#if TARGET_J2EE
+               void RawLoadWwwForm ()
+#else
                void LoadWwwForm ()
+#endif
                {
                        Stream input = GetSubStream (InputStream);
                        StreamReader s = new StreamReader (input, ContentEncoding);
@@ -483,7 +576,7 @@ namespace System.Web {
 
                        EndSubStream (input);
                }
-               
+
                bool IsContentType (string ct, bool starts_with)
                {
                        if (starts_with)
@@ -498,10 +591,14 @@ namespace System.Web {
                                        form = new WebROCollection ();
                                        files = new HttpFileCollection ();
 
-                                       if (IsContentType ("application/x-www-form-urlencoded", true))
-                                               LoadWwwForm ();
-                                       else if (IsContentType ("multipart/form-data", true))
+                                       if (IsContentType ("multipart/form-data", true))
                                                LoadMultiPart ();
+                                       else if (
+#if TARGET_J2EE
+                                               Context.IsPortletRequest ||
+#endif
+                                               IsContentType ("application/x-www-form-urlencoded", true))
+                                               LoadWwwForm ();
 
                                        form.Protect ();
                                }
@@ -517,34 +614,9 @@ namespace System.Web {
 
                public NameValueCollection Headers {
                        get {
-                               if (headers == null){
-                                       headers = new WebROCollection ();
-                                       if (worker_request == null) {
-                                               headers.Protect ();
-                                               return headers;
-                                       }
-
-                                       for (int i = 0; i < HttpWorkerRequest.RequestHeaderMaximum; i++){
-                                               string hval = worker_request.GetKnownRequestHeader (i);
-
-                                               if (hval == null || hval == "")
-                                                       continue;
-                                               
-                                               headers.Add (HttpWorkerRequest.GetKnownRequestHeaderName (i), hval);
-                                       }
+                               if (headers == null)
+                                       headers = new HeadersCollection (this);
                                
-                                       string [][] unknown = worker_request.GetUnknownRequestHeaders ();
-                                       if (unknown != null && unknown.GetUpperBound (0) != -1){
-                                               int top = unknown.GetUpperBound (0) + 1;
-                                               
-                                               for (int i = 0; i < top; i++){
-                                                       // should check if unknown [i] is not null, but MS does not. 
-                                                       
-                                                       headers.Add (unknown [i][0], unknown [i][1]);
-                                               }
-                                       }
-                                       headers.Protect ();
-                               }
                                return headers;
                        }
                }
@@ -561,66 +633,28 @@ namespace System.Web {
                        }
                }
 
-
-#if TARGET_JVM 
-               const int INPUT_BUFFER_SIZE = 1024;
-
-               void MakeInputStream ()
+               void DoFilter (byte [] buffer)
                {
-                       if (worker_request == null)
-                               throw new HttpException ("No HttpWorkerRequest");
-
-                       // consider for perf:
-                       //    return ((ServletWorkerRequest)worker_request).InputStream();
-
-                       //
-                       // Use an unmanaged memory block as this might be a large
-                       // upload
-                       //
-                       int content_length = ContentLength;
+                       if (input_filter == null || filter == null)
+                               return;
 
-                       if (content_length == 0 && HttpMethod == "POST")
-                               throw new HttpException (411, "Length expected");
-                       
-#if NET_2_0
-                       HttpRuntimeSection config = (HttpRuntimeSection) WebConfigurationManager.GetSection ("system.web/httpRuntime");
-#else
-                       HttpRuntimeConfig config = (HttpRuntimeConfig) HttpContext.GetAppConfig ("system.web/httpRuntime");
-#endif
-                       if (content_length > (config.MaxRequestLength * 1024))
-                               throw new HttpException ("File exceeds httpRuntime limit");
-                       
-                       byte[] content = new byte[content_length];
-                       if (content == null)
-                               throw new HttpException (String.Format ("Not enough memory to allocate {0} bytes", content_length));
+                       if (buffer.Length < 1024)
+                               buffer = new byte [1024];
 
-                       int total;
-                       byte [] buffer;
-                       buffer = worker_request.GetPreloadedEntityBody ();
-                       if (buffer != null){
-                               total = buffer.Length;
-                               if (content_length > 0)
-                                       total = Math.Min (content_length, total);
-                               Array.Copy (buffer, content, total);
-                       } else
-                               total = 0;
-                       
-                       
-                       buffer = new byte [INPUT_BUFFER_SIZE];
-                       while (total < content_length){
-                               int n;
-                               n = worker_request.ReadEntityBody (buffer, Math.Min (content_length-total, INPUT_BUFFER_SIZE));
+                       // Replace the input with the filtered input
+                       input_filter.BaseStream = input_stream;
+                       MemoryStream ms = new MemoryStream ();
+                       while (true) {
+                               int n = filter.Read (buffer, 0, buffer.Length);
                                if (n <= 0)
                                        break;
-                               Array.Copy (buffer, 0, content, total, n);
-                               total += n;
-                       } 
-                       if (total < content_length)
-                               throw new HttpException (411, "The uploaded file is incomplete");
-                                                        
-                       input_stream = new MemoryStream (content, 0, content.Length, false, true);
+                               ms.Write (buffer, 0, n);
+                       }
+                       // From now on input_stream has the filtered input
+                       input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
                }
-#else
+
+#if !TARGET_JVM
                const int INPUT_BUFFER_SIZE = 32*1024;
 
                TempFileStream GetTempStream ()
@@ -646,27 +680,6 @@ namespace System.Web {
                        return f;
                }
 
-               void DoFilter (byte [] buffer)
-               {
-                       if (input_filter == null || filter == null)
-                               return;
-
-                       if (buffer.Length < 1024)
-                               buffer = new byte [1024];
-
-                       // Replace the input with the filtered input
-                       input_filter.BaseStream = input_stream;
-                       MemoryStream ms = new MemoryStream ();
-                       while (true) {
-                               int n = filter.Read (buffer, 0, buffer.Length);
-                               if (n <= 0)
-                                       break;
-                               ms.Write (buffer, 0, n);
-                       }
-                       // From now on input_stream has the filtered input
-                       input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
-               }
-
                void MakeInputStream ()
                {
                        if (input_stream != null)
@@ -799,6 +812,7 @@ namespace System.Web {
                                throw new HttpException (411, "The request body is incomplete.");
                }
 #endif
+
                internal void ReleaseResources ()
                {
                        Stream stream;
@@ -867,21 +881,8 @@ namespace System.Web {
                public NameValueCollection Params {
                        [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
                        get {
-                               if (all_params == null) {
-                                       all_params = new WebROCollection ();
-
-                                       all_params.Add (QueryString);
-
-                                       /* special handling for Cookies since
-                                        * it isn't a NameValueCollection. */
-                                       foreach (string key in Cookies.AllKeys) {
-                                               all_params.Add (key, Cookies[key].Value);
-                                       }
-
-                                       all_params.Add (Form);
-                                       all_params.Add (ServerVariables);
-                                       all_params.Protect ();
-                               }
+                               if (all_params == null)
+                                       all_params = new HttpParamsCollection (QueryString, Form, ServerVariables, Cookies);
 
                                return all_params;
                        }
@@ -889,7 +890,28 @@ namespace System.Web {
 
                public string Path {
                        get {
-                               return UrlComponents.Path;
+                               if (unescaped_path == null) {
+                                       string path;
+                                       if (url_components != null) {
+                                               // use only if it's already been instantiated, so that we can't go into endless
+                                               // recursion in some scenarios
+                                               path = UrlComponents.Path;
+                                       } else {
+#if NET_2_0
+                                               path = ApplyUrlMapping (worker_request.GetUriPath ());
+#else
+                                               path = worker_request.GetUriPath ();
+#endif
+                                       }
+                                               
+#if NET_2_0
+                                       unescaped_path = Uri.UnescapeDataString (path);
+#else
+                                       unescaped_path = HttpUtility.UrlDecode (path);
+#endif
+                               }
+                               
+                               return unescaped_path;
                        }
                }
 
@@ -923,8 +945,10 @@ namespace System.Web {
                                if (worker_request == null)
                                        return String.Empty; // don't check security with an empty string!
 
-                               if (physical_path == null)
-                                       physical_path = MapPath (CurrentExecutionFilePath);
+                               if (physical_path == null) {
+                                       // Don't call HttpRequest.MapPath here, as that one *trims* the input
+                                       physical_path = worker_request.MapPath (FilePath);
+                               }
 
                                if (SecurityManager.SecurityEnabled) {
                                        new FileIOPermission (FileIOPermissionAccess.PathDiscovery, physical_path).Demand ();
@@ -939,25 +963,27 @@ namespace System.Web {
                                        string fp = FilePath;
                                        int p = fp.LastIndexOf ('/');
 
-                                       if (p == -1)
+                                       if (p 1)
                                                root_virtual_dir = "/";
                                        else
                                                root_virtual_dir = fp.Substring (0, p);
                                }
-
                                return root_virtual_dir;
                        }
                }
 
                public NameValueCollection QueryString {
                        get {
-                               if (query_string_nvc == null){
+                               if (query_string_nvc == null) {
+                                       query_string_nvc = new WebROCollection ();
                                        string q = UrlComponents.Query;
-                                       if (q.Length != 0)
-                                               q = q.Remove(0, 1);
-
-                                       query_string_nvc = new WebROCollection ();                                      
-                                       HttpUtility.ParseQueryString (q, ContentEncoding, query_string_nvc);
+                                       if (q != null) {
+                                               if (q.Length != 0)
+                                                       q = q.Remove(0, 1);
+                                       
+                                               HttpUtility.ParseQueryString (q, ContentEncoding, query_string_nvc);
+                                       }
+                                       
                                        query_string_nvc.Protect();
                                }
                                
@@ -1023,7 +1049,7 @@ namespace System.Web {
                                        if (orig_url == null)
                                                cached_url = UrlComponents.Uri;
                                        else
-                                               cached_url = new Uri (orig_url); 
+                                               cached_url = new Uri (orig_url);
                                }
 
                                return cached_url;                      
@@ -1140,30 +1166,40 @@ namespace System.Web {
                        if (worker_request == null)
                                throw new HttpException ("No HttpWorkerRequest");
 
-                       if (virtualPath == null || virtualPath == "")
-                               virtualPath = "";
-                       else
+                       if (virtualPath == null)
+                               virtualPath = "~";
+                       else {
                                virtualPath = virtualPath.Trim ();
+                               if (virtualPath.Length == 0)
+                                       virtualPath = "~";
+                       }
 
                        if (virtualPath.IndexOf (':') != -1)
-                               throw new ArgumentNullException (
-                                       String.Format ("MapPath: Invalid path '{0}', only virtual paths are accepted", virtualPath));
+                               throw new HttpException (String.Format ("'{0}' is not a valid virtual path.", virtualPath));
 
-#if TARGET_J2EE
-                       if (virtualPath.StartsWith(vmw.common.IAppDomainConfig.WAR_ROOT_SYMBOL))                        
-                               return  virtualPath;                    
-#endif 
-                       if (baseVirtualDir == null)
-                               baseVirtualDir = RootVirtualDir;
-                       virtualPath = UrlUtils.Combine (baseVirtualDir, virtualPath);
+                       string appVirtualPath = HttpRuntime.AppDomainAppVirtualPath;
+
+                       if (!VirtualPathUtility.IsRooted (virtualPath)) {
+                               if (StrUtils.IsNullOrEmpty (baseVirtualDir))
+                                       baseVirtualDir = appVirtualPath;
+                               virtualPath = VirtualPathUtility.Combine (VirtualPathUtility.AppendTrailingSlash (baseVirtualDir), virtualPath);
+                       }
+                       virtualPath = VirtualPathUtility.ToAbsolute (virtualPath);
 
                        if (!allowCrossAppMapping){
-                               if (!StrUtils.StartsWith (virtualPath, RootVirtualDir, true))
+                               if (!StrUtils.StartsWith (virtualPath, appVirtualPath, true))
                                        throw new HttpException ("MapPath: Mapping across applications not allowed");
-                               if (RootVirtualDir.Length > 1 && virtualPath.Length > 1 && virtualPath [0] != '/')
+                               if (appVirtualPath.Length > 1 && virtualPath.Length > 1 && virtualPath [0] != '/')
                                        throw new HttpException ("MapPath: Mapping across applications not allowed");
                        }
+#if TARGET_JVM
                        return worker_request.MapPath (virtualPath);
+#else
+                       string path = worker_request.MapPath (virtualPath);
+                       if (virtualPath [virtualPath.Length - 1] != '/' && path [path.Length - 1] == System.IO.Path.DirectorySeparatorChar)
+                               path = path.TrimEnd (System.IO.Path.DirectorySeparatorChar);
+                       return path;
+#endif
                }
 
                public void SaveAs (string filename, bool includeHeaders)
@@ -1239,13 +1275,28 @@ namespace System.Web {
                        get {
                                string address = worker_request.GetRemoteAddress ();
 
-                               return (address == "127.0.0.1");
+                               if (StrUtils.IsNullOrEmpty (address))
+                                       return false;
+
+                               if (address == "127.0.0.1")
+                                       return true;
+
+                               System.Net.IPAddress remoteAddr = System.Net.IPAddress.Parse (address);
+                               if (System.Net.IPAddress.IsLoopback (remoteAddr))
+                                       return true;
+
+                               for (int i = 0; i < host_addresses.Length; i++)
+                                       if (remoteAddr.Equals (host_addresses [i]))
+                                               return true;
+
+                               return false;
                        }
                }
 
                internal void SetFilePath (string path)
                {
                        file_path = path;
+                       physical_path = null;
                }
 
                internal void SetCurrentExePath (string path)
@@ -1258,6 +1309,7 @@ namespace System.Web {
                        root_virtual_dir = null;
                        base_virtual_dir = null;
                        physical_path = null;
+                       unescaped_path = null;
                }
 
                internal void SetPathInfo (string pi)
@@ -1370,6 +1422,22 @@ namespace System.Web {
                        return false;
                }
 
+               static System.Net.IPAddress [] GetLocalHostAddresses ()
+               {
+                       try {
+                               string hostName = System.Net.Dns.GetHostName ();
+#if NET_2_0
+                               System.Net.IPAddress [] ipaddr = System.Net.Dns.GetHostAddresses (hostName);
+#else
+                               System.Net.IPAddress [] ipaddr = System.Net.Dns.GetHostByName (hostName).AddressList;
+#endif
+                               return ipaddr;
+                       }
+                       catch
+                       {
+                               return new System.Net.IPAddress[0];
+                       }
+               }
        }
 #endregion
 
@@ -1607,7 +1675,7 @@ namespace System.Web {
                        while ((header = ReadHeaders ()) != null) {
                                if (StrUtils.StartsWith (header, "Content-Disposition:", true)) {
                                        elem.Name = GetContentDispositionAttribute (header, "name");
-                                       elem.Filename = GetContentDispositionAttributeWithEncoding (header, "filename");      
+                                       elem.Filename = StripPath (GetContentDispositionAttributeWithEncoding (header, "filename"));
                                } else if (StrUtils.StartsWith (header, "Content-Type:", true)) {
                                        elem.ContentType = header.Substring ("Content-Type:".Length).Trim ();
                                }
@@ -1622,7 +1690,16 @@ namespace System.Web {
                        elem.Length = pos - start;
                        return elem;
                }
-               
+
+               static string StripPath (string path)
+               {
+                       if (path == null || path.Length == 0)
+                               return path;
+                       
+                       if (path.IndexOf (":\\") != 1)
+                               return path;
+                       return path.Substring (path.LastIndexOf ("\\") + 1);
+               }
        }
 #endregion
 }