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.Configuration;
44 using System.Web.SessionState;
46 namespace System.Web {
48 // CAS - no InheritanceDemand here as the class is sealed
49 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50 public sealed partial class HttpResponse {
51 internal HttpWorkerRequest WorkerRequest;
52 internal HttpResponseStream output_stream;
53 internal bool buffer = true;
55 ArrayList fileDependencies;
59 HttpCachePolicy cache_policy;
61 HttpCookieCollection cookies;
63 int status_code = 200;
64 string status_description = "OK";
66 string content_type = "text/html";
69 CachedRawResponse cached_response;
70 string user_cache_control = "private";
71 string redirect_location;
74 // Negative Content-Length means we auto-compute the size of content-length
75 // can be overwritten with AppendHeader ("Content-Length", value)
77 long content_length = -1;
80 // The list of the headers that we will send back to the client, except
81 // the headers that we compute here.
83 ArrayList headers = new ArrayList ();
85 ArrayList cached_headers;
88 // Transfer encoding state
90 string transfer_encoding;
91 internal bool use_chunked;
94 internal bool suppress_content;
102 bool is_request_being_redirected;
103 Encoding headerEncoding;
106 internal HttpResponse ()
108 output_stream = new HttpResponseStream (this);
111 public HttpResponse (TextWriter writer) : this ()
113 this.writer = writer;
116 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
118 WorkerRequest = worker_request;
119 this.context = context;
122 if (worker_request != null)
123 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
127 internal TextWriter SetTextWriter (TextWriter writer)
129 TextWriter prev = this.writer;
130 this.writer = writer;
134 internal string[] FileDependencies {
136 if (fileDependencies == null || fileDependencies.Count == 0)
137 return new string[0] {};
138 return (string[]) fileDependencies.ToArray (typeof (string));
142 ArrayList FileDependenciesArray {
144 if (fileDependencies == null)
145 fileDependencies = new ArrayList ();
146 return fileDependencies;
160 public bool BufferOutput {
171 // Use the default from <globalization> section if the client has not set the encoding
173 public Encoding ContentEncoding {
175 if (encoding == null) {
176 if (context != null) {
177 string client_content_type = context.Request.ContentType;
178 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
179 if (parameter != null) {
181 // Do what the #1 web server does
182 encoding = Encoding.GetEncoding (parameter);
187 if (encoding == null)
188 encoding = WebEncoding.ResponseEncoding;
195 throw new ArgumentException ("ContentEncoding can not be null");
198 HttpWriter http_writer = writer as HttpWriter;
199 if (http_writer != null)
200 http_writer.SetEncoding (encoding);
204 public string ContentType {
210 content_type = value;
214 public string Charset {
217 charset = ContentEncoding.WebName;
228 public HttpCookieCollection Cookies {
231 cookies = new HttpCookieCollection (true, false);
238 if (cache_policy == null)
241 return cache_policy.ExpireMinutes ();
245 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
249 public DateTime ExpiresAbsolute {
251 return Cache.Expires;
255 Cache.SetExpires (value);
259 public Stream Filter {
261 if (WorkerRequest == null)
264 return output_stream.Filter;
268 output_stream.Filter = value;
272 public Encoding HeaderEncoding {
274 if (headerEncoding == null) {
275 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
278 headerEncoding = Encoding.UTF8;
280 headerEncoding = gs.ResponseHeaderEncoding;
281 if (headerEncoding == Encoding.Unicode)
282 throw new HttpException ("HeaderEncoding must not be Unicode");
285 return headerEncoding;
289 throw new HttpException ("headers have already been sent");
291 throw new ArgumentNullException ("HeaderEncoding");
292 if (value == Encoding.Unicode)
293 throw new HttpException ("HeaderEncoding must not be Unicode");
294 headerEncoding = value;
298 public bool IsClientConnected {
300 if (WorkerRequest == null)
301 return true; // yep that's true
303 return WorkerRequest.IsClientConnected ();
307 public bool IsRequestBeingRedirected {
308 get { return is_request_being_redirected; }
311 public TextWriter Output {
314 writer = new HttpWriter (this);
320 public Stream OutputStream {
322 return output_stream;
326 public string RedirectLocation {
328 return redirect_location;
332 redirect_location = value;
336 public string Status {
337 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
340 int p = value.IndexOf (' ');
342 throw new HttpException ("Invalid format for the Status property");
344 string s = value.Substring (0, p);
347 if (!Int32.TryParse (s, out status_code))
348 throw new HttpException ("Invalid format for the Status property");
352 status_code = Int32.Parse (s);
354 throw new HttpException ("Invalid format for the Status property");
358 status_description = value.Substring (p+1);
362 public int StatusCode {
369 throw new HttpException ("headers have already been sent");
372 status_description = null;
376 public string StatusDescription {
378 if (status_description == null)
379 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
381 return status_description;
386 throw new HttpException ("headers have already been sent");
388 status_description = value;
392 public bool SuppressContent {
394 return suppress_content;
398 suppress_content = value;
403 [MonoTODO ("Not implemented")]
404 public void AddCacheDependency (CacheDependency[] dependencies)
406 throw new NotImplementedException ();
409 [MonoTODO ("Not implemented")]
410 public void AddCacheItemDependencies (string[] cacheKeys)
412 throw new NotImplementedException ();
415 [MonoTODO("Currently does nothing")]
416 public void AddCacheItemDependencies (ArrayList cacheKeys)
418 // TODO: talk to jackson about the cache
421 [MonoTODO("Currently does nothing")]
422 public void AddCacheItemDependency (string cacheKey)
424 // TODO: talk to jackson about the cache
427 public void AddFileDependencies (ArrayList filenames)
429 if (filenames == null || filenames.Count == 0)
431 FileDependenciesArray.AddRange (filenames);
434 public void AddFileDependencies (string[] filenames)
436 if (filenames == null || filenames.Length == 0)
438 FileDependenciesArray.AddRange (filenames);
442 public void AddFileDependency (string filename)
444 if (filename == null || filename == String.Empty)
446 FileDependenciesArray.Add (filename);
449 public void AddHeader (string name, string value)
451 AppendHeader (name, value);
454 public void AppendCookie (HttpCookie cookie)
456 Cookies.Add (cookie);
461 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
464 public void AppendHeader (string name, string value)
467 throw new HttpException ("headers have been already sent");
470 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
471 content_length = (long) UInt64.Parse (value);
477 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
483 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
484 transfer_encoding = value;
490 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
491 user_cache_control = value;
495 headers.Add (new UnknownResponseHeader (name, value));
498 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
499 public void AppendToLog (string param)
501 Console.Write ("System.Web: ");
502 Console.WriteLine (param);
505 public string ApplyAppPathModifier (string virtualPath)
507 if (virtualPath == null)
510 if (virtualPath == "")
511 return context.Request.RootVirtualDir;
513 if (UrlUtils.IsRelativeUrl (virtualPath)) {
514 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
515 } else if (UrlUtils.IsRooted (virtualPath)) {
516 virtualPath = UrlUtils.Canonic (virtualPath);
519 bool cookieless = false;
521 SessionStateSection config = WebConfigurationManager.GetSection ("system.web/sessionState") as SessionStateSection;
522 cookieless = SessionStateModule.IsCookieLess (context, config);
524 SessionConfig config = HttpContext.GetAppConfig ("system.web/sessionState") as SessionConfig;
525 cookieless = config.CookieLess;
530 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
531 if (UrlUtils.HasSessionId (virtualPath))
532 virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
533 return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
539 public void BinaryWrite (byte [] buffer)
541 output_stream.Write (buffer, 0, buffer.Length);
544 internal void BinaryWrite (byte [] buffer, int start, int len)
546 output_stream.Write (buffer, start, len);
554 public void ClearContent ()
556 output_stream.Clear ();
560 public void ClearHeaders ()
563 throw new HttpException ("headers have been already sent");
565 // Reset the special case headers.
567 content_type = "text/html";
568 transfer_encoding = null;
569 user_cache_control = null;
573 internal bool HeadersSent {
583 if (WorkerRequest != null)
584 WorkerRequest.CloseConnection ();
593 if (context.TimeoutPossible) {
594 Thread.CurrentThread.Abort (FlagEnd.Value);
596 // If this is called from an async event, signal the completion
598 HttpApplication app_instance = context.ApplicationInstance;
599 if (app_instance != null)
600 app_instance.CompleteRequest ();
607 // Transfer-Encoding (chunked)
609 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
616 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
617 else if (transfer_encoding != null)
618 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
620 if (redirect_location != null)
621 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
625 // If Content-Length is set.
627 if (content_length >= 0) {
628 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
629 content_length.ToString (CultureInfo.InvariantCulture)));
630 } else if (BufferOutput) {
633 // If we are buffering and this is the last flush, not a middle-flush,
634 // we know the content-length.
636 content_length = output_stream.total;
637 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
638 content_length.ToString (CultureInfo.InvariantCulture)));
641 // We are buffering, and this is a flush in the middle.
642 // If we are not chunked, we need to set "Connection: close".
645 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
650 // If the content-length is not set, and we are not buffering, we must
654 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
660 // Cache Control, the cache policy takes precedence over the cache_control property.
662 if (cache_policy != null)
663 cache_policy.SetHeaders (this, headers);
665 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
670 if (content_type != null){
671 string header = content_type;
673 if (charset_set || header == "text/plain" || header == "text/html") {
674 if (header.IndexOf ("charset=") == -1) {
675 if (charset == null || charset == "")
676 charset = ContentEncoding.HeaderName;
677 header += "; charset=" + charset;
681 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
684 if (cookies != null && cookies.Count != 0){
685 int n = cookies.Count;
686 for (int i = 0; i < n; i++)
687 write_headers.Add (cookies.Get (i).GetCookieHeader ());
689 // For J2EE Portal support emulate cookies by storing them in the session.
690 context.Request.SetSessionCookiesForPortal (cookies);
695 internal void WriteHeaders (bool final_flush)
703 if (context != null) {
704 HttpApplication app_instance = context.ApplicationInstance;
705 if (app_instance != null)
706 app_instance.TriggerPreSendRequestHeaders ();
711 if (cached_response != null)
712 cached_response.SetHeaders (headers);
714 // If this page is cached use the cached headers
715 // instead of the standard headers
716 ArrayList write_headers = headers;
717 if (cached_headers != null)
718 write_headers = cached_headers;
720 AddHeadersNoCache (write_headers, final_flush);
722 if (WorkerRequest != null)
723 WorkerRequest.SendStatus (status_code, StatusDescription);
725 if (WorkerRequest != null) {
726 foreach (BaseResponseHeader header in write_headers){
727 header.SendContent (WorkerRequest);
732 internal void DoFilter (bool close)
734 if (output_stream.HaveFilter && context != null && context.Error == null)
735 output_stream.ApplyFilter (close);
738 internal void Flush (bool final_flush)
740 DoFilter (final_flush);
742 if (final_flush || status_code != 200)
746 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
747 if (suppress_content || head) {
750 output_stream.Clear ();
751 if (WorkerRequest != null)
752 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
757 WriteHeaders (final_flush);
759 if (context != null) {
760 HttpApplication app_instance = context.ApplicationInstance;
761 if (app_instance != null)
762 app_instance.TriggerPreSendRequestContent ();
766 MemoryStream ms = output_stream.GetData ();
767 cached_response.ContentLength = (int) ms.Length;
768 cached_response.SetData (ms.GetBuffer ());
771 if (WorkerRequest != null)
772 output_stream.Flush (WorkerRequest, final_flush);
780 public void Pics (string value)
782 AppendHeader ("PICS-Label", value);
785 public void Redirect (string url)
787 Redirect (url, true);
790 public void Redirect (string url, bool endResponse)
793 throw new HttpException ("Headers have already been sent");
796 is_request_being_redirected = true;
802 url = ApplyAppPathModifier (url);
803 redirect_location = url;
805 // Text for browsers that can't handle location header
806 Write ("<html><head><title>Object moved</title></head><body>\r\n");
807 Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
808 Write ("</body><html>\r\n");
813 is_request_being_redirected = false;
817 public static void RemoveOutputCacheItem (string path)
820 throw new ArgumentNullException ("path");
822 if (path.Length == 0)
826 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
828 HttpRuntime.InternalCache.Remove (path);
831 public void SetCookie (HttpCookie cookie)
833 AppendCookie (cookie);
836 public void Write (char ch)
841 public void Write (object obj)
846 Output.Write (obj.ToString ());
849 public void Write (string s)
854 public void Write (char [] buffer, int index, int count)
856 Output.Write (buffer, index, count);
859 internal void WriteFile (FileStream fs, long offset, long size)
861 byte [] buffer = new byte [32*1024];
864 fs.Position = offset;
868 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
870 output_stream.Write (buffer, 0, n);
874 public void WriteFile (string filename)
876 WriteFile (filename, false);
879 public void WriteFile (string filename, bool readIntoMemory)
881 if (filename == null)
882 throw new ArgumentNullException ("filename");
885 using (FileStream fs = File.OpenRead (filename))
886 WriteFile (fs, 0, fs.Length);
888 FileInfo fi = new FileInfo (filename);
889 output_stream.WriteFile (filename, 0, fi.Length);
894 output_stream.ApplyFilter (false);
899 public void WriteFile (IntPtr fileHandle, long offset, long size) {
900 throw new PlatformNotSupportedException("IntPtr not supported");
903 public void WriteFile (IntPtr fileHandle, long offset, long size)
906 throw new ArgumentNullException ("offset can not be negative");
908 throw new ArgumentNullException ("size can not be negative");
913 // Note: this .ctor will throw a SecurityException if the caller
914 // doesn't have the UnmanagedCode permission
915 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
916 WriteFile (fs, offset, size);
920 output_stream.ApplyFilter (false);
925 public void WriteFile (string filename, long offset, long size)
927 if (filename == null)
928 throw new ArgumentNullException ("filename");
930 throw new ArgumentNullException ("offset can not be negative");
932 throw new ArgumentNullException ("size can not be negative");
937 FileStream fs = File.OpenRead (filename);
938 WriteFile (fs, offset, size);
943 output_stream.ApplyFilter (false);
947 [MonoTODO ("Not implemented")]
948 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
950 throw new NotImplementedException ();
954 // Like WriteFile, but never buffers, so we manually Flush here
956 public void TransmitFile (string filename)
958 if (filename == null)
959 throw new ArgumentNullException ("filename");
961 TransmitFile (filename, false);
964 internal void TransmitFile (string filename, bool final_flush)
966 FileInfo fi = new FileInfo (filename);
967 using (Stream s = fi.OpenRead ()); // Just check if we can read.
968 output_stream.WriteFile (filename, 0, fi.Length);
969 output_stream.ApplyFilter (final_flush);
974 internal void TransmitFile (VirtualFile vf)
976 TransmitFile (vf, false);
979 internal void TransmitFile (VirtualFile vf, bool final_flush)
982 throw new ArgumentNullException ("vf");
984 using (Stream s = vf.Open ()) {
986 byte[] buf = new byte [len];
987 int readBytes = s.Read (buf, 0, (int) len);
988 output_stream.Write (buf, 0, readBytes);
989 output_stream.ApplyFilter (final_flush);
995 #region Session state support
996 internal void SetAppPathModifier (string app_modifier)
998 app_path_mod = app_modifier;
1002 #region Cache Support
1003 internal void SetCachedHeaders (ArrayList headers)
1005 cached_headers = headers;
1008 internal bool IsCached {
1010 return cached_response != null;
1014 public HttpCachePolicy Cache {
1016 if (cache_policy == null) {
1017 cache_policy = new HttpCachePolicy ();
1018 cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
1021 return cache_policy;
1025 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
1027 if (e.Cacheability >= HttpCacheability.Server && !IsCached)
1028 cached_response = new CachedRawResponse (cache_policy);
1029 else if (e.Cacheability <= HttpCacheability.Private)
1030 cached_response = null;
1033 internal CachedRawResponse GetCachedResponse ()
1035 cached_response.StatusCode = StatusCode;
1036 cached_response.StatusDescription = StatusDescription;
1037 return cached_response;
1041 // This is one of the old ASP compatibility methods, the real cache
1042 // control is in the Cache property, and this is a second class citizen
1044 public string CacheControl {
1046 if (value == null || value == "") {
1047 Cache.SetCacheability (HttpCacheability.NoCache);
1048 user_cache_control = null;
1049 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1050 Cache.SetCacheability (HttpCacheability.Public);
1051 user_cache_control = "public";
1052 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1053 Cache.SetCacheability (HttpCacheability.Private);
1054 user_cache_control = "private";
1055 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1056 Cache.SetCacheability (HttpCacheability.NoCache);
1057 user_cache_control = "no-cache";
1059 throw new ArgumentException ("CacheControl property only allows `public', " +
1060 "`private' or no-cache, for different uses, use " +
1061 "Response.AppendHeader");
1064 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1068 internal int GetOutputByteCount ()
1070 return output_stream.GetTotalLength ();
1073 internal void ReleaseResources ()
1075 output_stream.ReleaseResources (true);
1076 output_stream = null;
1083 static class FlagEnd
1085 public static readonly object Value = new object ();