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 using vmw.@internal.j2ee;
46 namespace System.Web {
48 // CAS - no InheritanceDemand here as the class is sealed
49 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50 public sealed class HttpResponse {
51 internal HttpWorkerRequest WorkerRequest;
52 internal HttpResponseStream output_stream;
53 internal bool buffer = true;
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;
102 internal object FlagEnd = new object ();
105 bool is_request_being_redirected;
106 Encoding headerEncoding;
109 internal HttpResponse ()
111 output_stream = new HttpResponseStream (this);
114 public HttpResponse (TextWriter writer) : this ()
116 this.writer = writer;
119 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
121 WorkerRequest = worker_request;
122 this.context = context;
124 if (worker_request != null)
125 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
128 internal TextWriter SetTextWriter (TextWriter writer)
130 TextWriter prev = this.writer;
131 this.writer = writer;
145 public bool BufferOutput {
156 // Use the default from <globalization> section if the client has not set the encoding
158 public Encoding ContentEncoding {
160 if (encoding == null) {
161 if (context != null) {
162 string client_content_type = context.Request.ContentType;
163 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
164 if (parameter != null) {
166 // Do what the #1 web server does
167 encoding = Encoding.GetEncoding (parameter);
172 if (encoding == null)
173 encoding = WebEncoding.ResponseEncoding;
180 throw new ArgumentException ("ContentEncoding can not be null");
183 HttpWriter http_writer = writer as HttpWriter;
184 if (http_writer != null)
185 http_writer.SetEncoding (encoding);
189 public string ContentType {
195 content_type = value;
199 public string Charset {
202 charset = ContentEncoding.WebName;
213 public HttpCookieCollection Cookies {
216 cookies = new HttpCookieCollection (true, false);
223 if (cache_policy == null)
226 return cache_policy.ExpireMinutes ();
230 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
234 public DateTime ExpiresAbsolute {
236 return Cache.Expires;
240 Cache.SetExpires (value);
244 public Stream Filter {
246 if (WorkerRequest == null)
249 return output_stream.Filter;
253 output_stream.Filter = value;
257 public Encoding HeaderEncoding {
259 if (headerEncoding == null) {
260 GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
263 headerEncoding = Encoding.UTF8;
265 headerEncoding = gs.ResponseHeaderEncoding;
266 if (headerEncoding == Encoding.Unicode)
267 throw new HttpException ("HeaderEncoding must not be Unicode");
270 return headerEncoding;
274 throw new HttpException ("headers have already been sent");
276 throw new ArgumentNullException ("HeaderEncoding");
277 if (value == Encoding.Unicode)
278 throw new HttpException ("HeaderEncoding must not be Unicode");
279 headerEncoding = value;
283 public bool IsClientConnected {
285 if (WorkerRequest == null)
286 return true; // yep that's true
288 return WorkerRequest.IsClientConnected ();
292 public bool IsRequestBeingRedirected {
293 get { return is_request_being_redirected; }
296 public TextWriter Output {
299 writer = new HttpWriter (this);
305 public Stream OutputStream {
307 return output_stream;
311 public string RedirectLocation {
313 return redirect_location;
317 redirect_location = value;
321 public string Status {
323 return String.Format ("{0} {1}", status_code, StatusDescription);
327 int p = value.IndexOf (' ');
329 throw new HttpException ("Invalid format for the Status property");
331 string s = value.Substring (0, p);
334 if (!Int32.TryParse (s, out status_code))
335 throw new HttpException ("Invalid format for the Status property");
339 status_code = Int32.Parse (s);
341 throw new HttpException ("Invalid format for the Status property");
345 status_description = value.Substring (p+1);
349 public int StatusCode {
356 throw new HttpException ("headers have already been sent");
359 status_description = null;
363 public string StatusDescription {
365 if (status_description == null)
366 status_description = HttpWorkerRequest.GetStatusDescription (status_code);
368 return status_description;
373 throw new HttpException ("headers have already been sent");
375 status_description = value;
379 public bool SuppressContent {
381 return suppress_content;
385 suppress_content = value;
390 [MonoTODO ("Not implemented")]
391 public void AddCacheDependency (CacheDependency[] dependencies)
393 throw new NotImplementedException ();
396 [MonoTODO ("Not implemented")]
397 public void AddCacheItemDependencies (string[] cacheKeys)
399 throw new NotImplementedException ();
402 [MonoTODO("Currently does nothing")]
403 public void AddCacheItemDependencies (ArrayList cacheKeys)
405 // TODO: talk to jackson about the cache
408 [MonoTODO("Currently does nothing")]
409 public void AddCacheItemDependency (string cacheKey)
411 // TODO: talk to jackson about the cache
414 [MonoTODO("Currently does nothing")]
415 public void AddFileDependencies (ArrayList filenames)
417 // TODO: talk to jackson about the cache
420 [MonoTODO ("Not implemented")]
421 public void AddFileDependencies (string[] filenames)
423 throw new NotImplementedException ();
426 [MonoTODO ("Currently does nothing")]
427 public void AddFileDependency (string filename)
429 // TODO: talk to jackson about the cache
432 public void AddHeader (string name, string value)
434 AppendHeader (name, value);
437 public void AppendCookie (HttpCookie cookie)
439 Cookies.Add (cookie);
444 // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
447 public void AppendHeader (string name, string value)
450 throw new HttpException ("headers have been already sent");
452 if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
453 content_length = (long) UInt64.Parse (value);
458 if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
463 if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
464 transfer_encoding = value;
469 if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
470 user_cache_control = value;
474 headers.Add (new UnknownResponseHeader (name, value));
477 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
478 public void AppendToLog (string param)
480 Console.Write ("System.Web: ");
481 Console.WriteLine (param);
484 public string ApplyAppPathModifier (string virtualPath)
486 if (virtualPath == null)
489 if (virtualPath == "")
490 return context.Request.RootVirtualDir;
492 if (UrlUtils.IsRelativeUrl (virtualPath)) {
493 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
494 } else if (UrlUtils.IsRooted (virtualPath)) {
495 virtualPath = UrlUtils.Canonic (virtualPath);
498 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
499 string rvd = context.Request.RootVirtualDir;
500 string basevd = rvd.Replace (app_path_mod, "");
502 if (!StrUtils.StartsWith (virtualPath, basevd))
505 virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
511 public void BinaryWrite (byte [] buffer)
513 output_stream.Write (buffer, 0, buffer.Length);
516 internal void BinaryWrite (byte [] buffer, int start, int len)
518 output_stream.Write (buffer, start, len);
526 public void ClearContent ()
528 output_stream.Clear ();
531 public void ClearHeaders ()
534 throw new HttpException ("headers have been already sent");
536 // Reset the special case headers.
538 content_type = "text/html";
539 transfer_encoding = null;
540 user_cache_control = null;
544 internal bool HeadersSent {
554 if (WorkerRequest != null)
555 WorkerRequest.CloseConnection ();
561 if (context.TimeoutPossible) {
562 Thread.CurrentThread.Abort (FlagEnd);
564 // If this is called from an async event, signal the completion
566 context.ApplicationInstance.CompleteRequest ();
573 // Transfer-Encoding (chunked)
575 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
581 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
582 else if (transfer_encoding != null)
583 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
585 UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
586 DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
587 write_headers.Add (date_header);
590 cached_response.DateHeader = date_header;
592 if (redirect_location != null)
593 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
596 // If Content-Length is set.
598 if (content_length >= 0){
599 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
600 content_length.ToString (CultureInfo.InvariantCulture)));
601 } else if (BufferOutput){
604 // If we are buffering and this is the last flush, not a middle-flush,
605 // we know the content-length.
608 content_length = output_stream.total;
609 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
610 content_length.ToString (CultureInfo.InvariantCulture)));
614 // We are buffering, and this is a flush in the middle.
615 // If we are not chunked, we need to set "Connection: close".
619 Console.WriteLine ("Setting to close2");
621 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
626 // If the content-length is not set, and we are not buffering, we must
631 Console.WriteLine ("Setting to close");
633 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
638 // Cache Control, the cache policy takes precedence over the cache_control property.
640 if (cache_policy != null)
641 cache_policy.SetHeaders (this, headers);
643 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
648 if (content_type != null){
649 string header = content_type;
651 if (charset_set || header == "text/plain" || header == "text/html") {
652 if (header.IndexOf ("charset=") == -1) {
653 if (charset == null || charset == "")
654 charset = ContentEncoding.HeaderName;
655 header += "; charset=" + charset;
659 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
662 if (cookies != null && cookies.Count != 0){
663 int n = cookies.Count;
664 for (int i = 0; i < n; i++)
665 write_headers.Add (cookies.Get (i).GetCookieHeader ());
667 // For J2EE Portal support emulate cookies by storing them in the session.
668 context.Request.SetSessionCookiesForPortal (cookies);
673 internal void WriteHeaders (bool final_flush)
678 if (WorkerRequest != null)
679 WorkerRequest.SendStatus (status_code, StatusDescription);
681 if (cached_response != null)
682 cached_response.SetHeaders (headers);
684 // If this page is cached use the cached headers
685 // instead of the standard headers
686 ArrayList write_headers = headers;
687 if (cached_headers != null)
688 write_headers = cached_headers;
690 AddHeadersNoCache (write_headers, final_flush);
695 if (context != null) {
696 HttpApplication app_instance = context.ApplicationInstance;
697 if (app_instance != null)
698 app_instance.TriggerPreSendRequestHeaders ();
700 if (WorkerRequest != null) {
701 foreach (BaseResponseHeader header in write_headers){
702 header.SendContent (WorkerRequest);
708 internal void DoFilter (bool close)
710 if (output_stream.HaveFilter && context != null && context.Error == null)
711 output_stream.ApplyFilter (close);
714 internal void Flush (bool final_flush)
716 DoFilter (final_flush);
718 if (final_flush || status_code != 200)
722 bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
723 if (suppress_content || head) {
726 output_stream.Clear ();
727 if (WorkerRequest != null)
728 output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
733 WriteHeaders (final_flush);
735 if (context != null) {
736 HttpApplication app_instance = context.ApplicationInstance;
737 if (app_instance != null)
738 app_instance.TriggerPreSendRequestContent ();
742 MemoryStream ms = output_stream.GetData ();
743 cached_response.ContentLength = (int) ms.Length;
744 cached_response.SetData (ms.GetBuffer ());
747 if (WorkerRequest != null)
748 output_stream.Flush (WorkerRequest, final_flush);
756 public void Pics (string value)
758 AppendHeader ("PICS-Label", value);
761 public void Redirect (string url)
763 Redirect (url, true);
766 public void Redirect (string url, bool endResponse)
769 throw new HttpException ("Headers have already been sent");
772 // In J2EE portal we need to handle Redirect at the processAction phase
773 // using the portlet ActionResponse that will send for us the redirect header.
774 IPortletActionResponse resp = context.ServletResponse as IPortletActionResponse;
776 resp.sendRedirect (ApplyAppPathModifier (url));
784 is_request_being_redirected = true;
790 url = ApplyAppPathModifier (url);
791 headers.Add (new UnknownResponseHeader ("Location", url));
793 // Text for browsers that can't handle location header
794 Write ("<html><head><title>Object moved</title></head><body>\r\n");
795 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
796 Write ("</body><html>\r\n");
801 is_request_being_redirected = false;
805 public static void RemoveOutputCacheItem (string path)
808 throw new ArgumentNullException ("path");
810 if (path.Length == 0)
814 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
816 HttpRuntime.InternalCache.Remove (path);
819 public void SetCookie (HttpCookie cookie)
821 AppendCookie (cookie);
824 public void Write (char ch)
829 public void Write (object obj)
834 Output.Write (obj.ToString ());
837 public void Write (string s)
842 public void Write (char [] buffer, int index, int count)
844 Output.Write (buffer, index, count);
847 internal void WriteFile (FileStream fs, long offset, long size)
849 byte [] buffer = new byte [32*1024];
852 fs.Position = offset;
856 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
858 output_stream.Write (buffer, 0, n);
862 public void WriteFile (string filename)
864 WriteFile (filename, false);
867 public void WriteFile (string filename, bool readIntoMemory)
869 if (filename == null)
870 throw new ArgumentNullException ("filename");
873 using (FileStream fs = File.OpenRead (filename))
874 WriteFile (fs, 0, fs.Length);
876 FileInfo fi = new FileInfo (filename);
877 output_stream.WriteFile (filename, 0, fi.Length);
882 output_stream.ApplyFilter (false);
887 public void WriteFile (IntPtr fileHandle, long offset, long size) {
888 throw new PlatformNotSupportedException("IntPtr not supported");
891 public void WriteFile (IntPtr fileHandle, long offset, long size)
894 throw new ArgumentNullException ("offset can not be negative");
896 throw new ArgumentNullException ("size can not be negative");
901 // Note: this .ctor will throw a SecurityException if the caller
902 // doesn't have the UnmanagedCode permission
903 using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
904 WriteFile (fs, offset, size);
908 output_stream.ApplyFilter (false);
913 public void WriteFile (string filename, long offset, long size)
915 if (filename == null)
916 throw new ArgumentNullException ("filename");
918 throw new ArgumentNullException ("offset can not be negative");
920 throw new ArgumentNullException ("size can not be negative");
925 FileStream fs = File.OpenRead (filename);
926 WriteFile (fs, offset, size);
931 output_stream.ApplyFilter (false);
935 [MonoTODO ("Not implemented")]
936 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
938 throw new NotImplementedException ();
942 // Like WriteFile, but never buffers, so we manually Flush here
944 public void TransmitFile (string filename)
946 if (filename == null)
947 throw new ArgumentNullException ("filename");
949 TransmitFile (filename, false);
952 internal void TransmitFile (string filename, bool final_flush)
954 FileInfo fi = new FileInfo (filename);
955 using (Stream s = fi.OpenRead ()); // Just check if we can read.
956 output_stream.WriteFile (filename, 0, fi.Length);
957 output_stream.ApplyFilter (final_flush);
962 #region Session state support
963 internal void SetAppPathModifier (string app_modifier)
965 app_path_mod = app_modifier;
969 #region Cache Support
970 internal void SetCachedHeaders (ArrayList headers)
972 cached_headers = headers;
975 internal bool IsCached {
977 return cached_response != null;
981 public HttpCachePolicy Cache {
983 if (cache_policy == null) {
984 cache_policy = new HttpCachePolicy ();
985 cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
992 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
994 if (e.Cacheability >= HttpCacheability.Server && !IsCached)
995 cached_response = new CachedRawResponse (cache_policy);
996 else if (e.Cacheability <= HttpCacheability.Private)
997 cached_response = null;
1000 internal CachedRawResponse GetCachedResponse ()
1002 cached_response.StatusCode = StatusCode;
1003 cached_response.StatusDescription = StatusDescription;
1004 return cached_response;
1008 // This is one of the old ASP compatibility methods, the real cache
1009 // control is in the Cache property, and this is a second class citizen
1011 public string CacheControl {
1013 if (value == null || value == "") {
1014 Cache.SetCacheability (HttpCacheability.NoCache);
1015 user_cache_control = null;
1016 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1017 Cache.SetCacheability (HttpCacheability.Public);
1018 user_cache_control = "public";
1019 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1020 Cache.SetCacheability (HttpCacheability.Private);
1021 user_cache_control = "private";
1022 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1023 Cache.SetCacheability (HttpCacheability.NoCache);
1024 user_cache_control = "no-cache";
1026 throw new ArgumentException ("CacheControl property only allows `public', " +
1027 "`private' or no-cache, for different uses, use " +
1028 "Response.AppendHeader");
1031 get { return (user_cache_control != null) ? user_cache_control : "private"; }
1035 internal int GetOutputByteCount ()
1037 return output_stream.GetTotalLength ();
1040 internal void ReleaseResources ()
1042 output_stream.ReleaseResources (true);
1043 output_stream = null;