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