Merge pull request #1066 from esdrubal/bug19313
[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 //      Marek Habersack <mhabersack@novell.com>
9 //
10 // Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Text;
33 using System.Web.UI;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.IO;
37 using System.Web.Caching;
38 using System.Threading;
39 using System.Web.Util;
40 using System.Web.Configuration;
41 using System.Globalization;
42 using System.Security.Permissions;
43 using System.Web.Hosting;
44 using System.Web.SessionState;
45
46 #if NET_4_0
47 using System.Web.Routing;
48 #endif
49
50 namespace System.Web
51 {       
52         // CAS - no InheritanceDemand here as the class is sealed
53         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
54         public sealed partial class HttpResponse
55         {
56                 internal HttpWorkerRequest WorkerRequest;
57                 internal HttpResponseStream output_stream;
58                 internal bool buffer = true;
59
60                 ArrayList fileDependencies;
61                 
62                 HttpContext context;
63                 TextWriter writer;
64                 HttpCachePolicy cache_policy;
65                 Encoding encoding;
66                 HttpCookieCollection cookies;
67                 
68                 int status_code = 200;
69                 string status_description = "OK";
70
71                 string content_type = "text/html";
72                 string charset;
73                 bool charset_set;
74                 CachedRawResponse cached_response;
75                 string user_cache_control = "private";
76                 string redirect_location;
77                 string version_header;
78                 bool version_header_checked;
79                 
80                 //
81                 // Negative Content-Length means we auto-compute the size of content-length
82                 // can be overwritten with AppendHeader ("Content-Length", value)
83                 //
84                 long content_length = -1;
85
86                 //
87                 // The list of the headers that we will send back to the client, except
88                 // the headers that we compute here.
89                 //
90
91                 HttpHeaderCollection headers;
92                 bool headers_sent;
93                 NameValueCollection cached_headers;
94
95                 //
96                 // Transfer encoding state
97                 //
98                 string transfer_encoding;
99                 internal bool use_chunked;
100                 
101                 bool closed;
102                 bool completed;
103                 internal bool suppress_content;
104
105                 //
106                 // Session State
107                 //
108                 string app_path_mod;
109                 bool is_request_being_redirected;
110                 Encoding headerEncoding;
111
112                 internal HttpResponse ()
113                 {
114                         output_stream = new HttpResponseStream (this);
115                         writer = new HttpWriter (this);
116                 }
117
118                 public HttpResponse (TextWriter writer) : this ()
119                 {
120
121                         this.writer = writer;
122                 }
123
124                 internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
125                 {
126                         WorkerRequest = worker_request;
127                         this.context = context;
128
129 #if !TARGET_J2EE
130                         if (worker_request != null && worker_request.GetHttpVersion () == "HTTP/1.1") {
131                                 string gi = worker_request.GetServerVariable ("GATEWAY_INTERFACE");
132                                 use_chunked = (String.IsNullOrEmpty (gi) ||
133                                         !gi.StartsWith ("cgi", StringComparison.OrdinalIgnoreCase));
134                         } else {
135                                 use_chunked = false;
136                         }
137 #endif
138                         writer = new HttpWriter (this);
139                 }
140
141                 internal TextWriter SetTextWriter (TextWriter writer)
142                 {
143                         TextWriter prev = this.writer;
144                         this.writer = writer;
145                         return prev;
146                 }
147
148                 internal string VersionHeader {
149                         get {
150                                 if (!version_header_checked && version_header == null) {
151                                         version_header_checked = true;
152                                         HttpRuntimeSection config = HttpRuntime.Section;
153                                         if (config != null && config.EnableVersionHeader)
154                                                 version_header = Environment.Version.ToString (3);
155                                 }
156
157                                 return version_header;
158                         }
159                 }
160
161                 internal HttpContext Context {
162                         get { return context; }
163                         set { context = value; }
164                 }
165                         
166                 internal string[] FileDependencies {
167                         get {
168                                 if (fileDependencies == null || fileDependencies.Count == 0)
169                                         return new string[0] {};
170                                 return (string[]) fileDependencies.ToArray (typeof (string));
171                         }
172                 }
173                 
174                 ArrayList FileDependenciesArray {
175                         get {
176                                 if (fileDependencies == null)
177                                         fileDependencies = new ArrayList ();
178                                 return fileDependencies;
179                         }
180                 }
181                 
182                 public bool Buffer {
183                         get {
184                                 return buffer;
185                         }
186
187                         set {
188                                 buffer = value;
189                         }
190                 }
191
192                 public bool BufferOutput {
193                         get {
194                                 return buffer;
195                         }
196
197                         set {
198                                 buffer = value;
199                         }
200                 }
201
202                 //
203                 // Use the default from <globalization> section if the client has not set the encoding
204                 //
205                 public Encoding ContentEncoding {
206                         get {
207                                 if (encoding == null) {
208                                         if (context != null) {
209                                                 string client_content_type = context.Request.ContentType;
210                                                 string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
211                                                 if (parameter != null) {
212                                                         try {
213                                                                 // Do what the #1 web server does
214                                                                 encoding = Encoding.GetEncoding (parameter);
215                                                         } catch {
216                                                         }
217                                                 }
218                                         }
219                                         if (encoding == null)
220                                                 encoding = WebEncoding.ResponseEncoding;
221                                 }
222                                 return encoding;
223                         }
224
225                         set {
226                                 if (value == null)
227                                         throw new ArgumentException ("ContentEncoding can not be null");
228
229                                 encoding = value;
230                                 HttpWriter http_writer = writer as HttpWriter;
231                                 if (http_writer != null)
232                                         http_writer.SetEncoding (encoding);
233                         }
234                 }
235                 
236                 public string ContentType {
237                         get {
238                                 return content_type;
239                         }
240
241                         set {
242                                 content_type = value;
243                         }
244                 }
245
246                 public string Charset {
247                         get {
248                                 if (charset == null)
249                                         charset = ContentEncoding.WebName;
250                                 
251                                 return charset;
252                         }
253
254                         set {
255                                 charset_set = true;
256                                 charset = value;
257                         }
258                 }
259                 
260                 public HttpCookieCollection Cookies {
261                         get {
262                                 if (cookies == null)
263                                         cookies = new HttpCookieCollection (true, false);
264                                 return cookies;
265                         }
266                 }
267                 
268                 public int Expires {
269                         get {
270                                 if (cache_policy == null)
271                                         return 0;
272
273                                 return cache_policy.ExpireMinutes ();
274                         }
275
276                         set {
277                                 Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
278                         }
279                 }
280                 
281                 public DateTime ExpiresAbsolute {
282                         get {
283                                 return Cache.Expires;
284                         }
285
286                         set {
287                                 Cache.SetExpires (value);
288                         }
289                 }
290
291                 public Stream Filter {
292                         get {
293                                 if (WorkerRequest == null)
294                                         return null;
295
296                                 return output_stream.Filter;
297                         }
298
299                         set {
300                                 output_stream.Filter = value;
301                         }
302                 }
303
304                 public Encoding HeaderEncoding {
305                         get {
306                                 if (headerEncoding == null) {
307                                         GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
308
309                                         if (gs == null)
310                                                 headerEncoding = Encoding.UTF8;
311                                         else {
312                                                 headerEncoding = gs.ResponseHeaderEncoding;
313                                                 if (headerEncoding == Encoding.Unicode)
314                                                         throw new HttpException ("HeaderEncoding must not be Unicode");
315                                         }
316                                 }
317                                 return headerEncoding;
318                         }
319                         set {
320                                 if (headers_sent)
321                                         throw new HttpException ("headers have already been sent");
322                                 if (value == null)
323                                         throw new ArgumentNullException ("HeaderEncoding");
324                                 if (value == Encoding.Unicode)
325                                         throw new HttpException ("HeaderEncoding must not be Unicode");
326                                 headerEncoding = value;
327                         }
328                 }
329
330                 public NameValueCollection Headers {
331                         get {
332                                 if (headers == null)
333                                         headers = new HttpHeaderCollection ();
334
335                                 return headers;
336                         }
337                 }
338
339                 
340                 public bool IsClientConnected {
341                         get {
342                                 if (WorkerRequest == null)
343                                         return true; // yep that's true
344
345                                 return WorkerRequest.IsClientConnected ();
346                         }
347                 }
348
349                 public bool IsRequestBeingRedirected {
350                         get { return is_request_being_redirected; }
351                 }
352
353                 public TextWriter Output {
354                         get {
355                                 return writer;
356                         }
357 #if NET_4_0
358                         set { writer = value; }
359 #endif
360                 }
361
362                 public Stream OutputStream {
363                         get {
364                                 return output_stream;
365                         }
366                 }
367                 
368                 public string RedirectLocation {
369                         get {
370                                 return redirect_location;
371                         }
372
373                         set {
374                                 redirect_location = value;
375                         }
376                 }
377                 
378                 public string Status {
379                         get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
380
381                         set {
382                                 int p = value.IndexOf (' ');
383                                 if (p == -1)
384                                         throw new HttpException ("Invalid format for the Status property");
385
386                                 string s = value.Substring (0, p);
387                                 
388                                 if (!Int32.TryParse (s, out status_code))
389                                         throw new HttpException ("Invalid format for the Status property");
390                                 
391                                 status_description = value.Substring (p+1);
392                         }
393                 }
394
395                 // We ignore the two properties on Mono as they are for use with IIS7, but there is
396                 // no point in throwing PlatformNotSupportedException. We might find a use for them
397                 // some day.
398                 public int SubStatusCode {
399                         get;
400                         set;
401                 }
402
403                 public bool TrySkipIisCustomErrors {
404                         get;
405                         set;
406                 }
407                 
408                 public int StatusCode {
409                         get {
410                                 return status_code;
411                         }
412
413                         set {
414                                 if (headers_sent)
415                                         throw new HttpException ("headers have already been sent");
416                                 
417                                 status_code = value;
418                                 status_description = null;
419                         }
420                 }
421
422                 public string StatusDescription {
423                         get {
424                                 if (status_description == null)
425                                         status_description = HttpWorkerRequest.GetStatusDescription (status_code);
426
427                                 return status_description;
428                         }
429
430                         set {
431                                 if (headers_sent)
432                                         throw new HttpException ("headers have already been sent");
433                                 
434                                 status_description = value;
435                         }
436                 }
437                 
438                 public bool SuppressContent {
439                         get {
440                                 return suppress_content;
441                         }
442
443                         set {
444                                 suppress_content = value;
445                         }
446                 }
447
448                 [MonoTODO ("Not implemented")]
449                 public void AddCacheDependency (params CacheDependency[] dependencies)
450                 {
451                         throw new NotImplementedException ();
452                 }
453
454                 [MonoTODO ("Not implemented")]
455                 public void AddCacheItemDependencies (string[] cacheKeys)
456                 {
457                         throw new NotImplementedException ();
458                 }
459
460                 [MonoTODO("Currently does nothing")]
461                 public void AddCacheItemDependencies (ArrayList cacheKeys)
462                 {
463                         // TODO: talk to jackson about the cache
464                 }
465
466                 [MonoTODO("Currently does nothing")]
467                 public void AddCacheItemDependency (string cacheKey)
468                 {
469                         // TODO: talk to jackson about the cache
470                 }
471                 
472                 public void AddFileDependencies (ArrayList filenames)
473                 {
474                         if (filenames == null || filenames.Count == 0)
475                                 return;
476                         FileDependenciesArray.AddRange (filenames);
477                 }
478
479                 public void AddFileDependencies (string[] filenames)
480                 {
481                         if (filenames == null || filenames.Length == 0)
482                                 return;
483                         FileDependenciesArray.AddRange (filenames);
484                 }
485
486                 public void AddFileDependency (string filename)
487                 {
488                         if (filename == null || filename == String.Empty)
489                                 return;
490                         FileDependenciesArray.Add (filename);
491                 }
492
493                 public void AddHeader (string name, string value)
494                 {
495                         AppendHeader (name, value);
496                 }
497
498                 public void AppendCookie (HttpCookie cookie)
499                 {
500                         Cookies.Add (cookie);
501                 }
502
503                 //
504                 // AppendHeader:
505                 //    Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
506                 //
507                 //
508                 public void AppendHeader (string name, string value)
509                 {
510                         if (headers_sent)
511                                 throw new HttpException ("Headers have been already sent");
512 #if !TARGET_J2EE
513                         if (String.Compare (name, "content-length", StringComparison.OrdinalIgnoreCase) == 0){
514                                 content_length = (long) UInt64.Parse (value);
515                                 use_chunked = false;
516                                 return;
517                         }
518 #endif
519
520                         if (String.Compare (name, "content-type", StringComparison.OrdinalIgnoreCase) == 0){
521                                 ContentType = value;
522                                 return;
523                         }
524
525 #if !TARGET_J2EE
526                         if (String.Compare (name, "transfer-encoding", StringComparison.OrdinalIgnoreCase) == 0){
527                                 transfer_encoding = value;
528                                 use_chunked = false;
529                                 return;
530                         }
531 #endif
532
533                         if (String.Compare (name, "cache-control", StringComparison.OrdinalIgnoreCase) == 0){
534                                 user_cache_control = value;
535                                 return;
536                         }
537
538                         Headers.Add (name, value);
539                 }
540
541                 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
542                 public void AppendToLog (string param)
543                 {
544                         Console.Write ("System.Web: ");
545                         Console.WriteLine (param);
546                 }
547                 
548                 public string ApplyAppPathModifier (string virtualPath)
549                 {
550                         if (virtualPath == null || context == null)
551                                 return null;
552                 
553                         if (virtualPath.Length == 0)
554                                 return context.Request.RootVirtualDir;
555
556                         if (UrlUtils.IsRelativeUrl (virtualPath)) {
557                                 virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
558                         } else if (UrlUtils.IsRooted (virtualPath)) {
559                                 virtualPath = UrlUtils.Canonic (virtualPath);
560                         }
561
562                         bool cookieless = false;
563                         SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
564                         cookieless = SessionStateModule.IsCookieLess (context, config);
565
566                         if (!cookieless)
567                                 return virtualPath;
568
569                         if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
570                                 if (UrlUtils.HasSessionId (virtualPath))
571                                         virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
572                                 return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
573                         }
574                 
575                         return virtualPath;
576                 }
577
578                 public void BinaryWrite (byte [] buffer)
579                 {
580                         output_stream.Write (buffer, 0, buffer.Length);
581                 }
582
583                 internal void BinaryWrite (byte [] buffer, int start, int len)
584                 {
585                         output_stream.Write (buffer, start, len);
586                 }
587
588                 public void Clear ()
589                 {
590                         ClearContent ();
591                 }
592
593                 public void ClearContent ()
594                 {
595                         output_stream.Clear ();
596                         content_length = -1;
597                 }
598
599                 public void ClearHeaders ()
600                 {
601                         if (headers_sent)
602                                 throw new HttpException ("headers have been already sent");
603
604                         // Reset the special case headers.
605                         content_length = -1;
606                         content_type = "text/html";
607                         transfer_encoding = null;
608                         user_cache_control = "private";
609                         if (cache_policy != null)
610                                 cache_policy.Cacheability = HttpCacheability.Private;
611
612                         if (headers != null)
613                                 headers.Clear ();
614                 }
615
616                 internal bool HeadersSent {
617                         get {
618                                 return headers_sent;
619                         }
620                 }
621
622                 public void Close ()
623                 {
624                         if (closed)
625                                 return;
626                         if (WorkerRequest != null)
627                                 WorkerRequest.CloseConnection ();
628                         closed = true;
629                 }
630
631                 public void DisableKernelCache ()
632                 {
633                         // does nothing in Mono
634                 }
635                 
636                 public void End ()
637                 {
638                         if (context == null)
639                                 return;
640                         
641                         if (context.TimeoutPossible) {
642                                 Thread.CurrentThread.Abort (FlagEnd.Value);
643                         } else {
644                                 // If this is called from an async event, signal the completion
645                                 // but don't throw.
646                                 HttpApplication app_instance = context.ApplicationInstance;
647                                 if (app_instance != null)
648                                         app_instance.CompleteRequest ();
649                         }
650                 }
651
652                 // Generate:
653                 //   Content-Length
654                 //   Content-Type
655                 //   Transfer-Encoding (chunked)
656                 //   Cache-Control
657                 //   X-AspNet-Version
658                 void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush)
659                 {
660 #if !TARGET_J2EE
661                         //
662                         // Transfer-Encoding
663                         //
664                         if (use_chunked)
665                                 write_headers.Add ("Transfer-Encoding", "chunked");
666                         else if (transfer_encoding != null)
667                                 write_headers.Add ("Transfer-Encoding", transfer_encoding);
668 #endif
669                         if (redirect_location != null)
670                                 write_headers.Add ("Location", redirect_location);
671                         
672 #if !TARGET_J2EE
673                         string vh = VersionHeader;
674                         if (vh != null)
675                                 write_headers.Add ("X-AspNet-Version", vh);
676
677                         //
678                         // If Content-Length is set.
679                         //
680                         if (content_length >= 0) {
681                                 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
682                                                    content_length.ToString (Helpers.InvariantCulture));
683                         } else if (BufferOutput) {
684                                 if (final_flush) {                                      
685                                         //
686                                         // If we are buffering and this is the last flush, not a middle-flush,
687                                         // we know the content-length.
688                                         //
689                                         content_length = output_stream.total;
690                                         write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
691                                                            content_length.ToString (Helpers.InvariantCulture));
692                                 } else {
693                                         //
694                                         // We are buffering, and this is a flush in the middle.
695                                         // If we are not chunked, we need to set "Connection: close".
696                                         //
697                                         if (use_chunked){
698                                                 write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
699                                         }
700                                 }
701                         } else {
702                                 //
703                                 // If the content-length is not set, and we are not buffering, we must
704                                 // close at the end.
705                                 //
706                                 if (use_chunked){
707                                         write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
708                                 }
709                         }
710 #endif
711
712                         //
713                         // Cache Control, the cache policy takes precedence over the cache_control property.
714                         //
715                         if (cache_policy != null)
716                                 cache_policy.SetHeaders (this, headers);
717                         else
718                                 write_headers.Add ("Cache-Control", CacheControl);
719                         
720                         //
721                         // Content-Type
722                         //
723                         if (content_type != null){
724                                 string header = content_type;
725
726                                 if (charset_set || header == "text/plain" || header == "text/html") {
727                                         if (header.IndexOf ("charset=") == -1 && !string.IsNullOrEmpty (charset)) {
728                                                 header += "; charset=" + charset;
729                                         }
730                                 }
731                                 
732                                 write_headers.Add ("Content-Type", header);
733                         }
734
735                         if (cookies != null && cookies.Count != 0){
736                                 int n = cookies.Count;
737                                 for (int i = 0; i < n; i++)
738                                         write_headers.Add ("Set-Cookie", cookies.Get (i).GetCookieHeaderValue ());
739 #if TARGET_J2EE
740                                 // For J2EE Portal support emulate cookies by storing them in the session.
741                                 context.Request.SetSessionCookiesForPortal (cookies);
742 #endif
743                         }
744                 }
745
746                 internal void WriteHeaders (bool final_flush)
747                 {
748                         if (headers_sent)
749                                 return;
750
751                         //
752                         // Flush
753                         //
754                         if (context != null) {
755                                 HttpApplication app_instance = context.ApplicationInstance;
756                                 if (app_instance != null)
757                                         app_instance.TriggerPreSendRequestHeaders ();
758                         }
759
760                         headers_sent = true;
761
762                         if (cached_response != null)
763                                 cached_response.SetHeaders (headers);
764
765                         // If this page is cached use the cached headers
766                         // instead of the standard headers      
767                         NameValueCollection write_headers;
768                         if (cached_headers != null)
769                                 write_headers = cached_headers;
770                         else {
771                                 write_headers = Headers;
772                                 AddHeadersNoCache (write_headers, final_flush);
773                         }
774                         
775                         if (WorkerRequest != null)
776                                 WorkerRequest.SendStatus (status_code, StatusDescription);
777
778                         if (WorkerRequest != null) {
779                                 string header_name;
780                                 string[] values;
781                                 int header_index;
782                                 
783                                 for (int i = 0; i < write_headers.Count; i++) {
784                                         header_name = write_headers.GetKey (i);
785                                         header_index = HttpWorkerRequest.GetKnownResponseHeaderIndex (header_name);
786                                         values = write_headers.GetValues (i);
787                                         if (values == null)
788                                                 continue;
789                                         
790                                         foreach (string val in values) {
791                                                 if (header_index > -1)
792                                                         WorkerRequest.SendKnownResponseHeader (header_index, val);
793                                                 else
794                                                         WorkerRequest.SendUnknownResponseHeader (header_name, val);
795                                         }
796                                 }
797                         }
798                 }
799
800                 internal void DoFilter (bool close)
801                 {
802                         if (output_stream.HaveFilter && context != null && context.Error == null)
803                                 output_stream.ApplyFilter (close);
804                 }
805
806                 internal void Flush (bool final_flush)
807                 {
808                         if (completed)
809                                 throw new HttpException ("Server cannot flush a completed response");
810                         
811                         DoFilter (final_flush);
812                         if (!headers_sent){
813                                 if (final_flush || status_code != 200)
814                                         use_chunked = false;
815                         }
816
817                         bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
818                         if (suppress_content || head) {
819                                 if (!headers_sent)
820                                         WriteHeaders (true);
821                                 output_stream.Clear ();
822                                 if (WorkerRequest != null)
823                                         output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
824                                 completed = true;
825                                 return;
826                         }
827                         completed = final_flush;
828                         
829                         if (!headers_sent)
830                                 WriteHeaders (final_flush);
831
832                         if (context != null) {
833                                 HttpApplication app_instance = context.ApplicationInstance;
834                                 if (app_instance != null)
835                                         app_instance.TriggerPreSendRequestContent ();
836                         }
837
838                         if (IsCached)
839                                 cached_response.SetData (output_stream.GetData ());
840
841                         if (WorkerRequest != null)
842                                 output_stream.Flush (WorkerRequest, final_flush);
843                 }
844
845                 public void Flush ()
846                 {
847                         Flush (false);
848                 }
849
850                 public void Pics (string value)
851                 {
852                         AppendHeader ("PICS-Label", value);
853                 }
854
855                 void Redirect (string url, bool endResponse, int code)
856                 {
857                         if (url == null)
858                                 throw new ArgumentNullException ("url");
859                         
860                         if (headers_sent)
861                                 throw new HttpException ("Headers have already been sent");
862
863                         if (url.IndexOf ('\n') != -1)
864                                 throw new ArgumentException ("Redirect URI cannot contain newline characters.", "url");
865                         
866                         is_request_being_redirected = true;
867                         ClearHeaders ();
868                         ClearContent ();
869                         
870                         StatusCode = code;
871                         url = ApplyAppPathModifier (url);
872
873                         bool isFullyQualified;
874                         if (StrUtils.StartsWith (url, "http:", true) ||
875                             StrUtils.StartsWith (url, "https:", true) ||
876                             StrUtils.StartsWith (url, "file:", true) ||
877                             StrUtils.StartsWith (url, "ftp:", true))
878                                 isFullyQualified = true;
879                         else
880                                 isFullyQualified = false;
881
882                         if (!isFullyQualified) {
883                                 HttpRuntimeSection config = HttpRuntime.Section;
884                                 if (config != null && config.UseFullyQualifiedRedirectUrl) {
885                                         var ub = new UriBuilder (context.Request.Url);
886                                         int qpos = url.IndexOf ('?');
887                                         if (qpos == -1) {
888                                                 ub.Path = url;
889                                                 ub.Query = null;
890                                         } else {
891                                                 ub.Path = url.Substring (0, qpos);
892                                                 ub.Query = url.Substring (qpos + 1);
893                                         }
894                                         ub.Fragment = null;
895                                         ub.Password = null;
896                                         ub.UserName = null;
897                                         url = ub.Uri.ToString ();
898                                 }
899                         }
900                         
901                         redirect_location = url;
902
903                         // Text for browsers that can't handle location header
904                         Write ("<html><head><title>Object moved</title></head><body>\r\n");
905                         Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
906                         Write ("</body><html>\r\n");
907                         
908                         if (endResponse)
909                                 End ();
910                         is_request_being_redirected = false;
911                 }
912                 
913                 public void Redirect (string url)
914                 {
915                         Redirect (url, true);
916                 }
917
918                 public void Redirect (string url, bool endResponse)
919                 {
920                         Redirect (url, endResponse, 302);
921                 }
922 #if NET_4_0
923                 public void RedirectPermanent (string url)
924                 {
925                         RedirectPermanent (url, true);
926                 }
927
928                 public void RedirectPermanent (string url, bool endResponse)
929                 {
930                         Redirect (url, endResponse, 301);
931                 }
932
933                 public void RedirectToRoute (object routeValues)
934                 {
935                         RedirectToRoute ("RedirectToRoute", null, new RouteValueDictionary (routeValues), 302, true);
936                 }
937
938                 public void RedirectToRoute (RouteValueDictionary routeValues)
939                 {
940                         RedirectToRoute ("RedirectToRoute", null, routeValues, 302, true);
941                 }
942
943                 public void RedirectToRoute (string routeName)
944                 {
945                         RedirectToRoute ("RedirectToRoute", routeName, null, 302, true);
946                 }
947
948                 public void RedirectToRoute (string routeName, object routeValues)
949                 {
950                         RedirectToRoute ("RedirectToRoute", routeName, new RouteValueDictionary (routeValues), 302, true);
951                 }
952
953                 public void RedirectToRoute (string routeName, RouteValueDictionary routeValues)
954                 {
955                         RedirectToRoute ("RedirectToRoute", routeName, routeValues, 302, true);
956                 }
957
958                 public void RedirectToRoutePermanent (object routeValues)
959                 {
960                         RedirectToRoute ("RedirectToRoutePermanent", null, new RouteValueDictionary (routeValues), 301, false);
961                 }
962
963                 public void RedirectToRoutePermanent (RouteValueDictionary routeValues)
964                 {
965                         RedirectToRoute ("RedirectToRoutePermanent", null, routeValues, 301, false);
966                 }
967
968                 public void RedirectToRoutePermanent (string routeName)
969                 {
970                         RedirectToRoute ("RedirectToRoutePermanent", routeName, null, 301, false);
971                 }
972
973                 public void RedirectToRoutePermanent (string routeName, object routeValues)
974                 {
975                         RedirectToRoute ("RedirectToRoutePermanent", routeName, new RouteValueDictionary (routeValues), 301, false);
976                 }               
977
978                 public void RedirectToRoutePermanent (string routeName, RouteValueDictionary routeValues)
979                 {
980                         RedirectToRoute ("RedirectToRoutePermanent", routeName, routeValues, 301, false);
981                 }
982                 
983                 void RedirectToRoute (string callerName, string routeName, RouteValueDictionary routeValues, int redirectCode, bool endResponse)
984                 {
985                         HttpContext ctx = context ?? HttpContext.Current;
986                         HttpRequest req = ctx != null ? ctx.Request : null;
987                         
988                         if (req == null)
989                                 // Let's emulate .NET
990                                 throw new NullReferenceException ();
991                         
992                         VirtualPathData vpd = RouteTable.Routes.GetVirtualPath (req.RequestContext, routeName, routeValues);
993                         string redirectUrl = vpd != null ? vpd.VirtualPath : null;
994                         if (String.IsNullOrEmpty (redirectUrl))
995                                 throw new InvalidOperationException ("No matching route found for RedirectToRoute");
996
997                         Redirect (redirectUrl, true, redirectCode);
998                 }
999
1000                 public static void RemoveOutputCacheItem (string path, string providerName)
1001                 {
1002                         if (path == null)
1003                                 throw new ArgumentNullException ("path");
1004
1005                         if (path.Length > 0 && path [0] != '/')
1006                                 throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem: '" + path + "'. An absolute virtual path is expected");
1007
1008                         OutputCache.RemoveFromProvider (path, providerName);
1009                 }
1010 #endif
1011                 public static void RemoveOutputCacheItem (string path)
1012                 {
1013                         if (path == null)
1014                                 throw new ArgumentNullException ("path");
1015
1016                         if (path.Length == 0)
1017                                 return;
1018
1019                         if (path [0] != '/')
1020                                 throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
1021
1022 #if NET_4_0
1023                         RemoveOutputCacheItem (path, OutputCache.DefaultProviderName);
1024 #else
1025                         HttpContext context = HttpContext.Current;
1026                         HttpApplication app_instance = context != null ? context.ApplicationInstance : null;
1027                         HttpModuleCollection modules = app_instance != null ? app_instance.Modules : null;
1028                         OutputCacheModule ocm = modules != null ? modules.Get ("OutputCache") as OutputCacheModule : null;
1029                         OutputCacheProvider internalProvider = ocm != null ? ocm.InternalProvider : null;
1030                         if (internalProvider == null)
1031                                 return;
1032
1033                         internalProvider.Remove (path);
1034 #endif
1035                 }
1036
1037                 public void SetCookie (HttpCookie cookie)
1038                 {
1039                         AppendCookie (cookie);
1040                 }
1041
1042                 public void Write (char ch)
1043                 {
1044                         TextWriter writer = Output;
1045 #if NET_4_0
1046                         // Emulating .NET
1047                         if (writer == null)
1048                                 throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
1049 #endif
1050                         writer.Write (ch);
1051                 }
1052
1053                 public void Write (object obj)
1054                 {
1055                         TextWriter writer = Output;
1056 #if NET_4_0
1057                         // Emulating .NET
1058                         if (writer == null)
1059                                 throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
1060 #endif
1061                         if (obj == null)
1062                                 return;
1063                         
1064                         writer.Write (obj.ToString ());
1065                 }
1066                 
1067                 public void Write (string s)
1068                 {
1069                         TextWriter writer = Output;
1070 #if NET_4_0
1071                         // Emulating .NET
1072                         if (writer == null)
1073                                 throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
1074 #endif
1075                         writer.Write (s);
1076                 }
1077                 
1078                 public void Write (char [] buffer, int index, int count)
1079                 {
1080                         TextWriter writer = Output;
1081 #if NET_4_0
1082                         // Emulating .NET
1083                         if (writer == null)
1084                                 throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
1085 #endif
1086                         writer.Write (buffer, index, count);
1087                 }
1088
1089                 bool IsFileSystemDirSeparator (char ch)
1090                 {
1091                         return ch == '\\' || ch == '/';
1092                 }
1093                 
1094                 string GetNormalizedFileName (string fn)
1095                 {
1096                         if (String.IsNullOrEmpty (fn))
1097                                 return fn;
1098
1099                         // On Linux we don't change \ to / since filenames with \ are valid. We also
1100                         // don't remove drive: designator for the same reason.
1101                         int len = fn.Length;
1102                         if (len >= 3 && fn [1] == ':' && IsFileSystemDirSeparator (fn [2]))
1103                                 return Path.GetFullPath (fn); // drive-qualified absolute file path
1104
1105                         bool startsWithDirSeparator = IsFileSystemDirSeparator (fn [0]);
1106                         if (len >= 2 && startsWithDirSeparator && IsFileSystemDirSeparator (fn [1]))
1107                                 return Path.GetFullPath (fn); // UNC path
1108
1109                         if (!startsWithDirSeparator) {
1110                                 HttpContext ctx = context ?? HttpContext.Current;
1111                                 HttpRequest req = ctx != null ? ctx.Request : null;
1112                                 
1113                                 if (req != null)
1114                                         return req.MapPath (fn);
1115                         }
1116                         
1117                         return fn; // Or should we rather throw?
1118                 }
1119                 
1120                 internal void WriteFile (FileStream fs, long offset, long size)
1121                 {
1122                         byte [] buffer = new byte [32*1024];
1123
1124                         if (offset != 0)
1125                                 fs.Position = offset;
1126
1127                         long remain = size;
1128                         int n;
1129                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
1130                                 remain -= n;
1131                                 output_stream.Write (buffer, 0, n);
1132                         }
1133                 }
1134                 
1135                 public void WriteFile (string filename)
1136                 {
1137                         WriteFile (filename, false);
1138                 }
1139
1140                 public void WriteFile (string filename, bool readIntoMemory)
1141                 {
1142                         if (filename == null)
1143                                 throw new ArgumentNullException ("filename");
1144
1145                         string fn = GetNormalizedFileName (filename);
1146                         if (readIntoMemory){
1147                                 using (FileStream fs = File.OpenRead (fn))
1148                                         WriteFile (fs, 0, fs.Length);
1149                         } else {
1150                                 FileInfo fi = new FileInfo (fn);
1151                                 output_stream.WriteFile (fn, 0, fi.Length);
1152                         }
1153                         if (buffer)
1154                                 return;
1155
1156                         output_stream.ApplyFilter (false);
1157                         Flush ();
1158                 }
1159
1160 #if TARGET_JVM
1161                 public void WriteFile (IntPtr fileHandle, long offset, long size) {
1162                         throw new PlatformNotSupportedException("IntPtr not supported");
1163                 }
1164 #else
1165                 public void WriteFile (IntPtr fileHandle, long offset, long size)
1166                 {
1167                         if (offset < 0)
1168                                 throw new ArgumentNullException ("offset can not be negative");
1169                         if (size < 0)
1170                                 throw new ArgumentNullException ("size can not be negative");
1171
1172                         if (size == 0)
1173                                 return;
1174
1175                         // Note: this .ctor will throw a SecurityException if the caller 
1176                         // doesn't have the UnmanagedCode permission
1177                         using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
1178                                 WriteFile (fs, offset, size);
1179
1180                         if (buffer)
1181                                 return;
1182                         output_stream.ApplyFilter (false);
1183                         Flush ();
1184                 }
1185 #endif
1186
1187                 public void WriteFile (string filename, long offset, long size)
1188                 {
1189                         if (filename == null)
1190                                 throw new ArgumentNullException ("filename");
1191                         if (offset < 0)
1192                                 throw new ArgumentNullException ("offset can not be negative");
1193                         if (size < 0)
1194                                 throw new ArgumentNullException ("size can not be negative");
1195
1196                         if (size == 0)
1197                                 return;
1198                         
1199                         FileStream fs = File.OpenRead (filename);
1200                         WriteFile (fs, offset, size);
1201
1202                         if (buffer)
1203                                 return;
1204
1205                         output_stream.ApplyFilter (false);
1206                         Flush ();
1207                 }
1208
1209                 public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
1210                 {
1211                         // Emulation of .NET behavior
1212                         if (callback == null)
1213                                 throw new NullReferenceException ();
1214
1215                         object target = callback.Target;
1216                         if (target != null && target.GetType () == typeof (Control))
1217                                 throw new ArgumentException ("callback");
1218
1219                         string s = callback (context);
1220                         if (!IsCached) {
1221                                 Write (s);
1222                                 return;
1223                         }
1224
1225                         Cache.Cacheability = HttpCacheability.Server;
1226                         Flush ();
1227                         if (WorkerRequest == null)
1228                                 Write (s); // better this than nothing
1229                         else {
1230                                 byte[] bytes = WebEncoding.ResponseEncoding.GetBytes (s);
1231                                 WorkerRequest.SendResponseFromMemory (bytes, bytes.Length);
1232                         }
1233                         
1234                         cached_response.SetData (callback);
1235                 }
1236
1237                 //
1238                 // Like WriteFile, but never buffers, so we manually Flush here
1239                 //
1240                 public void TransmitFile (string filename) 
1241                 {
1242                         if (filename == null)
1243                                 throw new ArgumentNullException ("filename");
1244
1245                         TransmitFile (filename, false);
1246                 }
1247
1248                 internal void TransmitFile (string filename, bool final_flush)
1249                 {
1250                         FileInfo fi = new FileInfo (filename);
1251                         using (Stream s = fi.OpenRead ()); // Just check if we can read.
1252                         output_stream.WriteFile (filename, 0, fi.Length);
1253                         output_stream.ApplyFilter (final_flush);
1254                         Flush (final_flush);
1255                 }
1256
1257                 public void TransmitFile (string filename, long offset, long length)
1258                 {
1259                         output_stream.WriteFile (filename, offset, length);
1260                         output_stream.ApplyFilter (false);
1261                         Flush (false);
1262                 }
1263                 
1264                 internal void TransmitFile (VirtualFile vf)
1265                 {
1266                         TransmitFile (vf, false);
1267                 }
1268
1269                 const int bufLen = 65535;
1270                 internal void TransmitFile (VirtualFile vf, bool final_flush)
1271                 {
1272                         if (vf == null)
1273                                 throw new ArgumentNullException ("vf");
1274
1275                         if (vf is DefaultVirtualFile) {
1276                                 TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
1277                                 return;
1278                         }
1279                         
1280                         byte[] buf = new byte [bufLen];
1281                         using (Stream s = vf.Open ()) {
1282                                 int readBytes;
1283                                 while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
1284                                         output_stream.Write (buf, 0, readBytes);
1285                                         output_stream.ApplyFilter (final_flush);
1286                                         Flush (false);
1287                                 }
1288                                 if (final_flush)
1289                                         Flush (true);
1290                         }
1291                 }
1292                 
1293 #region Session state support
1294                 internal void SetAppPathModifier (string app_modifier)
1295                 {
1296                         app_path_mod = app_modifier;
1297                 }
1298 #endregion
1299                 
1300 #region Cache Support
1301                 internal void SetCachedHeaders (NameValueCollection headers)
1302                 {
1303                         cached_headers = headers;
1304                         
1305                 }
1306
1307                 internal bool IsCached {
1308                         get { return cached_response != null; }
1309                         set {
1310                                 if (value)
1311                                         cached_response = new CachedRawResponse (cache_policy);
1312                                 else
1313                                         cached_response = null;
1314                         }
1315                 }
1316
1317                 public HttpCachePolicy Cache {
1318                         get {
1319                                 if (cache_policy == null)
1320                                         cache_policy = new HttpCachePolicy ();
1321                                 
1322                                 return cache_policy;
1323                         }
1324                 }               
1325
1326                 internal CachedRawResponse GetCachedResponse ()
1327                 {
1328                         if (cached_response != null) {
1329                                 cached_response.StatusCode = StatusCode;
1330                                 cached_response.StatusDescription = StatusDescription;
1331                         }
1332                         
1333                         return cached_response;
1334                 }
1335
1336                 //
1337                 // This is one of the old ASP compatibility methods, the real cache
1338                 // control is in the Cache property, and this is a second class citizen
1339                 //
1340                 public string CacheControl {
1341                         set {
1342                                 if (value == null || value == "") {
1343                                         Cache.SetCacheability (HttpCacheability.NoCache);
1344                                         user_cache_control = null;
1345                                 } else if (String.Compare (value, "public", true, Helpers.InvariantCulture) == 0) {
1346                                         Cache.SetCacheability (HttpCacheability.Public);
1347                                         user_cache_control = "public";
1348                                 } else if (String.Compare (value, "private", true, Helpers.InvariantCulture) == 0) {
1349                                         Cache.SetCacheability (HttpCacheability.Private);
1350                                         user_cache_control = "private";
1351                                 } else if (String.Compare (value, "no-cache", true, Helpers.InvariantCulture) == 0) {
1352                                         Cache.SetCacheability (HttpCacheability.NoCache);
1353                                         user_cache_control = "no-cache";
1354                                 } else
1355                                         throw new ArgumentException ("CacheControl property only allows `public', " +
1356                                                                      "`private' or no-cache, for different uses, use " +
1357                                                                      "Response.AppendHeader");
1358                         }
1359
1360                         get { return (user_cache_control != null) ? user_cache_control : "private"; }
1361                 }
1362 #endregion
1363
1364                 internal int GetOutputByteCount ()
1365                 {
1366                         return output_stream.GetTotalLength ();
1367                 }
1368
1369                 internal void ReleaseResources ()
1370                 {
1371                         if (output_stream != null)
1372                                 output_stream.ReleaseResources (true);
1373                         if (completed)
1374                                 return;
1375                         
1376                         Close ();
1377                         completed = true;
1378                 }
1379         }
1380
1381 #if TARGET_J2EE
1382         public 
1383 #endif  
1384         static class FlagEnd
1385         {
1386                 public static readonly object Value = new object ();
1387         }
1388 }
1389