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 public HttpResponse (TextWriter output)
70 _bHeadersSent = false;
72 _Headers = new ArrayList ();
74 _sContentType = "text/html";
78 _sCacheControl = null;
81 _bSuppressContent = false;
82 _bSuppressHeaders = false;
83 _bClientDisconnected = false;
90 internal HttpResponse (HttpWorkerRequest WorkerRequest, HttpContext Context)
93 _WorkerRequest = WorkerRequest;
97 _bHeadersSent = false;
99 _Headers = new ArrayList ();
101 _sContentType = "text/html";
105 _sCacheControl = null;
108 _bSuppressContent = false;
109 _bSuppressHeaders = false;
110 _bClientDisconnected = false;
114 _Writer = new HttpWriter (this);
115 _TextWriter = _Writer;
118 internal void FinalFlush ()
123 internal void DoFilter (bool really)
125 if (really && null != _Writer)
126 _Writer.FilterData (true);
131 internal bool IsCached {
132 get { return cached_response != null; }
135 internal void CacheResponse (HttpRequest request) {
136 cached_response = new CachedRawResponse (_CachePolicy, request);
139 internal CachedRawResponse GetCachedResponse () {
140 cached_response.StatusCode = StatusCode;
141 cached_response.StatusDescription = StatusDescription;
142 return cached_response;
145 internal void SetCachedHeaders (ArrayList headers)
147 cached_headers = headers;
150 [MonoTODO("We need to add cache headers also")]
151 private ArrayList GenerateHeaders ()
153 ArrayList oHeaders = new ArrayList (_Headers.ToArray ());
155 oHeaders.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
156 // save culture info, we need us info here
157 CultureInfo oSavedInfo = Thread.CurrentThread.CurrentCulture;
158 Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
160 string date = DateTime.Now.ToUniversalTime ().ToString ("ddd, d MMM yyyy HH:mm:ss ");
161 oHeaders.Add (new HttpResponseHeader ("Date", date + "GMT"));
163 Thread.CurrentThread.CurrentCulture = oSavedInfo;
165 if (_lContentLength > 0) {
166 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentLength,
167 _lContentLength.ToString ()));
170 if (_sContentType != null) {
171 if (_sContentType.IndexOf ("charset=") == -1) {
172 if (Charset.Length == 0) {
173 Charset = ContentEncoding.HeaderName;
176 // Time to build our string
177 if (Charset.Length > 0) {
178 _sContentType += "; charset=" + Charset;
182 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentType,
186 if (_CachePolicy != null)
187 _CachePolicy.SetHeaders (this, oHeaders);
189 if (_sCacheControl != null) {
190 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderPragma,
194 if (_sTransferEncoding != null) {
195 oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderTransferEncoding,
196 _sTransferEncoding));
199 if (_Cookies != null) {
200 int length = _Cookies.Count;
201 for (int i = 0; i < length; i++) {
202 oHeaders.Add (_Cookies.Get (i).GetCookieHeader ());
209 private void SendHeaders ()
211 _WorkerRequest.SendStatus (StatusCode, StatusDescription);
215 if (cached_headers != null)
216 oHeaders = cached_headers;
218 oHeaders = GenerateHeaders ();
220 if (cached_response != null)
221 cached_response.SetHeaders (oHeaders);
223 foreach (HttpResponseHeader oHeader in oHeaders)
224 oHeader.SendContent (_WorkerRequest);
226 _bHeadersSent = true;
232 return String.Format ("{0} {1}", StatusCode, StatusDescription);
240 iCode = Int32.Parse (value.Substring (0, value.IndexOf (' ')));
241 sMsg = value.Substring (value.IndexOf (' ') + 1);
242 } catch (Exception) {
243 throw new HttpException ("Invalid status string");
247 StatusDescription = sMsg;
252 public void AddCacheItemDependencies (ArrayList cacheKeys)
254 throw new NotImplementedException ();
258 public void AddCacheItemDependency(string cacheKey)
260 throw new NotImplementedException ();
263 public void AddFileDependencies (ArrayList filenames)
265 if (filenames == null || filenames.Count == 0)
268 if (fileDependencies == null) {
269 fileDependencies = (ArrayList) filenames.Clone ();
273 foreach (string fn in filenames)
274 AddFileDependency (fn);
277 public void AddFileDependency (string filename)
279 if (fileDependencies == null)
280 fileDependencies = new ArrayList ();
282 fileDependencies.Add (filename);
285 public void AddHeader (string name, string value)
287 AppendHeader(name, value);
290 public void AppendCookie (HttpCookie cookie)
293 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
295 Cookies.Add (cookie);
299 public void AppendToLog (string param)
301 throw new NotImplementedException ();
304 public string ApplyAppPathModifier (string virtualPath)
306 if (virtualPath == null)
309 if (virtualPath == "")
310 return _Context.Request.RootVirtualDir;
312 if (UrlUtils.IsRelativeUrl (virtualPath)) {
313 virtualPath = UrlUtils.Combine (_Context.Request.RootVirtualDir, virtualPath);
314 } else if (UrlUtils.IsRooted (virtualPath)) {
315 virtualPath = UrlUtils.Reduce (virtualPath);
328 BufferOutput = value;
332 public bool BufferOutput
346 public HttpCachePolicy Cache
349 if (null == _CachePolicy)
350 _CachePolicy = new HttpCachePolicy ();
356 [MonoTODO("Set status in the cache policy")]
357 public string CacheControl
360 return _sCacheControl;
365 throw new HttpException ("Headers has been sent to the client");
367 _sCacheControl = value;
371 public string Charset
374 if (null == _sCharset)
375 _sCharset = ContentEncoding.WebName;
382 throw new HttpException ("Headers has been sent to the client");
388 public Encoding ContentEncoding
391 if (_ContentEncoding == null)
392 _ContentEncoding = WebEncoding.ResponseEncoding;
394 return _ContentEncoding;
399 throw new ArgumentNullException ("Can't set a null as encoding");
401 _ContentEncoding = value;
408 public string ContentType
411 return _sContentType;
416 throw new HttpException ("Headers has been sent to the client");
418 _sContentType = value;
422 public HttpCookieCollection Cookies
425 if (null == _Cookies)
426 _Cookies = new HttpCookieCollection (this, false);
432 [MonoTODO("Set expires in the cache policy")]
436 throw new NotImplementedException ();
440 throw new NotImplementedException ();
444 [MonoTODO("Set expiresabsolute in the cache policy")]
445 public DateTime ExpiresAbsolute
448 throw new NotImplementedException ();
452 throw new NotImplementedException ();
460 return _Writer.GetActiveFilter ();
467 throw new HttpException ("Filtering is not allowed");
469 _Writer.ActivateFilter (value);
473 public bool IsClientConnected
476 if (_ClientDisconnected)
479 if (null != _WorkerRequest && (!_WorkerRequest.IsClientConnected ())) {
480 _ClientDisconnected = false;
488 public TextWriter Output
495 public Stream OutputStream
499 throw new HttpException ("an Output stream not available when " +
500 "running with custom text writer");
502 return _Writer.OutputStream;
506 public string StatusDescription
509 if (null == _sStatusDescription)
510 _sStatusDescription =
511 HttpWorkerRequest.GetStatusDescription (_iStatusCode);
513 return _sStatusDescription;
518 throw new HttpException ("Headers has been sent to the client");
520 _sStatusDescription = value;
524 public int StatusCode
532 throw new HttpException ("Headers has been sent to the client");
534 _sStatusDescription = null;
535 _iStatusCode = value;
539 public bool SuppressContent
542 return _bSuppressContent;
547 throw new HttpException ("Headers has been sent to the client");
549 _bSuppressContent = true;
556 if (_Context == null)
559 return _Context.Request;
563 internal void AppendHeader (int iIndex, string value)
566 throw new HttpException ("Headers has been sent to the client");
569 case HttpWorkerRequest.HeaderContentLength:
570 _lContentLength = Int64.Parse (value);
572 case HttpWorkerRequest.HeaderContentEncoding:
573 _sContentType = value;
575 case HttpWorkerRequest.HeaderTransferEncoding:
576 _sTransferEncoding = value;
577 if (value.Equals ("chunked")) {
583 case HttpWorkerRequest.HeaderPragma:
584 _sCacheControl = value;
587 _Headers.Add (new HttpResponseHeader (iIndex, value));
592 public void AppendHeader (string name, string value)
595 throw new HttpException ("Headers has been sent to the client");
597 switch (name.ToLower ()) {
598 case "content-length":
599 _lContentLength = Int64.Parse (value);
602 _sContentType = value;
604 case "transfer-encoding":
605 _sTransferEncoding = value;
606 if (value.Equals ("chunked")) {
613 _sCacheControl = value;
616 _Headers.Add (new HttpResponseHeader (name, value));
621 internal TextWriter SetTextWriter (TextWriter w)
623 TextWriter prev = _TextWriter;
628 public void BinaryWrite (byte [] buffer)
630 OutputStream.Write (buffer, 0, buffer.Length);
639 public void ClearContent ()
644 public void ClearHeaders ()
647 throw new HttpException ("Headers has been sent to the client");
649 _sContentType = "text/html";
653 _Headers = new ArrayList ();
654 _sCacheControl = null;
655 _sTransferEncoding = null;
658 _bSuppressContent = false;
659 _bSuppressHeaders = false;
660 _bClientDisconnected = false;
665 if (closed && !_bClientDisconnected) {
666 _bClientDisconnected = false;
667 _WorkerRequest.CloseConnection ();
668 _bClientDisconnected = true;
672 internal void Dispose ()
674 if (_Writer != null) {
680 [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]
681 internal void FlushAtEndOfRequest ()
693 _Context.ApplicationInstance.CompleteRequest ();
699 throw new HttpException ("Response already finished.");
704 private void Flush (bool bFinish)
706 if (_bFlushing || closed)
711 if (_Writer == null) {
712 _TextWriter.Flush ();
718 if (_bClientDisconnected)
721 long length = _Writer.BufferSize;
722 if (!_bHeadersSent && !_bSuppressHeaders) {
724 if (length == 0 && _lContentLength == 0)
725 _sContentType = null;
728 length = _Writer.BufferSize;
730 _WorkerRequest.SendCalculatedContentLength ((int) length);
732 if (_lContentLength == 0 && _iStatusCode == 200 &&
733 _sTransferEncoding == null) {
734 // Check we are going todo chunked encoding
735 string sProto = Request.ServerVariables ["SERVER_PROTOCOL"];
736 sProto = "HTTP/1.0"; // Remove this line when we support properly
739 if (sProto != null && sProto == "HTTP/1.1") {
741 HttpWorkerRequest.HeaderTransferEncoding,
744 // Just in case, the old browsers send a HTTP/1.0
745 // request with Connection: Keep-Alive
747 HttpWorkerRequest.HeaderConnection,
752 length = _Writer.BufferSize;
758 _Writer.FilterData (false);
759 length = _Writer.BufferSize;
763 _WorkerRequest.FlushResponse (bFinish);
769 if (!_bSuppressContent && Request.HttpMethod == "HEAD")
770 _bSuppressContent = true;
772 if (!_bSuppressContent) {
773 _bClientDisconnected = false;
775 Encoding oASCII = Encoding.ASCII;
777 string chunk = Convert.ToString(_Writer.BufferSize, 16);
778 byte [] arrPrefix = oASCII.GetBytes (chunk + s_sChunkedPrefix);
780 _WorkerRequest.SendResponseFromMemory (arrPrefix,
783 _Writer.SendContent (_WorkerRequest);
785 _WorkerRequest.SendResponseFromMemory (s_arrChunkSuffix,
786 s_arrChunkSuffix.Length);
788 _WorkerRequest.SendResponseFromMemory (
789 s_arrChunkEnd, s_arrChunkEnd.Length);
791 _Writer.SendContent (_WorkerRequest);
795 _WorkerRequest.FlushResponse (bFinish);
797 cached_response.SetData (_Writer.GetBuffer ());
806 public void Pics (string value)
808 AppendHeader ("PICS-Label", value);
812 public void Redirect (string url)
814 Redirect (url, true);
817 public void Redirect (string url, bool endResponse)
820 throw new HttpException ("Headers has been sent to the client");
824 url = ApplyAppPathModifier (url);
826 AppendHeader (HttpWorkerRequest.HeaderLocation, url);
828 // Text for browsers that can't handle location header
829 Write ("<html><head><title>Object moved</title></head><body>\r\n");
830 Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
831 Write ("</body><html>\r\n");
837 public void Write (char ch)
839 _TextWriter.Write(ch);
842 public void Write (object obj)
844 _TextWriter.Write(obj);
847 public void Write (string str)
849 _TextWriter.Write (str);
852 public void Write (char [] buffer, int index, int count)
854 _TextWriter.Write (buffer, index, count);
858 public static void RemoveOutputCacheItem (string path)
860 throw new NotImplementedException ();
863 public void SetCookie (HttpCookie cookie)
866 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
868 Cookies.Add (cookie);
871 private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
873 if (offset < 0 || length <= 0)
876 long stLength = stream.Length;
877 if (offset + length > stLength)
878 length = stLength - offset;
881 stream.Seek (offset, SeekOrigin.Begin);
883 byte [] fileContent = new byte [bufsize];
884 int count = (int) Math.Min (Int32.MaxValue, bufsize);
885 while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
886 _Writer.WriteBytes (fileContent, 0, count);
888 count = (int) Math.Min (length, fileContent.Length);
892 public void WriteFile (string filename)
894 WriteFile (filename, false);
897 public void WriteFile (string filename, bool readIntoMemory)
899 FileStream fs = null;
901 fs = File.OpenRead (filename);
902 long size = fs.Length;
903 if (readIntoMemory) {
904 WriteFromStream (fs, 0, size, size);
906 WriteFromStream (fs, 0, size, 8192);
914 public void WriteFile (string filename, long offset, long size)
916 FileStream fs = null;
918 fs = File.OpenRead (filename);
919 WriteFromStream (fs, offset, size, 8192);
926 public void WriteFile (IntPtr fileHandle, long offset, long size)
928 FileStream fs = null;
930 fs = new FileStream (fileHandle, FileAccess.Read);
931 WriteFromStream (fs, offset, size, 8192);
939 internal void OnCookieAdd (HttpCookie cookie)
943 [MonoTODO("Do we need this?")]
944 internal void OnCookieChange (HttpCookie cookie)
949 internal void GoingToChangeCookieColl ()
954 internal void ChangedCookieColl ()