2 // System.Web.HttpResponse
5 // Patrik Torstensson (Patrik.Torstensson@labs2.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (c) 2002 Ximian, Inc. (http://www.ximian.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System
.Collections
;
33 using System
.Globalization
;
36 using System
.Threading
;
37 using System
.Web
.Util
;
38 using System
.Web
.Caching
;
42 public sealed class HttpResponse
44 // Chunked encoding static helpers
45 static byte [] s_arrChunkSuffix
= {13, 10}
;
46 static byte [] s_arrChunkEnd
= {48, 13, 10, 13, 10}
;
47 static string s_sChunkedPrefix
= "\r\n";
51 bool _bClientDisconnected
;
52 bool _bSuppressHeaders
;
53 bool _bSuppressContent
;
63 int _expiresInMinutes
;
64 bool _expiresInMinutesSet
;
65 DateTime _expiresAbsolute
;
66 bool _expiresAbsoluteSet
;
68 bool _ClientDisconnected
;
72 string _sCacheControl
;
73 string _sTransferEncoding
;
75 string _sStatusDescription
;
77 HttpCookieCollection _Cookies
;
78 HttpCachePolicy _CachePolicy
;
80 Encoding _ContentEncoding
;
84 TextWriter _TextWriter
;
86 HttpWorkerRequest _WorkerRequest
;
88 ArrayList fileDependencies
;
89 CachedRawResponse cached_response
;
90 ArrayList cached_headers
;
92 string redirectLocation
;
95 string app_path_mod
= null;
97 public HttpResponse (TextWriter output
)
101 _bHeadersSent
= false;
103 _Headers
= new ArrayList ();
105 _sContentType
= "text/html";
109 _sCacheControl
= null;
112 _bSuppressContent
= false;
113 _bSuppressHeaders
= false;
114 _bClientDisconnected
= false;
118 _TextWriter
= output
;
121 internal HttpResponse (HttpWorkerRequest WorkerRequest
, HttpContext Context
)
124 _WorkerRequest
= WorkerRequest
;
128 _bHeadersSent
= false;
130 _Headers
= new ArrayList ();
132 _sContentType
= "text/html";
136 _sCacheControl
= null;
139 _bSuppressContent
= false;
140 _bSuppressHeaders
= false;
141 _bClientDisconnected
= false;
146 internal void InitializeWriter ()
148 // We cannot do this in the .ctor because HttpWriter uses configuration and
149 // it may not be initialized
150 if (_Writer
== null) {
151 _Writer
= new HttpWriter (this);
152 _TextWriter
= _Writer
;
156 internal void FinalFlush ()
161 internal void DoFilter (bool really
)
163 if (really
&& null != _Writer
)
164 _Writer
.FilterData (true);
169 internal bool IsCached
{
170 get { return cached_response != null; }
173 internal CachedRawResponse
GetCachedResponse () {
174 cached_response
.StatusCode
= StatusCode
;
175 cached_response
.StatusDescription
= StatusDescription
;
176 return cached_response
;
179 internal void SetCachedHeaders (ArrayList headers
)
181 cached_headers
= headers
;
184 private ArrayList
GenerateHeaders ()
186 ArrayList oHeaders
= new ArrayList (_Headers
.ToArray ());
188 oHeaders
.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
189 // save culture info, we need us info here
190 CultureInfo oSavedInfo
= Thread
.CurrentThread
.CurrentCulture
;
191 Thread
.CurrentThread
.CurrentCulture
= CultureInfo
.InvariantCulture
;
193 string date
= DateTime
.UtcNow
.ToString ("ddd, d MMM yyyy HH:mm:ss ");
194 HttpResponseHeader date_header
= new HttpResponseHeader ("Date", date
+ "GMT");
195 oHeaders
.Add (date_header
);
198 cached_response
.DateHeader
= date_header
;
200 Thread
.CurrentThread
.CurrentCulture
= oSavedInfo
;
202 if (_lContentLength
> 0) {
203 oHeaders
.Add (new HttpResponseHeader (HttpWorkerRequest
.HeaderContentLength
,
204 _lContentLength
.ToString ()));
207 if (_sContentType
!= null) {
208 if (_sContentType
.IndexOf ("charset=") == -1) {
209 if (Charset
.Length
== 0) {
210 Charset
= ContentEncoding
.HeaderName
;
213 // Time to build our string
214 if (Charset
.Length
> 0) {
215 _sContentType
+= "; charset=" + Charset
;
219 oHeaders
.Add (new HttpResponseHeader (HttpWorkerRequest
.HeaderContentType
,
223 if (_CachePolicy
!= null)
224 _CachePolicy
.SetHeaders (this, oHeaders
);
226 if (_sCacheControl
!= null) {
227 oHeaders
.Add (new HttpResponseHeader (HttpWorkerRequest
.HeaderPragma
,
231 if (_sTransferEncoding
!= null) {
232 oHeaders
.Add (new HttpResponseHeader (HttpWorkerRequest
.HeaderTransferEncoding
,
233 _sTransferEncoding
));
236 if (_Cookies
!= null) {
237 int length
= _Cookies
.Count
;
238 for (int i
= 0; i
< length
; i
++) {
239 oHeaders
.Add (_Cookies
.Get (i
).GetCookieHeader ());
243 if (redirectLocation
!= null)
244 oHeaders
.Add (new HttpResponseHeader (HttpWorkerRequest
.HeaderLocation
,
250 private void SendHeaders ()
252 _WorkerRequest
.SendStatus (StatusCode
, StatusDescription
);
256 if (cached_headers
!= null)
257 oHeaders
= cached_headers
;
259 oHeaders
= GenerateHeaders ();
261 if (cached_response
!= null)
262 cached_response
.SetHeaders (oHeaders
);
264 foreach (HttpResponseHeader oHeader
in oHeaders
)
265 oHeader
.SendContent (_WorkerRequest
);
267 _bHeadersSent
= true;
273 return String
.Format ("{0} {1}", StatusCode
, StatusDescription
);
281 iCode
= Int32
.Parse (value.Substring (0, value.IndexOf (' ')));
282 sMsg
= value.Substring (value.IndexOf (' ') + 1);
283 } catch (Exception
) {
284 throw new HttpException ("Invalid status string");
288 StatusDescription
= sMsg
;
293 public void AddCacheItemDependencies (ArrayList cacheKeys
)
295 throw new NotImplementedException ();
299 public void AddCacheItemDependency(string cacheKey
)
301 throw new NotImplementedException ();
304 public void AddFileDependencies (ArrayList filenames
)
306 if (filenames
== null || filenames
.Count
== 0)
309 if (fileDependencies
== null) {
310 fileDependencies
= (ArrayList
) filenames
.Clone ();
314 foreach (string fn
in filenames
)
315 AddFileDependency (fn
);
318 public void AddFileDependency (string filename
)
320 if (fileDependencies
== null)
321 fileDependencies
= new ArrayList ();
323 fileDependencies
.Add (filename
);
326 public void AddHeader (string name
, string value)
328 AppendHeader(name
, value);
331 public void AppendCookie (HttpCookie cookie
)
334 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
336 Cookies
.Add (cookie
);
340 public void AppendToLog (string param
)
342 throw new NotImplementedException ();
345 public string ApplyAppPathModifier (string virtualPath
)
347 if (virtualPath
== null)
350 if (virtualPath
== "")
351 return _Context
.Request
.RootVirtualDir
;
353 if (UrlUtils
.IsRelativeUrl (virtualPath
)) {
354 virtualPath
= UrlUtils
.Combine (_Context
.Request
.RootVirtualDir
, virtualPath
);
355 } else if (UrlUtils
.IsRooted (virtualPath
)) {
356 virtualPath
= UrlUtils
.Reduce (virtualPath
);
359 if (app_path_mod
!= null && virtualPath
.IndexOf (app_path_mod
) > 0)
360 virtualPath
= UrlUtils
.Combine (app_path_mod
, virtualPath
);
365 internal void SetAppPathModifier (string app_path_mod
)
367 this.app_path_mod
= app_path_mod
;
377 BufferOutput
= value;
381 public bool BufferOutput
395 public HttpCachePolicy Cache
398 if (null == _CachePolicy
) {
399 _CachePolicy
= new HttpCachePolicy ();
400 _CachePolicy
.CacheabilityUpdated
+= new CacheabilityUpdatedCallback (
401 OnCacheabilityUpdated
);
408 private void OnCacheabilityUpdated (object sender
, CacheabilityUpdatedEventArgs e
)
410 if (e
.Cacheability
>= HttpCacheability
.Server
&& !IsCached
)
411 cached_response
= new CachedRawResponse (_CachePolicy
);
412 else if (e
.Cacheability
<= HttpCacheability
.Private
)
413 cached_response
= null;
416 [MonoTODO("Set status in the cache policy")]
417 public string CacheControl
420 return _sCacheControl
;
425 throw new HttpException ("Headers has been sent to the client");
427 _sCacheControl
= value;
431 public string Charset
434 if (null == _sCharset
)
435 _sCharset
= ContentEncoding
.WebName
;
442 throw new HttpException ("Headers has been sent to the client");
448 public Encoding ContentEncoding
451 if (_ContentEncoding
== null)
452 _ContentEncoding
= WebEncoding
.ResponseEncoding
;
454 return _ContentEncoding
;
459 throw new ArgumentNullException ("Can't set a null as encoding");
461 _ContentEncoding
= value;
468 public string ContentType
471 return _sContentType
;
476 throw new HttpException ("Headers has been sent to the client");
478 _sContentType
= value;
482 public HttpCookieCollection Cookies
485 if (null == _Cookies
)
486 _Cookies
= new HttpCookieCollection (this, false);
495 return _expiresInMinutes
;
499 if (!_expiresInMinutesSet
|| (value < _expiresInMinutes
))
501 _expiresInMinutes
= value;
502 Cache
.SetExpires(_Context
.Timestamp
.Add(new TimeSpan(0, _expiresInMinutes
, 0)));
504 _expiresInMinutesSet
= true;
508 public DateTime ExpiresAbsolute
511 return _expiresAbsolute
;
515 if (!_expiresAbsoluteSet
|| value.CompareTo(_expiresAbsolute
)<0)
517 _expiresAbsolute
= value;
518 Cache
.SetExpires(_expiresAbsolute
);
520 _expiresAbsoluteSet
= true;
528 return _Writer
.GetActiveFilter ();
535 throw new HttpException ("Filtering is not allowed");
537 _Writer
.ActivateFilter (value);
541 public bool IsClientConnected
544 if (_ClientDisconnected
)
547 if (null != _WorkerRequest
&& (!_WorkerRequest
.IsClientConnected ())) {
548 _ClientDisconnected
= false;
556 public TextWriter Output
563 public Stream OutputStream
567 throw new HttpException ("an Output stream not available when " +
568 "running with custom text writer");
570 return _Writer
.OutputStream
;
575 public string RedirectLocation
{
576 get { return redirectLocation; }
577 set { redirectLocation = value; }
581 public string StatusDescription
584 if (null == _sStatusDescription
)
585 _sStatusDescription
=
586 HttpWorkerRequest
.GetStatusDescription (_iStatusCode
);
588 return _sStatusDescription
;
593 throw new HttpException ("Headers has been sent to the client");
595 _sStatusDescription
= value;
599 public int StatusCode
607 throw new HttpException ("Headers has been sent to the client");
609 _sStatusDescription
= null;
610 _iStatusCode
= value;
614 public bool SuppressContent
617 return _bSuppressContent
;
621 _bSuppressContent
= true;
628 if (_Context
== null)
631 return _Context
.Request
;
635 internal void AppendHeader (int iIndex
, string value)
638 throw new HttpException ("Headers has been sent to the client");
641 case HttpWorkerRequest
.HeaderContentLength
:
642 _lContentLength
= Int64
.Parse (value);
644 case HttpWorkerRequest
.HeaderContentEncoding
:
645 _sContentType
= value;
647 case HttpWorkerRequest
.HeaderTransferEncoding
:
648 _sTransferEncoding
= value;
649 _bChunked
= (value == "chunked");
651 case HttpWorkerRequest
.HeaderPragma
:
652 _sCacheControl
= value;
655 _Headers
.Add (new HttpResponseHeader (iIndex
, value));
660 public void AppendHeader (string name
, string value)
663 throw new HttpException ("Headers has been sent to the client");
665 switch (name
.ToLower ()) {
666 case "content-length":
667 _lContentLength
= Int64
.Parse (value);
670 _sContentType
= value;
672 case "transfer-encoding":
673 _sTransferEncoding
= value;
674 _bChunked
= (value == "chunked");
677 _sCacheControl
= value;
680 _Headers
.Add (new HttpResponseHeader (name
, value));
685 internal TextWriter
SetTextWriter (TextWriter w
)
687 TextWriter prev
= _TextWriter
;
692 public void BinaryWrite (byte [] buffer
)
694 OutputStream
.Write (buffer
, 0, buffer
.Length
);
697 internal void BinaryWrite (byte [] buffer
, int start
, int length
)
699 OutputStream
.Write (buffer
, start
, length
);
708 public void ClearContent ()
713 public void ClearHeaders ()
716 throw new HttpException ("Headers has been sent to the client");
718 _sContentType
= "text/html";
722 _Headers
= new ArrayList ();
723 _sCacheControl
= null;
724 _sTransferEncoding
= null;
727 _bSuppressContent
= false;
728 _bSuppressHeaders
= false;
729 _bClientDisconnected
= false;
734 if (closed
&& !_bClientDisconnected
) {
735 _bClientDisconnected
= false;
736 _WorkerRequest
.CloseConnection ();
737 _bClientDisconnected
= true;
741 internal void Dispose ()
743 if (_Writer
!= null) {
749 [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]
750 internal void FlushAtEndOfRequest ()
760 if (_Context
.TimeoutPossible
)
761 Thread
.CurrentThread
.Abort (new StepCompleteRequest ());
765 _Context
.ApplicationInstance
.CompleteRequest ();
771 throw new HttpException ("Response already finished.");
776 private void Flush (bool bFinish
)
778 if (_bFlushing
|| closed
)
783 if (_Writer
== null) {
784 _TextWriter
.Flush ();
790 if (_bClientDisconnected
)
793 long length
= _Writer
.BufferSize
;
794 if (!_bHeadersSent
&& !_bSuppressHeaders
) {
796 if (length
== 0 && _lContentLength
== 0)
797 _sContentType
= null;
800 length
= _Writer
.BufferSize
;
802 _WorkerRequest
.SendCalculatedContentLength ((int) length
);
804 if (_lContentLength
== 0 && _iStatusCode
== 200 &&
805 _sTransferEncoding
== null) {
806 // Check we are going todo chunked encoding
807 string sProto
= Request
.ServerVariables
["SERVER_PROTOCOL"];
808 if (sProto
!= null && sProto
== "HTTP/1.1") {
810 HttpWorkerRequest
.HeaderTransferEncoding
,
813 // Just in case, the old browsers send a HTTP/1.0
814 // request with Connection: Keep-Alive
816 HttpWorkerRequest
.HeaderConnection
,
821 length
= _Writer
.BufferSize
;
827 _Writer
.FilterData (false);
828 length
= _Writer
.BufferSize
;
832 if (bFinish
&& _bChunked
) {
833 _WorkerRequest
.SendResponseFromMemory (s_arrChunkEnd
,
834 s_arrChunkEnd
.Length
);
837 _WorkerRequest
.FlushResponse (bFinish
);
843 if (!_bSuppressContent
&& Request
.HttpMethod
== "HEAD")
844 _bSuppressContent
= true;
846 if (_bSuppressContent
)
849 if (!_bSuppressContent
) {
850 _bClientDisconnected
= false;
852 Encoding oASCII
= Encoding
.ASCII
;
854 string chunk
= Convert
.ToString(_Writer
.BufferSize
, 16);
855 byte [] arrPrefix
= oASCII
.GetBytes (chunk
+ s_sChunkedPrefix
);
857 _WorkerRequest
.SendResponseFromMemory (arrPrefix
,
860 _Writer
.SendContent (_WorkerRequest
);
862 _WorkerRequest
.SendResponseFromMemory (s_arrChunkSuffix
,
863 s_arrChunkSuffix
.Length
);
865 _WorkerRequest
.SendResponseFromMemory (
866 s_arrChunkEnd
, s_arrChunkEnd
.Length
);
868 _Writer
.SendContent (_WorkerRequest
);
872 _WorkerRequest
.FlushResponse (bFinish
);
874 cached_response
.ContentLength
= (int) length
;
875 cached_response
.SetData (_Writer
.GetBuffer ());
885 public void Pics (string value)
887 AppendHeader ("PICS-Label", value);
891 public void Redirect (string url
)
893 Redirect (url
, true);
896 public void Redirect (string url
, bool endResponse
)
899 throw new HttpException ("Headers has been sent to the client");
903 url
= ApplyAppPathModifier (url
);
905 AppendHeader (HttpWorkerRequest
.HeaderLocation
, url
);
907 // Text for browsers that can't handle location header
908 Write ("<html><head><title>Object moved</title></head><body>\r\n");
909 Write ("<h2>Object moved to <a href='" + url
+ "'>here</a></h2>\r\n");
910 Write ("</body><html>\r\n");
916 internal bool RedirectCustomError (string errorPage
)
921 if (Request
.QueryString
["aspxerrorpath"] != null)
922 return false; // Prevent endless loop
924 Redirect (errorPage
+ "?aspxerrorpath=" + Request
.Path
, false);
928 public void Write (char ch
)
930 _TextWriter
.Write(ch
);
933 public void Write (object obj
)
935 _TextWriter
.Write(obj
);
938 public void Write (string str
)
940 _TextWriter
.Write (str
);
943 public void Write (char [] buffer
, int index
, int count
)
945 _TextWriter
.Write (buffer
, index
, count
);
948 public static void RemoveOutputCacheItem (string path
)
951 throw new ArgumentNullException ("path");
953 if (!UrlUtils
.IsRooted (path
))
954 throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem '" +
955 path
+ "'. An absolute virtual path is expected.");
957 Cache cache
= HttpRuntime
.Cache
;
961 public void SetCookie (HttpCookie cookie
)
964 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
966 Cookies
.Add (cookie
);
969 private void WriteFromStream (Stream stream
, long offset
, long length
, long bufsize
)
971 if (offset
< 0 || length
<= 0)
974 long stLength
= stream
.Length
;
975 if (offset
+ length
> stLength
)
976 length
= stLength
- offset
;
979 stream
.Seek (offset
, SeekOrigin
.Begin
);
981 byte [] fileContent
= new byte [bufsize
];
982 int count
= (int) Math
.Min (Int32
.MaxValue
, bufsize
);
983 while (length
> 0 && (count
= stream
.Read (fileContent
, 0, count
)) != 0) {
984 _Writer
.WriteBytes (fileContent
, 0, count
);
986 count
= (int) Math
.Min (length
, fileContent
.Length
);
990 public void WriteFile (string filename
)
992 WriteFile (filename
, false);
995 public void WriteFile (string filename
, bool readIntoMemory
)
997 FileStream fs
= null;
999 fs
= File
.OpenRead (filename
);
1000 long size
= fs
.Length
;
1001 if (readIntoMemory
) {
1002 WriteFromStream (fs
, 0, size
, size
);
1004 WriteFromStream (fs
, 0, size
, 8192);
1012 public void WriteFile (string filename
, long offset
, long size
)
1014 FileStream fs
= null;
1016 fs
= File
.OpenRead (filename
);
1017 WriteFromStream (fs
, offset
, size
, 8192);
1024 public void WriteFile (IntPtr fileHandle
, long offset
, long size
)
1026 FileStream fs
= null;
1028 fs
= new FileStream (fileHandle
, FileAccess
.Read
);
1029 WriteFromStream (fs
, offset
, size
, 8192);
1037 internal void OnCookieAdd (HttpCookie cookie
)
1041 [MonoTODO("Do we need this?")]
1042 internal void OnCookieChange (HttpCookie cookie
)
1047 internal void GoingToChangeCookieColl ()
1052 internal void ChangedCookieColl ()