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.Globalization;
40 using System.Security.Permissions;
42 namespace System.Web {
44 // CAS - no InheritanceDemand here as the class is sealed
45 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46 public sealed class HttpResponse {
47 internal HttpWorkerRequest WorkerRequest;
48 internal HttpResponseStream output_stream;
49 internal bool buffer = true;
53 HttpCachePolicy cache_policy;
55 HttpCookieCollection cookies;
57 int status_code = 200;
58 string status_description = "OK";
60 string content_type = "text/html";
63 CachedRawResponse cached_response;
64 string cache_control = "private";
65 string redirect_location;
68 // Negative Content-Length means we auto-compute the size of content-length
69 // can be overwritten with AppendHeader ("Content-Length", value)
71 long content_length = -1;
74 // The list of the headers that we will send back to the client, except
75 // the headers that we compute here.
77 ArrayList headers = new ArrayList ();
79 ArrayList cached_headers;
82 // Transfer encoding state
84 string transfer_encoding;
85 internal bool use_chunked;
88 internal bool suppress_content;
98 internal object FlagEnd = new object ();
100 internal HttpResponse ()
102 output_stream = new HttpResponseStream (this);
105 public HttpResponse (TextWriter writer) : this ()
107 this.writer = writer;
110 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
112 WorkerRequest = worker_request;
113 this.context = context;
115 if (worker_request != null)
116 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
119 internal TextWriter SetTextWriter (TextWriter writer)
121 TextWriter prev = writer;
123 this.writer = writer;
138 public bool BufferOutput {
149 // Use the default from <globalization> section if the client has not set the encoding
151 public Encoding ContentEncoding {
153 if (encoding == null) {
154 if (context != null) {
155 string client_content_type = context.Request.ContentType;
156 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
157 if (parameter != null) {
159 // Do what the #1 web server does
160 encoding = Encoding.GetEncoding (parameter);
165 if (encoding == null)
166 encoding = WebEncoding.ResponseEncoding;
173 throw new ArgumentException ("ContentEncoding can not be null");
176 HttpWriter http_writer = writer as HttpWriter;
177 if (http_writer != null)
178 http_writer.SetEncoding (encoding);
182 public string ContentType {
188 content_type = value;
192 public string Charset {
195 charset = ContentEncoding.WebName;
206 public HttpCookieCollection Cookies {
209 cookies = new HttpCookieCollection (true, false);
216 if (cache_policy == null)
219 return cache_policy.ExpireMinutes ();
223 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
227 public DateTime ExpiresAbsolute {
229 return Cache.Expires;
233 Cache.SetExpires (value);
237 public Stream Filter {
239 if (WorkerRequest == null)
242 return output_stream.Filter;
246 output_stream.Filter = value;
251 public Encoding HeaderEncoding {
252 get { throw new NotImplementedException (); }
255 throw new ArgumentNullException ("HeaderEncoding");
256 throw new NotImplementedException ();
260 public bool IsClientConnected {
262 if (WorkerRequest == null)
263 return true; // yep that's true
265 return WorkerRequest.IsClientConnected ();
270 public bool IsRequestBeingRedirected {
271 get { throw new NotImplementedException (); }
274 public TextWriter Output {
277 writer = new HttpWriter (this);
283 public Stream OutputStream {
285 return output_stream;
289 public string RedirectLocation {
291 return redirect_location;
295 redirect_location = value;
299 public string Status {
301 return String.Format ("{0} {1}", status_code, StatusDescription);
305 int p = value.IndexOf (' ');
307 throw new HttpException ("Invalid format for the Status property");
309 string s = value.Substring (0, p);
312 if (!Int32.TryParse (s, out status_code))
313 throw new HttpException ("Invalid format for the Status property");
317 status_code = Int32.Parse (s);
319 throw new HttpException ("Invalid format for the Status property");
323 status_description = value.Substring (p+1);
327 public int StatusCode {
334 throw new HttpException ("headers have already been sent");
337 status_description = null;
341 public string StatusDescription {
343 if (status_description == null)
344 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
346 return status_description;
351 throw new HttpException ("headers have already been sent");
353 status_description = value;
357 public bool SuppressContent {
359 return suppress_content;
363 suppress_content = value;
368 public void AddCacheDependency (CacheDependency[] dependencies)
370 throw new NotImplementedException ();
374 public void AddCacheItemDependencies (string[] cacheKeys)
376 throw new NotImplementedException ();
380 public void AddCacheItemDependencies (ArrayList cacheKeys)
382 // TODO: talk to jackson about the cache
386 public void AddCacheItemDependency (string cacheKey)
388 // TODO: talk to jackson about the cache
392 public void AddFileDependencies (ArrayList filenames)
394 // TODO: talk to jackson about the cache
398 public void AddFileDependencies (string[] filenames)
400 throw new NotImplementedException ();
404 public void AddFileDependency (string filename)
406 // TODO: talk to jackson about the cache
409 public void AddHeader (string name, string value)
411 AppendHeader (name, value);
414 public void AppendCookie (HttpCookie cookie)
416 Cookies.Add (cookie);
421 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
424 public void AppendHeader (string name, string value)
427 throw new HttpException ("headers have been already sent");
429 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
430 content_length = (long) UInt64.Parse (value);
435 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
440 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
441 transfer_encoding = value;
446 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
447 CacheControl = value;
451 headers.Add (new UnknownResponseHeader (name, value));
454 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
455 public void AppendToLog (string param)
457 Console.Write ("System.Web: ");
458 Console.WriteLine (param);
461 public string ApplyAppPathModifier (string virtualPath)
463 if (virtualPath == null)
466 if (virtualPath == "")
467 return context.Request.RootVirtualDir;
469 if (UrlUtils.IsRelativeUrl (virtualPath)) {
470 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
471 } else if (UrlUtils.IsRooted (virtualPath)) {
472 virtualPath = UrlUtils.Canonic (virtualPath);
475 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
476 string rvd = context.Request.RootVirtualDir;
477 string basevd = rvd.Replace (app_path_mod, "");
479 if (!StrUtils.StartsWith (virtualPath, basevd))
482 virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
488 public void BinaryWrite (byte [] buffer)
490 output_stream.Write (buffer, 0, buffer.Length);
493 internal void BinaryWrite (byte [] buffer, int start, int len)
495 output_stream.Write (buffer, start, len);
503 public void ClearContent ()
505 output_stream.Clear ();
508 public void ClearHeaders ()
511 throw new HttpException ("headers have been already sent");
513 // Reset the special case headers.
515 content_type = "text/html";
516 transfer_encoding = null;
517 cache_control = "private";
521 internal bool HeadersSent {
531 if (WorkerRequest != null)
532 WorkerRequest.CloseConnection ();
538 if (context.TimeoutPossible) {
539 Thread.CurrentThread.Abort (FlagEnd);
541 // If this is called from an async event, signal the completion
543 context.ApplicationInstance.CompleteRequest ();
550 // Transfer-Encoding (chunked)
552 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
558 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
559 else if (transfer_encoding != null)
560 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
562 UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
563 DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
564 write_headers.Add (date_header);
567 cached_response.DateHeader = date_header;
569 if (redirect_location != null)
570 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
573 // If Content-Length is set.
575 if (content_length >= 0){
576 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
577 content_length.ToString (CultureInfo.InvariantCulture)));
581 // If we are buffering and this is the last flush, not a middle-flush,
582 // we know the content-length.
584 content_length = output_stream.total;
585 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
586 content_length.ToString (CultureInfo.InvariantCulture)));
589 // We are buffering, and this is a flush in the middle.
590 // If we are not chunked, we need to set "Connection: close".
593 Console.WriteLine ("Setting to close2");
594 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
599 // If the content-length is not set, and we are not buffering, we must
603 Console.WriteLine ("Setting to close");
604 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
609 // Cache Control, the cache policy takes precedence over the cache_control property.
611 if (cache_policy != null)
612 cache_policy.SetHeaders (this, headers);
614 write_headers.Add (new UnknownResponseHeader ("Cache-Control", cache_control));
619 if (content_type != null){
620 string header = content_type;
622 if (charset_set || header == "text/plain" || header == "text/html") {
623 if (header.IndexOf ("charset=") == -1) {
624 if (charset == null || charset == "")
625 charset = ContentEncoding.HeaderName;
626 header += "; charset=" + charset;
630 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
633 if (cookies != null && cookies.Count != 0){
634 int n = cookies.Count;
635 for (int i = 0; i < n; i++)
636 write_headers.Add (cookies.Get (i).GetCookieHeader ());
641 internal void WriteHeaders (bool final_flush)
646 if (WorkerRequest != null)
647 WorkerRequest.SendStatus (status_code, StatusDescription);
649 if (cached_response != null)
650 cached_response.SetHeaders (headers);
652 // If this page is cached use the cached headers
653 // instead of the standard headers
654 ArrayList write_headers = headers;
655 if (cached_headers != null)
656 write_headers = cached_headers;
658 AddHeadersNoCache (write_headers, final_flush);
663 if (context != null) {
664 HttpApplication app_instance = context.ApplicationInstance;
665 if (app_instance != null)
666 app_instance.TriggerPreSendRequestHeaders ();
668 if (WorkerRequest != null) {
669 foreach (BaseResponseHeader header in write_headers){
670 header.SendContent (WorkerRequest);
676 internal void DoFilter (bool close)
678 if (output_stream.HaveFilter && context != null && context.Error == null)
679 output_stream.ApplyFilter (close);
682 internal void Flush (bool final_flush)
684 DoFilter (final_flush);
686 if (final_flush || status_code != 200)
690 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
691 if (suppress_content || head) {
694 output_stream.Clear ();
695 if (WorkerRequest != null)
696 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
701 WriteHeaders (final_flush);
703 if (context != null) {
704 HttpApplication app_instance = context.ApplicationInstance;
705 if (app_instance != null)
706 app_instance.TriggerPreSendRequestContent ();
710 MemoryStream ms = output_stream.GetData ();
711 cached_response.ContentLength = (int) ms.Length;
712 cached_response.SetData (ms.GetBuffer ());
715 if (WorkerRequest != null)
716 output_stream.Flush (WorkerRequest, final_flush);
724 public void Pics (string value)
726 AppendHeader ("PICS-Label", value);
729 public void Redirect (string url)
731 Redirect (url, true);
734 public void Redirect (string url, bool endResponse)
737 throw new HttpException ("header have been already sent");
743 url = ApplyAppPathModifier (url);
744 headers.Add (new UnknownResponseHeader ("Location", url));
746 // Text for browsers that can't handle location header
747 Write ("<html><head><title>Object moved</title></head><body>\r\n");
748 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
749 Write ("</body><html>\r\n");
755 public static void RemoveOutputCacheItem (string path)
758 throw new ArgumentNullException ("path");
760 if (path.Length == 0)
764 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
766 HttpRuntime.Cache.Remove (path);
769 public void SetCookie (HttpCookie cookie)
771 AppendCookie (cookie);
774 public void Write (char ch)
779 public void Write (object obj)
784 Output.Write (obj.ToString ());
787 public void Write (string s)
792 public void Write (char [] buffer, int index, int count)
794 Output.Write (buffer, index, count);
797 internal void WriteFile (FileStream fs, long offset, long size)
799 byte [] buffer = new byte [32*1024];
802 fs.Position = offset;
806 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
808 output_stream.Write (buffer, 0, n);
812 public void WriteFile (string filename)
814 WriteFile (filename, false);
817 public void WriteFile (string filename, bool readIntoMemory)
819 if (filename == null)
820 throw new ArgumentNullException ("filename");
823 using (FileStream fs = File.OpenRead (filename))
824 WriteFile (fs, 0, fs.Length);
826 FileInfo fi = new FileInfo (filename);
827 output_stream.WriteFile (filename, 0, fi.Length);
832 output_stream.ApplyFilter (false);
837 public void WriteFile (IntPtr fileHandle, long offset, long size) {
838 throw new NotSupportedException("IntPtr not supported");
841 public void WriteFile (IntPtr fileHandle, long offset, long size)
844 throw new ArgumentNullException ("offset can not be negative");
846 throw new ArgumentNullException ("size can not be negative");
851 // Note: this .ctor will throw a SecurityException if the caller
852 // doesn't have the UnmanagedCode permission
853 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
854 WriteFile (fs, offset, size);
858 output_stream.ApplyFilter (false);
863 public void WriteFile (string filename, long offset, long size)
865 if (filename == null)
866 throw new ArgumentNullException ("filename");
868 throw new ArgumentNullException ("offset can not be negative");
870 throw new ArgumentNullException ("size can not be negative");
875 FileStream fs = File.OpenRead (filename);
876 WriteFile (fs, offset, size);
881 output_stream.ApplyFilter (false);
886 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
888 throw new NotImplementedException ();
892 // Like WriteFile, but never buffers, so we manually Flush here
894 public void TransmitFile (string filename)
896 if (filename == null)
897 throw new ArgumentNullException ("filename");
899 TransmitFile (filename, false);
902 internal void TransmitFile (string filename, bool final_flush)
904 FileInfo fi = new FileInfo (filename);
905 output_stream.WriteFile (filename, 0, fi.Length);
906 output_stream.ApplyFilter (final_flush);
911 #region Session state support
912 internal void SetAppPathModifier (string app_modifier)
914 app_path_mod = app_modifier;
918 #region Cache Support
919 internal void SetCachedHeaders (ArrayList headers)
921 cached_headers = headers;
924 internal bool IsCached {
926 return cached_response != null;
930 public HttpCachePolicy Cache {
932 if (cache_policy == null) {
933 cache_policy = new HttpCachePolicy ();
934 cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
941 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
943 if (e.Cacheability >= HttpCacheability.Server && !IsCached)
944 cached_response = new CachedRawResponse (cache_policy);
945 else if (e.Cacheability <= HttpCacheability.Private)
946 cached_response = null;
949 internal CachedRawResponse GetCachedResponse ()
951 cached_response.StatusCode = StatusCode;
952 cached_response.StatusDescription = StatusDescription;
953 return cached_response;
957 // This is one of the old ASP compatibility methods, the real cache
958 // control is in the Cache property, and this is a second class citizen
960 public string CacheControl {
962 if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0)
963 Cache.SetCacheability (HttpCacheability.Public);
964 else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0)
965 Cache.SetCacheability (HttpCacheability.Private);
966 else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0)
967 Cache.SetCacheability (HttpCacheability.NoCache);
969 throw new ArgumentException ("CacheControl property only allows `public', " +
970 "`private' or no-cache, for different uses, use " +
971 "Response.AppendHeader");
972 cache_control = value;
976 if ((cache_control == null) && (cache_policy != null)) {
977 switch (Cache.Cacheability) {
978 case (HttpCacheability)0:
979 case HttpCacheability.NoCache:
981 case HttpCacheability.Private:
982 case HttpCacheability.Server:
983 case HttpCacheability.ServerAndPrivate:
985 case HttpCacheability.Public:
988 throw new Exception ("Unknown internal state: " + Cache.Cacheability);
991 return cache_control;
996 internal int GetOutputByteCount ()
998 return output_stream.GetTotalLength ();
1001 internal void ReleaseResources ()
1003 output_stream.ReleaseResources (true);
1004 output_stream = null;