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;
43 namespace System.Web {
45 // CAS - no InheritanceDemand here as the class is sealed
46 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
47 public sealed partial class HttpResponse {
48 internal HttpWorkerRequest WorkerRequest;
49 internal HttpResponseStream output_stream;
50 internal bool buffer = true;
52 ArrayList fileDependencies;
56 HttpCachePolicy cache_policy;
58 HttpCookieCollection cookies;
60 int status_code = 200;
61 string status_description = "OK";
63 string content_type = "text/html";
66 CachedRawResponse cached_response;
67 string user_cache_control = "private";
68 string redirect_location;
71 // Negative Content-Length means we auto-compute the size of content-length
72 // can be overwritten with AppendHeader ("Content-Length", value)
74 long content_length = -1;
77 // The list of the headers that we will send back to the client, except
78 // the headers that we compute here.
80 ArrayList headers = new ArrayList ();
82 ArrayList cached_headers;
85 // Transfer encoding state
87 string transfer_encoding;
88 internal bool use_chunked;
91 internal bool suppress_content;
101 internal object FlagEnd = new object ();
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[] FileDependencies {
138 if (fileDependencies == null || fileDependencies.Count == 0)
139 return new string[0] {};
140 return (string[]) fileDependencies.ToArray (typeof (string));
144 ArrayList FileDependenciesArray {
146 if (fileDependencies == null)
147 fileDependencies = new ArrayList ();
148 return fileDependencies;
162 public bool BufferOutput {
173 // Use the default from <globalization> section if the client has not set the encoding
175 public Encoding ContentEncoding {
177 if (encoding == null) {
178 if (context != null) {
179 string client_content_type = context.Request.ContentType;
180 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
181 if (parameter != null) {
183 // Do what the #1 web server does
184 encoding = Encoding.GetEncoding (parameter);
189 if (encoding == null)
190 encoding = WebEncoding.ResponseEncoding;
197 throw new ArgumentException ("ContentEncoding can not be null");
200 HttpWriter http_writer = writer as HttpWriter;
201 if (http_writer != null)
202 http_writer.SetEncoding (encoding);
206 public string ContentType {
212 content_type = value;
216 public string Charset {
219 charset = ContentEncoding.WebName;
230 public HttpCookieCollection Cookies {
233 cookies = new HttpCookieCollection (true, false);
240 if (cache_policy == null)
243 return cache_policy.ExpireMinutes ();
247 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
251 public DateTime ExpiresAbsolute {
253 return Cache.Expires;
257 Cache.SetExpires (value);
261 public Stream Filter {
263 if (WorkerRequest == null)
266 return output_stream.Filter;
270 output_stream.Filter = value;
274 public Encoding HeaderEncoding {
276 if (headerEncoding == null) {
277 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
280 headerEncoding = Encoding.UTF8;
282 headerEncoding = gs.ResponseHeaderEncoding;
283 if (headerEncoding == Encoding.Unicode)
284 throw new HttpException ("HeaderEncoding must not be Unicode");
287 return headerEncoding;
291 throw new HttpException ("headers have already been sent");
293 throw new ArgumentNullException ("HeaderEncoding");
294 if (value == Encoding.Unicode)
295 throw new HttpException ("HeaderEncoding must not be Unicode");
296 headerEncoding = value;
300 public bool IsClientConnected {
302 if (WorkerRequest == null)
303 return true; // yep that's true
305 return WorkerRequest.IsClientConnected ();
309 public bool IsRequestBeingRedirected {
310 get { return is_request_being_redirected; }
313 public TextWriter Output {
316 writer = new HttpWriter (this);
322 public Stream OutputStream {
324 return output_stream;
328 public string RedirectLocation {
330 return redirect_location;
334 redirect_location = value;
338 public string Status {
339 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
342 int p = value.IndexOf (' ');
344 throw new HttpException ("Invalid format for the Status property");
346 string s = value.Substring (0, p);
349 if (!Int32.TryParse (s, out status_code))
350 throw new HttpException ("Invalid format for the Status property");
354 status_code = Int32.Parse (s);
356 throw new HttpException ("Invalid format for the Status property");
360 status_description = value.Substring (p+1);
364 public int StatusCode {
371 throw new HttpException ("headers have already been sent");
374 status_description = null;
378 public string StatusDescription {
380 if (status_description == null)
381 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
383 return status_description;
388 throw new HttpException ("headers have already been sent");
390 status_description = value;
394 public bool SuppressContent {
396 return suppress_content;
400 suppress_content = value;
405 [MonoTODO ("Not implemented")]
406 public void AddCacheDependency (CacheDependency[] dependencies)
408 throw new NotImplementedException ();
411 [MonoTODO ("Not implemented")]
412 public void AddCacheItemDependencies (string[] cacheKeys)
414 throw new NotImplementedException ();
417 [MonoTODO("Currently does nothing")]
418 public void AddCacheItemDependencies (ArrayList cacheKeys)
420 // TODO: talk to jackson about the cache
423 [MonoTODO("Currently does nothing")]
424 public void AddCacheItemDependency (string cacheKey)
426 // TODO: talk to jackson about the cache
429 public void AddFileDependencies (ArrayList filenames)
431 if (filenames == null || filenames.Count == 0)
433 FileDependenciesArray.AddRange (filenames);
436 public void AddFileDependencies (string[] filenames)
438 if (filenames == null || filenames.Length == 0)
440 FileDependenciesArray.AddRange (filenames);
444 public void AddFileDependency (string filename)
446 if (filename == null || filename == String.Empty)
448 FileDependenciesArray.Add (filename);
451 public void AddHeader (string name, string value)
453 AppendHeader (name, value);
456 public void AppendCookie (HttpCookie cookie)
458 Cookies.Add (cookie);
463 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
466 public void AppendHeader (string name, string value)
469 throw new HttpException ("headers have been already sent");
472 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
473 content_length = (long) UInt64.Parse (value);
479 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
485 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
486 transfer_encoding = value;
492 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
493 user_cache_control = value;
497 headers.Add (new UnknownResponseHeader (name, value));
500 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
501 public void AppendToLog (string param)
503 Console.Write ("System.Web: ");
504 Console.WriteLine (param);
507 public string ApplyAppPathModifier (string virtualPath)
509 if (virtualPath == null)
512 if (virtualPath == "")
513 return context.Request.RootVirtualDir;
515 if (UrlUtils.IsRelativeUrl (virtualPath)) {
516 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
517 } else if (UrlUtils.IsRooted (virtualPath)) {
518 virtualPath = UrlUtils.Canonic (virtualPath);
521 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
522 string rvd = context.Request.RootVirtualDir;
523 string basevd = rvd.Replace (app_path_mod, "");
525 if (!StrUtils.StartsWith (virtualPath, basevd))
528 virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
534 public void BinaryWrite (byte [] buffer)
536 output_stream.Write (buffer, 0, buffer.Length);
539 internal void BinaryWrite (byte [] buffer, int start, int len)
541 output_stream.Write (buffer, start, len);
549 public void ClearContent ()
551 output_stream.Clear ();
555 public void ClearHeaders ()
558 throw new HttpException ("headers have been already sent");
560 // Reset the special case headers.
562 content_type = "text/html";
563 transfer_encoding = null;
564 user_cache_control = null;
568 internal bool HeadersSent {
578 if (WorkerRequest != null)
579 WorkerRequest.CloseConnection ();
588 if (context.TimeoutPossible) {
589 Thread.CurrentThread.Abort (FlagEnd);
591 // If this is called from an async event, signal the completion
593 HttpApplication app_instance = context.ApplicationInstance;
594 if (app_instance != null)
595 app_instance.CompleteRequest ();
602 // Transfer-Encoding (chunked)
604 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
611 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
612 else if (transfer_encoding != null)
613 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
616 UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
617 DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
618 write_headers.Add (date_header);
621 cached_response.DateHeader = date_header;
623 if (redirect_location != null)
624 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
628 // If Content-Length is set.
630 if (content_length >= 0) {
631 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
632 content_length.ToString (CultureInfo.InvariantCulture)));
633 } else if (BufferOutput) {
636 // If we are buffering and this is the last flush, not a middle-flush,
637 // we know the content-length.
639 content_length = output_stream.total;
640 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
641 content_length.ToString (CultureInfo.InvariantCulture)));
644 // We are buffering, and this is a flush in the middle.
645 // If we are not chunked, we need to set "Connection: close".
648 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
653 // If the content-length is not set, and we are not buffering, we must
657 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
663 // Cache Control, the cache policy takes precedence over the cache_control property.
665 if (cache_policy != null)
666 cache_policy.SetHeaders (this, headers);
668 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
673 if (content_type != null){
674 string header = content_type;
676 if (charset_set || header == "text/plain" || header == "text/html") {
677 if (header.IndexOf ("charset=") == -1) {
678 if (charset == null || charset == "")
679 charset = ContentEncoding.HeaderName;
680 header += "; charset=" + charset;
684 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
687 if (cookies != null && cookies.Count != 0){
688 int n = cookies.Count;
689 for (int i = 0; i < n; i++)
690 write_headers.Add (cookies.Get (i).GetCookieHeader ());
692 // For J2EE Portal support emulate cookies by storing them in the session.
693 context.Request.SetSessionCookiesForPortal (cookies);
698 internal void WriteHeaders (bool final_flush)
703 if (cached_response != null)
704 cached_response.SetHeaders (headers);
709 if (context != null) {
710 HttpApplication app_instance = context.ApplicationInstance;
711 if (app_instance != null)
712 app_instance.TriggerPreSendRequestHeaders ();
715 // If this page is cached use the cached headers
716 // instead of the standard headers
717 ArrayList write_headers = headers;
718 if (cached_headers != null)
719 write_headers = cached_headers;
721 AddHeadersNoCache (write_headers, final_flush);
723 if (WorkerRequest != null)
724 WorkerRequest.SendStatus (status_code, StatusDescription);
726 if (WorkerRequest != null) {
727 foreach (BaseResponseHeader header in write_headers){
728 header.SendContent (WorkerRequest);
734 internal void DoFilter (bool close)
736 if (output_stream.HaveFilter && context != null && context.Error == null)
737 output_stream.ApplyFilter (close);
740 internal void Flush (bool final_flush)
742 DoFilter (final_flush);
744 if (final_flush || status_code != 200)
748 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
749 if (suppress_content || head) {
752 output_stream.Clear ();
753 if (WorkerRequest != null)
754 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
759 WriteHeaders (final_flush);
761 if (context != null) {
762 HttpApplication app_instance = context.ApplicationInstance;
763 if (app_instance != null)
764 app_instance.TriggerPreSendRequestContent ();
768 MemoryStream ms = output_stream.GetData ();
769 cached_response.ContentLength = (int) ms.Length;
770 cached_response.SetData (ms.GetBuffer ());
773 if (WorkerRequest != null)
774 output_stream.Flush (WorkerRequest, final_flush);
782 public void Pics (string value)
784 AppendHeader ("PICS-Label", value);
787 public void Redirect (string url)
789 Redirect (url, true);
792 public void Redirect (string url, bool endResponse)
795 throw new HttpException ("Headers have already been sent");
798 is_request_being_redirected = true;
804 url = ApplyAppPathModifier (url);
805 redirect_location = url;
807 // Text for browsers that can't handle location header
808 Write ("<html><head><title>Object moved</title></head><body>\r\n");
809 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
810 Write ("</body><html>\r\n");
815 is_request_being_redirected = false;
819 public static void RemoveOutputCacheItem (string path)
822 throw new ArgumentNullException ("path");
824 if (path.Length == 0)
828 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
830 HttpRuntime.InternalCache.Remove (path);
833 public void SetCookie (HttpCookie cookie)
835 AppendCookie (cookie);
838 public void Write (char ch)
843 public void Write (object obj)
848 Output.Write (obj.ToString ());
851 public void Write (string s)
856 public void Write (char [] buffer, int index, int count)
858 Output.Write (buffer, index, count);
861 internal void WriteFile (FileStream fs, long offset, long size)
863 byte [] buffer = new byte [32*1024];
866 fs.Position = offset;
870 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
872 output_stream.Write (buffer, 0, n);
876 public void WriteFile (string filename)
878 WriteFile (filename, false);
881 public void WriteFile (string filename, bool readIntoMemory)
883 if (filename == null)
884 throw new ArgumentNullException ("filename");
887 using (FileStream fs = File.OpenRead (filename))
888 WriteFile (fs, 0, fs.Length);
890 FileInfo fi = new FileInfo (filename);
891 output_stream.WriteFile (filename, 0, fi.Length);
896 output_stream.ApplyFilter (false);
901 public void WriteFile (IntPtr fileHandle, long offset, long size) {
902 throw new PlatformNotSupportedException("IntPtr not supported");
905 public void WriteFile (IntPtr fileHandle, long offset, long size)
908 throw new ArgumentNullException ("offset can not be negative");
910 throw new ArgumentNullException ("size can not be negative");
915 // Note: this .ctor will throw a SecurityException if the caller
916 // doesn't have the UnmanagedCode permission
917 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
918 WriteFile (fs, offset, size);
922 output_stream.ApplyFilter (false);
927 public void WriteFile (string filename, long offset, long size)
929 if (filename == null)
930 throw new ArgumentNullException ("filename");
932 throw new ArgumentNullException ("offset can not be negative");
934 throw new ArgumentNullException ("size can not be negative");
939 FileStream fs = File.OpenRead (filename);
940 WriteFile (fs, offset, size);
945 output_stream.ApplyFilter (false);
949 [MonoTODO ("Not implemented")]
950 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
952 throw new NotImplementedException ();
956 // Like WriteFile, but never buffers, so we manually Flush here
958 public void TransmitFile (string filename)
960 if (filename == null)
961 throw new ArgumentNullException ("filename");
963 TransmitFile (filename, false);
966 internal void TransmitFile (string filename, bool final_flush)
968 FileInfo fi = new FileInfo (filename);
969 using (Stream s = fi.OpenRead ()); // Just check if we can read.
970 output_stream.WriteFile (filename, 0, fi.Length);
971 output_stream.ApplyFilter (final_flush);
976 #region Session state support
977 internal void SetAppPathModifier (string app_modifier)
979 app_path_mod = app_modifier;
983 #region Cache Support
984 internal void SetCachedHeaders (ArrayList headers)
986 cached_headers = headers;
989 internal bool IsCached {
991 return cached_response != null;
995 public HttpCachePolicy Cache {
997 if (cache_policy == null) {
998 cache_policy = new HttpCachePolicy ();
999 cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
1002 return cache_policy;
1006 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
1008 if (e.Cacheability >= HttpCacheability.Server && !IsCached)
1009 cached_response = new CachedRawResponse (cache_policy);
1010 else if (e.Cacheability <= HttpCacheability.Private)
1011 cached_response = null;
1014 internal CachedRawResponse GetCachedResponse ()
1016 cached_response.StatusCode = StatusCode;
1017 cached_response.StatusDescription = StatusDescription;
1018 return cached_response;
1022 // This is one of the old ASP compatibility methods, the real cache
1023 // control is in the Cache property, and this is a second class citizen
1025 public string CacheControl {
1027 if (value == null || value == "") {
1028 Cache.SetCacheability (HttpCacheability.NoCache);
1029 user_cache_control = null;
1030 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1031 Cache.SetCacheability (HttpCacheability.Public);
1032 user_cache_control = "public";
1033 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1034 Cache.SetCacheability (HttpCacheability.Private);
1035 user_cache_control = "private";
1036 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1037 Cache.SetCacheability (HttpCacheability.NoCache);
1038 user_cache_control = "no-cache";
1040 throw new ArgumentException ("CacheControl property only allows `public', " +
1041 "`private' or no-cache, for different uses, use " +
1042 "Response.AppendHeader");
1045 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1049 internal int GetOutputByteCount ()
1051 return output_stream.GetTotalLength ();
1054 internal void ReleaseResources ()
1056 output_stream.ReleaseResources (true);
1057 output_stream = null;