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