2002-10-24 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpResponse.cs
1 // \r
2 // System.Web.HttpResponse\r
3 //\r
4 // Author:\r
5 //   Patrik Torstensson (Patrik.Torstensson@labs2.com)\r
6 //\r
7 using System;\r
8 using System.Collections;\r
9 using System.IO;\r
10 using System.Text;\r
11 using System.Globalization;\r
12 \r
13 namespace System.Web {\r
14    public sealed class HttpResponse {\r
15       // Chunked encoding static helpers\r
16       private static byte [] s_arrChunkSuffix = { 10, 13 };\r
17       private static byte [] s_arrChunkEnd = { 10 , 13 };\r
18       private static string s_sChunkedPrefix = "\r\n";\r
19 \r
20       private ArrayList _Headers;\r
21                 \r
22       private bool _bClientDisconnected;\r
23       private bool _bSuppressHeaders;\r
24       private bool _bSuppressContent;\r
25       private bool _bChunked;\r
26       private bool _bEnded;\r
27       private bool _bBuffering;\r
28       private bool _bHeadersSent;\r
29       private bool _bFlushing;\r
30       private long _lContentLength;\r
31       private int _iStatusCode;\r
32 \r
33       private bool _ClientDisconnected;\r
34 \r
35       private string    _sContentType;\r
36       private string    _sCacheControl;\r
37       private string    _sTransferEncoding;\r
38       private string    _sCharset;\r
39       private string    _sStatusDescription;\r
40 \r
41       private HttpCookieCollection _Cookies;\r
42       private HttpCachePolicy _CachePolicy;\r
43 \r
44       private Encoding _ContentEncoding;\r
45                 \r
46       private HttpContext _Context;\r
47       private HttpWriter _Writer;\r
48       private TextWriter _TextWriter;\r
49 \r
50       private HttpWorkerRequest _WorkerRequest;\r
51 \r
52       public HttpResponse(TextWriter output) {\r
53          _bBuffering = true;\r
54          _bFlushing = false;\r
55          _bHeadersSent = false;\r
56 \r
57          _Headers = new ArrayList();\r
58 \r
59          _sContentType = "text/html";\r
60 \r
61          _iStatusCode = 200;\r
62          _sCharset = null;\r
63          _sCacheControl = null;\r
64 \r
65          _lContentLength = 0;\r
66          _bSuppressContent = false;\r
67          _bSuppressHeaders = false;\r
68          _bClientDisconnected = false;\r
69 \r
70          _bChunked = false;\r
71 \r
72          _TextWriter = output;\r
73       }\r
74 \r
75       internal HttpResponse(HttpWorkerRequest WorkerRequest, HttpContext Context) {\r
76          _Context = Context;\r
77          _WorkerRequest = WorkerRequest;\r
78 \r
79          _bBuffering = true;\r
80          _bFlushing = false;\r
81          _bHeadersSent = false;\r
82 \r
83          _Headers = new ArrayList();\r
84 \r
85          _sContentType = "text/html";\r
86 \r
87          _iStatusCode = 200;\r
88          _sCharset = null;\r
89          _sCacheControl = null;\r
90 \r
91          _lContentLength = 0;\r
92          _bSuppressContent = false;\r
93          _bSuppressHeaders = false;\r
94          _bClientDisconnected = false;\r
95 \r
96          _bChunked = false;\r
97 \r
98          _Writer = new HttpWriter(this);\r
99          _TextWriter = _Writer;\r
100       }\r
101 \r
102       internal System.Text.Encoder ContentEncoder {\r
103          get {\r
104             return ContentEncoding.GetEncoder();\r
105          }\r
106       }\r
107 \r
108       internal void FinalFlush() {\r
109          Flush(true);\r
110       }\r
111 \r
112                 internal void DoFilter() {\r
113                         if (null != _Writer) \r
114                                 _Writer.FilterData(true);\r
115                 }\r
116 \r
117       [MonoTODO("We need to add cache headers also")]\r
118       private ArrayList GenerateHeaders() {\r
119          ArrayList oHeaders = new ArrayList(_Headers.ToArray());\r
120 \r
121          // save culture info, we need us info here\r
122          CultureInfo oSavedInfo = System.Threading.Thread.CurrentThread.CurrentCulture;\r
123          System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(0x0409);\r
124 \r
125          oHeaders.Add(new HttpResponseHeader("Date", DateTime.Now.ToUniversalTime().ToString("ddd, d MMM yyyy HH:mm:ss") + " GMT"));\r
126 \r
127          System.Threading.Thread.CurrentThread.CurrentCulture = oSavedInfo;\r
128 \r
129          if (_lContentLength > 0) {\r
130             oHeaders.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderContentLength, _lContentLength.ToString()));\r
131          }\r
132 \r
133          if (_sContentType != null) {\r
134             if (_sContentType.IndexOf("charset=") == -1) {\r
135                if (Charset.Length == 0) {\r
136                   Charset = ContentEncoding.HeaderName;\r
137                }\r
138 \r
139                // Time to build our string\r
140                if (Charset.Length > 0) {\r
141                   _sContentType += "; charset=" + Charset;\r
142                }\r
143             }\r
144 \r
145             oHeaders.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderContentType, _sContentType));\r
146          }\r
147 \r
148          if (_sCacheControl != null) {\r
149             oHeaders.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderPragma, _sCacheControl));\r
150          }\r
151 \r
152          if (_sTransferEncoding != null) {\r
153             oHeaders.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderTransferEncoding, _sTransferEncoding));\r
154          }\r
155 \r
156          // TODO: Add Cookie headers..\r
157 \r
158          return oHeaders;\r
159       }\r
160 \r
161       private void SendHeaders() {\r
162          _WorkerRequest.SendStatus(StatusCode, StatusDescription);\r
163                         \r
164          ArrayList oHeaders = GenerateHeaders();\r
165          foreach (object oHeader in oHeaders) {\r
166             ((HttpResponseHeader) oHeader).SendContent(_WorkerRequest);\r
167          }\r
168                         \r
169          _bHeadersSent = true;\r
170       }\r
171 \r
172       public string Status {\r
173          get {\r
174             return StatusCode.ToString() + " " + StatusDescription;\r
175          }\r
176 \r
177          set {\r
178             string sMsg = "OK";\r
179             int iCode = 200;\r
180 \r
181             try {\r
182                iCode = Int32.Parse(value.Substring(0, value.IndexOf(' ')));\r
183                sMsg = value.Substring(value.IndexOf(' ') + 1);\r
184             }\r
185             catch(Exception) {\r
186                throw new HttpException("Invalid status string");\r
187             }\r
188 \r
189             StatusCode = iCode;\r
190             StatusDescription = sMsg;\r
191          }\r
192       }\r
193 \r
194       [MonoTODO()]\r
195       public void AddCacheItemDependencies(ArrayList cacheKeys) {\r
196          throw new NotImplementedException();\r
197       }\r
198 \r
199       [MonoTODO()]\r
200       public void AddCacheItemDependency(string cacheKey) {\r
201          throw new NotImplementedException();\r
202       }\r
203 \r
204       [MonoTODO()]\r
205       public void AddFileDependencies(ArrayList filenames) {\r
206          //throw new NotImplementedException();\r
207       }\r
208 \r
209       [MonoTODO()]\r
210       public void AddFileDependency(string filename) {\r
211          //throw new NotImplementedException();\r
212       }\r
213 \r
214       public void AddHeader(string name, string value) {\r
215          AppendHeader(name, value);\r
216       }\r
217 \r
218       [MonoTODO()]\r
219       public void AppendCookie(HttpCookie cookie) {\r
220          throw new NotImplementedException();\r
221       }\r
222 \r
223       [MonoTODO()]\r
224       public void AppendToLog(string param) {\r
225          throw new NotImplementedException();\r
226       }\r
227 \r
228       [MonoTODO()]\r
229       public string ApplyAppPathModifier(string virtualPath) {\r
230          throw new NotImplementedException();\r
231       }\r
232 \r
233       public bool Buffer {\r
234          get {\r
235             return BufferOutput;\r
236          }\r
237                         \r
238          set {\r
239             BufferOutput = value;\r
240          }\r
241       }\r
242 \r
243       public bool BufferOutput {\r
244          get {\r
245             return _bBuffering;\r
246          }\r
247                         \r
248          set {\r
249             if (_Writer != null) {\r
250                _Writer.Update();\r
251             }\r
252 \r
253             _bBuffering = value;\r
254          }\r
255       }\r
256 \r
257       public HttpCachePolicy Cache {\r
258          get {\r
259             if (null == _CachePolicy) {\r
260                _CachePolicy = new HttpCachePolicy();\r
261             }\r
262 \r
263             return _CachePolicy;\r
264          }\r
265       }\r
266 \r
267       [MonoTODO("Set status in the cache policy")]\r
268       public string CacheControl {\r
269          get {\r
270             return _sCacheControl;\r
271          }\r
272 \r
273          set {\r
274             if (_bHeadersSent) {\r
275                throw new System.Web.HttpException("Headers has been sent to the client");\r
276             }\r
277 \r
278             _sCacheControl = value;\r
279          }\r
280       }\r
281 \r
282       public string Charset {\r
283          get {  \r
284             if (null == _sCharset) {\r
285                _sCharset = ContentEncoding.WebName;\r
286             }\r
287 \r
288             return _sCharset;\r
289          }\r
290          set {\r
291             if (_bHeadersSent) {\r
292                throw new System.Web.HttpException("Headers has been sent to the client");\r
293             }\r
294 \r
295             _sCharset = value;\r
296          }\r
297       }\r
298 \r
299       public Encoding ContentEncoding {\r
300          get {\r
301             if (_ContentEncoding == null) {\r
302                _ContentEncoding = Encoding.UTF8;\r
303             }\r
304 \r
305             return _ContentEncoding;\r
306          }\r
307 \r
308          set {\r
309             if (value == null) {\r
310                throw new ArgumentException("Can't set a null as encoding");\r
311             }\r
312 \r
313             _ContentEncoding = value;\r
314 \r
315             if (_Writer != null) {\r
316                _Writer.Update();\r
317             }\r
318          }\r
319       }\r
320 \r
321       public string ContentType {\r
322          get {\r
323             return _sContentType;\r
324          }\r
325 \r
326          set {\r
327             if (_bHeadersSent) {\r
328                throw new System.Web.HttpException("Headers has been sent to the client");\r
329             }\r
330 \r
331             _sContentType = value;\r
332          }\r
333       }\r
334 \r
335       public HttpCookieCollection Cookies {\r
336          get {\r
337             if (null == _Cookies) {\r
338                _Cookies = new HttpCookieCollection(this, false);\r
339             }\r
340             return _Cookies;\r
341          }\r
342       }\r
343 \r
344       [MonoTODO("Set expires in the cache policy")]\r
345       public int Expires {\r
346          get {\r
347             throw new NotImplementedException();\r
348          }\r
349 \r
350          set {\r
351             throw new NotImplementedException();\r
352          }\r
353       }\r
354 \r
355       [MonoTODO("Set expiresabsolute in the cache policy")]\r
356       public DateTime ExpiresAbsolute {\r
357          get {\r
358             throw new NotImplementedException();\r
359          }\r
360 \r
361          set {\r
362             throw new NotImplementedException();\r
363          }\r
364       }\r
365 \r
366       public Stream Filter {\r
367          get {\r
368             if (_Writer != null) {\r
369                return _Writer.GetActiveFilter();\r
370             }\r
371             return null;\r
372          }\r
373 \r
374          set {\r
375             if (_Writer == null) {\r
376                throw new HttpException("Filtering is not allowed");\r
377             }\r
378 \r
379             _Writer.ActivateFilter(value);\r
380          }\r
381       }\r
382 \r
383       public bool IsClientConnected {\r
384          get {\r
385             if (_ClientDisconnected) {\r
386                return false;\r
387             }\r
388 \r
389             if (null != _WorkerRequest && (!_WorkerRequest.IsClientConnected())) {\r
390                _ClientDisconnected = false;\r
391                return false;\r
392             }\r
393 \r
394             return true;\r
395          }\r
396       }\r
397       \r
398       public TextWriter Output {\r
399          get {\r
400             return _TextWriter;\r
401          }\r
402       }\r
403 \r
404       public Stream OutputStream {\r
405          get {\r
406             if (_Writer == null) {\r
407                throw new System.Web.HttpException("a Output stream not available when running with custom text writer");\r
408             }\r
409 \r
410             return _Writer.OutputStream;\r
411          }\r
412       }\r
413 \r
414       public string StatusDescription {\r
415          get {\r
416             if (null == _sStatusDescription) {\r
417                _sStatusDescription = HttpWorkerRequest.GetStatusDescription(_iStatusCode);\r
418             } \r
419                \r
420             return _sStatusDescription;\r
421          }\r
422 \r
423          set {\r
424             if (_bHeadersSent) {\r
425                throw new System.Web.HttpException("Headers has been sent to the client");\r
426             }\r
427 \r
428             _sStatusDescription = value;\r
429          }\r
430       }\r
431         \r
432       public int StatusCode {\r
433          get {\r
434             return _iStatusCode;\r
435          }\r
436          set {\r
437             if (_bHeadersSent) {\r
438                throw new System.Web.HttpException("Headers has been sent to the client");\r
439             }\r
440 \r
441             _sStatusDescription = null;\r
442             _iStatusCode = value;\r
443          }\r
444       }\r
445 \r
446       public bool SuppressContent {\r
447          get {\r
448             return _bSuppressContent;\r
449          }\r
450                         \r
451          set {\r
452             if (_bHeadersSent) {\r
453                throw new System.Web.HttpException("Headers has been sent to the client");\r
454             }\r
455 \r
456             _bSuppressContent = true;\r
457          }\r
458       }\r
459 \r
460       public HttpRequest Request {\r
461          get {\r
462             return _Context.Request;\r
463          }\r
464       }\r
465 \r
466       internal void AppendHeader(int iIndex, string value) {\r
467          if (_bHeadersSent) {\r
468             throw new System.Web.HttpException("Headers has been sent to the client");\r
469          }\r
470          switch (iIndex) {\r
471             case HttpWorkerRequest.HeaderContentLength :        \r
472                _lContentLength = Int64.Parse(value);\r
473                break;\r
474             case HttpWorkerRequest.HeaderContentEncoding :      \r
475                _sContentType = value;\r
476                break;\r
477             case HttpWorkerRequest.HeaderTransferEncoding :     \r
478                _sTransferEncoding = value;\r
479                if (value.Equals("chunked")) {\r
480                   _bChunked = true;\r
481                } else {\r
482                   _bChunked = false;\r
483                }\r
484                break;\r
485 \r
486             case HttpWorkerRequest.HeaderPragma :       \r
487                _sCacheControl = value;\r
488                break;\r
489 \r
490             default     :       \r
491                _Headers.Add(new HttpResponseHeader(iIndex, value));\r
492                break;\r
493          }\r
494       \r
495 \r
496       }\r
497 \r
498       public void AppendHeader(string name, string value) {\r
499          if (_bHeadersSent) {\r
500             throw new System.Web.HttpException("Headers has been sent to the client");\r
501          }\r
502         \r
503          switch (name.ToLower()) {\r
504             case "content-length"               :       _lContentLength = Int64.Parse(value);\r
505                break;\r
506             case "content-type"                 :       _sContentType = value;\r
507                break;\r
508             case "transfer-encoding"    :       _sTransferEncoding = value;\r
509                if (value.Equals("chunked")) {\r
510                   _bChunked = true;\r
511                } else {\r
512                   _bChunked = false;\r
513                }\r
514 \r
515                break;\r
516             case "pragma"                               :       _sCacheControl = value;\r
517                break;\r
518 \r
519             default                                             :       _Headers.Add(new HttpResponseHeader(name, value));\r
520                break;\r
521          }\r
522       }\r
523         \r
524       public void BinaryWrite(byte [] buffer) {\r
525          OutputStream.Write(buffer, 0, buffer.Length);\r
526       }\r
527 \r
528       public void Clear() {\r
529          if (_Writer != null) {\r
530             _Writer.Clear();\r
531          }\r
532       }\r
533 \r
534       public void ClearContent() {\r
535          Clear();\r
536       }\r
537 \r
538       public void ClearHeaders() {\r
539          if (_bHeadersSent) {\r
540             throw new System.Web.HttpException("Headers has been sent to the client");\r
541          }\r
542 \r
543          _sContentType = "text/html";\r
544 \r
545          _iStatusCode = 200;\r
546          _sCharset = null;\r
547          _Headers = new ArrayList();\r
548          _sCacheControl = null;\r
549          _sTransferEncoding = null;\r
550 \r
551          _lContentLength = 0;\r
552          _bSuppressContent = false;\r
553          _bSuppressHeaders = false;\r
554          _bClientDisconnected = false;\r
555       }\r
556 \r
557       public void Close() {\r
558          _bClientDisconnected = false;\r
559 \r
560          _WorkerRequest.CloseConnection();\r
561 \r
562          _bClientDisconnected = true;\r
563       }\r
564 \r
565       internal void Dispose() {\r
566          if (_Writer != null) {\r
567             _Writer.Dispose();\r
568             _Writer = null;\r
569          }\r
570       }\r
571 \r
572       [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]\r
573       internal void FlushAtEndOfRequest() \r
574       {\r
575          Flush(true);\r
576       }\r
577 \r
578       [MonoTODO("Check timeout and if we can cancel the thread...")]\r
579       public void End() {\r
580          if (!_bEnded) {\r
581             Flush();\r
582             _WorkerRequest.CloseConnection();\r
583 \r
584             _bEnded = true;\r
585          }\r
586       }\r
587 \r
588       public void Flush() {\r
589          Flush(false);\r
590       }\r
591 \r
592       private void Flush(bool bFinish) {\r
593          if (_bFlushing) {\r
594             return;\r
595          }\r
596 \r
597          _bFlushing = true;\r
598 \r
599          if (_Writer != null) {\r
600             _Writer.FlushBuffers();\r
601          } else {\r
602             _TextWriter.Flush();\r
603          }\r
604 \r
605          try {\r
606             if (!_bHeadersSent) {\r
607                if (!_bSuppressHeaders && !_bClientDisconnected) {\r
608                   if (_Writer != null && BufferOutput) {\r
609                      _lContentLength = _Writer.BufferSize;\r
610                   } \r
611                   else {\r
612                      _lContentLength = 0;\r
613                   }\r
614                 \r
615                   if (_lContentLength == 0 && _iStatusCode == 200 && _sTransferEncoding == null) {\r
616                      // Check we are going todo chunked encoding\r
617                      string sProto = _Context.Request.ServerVariables["SERVER_PROTOCOL"];\r
618                      if (sProto != null && sProto == "HTTP/1.1") {\r
619                         AppendHeader(HttpWorkerRequest.HeaderTransferEncoding, "chunked");\r
620                      }  \r
621                      else {\r
622                         // Just in case, the old browsers sends a HTTP/1.0 request with Connection: Keep-Alive\r
623                         AppendHeader(HttpWorkerRequest.HeaderConnection, "Close");\r
624                      }\r
625                   } \r
626 \r
627                   SendHeaders();\r
628                }                                        \r
629             }   \r
630             if ((!_bSuppressContent && Request.HttpMethod.Equals("HEAD")) || _Writer == null) {\r
631                _bSuppressContent = true;\r
632             }\r
633 \r
634             if (!_bSuppressContent) {\r
635                _bClientDisconnected = false;\r
636                if (_bChunked) {\r
637                   Encoding oASCII = Encoding.ASCII;\r
638 \r
639                   byte [] arrPrefix = oASCII.GetBytes(Convert.ToString(_Writer.BufferSize, 16) + s_sChunkedPrefix);\r
640                   _WorkerRequest.SendResponseFromMemory(arrPrefix, arrPrefix.Length);\r
641 \r
642                   _Writer.SendContent(_WorkerRequest);\r
643 \r
644                   _WorkerRequest.SendResponseFromMemory(s_arrChunkSuffix, s_arrChunkSuffix.Length);\r
645                   if (bFinish) {\r
646                      _WorkerRequest.SendResponseFromMemory(s_arrChunkEnd, s_arrChunkEnd.Length);\r
647                   }\r
648                } \r
649                else {\r
650                   _Writer.SendContent(_WorkerRequest);\r
651                }\r
652 \r
653                _WorkerRequest.FlushResponse(bFinish);   \r
654 \r
655                if (!bFinish) {\r
656                   _Writer.Clear();\r
657                }\r
658             }\r
659          }\r
660          finally {\r
661             _bFlushing = false;\r
662          }\r
663       }\r
664 \r
665       public void Pics(string value) {\r
666          AppendHeader("PICS-Label", value);\r
667       }\r
668 \r
669 \r
670       public void Redirect(string url) {\r
671          Redirect(url, true);\r
672       }\r
673 \r
674       //FIXME: [1] this is an ugly hack to make it work until we have SimpleWorkerRequest!\r
675       private string redirectLocation;\r
676       public string RedirectLocation\r
677       {\r
678               get {\r
679                       return redirectLocation;\r
680               }\r
681       }\r
682       public void Redirect(string url, bool endResponse) {\r
683          if (_bHeadersSent) {\r
684             throw new System.Web.HttpException("Headers has been sent to the client");\r
685          }\r
686 \r
687          Clear();\r
688 \r
689          StatusCode = 302;\r
690          redirectLocation = url;\r
691          //[1]AppendHeader(HttpWorkerRequest.HeaderLocation, url);\r
692 \r
693          // Text for browsers that can't handle location header\r
694          Write("<html><head><title>Object moved</title></head><body>\r\n");\r
695          Write("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");\r
696          Write("</body><html>\r\n");\r
697 \r
698          /* [1]\r
699          if (endResponse) {\r
700             End();\r
701          }\r
702          */\r
703       }\r
704 \r
705       public void Write(char ch) {\r
706          _TextWriter.Write(ch);\r
707       }\r
708 \r
709       public void Write(object obj) {\r
710          _TextWriter.Write(obj);\r
711       }\r
712 \r
713       public void Write(string str) {\r
714          _TextWriter.Write(str);\r
715       }\r
716 \r
717       public void Write(char [] buffer, int index, int count) {\r
718          _TextWriter.Write(buffer, index, count);\r
719       }\r
720 \r
721       [MonoTODO()]\r
722       public static void RemoveOutputCacheItem(string path) {\r
723          throw new NotImplementedException();\r
724       }\r
725 \r
726       [MonoTODO()]\r
727       public void SetCookie(HttpCookie cookie) {\r
728          throw new NotImplementedException();\r
729       }\r
730 \r
731       public void WriteFile(string filename) {\r
732          WriteFile(filename, false);\r
733       }\r
734 \r
735       [MonoTODO()]\r
736       public void WriteFile(string filename, bool readIntoMemory) {\r
737          throw new NotImplementedException();\r
738       }\r
739       \r
740       [MonoTODO()]\r
741       public void WriteFile(string filename, long offset, long size) {\r
742          throw new NotImplementedException();\r
743       }\r
744 \r
745       [MonoTODO("Should we support fileHandle ptrs?")]\r
746       public void WriteFile(IntPtr fileHandle, long offset, long size) {\r
747       }   \r
748 \r
749       [MonoTODO()]\r
750       internal void OnCookieAdd(HttpCookie cookie) {\r
751       }\r
752 \r
753       [MonoTODO("Do we need this?")]\r
754       internal void OnCookieChange(HttpCookie cookie) {\r
755       }\r
756 \r
757       [MonoTODO()]\r
758       internal void GoingToChangeCookieColl() {\r
759       }\r
760 \r
761       [MonoTODO()]\r
762       internal void ChangedCookieColl() {\r
763       }\r
764    }\r
765 }\r