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 string 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.
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 static HttpResponse ()
111 HttpRuntimeSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/httpRuntime") as HttpRuntimeSection;
113 HttpRuntimeConfig config = HttpContext.GetAppConfig ("system.web/httpRuntime") as HttpRuntimeConfig;
115 if (config != null && config.EnableVersionHeader)
116 version_header = Environment.Version.ToString (3);
119 internal HttpResponse ()
121 output_stream = new HttpResponseStream (this);
124 public HttpResponse (TextWriter writer) : this ()
126 this.writer = writer;
129 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
131 WorkerRequest = worker_request;
132 this.context = context;
135 if (worker_request != null)
136 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
140 internal TextWriter SetTextWriter (TextWriter writer)
142 TextWriter prev = this.writer;
143 this.writer = writer;
147 internal string[] FileDependencies {
149 if (fileDependencies == null || fileDependencies.Count == 0)
150 return new string[0] {};
151 return (string[]) fileDependencies.ToArray (typeof (string));
155 ArrayList FileDependenciesArray {
157 if (fileDependencies == null)
158 fileDependencies = new ArrayList ();
159 return fileDependencies;
173 public bool BufferOutput {
184 // Use the default from <globalization> section if the client has not set the encoding
186 public Encoding ContentEncoding {
188 if (encoding == null) {
189 if (context != null) {
190 string client_content_type = context.Request.ContentType;
191 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
192 if (parameter != null) {
194 // Do what the #1 web server does
195 encoding = Encoding.GetEncoding (parameter);
200 if (encoding == null)
201 encoding = WebEncoding.ResponseEncoding;
208 throw new ArgumentException ("ContentEncoding can not be null");
211 HttpWriter http_writer = writer as HttpWriter;
212 if (http_writer != null)
213 http_writer.SetEncoding (encoding);
217 public string ContentType {
223 content_type = value;
227 public string Charset {
230 charset = ContentEncoding.WebName;
241 public HttpCookieCollection Cookies {
244 cookies = new HttpCookieCollection (true, false);
251 if (cache_policy == null)
254 return cache_policy.ExpireMinutes ();
258 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
262 public DateTime ExpiresAbsolute {
264 return Cache.Expires;
268 Cache.SetExpires (value);
272 public Stream Filter {
274 if (WorkerRequest == null)
277 return output_stream.Filter;
281 output_stream.Filter = value;
285 public Encoding HeaderEncoding {
287 if (headerEncoding == null) {
288 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
291 headerEncoding = Encoding.UTF8;
293 headerEncoding = gs.ResponseHeaderEncoding;
294 if (headerEncoding == Encoding.Unicode)
295 throw new HttpException ("HeaderEncoding must not be Unicode");
298 return headerEncoding;
302 throw new HttpException ("headers have already been sent");
304 throw new ArgumentNullException ("HeaderEncoding");
305 if (value == Encoding.Unicode)
306 throw new HttpException ("HeaderEncoding must not be Unicode");
307 headerEncoding = value;
315 NameValueCollection Headers {
318 headers = new NameValueCollection ();
325 public bool IsClientConnected {
327 if (WorkerRequest == null)
328 return true; // yep that's true
330 return WorkerRequest.IsClientConnected ();
334 public bool IsRequestBeingRedirected {
335 get { return is_request_being_redirected; }
338 public TextWriter Output {
341 writer = new HttpWriter (this);
347 public Stream OutputStream {
349 return output_stream;
353 public string RedirectLocation {
355 return redirect_location;
359 redirect_location = value;
363 public string Status {
364 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
367 int p = value.IndexOf (' ');
369 throw new HttpException ("Invalid format for the Status property");
371 string s = value.Substring (0, p);
374 if (!Int32.TryParse (s, out status_code))
375 throw new HttpException ("Invalid format for the Status property");
379 status_code = Int32.Parse (s);
381 throw new HttpException ("Invalid format for the Status property");
385 status_description = value.Substring (p+1);
390 // We ignore the two properties on Mono as they are for use with IIS7, but there is
391 // no point in throwing PlatformNotSupportedException. We might find a use for them
393 public int SubStatusCode {
398 public bool TrySkipIisCustomErrors {
404 public int StatusCode {
411 throw new HttpException ("headers have already been sent");
414 status_description = null;
418 public string StatusDescription {
420 if (status_description == null)
421 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
423 return status_description;
428 throw new HttpException ("headers have already been sent");
430 status_description = value;
434 public bool SuppressContent {
436 return suppress_content;
440 suppress_content = value;
445 [MonoTODO ("Not implemented")]
446 public void AddCacheDependency (CacheDependency[] dependencies)
448 throw new NotImplementedException ();
451 [MonoTODO ("Not implemented")]
452 public void AddCacheItemDependencies (string[] cacheKeys)
454 throw new NotImplementedException ();
457 [MonoTODO("Currently does nothing")]
458 public void AddCacheItemDependencies (ArrayList cacheKeys)
460 // TODO: talk to jackson about the cache
463 [MonoTODO("Currently does nothing")]
464 public void AddCacheItemDependency (string cacheKey)
466 // TODO: talk to jackson about the cache
469 public void AddFileDependencies (ArrayList filenames)
471 if (filenames == null || filenames.Count == 0)
473 FileDependenciesArray.AddRange (filenames);
476 public void AddFileDependencies (string[] filenames)
478 if (filenames == null || filenames.Length == 0)
480 FileDependenciesArray.AddRange (filenames);
484 public void AddFileDependency (string filename)
486 if (filename == null || filename == String.Empty)
488 FileDependenciesArray.Add (filename);
491 public void AddHeader (string name, string value)
493 AppendHeader (name, value);
496 public void AppendCookie (HttpCookie cookie)
498 Cookies.Add (cookie);
503 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
506 public void AppendHeader (string name, string value)
509 throw new HttpException ("headers have been already sent");
512 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
513 content_length = (long) UInt64.Parse (value);
519 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
525 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
526 transfer_encoding = value;
532 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
533 user_cache_control = value;
537 Headers.Add (name, value);
540 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
541 public void AppendToLog (string param)
543 Console.Write ("System.Web: ");
544 Console.WriteLine (param);
547 public string ApplyAppPathModifier (string virtualPath)
549 if (virtualPath == null)
552 if (virtualPath.Length == 0)
553 return context.Request.RootVirtualDir;
555 if (UrlUtils.IsRelativeUrl (virtualPath)) {
556 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
557 } else if (UrlUtils.IsRooted (virtualPath)) {
558 virtualPath = UrlUtils.Canonic (virtualPath);
561 bool cookieless = false;
563 SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
564 cookieless = SessionStateModule.IsCookieLess (context, config);
566 SessionConfig config = HttpContext.GetAppConfig ("system.web/sessionState") as SessionConfig;
567 cookieless = config.CookieLess;
572 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
573 if (UrlUtils.HasSessionId (virtualPath))
574 virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
575 return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
581 public void BinaryWrite (byte [] buffer)
583 output_stream.Write (buffer, 0, buffer.Length);
586 internal void BinaryWrite (byte [] buffer, int start, int len)
588 output_stream.Write (buffer, start, len);
596 public void ClearContent ()
598 output_stream.Clear ();
602 public void ClearHeaders ()
605 throw new HttpException ("headers have been already sent");
607 // Reset the special case headers.
609 content_type = "text/html";
610 transfer_encoding = null;
611 user_cache_control = "private";
612 if (cache_policy != null)
613 cache_policy.Cacheability = HttpCacheability.Private;
619 internal bool HeadersSent {
629 if (WorkerRequest != null)
630 WorkerRequest.CloseConnection ();
635 public void DisableKernelCache ()
637 // does nothing in Mono
646 if (context.TimeoutPossible) {
647 Thread.CurrentThread.Abort (FlagEnd.Value);
649 // If this is called from an async event, signal the completion
651 HttpApplication app_instance = context.ApplicationInstance;
652 if (app_instance != null)
653 app_instance.CompleteRequest ();
660 // Transfer-Encoding (chunked)
663 void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush)
670 write_headers.Add ("Transfer-Encoding", "chunked");
671 else if (transfer_encoding != null)
672 write_headers.Add ("Transfer-Encoding", transfer_encoding);
674 if (redirect_location != null)
675 write_headers.Add ("Location", redirect_location);
678 if (version_header != null)
679 write_headers.Add ("X-AspNet-Version", version_header);
682 // If Content-Length is set.
684 if (content_length >= 0) {
685 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
686 content_length.ToString (CultureInfo.InvariantCulture));
687 } else if (BufferOutput) {
690 // If we are buffering and this is the last flush, not a middle-flush,
691 // we know the content-length.
693 content_length = output_stream.total;
694 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
695 content_length.ToString (CultureInfo.InvariantCulture));
698 // We are buffering, and this is a flush in the middle.
699 // If we are not chunked, we need to set "Connection: close".
702 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
707 // If the content-length is not set, and we are not buffering, we must
711 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
717 // Cache Control, the cache policy takes precedence over the cache_control property.
719 if (cache_policy != null)
720 cache_policy.SetHeaders (this, headers);
722 write_headers.Add ("Cache-Control", CacheControl);
727 if (content_type != null){
728 string header = content_type;
730 if (charset_set || header == "text/plain" || header == "text/html") {
731 if (header.IndexOf ("charset=") == -1) {
732 if (charset == null || charset == "")
733 charset = ContentEncoding.HeaderName;
734 header += "; charset=" + charset;
738 write_headers.Add ("Content-Type", header);
741 if (cookies != null && cookies.Count != 0){
742 int n = cookies.Count;
743 for (int i = 0; i < n; i++)
744 write_headers.Add ("Set-Cookie", cookies.Get (i).GetCookieHeaderValue ());
746 // For J2EE Portal support emulate cookies by storing them in the session.
747 context.Request.SetSessionCookiesForPortal (cookies);
752 internal void WriteHeaders (bool final_flush)
760 if (context != null) {
761 HttpApplication app_instance = context.ApplicationInstance;
762 if (app_instance != null)
763 app_instance.TriggerPreSendRequestHeaders ();
768 if (cached_response != null)
769 cached_response.SetHeaders (headers);
771 // If this page is cached use the cached headers
772 // instead of the standard headers
773 NameValueCollection write_headers;
774 if (cached_headers != null)
775 write_headers = cached_headers;
777 write_headers = Headers;
778 AddHeadersNoCache (write_headers, final_flush);
781 if (WorkerRequest != null)
782 WorkerRequest.SendStatus (status_code, StatusDescription);
784 if (WorkerRequest != null) {
789 for (int i = 0; i < write_headers.Count; i++) {
790 header_name = write_headers.GetKey (i);
791 header_index = HttpWorkerRequest.GetKnownResponseHeaderIndex (header_name);
792 values = write_headers.GetValues (i);
796 foreach (string val in values) {
797 if (header_index > -1)
798 WorkerRequest.SendKnownResponseHeader (header_index, val);
800 WorkerRequest.SendUnknownResponseHeader (header_name, val);
806 internal void DoFilter (bool close)
808 if (output_stream.HaveFilter && context != null && context.Error == null)
809 output_stream.ApplyFilter (close);
812 internal void Flush (bool final_flush)
814 DoFilter (final_flush);
816 if (final_flush || status_code != 200)
820 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
821 if (suppress_content || head) {
824 output_stream.Clear ();
825 if (WorkerRequest != null)
826 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
831 WriteHeaders (final_flush);
833 if (context != null) {
834 HttpApplication app_instance = context.ApplicationInstance;
835 if (app_instance != null)
836 app_instance.TriggerPreSendRequestContent ();
840 MemoryStream ms = output_stream.GetData ();
841 cached_response.ContentLength = (int) ms.Length;
842 cached_response.SetData (ms.GetBuffer ());
845 if (WorkerRequest != null)
846 output_stream.Flush (WorkerRequest, final_flush);
854 public void Pics (string value)
856 AppendHeader ("PICS-Label", value);
859 public void Redirect (string url)
861 Redirect (url, true);
864 public void Redirect (string url, bool endResponse)
867 throw new HttpException ("Headers have already been sent");
870 is_request_being_redirected = true;
876 url = ApplyAppPathModifier (url);
877 redirect_location = url;
879 // Text for browsers that can't handle location header
880 Write ("<html><head><title>Object moved</title></head><body>\r\n");
881 Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
882 Write ("</body><html>\r\n");
887 is_request_being_redirected = false;
891 public static void RemoveOutputCacheItem (string path)
894 throw new ArgumentNullException ("path");
896 if (path.Length == 0)
900 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
902 HttpRuntime.InternalCache.Remove (path);
905 public void SetCookie (HttpCookie cookie)
907 AppendCookie (cookie);
910 public void Write (char ch)
915 public void Write (object obj)
920 Output.Write (obj.ToString ());
923 public void Write (string s)
928 public void Write (char [] buffer, int index, int count)
930 Output.Write (buffer, index, count);
933 internal void WriteFile (FileStream fs, long offset, long size)
935 byte [] buffer = new byte [32*1024];
938 fs.Position = offset;
942 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
944 output_stream.Write (buffer, 0, n);
948 public void WriteFile (string filename)
950 WriteFile (filename, false);
953 public void WriteFile (string filename, bool readIntoMemory)
955 if (filename == null)
956 throw new ArgumentNullException ("filename");
959 using (FileStream fs = File.OpenRead (filename))
960 WriteFile (fs, 0, fs.Length);
962 FileInfo fi = new FileInfo (filename);
963 output_stream.WriteFile (filename, 0, fi.Length);
968 output_stream.ApplyFilter (false);
973 public void WriteFile (IntPtr fileHandle, long offset, long size) {
974 throw new PlatformNotSupportedException("IntPtr not supported");
977 public void WriteFile (IntPtr fileHandle, long offset, long size)
980 throw new ArgumentNullException ("offset can not be negative");
982 throw new ArgumentNullException ("size can not be negative");
987 // Note: this .ctor will throw a SecurityException if the caller
988 // doesn't have the UnmanagedCode permission
989 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
990 WriteFile (fs, offset, size);
994 output_stream.ApplyFilter (false);
999 public void WriteFile (string filename, long offset, long size)
1001 if (filename == null)
1002 throw new ArgumentNullException ("filename");
1004 throw new ArgumentNullException ("offset can not be negative");
1006 throw new ArgumentNullException ("size can not be negative");
1011 FileStream fs = File.OpenRead (filename);
1012 WriteFile (fs, offset, size);
1017 output_stream.ApplyFilter (false);
1021 [MonoTODO ("Not implemented")]
1022 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
1024 throw new NotImplementedException ();
1028 // Like WriteFile, but never buffers, so we manually Flush here
1030 public void TransmitFile (string filename)
1032 if (filename == null)
1033 throw new ArgumentNullException ("filename");
1035 TransmitFile (filename, false);
1038 internal void TransmitFile (string filename, bool final_flush)
1040 FileInfo fi = new FileInfo (filename);
1041 using (Stream s = fi.OpenRead ()); // Just check if we can read.
1042 output_stream.WriteFile (filename, 0, fi.Length);
1043 output_stream.ApplyFilter (final_flush);
1044 Flush (final_flush);
1048 public void TransmitFile (string filename, long offset, long length)
1050 output_stream.WriteFile (filename, offset, length);
1051 output_stream.ApplyFilter (false);
1055 internal void TransmitFile (VirtualFile vf)
1057 TransmitFile (vf, false);
1060 const int bufLen = 65535;
1061 internal void TransmitFile (VirtualFile vf, bool final_flush)
1064 throw new ArgumentNullException ("vf");
1066 if (vf is DefaultVirtualFile) {
1067 TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
1071 byte[] buf = new byte [bufLen];
1072 using (Stream s = vf.Open ()) {
1074 while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
1075 output_stream.Write (buf, 0, readBytes);
1076 output_stream.ApplyFilter (final_flush);
1085 #region Session state support
1086 internal void SetAppPathModifier (string app_modifier)
1088 app_path_mod = app_modifier;
1092 #region Cache Support
1093 internal void SetCachedHeaders (NameValueCollection headers)
1095 cached_headers = headers;
1099 internal bool IsCached {
1100 get { return cached_response != null; }
1103 cached_response = new CachedRawResponse (cache_policy);
1105 cached_response = null;
1109 public HttpCachePolicy Cache {
1111 if (cache_policy == null)
1112 cache_policy = new HttpCachePolicy ();
1114 return cache_policy;
1118 internal CachedRawResponse GetCachedResponse ()
1120 if (cached_response != null) {
1121 cached_response.StatusCode = StatusCode;
1122 cached_response.StatusDescription = StatusDescription;
1125 return cached_response;
1129 // This is one of the old ASP compatibility methods, the real cache
1130 // control is in the Cache property, and this is a second class citizen
1132 public string CacheControl {
1134 if (value == null || value == "") {
1135 Cache.SetCacheability (HttpCacheability.NoCache);
1136 user_cache_control = null;
1137 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1138 Cache.SetCacheability (HttpCacheability.Public);
1139 user_cache_control = "public";
1140 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1141 Cache.SetCacheability (HttpCacheability.Private);
1142 user_cache_control = "private";
1143 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1144 Cache.SetCacheability (HttpCacheability.NoCache);
1145 user_cache_control = "no-cache";
1147 throw new ArgumentException ("CacheControl property only allows `public', " +
1148 "`private' or no-cache, for different uses, use " +
1149 "Response.AppendHeader");
1152 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1156 internal int GetOutputByteCount ()
1158 return output_stream.GetTotalLength ();
1161 internal void ReleaseResources ()
1163 output_stream.ReleaseResources (true);
1164 output_stream = null;
1171 static class FlagEnd
1173 public static readonly object Value = new object ();