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