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