Fix typo in MergeCollections (); refactoring
[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 (Buffer){
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                                         content_length = output_stream.total;
608                                         write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
609                                                                               content_length.ToString (CultureInfo.InvariantCulture)));
610                                 } else {
611                                         //
612                                         // We are buffering, and this is a flush in the middle.
613                                         // If we are not chunked, we need to set "Connection: close".
614                                         //
615                                         if (use_chunked){
616 #if DEBUG
617                                                 Console.WriteLine ("Setting to close2");
618 #endif
619                                                 write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
620                                         }
621                                 }
622                         } else {
623                                 //
624                                 // If the content-length is not set, and we are not buffering, we must
625                                 // close at the end.
626                                 //
627                                 if (use_chunked){
628 #if DEBUG
629                                         Console.WriteLine ("Setting to close");
630 #endif
631                                         write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
632                                 }
633                         }
634
635                         //
636                         // Cache Control, the cache policy takes precedence over the cache_control property.
637                         //
638                         if (cache_policy != null)
639                                 cache_policy.SetHeaders (this, headers);
640                         else
641                                 write_headers.Add (new UnknownResponseHeader ("Cache-Control", CacheControl));
642                         
643                         //
644                         // Content-Type
645                         //
646                         if (content_type != null){
647                                 string header = content_type;
648
649                                 if (charset_set || header == "text/plain" || header == "text/html") {
650                                         if (header.IndexOf ("charset=") == -1) {
651                                                 if (charset == null || charset == "")
652                                                         charset = ContentEncoding.HeaderName;
653                                                 header += "; charset=" + charset;
654                                         }
655                                 }
656                                 
657                                 write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
658                         }
659
660                         if (cookies != null && cookies.Count != 0){
661                                 int n = cookies.Count;
662                                 for (int i = 0; i < n; i++)
663                                         write_headers.Add (cookies.Get (i).GetCookieHeader ());
664 #if TARGET_J2EE
665                                 // For J2EE Portal support emulate cookies by storing them in the session.
666                                 context.Request.SetSessionCookiesForPortal (cookies);
667 #endif
668                         }
669                 }
670
671                 internal void WriteHeaders (bool final_flush)
672                 {
673                         if (headers_sent)
674                                 return;
675
676                         if (WorkerRequest != null)
677                                 WorkerRequest.SendStatus (status_code, StatusDescription);
678
679                         if (cached_response != null)
680                                 cached_response.SetHeaders (headers);
681
682                         // If this page is cached use the cached headers
683                         // instead of the standard headers      
684                         ArrayList write_headers = headers;
685                         if (cached_headers != null)
686                                 write_headers = cached_headers;
687                         else
688                                 AddHeadersNoCache (write_headers, final_flush);
689
690                         //
691                         // Flush
692                         //
693                         if (context != null) {
694                                 HttpApplication app_instance = context.ApplicationInstance;
695                                 if (app_instance != null)
696                                         app_instance.TriggerPreSendRequestHeaders ();
697                         }
698                         if (WorkerRequest != null) {
699                                 foreach (BaseResponseHeader header in write_headers){
700                                         header.SendContent (WorkerRequest);
701                                 }
702                         }
703                         headers_sent = true;
704                 }
705
706                 internal void DoFilter (bool close)
707                 {
708                         if (output_stream.HaveFilter && context != null && context.Error == null)
709                                 output_stream.ApplyFilter (close);
710                 }
711
712                 internal void Flush (bool final_flush)
713                 {
714                         DoFilter (final_flush);
715                         if (!headers_sent){
716                                 if (final_flush || status_code != 200)
717                                         use_chunked = false;
718                         }
719
720                         bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
721                         if (suppress_content || head) {
722                                 if (!headers_sent)
723                                         WriteHeaders (true);
724                                 output_stream.Clear ();
725                                 if (WorkerRequest != null)
726                                         output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
727                                 return;
728                         }
729
730                         if (!headers_sent)
731                                 WriteHeaders (final_flush);
732
733                         if (context != null) {
734                                 HttpApplication app_instance = context.ApplicationInstance;
735                                 if (app_instance != null)
736                                         app_instance.TriggerPreSendRequestContent ();
737                         }
738
739                         if (IsCached) {
740                                 MemoryStream ms = output_stream.GetData ();
741                                 cached_response.ContentLength = (int) ms.Length;
742                                 cached_response.SetData (ms.GetBuffer ());
743                         }
744
745                         if (WorkerRequest != null)
746                                 output_stream.Flush (WorkerRequest, final_flush);
747                 }
748
749                 public void Flush ()
750                 {
751                         Flush (false);
752                 }
753
754                 public void Pics (string value)
755                 {
756                         AppendHeader ("PICS-Label", value);
757                 }
758
759                 public void Redirect (string url)
760                 {
761                         Redirect (url, true);
762                 }
763
764                 public void Redirect (string url, bool endResponse)
765                 {
766                         if (headers_sent)
767                                 throw new HttpException ("header have been already sent");
768
769 #if TARGET_J2EE
770                         // In J2EE portal we need to handle Redirect at the processAction phase
771                         // using the portlet ActionResponse that will send for us the redirect header.
772                         IPortletActionResponse resp = context.ServletResponse as IPortletActionResponse;
773                         if (resp != null) {
774                                 resp.sendRedirect (ApplyAppPathModifier (url));
775                                 if (endResponse)
776                                         End ();
777                                 return;
778                         }
779 #endif
780
781 #if NET_2_0
782                         is_request_being_redirected = true;
783 #endif
784                         ClearHeaders ();
785                         ClearContent ();
786                         
787                         StatusCode = 302;
788                         url = ApplyAppPathModifier (url);
789                         headers.Add (new UnknownResponseHeader ("Location", url));
790
791                         // Text for browsers that can't handle location header
792                         Write ("<html><head><title>Object moved</title></head><body>\r\n");
793                         Write ("<h2>Object moved to <a href='" + url + "'>here</a></h2>\r\n");
794                         Write ("</body><html>\r\n");
795                         
796                         if (endResponse)
797                                 End ();
798 #if NET_2_0
799                         is_request_being_redirected = false;
800 #endif
801                 }
802
803                 public static void RemoveOutputCacheItem (string path)
804                 {
805                         if (path == null)
806                                 throw new ArgumentNullException ("path");
807
808                         if (path.Length == 0)
809                                 return;
810
811                         if (path [0] != '/')
812                                 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
813
814                         HttpRuntime.Cache.Remove (path);
815                 }
816
817                 public void SetCookie (HttpCookie cookie)
818                 {
819                         AppendCookie (cookie);
820                 }
821
822                 public void Write (char ch)
823                 {
824                         Output.Write (ch);
825                 }
826
827                 public void Write (object obj)
828                 {
829                         if (obj == null)
830                                 return;
831                         
832                         Output.Write (obj.ToString ());
833                 }
834                 
835                 public void Write (string s)
836                 {
837                         Output.Write (s);
838                 }
839                 
840                 public void Write (char [] buffer, int index, int count)
841                 {
842                         Output.Write (buffer, index, count);
843                 }
844
845                 internal void WriteFile (FileStream fs, long offset, long size)
846                 {
847                         byte [] buffer = new byte [32*1024];
848
849                         if (offset != 0)
850                                 fs.Position = offset;
851
852                         long remain = size;
853                         int n;
854                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
855                                 remain -= n;
856                                 output_stream.Write (buffer, 0, n);
857                         }
858                 }
859                 
860                 public void WriteFile (string filename)
861                 {
862                         WriteFile (filename, false);
863                 }
864
865                 public void WriteFile (string filename, bool readIntoMemory)
866                 {
867                         if (filename == null)
868                                 throw new ArgumentNullException ("filename");
869
870                         if (readIntoMemory){
871                                 using (FileStream fs = File.OpenRead (filename))
872                                         WriteFile (fs, 0, fs.Length);
873                         } else {
874                                 FileInfo fi = new FileInfo (filename);
875                                 output_stream.WriteFile (filename, 0, fi.Length);
876                         }
877                         if (buffer)
878                                 return;
879
880                         output_stream.ApplyFilter (false);
881                         Flush ();
882                 }
883
884 #if TARGET_JVM
885                 public void WriteFile (IntPtr fileHandle, long offset, long size) {
886                         throw new NotSupportedException("IntPtr not supported");
887                 }
888 #else
889                 public void WriteFile (IntPtr fileHandle, long offset, long size)
890                 {
891                         if (offset < 0)
892                                 throw new ArgumentNullException ("offset can not be negative");
893                         if (size < 0)
894                                 throw new ArgumentNullException ("size can not be negative");
895
896                         if (size == 0)
897                                 return;
898
899                         // Note: this .ctor will throw a SecurityException if the caller 
900                         // doesn't have the UnmanagedCode permission
901                         using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
902                                 WriteFile (fs, offset, size);
903
904                         if (buffer)
905                                 return;
906                         output_stream.ApplyFilter (false);
907                         Flush ();
908                 }
909 #endif
910
911                 public void WriteFile (string filename, long offset, long size)
912                 {
913                         if (filename == null)
914                                 throw new ArgumentNullException ("filename");
915                         if (offset < 0)
916                                 throw new ArgumentNullException ("offset can not be negative");
917                         if (size < 0)
918                                 throw new ArgumentNullException ("size can not be negative");
919
920                         if (size == 0)
921                                 return;
922                         
923                         FileStream fs = File.OpenRead (filename);
924                         WriteFile (fs, offset, size);
925
926                         if (buffer)
927                                 return;
928
929                         output_stream.ApplyFilter (false);
930                         Flush ();
931                 }
932 #if NET_2_0
933                 [MonoTODO ("Not implemented")]
934                 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
935                 {
936                         throw new NotImplementedException ();
937                 }
938 #endif
939                 //
940                 // Like WriteFile, but never buffers, so we manually Flush here
941                 //
942                 public void TransmitFile (string filename) 
943                 {
944                         if (filename == null)
945                                 throw new ArgumentNullException ("filename");
946
947                         TransmitFile (filename, false);
948                 }
949
950                 internal void TransmitFile (string filename, bool final_flush)
951                 {
952                         FileInfo fi = new FileInfo (filename);
953                         using (Stream s = fi.OpenRead ()); // Just check if we can read.
954                         output_stream.WriteFile (filename, 0, fi.Length);
955                         output_stream.ApplyFilter (final_flush);
956                         Flush (final_flush);
957                 }
958                 
959
960 #region Session state support
961                 internal void SetAppPathModifier (string app_modifier)
962                 {
963                         app_path_mod = app_modifier;
964                 }
965 #endregion
966                 
967 #region Cache Support
968                 internal void SetCachedHeaders (ArrayList headers)
969                 {
970                         cached_headers = headers;
971                 }
972
973                 internal bool IsCached {
974                         get {
975                                 return cached_response != null;
976                         }
977                 }
978
979                 public HttpCachePolicy Cache {
980                         get {
981                                 if (cache_policy == null) {
982                                         cache_policy = new HttpCachePolicy ();
983                                         cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
984                                 }
985                                 
986                                 return cache_policy;
987                         }
988                 }
989                 
990                 private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
991                 {
992                         if (e.Cacheability >= HttpCacheability.Server && !IsCached)
993                                 cached_response = new CachedRawResponse (cache_policy);
994                         else if (e.Cacheability <= HttpCacheability.Private)
995                                 cached_response = null;
996                 }
997
998                 internal CachedRawResponse GetCachedResponse ()
999                 {
1000                         cached_response.StatusCode = StatusCode;
1001                         cached_response.StatusDescription = StatusDescription;
1002                         return cached_response;
1003                 }
1004
1005                 //
1006                 // This is one of the old ASP compatibility methods, the real cache
1007                 // control is in the Cache property, and this is a second class citizen
1008                 //
1009                 public string CacheControl {
1010                         set {
1011                                 if (value == null || value == "") {
1012                                         Cache.SetCacheability (HttpCacheability.NoCache);
1013                                         user_cache_control = null;
1014                                 } else if (String.Compare (value, "public", true, CultureInfo.InvariantCulture) == 0) {
1015                                         Cache.SetCacheability (HttpCacheability.Public);
1016                                         user_cache_control = "public";
1017                                 } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
1018                                         Cache.SetCacheability (HttpCacheability.Private);
1019                                         user_cache_control = "private";
1020                                 } else if (String.Compare (value, "no-cache", true, CultureInfo.InvariantCulture) == 0) {
1021                                         Cache.SetCacheability (HttpCacheability.NoCache);
1022                                         user_cache_control = "no-cache";
1023                                 } else
1024                                         throw new ArgumentException ("CacheControl property only allows `public', " +
1025                                                                      "`private' or no-cache, for different uses, use " +
1026                                                                      "Response.AppendHeader");
1027                         }
1028
1029                         get { return (user_cache_control != null) ? user_cache_control : "private"; }
1030                 }
1031 #endregion
1032
1033                 internal int GetOutputByteCount ()
1034                 {
1035                         return output_stream.GetTotalLength ();
1036                 }
1037
1038                 internal void ReleaseResources ()
1039                 {
1040                         output_stream.ReleaseResources (true);
1041                         output_stream = null;
1042                 }
1043         }
1044 }
1045