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;
71 string version_header;
72 bool version_header_checked;
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.
85 NameValueCollection headers;
87 NameValueCollection cached_headers;
90 // Transfer encoding state
92 string transfer_encoding;
93 internal bool use_chunked;
96 internal bool suppress_content;
104 bool is_request_being_redirected;
105 Encoding headerEncoding;
108 internal HttpResponse ()
110 output_stream = new HttpResponseStream (this);
113 public HttpResponse (TextWriter writer) : this ()
115 this.writer = writer;
118 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
120 WorkerRequest = worker_request;
121 this.context = context;
124 if (worker_request != null)
125 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
129 internal TextWriter SetTextWriter (TextWriter writer)
131 TextWriter prev = this.writer;
132 this.writer = writer;
136 internal string VersionHeader {
138 if (!version_header_checked && version_header == null) {
139 version_header_checked = true;
141 HttpRuntimeSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/httpRuntime") as HttpRuntimeSection;
143 HttpRuntimeConfig config = HttpContext.GetAppConfig ("system.web/httpRuntime") as HttpRuntimeConfig;
145 if (config != null && config.EnableVersionHeader)
146 version_header = Environment.Version.ToString (3);
149 return version_header;
153 internal string[] FileDependencies {
155 if (fileDependencies == null || fileDependencies.Count == 0)
156 return new string[0] {};
157 return (string[]) fileDependencies.ToArray (typeof (string));
161 ArrayList FileDependenciesArray {
163 if (fileDependencies == null)
164 fileDependencies = new ArrayList ();
165 return fileDependencies;
179 public bool BufferOutput {
190 // Use the default from <globalization> section if the client has not set the encoding
192 public Encoding ContentEncoding {
194 if (encoding == null) {
195 if (context != null) {
196 string client_content_type = context.Request.ContentType;
197 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
198 if (parameter != null) {
200 // Do what the #1 web server does
201 encoding = Encoding.GetEncoding (parameter);
206 if (encoding == null)
207 encoding = WebEncoding.ResponseEncoding;
214 throw new ArgumentException ("ContentEncoding can not be null");
217 HttpWriter http_writer = writer as HttpWriter;
218 if (http_writer != null)
219 http_writer.SetEncoding (encoding);
223 public string ContentType {
229 content_type = value;
233 public string Charset {
236 charset = ContentEncoding.WebName;
247 public HttpCookieCollection Cookies {
250 cookies = new HttpCookieCollection (true, false);
257 if (cache_policy == null)
260 return cache_policy.ExpireMinutes ();
264 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
268 public DateTime ExpiresAbsolute {
270 return Cache.Expires;
274 Cache.SetExpires (value);
278 public Stream Filter {
280 if (WorkerRequest == null)
283 return output_stream.Filter;
287 output_stream.Filter = value;
291 public Encoding HeaderEncoding {
293 if (headerEncoding == null) {
294 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
297 headerEncoding = Encoding.UTF8;
299 headerEncoding = gs.ResponseHeaderEncoding;
300 if (headerEncoding == Encoding.Unicode)
301 throw new HttpException ("HeaderEncoding must not be Unicode");
304 return headerEncoding;
308 throw new HttpException ("headers have already been sent");
310 throw new ArgumentNullException ("HeaderEncoding");
311 if (value == Encoding.Unicode)
312 throw new HttpException ("HeaderEncoding must not be Unicode");
313 headerEncoding = value;
321 NameValueCollection Headers {
324 headers = new NameValueCollection ();
331 public bool IsClientConnected {
333 if (WorkerRequest == null)
334 return true; // yep that's true
336 return WorkerRequest.IsClientConnected ();
340 public bool IsRequestBeingRedirected {
341 get { return is_request_being_redirected; }
344 public TextWriter Output {
347 writer = new HttpWriter (this);
353 public Stream OutputStream {
355 return output_stream;
359 public string RedirectLocation {
361 return redirect_location;
365 redirect_location = value;
369 public string Status {
370 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
373 int p = value.IndexOf (' ');
375 throw new HttpException ("Invalid format for the Status property");
377 string s = value.Substring (0, p);
380 if (!Int32.TryParse (s, out status_code))
381 throw new HttpException ("Invalid format for the Status property");
385 status_code = Int32.Parse (s);
387 throw new HttpException ("Invalid format for the Status property");
391 status_description = value.Substring (p+1);
396 // We ignore the two properties on Mono as they are for use with IIS7, but there is
397 // no point in throwing PlatformNotSupportedException. We might find a use for them
399 public int SubStatusCode {
404 public bool TrySkipIisCustomErrors {
410 public int StatusCode {
417 throw new HttpException ("headers have already been sent");
420 status_description = null;
424 public string StatusDescription {
426 if (status_description == null)
427 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
429 return status_description;
434 throw new HttpException ("headers have already been sent");
436 status_description = value;
440 public bool SuppressContent {
442 return suppress_content;
446 suppress_content = value;
451 [MonoTODO ("Not implemented")]
452 public void AddCacheDependency (CacheDependency[] dependencies)
454 throw new NotImplementedException ();
457 [MonoTODO ("Not implemented")]
458 public void AddCacheItemDependencies (string[] cacheKeys)
460 throw new NotImplementedException ();
463 [MonoTODO("Currently does nothing")]
464 public void AddCacheItemDependencies (ArrayList cacheKeys)
466 // TODO: talk to jackson about the cache
469 [MonoTODO("Currently does nothing")]
470 public void AddCacheItemDependency (string cacheKey)
472 // TODO: talk to jackson about the cache
475 public void AddFileDependencies (ArrayList filenames)
477 if (filenames == null || filenames.Count == 0)
479 FileDependenciesArray.AddRange (filenames);
482 public void AddFileDependencies (string[] filenames)
484 if (filenames == null || filenames.Length == 0)
486 FileDependenciesArray.AddRange (filenames);
490 public void AddFileDependency (string filename)
492 if (filename == null || filename == String.Empty)
494 FileDependenciesArray.Add (filename);
497 public void AddHeader (string name, string value)
499 AppendHeader (name, value);
502 public void AppendCookie (HttpCookie cookie)
504 Cookies.Add (cookie);
509 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
512 public void AppendHeader (string name, string value)
515 throw new HttpException ("headers have been already sent");
518 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
519 content_length = (long) UInt64.Parse (value);
525 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
531 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
532 transfer_encoding = value;
538 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
539 user_cache_control = value;
543 Headers.Add (name, value);
546 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
547 public void AppendToLog (string param)
549 Console.Write ("System.Web: ");
550 Console.WriteLine (param);
553 public string ApplyAppPathModifier (string virtualPath)
555 if (virtualPath == null)
558 if (virtualPath.Length == 0)
559 return context.Request.RootVirtualDir;
561 if (UrlUtils.IsRelativeUrl (virtualPath)) {
562 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
563 } else if (UrlUtils.IsRooted (virtualPath)) {
564 virtualPath = UrlUtils.Canonic (virtualPath);
567 bool cookieless = false;
569 SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
570 cookieless = SessionStateModule.IsCookieLess (context, config);
572 SessionConfig config = HttpContext.GetAppConfig ("system.web/sessionState") as SessionConfig;
573 cookieless = config.CookieLess;
578 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
579 if (UrlUtils.HasSessionId (virtualPath))
580 virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
581 return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
587 public void BinaryWrite (byte [] buffer)
589 output_stream.Write (buffer, 0, buffer.Length);
592 internal void BinaryWrite (byte [] buffer, int start, int len)
594 output_stream.Write (buffer, start, len);
602 public void ClearContent ()
604 output_stream.Clear ();
608 public void ClearHeaders ()
611 throw new HttpException ("headers have been already sent");
613 // Reset the special case headers.
615 content_type = "text/html";
616 transfer_encoding = null;
617 user_cache_control = "private";
618 if (cache_policy != null)
619 cache_policy.Cacheability = HttpCacheability.Private;
625 internal bool HeadersSent {
635 if (WorkerRequest != null)
636 WorkerRequest.CloseConnection ();
641 public void DisableKernelCache ()
643 // does nothing in Mono
652 if (context.TimeoutPossible) {
653 Thread.CurrentThread.Abort (FlagEnd.Value);
655 // If this is called from an async event, signal the completion
657 HttpApplication app_instance = context.ApplicationInstance;
658 if (app_instance != null)
659 app_instance.CompleteRequest ();
666 // Transfer-Encoding (chunked)
669 void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush)
676 write_headers.Add ("Transfer-Encoding", "chunked");
677 else if (transfer_encoding != null)
678 write_headers.Add ("Transfer-Encoding", transfer_encoding);
680 if (redirect_location != null)
681 write_headers.Add ("Location", redirect_location);
684 string vh = VersionHeader;
686 write_headers.Add ("X-AspNet-Version", vh);
689 // If Content-Length is set.
691 if (content_length >= 0) {
692 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
693 content_length.ToString (CultureInfo.InvariantCulture));
694 } else if (BufferOutput) {
697 // If we are buffering and this is the last flush, not a middle-flush,
698 // we know the content-length.
700 content_length = output_stream.total;
701 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
702 content_length.ToString (CultureInfo.InvariantCulture));
705 // We are buffering, and this is a flush in the middle.
706 // If we are not chunked, we need to set "Connection: close".
709 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
714 // If the content-length is not set, and we are not buffering, we must
718 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
724 // Cache Control, the cache policy takes precedence over the cache_control property.
726 if (cache_policy != null)
727 cache_policy.SetHeaders (this, headers);
729 write_headers.Add ("Cache-Control", CacheControl);
734 if (content_type != null){
735 string header = content_type;
737 if (charset_set || header == "text/plain" || header == "text/html") {
738 if (header.IndexOf ("charset=") == -1) {
739 if (charset == null || charset == "")
740 charset = ContentEncoding.HeaderName;
741 header += "; charset=" + charset;
745 write_headers.Add ("Content-Type", header);
748 if (cookies != null && cookies.Count != 0){
749 int n = cookies.Count;
750 for (int i = 0; i < n; i++)
751 write_headers.Add ("Set-Cookie", cookies.Get (i).GetCookieHeaderValue ());
753 // For J2EE Portal support emulate cookies by storing them in the session.
754 context.Request.SetSessionCookiesForPortal (cookies);
759 internal void WriteHeaders (bool final_flush)
767 if (context != null) {
768 HttpApplication app_instance = context.ApplicationInstance;
769 if (app_instance != null)
770 app_instance.TriggerPreSendRequestHeaders ();
775 if (cached_response != null)
776 cached_response.SetHeaders (headers);
778 // If this page is cached use the cached headers
779 // instead of the standard headers
780 NameValueCollection write_headers;
781 if (cached_headers != null)
782 write_headers = cached_headers;
784 write_headers = Headers;
785 AddHeadersNoCache (write_headers, final_flush);
788 if (WorkerRequest != null)
789 WorkerRequest.SendStatus (status_code, StatusDescription);
791 if (WorkerRequest != null) {
796 for (int i = 0; i < write_headers.Count; i++) {
797 header_name = write_headers.GetKey (i);
798 header_index = HttpWorkerRequest.GetKnownResponseHeaderIndex (header_name);
799 values = write_headers.GetValues (i);
803 foreach (string val in values) {
804 if (header_index > -1)
805 WorkerRequest.SendKnownResponseHeader (header_index, val);
807 WorkerRequest.SendUnknownResponseHeader (header_name, val);
813 internal void DoFilter (bool close)
815 if (output_stream.HaveFilter && context != null && context.Error == null)
816 output_stream.ApplyFilter (close);
819 internal void Flush (bool final_flush)
821 DoFilter (final_flush);
823 if (final_flush || status_code != 200)
827 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
828 if (suppress_content || head) {
831 output_stream.Clear ();
832 if (WorkerRequest != null)
833 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
838 WriteHeaders (final_flush);
840 if (context != null) {
841 HttpApplication app_instance = context.ApplicationInstance;
842 if (app_instance != null)
843 app_instance.TriggerPreSendRequestContent ();
847 MemoryStream ms = output_stream.GetData ();
848 cached_response.ContentLength = (int) ms.Length;
849 cached_response.SetData (ms.GetBuffer ());
852 if (WorkerRequest != null)
853 output_stream.Flush (WorkerRequest, final_flush);
861 public void Pics (string value)
863 AppendHeader ("PICS-Label", value);
866 public void Redirect (string url)
868 Redirect (url, true);
871 public void Redirect (string url, bool endResponse)
874 throw new HttpException ("Headers have already been sent");
877 is_request_being_redirected = true;
883 url = ApplyAppPathModifier (url);
884 redirect_location = url;
886 // Text for browsers that can't handle location header
887 Write ("<html><head><title>Object moved</title></head><body>\r\n");
888 Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
889 Write ("</body><html>\r\n");
894 is_request_being_redirected = false;
898 public static void RemoveOutputCacheItem (string path)
901 throw new ArgumentNullException ("path");
903 if (path.Length == 0)
907 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
909 HttpRuntime.InternalCache.Remove (path);
912 public void SetCookie (HttpCookie cookie)
914 AppendCookie (cookie);
917 public void Write (char ch)
922 public void Write (object obj)
927 Output.Write (obj.ToString ());
930 public void Write (string s)
935 public void Write (char [] buffer, int index, int count)
937 Output.Write (buffer, index, count);
940 internal void WriteFile (FileStream fs, long offset, long size)
942 byte [] buffer = new byte [32*1024];
945 fs.Position = offset;
949 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
951 output_stream.Write (buffer, 0, n);
955 public void WriteFile (string filename)
957 WriteFile (filename, false);
960 public void WriteFile (string filename, bool readIntoMemory)
962 if (filename == null)
963 throw new ArgumentNullException ("filename");
966 using (FileStream fs = File.OpenRead (filename))
967 WriteFile (fs, 0, fs.Length);
969 FileInfo fi = new FileInfo (filename);
970 output_stream.WriteFile (filename, 0, fi.Length);
975 output_stream.ApplyFilter (false);
980 public void WriteFile (IntPtr fileHandle, long offset, long size) {
981 throw new PlatformNotSupportedException("IntPtr not supported");
984 public void WriteFile (IntPtr fileHandle, long offset, long size)
987 throw new ArgumentNullException ("offset can not be negative");
989 throw new ArgumentNullException ("size can not be negative");
994 // Note: this .ctor will throw a SecurityException if the caller
995 // doesn't have the UnmanagedCode permission
996 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
997 WriteFile (fs, offset, size);
1001 output_stream.ApplyFilter (false);
1006 public void WriteFile (string filename, long offset, long size)
1008 if (filename == null)
1009 throw new ArgumentNullException ("filename");
1011 throw new ArgumentNullException ("offset can not be negative");
1013 throw new ArgumentNullException ("size can not be negative");
1018 FileStream fs = File.OpenRead (filename);
1019 WriteFile (fs, offset, size);
1024 output_stream.ApplyFilter (false);
1028 [MonoTODO ("Not implemented")]
1029 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
1031 throw new NotImplementedException ();
1035 // Like WriteFile, but never buffers, so we manually Flush here
1037 public void TransmitFile (string filename)
1039 if (filename == null)
1040 throw new ArgumentNullException ("filename");
1042 TransmitFile (filename, false);
1045 internal void TransmitFile (string filename, bool final_flush)
1047 FileInfo fi = new FileInfo (filename);
1048 using (Stream s = fi.OpenRead ()); // Just check if we can read.
1049 output_stream.WriteFile (filename, 0, fi.Length);
1050 output_stream.ApplyFilter (final_flush);
1051 Flush (final_flush);
1055 public void TransmitFile (string filename, long offset, long length)
1057 output_stream.WriteFile (filename, offset, length);
1058 output_stream.ApplyFilter (false);
1062 internal void TransmitFile (VirtualFile vf)
1064 TransmitFile (vf, false);
1067 const int bufLen = 65535;
1068 internal void TransmitFile (VirtualFile vf, bool final_flush)
1071 throw new ArgumentNullException ("vf");
1073 if (vf is DefaultVirtualFile) {
1074 TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
1078 byte[] buf = new byte [bufLen];
1079 using (Stream s = vf.Open ()) {
1081 while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
1082 output_stream.Write (buf, 0, readBytes);
1083 output_stream.ApplyFilter (final_flush);
1092 #region Session state support
1093 internal void SetAppPathModifier (string app_modifier)
1095 app_path_mod = app_modifier;
1099 #region Cache Support
1100 internal void SetCachedHeaders (NameValueCollection headers)
1102 cached_headers = headers;
1106 internal bool IsCached {
1107 get { return cached_response != null; }
1110 cached_response = new CachedRawResponse (cache_policy);
1112 cached_response = null;
1116 public HttpCachePolicy Cache {
1118 if (cache_policy == null)
1119 cache_policy = new HttpCachePolicy ();
1121 return cache_policy;
1125 internal CachedRawResponse GetCachedResponse ()
1127 if (cached_response != null) {
1128 cached_response.StatusCode = StatusCode;
1129 cached_response.StatusDescription = StatusDescription;
1132 return cached_response;
1136 // This is one of the old ASP compatibility methods, the real cache
1137 // control is in the Cache property, and this is a second class citizen
1139 public string CacheControl {
1141 if (value == null || value == "") {
1142 Cache.SetCacheability (HttpCacheability.NoCache);
1143 user_cache_control = null;
1144 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1145 Cache.SetCacheability (HttpCacheability.Public);
1146 user_cache_control = "public";
1147 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1148 Cache.SetCacheability (HttpCacheability.Private);
1149 user_cache_control = "private";
1150 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1151 Cache.SetCacheability (HttpCacheability.NoCache);
1152 user_cache_control = "no-cache";
1154 throw new ArgumentException ("CacheControl property only allows `public', " +
1155 "`private' or no-cache, for different uses, use " +
1156 "Response.AppendHeader");
1159 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1163 internal int GetOutputByteCount ()
1165 return output_stream.GetTotalLength ();
1168 internal void ReleaseResources ()
1170 output_stream.ReleaseResources (true);
1171 output_stream = null;
1178 static class FlagEnd
1180 public static readonly object Value = new object ();