2007-08-23 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpResponse.cs
1 //
2 // System.Web.HttpResponse.cs 
3 //
4 // 
5 // Author:
6 //      Miguel de Icaza (miguel@novell.com)
7 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 //
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Text;
32 using System.Web.UI;
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.IO;
36 using System.Web.Caching;
37 using System.Threading;
38 using System.Web.Util;
39 using System.Web.Configuration;
40 using System.Globalization;
41 using System.Security.Permissions;
42 #if TARGET_J2EE
43 using vmw.@internal.j2ee;
44 #endif
45
46 namespace System.Web {
47         
48         // CAS - no InheritanceDemand here as the class is sealed
49         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50         public sealed class HttpResponse {
51                 internal HttpWorkerRequest WorkerRequest;
52                 internal HttpResponseStream output_stream;
53                 internal bool buffer = true;
54                 
55                 HttpContext context;
56                 TextWriter writer;
57                 HttpCachePolicy cache_policy;
58                 Encoding encoding;
59                 HttpCookieCollection cookies;
60                 
61                 int status_code = 200;
62                 string status_description = "OK";
63
64                 string content_type = "text/html";
65                 string charset;
66                 bool charset_set;
67                 CachedRawResponse cached_response;
68                 string user_cache_control = "private";
69                 string redirect_location;
70                 
71                 //
72                 // Negative Content-Length means we auto-compute the size of content-length
73                 // can be overwritten with AppendHeader ("Content-Length", value)
74                 //
75                 long content_length = -1;
76
77                 //
78                 // The list of the headers that we will send back to the client, except
79                 // the headers that we compute here.
80                 //
81                 ArrayList headers = new ArrayList ();
82                 bool headers_sent;
83                 ArrayList cached_headers;
84
85                 //
86                 // Transfer encoding state
87                 //
88                 string transfer_encoding;
89                 internal bool use_chunked;
90                 
91                 bool closed;
92                 internal bool suppress_content;
93
94                 //
95                 // Session State
96                 //
97                 string app_path_mod;
98                 
99                 //
100                 // Passed as flags
101                 //
102                 internal object FlagEnd = new object ();
103
104 #if NET_2_0
105                 bool is_request_being_redirected;
106                 Encoding headerEncoding;
107 #endif
108                 
109                 internal HttpResponse ()
110                 {
111                         output_stream = new HttpResponseStream (this);
112                 }
113
114                 public HttpResponse (TextWriter writer) : this ()
115                 {
116                         this.writer = writer;
117                 }
118
119                 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
120                 {
121                         WorkerRequest = worker_request;
122                         this.context = context;
123
124                         if (worker_request != null)
125                                 use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
126                 }
127                 
128                 internal TextWriter SetTextWriter (TextWriter writer)
129                 {
130                         TextWriter prev = this.writer;
131                         this.writer = writer;
132                         return prev;
133                 }
134                 
135                 public bool Buffer {
136                         get {
137                                 return buffer;
138                         }
139
140                         set {
141                                 buffer = value;
142                         }
143                 }
144
145                 public bool BufferOutput {
146                         get {
147                                 return buffer;
148                         }
149
150                         set {
151                                 buffer = value;
152                         }
153                 }
154
155                 //
156                 // Use the default from <globalization> section if the client has not set the encoding
157                 //
158                 public Encoding ContentEncoding {
159                         get {
160                                 if (encoding == null) {
161                                         if (context != null) {
162                                                 string client_content_type = context.Request.ContentType;
163                                                 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
164                                                 if (parameter != null) {
165                                                         try {
166                                                                 // Do what the #1 web server does
167                                                                 encoding = Encoding.GetEncoding (parameter);
168                                                         } catch {
169                                                         }
170                                                 }
171                                         }
172                                         if (encoding == null)
173                                                 encoding = WebEncoding.ResponseEncoding;
174                                 }
175                                 return encoding;
176                         }
177
178                         set {
179                                 if (value == null)
180                                         throw new ArgumentException ("ContentEncoding can not be null");
181
182                                 encoding = value;
183                                 HttpWriter http_writer = writer as HttpWriter;
184                                 if (http_writer != null)
185                                         http_writer.SetEncoding (encoding);
186                         }
187                 }
188                 
189                 public string ContentType {
190                         get {
191                                 return content_type;
192                         }
193
194                         set {
195                                 content_type = value;
196                         }
197                 }
198
199                 public string Charset {
200                         get {
201                                 if (charset == null)
202                                         charset = ContentEncoding.WebName;
203                                 
204                                 return charset;
205                         }
206
207                         set {
208                                 charset_set = true;
209                                 charset = value;
210                         }
211                 }
212                 
213                 public HttpCookieCollection Cookies {
214                         get {
215                                 if (cookies == null)
216                                         cookies = new HttpCookieCollection (true, false);
217                                 return cookies;
218                         }
219                 }
220                 
221                 public int Expires {
222                         get {
223                                 if (cache_policy == null)
224                                         return 0;
225
226                                 return cache_policy.ExpireMinutes ();
227                         }
228
229                         set {
230                                 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
231                         }
232                 }
233                 
234                 public DateTime ExpiresAbsolute {
235                         get {
236                                 return Cache.Expires;
237                         }
238
239                         set {
240                                 Cache.SetExpires (value);
241                         }
242                 }
243
244                 public Stream Filter {
245                         get {
246                                 if (WorkerRequest == null)
247                                         return null;
248
249                                 return output_stream.Filter;
250                         }
251
252                         set {
253                                 output_stream.Filter = value;
254                         }
255                 }
256 #if NET_2_0
257                 public Encoding HeaderEncoding {
258                         get {
259                                 if (headerEncoding == null) {
260                                         GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
261
262                                         if (gs == null)
263                                                 headerEncoding = Encoding.UTF8;
264                                         else {
265                                                 headerEncoding = gs.ResponseHeaderEncoding;
266                                                 if (headerEncoding == Encoding.Unicode)
267                                                         throw new HttpException ("HeaderEncoding must not be Unicode");
268                                         }
269                                 }
270                                 return headerEncoding;
271                         }
272                         set {
273                                 if (headers_sent)
274                                         throw new HttpException ("headers have already been sent");
275                                 if (value == null)
276                                         throw new ArgumentNullException ("HeaderEncoding");
277                                 if (value == Encoding.Unicode)
278                                         throw new HttpException ("HeaderEncoding must not be Unicode");
279                                 headerEncoding = value;
280                         }
281                 }
282 #endif
283                 public bool IsClientConnected {
284                         get {
285                                 if (WorkerRequest == null)
286                                         return true; // yep that's true
287
288                                 return WorkerRequest.IsClientConnected ();
289                         }
290                 }
291 #if NET_2_0
292                 public bool IsRequestBeingRedirected {
293                         get { return is_request_being_redirected; }
294                 }
295 #endif
296                 public TextWriter Output {
297                         get {
298                                 if (writer == null)
299                                         writer = new HttpWriter (this);
300
301                                 return writer;
302                         }
303                 }
304
305                 public Stream OutputStream {
306                         get {
307                                 return output_stream;
308                         }
309                 }
310                 
311                 public string RedirectLocation {
312                         get {
313                                 return redirect_location;
314                         }
315
316                         set {
317                                 redirect_location = value;
318                         }
319                 }
320                 
321                 public string Status {
322                         get {
323                                 return String.Format ("{0} {1}", status_code, StatusDescription);
324                         }
325
326                         set {
327                                 int p = value.IndexOf (' ');
328                                 if (p == -1)
329                                         throw new HttpException ("Invalid format for the Status property");
330
331                                 string s = value.Substring (0, p);
332                                 
333 #if NET_2_0
334                                 if (!Int32.TryParse (s, out status_code))
335                                         throw new HttpException ("Invalid format for the Status property");
336 #else
337                                                     
338                                 try {
339                                         status_code = Int32.Parse (s);
340                                 } catch {
341                                         throw new HttpException ("Invalid format for the Status property");
342                                 }
343 #endif
344                                 
345                                 status_description = value.Substring (p+1);
346                         }
347                 }
348
349                 public int StatusCode {
350                         get {
351                                 return status_code;
352                         }
353
354                         set {
355                                 if (headers_sent)
356                                         throw new HttpException ("headers have already been sent");
357                                 
358                                 status_code = value;
359                                 status_description = null;
360                         }
361                 }
362
363                 public string StatusDescription {
364                         get {
365                                 if (status_description == null)
366                                         status_description = HttpWorkerRequest.GetStatusDescription (status_code);
367
368                                 return status_description;
369                         }
370
371                         set {
372                                 if (headers_sent)
373                                         throw new HttpException ("headers have already been sent");
374                                 
375                                 status_description = value;
376                         }
377                 }
378                 
379                 public bool SuppressContent {
380                         get {
381                                 return suppress_content;
382                         }
383
384                         set {
385                                 suppress_content = value;
386                         }
387                 }
388
389 #if NET_2_0
390                 [MonoTODO ("Not implemented")]
391                 public void AddCacheDependency (CacheDependency[] dependencies)
392                 {
393                         throw new NotImplementedException ();
394                 }
395
396                 [MonoTODO ("Not implemented")]
397                 public void AddCacheItemDependencies (string[] cacheKeys)
398                 {
399                         throw new NotImplementedException ();
400                 }
401 #endif
402                 [MonoTODO("Currently does nothing")]
403                 public void AddCacheItemDependencies (ArrayList cacheKeys)
404                 {
405                         // TODO: talk to jackson about the cache
406                 }
407
408                 [MonoTODO("Currently does nothing")]
409                 public void AddCacheItemDependency (string cacheKey)
410                 {
411                         // TODO: talk to jackson about the cache
412                 }
413
414                 [MonoTODO("Currently does nothing")]
415                 public void AddFileDependencies (ArrayList filenames)
416                 {
417                         // TODO: talk to jackson about the cache
418                 }
419 #if NET_2_0
420                 [MonoTODO ("Not implemented")]
421                 public void AddFileDependencies (string[] filenames)
422                 {
423                         throw new NotImplementedException ();
424                 }
425 #endif
426                 [MonoTODO ("Currently does nothing")]
427                 public void AddFileDependency (string filename)
428                 {
429                         // TODO: talk to jackson about the cache
430                 }
431
432                 public void AddHeader (string name, string value)
433                 {
434                         AppendHeader (name, value);
435                 }
436
437                 public void AppendCookie (HttpCookie cookie)
438                 {
439                         Cookies.Add (cookie);
440                 }
441
442                 //
443                 // AppendHeader:
444                 //    Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
445                 //
446                 //
447                 public void AppendHeader (string name, string value)
448                 {
449                         if (headers_sent)
450                                 throw new HttpException ("headers have been already sent");
451                         
452                         if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
453                                 content_length = (long) UInt64.Parse (value);
454                                 use_chunked = false;
455                                 return;
456                         }
457
458                         if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
459                                 ContentType = value;
460                                 return;
461                         }
462
463                         if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
464                                 transfer_encoding = value;
465                                 use_chunked = false;
466                                 return;
467                         }
468
469                         if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
470                                 user_cache_control = value;
471                                 return;
472                         }
473
474                         headers.Add (new UnknownResponseHeader (name, value));
475                 }
476
477                 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
478                 public void AppendToLog (string param)
479                 {
480                         Console.Write ("System.Web: ");
481                         Console.WriteLine (param);
482                 }
483                 
484                 public string ApplyAppPathModifier (string virtualPath)
485                 {
486                         if (virtualPath == null)
487                                 return null;
488                 
489                         if (virtualPath == "")
490                                 return context.Request.RootVirtualDir;
491                 
492                         if (UrlUtils.IsRelativeUrl (virtualPath)) {
493                                 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
494                         } else if (UrlUtils.IsRooted (virtualPath)) {
495                                 virtualPath = UrlUtils.Canonic (virtualPath);
496                         }
497                 
498                         if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
499                                 string rvd = context.Request.RootVirtualDir;
500                                 string basevd = rvd.Replace (app_path_mod, "");
501                 
502                                 if (!StrUtils.StartsWith (virtualPath, basevd))
503                                         return virtualPath;
504                 
505                                 virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
506                         }
507                 
508                         return virtualPath;
509                 }
510
511                 public void BinaryWrite (byte [] buffer)
512                 {
513                         output_stream.Write (buffer, 0, buffer.Length);
514                 }
515
516                 internal void BinaryWrite (byte [] buffer, int start, int len)
517                 {
518                         output_stream.Write (buffer, start, len);
519                 }
520
521                 public void Clear ()
522                 {
523                         ClearContent ();
524                 }
525
526                 public void ClearContent ()
527                 {
528                         output_stream.Clear ();
529                 }
530
531                 public void ClearHeaders ()
532                 {
533                         if (headers_sent)
534                                 throw new HttpException ("headers have been already sent");
535
536                         // Reset the special case headers.
537                         content_length = -1;
538                         content_type = "text/html";
539                         transfer_encoding = null;
540                         user_cache_control = null;
541                         headers.Clear ();
542                 }
543
544                 internal bool HeadersSent {
545                         get {
546                                 return headers_sent;
547                         }
548                 }
549
550                 public void Close ()
551                 {
552                         if (closed)
553                                 return;
554                         if (WorkerRequest != null)
555                                 WorkerRequest.CloseConnection ();
556                         closed = true;
557                 }
558
559                 public void End ()
560                 {
561                         if (context.TimeoutPossible) {
562                                 Thread.CurrentThread.Abort (FlagEnd);
563                         } else {
564                                 // If this is called from an async event, signal the completion
565                                 // but don't throw.
566                                 context.ApplicationInstance.CompleteRequest ();
567                         }
568                 }
569
570                 // Generate:
571                 //   Content-Length
572                 //   Content-Type
573                 //   Transfer-Encoding (chunked)
574                 //   Cache-Control
575                 void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
576                 {
577                         //
578                         // Transfer-Encoding
579                         //
580                         if (use_chunked)
581                                 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
582                         else if (transfer_encoding != null)
583                                 write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
584
585                         UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
586                                         DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
587                         write_headers.Add (date_header);
588
589                         if (IsCached)
590                                 cached_response.DateHeader = date_header;
591                                         
592                         if (redirect_location != null)
593                                 write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
594                         
595                         //
596                         // If Content-Length is set.
597                         //
598                         if (content_length >= 0){
599                                 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
600                                                                       content_length.ToString (CultureInfo.InvariantCulture)));
601                         } else if (BufferOutput){
602                                 if (final_flush){
603                                         //
604                                         // If we are buffering and this is the last flush, not a middle-flush,
605                                         // we know the content-length.
606                                         //
607 #if !TARGET_JVM
608                                         content_length = output_stream.total;
609                                         write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
610                                                                               content_length.ToString (CultureInfo.InvariantCulture)));
611 #endif
612                                 } else {
613                                         //
614                                         // We are buffering, and this is a flush in the middle.
615                                         // If we are not chunked, we need to set "Connection: close".
616                                         //
617                                         if (use_chunked){
618 #if DEBUG
619                                                 Console.WriteLine ("Setting to close2");
620 #endif
621                                                 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
622                                         }
623                                 }
624                         } else {
625                                 //
626                                 // If the content-length is not set, and we are not buffering, we must
627                                 // close at the end.
628                                 //
629                                 if (use_chunked){
630 #if DEBUG
631                                         Console.WriteLine ("Setting to close");
632 #endif
633                                         write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
634                                 }
635                         }
636
637                         //
638                         // Cache Control, the cache policy takes precedence over the cache_control property.
639                         //
640                         if (cache_policy != null)
641                                 cache_policy.SetHeaders (this, headers);
642                         else
643                                 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
644                         
645                         //
646                         // Content-Type
647                         //
648                         if (content_type != null){
649                                 string header = content_type;
650
651                                 if (charset_set || header == "text/plain" || header == "text/html") {
652                                         if (header.IndexOf ("charset=") == -1) {
653                                                 if (charset == null || charset == "")
654                                                         charset = ContentEncoding.HeaderName;
655                                                 header += "; charset=" + charset;
656                                         }
657                                 }
658                                 
659                                 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
660                         }
661
662                         if (cookies != null && cookies.Count != 0){
663                                 int n = cookies.Count;
664                                 for (int i = 0; i < n; i++)
665                                         write_headers.Add (cookies.Get (i).GetCookieHeader ());
666 #if TARGET_J2EE
667                                 // For J2EE Portal support emulate cookies by storing them in the session.
668                                 context.Request.SetSessionCookiesForPortal (cookies);
669 #endif
670                         }
671                 }
672
673                 internal void WriteHeaders (bool final_flush)
674                 {
675                         if (headers_sent)
676                                 return;
677
678                         if (WorkerRequest != null)
679                                 WorkerRequest.SendStatus (status_code, StatusDescription);
680
681                         if (cached_response != null)
682                                 cached_response.SetHeaders (headers);
683
684                         // If this page is cached use the cached headers
685                         // instead of the standard headers      
686                         ArrayList write_headers = headers;
687                         if (cached_headers != null)
688                                 write_headers = cached_headers;
689                         else
690                                 AddHeadersNoCache (write_headers, final_flush);
691
692                         //
693                         // Flush
694                         //
695                         if (context != null) {
696                                 HttpApplication app_instance = context.ApplicationInstance;
697                                 if (app_instance != null)
698                                         app_instance.TriggerPreSendRequestHeaders ();
699                         }
700                         if (WorkerRequest != null) {
701                                 foreach (BaseResponseHeader header in write_headers){
702                                         header.SendContent (WorkerRequest);
703                                 }
704                         }
705                         headers_sent = true;
706                 }
707
708                 internal void DoFilter (bool close)
709                 {
710                         if (output_stream.HaveFilter && context != null && context.Error == null)
711                                 output_stream.ApplyFilter (close);
712                 }
713
714                 internal void Flush (bool final_flush)
715                 {
716                         DoFilter (final_flush);
717                         if (!headers_sent){
718                                 if (final_flush || status_code != 200)
719                                         use_chunked = false;
720                         }
721
722                         bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
723                         if (suppress_content || head) {
724                                 if (!headers_sent)
725                                         WriteHeaders (true);
726                                 output_stream.Clear ();
727                                 if (WorkerRequest != null)
728                                         output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
729                                 return;
730                         }
731
732                         if (!headers_sent)
733                                 WriteHeaders (final_flush);
734
735                         if (context != null) {
736                                 HttpApplication app_instance = context.ApplicationInstance;
737                                 if (app_instance != null)
738                                         app_instance.TriggerPreSendRequestContent ();
739                         }
740
741                         if (IsCached) {
742                                 MemoryStream ms = output_stream.GetData ();
743                                 cached_response.ContentLength = (int) ms.Length;
744                                 cached_response.SetData (ms.GetBuffer ());
745                         }
746
747                         if (WorkerRequest != null)
748                                 output_stream.Flush (WorkerRequest, final_flush);
749                 }
750
751                 public void Flush ()
752                 {
753                         Flush (false);
754                 }
755
756                 public void Pics (string value)
757                 {
758                         AppendHeader ("PICS-Label", value);
759                 }
760
761                 public void Redirect (string url)
762                 {
763                         Redirect (url, true);
764                 }
765
766                 public void Redirect (string url, bool endResponse)
767                 {
768                         if (headers_sent)
769                                 throw new HttpException ("Headers have already been sent");
770
771 #if TARGET_J2EE
772                         // In J2EE portal we need to handle Redirect at the processAction phase
773                         // using the portlet ActionResponse that will send for us the redirect header.
774                         IPortletActionResponse resp = context.ServletResponse as IPortletActionResponse;
775                         if (resp != null) {
776                                 resp.sendRedirect (ApplyAppPathModifier (url));
777                                 if (endResponse)
778                                         End ();
779                                 return;
780                         }
781 #endif
782
783 #if NET_2_0
784                         is_request_being_redirected = true;
785 #endif
786                         ClearHeaders ();
787                         ClearContent ();
788                         
789                         StatusCode = 302;
790                         url = ApplyAppPathModifier (url);
791                         headers.Add (new UnknownResponseHeader ("Location", url));
792
793                         // Text for browsers that can't handle location header
794                         Write ("<html><head><title>Object moved</title></head><body>\r\n");
795                         Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
796                         Write ("</body><html>\r\n");
797                         
798                         if (endResponse)
799                                 End ();
800 #if NET_2_0
801                         is_request_being_redirected = false;
802 #endif
803                 }
804
805                 public static void RemoveOutputCacheItem (string path)
806                 {
807                         if (path == null)
808                                 throw new ArgumentNullException ("path");
809
810                         if (path.Length == 0)
811                                 return;
812
813                         if (path [0] != '/')
814                                 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
815
816                         HttpRuntime.InternalCache.Remove (path);
817                 }
818
819                 public void SetCookie (HttpCookie cookie)
820                 {
821                         AppendCookie (cookie);
822                 }
823
824                 public void Write (char ch)
825                 {
826                         Output.Write (ch);
827                 }
828
829                 public void Write (object obj)
830                 {
831                         if (obj == null)
832                                 return;
833                         
834                         Output.Write (obj.ToString ());
835                 }
836                 
837                 public void Write (string s)
838                 {
839                         Output.Write (s);
840                 }
841                 
842                 public void Write (char [] buffer, int index, int count)
843                 {
844                         Output.Write (buffer, index, count);
845                 }
846
847                 internal void WriteFile (FileStream fs, long offset, long size)
848                 {
849                         byte [] buffer = new byte [32*1024];
850
851                         if (offset != 0)
852                                 fs.Position = offset;
853
854                         long remain = size;
855                         int n;
856                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
857                                 remain -= n;
858                                 output_stream.Write (buffer, 0, n);
859                         }
860                 }
861                 
862                 public void WriteFile (string filename)
863                 {
864                         WriteFile (filename, false);
865                 }
866
867                 public void WriteFile (string filename, bool readIntoMemory)
868                 {
869                         if (filename == null)
870                                 throw new ArgumentNullException ("filename");
871
872                         if (readIntoMemory){
873                                 using (FileStream fs = File.OpenRead (filename))
874                                         WriteFile (fs, 0, fs.Length);
875                         } else {
876                                 FileInfo fi = new FileInfo (filename);
877                                 output_stream.WriteFile (filename, 0, fi.Length);
878                         }
879                         if (buffer)
880                                 return;
881
882                         output_stream.ApplyFilter (false);
883                         Flush ();
884                 }
885
886 #if TARGET_JVM
887                 public void WriteFile (IntPtr fileHandle, long offset, long size) {
888                         throw new PlatformNotSupportedException("IntPtr not supported");
889                 }
890 #else
891                 public void WriteFile (IntPtr fileHandle, long offset, long size)
892                 {
893                         if (offset < 0)
894                                 throw new ArgumentNullException ("offset can not be negative");
895                         if (size < 0)
896                                 throw new ArgumentNullException ("size can not be negative");
897
898                         if (size == 0)
899                                 return;
900
901                         // Note: this .ctor will throw a SecurityException if the caller 
902                         // doesn't have the UnmanagedCode permission
903                         using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
904                                 WriteFile (fs, offset, size);
905
906                         if (buffer)
907                                 return;
908                         output_stream.ApplyFilter (false);
909                         Flush ();
910                 }
911 #endif
912
913                 public void WriteFile (string filename, long offset, long size)
914                 {
915                         if (filename == null)
916                                 throw new ArgumentNullException ("filename");
917                         if (offset < 0)
918                                 throw new ArgumentNullException ("offset can not be negative");
919                         if (size < 0)
920                                 throw new ArgumentNullException ("size can not be negative");
921
922                         if (size == 0)
923                                 return;
924                         
925                         FileStream fs = File.OpenRead (filename);
926                         WriteFile (fs, offset, size);
927
928                         if (buffer)
929                                 return;
930
931                         output_stream.ApplyFilter (false);
932                         Flush ();
933                 }
934 #if NET_2_0
935                 [MonoTODO ("Not implemented")]
936                 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
937                 {
938                         throw new NotImplementedException ();
939                 }
940 #endif
941                 //
942                 // Like WriteFile, but never buffers, so we manually Flush here
943                 //
944                 public void TransmitFile (string filename) 
945                 {
946                         if (filename == null)
947                                 throw new ArgumentNullException ("filename");
948
949                         TransmitFile (filename, false);
950                 }
951
952                 internal void TransmitFile (string filename, bool final_flush)
953                 {
954                         FileInfo fi = new FileInfo (filename);
955                         using (Stream s = fi.OpenRead ()); // Just check if we can read.
956                         output_stream.WriteFile (filename, 0, fi.Length);
957                         output_stream.ApplyFilter (final_flush);
958                         Flush (final_flush);
959                 }
960                 
961
962 #region Session state support
963                 internal void SetAppPathModifier (string app_modifier)
964                 {
965                         app_path_mod = app_modifier;
966                 }
967 #endregion
968                 
969 #region Cache Support
970                 internal void SetCachedHeaders (ArrayList headers)
971                 {
972                         cached_headers = headers;
973                 }
974
975                 internal bool IsCached {
976                         get {
977                                 return cached_response != null;
978                         }
979                 }
980
981                 public HttpCachePolicy Cache {
982                         get {
983                                 if (cache_policy == null) {
984                                         cache_policy = new HttpCachePolicy ();
985                                         cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
986                                 }
987                                 
988                                 return cache_policy;
989                         }
990                 }
991                 
992                 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
993                 {
994                         if (e.Cacheability >= HttpCacheability.Server && !IsCached)
995                                 cached_response = new CachedRawResponse (cache_policy);
996                         else if (e.Cacheability <= HttpCacheability.Private)
997                                 cached_response = null;
998                 }
999
1000                 internal CachedRawResponse GetCachedResponse ()
1001                 {
1002                         cached_response.StatusCode = StatusCode;
1003                         cached_response.StatusDescription = StatusDescription;
1004                         return cached_response;
1005                 }
1006
1007                 //
1008                 // This is one of the old ASP compatibility methods, the real cache
1009                 // control is in the Cache property, and this is a second class citizen
1010                 //
1011                 public string CacheControl {
1012                         set {
1013                                 if (value == null || value == "") {
1014                                         Cache.SetCacheability (HttpCacheability.NoCache);
1015                                         user_cache_control = null;
1016                                 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1017                                         Cache.SetCacheability (HttpCacheability.Public);
1018                                         user_cache_control = "public";
1019                                 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1020                                         Cache.SetCacheability (HttpCacheability.Private);
1021                                         user_cache_control = "private";
1022                                 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1023                                         Cache.SetCacheability (HttpCacheability.NoCache);
1024                                         user_cache_control = "no-cache";
1025                                 } else
1026                                         throw new ArgumentException ("CacheControl property only allows `public', " +
1027                                                                      "`private' or no-cache, for different uses, use " +
1028                                                                      "Response.AppendHeader");
1029                         }
1030
1031                         get { return (user_cache_control != null) ? user_cache_control : "private"; }
1032                 }
1033 #endregion
1034
1035                 internal int GetOutputByteCount ()
1036                 {
1037                         return output_stream.GetTotalLength ();
1038                 }
1039
1040                 internal void ReleaseResources ()
1041                 {
1042                         output_stream.ReleaseResources (true);
1043                         output_stream = null;
1044                 }
1045         }
1046 }
1047