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 bool _ClientDisconnected;
46 string _sCacheControl;
47 string _sTransferEncoding;
49 string _sStatusDescription;
51 HttpCookieCollection _Cookies;
52 HttpCachePolicy _CachePolicy;
54 Encoding _ContentEncoding;
58 TextWriter _TextWriter;
60 HttpWorkerRequest _WorkerRequest;
62 ArrayList fileDependencies;
63 CachedRawResponse cached_response;
64 ArrayList cached_headers;
66 string app_path_mod = null;
68 public HttpResponse (TextWriter output)
72 _bHeadersSent = false;
74 _Headers = new ArrayList ();
76 _sContentType = "text/html";
80 _sCacheControl = null;
83 _bSuppressContent = false;
84 _bSuppressHeaders = false;
85 _bClientDisconnected = false;
92 internal HttpResponse (HttpWorkerRequest WorkerRequest, HttpContext Context)
95 _WorkerRequest = WorkerRequest;
99 _bHeadersSent = false;
101 _Headers = new ArrayList ();
103 _sContentType = "text/html";
107 _sCacheControl = null;
110 _bSuppressContent = false;
111 _bSuppressHeaders = false;
112 _bClientDisconnected = false;
117 internal void InitializeWriter ()
119 // We cannot do this in the .ctor because HttpWriter uses configuration and
120 // it may not be initialized
121 if (_Writer == null) {
122 _Writer = new HttpWriter (this);
123 _TextWriter = _Writer;
127 internal void FinalFlush ()
132 internal void DoFilter (bool really)
134 if (really && null != _Writer)
135 _Writer.FilterData (true);
140 internal bool IsCached {
141 get { return cached_response != null; }
144 internal void CacheResponse (HttpRequest request) {
145 cached_response = new CachedRawResponse (_CachePolicy);
148 internal CachedRawResponse GetCachedResponse () {
149 cached_response.StatusCode = StatusCode;
150 cached_response.StatusDescription = StatusDescription;
151 return cached_response;
154 internal void SetCachedHeaders (ArrayList headers)
156 cached_headers = headers;
159 [MonoTODO("We need to add cache headers also")]
160 private ArrayList GenerateHeaders ()
162 ArrayList oHeaders = new ArrayList (_Headers.ToArray ());
164 oHeaders.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
165 // save culture info, we need us info here
166 CultureInfo oSavedInfo = Thread.CurrentThread.CurrentCulture;
167 Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
169 string date = DateTime.Now.ToUniversalTime ().ToString ("ddd, d MMM yyyy HH:mm:ss ");
170 oHeaders.Add (new HttpResponseHeader ("Date", date + "GMT"));
172 Thread.CurrentThread.CurrentCulture = oSavedInfo;
174 if (_lContentLength > 0) {
175 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentLength,
176 _lContentLength.ToString ()));
179 if (_sContentType != null) {
180 if (_sContentType.IndexOf ("charset=") == -1) {
181 if (Charset.Length == 0) {
182 Charset = ContentEncoding.HeaderName;
185 // Time to build our string
186 if (Charset.Length > 0) {
187 _sContentType += "; charset=" + Charset;
191 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentType,
195 if (_CachePolicy != null)
196 _CachePolicy.SetHeaders (this, oHeaders);
198 if (_sCacheControl != null) {
199 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderPragma,
203 if (_sTransferEncoding != null) {
204 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderTransferEncoding,
205 _sTransferEncoding));
208 if (_Cookies != null) {
209 int length = _Cookies.Count;
210 for (int i = 0; i < length; i++) {
211 oHeaders.Add (_Cookies.Get (i).GetCookieHeader ());
218 private void SendHeaders ()
220 _WorkerRequest.SendStatus (StatusCode, StatusDescription);
224 if (cached_headers != null)
225 oHeaders = cached_headers;
227 oHeaders = GenerateHeaders ();
229 if (cached_response != null)
230 cached_response.SetHeaders (oHeaders);
232 foreach (HttpResponseHeader oHeader in oHeaders)
233 oHeader.SendContent (_WorkerRequest);
235 _bHeadersSent = true;
241 return String.Format ("{0} {1}", StatusCode, StatusDescription);
249 iCode = Int32.Parse (value.Substring (0, value.IndexOf (' ')));
250 sMsg = value.Substring (value.IndexOf (' ') + 1);
251 } catch (Exception) {
252 throw new HttpException ("Invalid status string");
256 StatusDescription = sMsg;
261 public void AddCacheItemDependencies (ArrayList cacheKeys)
263 throw new NotImplementedException ();
267 public void AddCacheItemDependency(string cacheKey)
269 throw new NotImplementedException ();
272 public void AddFileDependencies (ArrayList filenames)
274 if (filenames == null || filenames.Count == 0)
277 if (fileDependencies == null) {
278 fileDependencies = (ArrayList) filenames.Clone ();
282 foreach (string fn in filenames)
283 AddFileDependency (fn);
286 public void AddFileDependency (string filename)
288 if (fileDependencies == null)
289 fileDependencies = new ArrayList ();
291 fileDependencies.Add (filename);
294 public void AddHeader (string name, string value)
296 AppendHeader(name, value);
299 public void AppendCookie (HttpCookie cookie)
302 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
304 Cookies.Add (cookie);
308 public void AppendToLog (string param)
310 throw new NotImplementedException ();
313 public string ApplyAppPathModifier (string virtualPath)
315 if (virtualPath == null)
318 if (virtualPath == "")
319 return _Context.Request.RootVirtualDir;
321 if (UrlUtils.IsRelativeUrl (virtualPath)) {
322 virtualPath = UrlUtils.Combine (_Context.Request.RootVirtualDir, virtualPath);
323 } else if (UrlUtils.IsRooted (virtualPath)) {
324 virtualPath = UrlUtils.Reduce (virtualPath);
327 if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) > 0)
328 virtualPath = UrlUtils.Combine (app_path_mod, virtualPath);
333 internal void SetAppPathModifier (string app_path_mod)
335 this.app_path_mod = app_path_mod;
345 BufferOutput = value;
349 public bool BufferOutput
363 public HttpCachePolicy Cache
366 if (null == _CachePolicy)
367 _CachePolicy = new HttpCachePolicy ();
373 [MonoTODO("Set status in the cache policy")]
374 public string CacheControl
377 return _sCacheControl;
382 throw new HttpException ("Headers has been sent to the client");
384 _sCacheControl = value;
388 public string Charset
391 if (null == _sCharset)
392 _sCharset = ContentEncoding.WebName;
399 throw new HttpException ("Headers has been sent to the client");
405 public Encoding ContentEncoding
408 if (_ContentEncoding == null)
409 _ContentEncoding = WebEncoding.ResponseEncoding;
411 return _ContentEncoding;
416 throw new ArgumentNullException ("Can't set a null as encoding");
418 _ContentEncoding = value;
425 public string ContentType
428 return _sContentType;
433 throw new HttpException ("Headers has been sent to the client");
435 _sContentType = value;
439 public HttpCookieCollection Cookies
442 if (null == _Cookies)
443 _Cookies = new HttpCookieCollection (this, false);
449 [MonoTODO("Set expires in the cache policy")]
453 throw new NotImplementedException ();
457 throw new NotImplementedException ();
461 [MonoTODO("Set expiresabsolute in the cache policy")]
462 public DateTime ExpiresAbsolute
465 throw new NotImplementedException ();
469 throw new NotImplementedException ();
477 return _Writer.GetActiveFilter ();
484 throw new HttpException ("Filtering is not allowed");
486 _Writer.ActivateFilter (value);
490 public bool IsClientConnected
493 if (_ClientDisconnected)
496 if (null != _WorkerRequest && (!_WorkerRequest.IsClientConnected ())) {
497 _ClientDisconnected = false;
505 public TextWriter Output
512 public Stream OutputStream
516 throw new HttpException ("an Output stream not available when " +
517 "running with custom text writer");
519 return _Writer.OutputStream;
523 public string StatusDescription
526 if (null == _sStatusDescription)
527 _sStatusDescription =
528 HttpWorkerRequest.GetStatusDescription (_iStatusCode);
530 return _sStatusDescription;
535 throw new HttpException ("Headers has been sent to the client");
537 _sStatusDescription = value;
541 public int StatusCode
549 throw new HttpException ("Headers has been sent to the client");
551 _sStatusDescription = null;
552 _iStatusCode = value;
556 public bool SuppressContent
559 return _bSuppressContent;
564 throw new HttpException ("Headers has been sent to the client");
566 _bSuppressContent = true;
573 if (_Context == null)
576 return _Context.Request;
580 internal void AppendHeader (int iIndex, string value)
583 throw new HttpException ("Headers has been sent to the client");
586 case HttpWorkerRequest.HeaderContentLength:
587 _lContentLength = Int64.Parse (value);
589 case HttpWorkerRequest.HeaderContentEncoding:
590 _sContentType = value;
592 case HttpWorkerRequest.HeaderTransferEncoding:
593 _sTransferEncoding = value;
594 if (value.Equals ("chunked")) {
600 case HttpWorkerRequest.HeaderPragma:
601 _sCacheControl = value;
604 _Headers.Add (new HttpResponseHeader (iIndex, value));
609 public void AppendHeader (string name, string value)
612 throw new HttpException ("Headers has been sent to the client");
614 switch (name.ToLower ()) {
615 case "content-length":
616 _lContentLength = Int64.Parse (value);
619 _sContentType = value;
621 case "transfer-encoding":
622 _sTransferEncoding = value;
623 if (value.Equals ("chunked")) {
630 _sCacheControl = value;
633 _Headers.Add (new HttpResponseHeader (name, value));
638 internal TextWriter SetTextWriter (TextWriter w)
640 TextWriter prev = _TextWriter;
645 public void BinaryWrite (byte [] buffer)
647 OutputStream.Write (buffer, 0, buffer.Length);
650 internal void BinaryWrite (byte [] buffer, int start, int length)
652 OutputStream.Write (buffer, start, length);
661 public void ClearContent ()
666 public void ClearHeaders ()
669 throw new HttpException ("Headers has been sent to the client");
671 _sContentType = "text/html";
675 _Headers = new ArrayList ();
676 _sCacheControl = null;
677 _sTransferEncoding = null;
680 _bSuppressContent = false;
681 _bSuppressHeaders = false;
682 _bClientDisconnected = false;
687 if (closed && !_bClientDisconnected) {
688 _bClientDisconnected = false;
689 _WorkerRequest.CloseConnection ();
690 _bClientDisconnected = true;
694 internal void Dispose ()
696 if (_Writer != null) {
702 [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]
703 internal void FlushAtEndOfRequest ()
713 if (_Context.TimeoutPossible)
714 Thread.CurrentThread.Abort (new StepCompleteRequest ());
718 _Context.ApplicationInstance.CompleteRequest ();
724 throw new HttpException ("Response already finished.");
729 private void Flush (bool bFinish)
731 if (_bFlushing || closed)
736 if (_Writer == null) {
737 _TextWriter.Flush ();
743 if (_bClientDisconnected)
746 long length = _Writer.BufferSize;
747 if (!_bHeadersSent && !_bSuppressHeaders) {
749 if (length == 0 && _lContentLength == 0)
750 _sContentType = null;
753 length = _Writer.BufferSize;
755 _WorkerRequest.SendCalculatedContentLength ((int) length);
757 if (_lContentLength == 0 && _iStatusCode == 200 &&
758 _sTransferEncoding == null) {
759 // Check we are going todo chunked encoding
760 string sProto = Request.ServerVariables ["SERVER_PROTOCOL"];
761 sProto = "HTTP/1.0"; // Remove this line when we support properly
764 if (sProto != null && sProto == "HTTP/1.1") {
766 HttpWorkerRequest.HeaderTransferEncoding,
769 // Just in case, the old browsers send a HTTP/1.0
770 // request with Connection: Keep-Alive
772 HttpWorkerRequest.HeaderConnection,
777 length = _Writer.BufferSize;
783 _Writer.FilterData (false);
784 length = _Writer.BufferSize;
788 _WorkerRequest.FlushResponse (bFinish);
794 if (!_bSuppressContent && Request.HttpMethod == "HEAD")
795 _bSuppressContent = true;
797 if (!_bSuppressContent) {
798 _bClientDisconnected = false;
800 Encoding oASCII = Encoding.ASCII;
802 string chunk = Convert.ToString(_Writer.BufferSize, 16);
803 byte [] arrPrefix = oASCII.GetBytes (chunk + s_sChunkedPrefix);
805 _WorkerRequest.SendResponseFromMemory (arrPrefix,
808 _Writer.SendContent (_WorkerRequest);
810 _WorkerRequest.SendResponseFromMemory (s_arrChunkSuffix,
811 s_arrChunkSuffix.Length);
813 _WorkerRequest.SendResponseFromMemory (
814 s_arrChunkEnd, s_arrChunkEnd.Length);
816 _Writer.SendContent (_WorkerRequest);
820 _WorkerRequest.FlushResponse (bFinish);
822 cached_response.ContentLength = (int) length;
823 cached_response.SetData (_Writer.GetBuffer ());
833 public void Pics (string value)
835 AppendHeader ("PICS-Label", value);
839 public void Redirect (string url)
841 Redirect (url, true);
844 public void Redirect (string url, bool endResponse)
847 throw new HttpException ("Headers has been sent to the client");
851 url = ApplyAppPathModifier (url);
853 AppendHeader (HttpWorkerRequest.HeaderLocation, url);
855 // Text for browsers that can't handle location header
856 Write ("<html><head><title>Object moved</title></head><body>\r\n");
857 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
858 Write ("</body><html>\r\n");
864 internal bool RedirectCustomError (string errorPage)
869 if (Request.QueryString ["aspxerrorpath"] != null)
870 return false; // Prevent endless loop
872 Redirect (errorPage + "?aspxerrorpath=" + Request.Path, false);
876 public void Write (char ch)
878 _TextWriter.Write(ch);
881 public void Write (object obj)
883 _TextWriter.Write(obj);
886 public void Write (string str)
888 _TextWriter.Write (str);
891 public void Write (char [] buffer, int index, int count)
893 _TextWriter.Write (buffer, index, count);
897 public static void RemoveOutputCacheItem (string path)
899 throw new NotImplementedException ();
902 public void SetCookie (HttpCookie cookie)
905 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
907 Cookies.Add (cookie);
910 private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
912 if (offset < 0 || length <= 0)
915 long stLength = stream.Length;
916 if (offset + length > stLength)
917 length = stLength - offset;
920 stream.Seek (offset, SeekOrigin.Begin);
922 byte [] fileContent = new byte [bufsize];
923 int count = (int) Math.Min (Int32.MaxValue, bufsize);
924 while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
925 _Writer.WriteBytes (fileContent, 0, count);
927 count = (int) Math.Min (length, fileContent.Length);
931 public void WriteFile (string filename)
933 WriteFile (filename, false);
936 public void WriteFile (string filename, bool readIntoMemory)
938 FileStream fs = null;
940 fs = File.OpenRead (filename);
941 long size = fs.Length;
942 if (readIntoMemory) {
943 WriteFromStream (fs, 0, size, size);
945 WriteFromStream (fs, 0, size, 8192);
953 public void WriteFile (string filename, long offset, long size)
955 FileStream fs = null;
957 fs = File.OpenRead (filename);
958 WriteFromStream (fs, offset, size, 8192);
965 public void WriteFile (IntPtr fileHandle, long offset, long size)
967 FileStream fs = null;
969 fs = new FileStream (fileHandle, FileAccess.Read);
970 WriteFromStream (fs, offset, size, 8192);
978 internal void OnCookieAdd (HttpCookie cookie)
982 [MonoTODO("Do we need this?")]
983 internal void OnCookieChange (HttpCookie cookie)
988 internal void GoingToChangeCookieColl ()
993 internal void ChangedCookieColl ()