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