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