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)
11 using System.Collections;
12 using System.Globalization;
15 using System.Threading;
16 using System.Web.Util;
17 using System.Web.Caching;
21 public sealed class HttpResponse
23 // Chunked encoding static helpers
24 static byte [] s_arrChunkSuffix = { 10, 13 };
25 static byte [] s_arrChunkEnd = { 10 , 13 };
26 static string s_sChunkedPrefix = "\r\n";
30 bool _bClientDisconnected;
31 bool _bSuppressHeaders;
32 bool _bSuppressContent;
42 int _expiresInMinutes;
43 bool _expiresInMinutesSet;
44 DateTime _expiresAbsolute;
45 bool _expiresAbsoluteSet;
47 bool _ClientDisconnected;
51 string _sCacheControl;
52 string _sTransferEncoding;
54 string _sStatusDescription;
56 HttpCookieCollection _Cookies;
57 HttpCachePolicy _CachePolicy;
59 Encoding _ContentEncoding;
63 TextWriter _TextWriter;
65 HttpWorkerRequest _WorkerRequest;
67 ArrayList fileDependencies;
68 CachedRawResponse cached_response;
69 ArrayList cached_headers;
71 string redirectLocation;
74 string app_path_mod = null;
76 public HttpResponse (TextWriter output)
80 _bHeadersSent = false;
82 _Headers = new ArrayList ();
84 _sContentType = "text/html";
88 _sCacheControl = null;
91 _bSuppressContent = false;
92 _bSuppressHeaders = false;
93 _bClientDisconnected = false;
100 internal HttpResponse (HttpWorkerRequest WorkerRequest, HttpContext Context)
103 _WorkerRequest = WorkerRequest;
107 _bHeadersSent = false;
109 _Headers = new ArrayList ();
111 _sContentType = "text/html";
115 _sCacheControl = null;
118 _bSuppressContent = false;
119 _bSuppressHeaders = false;
120 _bClientDisconnected = false;
125 internal void InitializeWriter ()
127 // We cannot do this in the .ctor because HttpWriter uses configuration and
128 // it may not be initialized
129 if (_Writer == null) {
130 _Writer = new HttpWriter (this);
131 _TextWriter = _Writer;
135 internal void FinalFlush ()
140 internal void DoFilter (bool really)
142 if (really && null != _Writer)
143 _Writer.FilterData (true);
148 internal bool IsCached {
149 get { return cached_response != null; }
152 internal void CacheResponse (HttpRequest request) {
153 cached_response = new CachedRawResponse (_CachePolicy);
156 internal CachedRawResponse GetCachedResponse () {
157 cached_response.StatusCode = StatusCode;
158 cached_response.StatusDescription = StatusDescription;
159 return cached_response;
162 internal void SetCachedHeaders (ArrayList headers)
164 cached_headers = headers;
167 [MonoTODO("We need to add cache headers also")]
168 private ArrayList GenerateHeaders ()
170 ArrayList oHeaders = new ArrayList (_Headers.ToArray ());
172 oHeaders.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
173 // save culture info, we need us info here
174 CultureInfo oSavedInfo = Thread.CurrentThread.CurrentCulture;
175 Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
177 string date = DateTime.Now.ToUniversalTime ().ToString ("ddd, d MMM yyyy HH:mm:ss ");
178 HttpResponseHeader date_header = new HttpResponseHeader ("Date", date + "GMT");
179 oHeaders.Add (date_header);
182 cached_response.DateHeader = date_header;
184 Thread.CurrentThread.CurrentCulture = oSavedInfo;
186 if (_lContentLength > 0) {
187 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentLength,
188 _lContentLength.ToString ()));
191 if (_sContentType != null) {
192 if (_sContentType.IndexOf ("charset=") == -1) {
193 if (Charset.Length == 0) {
194 Charset = ContentEncoding.HeaderName;
197 // Time to build our string
198 if (Charset.Length > 0) {
199 _sContentType += "; charset=" + Charset;
203 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentType,
207 if (_CachePolicy != null)
208 _CachePolicy.SetHeaders (this, oHeaders);
210 if (_sCacheControl != null) {
211 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderPragma,
215 if (_sTransferEncoding != null) {
216 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderTransferEncoding,
217 _sTransferEncoding));
220 if (_Cookies != null) {
221 int length = _Cookies.Count;
222 for (int i = 0; i < length; i++) {
223 oHeaders.Add (_Cookies.Get (i).GetCookieHeader ());
227 if (redirectLocation != null)
228 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderLocation,
234 private void SendHeaders ()
236 _WorkerRequest.SendStatus (StatusCode, StatusDescription);
240 if (cached_headers != null)
241 oHeaders = cached_headers;
243 oHeaders = GenerateHeaders ();
245 if (cached_response != null)
246 cached_response.SetHeaders (oHeaders);
248 foreach (HttpResponseHeader oHeader in oHeaders)
249 oHeader.SendContent (_WorkerRequest);
251 _bHeadersSent = true;
257 return String.Format ("{0} {1}", StatusCode, StatusDescription);
265 iCode = Int32.Parse (value.Substring (0, value.IndexOf (' ')));
266 sMsg = value.Substring (value.IndexOf (' ') + 1);
267 } catch (Exception) {
268 throw new HttpException ("Invalid status string");
272 StatusDescription = sMsg;
277 public void AddCacheItemDependencies (ArrayList cacheKeys)
279 throw new NotImplementedException ();
283 public void AddCacheItemDependency(string cacheKey)
285 throw new NotImplementedException ();
288 public void AddFileDependencies (ArrayList filenames)
290 if (filenames == null || filenames.Count == 0)
293 if (fileDependencies == null) {
294 fileDependencies = (ArrayList) filenames.Clone ();
298 foreach (string fn in filenames)
299 AddFileDependency (fn);
302 public void AddFileDependency (string filename)
304 if (fileDependencies == null)
305 fileDependencies = new ArrayList ();
307 fileDependencies.Add (filename);
310 public void AddHeader (string name, string value)
312 AppendHeader(name, value);
315 public void AppendCookie (HttpCookie cookie)
318 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
320 Cookies.Add (cookie);
324 public void AppendToLog (string param)
326 throw new NotImplementedException ();
329 public string ApplyAppPathModifier (string virtualPath)
331 if (virtualPath == null)
334 if (virtualPath == "")
335 return _Context.Request.RootVirtualDir;
337 if (UrlUtils.IsRelativeUrl (virtualPath)) {
338 virtualPath = UrlUtils.Combine (_Context.Request.RootVirtualDir, virtualPath);
339 } else if (UrlUtils.IsRooted (virtualPath)) {
340 virtualPath = UrlUtils.Reduce (virtualPath);
343 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) > 0)
344 virtualPath = UrlUtils.Combine (app_path_mod, virtualPath);
349 internal void SetAppPathModifier (string app_path_mod)
351 this.app_path_mod = app_path_mod;
361 BufferOutput = value;
365 public bool BufferOutput
379 public HttpCachePolicy Cache
382 if (null == _CachePolicy)
383 _CachePolicy = new HttpCachePolicy ();
389 [MonoTODO("Set status in the cache policy")]
390 public string CacheControl
393 return _sCacheControl;
398 throw new HttpException ("Headers has been sent to the client");
400 _sCacheControl = value;
404 public string Charset
407 if (null == _sCharset)
408 _sCharset = ContentEncoding.WebName;
415 throw new HttpException ("Headers has been sent to the client");
421 public Encoding ContentEncoding
424 if (_ContentEncoding == null)
425 _ContentEncoding = WebEncoding.ResponseEncoding;
427 return _ContentEncoding;
432 throw new ArgumentNullException ("Can't set a null as encoding");
434 _ContentEncoding = value;
441 public string ContentType
444 return _sContentType;
449 throw new HttpException ("Headers has been sent to the client");
451 _sContentType = value;
455 public HttpCookieCollection Cookies
458 if (null == _Cookies)
459 _Cookies = new HttpCookieCollection (this, false);
468 return _expiresInMinutes;
472 if (!_expiresInMinutesSet || (value < _expiresInMinutes))
\r
474 _expiresInMinutes = value;
\r
475 Cache.SetExpires(_Context.Timestamp.Add(new TimeSpan(0, _expiresInMinutes, 0)));
\r
477 _expiresInMinutesSet = true;
481 public DateTime ExpiresAbsolute
484 return _expiresAbsolute;
488 if (!_expiresAbsoluteSet || value.CompareTo(_expiresAbsolute)<0)
\r
490 _expiresAbsolute = value;
\r
491 Cache.SetExpires(_expiresAbsolute);
\r
493 _expiresAbsoluteSet = true;
501 return _Writer.GetActiveFilter ();
508 throw new HttpException ("Filtering is not allowed");
510 _Writer.ActivateFilter (value);
514 public bool IsClientConnected
517 if (_ClientDisconnected)
520 if (null != _WorkerRequest && (!_WorkerRequest.IsClientConnected ())) {
521 _ClientDisconnected = false;
529 public TextWriter Output
536 public Stream OutputStream
540 throw new HttpException ("an Output stream not available when " +
541 "running with custom text writer");
543 return _Writer.OutputStream;
548 public string RedirectLocation {
549 get { return redirectLocation; }
550 set { redirectLocation = value; }
554 public string StatusDescription
557 if (null == _sStatusDescription)
558 _sStatusDescription =
559 HttpWorkerRequest.GetStatusDescription (_iStatusCode);
561 return _sStatusDescription;
566 throw new HttpException ("Headers has been sent to the client");
568 _sStatusDescription = value;
572 public int StatusCode
580 throw new HttpException ("Headers has been sent to the client");
582 _sStatusDescription = null;
583 _iStatusCode = value;
587 public bool SuppressContent
590 return _bSuppressContent;
595 throw new HttpException ("Headers has been sent to the client");
597 _bSuppressContent = true;
604 if (_Context == null)
607 return _Context.Request;
611 internal void AppendHeader (int iIndex, string value)
614 throw new HttpException ("Headers has been sent to the client");
617 case HttpWorkerRequest.HeaderContentLength:
618 _lContentLength = Int64.Parse (value);
620 case HttpWorkerRequest.HeaderContentEncoding:
621 _sContentType = value;
623 case HttpWorkerRequest.HeaderTransferEncoding:
624 _sTransferEncoding = value;
625 if (value.Equals ("chunked")) {
631 case HttpWorkerRequest.HeaderPragma:
632 _sCacheControl = value;
635 _Headers.Add (new HttpResponseHeader (iIndex, value));
640 public void AppendHeader (string name, string value)
643 throw new HttpException ("Headers has been sent to the client");
645 switch (name.ToLower ()) {
646 case "content-length":
647 _lContentLength = Int64.Parse (value);
650 _sContentType = value;
652 case "transfer-encoding":
653 _sTransferEncoding = value;
654 if (value.Equals ("chunked")) {
661 _sCacheControl = value;
664 _Headers.Add (new HttpResponseHeader (name, value));
669 internal TextWriter SetTextWriter (TextWriter w)
671 TextWriter prev = _TextWriter;
676 public void BinaryWrite (byte [] buffer)
678 OutputStream.Write (buffer, 0, buffer.Length);
681 internal void BinaryWrite (byte [] buffer, int start, int length)
683 OutputStream.Write (buffer, start, length);
692 public void ClearContent ()
697 public void ClearHeaders ()
700 throw new HttpException ("Headers has been sent to the client");
702 _sContentType = "text/html";
706 _Headers = new ArrayList ();
707 _sCacheControl = null;
708 _sTransferEncoding = null;
711 _bSuppressContent = false;
712 _bSuppressHeaders = false;
713 _bClientDisconnected = false;
718 if (closed && !_bClientDisconnected) {
719 _bClientDisconnected = false;
720 _WorkerRequest.CloseConnection ();
721 _bClientDisconnected = true;
725 internal void Dispose ()
727 if (_Writer != null) {
733 [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]
734 internal void FlushAtEndOfRequest ()
744 if (_Context.TimeoutPossible)
745 Thread.CurrentThread.Abort (new StepCompleteRequest ());
749 _Context.ApplicationInstance.CompleteRequest ();
755 throw new HttpException ("Response already finished.");
760 private void Flush (bool bFinish)
762 if (_bFlushing || closed)
767 if (_Writer == null) {
768 _TextWriter.Flush ();
774 if (_bClientDisconnected)
777 long length = _Writer.BufferSize;
778 if (!_bHeadersSent && !_bSuppressHeaders) {
780 if (length == 0 && _lContentLength == 0)
781 _sContentType = null;
784 length = _Writer.BufferSize;
786 _WorkerRequest.SendCalculatedContentLength ((int) length);
788 if (_lContentLength == 0 && _iStatusCode == 200 &&
789 _sTransferEncoding == null) {
790 // Check we are going todo chunked encoding
791 string sProto = Request.ServerVariables ["SERVER_PROTOCOL"];
792 sProto = "HTTP/1.0"; // Remove this line when we support properly
795 if (sProto != null && sProto == "HTTP/1.1") {
797 HttpWorkerRequest.HeaderTransferEncoding,
800 // Just in case, the old browsers send a HTTP/1.0
801 // request with Connection: Keep-Alive
803 HttpWorkerRequest.HeaderConnection,
808 length = _Writer.BufferSize;
814 _Writer.FilterData (false);
815 length = _Writer.BufferSize;
819 _WorkerRequest.FlushResponse (bFinish);
825 if (!_bSuppressContent && Request.HttpMethod == "HEAD")
826 _bSuppressContent = true;
828 if (!_bSuppressContent) {
829 _bClientDisconnected = false;
831 Encoding oASCII = Encoding.ASCII;
833 string chunk = Convert.ToString(_Writer.BufferSize, 16);
834 byte [] arrPrefix = oASCII.GetBytes (chunk + s_sChunkedPrefix);
836 _WorkerRequest.SendResponseFromMemory (arrPrefix,
839 _Writer.SendContent (_WorkerRequest);
841 _WorkerRequest.SendResponseFromMemory (s_arrChunkSuffix,
842 s_arrChunkSuffix.Length);
844 _WorkerRequest.SendResponseFromMemory (
845 s_arrChunkEnd, s_arrChunkEnd.Length);
847 _Writer.SendContent (_WorkerRequest);
851 _WorkerRequest.FlushResponse (bFinish);
853 cached_response.ContentLength = (int) length;
854 cached_response.SetData (_Writer.GetBuffer ());
864 public void Pics (string value)
866 AppendHeader ("PICS-Label", value);
870 public void Redirect (string url)
872 Redirect (url, true);
875 public void Redirect (string url, bool endResponse)
878 throw new HttpException ("Headers has been sent to the client");
882 url = ApplyAppPathModifier (url);
884 AppendHeader (HttpWorkerRequest.HeaderLocation, url);
886 // Text for browsers that can't handle location header
887 Write ("<html><head><title>Object moved</title></head><body>\r\n");
888 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
889 Write ("</body><html>\r\n");
895 internal bool RedirectCustomError (string errorPage)
900 if (Request.QueryString ["aspxerrorpath"] != null)
901 return false; // Prevent endless loop
903 Redirect (errorPage + "?aspxerrorpath=" + Request.Path, false);
907 public void Write (char ch)
909 _TextWriter.Write(ch);
912 public void Write (object obj)
914 _TextWriter.Write(obj);
917 public void Write (string str)
919 _TextWriter.Write (str);
922 public void Write (char [] buffer, int index, int count)
924 _TextWriter.Write (buffer, index, count);
927 public static void RemoveOutputCacheItem (string path)
930 throw new ArgumentNullException ("path");
932 if (!UrlUtils.IsRooted (path))
933 throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem '" +
934 path + "'. An absolute virtual path is expected.");
936 Cache cache = HttpRuntime.Cache;
940 public void SetCookie (HttpCookie cookie)
943 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
945 Cookies.Add (cookie);
948 private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
950 if (offset < 0 || length <= 0)
953 long stLength = stream.Length;
954 if (offset + length > stLength)
955 length = stLength - offset;
958 stream.Seek (offset, SeekOrigin.Begin);
960 byte [] fileContent = new byte [bufsize];
961 int count = (int) Math.Min (Int32.MaxValue, bufsize);
962 while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
963 _Writer.WriteBytes (fileContent, 0, count);
965 count = (int) Math.Min (length, fileContent.Length);
969 public void WriteFile (string filename)
971 WriteFile (filename, false);
974 public void WriteFile (string filename, bool readIntoMemory)
976 FileStream fs = null;
978 fs = File.OpenRead (filename);
979 long size = fs.Length;
980 if (readIntoMemory) {
981 WriteFromStream (fs, 0, size, size);
983 WriteFromStream (fs, 0, size, 8192);
991 public void WriteFile (string filename, long offset, long size)
993 FileStream fs = null;
995 fs = File.OpenRead (filename);
996 WriteFromStream (fs, offset, size, 8192);
1003 public void WriteFile (IntPtr fileHandle, long offset, long size)
1005 FileStream fs = null;
1007 fs = new FileStream (fileHandle, FileAccess.Read);
1008 WriteFromStream (fs, offset, size, 8192);
1016 internal void OnCookieAdd (HttpCookie cookie)
1020 [MonoTODO("Do we need this?")]
1021 internal void OnCookieChange (HttpCookie cookie)
1026 internal void GoingToChangeCookieColl ()
1031 internal void ChangedCookieColl ()