2002-12-05 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                                                         sProto = "HTTP/1.0"; // Remove this line when we support properly
680                                                                              // chunked content
681
682                                                         if (sProto != null && sProto == "HTTP/1.1") {
683                                                                 AppendHeader (
684                                                                         HttpWorkerRequest.HeaderTransferEncoding,
685                                                                         "chunked");
686                                                         }  else {
687                                                                 // Just in case, the old browsers send a HTTP/1.0
688                                                                 // request with Connection: Keep-Alive
689                                                                 AppendHeader (
690                                                                         HttpWorkerRequest.HeaderConnection,
691                                                                         "Close");
692                                                         }
693                                                 }
694
695                                                 SendHeaders ();
696                                         }
697                                 }
698
699                                 if (!_bSuppressContent && Request.HttpMethod == "HEAD")
700                                         _bSuppressContent = true;
701
702                                 if (!_bSuppressContent) {
703                                         _bClientDisconnected = false;
704                                         if (_bChunked) {
705                                                 Encoding oASCII = Encoding.ASCII;
706
707                                                 string chunk = Convert.ToString(_Writer.BufferSize, 16);
708                                                 byte [] arrPrefix = oASCII.GetBytes (chunk + s_sChunkedPrefix);
709
710                                                 _WorkerRequest.SendResponseFromMemory (arrPrefix,
711                                                                                        arrPrefix.Length);
712
713                                                 _Writer.SendContent (_WorkerRequest);
714
715                                                 _WorkerRequest.SendResponseFromMemory (s_arrChunkSuffix,
716                                                                                        s_arrChunkSuffix.Length);
717                                                 if (bFinish)
718                                                         _WorkerRequest.SendResponseFromMemory (
719                                                                         s_arrChunkEnd, s_arrChunkEnd.Length);
720                                         } else {
721                                                 _Writer.SendContent (_WorkerRequest);
722                                         }
723                                 } else {
724                                         _Writer.Clear ();
725                                 }
726
727                                 _WorkerRequest.FlushResponse (bFinish);
728
729                                 if (!bFinish)
730                                         _Writer.Clear ();
731                         } finally {
732                                 _bFlushing = false;
733                         }
734                 }
735
736                 public void Pics (string value)
737                 {
738                         AppendHeader ("PICS-Label", value);
739                 }
740
741
742                 public void Redirect (string url)
743                 {
744                         Redirect (url, true);
745                 }
746
747                 public void Redirect (string url, bool endResponse)
748                 {
749                         if (_bHeadersSent)
750                                 throw new HttpException ("Headers has been sent to the client");
751
752                         Clear ();
753
754                         StatusCode = 302;
755                         AppendHeader (HttpWorkerRequest.HeaderLocation, url);
756
757                         // Text for browsers that can't handle location header
758                         Write ("<html><head><title>Object moved</title></head><body>\r\n");
759                         Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
760                         Write ("</body><html>\r\n");
761
762                         if (endResponse)
763                                 End ();
764                 }
765
766                 public void Write (char ch)
767                 {
768                         _TextWriter.Write(ch);
769                 }
770
771                 public void Write (object obj)
772                 {
773                         _TextWriter.Write(obj);
774                 }
775
776                 public void Write (string str)
777                 {
778                         _TextWriter.Write (str);
779                 }
780
781                 public void Write (char [] buffer, int index, int count)
782                 {
783                         _TextWriter.Write (buffer, index, count);
784                 }
785
786                 [MonoTODO()]
787                 public static void RemoveOutputCacheItem (string path)
788                 {
789                         throw new NotImplementedException ();
790                 }
791
792                 public void SetCookie (HttpCookie cookie)
793                 {
794                         if (_bHeadersSent)
795                                 throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
796
797                         Cookies.Add (cookie);
798                 }
799
800                 private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
801                 {
802                         if (offset < 0 || length <= 0)
803                                 return;
804                         
805                         long stLength = stream.Length;
806                         if (offset + length > stLength)
807                                 length = stLength - offset;
808
809                         if (offset > 0)
810                                 stream.Seek (offset, SeekOrigin.Begin);
811
812                         byte [] fileContent = new byte [bufsize];
813                         int count = (int) Math.Min (Int32.MaxValue, bufsize);
814                         while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
815                                 _Writer.WriteBytes (fileContent, 0, count);
816                                 length -= count;
817                                 count = (int) Math.Min (length, fileContent.Length);
818                         }
819                 }
820
821                 public void WriteFile (string filename)
822                 {
823                         WriteFile (filename, false);
824                 }
825
826                 public void WriteFile (string filename, bool readIntoMemory)
827                 {
828                         FileStream fs = null;
829                         try {
830                                 fs = File.OpenRead (filename);
831                                 long size = fs.Length;
832                                 if (readIntoMemory) {
833                                         WriteFromStream (fs, 0, size, size);
834                                 } else {
835                                         WriteFromStream (fs, 0, size, 8192);
836                                 }
837                         } finally {
838                                 if (fs != null)
839                                         fs.Close ();
840                         }
841                 }
842
843                 public void WriteFile (string filename, long offset, long size)
844                 {
845                         FileStream fs = null;
846                         try {
847                                 fs = File.OpenRead (filename);
848                                 WriteFromStream (fs, offset, size, 8192);
849                         } finally {
850                                 if (fs != null)
851                                         fs.Close ();
852                         }
853                 }
854
855                 public void WriteFile (IntPtr fileHandle, long offset, long size)
856                 {
857                         FileStream fs = null;
858                         try {
859                                 fs = new FileStream (fileHandle, FileAccess.Read);
860                                 WriteFromStream (fs, offset, size, 8192);
861                         } finally {
862                                 if (fs != null)
863                                         fs.Close ();
864                         }
865                 }   
866
867                 [MonoTODO()]
868                 internal void OnCookieAdd (HttpCookie cookie)
869                 {
870                 }
871
872                 [MonoTODO("Do we need this?")]
873                 internal void OnCookieChange (HttpCookie cookie)
874                 {
875                 }
876
877                 [MonoTODO()]
878                 internal void GoingToChangeCookieColl ()
879                 {
880                 }
881
882                 [MonoTODO()]
883                 internal void ChangedCookieColl ()
884                 {
885                 }
886         }
887 }
888