Call SetEndOfSendNotification
[mono.git] / mcs / class / System.Web / System.Web / HttpResponse.cs
index b5ca416e2e0e300d32c34717b8fea8276ba4c2c3..9aa99d9244bba7f1fefdf3e5dd6a763be74ae5a0 100644 (file)
@@ -5,10 +5,9 @@
 // Author:
 //     Miguel de Icaza (miguel@novell.com)
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//      Marek Habersack <mhabersack@novell.com>
 //
-
-//
-// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -29,6 +28,7 @@
 // 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.Text;
 using System.Web.UI;
 using System.Collections;
@@ -37,14 +37,27 @@ using System.IO;
 using System.Web.Caching;
 using System.Threading;
 using System.Web.Util;
+using System.Web.Configuration;
 using System.Globalization;
+using System.Security.Permissions;
+using System.Web.Hosting;
+using System.Web.SessionState;
+
+#if NET_4_0
+using System.Web.Routing;
+#endif
 
-namespace System.Web {
-       
-       public sealed class HttpResponse {
+namespace System.Web
+{      
+       // CAS - no InheritanceDemand here as the class is sealed
+       [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
+       public sealed partial class HttpResponse
+       {
                internal HttpWorkerRequest WorkerRequest;
                internal HttpResponseStream output_stream;
                internal bool buffer = true;
+
+               ArrayList fileDependencies;
                
                HttpContext context;
                TextWriter writer;
@@ -59,8 +72,10 @@ namespace System.Web {
                string charset;
                bool charset_set;
                CachedRawResponse cached_response;
-               string cache_control = "private";
+               string user_cache_control = "private";
                string redirect_location;
+               string version_header;
+               bool version_header_checked;
                
                //
                // Negative Content-Length means we auto-compute the size of content-length
@@ -72,15 +87,16 @@ namespace System.Web {
                // The list of the headers that we will send back to the client, except
                // the headers that we compute here.
                //
-               ArrayList headers = new ArrayList ();
+
+               HttpHeaderCollection headers;
                bool headers_sent;
-               ArrayList cached_headers;
+               NameValueCollection cached_headers;
 
                //
                // Transfer encoding state
                //
                string transfer_encoding;
-               internal byte [] use_chunked;
+               internal bool use_chunked;
                
                bool closed;
                internal bool suppress_content;
@@ -89,21 +105,18 @@ namespace System.Web {
                // Session State
                //
                string app_path_mod;
-               
-               //
-               // Passed as flags
-               //
-               internal object FlagEnd = new object ();
+               bool is_request_being_redirected;
+               Encoding headerEncoding;
 
-               internal readonly static byte [] ChunkedNewline = new byte [2] { 13, 10 };
-               
                internal HttpResponse ()
                {
                        output_stream = new HttpResponseStream (this);
+                       writer = new HttpWriter (this);
                }
 
                public HttpResponse (TextWriter writer) : this ()
                {
+
                        this.writer = writer;
                }
 
@@ -112,18 +125,53 @@ namespace System.Web {
                        WorkerRequest = worker_request;
                        this.context = context;
 
+#if !TARGET_J2EE
                        if (worker_request != null)
-                               use_chunked = worker_request.GetHttpVersion () == "HTTP/1.1" ? new byte [24] : null;
+                               use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
+#endif
+                       writer = new HttpWriter (this);
                }
-               
+
                internal TextWriter SetTextWriter (TextWriter writer)
                {
-                       TextWriter prev = writer;
-                       
+                       TextWriter prev = this.writer;
                        this.writer = writer;
-                       
                        return prev;
                }
+
+               internal string VersionHeader {
+                       get {
+                               if (!version_header_checked && version_header == null) {
+                                       version_header_checked = true;
+                                       HttpRuntimeSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/httpRuntime") as HttpRuntimeSection;
+                                       if (config != null && config.EnableVersionHeader)
+                                               version_header = Environment.Version.ToString (3);
+                               }
+
+                               return version_header;
+                       }
+               }
+
+               internal HttpContext Context {
+                       get { return context; }
+                       set { context = value; }
+               }
+                       
+               internal string[] FileDependencies {
+                       get {
+                               if (fileDependencies == null || fileDependencies.Count == 0)
+                                       return new string[0] {};
+                               return (string[]) fileDependencies.ToArray (typeof (string));
+                       }
+               }
+               
+               ArrayList FileDependenciesArray {
+                       get {
+                               if (fileDependencies == null)
+                                       fileDependencies = new ArrayList ();
+                               return fileDependencies;
+                       }
+               }
                
                public bool Buffer {
                        get {
@@ -150,17 +198,19 @@ namespace System.Web {
                //
                public Encoding ContentEncoding {
                        get {
-                               if (encoding == null){
-                                       string client_content_type = context.Request.ContentType;
-                                       string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
-                                       if (parameter != null){
-                                               try {
-                                                       // Do what the #1 web server does
-                                                       encoding = Encoding.GetEncoding (parameter);
-                                               } catch {
-                                                       encoding = WebEncoding.ResponseEncoding;
+                               if (encoding == null) {
+                                       if (context != null) {
+                                               string client_content_type = context.Request.ContentType;
+                                               string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
+                                               if (parameter != null) {
+                                                       try {
+                                                               // Do what the #1 web server does
+                                                               encoding = Encoding.GetEncoding (parameter);
+                                                       } catch {
+                                                       }
                                                }
-                                       } else
+                                       }
+                                       if (encoding == null)
                                                encoding = WebEncoding.ResponseEncoding;
                                }
                                return encoding;
@@ -231,30 +281,76 @@ namespace System.Web {
                                Cache.SetExpires (value);
                        }
                }
-               
+
                public Stream Filter {
                        get {
-                               throw new NotImplementedException ();
+                               if (WorkerRequest == null)
+                                       return null;
+
+                               return output_stream.Filter;
+                       }
+
+                       set {
+                               output_stream.Filter = value;
                        }
+               }
 
+               public Encoding HeaderEncoding {
+                       get {
+                               if (headerEncoding == null) {
+                                       GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
+
+                                       if (gs == null)
+                                               headerEncoding = Encoding.UTF8;
+                                       else {
+                                               headerEncoding = gs.ResponseHeaderEncoding;
+                                               if (headerEncoding == Encoding.Unicode)
+                                                       throw new HttpException ("HeaderEncoding must not be Unicode");
+                                       }
+                               }
+                               return headerEncoding;
+                       }
                        set {
-                               throw new NotImplementedException ();
+                               if (headers_sent)
+                                       throw new HttpException ("headers have already been sent");
+                               if (value == null)
+                                       throw new ArgumentNullException ("HeaderEncoding");
+                               if (value == Encoding.Unicode)
+                                       throw new HttpException ("HeaderEncoding must not be Unicode");
+                               headerEncoding = value;
+                       }
+               }
+
+               public NameValueCollection Headers {
+                       get {
+                               if (headers == null)
+                                       headers = new HttpHeaderCollection ();
+
+                               return headers;
                        }
                }
+
                
                public bool IsClientConnected {
                        get {
+                               if (WorkerRequest == null)
+                                       return true; // yep that's true
+
                                return WorkerRequest.IsClientConnected ();
                        }
                }
-               
+
+               public bool IsRequestBeingRedirected {
+                       get { return is_request_being_redirected; }
+               }
+
                public TextWriter Output {
                        get {
-                               if (writer == null)
-                                       writer = new HttpWriter (this);
-
                                return writer;
                        }
+#if NET_4_0
+                       set { writer = value; }
+#endif
                }
 
                public Stream OutputStream {
@@ -274,9 +370,7 @@ namespace System.Web {
                }
                
                public string Status {
-                       get {
-                               return String.Format ("{0} {1}", status_code, StatusDescription);
-                       }
+                       get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
 
                        set {
                                int p = value.IndexOf (' ');
@@ -285,22 +379,26 @@ namespace System.Web {
 
                                string s = value.Substring (0, p);
                                
-#if NET_2_0
                                if (!Int32.TryParse (s, out status_code))
                                        throw new HttpException ("Invalid format for the Status property");
-#else
-                                                   
-                               try {
-                                       status_code = Int32.Parse (s);
-                               } catch {
-                                       throw new HttpException ("Invalid format for the Status property");
-                               }
-#endif
                                
                                status_description = value.Substring (p+1);
                        }
                }
 
+               // We ignore the two properties on Mono as they are for use with IIS7, but there is
+               // no point in throwing PlatformNotSupportedException. We might find a use for them
+               // some day.
+               public int SubStatusCode {
+                       get;
+                       set;
+               }
+
+               public bool TrySkipIisCustomErrors {
+                       get;
+                       set;
+               }
+               
                public int StatusCode {
                        get {
                                return status_code;
@@ -341,24 +439,49 @@ namespace System.Web {
                        }
                }
 
+               [MonoTODO ("Not implemented")]
+               public void AddCacheDependency (CacheDependency[] dependencies)
+               {
+                       throw new NotImplementedException ();
+               }
+
+               [MonoTODO ("Not implemented")]
+               public void AddCacheItemDependencies (string[] cacheKeys)
+               {
+                       throw new NotImplementedException ();
+               }
+
+               [MonoTODO("Currently does nothing")]
                public void AddCacheItemDependencies (ArrayList cacheKeys)
                {
                        // TODO: talk to jackson about the cache
                }
 
+               [MonoTODO("Currently does nothing")]
                public void AddCacheItemDependency (string cacheKey)
                {
                        // TODO: talk to jackson about the cache
                }
-
+               
                public void AddFileDependencies (ArrayList filenames)
                {
-                       // TODO: talk to jackson about the cache
+                       if (filenames == null || filenames.Count == 0)
+                               return;
+                       FileDependenciesArray.AddRange (filenames);
+               }
+
+               public void AddFileDependencies (string[] filenames)
+               {
+                       if (filenames == null || filenames.Length == 0)
+                               return;
+                       FileDependenciesArray.AddRange (filenames);
                }
 
                public void AddFileDependency (string filename)
                {
-                       // TODO: talk to jackson about the cache
+                       if (filename == null || filename == String.Empty)
+                               return;
+                       FileDependenciesArray.Add (filename);
                }
 
                public void AddHeader (string name, string value)
@@ -379,33 +502,37 @@ namespace System.Web {
                public void AppendHeader (string name, string value)
                {
                        if (headers_sent)
-                               throw new HttpException ("headers have been already sent");
-                       
-                       if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
-                               content_length = Int64.Parse (value);
-                               use_chunked = null;
+                               throw new HttpException ("Headers have been already sent");
+#if !TARGET_J2EE
+                       if (String.Compare (name, "content-length", true, Helpers.InvariantCulture) == 0){
+                               content_length = (long) UInt64.Parse (value);
+                               use_chunked = false;
                                return;
                        }
+#endif
 
-                       if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
+                       if (String.Compare (name, "content-type", true, Helpers.InvariantCulture) == 0){
                                ContentType = value;
                                return;
                        }
 
-                       if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
+#if !TARGET_J2EE
+                       if (String.Compare (name, "transfer-encoding", true, Helpers.InvariantCulture) == 0){
                                transfer_encoding = value;
-                               use_chunked = null;
+                               use_chunked = false;
                                return;
                        }
+#endif
 
-                       if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
-                               cache_control = value;
+                       if (String.Compare (name, "cache-control", true, Helpers.InvariantCulture) == 0){
+                               user_cache_control = value;
                                return;
                        }
 
-                       headers.Add (new UnknownResponseHeader (name, value));
+                       Headers.Add (name, value);
                }
 
+               [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
                public void AppendToLog (string param)
                {
                        Console.Write ("System.Web: ");
@@ -414,62 +541,37 @@ namespace System.Web {
                
                public string ApplyAppPathModifier (string virtualPath)
                {
-                       if (virtualPath == null)
+                       if (virtualPath == null || context == null)
                                return null;
                
-                       if (virtualPath == "")
+                       if (virtualPath.Length == 0)
                                return context.Request.RootVirtualDir;
-               
+
                        if (UrlUtils.IsRelativeUrl (virtualPath)) {
                                virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
                        } else if (UrlUtils.IsRooted (virtualPath)) {
                                virtualPath = UrlUtils.Canonic (virtualPath);
                        }
-               
+
+                       bool cookieless = false;
+                       SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
+                       cookieless = SessionStateModule.IsCookieLess (context, config);
+
+                       if (!cookieless)
+                               return virtualPath;
+
                        if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
-                               string rvd = context.Request.RootVirtualDir;
-                               string basevd = rvd.Replace (app_path_mod, "");
-               
-                               if (!StrUtils.StartsWith (virtualPath, basevd))
-                                       return virtualPath;
-               
-                               virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
+                               if (UrlUtils.HasSessionId (virtualPath))
+                                       virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
+                               return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
                        }
                
                        return virtualPath;
                }
 
-               internal void SendSize (long l)
-               {
-                       string s = l.ToString ();
-                       int i;
-                       
-                       for (i = 0; i < s.Length; i++){
-                               use_chunked [i] = (byte) s [i];
-                       }
-                       use_chunked [i++] = 13;
-                       use_chunked [i++] = 10;
-
-                       WorkerRequest.SendResponseFromMemory (use_chunked, i);
-               }
-               
                public void BinaryWrite (byte [] buffer)
                {
-                       if (this.buffer)
-                               output_stream.Write (buffer, 0, buffer.Length);
-                       else {
-                               if (use_chunked != null)
-                                       SendSize (buffer.Length);
-                                               
-                               WorkerRequest.SendResponseFromMemory (buffer, buffer.Length);
-
-                               WorkerRequest.SendResponseFromMemory (ChunkedNewline, 2);
-                               
-                               //
-                               // TODO This hack is for current XSP
-                               //
-                               WorkerRequest.FlushResponse (false);
-                       }
+                       output_stream.Write (buffer, 0, buffer.Length);
                }
 
                internal void BinaryWrite (byte [] buffer, int start, int len)
@@ -485,6 +587,7 @@ namespace System.Web {
                public void ClearContent ()
                {
                        output_stream.Clear ();
+                       content_length = -1;
                }
 
                public void ClearHeaders ()
@@ -496,8 +599,12 @@ namespace System.Web {
                        content_length = -1;
                        content_type = "text/html";
                        transfer_encoding = null;
-                       cache_control = "private";
-                       headers.Clear ();
+                       user_cache_control = "private";
+                       if (cache_policy != null)
+                               cache_policy.Cacheability = HttpCacheability.Private;
+
+                       if (headers != null)
+                               headers.Clear ();
                }
 
                internal bool HeadersSent {
@@ -510,18 +617,29 @@ namespace System.Web {
                {
                        if (closed)
                                return;
-                       WorkerRequest.CloseConnection ();
+                       if (WorkerRequest != null)
+                               WorkerRequest.CloseConnection ();
                        closed = true;
                }
 
+               public void DisableKernelCache ()
+               {
+                       // does nothing in Mono
+               }
+               
                public void End ()
                {
+                       if (context == null)
+                               return;
+                       
                        if (context.TimeoutPossible) {
-                               Thread.CurrentThread.Abort (FlagEnd);
+                               Thread.CurrentThread.Abort (FlagEnd.Value);
                        } else {
                                // If this is called from an async event, signal the completion
-                               // but don't thow.
-                               context.ApplicationInstance.CompleteRequest ();
+                               // but don't throw.
+                               HttpApplication app_instance = context.ApplicationInstance;
+                               if (app_instance != null)
+                                       app_instance.CompleteRequest ();
                        }
                }
 
@@ -530,60 +648,48 @@ namespace System.Web {
                //   Content-Type
                //   Transfer-Encoding (chunked)
                //   Cache-Control
-               void WriteHeaders (bool final_flush)
+               //   X-AspNet-Version
+               void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush)
                {
-                       WorkerRequest.SendStatus (status_code, StatusDescription);
-
-                       if (cached_response != null)
-                               cached_response.SetHeaders (headers);
-
-                       // If this page is cached use the cached headers
-                       // instead of the standard headers      
-                       ArrayList write_headers = headers;
-                       if (cached_headers != null)
-                               write_headers = cached_headers;
-
+#if !TARGET_J2EE
                        //
                        // Transfer-Encoding
                        //
-                       if (use_chunked != null)
-                               write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
+                       if (use_chunked)
+                               write_headers.Add ("Transfer-Encoding", "chunked");
                        else if (transfer_encoding != null)
-                               write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
-
-                       UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
-                                       DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
-                       write_headers.Add (date_header);
-
-                       if (IsCached)
-                               cached_response.DateHeader = date_header;
-                                       
+                               write_headers.Add ("Transfer-Encoding", transfer_encoding);
+#endif
                        if (redirect_location != null)
-                               write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
+                               write_headers.Add ("Location", redirect_location);
                        
+#if !TARGET_J2EE
+                       string vh = VersionHeader;
+                       if (vh != null)
+                               write_headers.Add ("X-AspNet-Version", vh);
+
                        //
                        // If Content-Length is set.
                        //
-                       if (content_length >= 0){
-                               write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
-                                                                     content_length.ToString (CultureInfo.InvariantCulture)));
-                       } else if (Buffer){
-                               if (final_flush){
+                       if (content_length >= 0) {
+                               write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
+                                                  content_length.ToString (Helpers.InvariantCulture));
+                       } else if (BufferOutput) {
+                               if (final_flush) {                                      
                                        //
                                        // If we are buffering and this is the last flush, not a middle-flush,
                                        // we know the content-length.
                                        //
                                        content_length = output_stream.total;
-                                       write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
-                                                                             content_length.ToString (CultureInfo.InvariantCulture)));
+                                       write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
+                                                          content_length.ToString (Helpers.InvariantCulture));
                                } else {
                                        //
                                        // We are buffering, and this is a flush in the middle.
                                        // If we are not chunked, we need to set "Connection: close".
                                        //
-                                       if (use_chunked == null){
-                                               Console.WriteLine ("Setting to close2");
-                                               write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
+                                       if (use_chunked){
+                                               write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
                                        }
                                }
                        } else {
@@ -591,11 +697,11 @@ namespace System.Web {
                                // If the content-length is not set, and we are not buffering, we must
                                // close at the end.
                                //
-                               if (use_chunked == null){
-                                       Console.WriteLine ("Setting to close");
-                                       write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
+                               if (use_chunked){
+                                       write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
                                }
                        }
+#endif
 
                        //
                        // Cache Control, the cache policy takes precedence over the cache_control property.
@@ -603,7 +709,7 @@ namespace System.Web {
                        if (cache_policy != null)
                                cache_policy.SetHeaders (this, headers);
                        else
-                               write_headers.Add (new UnknownResponseHeader ("Cache-Control", cache_control));
+                               write_headers.Add ("Cache-Control", CacheControl);
                        
                        //
                        // Content-Type
@@ -619,54 +725,112 @@ namespace System.Web {
                                        }
                                }
                                
-                               write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
+                               write_headers.Add ("Content-Type", header);
                        }
 
                        if (cookies != null && cookies.Count != 0){
                                int n = cookies.Count;
                                for (int i = 0; i < n; i++)
-                                       write_headers.Add (cookies.Get (i).GetCookieHeader ());
+                                       write_headers.Add ("Set-Cookie", cookies.Get (i).GetCookieHeaderValue ());
+#if TARGET_J2EE
+                               // For J2EE Portal support emulate cookies by storing them in the session.
+                               context.Request.SetSessionCookiesForPortal (cookies);
+#endif
                        }
-                       
+               }
+
+               internal void WriteHeaders (bool final_flush)
+               {
+                       if (headers_sent)
+                               return;
+
                        //
                        // Flush
                        //
-                       HttpApplication app_instance = context.ApplicationInstance;
-                       if (app_instance != null)
-                               app_instance.TriggerPreSendRequestHeaders ();
-                               
-                       foreach (BaseResponseHeader header in write_headers){
-                               header.SendContent (WorkerRequest);
+                       if (context != null) {
+                               HttpApplication app_instance = context.ApplicationInstance;
+                               if (app_instance != null)
+                                       app_instance.TriggerPreSendRequestHeaders ();
                        }
+
                        headers_sent = true;
+
+                       if (cached_response != null)
+                               cached_response.SetHeaders (headers);
+
+                       // If this page is cached use the cached headers
+                       // instead of the standard headers      
+                       NameValueCollection write_headers;
+                       if (cached_headers != null)
+                               write_headers = cached_headers;
+                       else {
+                               write_headers = Headers;
+                               AddHeadersNoCache (write_headers, final_flush);
+                       }
+                       
+                       if (WorkerRequest != null)
+                               WorkerRequest.SendStatus (status_code, StatusDescription);
+
+                       if (WorkerRequest != null) {
+                               string header_name;
+                               string[] values;
+                               int header_index;
+                               
+                               for (int i = 0; i < write_headers.Count; i++) {
+                                       header_name = write_headers.GetKey (i);
+                                       header_index = HttpWorkerRequest.GetKnownResponseHeaderIndex (header_name);
+                                       values = write_headers.GetValues (i);
+                                       if (values == null)
+                                               continue;
+                                       
+                                       foreach (string val in values) {
+                                               if (header_index > -1)
+                                                       WorkerRequest.SendKnownResponseHeader (header_index, val);
+                                               else
+                                                       WorkerRequest.SendUnknownResponseHeader (header_name, val);
+                                       }
+                               }
+                       }
                }
-               
+
+               internal void DoFilter (bool close)
+               {
+                       if (output_stream.HaveFilter && context != null && context.Error == null)
+                               output_stream.ApplyFilter (close);
+               }
+
                internal void Flush (bool final_flush)
                {
+                       DoFilter (final_flush);
                        if (!headers_sent){
                                if (final_flush || status_code != 200)
-                                       use_chunked = null;
-
-                               WriteHeaders (final_flush);
+                                       use_chunked = false;
                        }
 
-                       if (suppress_content)
+                       bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
+                       if (suppress_content || head) {
+                               if (!headers_sent)
+                                       WriteHeaders (true);
+                               output_stream.Clear ();
+                               if (WorkerRequest != null)
+                                       output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
                                return;
+                       }
 
-                       HttpApplication app_instance = context.ApplicationInstance;
-                       if (app_instance != null)
-                               app_instance.TriggerPreSendRequestContent ();
+                       if (!headers_sent)
+                               WriteHeaders (final_flush);
 
-                       if (IsCached) {
-                               byte [] data = output_stream.GetData ();
-                               cached_response.ContentLength = data.Length;
-                               cached_response.SetData (data);
+                       if (context != null) {
+                               HttpApplication app_instance = context.ApplicationInstance;
+                               if (app_instance != null)
+                                       app_instance.TriggerPreSendRequestContent ();
                        }
 
-                       output_stream.Flush (WorkerRequest, final_flush);
-                       
-                       if (final_flush && use_chunked != null)
-                               Write ("0\r\n\r\n");
+                       if (IsCached)
+                               cached_response.SetData (output_stream.GetData ());
+
+                       if (WorkerRequest != null)
+                               output_stream.Flush (WorkerRequest, final_flush);
                }
 
                public void Flush ()
@@ -679,32 +843,156 @@ namespace System.Web {
                        AppendHeader ("PICS-Label", value);
                }
 
-               public void Redirect (string url)
-               {
-                       Redirect (url, true);
-               }
-
-               public void Redirect (string url, bool endResponse)
+               void Redirect (string url, bool endResponse, int code)
                {
+                       if (url == null)
+                               throw new ArgumentNullException ("url");
+                       
                        if (headers_sent)
-                               throw new HttpException ("header have been already sent");
+                               throw new HttpException ("Headers have already been sent");
 
+                       if (url.IndexOf ('\n') != -1)
+                               throw new ArgumentException ("Redirect URI cannot contain newline characters.", "url");
+                       
+                       is_request_being_redirected = true;
                        ClearHeaders ();
                        ClearContent ();
                        
-                       StatusCode = 302;
+                       StatusCode = code;
                        url = ApplyAppPathModifier (url);
-                       headers.Add (new UnknownResponseHeader ("Location", url));
+
+                       bool isFullyQualified;
+                       if (StrUtils.StartsWith (url, "http:", true) ||
+                           StrUtils.StartsWith (url, "https:", true) ||
+                           StrUtils.StartsWith (url, "file:", true) ||
+                           StrUtils.StartsWith (url, "ftp:", true))
+                               isFullyQualified = true;
+                       else
+                               isFullyQualified = false;
+
+                       if (!isFullyQualified) {
+                               HttpRuntimeSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/httpRuntime") as HttpRuntimeSection;
+                               if (config != null && config.UseFullyQualifiedRedirectUrl) {
+                                       var ub = new UriBuilder (context.Request.Url);
+                                       ub.Path = url;
+                                       ub.Fragment = null;
+                                       ub.Password = null;
+                                       ub.Query = null;
+                                       ub.UserName = null;
+                                       url = ub.Uri.ToString ();
+                               }
+                       }
+                       
+                       redirect_location = url;
 
                        // Text for browsers that can't handle location header
                        Write ("<html><head><title>Object moved</title></head><body>\r\n");
-                       Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
+                       Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
                        Write ("</body><html>\r\n");
                        
                        if (endResponse)
                                End ();
+                       is_request_being_redirected = false;
+               }
+               
+               public void Redirect (string url)
+               {
+                       Redirect (url, true);
+               }
+
+               public void Redirect (string url, bool endResponse)
+               {
+                       Redirect (url, endResponse, 302);
+               }
+#if NET_4_0
+               public void RedirectPermanent (string url)
+               {
+                       RedirectPermanent (url, true);
+               }
+
+               public void RedirectPermanent (string url, bool endResponse)
+               {
+                       Redirect (url, endResponse, 301);
+               }
+
+               public void RedirectToRoute (object routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoute", null, new RouteValueDictionary (routeValues), 302, true);
                }
 
+               public void RedirectToRoute (RouteValueDictionary routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoute", null, routeValues, 302, true);
+               }
+
+               public void RedirectToRoute (string routeName)
+               {
+                       RedirectToRoute ("RedirectToRoute", routeName, null, 302, true);
+               }
+
+               public void RedirectToRoute (string routeName, object routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoute", routeName, new RouteValueDictionary (routeValues), 302, true);
+               }
+
+               public void RedirectToRoute (string routeName, RouteValueDictionary routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoute", routeName, routeValues, 302, true);
+               }
+
+               public void RedirectToRoutePermanent (object routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoutePermanent", null, new RouteValueDictionary (routeValues), 301, false);
+               }
+
+               public void RedirectToRoutePermanent (RouteValueDictionary routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoutePermanent", null, routeValues, 301, false);
+               }
+
+               public void RedirectToRoutePermanent (string routeName)
+               {
+                       RedirectToRoute ("RedirectToRoutePermanent", routeName, null, 301, false);
+               }
+
+               public void RedirectToRoutePermanent (string routeName, object routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoutePermanent", routeName, new RouteValueDictionary (routeValues), 301, false);
+               }               
+
+               public void RedirectToRoutePermanent (string routeName, RouteValueDictionary routeValues)
+               {
+                       RedirectToRoute ("RedirectToRoutePermanent", routeName, routeValues, 301, false);
+               }
+               
+               void RedirectToRoute (string callerName, string routeName, RouteValueDictionary routeValues, int redirectCode, bool endResponse)
+               {
+                       HttpContext ctx = context ?? HttpContext.Current;
+                       HttpRequest req = ctx != null ? ctx.Request : null;
+                       
+                       if (req == null)
+                               // Let's emulate .NET
+                               throw new NullReferenceException ();
+                       
+                       VirtualPathData vpd = RouteTable.Routes.GetVirtualPath (req.RequestContext, routeName, routeValues);
+                       string redirectUrl = vpd != null ? vpd.VirtualPath : null;
+                       if (String.IsNullOrEmpty (redirectUrl))
+                               throw new InvalidOperationException ("No matching route found for RedirectToRoute");
+
+                       Redirect (redirectUrl, true, redirectCode);
+               }
+
+               public static void RemoveOutputCacheItem (string path, string providerName)
+               {
+                       if (path == null)
+                               throw new ArgumentNullException ("path");
+
+                       if (path.Length > 0 && path [0] != '/')
+                               throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem: '" + path + "'. An absolute virtual path is expected");
+
+                       OutputCache.RemoveFromProvider (path, providerName);
+               }
+#endif
                public static void RemoveOutputCacheItem (string path)
                {
                        if (path == null)
@@ -716,7 +1004,19 @@ namespace System.Web {
                        if (path [0] != '/')
                                throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
 
-                       HttpRuntime.Cache.Remove (path);
+#if NET_4_0
+                       RemoveOutputCacheItem (path, OutputCache.DefaultProviderName);
+#else
+                       HttpContext context = HttpContext.Current;
+                       HttpApplication app_instance = context != null ? context.ApplicationInstance : null;
+                       HttpModuleCollection modules = app_instance != null ? app_instance.Modules : null;
+                       OutputCacheModule ocm = modules != null ? modules.Get ("OutputCache") as OutputCacheModule : null;
+                       OutputCacheProvider internalProvider = ocm != null ? ocm.InternalProvider : null;
+                       if (internalProvider == null)
+                               return;
+
+                       internalProvider.Remove (path);
+#endif
                }
 
                public void SetCookie (HttpCookie cookie)
@@ -726,25 +1026,49 @@ namespace System.Web {
 
                public void Write (char ch)
                {
-                       Output.Write (ch);
+                       TextWriter writer = Output;
+#if NET_4_0
+                       // Emulating .NET
+                       if (writer == null)
+                               throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
+#endif
+                       writer.Write (ch);
                }
 
                public void Write (object obj)
                {
+                       TextWriter writer = Output;
+#if NET_4_0
+                       // Emulating .NET
+                       if (writer == null)
+                               throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
+#endif
                        if (obj == null)
                                return;
                        
-                       Output.Write (obj.ToString ());
+                       writer.Write (obj.ToString ());
                }
                
                public void Write (string s)
                {
-                       Output.Write (s);
+                       TextWriter writer = Output;
+#if NET_4_0
+                       // Emulating .NET
+                       if (writer == null)
+                               throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
+#endif
+                       writer.Write (s);
                }
                
                public void Write (char [] buffer, int index, int count)
                {
-                       Output.Write (buffer, index, count);
+                       TextWriter writer = Output;
+#if NET_4_0
+                       // Emulating .NET
+                       if (writer == null)
+                               throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
+#endif
+                       writer.Write (buffer, index, count);
                }
 
                internal void WriteFile (FileStream fs, long offset, long size)
@@ -782,9 +1106,15 @@ namespace System.Web {
                        if (buffer)
                                return;
 
+                       output_stream.ApplyFilter (false);
                        Flush ();
                }
 
+#if TARGET_JVM
+               public void WriteFile (IntPtr fileHandle, long offset, long size) {
+                       throw new PlatformNotSupportedException("IntPtr not supported");
+               }
+#else
                public void WriteFile (IntPtr fileHandle, long offset, long size)
                {
                        if (offset < 0)
@@ -795,13 +1125,17 @@ namespace System.Web {
                        if (size == 0)
                                return;
 
+                       // Note: this .ctor will throw a SecurityException if the caller 
+                       // doesn't have the UnmanagedCode permission
                        using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
                                WriteFile (fs, offset, size);
 
                        if (buffer)
                                return;
+                       output_stream.ApplyFilter (false);
                        Flush ();
                }
+#endif
 
                public void WriteFile (string filename, long offset, long size)
                {
@@ -821,9 +1155,38 @@ namespace System.Web {
                        if (buffer)
                                return;
 
+                       output_stream.ApplyFilter (false);
                        Flush ();
                }
 
+               public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
+               {
+                       // Emulation of .NET behavior
+                       if (callback == null)
+                               throw new NullReferenceException ();
+
+                       object target = callback.Target;
+                       if (target != null && target.GetType () == typeof (Control))
+                               throw new ArgumentException ("callback");
+
+                       string s = callback (context);
+                       if (!IsCached) {
+                               Write (s);
+                               return;
+                       }
+
+                       Cache.Cacheability = HttpCacheability.Server;
+                       Flush ();
+                       if (WorkerRequest == null)
+                               Write (s); // better this than nothing
+                       else {
+                               byte[] bytes = WebEncoding.ResponseEncoding.GetBytes (s);
+                               WorkerRequest.SendResponseFromMemory (bytes, bytes.Length);
+                       }
+                       
+                       cached_response.SetData (callback);
+               }
+
                //
                // Like WriteFile, but never buffers, so we manually Flush here
                //
@@ -832,12 +1195,54 @@ namespace System.Web {
                        if (filename == null)
                                throw new ArgumentNullException ("filename");
 
+                       TransmitFile (filename, false);
+               }
+
+               internal void TransmitFile (string filename, bool final_flush)
+               {
                        FileInfo fi = new FileInfo (filename);
+                       using (Stream s = fi.OpenRead ()); // Just check if we can read.
                        output_stream.WriteFile (filename, 0, fi.Length);
-                       Flush ();
+                       output_stream.ApplyFilter (final_flush);
+                       Flush (final_flush);
+               }
+
+               public void TransmitFile (string filename, long offset, long length)
+               {
+                       output_stream.WriteFile (filename, offset, length);
+                       output_stream.ApplyFilter (false);
+                       Flush (false);
                }
                
+               internal void TransmitFile (VirtualFile vf)
+               {
+                       TransmitFile (vf, false);
+               }
+
+               const int bufLen = 65535;
+               internal void TransmitFile (VirtualFile vf, bool final_flush)
+               {
+                       if (vf == null)
+                               throw new ArgumentNullException ("vf");
 
+                       if (vf is DefaultVirtualFile) {
+                               TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
+                               return;
+                       }
+                       
+                       byte[] buf = new byte [bufLen];
+                       using (Stream s = vf.Open ()) {
+                               int readBytes;
+                               while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
+                                       output_stream.Write (buf, 0, readBytes);
+                                       output_stream.ApplyFilter (final_flush);
+                                       Flush (false);
+                               }
+                               if (final_flush)
+                                       Flush (true);
+                       }
+               }
+               
 #region Session state support
                internal void SetAppPathModifier (string app_modifier)
                {
@@ -846,40 +1251,38 @@ namespace System.Web {
 #endregion
                
 #region Cache Support
-               internal void SetCachedHeaders (ArrayList headers)
+               internal void SetCachedHeaders (NameValueCollection headers)
                {
                        cached_headers = headers;
+                       
                }
 
                internal bool IsCached {
-                       get {
-                               return cached_response != null;
+                       get { return cached_response != null; }
+                       set {
+                               if (value)
+                                       cached_response = new CachedRawResponse (cache_policy);
+                               else
+                                       cached_response = null;
                        }
                }
 
                public HttpCachePolicy Cache {
                        get {
-                               if (cache_policy == null) {
+                               if (cache_policy == null)
                                        cache_policy = new HttpCachePolicy ();
-                                       cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
-                               }
                                
                                return cache_policy;
                        }
-               }
-               
-               private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
-               {
-                       if (e.Cacheability >= HttpCacheability.Server && !IsCached)
-                               cached_response = new CachedRawResponse (cache_policy);
-                       else if (e.Cacheability <= HttpCacheability.Private)
-                               cached_response = null;
-               }
+               }               
 
                internal CachedRawResponse GetCachedResponse ()
                {
-                       cached_response.StatusCode = StatusCode;
-                       cached_response.StatusDescription = StatusDescription;
+                       if (cached_response != null) {
+                               cached_response.StatusCode = StatusCode;
+                               cached_response.StatusDescription = StatusDescription;
+                       }
+                       
                        return cached_response;
                }
 
@@ -889,38 +1292,46 @@ namespace System.Web {
                //
                public string CacheControl {
                        set {
-                               if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0 || 
-                                   String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0 ||
-                                   String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0)
-                                       cache_control = value;
-                               else
+                               if (value == null || value == "") {
+                                       Cache.SetCacheability (HttpCacheability.NoCache);
+                                       user_cache_control = null;
+                               } else if (String.Compare (value, "public", true, Helpers.InvariantCulture) == 0) {
+                                       Cache.SetCacheability (HttpCacheability.Public);
+                                       user_cache_control = "public";
+                               } else if (String.Compare (value, "private", true, Helpers.InvariantCulture) == 0) {
+                                       Cache.SetCacheability (HttpCacheability.Private);
+                                       user_cache_control = "private";
+                               } else if (String.Compare (value, "no-cache", true, Helpers.InvariantCulture) == 0) {
+                                       Cache.SetCacheability (HttpCacheability.NoCache);
+                                       user_cache_control = "no-cache";
+                               } else
                                        throw new ArgumentException ("CacheControl property only allows `public', " +
                                                                     "`private' or no-cache, for different uses, use " +
                                                                     "Response.AppendHeader");
-
                        }
 
-                       get {
-                               if (cache_policy != null){
-                                       switch (Cache.Cacheability){
-                                       case HttpCacheability.NoCache: return "private";
-                                       case HttpCacheability.Private: return "private";
-                                       case HttpCacheability.Server: return "private";
-                                       case HttpCacheability.Public: return "public";
-                                       case HttpCacheability.ServerAndPrivate: return "private";
-                                       }
-                                       throw new Exception ("Unknown internal state: " + Cache.Cacheability);
-                               } else
-                                       return cache_control;
-                       }
+                       get { return (user_cache_control != null) ? user_cache_control : "private"; }
                }
 #endregion
 
+               internal int GetOutputByteCount ()
+               {
+                       return output_stream.GetTotalLength ();
+               }
+
                internal void ReleaseResources ()
                {
-                       output_stream.ReleaseResources ();
+                       output_stream.ReleaseResources (true);
                        output_stream = null;
                }
        }
-       
+
+#if TARGET_J2EE
+       public 
+#endif 
+       static class FlagEnd
+       {
+               public static readonly object Value = new object ();
+       }
 }
+