2 // System.Web.HttpResponse.cs
6 // Miguel de Icaza (miguel@novell.com)
7 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Collections.Specialized;
36 using System.Web.Caching;
37 using System.Threading;
38 using System.Web.Util;
39 using System.Web.Configuration;
40 using System.Globalization;
41 using System.Security.Permissions;
42 using System.Web.Hosting;
43 using System.Web.SessionState;
45 namespace System.Web {
47 // CAS - no InheritanceDemand here as the class is sealed
48 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
49 public sealed partial class HttpResponse {
50 internal HttpWorkerRequest WorkerRequest;
51 internal HttpResponseStream output_stream;
52 internal bool buffer = true;
54 ArrayList fileDependencies;
58 HttpCachePolicy cache_policy;
60 HttpCookieCollection cookies;
62 int status_code = 200;
63 string status_description = "OK";
65 string content_type = "text/html";
68 CachedRawResponse cached_response;
69 string user_cache_control = "private";
70 string redirect_location;
72 static UnknownResponseHeader version_header;
75 // Negative Content-Length means we auto-compute the size of content-length
76 // can be overwritten with AppendHeader ("Content-Length", value)
78 long content_length = -1;
81 // The list of the headers that we will send back to the client, except
82 // the headers that we compute here.
84 ArrayList headers = new ArrayList ();
86 ArrayList cached_headers;
89 // Transfer encoding state
91 string transfer_encoding;
92 internal bool use_chunked;
95 internal bool suppress_content;
103 bool is_request_being_redirected;
104 Encoding headerEncoding;
107 static HttpResponse ()
110 HttpRuntimeSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/httpRuntime") as HttpRuntimeSection;
112 HttpRuntimeConfig config = HttpContext.GetAppConfig ("system.web/httpRuntime") as HttpRuntimeConfig;
114 if (config != null && config.EnableVersionHeader) {
115 string version = Environment.Version.ToString (3);
116 version_header = new UnknownResponseHeader ("X-AspNet-Version", version);
120 internal HttpResponse ()
122 output_stream = new HttpResponseStream (this);
125 public HttpResponse (TextWriter writer) : this ()
127 this.writer = writer;
130 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
132 WorkerRequest = worker_request;
133 this.context = context;
136 if (worker_request != null)
137 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
141 internal TextWriter SetTextWriter (TextWriter writer)
143 TextWriter prev = this.writer;
144 this.writer = writer;
148 internal string[] FileDependencies {
150 if (fileDependencies == null || fileDependencies.Count == 0)
151 return new string[0] {};
152 return (string[]) fileDependencies.ToArray (typeof (string));
156 ArrayList FileDependenciesArray {
158 if (fileDependencies == null)
159 fileDependencies = new ArrayList ();
160 return fileDependencies;
174 public bool BufferOutput {
185 // Use the default from <globalization> section if the client has not set the encoding
187 public Encoding ContentEncoding {
189 if (encoding == null) {
190 if (context != null) {
191 string client_content_type = context.Request.ContentType;
192 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
193 if (parameter != null) {
195 // Do what the #1 web server does
196 encoding = Encoding.GetEncoding (parameter);
201 if (encoding == null)
202 encoding = WebEncoding.ResponseEncoding;
209 throw new ArgumentException ("ContentEncoding can not be null");
212 HttpWriter http_writer = writer as HttpWriter;
213 if (http_writer != null)
214 http_writer.SetEncoding (encoding);
218 public string ContentType {
224 content_type = value;
228 public string Charset {
231 charset = ContentEncoding.WebName;
242 public HttpCookieCollection Cookies {
245 cookies = new HttpCookieCollection (true, false);
252 if (cache_policy == null)
255 return cache_policy.ExpireMinutes ();
259 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
263 public DateTime ExpiresAbsolute {
265 return Cache.Expires;
269 Cache.SetExpires (value);
273 public Stream Filter {
275 if (WorkerRequest == null)
278 return output_stream.Filter;
282 output_stream.Filter = value;
286 public Encoding HeaderEncoding {
288 if (headerEncoding == null) {
289 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
292 headerEncoding = Encoding.UTF8;
294 headerEncoding = gs.ResponseHeaderEncoding;
295 if (headerEncoding == Encoding.Unicode)
296 throw new HttpException ("HeaderEncoding must not be Unicode");
299 return headerEncoding;
303 throw new HttpException ("headers have already been sent");
305 throw new ArgumentNullException ("HeaderEncoding");
306 if (value == Encoding.Unicode)
307 throw new HttpException ("HeaderEncoding must not be Unicode");
308 headerEncoding = value;
312 public bool IsClientConnected {
314 if (WorkerRequest == null)
315 return true; // yep that's true
317 return WorkerRequest.IsClientConnected ();
321 public bool IsRequestBeingRedirected {
322 get { return is_request_being_redirected; }
325 public TextWriter Output {
328 writer = new HttpWriter (this);
334 public Stream OutputStream {
336 return output_stream;
340 public string RedirectLocation {
342 return redirect_location;
346 redirect_location = value;
350 public string Status {
351 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
354 int p = value.IndexOf (' ');
356 throw new HttpException ("Invalid format for the Status property");
358 string s = value.Substring (0, p);
361 if (!Int32.TryParse (s, out status_code))
362 throw new HttpException ("Invalid format for the Status property");
366 status_code = Int32.Parse (s);
368 throw new HttpException ("Invalid format for the Status property");
372 status_description = value.Substring (p+1);
376 public int StatusCode {
383 throw new HttpException ("headers have already been sent");
386 status_description = null;
390 public string StatusDescription {
392 if (status_description == null)
393 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
395 return status_description;
400 throw new HttpException ("headers have already been sent");
402 status_description = value;
406 public bool SuppressContent {
408 return suppress_content;
412 suppress_content = value;
417 [MonoTODO ("Not implemented")]
418 public void AddCacheDependency (CacheDependency[] dependencies)
420 throw new NotImplementedException ();
423 [MonoTODO ("Not implemented")]
424 public void AddCacheItemDependencies (string[] cacheKeys)
426 throw new NotImplementedException ();
429 [MonoTODO("Currently does nothing")]
430 public void AddCacheItemDependencies (ArrayList cacheKeys)
432 // TODO: talk to jackson about the cache
435 [MonoTODO("Currently does nothing")]
436 public void AddCacheItemDependency (string cacheKey)
438 // TODO: talk to jackson about the cache
441 public void AddFileDependencies (ArrayList filenames)
443 if (filenames == null || filenames.Count == 0)
445 FileDependenciesArray.AddRange (filenames);
448 public void AddFileDependencies (string[] filenames)
450 if (filenames == null || filenames.Length == 0)
452 FileDependenciesArray.AddRange (filenames);
456 public void AddFileDependency (string filename)
458 if (filename == null || filename == String.Empty)
460 FileDependenciesArray.Add (filename);
463 public void AddHeader (string name, string value)
465 AppendHeader (name, value);
468 public void AppendCookie (HttpCookie cookie)
470 Cookies.Add (cookie);
475 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
478 public void AppendHeader (string name, string value)
481 throw new HttpException ("headers have been already sent");
484 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
485 content_length = (long) UInt64.Parse (value);
491 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
497 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
498 transfer_encoding = value;
504 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
505 user_cache_control = value;
509 headers.Add (new UnknownResponseHeader (name, value));
512 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
513 public void AppendToLog (string param)
515 Console.Write ("System.Web: ");
516 Console.WriteLine (param);
519 public string ApplyAppPathModifier (string virtualPath)
521 if (virtualPath == null)
524 if (virtualPath == "")
525 return context.Request.RootVirtualDir;
527 if (UrlUtils.IsRelativeUrl (virtualPath)) {
528 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
529 } else if (UrlUtils.IsRooted (virtualPath)) {
530 virtualPath = UrlUtils.Canonic (virtualPath);
533 bool cookieless = false;
535 SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
536 cookieless = SessionStateModule.IsCookieLess (context, config);
538 SessionConfig config = HttpContext.GetAppConfig ("system.web/sessionState") as SessionConfig;
539 cookieless = config.CookieLess;
544 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
545 if (UrlUtils.HasSessionId (virtualPath))
546 virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
547 return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
553 public void BinaryWrite (byte [] buffer)
555 output_stream.Write (buffer, 0, buffer.Length);
558 internal void BinaryWrite (byte [] buffer, int start, int len)
560 output_stream.Write (buffer, start, len);
568 public void ClearContent ()
570 output_stream.Clear ();
574 public void ClearHeaders ()
577 throw new HttpException ("headers have been already sent");
579 // Reset the special case headers.
581 content_type = "text/html";
582 transfer_encoding = null;
583 user_cache_control = null;
587 internal bool HeadersSent {
597 if (WorkerRequest != null)
598 WorkerRequest.CloseConnection ();
603 public void DisableKernelCache ()
605 // does nothing in Mono
614 if (context.TimeoutPossible) {
615 Thread.CurrentThread.Abort (FlagEnd.Value);
617 // If this is called from an async event, signal the completion
619 HttpApplication app_instance = context.ApplicationInstance;
620 if (app_instance != null)
621 app_instance.CompleteRequest ();
628 // Transfer-Encoding (chunked)
631 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
638 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
639 else if (transfer_encoding != null)
640 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
642 if (redirect_location != null)
643 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
646 if (version_header != null)
647 write_headers.Add (version_header);
650 // If Content-Length is set.
652 if (content_length >= 0) {
653 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
654 content_length.ToString (CultureInfo.InvariantCulture)));
655 } else if (BufferOutput) {
658 // If we are buffering and this is the last flush, not a middle-flush,
659 // we know the content-length.
661 content_length = output_stream.total;
662 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
663 content_length.ToString (CultureInfo.InvariantCulture)));
666 // We are buffering, and this is a flush in the middle.
667 // If we are not chunked, we need to set "Connection: close".
670 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
675 // If the content-length is not set, and we are not buffering, we must
679 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
685 // Cache Control, the cache policy takes precedence over the cache_control property.
687 if (cache_policy != null)
688 cache_policy.SetHeaders (this, headers);
690 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
695 if (content_type != null){
696 string header = content_type;
698 if (charset_set || header == "text/plain" || header == "text/html") {
699 if (header.IndexOf ("charset=") == -1) {
700 if (charset == null || charset == "")
701 charset = ContentEncoding.HeaderName;
702 header += "; charset=" + charset;
706 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
709 if (cookies != null && cookies.Count != 0){
710 int n = cookies.Count;
711 for (int i = 0; i < n; i++)
712 write_headers.Add (cookies.Get (i).GetCookieHeader ());
714 // For J2EE Portal support emulate cookies by storing them in the session.
715 context.Request.SetSessionCookiesForPortal (cookies);
720 internal void WriteHeaders (bool final_flush)
728 if (context != null) {
729 HttpApplication app_instance = context.ApplicationInstance;
730 if (app_instance != null)
731 app_instance.TriggerPreSendRequestHeaders ();
736 if (cached_response != null)
737 cached_response.SetHeaders (headers);
739 // If this page is cached use the cached headers
740 // instead of the standard headers
741 ArrayList write_headers = headers;
742 if (cached_headers != null)
743 write_headers = cached_headers;
745 AddHeadersNoCache (write_headers, final_flush);
747 if (WorkerRequest != null)
748 WorkerRequest.SendStatus (status_code, StatusDescription);
750 if (WorkerRequest != null) {
751 foreach (BaseResponseHeader header in write_headers){
752 header.SendContent (WorkerRequest);
757 internal void DoFilter (bool close)
759 if (output_stream.HaveFilter && context != null && context.Error == null)
760 output_stream.ApplyFilter (close);
763 internal void Flush (bool final_flush)
765 DoFilter (final_flush);
767 if (final_flush || status_code != 200)
771 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
772 if (suppress_content || head) {
775 output_stream.Clear ();
776 if (WorkerRequest != null)
777 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
782 WriteHeaders (final_flush);
784 if (context != null) {
785 HttpApplication app_instance = context.ApplicationInstance;
786 if (app_instance != null)
787 app_instance.TriggerPreSendRequestContent ();
791 MemoryStream ms = output_stream.GetData ();
792 cached_response.ContentLength = (int) ms.Length;
793 cached_response.SetData (ms.GetBuffer ());
796 if (WorkerRequest != null)
797 output_stream.Flush (WorkerRequest, final_flush);
805 public void Pics (string value)
807 AppendHeader ("PICS-Label", value);
810 public void Redirect (string url)
812 Redirect (url, true);
815 public void Redirect (string url, bool endResponse)
818 throw new HttpException ("Headers have already been sent");
821 is_request_being_redirected = true;
827 url = ApplyAppPathModifier (url);
828 redirect_location = url;
830 // Text for browsers that can't handle location header
831 Write ("<html><head><title>Object moved</title></head><body>\r\n");
832 Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
833 Write ("</body><html>\r\n");
838 is_request_being_redirected = false;
842 public static void RemoveOutputCacheItem (string path)
845 throw new ArgumentNullException ("path");
847 if (path.Length == 0)
851 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
853 HttpRuntime.InternalCache.Remove (path);
856 public void SetCookie (HttpCookie cookie)
858 AppendCookie (cookie);
861 public void Write (char ch)
866 public void Write (object obj)
871 Output.Write (obj.ToString ());
874 public void Write (string s)
879 public void Write (char [] buffer, int index, int count)
881 Output.Write (buffer, index, count);
884 internal void WriteFile (FileStream fs, long offset, long size)
886 byte [] buffer = new byte [32*1024];
889 fs.Position = offset;
893 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
895 output_stream.Write (buffer, 0, n);
899 public void WriteFile (string filename)
901 WriteFile (filename, false);
904 public void WriteFile (string filename, bool readIntoMemory)
906 if (filename == null)
907 throw new ArgumentNullException ("filename");
910 using (FileStream fs = File.OpenRead (filename))
911 WriteFile (fs, 0, fs.Length);
913 FileInfo fi = new FileInfo (filename);
914 output_stream.WriteFile (filename, 0, fi.Length);
919 output_stream.ApplyFilter (false);
924 public void WriteFile (IntPtr fileHandle, long offset, long size) {
925 throw new PlatformNotSupportedException("IntPtr not supported");
928 public void WriteFile (IntPtr fileHandle, long offset, long size)
931 throw new ArgumentNullException ("offset can not be negative");
933 throw new ArgumentNullException ("size can not be negative");
938 // Note: this .ctor will throw a SecurityException if the caller
939 // doesn't have the UnmanagedCode permission
940 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
941 WriteFile (fs, offset, size);
945 output_stream.ApplyFilter (false);
950 public void WriteFile (string filename, long offset, long size)
952 if (filename == null)
953 throw new ArgumentNullException ("filename");
955 throw new ArgumentNullException ("offset can not be negative");
957 throw new ArgumentNullException ("size can not be negative");
962 FileStream fs = File.OpenRead (filename);
963 WriteFile (fs, offset, size);
968 output_stream.ApplyFilter (false);
972 [MonoTODO ("Not implemented")]
973 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
975 throw new NotImplementedException ();
979 // Like WriteFile, but never buffers, so we manually Flush here
981 public void TransmitFile (string filename)
983 if (filename == null)
984 throw new ArgumentNullException ("filename");
986 TransmitFile (filename, false);
989 internal void TransmitFile (string filename, bool final_flush)
991 FileInfo fi = new FileInfo (filename);
992 using (Stream s = fi.OpenRead ()); // Just check if we can read.
993 output_stream.WriteFile (filename, 0, fi.Length);
994 output_stream.ApplyFilter (final_flush);
999 public void TransmitFile (string filename, long offset, long length)
1001 output_stream.WriteFile (filename, offset, length);
1002 output_stream.ApplyFilter (false);
1006 internal void TransmitFile (VirtualFile vf)
1008 TransmitFile (vf, false);
1011 const int bufLen = 65535;
1012 internal void TransmitFile (VirtualFile vf, bool final_flush)
1015 throw new ArgumentNullException ("vf");
1017 if (vf is DefaultVirtualFile) {
1018 TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
1022 byte[] buf = new byte [bufLen];
1023 using (Stream s = vf.Open ()) {
1025 while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
1026 output_stream.Write (buf, 0, readBytes);
1027 output_stream.ApplyFilter (final_flush);
1036 #region Session state support
1037 internal void SetAppPathModifier (string app_modifier)
1039 app_path_mod = app_modifier;
1043 #region Cache Support
1044 internal void SetCachedHeaders (ArrayList headers)
1046 cached_headers = headers;
1049 internal bool IsCached {
1050 get { return cached_response != null; }
1053 cached_response = new CachedRawResponse (cache_policy);
1055 cached_response = null;
1059 public HttpCachePolicy Cache {
1061 if (cache_policy == null)
1062 cache_policy = new HttpCachePolicy ();
1064 return cache_policy;
1068 internal CachedRawResponse GetCachedResponse ()
1070 if (cached_response != null) {
1071 cached_response.StatusCode = StatusCode;
1072 cached_response.StatusDescription = StatusDescription;
1075 return cached_response;
1079 // This is one of the old ASP compatibility methods, the real cache
1080 // control is in the Cache property, and this is a second class citizen
1082 public string CacheControl {
1084 if (value == null || value == "") {
1085 Cache.SetCacheability (HttpCacheability.NoCache);
1086 user_cache_control = null;
1087 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1088 Cache.SetCacheability (HttpCacheability.Public);
1089 user_cache_control = "public";
1090 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1091 Cache.SetCacheability (HttpCacheability.Private);
1092 user_cache_control = "private";
1093 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1094 Cache.SetCacheability (HttpCacheability.NoCache);
1095 user_cache_control = "no-cache";
1097 throw new ArgumentException ("CacheControl property only allows `public', " +
1098 "`private' or no-cache, for different uses, use " +
1099 "Response.AppendHeader");
1102 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1106 internal int GetOutputByteCount ()
1108 return output_stream.GetTotalLength ();
1111 internal void ReleaseResources ()
1113 output_stream.ReleaseResources (true);
1114 output_stream = null;
1121 static class FlagEnd
1123 public static readonly object Value = new object ();