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