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;
44 namespace System.Web {
46 // CAS - no InheritanceDemand here as the class is sealed
47 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48 public sealed partial class HttpResponse {
49 internal HttpWorkerRequest WorkerRequest;
50 internal HttpResponseStream output_stream;
51 internal bool buffer = true;
53 ArrayList fileDependencies;
57 HttpCachePolicy cache_policy;
59 HttpCookieCollection cookies;
61 int status_code = 200;
62 string status_description = "OK";
64 string content_type = "text/html";
67 CachedRawResponse cached_response;
68 string user_cache_control = "private";
69 string redirect_location;
72 // Negative Content-Length means we auto-compute the size of content-length
73 // can be overwritten with AppendHeader ("Content-Length", value)
75 long content_length = -1;
78 // The list of the headers that we will send back to the client, except
79 // the headers that we compute here.
81 ArrayList headers = new ArrayList ();
83 ArrayList cached_headers;
86 // Transfer encoding state
88 string transfer_encoding;
89 internal bool use_chunked;
92 internal bool suppress_content;
100 bool is_request_being_redirected;
101 Encoding headerEncoding;
104 internal HttpResponse ()
106 output_stream = new HttpResponseStream (this);
109 public HttpResponse (TextWriter writer) : this ()
111 this.writer = writer;
114 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
116 WorkerRequest = worker_request;
117 this.context = context;
120 if (worker_request != null)
121 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
125 internal TextWriter SetTextWriter (TextWriter writer)
127 TextWriter prev = this.writer;
128 this.writer = writer;
132 internal string[] FileDependencies {
134 if (fileDependencies == null || fileDependencies.Count == 0)
135 return new string[0] {};
136 return (string[]) fileDependencies.ToArray (typeof (string));
140 ArrayList FileDependenciesArray {
142 if (fileDependencies == null)
143 fileDependencies = new ArrayList ();
144 return fileDependencies;
158 public bool BufferOutput {
169 // Use the default from <globalization> section if the client has not set the encoding
171 public Encoding ContentEncoding {
173 if (encoding == null) {
174 if (context != null) {
175 string client_content_type = context.Request.ContentType;
176 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
177 if (parameter != null) {
179 // Do what the #1 web server does
180 encoding = Encoding.GetEncoding (parameter);
185 if (encoding == null)
186 encoding = WebEncoding.ResponseEncoding;
193 throw new ArgumentException ("ContentEncoding can not be null");
196 HttpWriter http_writer = writer as HttpWriter;
197 if (http_writer != null)
198 http_writer.SetEncoding (encoding);
202 public string ContentType {
208 content_type = value;
212 public string Charset {
215 charset = ContentEncoding.WebName;
226 public HttpCookieCollection Cookies {
229 cookies = new HttpCookieCollection (true, false);
236 if (cache_policy == null)
239 return cache_policy.ExpireMinutes ();
243 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
247 public DateTime ExpiresAbsolute {
249 return Cache.Expires;
253 Cache.SetExpires (value);
257 public Stream Filter {
259 if (WorkerRequest == null)
262 return output_stream.Filter;
266 output_stream.Filter = value;
270 public Encoding HeaderEncoding {
272 if (headerEncoding == null) {
273 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
276 headerEncoding = Encoding.UTF8;
278 headerEncoding = gs.ResponseHeaderEncoding;
279 if (headerEncoding == Encoding.Unicode)
280 throw new HttpException ("HeaderEncoding must not be Unicode");
283 return headerEncoding;
287 throw new HttpException ("headers have already been sent");
289 throw new ArgumentNullException ("HeaderEncoding");
290 if (value == Encoding.Unicode)
291 throw new HttpException ("HeaderEncoding must not be Unicode");
292 headerEncoding = value;
296 public bool IsClientConnected {
298 if (WorkerRequest == null)
299 return true; // yep that's true
301 return WorkerRequest.IsClientConnected ();
305 public bool IsRequestBeingRedirected {
306 get { return is_request_being_redirected; }
309 public TextWriter Output {
312 writer = new HttpWriter (this);
318 public Stream OutputStream {
320 return output_stream;
324 public string RedirectLocation {
326 return redirect_location;
330 redirect_location = value;
334 public string Status {
335 get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
338 int p = value.IndexOf (' ');
340 throw new HttpException ("Invalid format for the Status property");
342 string s = value.Substring (0, p);
345 if (!Int32.TryParse (s, out status_code))
346 throw new HttpException ("Invalid format for the Status property");
350 status_code = Int32.Parse (s);
352 throw new HttpException ("Invalid format for the Status property");
356 status_description = value.Substring (p+1);
360 public int StatusCode {
367 throw new HttpException ("headers have already been sent");
370 status_description = null;
374 public string StatusDescription {
376 if (status_description == null)
377 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
379 return status_description;
384 throw new HttpException ("headers have already been sent");
386 status_description = value;
390 public bool SuppressContent {
392 return suppress_content;
396 suppress_content = value;
401 [MonoTODO ("Not implemented")]
402 public void AddCacheDependency (CacheDependency[] dependencies)
404 throw new NotImplementedException ();
407 [MonoTODO ("Not implemented")]
408 public void AddCacheItemDependencies (string[] cacheKeys)
410 throw new NotImplementedException ();
413 [MonoTODO("Currently does nothing")]
414 public void AddCacheItemDependencies (ArrayList cacheKeys)
416 // TODO: talk to jackson about the cache
419 [MonoTODO("Currently does nothing")]
420 public void AddCacheItemDependency (string cacheKey)
422 // TODO: talk to jackson about the cache
425 public void AddFileDependencies (ArrayList filenames)
427 if (filenames == null || filenames.Count == 0)
429 FileDependenciesArray.AddRange (filenames);
432 public void AddFileDependencies (string[] filenames)
434 if (filenames == null || filenames.Length == 0)
436 FileDependenciesArray.AddRange (filenames);
440 public void AddFileDependency (string filename)
442 if (filename == null || filename == String.Empty)
444 FileDependenciesArray.Add (filename);
447 public void AddHeader (string name, string value)
449 AppendHeader (name, value);
452 public void AppendCookie (HttpCookie cookie)
454 Cookies.Add (cookie);
459 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
462 public void AppendHeader (string name, string value)
465 throw new HttpException ("headers have been already sent");
468 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
469 content_length = (long) UInt64.Parse (value);
475 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
481 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
482 transfer_encoding = value;
488 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
489 user_cache_control = value;
493 headers.Add (new UnknownResponseHeader (name, value));
496 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
497 public void AppendToLog (string param)
499 Console.Write ("System.Web: ");
500 Console.WriteLine (param);
503 public string ApplyAppPathModifier (string virtualPath)
505 if (virtualPath == null)
508 if (virtualPath == "")
509 return context.Request.RootVirtualDir;
511 if (UrlUtils.IsRelativeUrl (virtualPath)) {
512 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
513 } else if (UrlUtils.IsRooted (virtualPath)) {
514 virtualPath = UrlUtils.Canonic (virtualPath);
517 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
518 string rvd = context.Request.RootVirtualDir;
519 string basevd = rvd.Replace (app_path_mod, "");
521 if (!StrUtils.StartsWith (virtualPath, basevd))
524 virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
530 public void BinaryWrite (byte [] buffer)
532 output_stream.Write (buffer, 0, buffer.Length);
535 internal void BinaryWrite (byte [] buffer, int start, int len)
537 output_stream.Write (buffer, start, len);
545 public void ClearContent ()
547 output_stream.Clear ();
551 public void ClearHeaders ()
554 throw new HttpException ("headers have been already sent");
556 // Reset the special case headers.
558 content_type = "text/html";
559 transfer_encoding = null;
560 user_cache_control = null;
564 internal bool HeadersSent {
574 if (WorkerRequest != null)
575 WorkerRequest.CloseConnection ();
584 if (context.TimeoutPossible) {
585 Thread.CurrentThread.Abort (FlagEnd.Value);
587 // If this is called from an async event, signal the completion
589 HttpApplication app_instance = context.ApplicationInstance;
590 if (app_instance != null)
591 app_instance.CompleteRequest ();
598 // Transfer-Encoding (chunked)
600 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
607 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
608 else if (transfer_encoding != null)
609 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
611 if (redirect_location != null)
612 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
616 // If Content-Length is set.
618 if (content_length >= 0) {
619 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
620 content_length.ToString (CultureInfo.InvariantCulture)));
621 } else if (BufferOutput) {
624 // If we are buffering and this is the last flush, not a middle-flush,
625 // we know the content-length.
627 content_length = output_stream.total;
628 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
629 content_length.ToString (CultureInfo.InvariantCulture)));
632 // We are buffering, and this is a flush in the middle.
633 // If we are not chunked, we need to set "Connection: close".
636 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
641 // If the content-length is not set, and we are not buffering, we must
645 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
651 // Cache Control, the cache policy takes precedence over the cache_control property.
653 if (cache_policy != null)
654 cache_policy.SetHeaders (this, headers);
656 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
661 if (content_type != null){
662 string header = content_type;
664 if (charset_set || header == "text/plain" || header == "text/html") {
665 if (header.IndexOf ("charset=") == -1) {
666 if (charset == null || charset == "")
667 charset = ContentEncoding.HeaderName;
668 header += "; charset=" + charset;
672 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
675 if (cookies != null && cookies.Count != 0){
676 int n = cookies.Count;
677 for (int i = 0; i < n; i++)
678 write_headers.Add (cookies.Get (i).GetCookieHeader ());
680 // For J2EE Portal support emulate cookies by storing them in the session.
681 context.Request.SetSessionCookiesForPortal (cookies);
686 internal void WriteHeaders (bool final_flush)
694 if (context != null) {
695 HttpApplication app_instance = context.ApplicationInstance;
696 if (app_instance != null)
697 app_instance.TriggerPreSendRequestHeaders ();
702 if (cached_response != null)
703 cached_response.SetHeaders (headers);
705 // If this page is cached use the cached headers
706 // instead of the standard headers
707 ArrayList write_headers = headers;
708 if (cached_headers != null)
709 write_headers = cached_headers;
711 AddHeadersNoCache (write_headers, final_flush);
713 if (WorkerRequest != null)
714 WorkerRequest.SendStatus (status_code, StatusDescription);
716 if (WorkerRequest != null) {
717 foreach (BaseResponseHeader header in write_headers){
718 header.SendContent (WorkerRequest);
723 internal void DoFilter (bool close)
725 if (output_stream.HaveFilter && context != null && context.Error == null)
726 output_stream.ApplyFilter (close);
729 internal void Flush (bool final_flush)
731 DoFilter (final_flush);
733 if (final_flush || status_code != 200)
737 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
738 if (suppress_content || head) {
741 output_stream.Clear ();
742 if (WorkerRequest != null)
743 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
748 WriteHeaders (final_flush);
750 if (context != null) {
751 HttpApplication app_instance = context.ApplicationInstance;
752 if (app_instance != null)
753 app_instance.TriggerPreSendRequestContent ();
757 MemoryStream ms = output_stream.GetData ();
758 cached_response.ContentLength = (int) ms.Length;
759 cached_response.SetData (ms.GetBuffer ());
762 if (WorkerRequest != null)
763 output_stream.Flush (WorkerRequest, final_flush);
771 public void Pics (string value)
773 AppendHeader ("PICS-Label", value);
776 public void Redirect (string url)
778 Redirect (url, true);
781 public void Redirect (string url, bool endResponse)
784 throw new HttpException ("Headers have already been sent");
787 is_request_being_redirected = true;
793 url = ApplyAppPathModifier (url);
794 redirect_location = url;
796 // Text for browsers that can't handle location header
797 Write ("<html><head><title>Object moved</title></head><body>\r\n");
798 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
799 Write ("</body><html>\r\n");
804 is_request_being_redirected = false;
808 public static void RemoveOutputCacheItem (string path)
811 throw new ArgumentNullException ("path");
813 if (path.Length == 0)
817 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
819 HttpRuntime.InternalCache.Remove (path);
822 public void SetCookie (HttpCookie cookie)
824 AppendCookie (cookie);
827 public void Write (char ch)
832 public void Write (object obj)
837 Output.Write (obj.ToString ());
840 public void Write (string s)
845 public void Write (char [] buffer, int index, int count)
847 Output.Write (buffer, index, count);
850 internal void WriteFile (FileStream fs, long offset, long size)
852 byte [] buffer = new byte [32*1024];
855 fs.Position = offset;
859 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
861 output_stream.Write (buffer, 0, n);
865 public void WriteFile (string filename)
867 WriteFile (filename, false);
870 public void WriteFile (string filename, bool readIntoMemory)
872 if (filename == null)
873 throw new ArgumentNullException ("filename");
876 using (FileStream fs = File.OpenRead (filename))
877 WriteFile (fs, 0, fs.Length);
879 FileInfo fi = new FileInfo (filename);
880 output_stream.WriteFile (filename, 0, fi.Length);
885 output_stream.ApplyFilter (false);
890 public void WriteFile (IntPtr fileHandle, long offset, long size) {
891 throw new PlatformNotSupportedException("IntPtr not supported");
894 public void WriteFile (IntPtr fileHandle, long offset, long size)
897 throw new ArgumentNullException ("offset can not be negative");
899 throw new ArgumentNullException ("size can not be negative");
904 // Note: this .ctor will throw a SecurityException if the caller
905 // doesn't have the UnmanagedCode permission
906 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
907 WriteFile (fs, offset, size);
911 output_stream.ApplyFilter (false);
916 public void WriteFile (string filename, long offset, long size)
918 if (filename == null)
919 throw new ArgumentNullException ("filename");
921 throw new ArgumentNullException ("offset can not be negative");
923 throw new ArgumentNullException ("size can not be negative");
928 FileStream fs = File.OpenRead (filename);
929 WriteFile (fs, offset, size);
934 output_stream.ApplyFilter (false);
938 [MonoTODO ("Not implemented")]
939 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
941 throw new NotImplementedException ();
945 // Like WriteFile, but never buffers, so we manually Flush here
947 public void TransmitFile (string filename)
949 if (filename == null)
950 throw new ArgumentNullException ("filename");
952 TransmitFile (filename, false);
955 internal void TransmitFile (string filename, bool final_flush)
957 FileInfo fi = new FileInfo (filename);
958 using (Stream s = fi.OpenRead ()); // Just check if we can read.
959 output_stream.WriteFile (filename, 0, fi.Length);
960 output_stream.ApplyFilter (final_flush);
965 internal void TransmitFile (VirtualFile vf)
967 TransmitFile (vf, false);
970 internal void TransmitFile (VirtualFile vf, bool final_flush)
973 throw new ArgumentNullException ("vf");
975 using (Stream s = vf.Open ()) {
977 byte[] buf = new byte [len];
978 int readBytes = s.Read (buf, 0, (int) len);
979 output_stream.Write (buf, 0, readBytes);
980 output_stream.ApplyFilter (final_flush);
986 #region Session state support
987 internal void SetAppPathModifier (string app_modifier)
989 app_path_mod = app_modifier;
993 #region Cache Support
994 internal void SetCachedHeaders (ArrayList headers)
996 cached_headers = headers;
999 internal bool IsCached {
1001 return cached_response != null;
1005 public HttpCachePolicy Cache {
1007 if (cache_policy == null) {
1008 cache_policy = new HttpCachePolicy ();
1009 cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
1012 return cache_policy;
1016 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
1018 if (e.Cacheability >= HttpCacheability.Server && !IsCached)
1019 cached_response = new CachedRawResponse (cache_policy);
1020 else if (e.Cacheability <= HttpCacheability.Private)
1021 cached_response = null;
1024 internal CachedRawResponse GetCachedResponse ()
1026 cached_response.StatusCode = StatusCode;
1027 cached_response.StatusDescription = StatusDescription;
1028 return cached_response;
1032 // This is one of the old ASP compatibility methods, the real cache
1033 // control is in the Cache property, and this is a second class citizen
1035 public string CacheControl {
1037 if (value == null || value == "") {
1038 Cache.SetCacheability (HttpCacheability.NoCache);
1039 user_cache_control = null;
1040 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1041 Cache.SetCacheability (HttpCacheability.Public);
1042 user_cache_control = "public";
1043 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1044 Cache.SetCacheability (HttpCacheability.Private);
1045 user_cache_control = "private";
1046 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1047 Cache.SetCacheability (HttpCacheability.NoCache);
1048 user_cache_control = "no-cache";
1050 throw new ArgumentException ("CacheControl property only allows `public', " +
1051 "`private' or no-cache, for different uses, use " +
1052 "Response.AppendHeader");
1055 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1059 internal int GetOutputByteCount ()
1061 return output_stream.GetTotalLength ();
1064 internal void ReleaseResources ()
1066 output_stream.ReleaseResources (true);
1067 output_stream = null;
1074 static class FlagEnd
1076 public static readonly object Value = new object ();